diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a9d3d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,178 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# 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/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Azure Emulator +efc/ +rfc/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs diff --git a/CE.png b/CE.png new file mode 100644 index 0000000..2b19fbf Binary files /dev/null and b/CE.png differ diff --git a/CEInfo.h b/CEInfo.h new file mode 100644 index 0000000..e0080e2 --- /dev/null +++ b/CEInfo.h @@ -0,0 +1,4 @@ +#pragma once + +#define CE_DETECTION_1 L"ADDRESSES.FIRST" +#define CE_DETECTION_2 L"MEMORY.FIRST" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 2071b23..0000000 --- a/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) - -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/Main.cpp b/Main.cpp new file mode 100644 index 0000000..3ee645f --- /dev/null +++ b/Main.cpp @@ -0,0 +1,157 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// 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. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + + +#include "stdafx.h" +#include "ReadDirectoryChanges.h" +#include "CEInfo.h" + +LPCWSTR ExplainAction( DWORD dwAction ); +bool TryGetKeyboardInput( HANDLE hStdIn, bool &bTerminate, char* buf ); + + +// +// When the application starts, it immediately starts monitoring your home +// directory, including children, as well as C:\, not including children. +// The application exits when you hit Esc. +// You can add a directory to the monitoring list by typing the directory +// name and hitting Enter. Notifications will pause while you type. +// + +int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) +{ + const DWORD dwNotificationFlags = + FILE_NOTIFY_CHANGE_LAST_WRITE + | FILE_NOTIFY_CHANGE_CREATION + | FILE_NOTIFY_CHANGE_FILE_NAME; + + // Create the monitor and add two directories. + CReadDirectoryChanges changes; + changes.AddDirectory(_tgetenv(_T("USERPROFILE")), true, dwNotificationFlags); + changes.AddDirectory(_T("C:\\"), false, dwNotificationFlags); + + HANDLE hStdIn = ::GetStdHandle(STD_INPUT_HANDLE); + const HANDLE handles[] = { hStdIn, changes.GetWaitHandle() }; + + char buf[MAX_PATH]; + bool bTerminate = false; + + while (!bTerminate) + { + DWORD rc = ::WaitForMultipleObjectsEx(_countof(handles), handles, false, INFINITE, true); + switch (rc) + { + case WAIT_OBJECT_0 + 0: + // hStdIn was signaled. This can happen due to mouse input, focus change, + // Shift keys, and more. Delegate to TryGetKeyboardInput(). + // TryGetKeyboardInput sets bTerminate to true if the user hits Esc. + if (TryGetKeyboardInput(hStdIn, bTerminate, buf)) + { + std::string strbuf(buf); + std::wstring wstrbuf(strbuf.begin(), strbuf.end()); + changes.AddDirectory(wstrbuf.c_str(), false, dwNotificationFlags); + } + + break; + case WAIT_OBJECT_0 + 1: + // We've received a notification in the queue. + { + DWORD dwAction; + std::wstring wstrFilename; + if (changes.CheckOverflow()) + wprintf(L"Queue overflowed.\n"); + else + { + changes.Pop(dwAction, wstrFilename); + if (wstrFilename.find(CE_DETECTION_1) != std::wstring::npos || + wstrFilename.find(CE_DETECTION_2) != std::wstring::npos + ) + { + wprintf(L"Find CE:%s %s\n", ExplainAction(dwAction), wstrFilename.c_str()); + } + + } + } + break; + case WAIT_IO_COMPLETION: + // Nothing to do. + break; + } + } + + // Just for sample purposes. The destructor will + // call Terminate() automatically. + changes.Terminate(); + + return EXIT_SUCCESS; +} + +LPCWSTR ExplainAction( DWORD dwAction ) +{ + switch (dwAction) + { + case FILE_ACTION_ADDED : + return L"Added"; + case FILE_ACTION_REMOVED : + return L"Deleted"; + case FILE_ACTION_MODIFIED : + return L"Modified"; + case FILE_ACTION_RENAMED_OLD_NAME : + return L"Renamed From"; + case FILE_ACTION_RENAMED_NEW_NAME : + return L"Renamed To"; + default: + return L"BAD DATA"; + } +} + +bool TryGetKeyboardInput( HANDLE hStdIn, bool &bTerminate, char* buf ) +{ + DWORD dwNumberOfEventsRead=0; + INPUT_RECORD rec = {0}; + + if (!::PeekConsoleInput(hStdIn, &rec, 1, &dwNumberOfEventsRead)) + return false; + + if (rec.EventType == KEY_EVENT) + { + if (rec.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE) + bTerminate = true; + else if (rec.Event.KeyEvent.wVirtualKeyCode > VK_HELP) + { + if (!gets_s(buf, MAX_PATH)) // End of file, usually Ctrl-Z + bTerminate = true; + else + return true; + } + } + + ::FlushConsoleInputBuffer(hStdIn); + + return false; +} diff --git a/README.md b/README.md index a4ef83e..ed11c71 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,23 @@ -# Detection-CheatEngine +## Detection-CheatEngine -Using ReadDirectoryChangesW to detect CheatEngine \ No newline at end of file +![image](CE.png) + +## Principle +[Understanding ReadDirectoryChangesW]( +http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html) + +## Expansion +You can add your detection vector easily in [CEInfo.h](https://github.com/gmh5225/Detection-CheatEngine/blob/master/CEInfo.h) +```C++ +#pragma once + +#define CE_DETECTION_1 L"ADDRESSES.FIRST" +#define CE_DETECTION_2 L"MEMORY.FIRST" +``` + +## Compile +- Visual Studio 2022 +- llvm-msvc [[link]](https://github.com/NewWorldComingSoon/llvm-msvc-build) + +## Some discussions on UnknownCheats +https://www.unknowncheats.me/forum/general-programming-and-reversing/502279-using-readdirectorychangesw-detect-cheatengine.html diff --git a/ReadDirectoryChanges.cpp b/ReadDirectoryChanges.cpp new file mode 100644 index 0000000..ed5c6c4 --- /dev/null +++ b/ReadDirectoryChanges.cpp @@ -0,0 +1,113 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// 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. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + +#include "stdafx.h" +#include "ReadDirectoryChanges.h" +#include "ReadDirectoryChangesPrivate.h" + +using namespace ReadDirectoryChangesPrivate; + +/////////////////////////////////////////////////////////////////////////// +// CReadDirectoryChanges + +CReadDirectoryChanges::CReadDirectoryChanges(int nMaxCount) + : m_Notifications(nMaxCount) +{ + m_hThread = NULL; + m_dwThreadId= 0; + m_pServer = new CReadChangesServer(this); +} + +CReadDirectoryChanges::~CReadDirectoryChanges() +{ + Terminate(); + delete m_pServer; +} + +void CReadDirectoryChanges::Init() +{ + // + // Kick off the worker thread, which will be + // managed by CReadChangesServer. + // + m_hThread = (HANDLE)_beginthreadex(NULL, + 0, + CReadChangesServer::ThreadStartProc, + m_pServer, + 0, + &m_dwThreadId + ); +} + +void CReadDirectoryChanges::Terminate() +{ + if (m_hThread) + { + ::QueueUserAPC(CReadChangesServer::TerminateProc, m_hThread, (ULONG_PTR)m_pServer); + ::WaitForSingleObjectEx(m_hThread, 10000, true); + ::CloseHandle(m_hThread); + + m_hThread = NULL; + m_dwThreadId = 0; + } +} + +void CReadDirectoryChanges::AddDirectory( LPCTSTR szDirectory, BOOL bWatchSubtree, DWORD dwNotifyFilter, DWORD dwBufferSize ) +{ + if (!m_hThread) + Init(); + + CReadChangesRequest* pRequest = new CReadChangesRequest(m_pServer, szDirectory, bWatchSubtree, dwNotifyFilter, dwBufferSize); + QueueUserAPC(CReadChangesServer::AddDirectoryProc, m_hThread, (ULONG_PTR)pRequest); +} + +void CReadDirectoryChanges::Push(DWORD dwAction, std::wstring& wstrFilename) +{ + auto item = TDirectoryChangeNotification(dwAction, wstrFilename); + m_Notifications.push(item); +} + +bool CReadDirectoryChanges::Pop(DWORD& dwAction, std::wstring& wstrFilename) +{ + TDirectoryChangeNotification pair; + if (!m_Notifications.pop(pair)) + return false; + + dwAction = pair.first; + wstrFilename = pair.second; + + return true; +} + +bool CReadDirectoryChanges::CheckOverflow() +{ + bool b = m_Notifications.overflow(); + if (b) + m_Notifications.clear(); + return b; +} diff --git a/ReadDirectoryChanges.h b/ReadDirectoryChanges.h new file mode 100644 index 0000000..f4a4b61 --- /dev/null +++ b/ReadDirectoryChanges.h @@ -0,0 +1,142 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// 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. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + + +#pragma once + +#include "ThreadSafeQueue.h" + +typedef pair TDirectoryChangeNotification; + +namespace ReadDirectoryChangesPrivate +{ + class CReadChangesServer; +} + +/////////////////////////////////////////////////////////////////////////// + + +/// +/// Track changes to filesystem directories and report them +/// to the caller via a thread-safe queue. +/// +/// +/// +/// This sample code is based on my blog entry titled, "Understanding ReadDirectoryChangesW" +/// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +/// +/// All functions in CReadDirectoryChangesServer run in +/// the context of the calling thread. +/// +/// +/// CReadDirectoryChanges changes; +/// changes.AddDirectory(_T("C:\\"), false, dwNotificationFlags); +/// +/// const HANDLE handles[] = { hStopEvent, changes.GetWaitHandle() }; +/// +/// while (!bTerminate) +/// { +/// ::MsgWaitForMultipleObjectsEx( +/// _countof(handles), +/// handles, +/// INFINITE, +/// QS_ALLINPUT, +/// MWMO_INPUTAVAILABLE | MWMO_ALERTABLE); +/// switch (rc) +/// { +/// case WAIT_OBJECT_0 + 0: +/// bTerminate = true; +/// break; +/// case WAIT_OBJECT_0 + 1: +/// // We've received a notification in the queue. +/// { +/// DWORD dwAction; +/// CStringW wstrFilename; +/// changes.Pop(dwAction, wstrFilename); +/// wprintf(L"%s %s\n", ExplainAction(dwAction), wstrFilename); +/// } +/// break; +/// case WAIT_OBJECT_0 + _countof(handles): +/// // Get and dispatch message +/// break; +/// case WAIT_IO_COMPLETION: +/// // APC complete.No action needed. +/// break; +/// } +/// } +/// +/// +class CReadDirectoryChanges +{ +public: + CReadDirectoryChanges(int nMaxChanges=1000); + ~CReadDirectoryChanges(); + + void Init(); + void Terminate(); + + /// + /// Add a new directory to be monitored. + /// + /// Directory to monitor. + /// True to also monitor subdirectories. + /// The types of file system events to monitor, such as FILE_NOTIFY_CHANGE_ATTRIBUTES. + /// The size of the buffer used for overlapped I/O. + /// + /// + /// This function will make an APC call to the worker thread to issue a new + /// ReadDirectoryChangesW call for the given directory with the given flags. + /// + /// + void AddDirectory( LPCTSTR wszDirectory, BOOL bWatchSubtree, DWORD dwNotifyFilter, DWORD dwBufferSize=16384 ); + + /// + /// Return a handle for the Win32 Wait... functions that will be + /// signaled when there is a queue entry. + /// + HANDLE GetWaitHandle() { return m_Notifications.GetWaitHandle(); } + + bool Pop(DWORD& dwAction, std::wstring& wstrFilename); + + // "Push" is for usage by ReadChangesRequest. Not intended for external usage. + void Push(DWORD dwAction, std::wstring& wstrFilename); + + // Check if the queue overflowed. If so, clear it and return true. + bool CheckOverflow(); + + unsigned int GetThreadId() { return m_dwThreadId; } + +protected: + ReadDirectoryChangesPrivate::CReadChangesServer* m_pServer; + + HANDLE m_hThread; + + unsigned int m_dwThreadId; + + CThreadSafeQueue m_Notifications; +}; diff --git a/ReadDirectoryChanges.sln b/ReadDirectoryChanges.sln new file mode 100644 index 0000000..7c6a5e9 --- /dev/null +++ b/ReadDirectoryChanges.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32519.379 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReadDirectoryChanges", "ReadDirectoryChanges.vcxproj", "{72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}.Debug|Win32.ActiveCfg = Debug|Win32 + {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}.Debug|Win32.Build.0 = Debug|Win32 + {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}.Release|Win32.ActiveCfg = Release|Win32 + {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ReadDirectoryChanges.vcxproj b/ReadDirectoryChanges.vcxproj new file mode 100644 index 0000000..658ef3f --- /dev/null +++ b/ReadDirectoryChanges.vcxproj @@ -0,0 +1,105 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6} + Win32Proj + ReadDirectoryChanges + 10.0 + + + + Application + true + Unicode + Dynamic + Static + LLVM-MSVC_v143 + + + Application + false + true + Unicode + Dynamic + Static + LLVM-MSVC_v143 + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + Create + Create + + + + + + \ No newline at end of file diff --git a/ReadDirectoryChanges.vcxproj.filters b/ReadDirectoryChanges.vcxproj.filters new file mode 100644 index 0000000..ae25ea4 --- /dev/null +++ b/ReadDirectoryChanges.vcxproj.filters @@ -0,0 +1,50 @@ + + + + + {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 + + + + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/ReadDirectoryChangesPrivate.cpp b/ReadDirectoryChangesPrivate.cpp new file mode 100644 index 0000000..08e5595 --- /dev/null +++ b/ReadDirectoryChangesPrivate.cpp @@ -0,0 +1,180 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// 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. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + + +#include "stdafx.h" +#include "ReadDirectoryChanges.h" +#include "ReadDirectoryChangesPrivate.h" + + +// The namespace is a convenience to emphasize that these are internals +// interfaces. The namespace can be safely removed if you need to. +namespace ReadDirectoryChangesPrivate +{ + +/////////////////////////////////////////////////////////////////////////// +// CReadChangesRequest + +CReadChangesRequest::CReadChangesRequest(CReadChangesServer* pServer, LPCTSTR sz, BOOL b, DWORD dw, DWORD size) +{ + m_pServer = pServer; + m_dwFilterFlags = dw; + m_bIncludeChildren = b; + m_wstrDirectory = sz; + m_hDirectory = 0; + + ::ZeroMemory(&m_Overlapped, sizeof(OVERLAPPED)); + + // The hEvent member is not used when there is a completion + // function, so it's ok to use it to point to the object. + m_Overlapped.hEvent = this; + + m_Buffer.resize(size); + m_BackupBuffer.resize(size); +} + + +CReadChangesRequest::~CReadChangesRequest() +{ + // RequestTermination() must have been called successfully. + _ASSERTE(m_hDirectory == NULL); +} + + +bool CReadChangesRequest::OpenDirectory() +{ + // Allow this routine to be called redundantly. + if (m_hDirectory) + return true; + + m_hDirectory = ::CreateFileW( + m_wstrDirectory.c_str(), // pointer to the file name + FILE_LIST_DIRECTORY, // access (read/write) mode + FILE_SHARE_READ // share mode + | FILE_SHARE_WRITE + | FILE_SHARE_DELETE, + NULL, // security descriptor + OPEN_EXISTING, // how to create + FILE_FLAG_BACKUP_SEMANTICS // file attributes + | FILE_FLAG_OVERLAPPED, + NULL); // file with attributes to copy + + if (m_hDirectory == INVALID_HANDLE_VALUE) + { + return false; + } + + return true; +} + +void CReadChangesRequest::BeginRead() +{ + DWORD dwBytes=0; + + // This call needs to be reissued after every APC. + BOOL success = ::ReadDirectoryChangesW( + m_hDirectory, // handle to directory + &m_Buffer[0], // read results buffer + m_Buffer.size(), // length of buffer + m_bIncludeChildren, // monitoring option + m_dwFilterFlags, // filter conditions + &dwBytes, // bytes returned + &m_Overlapped, // overlapped buffer + &NotificationCompletion); // completion routine +} + +//static +VOID CALLBACK CReadChangesRequest::NotificationCompletion( + DWORD dwErrorCode, // completion code + DWORD dwNumberOfBytesTransfered, // number of bytes transferred + LPOVERLAPPED lpOverlapped) // I/O information buffer +{ + CReadChangesRequest* pBlock = (CReadChangesRequest*)lpOverlapped->hEvent; + + if (dwErrorCode == ERROR_OPERATION_ABORTED) + { + ::InterlockedDecrement(&pBlock->m_pServer->m_nOutstandingRequests); + delete pBlock; + return; + } + + // Can't use sizeof(FILE_NOTIFY_INFORMATION) because + // the structure is padded to 16 bytes. + _ASSERTE(dwNumberOfBytesTransfered >= offsetof(FILE_NOTIFY_INFORMATION, FileName) + sizeof(WCHAR)); + + // This might mean overflow? Not sure. + if(!dwNumberOfBytesTransfered) + return; + + pBlock->BackupBuffer(dwNumberOfBytesTransfered); + + // Get the new read issued as fast as possible. The documentation + // says that the original OVERLAPPED structure will not be used + // again once the completion routine is called. + pBlock->BeginRead(); + + pBlock->ProcessNotification(); +} + +void CReadChangesRequest::ProcessNotification() +{ + BYTE* pBase = m_BackupBuffer.data(); + + for (;;) + { + FILE_NOTIFY_INFORMATION& fni = (FILE_NOTIFY_INFORMATION&)*pBase; + + std::wstring wstrFilename(fni.FileName, fni.FileNameLength/sizeof(wchar_t)); + // Handle a trailing backslash, such as for a root directory. + if (m_wstrDirectory.find_last_of(L"\\") != std::wstring::npos) + wstrFilename = m_wstrDirectory + L"\\" + wstrFilename; + else + wstrFilename = m_wstrDirectory + wstrFilename; + + // If it could be a short filename, expand it. + LPCWSTR wszFilename = PathFindFileNameW(wstrFilename.c_str()); + int len = lstrlenW(wszFilename); + // The maximum length of an 8.3 filename is twelve, including the dot. + if (len <= 12 && wcschr(wszFilename, L'~')) + { + // Convert to the long filename form. Unfortunately, this + // does not work for deletions, so it's an imperfect fix. + wchar_t wbuf[MAX_PATH]; + if (::GetLongPathNameW(wstrFilename.c_str(), wbuf, _countof(wbuf)) > 0) + wstrFilename = wbuf; + } + + m_pServer->m_pBase->Push(fni.Action, wstrFilename); + + if (!fni.NextEntryOffset) + break; + pBase += fni.NextEntryOffset; + }; +} + +} diff --git a/ReadDirectoryChangesPrivate.h b/ReadDirectoryChangesPrivate.h new file mode 100644 index 0000000..b56d23a --- /dev/null +++ b/ReadDirectoryChangesPrivate.h @@ -0,0 +1,177 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// 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. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + +class CReadDirectoryChanges; + +namespace ReadDirectoryChangesPrivate +{ + +class CReadChangesServer; + +/////////////////////////////////////////////////////////////////////////// + +// All functions in CReadChangesRequest run in the context of the worker thread. +// One instance of this object is created for each call to AddDirectory(). +class CReadChangesRequest +{ +public: + CReadChangesRequest(CReadChangesServer* pServer, LPCTSTR sz, BOOL b, DWORD dw, DWORD size); + + ~CReadChangesRequest(); + + bool OpenDirectory(); + + void BeginRead(); + + // The dwSize is the actual number of bytes sent to the APC. + void BackupBuffer(DWORD dwSize) + { + // We could just swap back and forth between the two + // buffers, but this code is easier to understand and debug. + memcpy(&m_BackupBuffer[0], &m_Buffer[0], dwSize); + } + + void ProcessNotification(); + + void RequestTermination() + { + ::CancelIo(m_hDirectory); + ::CloseHandle(m_hDirectory); + m_hDirectory = nullptr; + } + + CReadChangesServer* m_pServer; + +protected: + + static VOID CALLBACK NotificationCompletion( + DWORD dwErrorCode, // completion code + DWORD dwNumberOfBytesTransfered, // number of bytes transferred + LPOVERLAPPED lpOverlapped); // I/O information buffer + + // Parameters from the caller for ReadDirectoryChangesW(). + DWORD m_dwFilterFlags; + BOOL m_bIncludeChildren; + std::wstring m_wstrDirectory; + + // Result of calling CreateFile(). + HANDLE m_hDirectory; + + // Required parameter for ReadDirectoryChangesW(). + OVERLAPPED m_Overlapped; + + // Data buffer for the request. + // Since the memory is allocated by malloc, it will always + // be aligned as required by ReadDirectoryChangesW(). + vector m_Buffer; + + // Double buffer strategy so that we can issue a new read + // request before we process the current buffer. + vector m_BackupBuffer; +}; + +/////////////////////////////////////////////////////////////////////////// + +// All functions in CReadChangesServer run in the context of the worker thread. +// One instance of this object is allocated for each instance of CReadDirectoryChanges. +// This class is responsible for thread startup, orderly thread shutdown, and shimming +// the various C++ member functions with C-style Win32 functions. +class CReadChangesServer +{ +public: + CReadChangesServer(CReadDirectoryChanges* pParent) + { + m_bTerminate=false; m_nOutstandingRequests=0;m_pBase=pParent; + } + + static unsigned int WINAPI ThreadStartProc(LPVOID arg) + { + CReadChangesServer* pServer = (CReadChangesServer*)arg; + pServer->Run(); + return 0; + } + + // Called by QueueUserAPC to start orderly shutdown. + static void CALLBACK TerminateProc(__in ULONG_PTR arg) + { + CReadChangesServer* pServer = (CReadChangesServer*)arg; + pServer->RequestTermination(); + } + + // Called by QueueUserAPC to add another directory. + static void CALLBACK AddDirectoryProc(__in ULONG_PTR arg) + { + CReadChangesRequest* pRequest = (CReadChangesRequest*)arg; + pRequest->m_pServer->AddDirectory(pRequest); + } + + CReadDirectoryChanges* m_pBase; + + volatile DWORD m_nOutstandingRequests; + +protected: + + void Run() + { + while (m_nOutstandingRequests || !m_bTerminate) + { + DWORD rc = ::SleepEx(INFINITE, true); + } + } + + void AddDirectory( CReadChangesRequest* pBlock ) + { + if (pBlock->OpenDirectory()) + { + ::InterlockedIncrement(&pBlock->m_pServer->m_nOutstandingRequests); + m_pBlocks.push_back(pBlock); + pBlock->BeginRead(); + } + else + delete pBlock; + } + + void RequestTermination() + { + m_bTerminate = true; + + for (DWORD i=0; iRequestTermination(); + } + + m_pBlocks.clear(); + } + + vector m_pBlocks; + + bool m_bTerminate; +}; + +} diff --git a/ThreadSafeQueue.h b/ThreadSafeQueue.h new file mode 100644 index 0000000..fa4a607 --- /dev/null +++ b/ThreadSafeQueue.h @@ -0,0 +1,116 @@ +// +// The MIT License +// +// Copyright (c) 2010 James E Beveridge +// +// 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. + + +// This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" +// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html +// See ReadMe.txt for overview information. + +#include + +template +class CThreadSafeQueue : protected std::list +{ +public: + CThreadSafeQueue(int nMaxCount) + { + m_bOverflow = false; + + m_hSemaphore = ::CreateSemaphore( + NULL, // no security attributes + 0, // initial count + nMaxCount, // max count + NULL); // anonymous + } + + ~CThreadSafeQueue() + { + ::CloseHandle(m_hSemaphore); + m_hSemaphore = NULL; + } + + void push(C& c) + { + CComCritSecLock lock( m_Crit, true ); + push_back( c ); + lock.Unlock(); + + if (!::ReleaseSemaphore(m_hSemaphore, 1, NULL)) + { + // If the semaphore is full, then take back the entry. + lock.Lock(); + pop_back(); + if (GetLastError() == ERROR_TOO_MANY_POSTS) + { + m_bOverflow = true; + } + } + } + + bool pop(C& c) + { + CComCritSecLock lock( m_Crit, true ); + + // If the user calls pop() more than once after the + // semaphore is signaled, then the semaphore count will + // get out of sync. We fix that when the queue empties. + if (empty()) + { + while (::WaitForSingleObject(m_hSemaphore, 0) != WAIT_TIMEOUT) + 1; + return false; + } + + c = front(); + pop_front(); + + return true; + } + + // If overflow, use this to clear the queue. + void clear() + { + CComCritSecLock lock( m_Crit, true ); + + for (DWORD i=0; i + +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers +#endif + +#include + +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; diff --git a/targetver.h b/targetver.h new file mode 100644 index 0000000..87c0086 --- /dev/null +++ b/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include