mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2025-11-11 02:58:02 -06:00
Windows: Revert use PDH API to gather system entropy because of issues encountered by users
cf thread: https://sourceforge.net/p/veracrypt/discussion/general/thread/293d401a30 delays and sporadic crashes in some cases.
This commit is contained in:
@@ -20,8 +20,6 @@
|
|||||||
#include "Crypto\rdrand.h"
|
#include "Crypto\rdrand.h"
|
||||||
#include <Strsafe.h>
|
#include <Strsafe.h>
|
||||||
#include <bcrypt.h>
|
#include <bcrypt.h>
|
||||||
#include <pdh.h>
|
|
||||||
#include <pdhmsg.h>
|
|
||||||
|
|
||||||
static unsigned __int8 buffer[RNG_POOL_SIZE];
|
static unsigned __int8 buffer[RNG_POOL_SIZE];
|
||||||
static unsigned char *pRandPool = NULL;
|
static unsigned char *pRandPool = NULL;
|
||||||
@@ -84,45 +82,15 @@ DWORD ProcessedMouseEventsCounter = 0;
|
|||||||
CRITICAL_SECTION critRandProt; /* The critical section */
|
CRITICAL_SECTION critRandProt; /* The critical section */
|
||||||
BOOL volatile bThreadTerminate = FALSE; /* This variable is shared among thread's so its made volatile */
|
BOOL volatile bThreadTerminate = FALSE; /* This variable is shared among thread's so its made volatile */
|
||||||
|
|
||||||
|
/* Network library handle for the SlowPoll function */
|
||||||
|
HANDLE hNetAPI32 = NULL;
|
||||||
|
|
||||||
// CryptoAPI
|
// CryptoAPI
|
||||||
DWORD CryptoAPILastError = ERROR_SUCCESS;
|
DWORD CryptoAPILastError = ERROR_SUCCESS;
|
||||||
|
|
||||||
typedef DWORD (WINAPI *RtlNtStatusToDosError_t)(NTSTATUS);
|
typedef DWORD (WINAPI *RtlNtStatusToDosError_t)(NTSTATUS);
|
||||||
RtlNtStatusToDosError_t pRtlNtStatusToDosError = NULL;
|
RtlNtStatusToDosError_t pRtlNtStatusToDosError = NULL;
|
||||||
|
|
||||||
static HMODULE hPdhLib = NULL;
|
|
||||||
|
|
||||||
typedef PDH_STATUS (WINAPI *PfnPdhOpenQueryW)(LPCWSTR, DWORD_PTR, PDH_HQUERY *);
|
|
||||||
typedef PDH_STATUS (WINAPI *PfnPdhAddCounterW)(PDH_HQUERY, LPCWSTR, DWORD_PTR, PDH_HCOUNTER *);
|
|
||||||
typedef PDH_STATUS (WINAPI *PfnPdhCollectQueryData)(PDH_HQUERY);
|
|
||||||
typedef PDH_STATUS (WINAPI *PfnPdhGetFormattedCounterValue)(PDH_HCOUNTER, DWORD, LPDWORD, PPDH_FMT_COUNTERVALUE);
|
|
||||||
typedef PDH_STATUS (WINAPI *PfnPdhCloseQuery)(PDH_HQUERY);
|
|
||||||
|
|
||||||
static PfnPdhOpenQueryW pfnPdhOpenQuery = NULL;
|
|
||||||
static PfnPdhAddCounterW pfnPdhAddCounter = NULL;
|
|
||||||
static PfnPdhCollectQueryData pfnPdhCollectQueryData = NULL;
|
|
||||||
static PfnPdhGetFormattedCounterValue pfnPdhGetFormattedCounterValue = NULL;
|
|
||||||
static PfnPdhCloseQuery pfnPdhCloseQuery = NULL;
|
|
||||||
|
|
||||||
static BOOL LoadPdhDll()
|
|
||||||
{
|
|
||||||
if (!hPdhLib)
|
|
||||||
{
|
|
||||||
hPdhLib = LoadLibraryExW(L"pdh.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
|
||||||
if (!hPdhLib)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
pfnPdhOpenQuery = (PfnPdhOpenQueryW) GetProcAddress(hPdhLib, "PdhOpenQueryW");
|
|
||||||
pfnPdhAddCounter = (PfnPdhAddCounterW) GetProcAddress(hPdhLib, "PdhAddCounterW");
|
|
||||||
pfnPdhCollectQueryData = (PfnPdhCollectQueryData) GetProcAddress(hPdhLib, "PdhCollectQueryData");
|
|
||||||
pfnPdhGetFormattedCounterValue = (PfnPdhGetFormattedCounterValue) GetProcAddress(hPdhLib, "PdhGetFormattedCounterValue");
|
|
||||||
pfnPdhCloseQuery = (PfnPdhCloseQuery) GetProcAddress(hPdhLib, "PdhCloseQuery");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (pfnPdhOpenQuery && pfnPdhAddCounter && pfnPdhCollectQueryData &&
|
|
||||||
pfnPdhGetFormattedCounterValue && pfnPdhCloseQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Init the random number generator, setup the hooks, and start the thread */
|
/* Init the random number generator, setup the hooks, and start the thread */
|
||||||
int RandinitWithCheck ( int* pAlreadyInitialized)
|
int RandinitWithCheck ( int* pAlreadyInitialized)
|
||||||
{
|
{
|
||||||
@@ -223,6 +191,12 @@ void RandStop (BOOL freePool)
|
|||||||
if (PeriodicFastPollThreadHandle)
|
if (PeriodicFastPollThreadHandle)
|
||||||
WaitForSingleObject (PeriodicFastPollThreadHandle, INFINITE);
|
WaitForSingleObject (PeriodicFastPollThreadHandle, INFINITE);
|
||||||
|
|
||||||
|
if (hNetAPI32 != 0)
|
||||||
|
{
|
||||||
|
FreeLibrary (hNetAPI32);
|
||||||
|
hNetAPI32 = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
hMouse = NULL;
|
hMouse = NULL;
|
||||||
hKeyboard = NULL;
|
hKeyboard = NULL;
|
||||||
@@ -666,192 +640,142 @@ static unsigned __stdcall PeriodicFastPollThreadProc (void *dummy)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Type definitions for function pointers to call NetAPI32 functions */
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------
|
typedef
|
||||||
GetDiskStatistics: This function uses the Windows Performance Data Helper (PDH) API
|
DWORD (WINAPI * NETSTATISTICSGET) (LPWSTR szServer, LPWSTR szService,
|
||||||
to collect disk statistics. The function collects the number of disk reads and writes
|
DWORD dwLevel, DWORD dwOptions,
|
||||||
per second for all physical disks. The function also collects high-resolution
|
LPBYTE * lpBuffer);
|
||||||
timestamps before and after the PDH query. The function then adds the collected data
|
typedef
|
||||||
to the random pool.
|
DWORD (WINAPI * NETAPIBUFFERSIZE) (LPVOID lpBuffer, LPDWORD cbBuffer);
|
||||||
The code waits a short random interval between the two PDH samples to ensures that
|
typedef
|
||||||
the performance counters have time to accumulate measurable changes and produce more
|
DWORD (WINAPI * NETAPIBUFFERFREE) (LPVOID lpBuffer);
|
||||||
varied data.
|
|
||||||
-------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
*/
|
NETSTATISTICSGET pNetStatisticsGet = NULL;
|
||||||
void GetDiskStatistics()
|
NETAPIBUFFERSIZE pNetApiBufferSize = NULL;
|
||||||
{
|
NETAPIBUFFERFREE pNetApiBufferFree = NULL;
|
||||||
if (!LoadPdhDll())
|
|
||||||
return;
|
|
||||||
PDH_STATUS status;
|
|
||||||
PDH_HQUERY query = NULL;
|
|
||||||
PDH_HCOUNTER counterReads = NULL, counterWrites = NULL;
|
|
||||||
PDH_FMT_COUNTERVALUE counterValue;
|
|
||||||
DWORD dwType;
|
|
||||||
LONGLONG llReads = 0, llWrites = 0;
|
|
||||||
DWORDLONG tstampBefore = 0, tstampAfter = 0;
|
|
||||||
LARGE_INTEGER perfCounterBefore, perfCounterAfter;
|
|
||||||
|
|
||||||
// High-resolution timestamps
|
|
||||||
if (!QueryPerformanceCounter(&perfCounterBefore))
|
|
||||||
return;
|
|
||||||
tstampBefore = GetTickCount64();
|
|
||||||
|
|
||||||
// Open PDH query
|
|
||||||
status = pfnPdhOpenQuery(NULL, 0, &query);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
// Add counters for disk reads and writes (all physical disks).
|
|
||||||
status = pfnPdhAddCounter(query, L"\\PhysicalDisk(*)\\Disk Reads/sec", 0, &counterReads);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
status = pfnPdhAddCounter(query, L"\\PhysicalDisk(*)\\Disk Writes/sec", 0, &counterWrites);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
// First sample
|
|
||||||
status = pfnPdhCollectQueryData(query);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
// Wait a short random interval
|
|
||||||
Sleep(10 + (GetCurrentProcessId() % 40));
|
|
||||||
|
|
||||||
// Second sample
|
|
||||||
status = pfnPdhCollectQueryData(query);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
// Format counters in PDH_FMT_LARGE
|
|
||||||
status = pfnPdhGetFormattedCounterValue(counterReads, PDH_FMT_LARGE, &dwType, &counterValue);
|
|
||||||
if (status == ERROR_SUCCESS)
|
|
||||||
llReads = counterValue.largeValue;
|
|
||||||
|
|
||||||
status = pfnPdhGetFormattedCounterValue(counterWrites, PDH_FMT_LARGE, &dwType, &counterValue);
|
|
||||||
if (status == ERROR_SUCCESS)
|
|
||||||
llWrites = counterValue.largeValue;
|
|
||||||
|
|
||||||
// Another high-resolution timestamp
|
|
||||||
if (!QueryPerformanceCounter(&perfCounterAfter))
|
|
||||||
goto error;
|
|
||||||
tstampAfter = GetTickCount64();
|
|
||||||
|
|
||||||
// Close PDH query
|
|
||||||
pfnPdhCloseQuery(query);
|
|
||||||
query = NULL;
|
|
||||||
|
|
||||||
// Collect results into the random pool
|
|
||||||
RandaddBuf(&llReads, sizeof(llReads));
|
|
||||||
RandaddBuf(&llWrites, sizeof(llWrites));
|
|
||||||
RandaddBuf(&tstampBefore, sizeof(tstampBefore));
|
|
||||||
RandaddBuf(&tstampAfter, sizeof(tstampAfter));
|
|
||||||
RandaddBuf(&perfCounterBefore.QuadPart, sizeof(perfCounterBefore.QuadPart));
|
|
||||||
RandaddBuf(&perfCounterAfter.QuadPart, sizeof(perfCounterAfter.QuadPart));
|
|
||||||
|
|
||||||
error:
|
|
||||||
if (query)
|
|
||||||
pfnPdhCloseQuery(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------------------
|
|
||||||
GetNetworkStatistics: This function uses the Windows Performance Data Helper (PDH) API
|
|
||||||
to collect network statistics. The function collects the number of bytes sent and
|
|
||||||
received per second for all network interfaces. The function also collects
|
|
||||||
high-resolution timestamps before and after the PDH query. The function then adds the
|
|
||||||
collected data to the random pool.
|
|
||||||
The code waits a short random interval between the two PDH samples to ensures that
|
|
||||||
the performance counters have time to accumulate measurable changes and produce more
|
|
||||||
varied data.
|
|
||||||
*/
|
|
||||||
void GetNetworkStatistics()
|
|
||||||
{
|
|
||||||
if (!LoadPdhDll())
|
|
||||||
return;
|
|
||||||
PDH_STATUS status;
|
|
||||||
PDH_HQUERY query = NULL;
|
|
||||||
PDH_HCOUNTER counterBytesSent = NULL, counterBytesReceived = NULL;
|
|
||||||
PDH_FMT_COUNTERVALUE counterValue;
|
|
||||||
DWORD dwType;
|
|
||||||
LONGLONG llBytesSent = 0, llBytesReceived = 0;
|
|
||||||
DWORDLONG tstampBefore = 0, tstampAfter = 0;
|
|
||||||
LARGE_INTEGER perfCounterBefore, perfCounterAfter;
|
|
||||||
|
|
||||||
// High-resolution timestamps
|
|
||||||
if (!QueryPerformanceCounter(&perfCounterBefore))
|
|
||||||
return;
|
|
||||||
tstampBefore = GetTickCount64();
|
|
||||||
|
|
||||||
// Open PDH query
|
|
||||||
status = pfnPdhOpenQuery(NULL, 0, &query);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
// Add counters for network bytes sent and received
|
|
||||||
status = pfnPdhAddCounter(query, L"\\Network Interface(*)\\Bytes Sent/sec", 0, &counterBytesSent);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
status = pfnPdhAddCounter(query, L"\\Network Interface(*)\\Bytes Received/sec", 0, &counterBytesReceived);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
// First sample
|
|
||||||
status = pfnPdhCollectQueryData(query);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
// Wait short, dynamic interval
|
|
||||||
Sleep(10 + (GetCurrentProcessId() % 40));
|
|
||||||
|
|
||||||
// Second sample
|
|
||||||
status = pfnPdhCollectQueryData(query);
|
|
||||||
if (status != ERROR_SUCCESS)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
// Format counters
|
|
||||||
status = pfnPdhGetFormattedCounterValue(counterBytesSent, PDH_FMT_LARGE, &dwType, &counterValue);
|
|
||||||
if (status == ERROR_SUCCESS)
|
|
||||||
llBytesSent = counterValue.largeValue;
|
|
||||||
|
|
||||||
status = pfnPdhGetFormattedCounterValue(counterBytesReceived, PDH_FMT_LARGE, &dwType, &counterValue);
|
|
||||||
if (status == ERROR_SUCCESS)
|
|
||||||
llBytesReceived = counterValue.largeValue;
|
|
||||||
|
|
||||||
if (!QueryPerformanceCounter(&perfCounterAfter))
|
|
||||||
goto error;
|
|
||||||
tstampAfter = GetTickCount64();
|
|
||||||
|
|
||||||
// Close PDH query
|
|
||||||
pfnPdhCloseQuery(query);
|
|
||||||
query = NULL;
|
|
||||||
|
|
||||||
// Collect results into our random pool
|
|
||||||
RandaddBuf(&llBytesSent, sizeof(llBytesSent));
|
|
||||||
RandaddBuf(&llBytesReceived, sizeof(llBytesReceived));
|
|
||||||
RandaddBuf(&tstampBefore, sizeof(tstampBefore));
|
|
||||||
RandaddBuf(&tstampAfter, sizeof(tstampAfter));
|
|
||||||
RandaddBuf(&perfCounterBefore.QuadPart, sizeof(perfCounterBefore.QuadPart));
|
|
||||||
RandaddBuf(&perfCounterAfter.QuadPart, sizeof(perfCounterAfter.QuadPart));
|
|
||||||
|
|
||||||
error:
|
|
||||||
if (query)
|
|
||||||
pfnPdhCloseQuery(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is the slowpoll function which gathers up network/hard drive
|
/* This is the slowpoll function which gathers up network/hard drive
|
||||||
performance data for the random pool */
|
performance data for the random pool */
|
||||||
BOOL SlowPoll (void)
|
BOOL SlowPoll (void)
|
||||||
{
|
{
|
||||||
|
static int isWorkstation = -1;
|
||||||
|
static int cbPerfData = 0x10000;
|
||||||
|
HANDLE hDevice;
|
||||||
|
LPBYTE lpBuffer;
|
||||||
|
DWORD dwSize, status;
|
||||||
|
LPWSTR lpszLanW, lpszLanS;
|
||||||
|
int nDrive;
|
||||||
NTSTATUS bStatus = 0;
|
NTSTATUS bStatus = 0;
|
||||||
|
|
||||||
// Gather disk stats via PDH
|
/* Find out whether this is an NT server or workstation if necessary */
|
||||||
GetDiskStatistics();
|
if (isWorkstation == -1)
|
||||||
|
{
|
||||||
|
HKEY hKey;
|
||||||
|
|
||||||
|
if (RegOpenKeyEx (HKEY_LOCAL_MACHINE,
|
||||||
|
L"SYSTEM\\CurrentControlSet\\Control\\ProductOptions",
|
||||||
|
0, KEY_READ, &hKey) == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
wchar_t szValue[32];
|
||||||
|
dwSize = sizeof (szValue);
|
||||||
|
|
||||||
|
isWorkstation = TRUE;
|
||||||
|
status = RegQueryValueEx (hKey, L"ProductType", 0, NULL,
|
||||||
|
(LPBYTE) szValue, &dwSize);
|
||||||
|
|
||||||
|
if (status == ERROR_SUCCESS && _wcsicmp (szValue, L"WinNT"))
|
||||||
|
/* Note: There are (at least) three cases for
|
||||||
|
ProductType: WinNT = NT Workstation,
|
||||||
|
ServerNT = NT Server, LanmanNT = NT Server
|
||||||
|
acting as a Domain Controller */
|
||||||
|
isWorkstation = FALSE;
|
||||||
|
|
||||||
|
RegCloseKey (hKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Initialize the NetAPI32 function pointers if necessary */
|
||||||
|
if (hNetAPI32 == NULL)
|
||||||
|
{
|
||||||
|
/* Obtain a handle to the module containing the Lan Manager
|
||||||
|
functions */
|
||||||
|
wchar_t dllPath[MAX_PATH];
|
||||||
|
if (GetSystemDirectory (dllPath, MAX_PATH))
|
||||||
|
{
|
||||||
|
StringCchCatW(dllPath, ARRAYSIZE(dllPath), L"\\NETAPI32.DLL");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
StringCchCopyW(dllPath, ARRAYSIZE(dllPath), L"C:\\Windows\\System32\\NETAPI32.DLL");
|
||||||
|
|
||||||
|
hNetAPI32 = LoadLibrary (dllPath);
|
||||||
|
if (hNetAPI32 != NULL)
|
||||||
|
{
|
||||||
|
/* Now get pointers to the functions */
|
||||||
|
pNetStatisticsGet = (NETSTATISTICSGET) GetProcAddress (hNetAPI32,
|
||||||
|
"NetStatisticsGet");
|
||||||
|
pNetApiBufferSize = (NETAPIBUFFERSIZE) GetProcAddress (hNetAPI32,
|
||||||
|
"NetApiBufferSize");
|
||||||
|
pNetApiBufferFree = (NETAPIBUFFERFREE) GetProcAddress (hNetAPI32,
|
||||||
|
"NetApiBufferFree");
|
||||||
|
|
||||||
|
/* Make sure we got valid pointers for every NetAPI32
|
||||||
|
function */
|
||||||
|
if (pNetStatisticsGet == NULL ||
|
||||||
|
pNetApiBufferSize == NULL ||
|
||||||
|
pNetApiBufferFree == NULL)
|
||||||
|
{
|
||||||
|
/* Free the library reference and reset the
|
||||||
|
static handle */
|
||||||
|
FreeLibrary (hNetAPI32);
|
||||||
|
hNetAPI32 = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get network statistics. Note: Both NT Workstation and NT Server
|
||||||
|
by default will be running both the workstation and server
|
||||||
|
services. The heuristic below is probably useful though on the
|
||||||
|
assumption that the majority of the network traffic will be via
|
||||||
|
the appropriate service */
|
||||||
|
lpszLanW = (LPWSTR) WIDE ("LanmanWorkstation");
|
||||||
|
lpszLanS = (LPWSTR) WIDE ("LanmanServer");
|
||||||
|
if (hNetAPI32 &&
|
||||||
|
pNetStatisticsGet (NULL,
|
||||||
|
isWorkstation ? lpszLanW : lpszLanS,
|
||||||
|
0, 0, &lpBuffer) == 0)
|
||||||
|
{
|
||||||
|
pNetApiBufferSize (lpBuffer, &dwSize);
|
||||||
|
RandaddBuf ((unsigned char *) lpBuffer, dwSize);
|
||||||
|
pNetApiBufferFree (lpBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get disk I/O statistics for all the hard drives */
|
||||||
|
for (nDrive = 0;; nDrive++)
|
||||||
|
{
|
||||||
|
DISK_PERFORMANCE diskPerformance;
|
||||||
|
wchar_t szDevice[24];
|
||||||
|
|
||||||
|
/* Check whether we can access this device */
|
||||||
|
StringCchPrintfW (szDevice, ARRAYSIZE(szDevice), L"\\\\.\\PhysicalDrive%d", nDrive);
|
||||||
|
hDevice = CreateFile (szDevice, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||||
|
NULL, OPEN_EXISTING, 0, NULL);
|
||||||
|
if (hDevice == INVALID_HANDLE_VALUE)
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
/* Note: This only works if you have turned on the disk
|
||||||
|
performance counters with 'diskperf -y'. These counters
|
||||||
|
are off by default */
|
||||||
|
if (DeviceIoControl (hDevice, IOCTL_DISK_PERFORMANCE, NULL, 0,
|
||||||
|
&diskPerformance, sizeof (DISK_PERFORMANCE),
|
||||||
|
&dwSize, NULL))
|
||||||
|
{
|
||||||
|
RandaddBuf ((unsigned char *) &diskPerformance, dwSize);
|
||||||
|
}
|
||||||
|
CloseHandle (hDevice);
|
||||||
|
}
|
||||||
|
|
||||||
// Gather network stats via PDH
|
|
||||||
GetNetworkStatistics();
|
|
||||||
|
|
||||||
bStatus = BCryptGenRandom(NULL, buffer, sizeof(buffer), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
bStatus = BCryptGenRandom(NULL, buffer, sizeof(buffer), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||||
if (NT_SUCCESS(bStatus))
|
if (NT_SUCCESS(bStatus))
|
||||||
@@ -890,8 +814,6 @@ BOOL SlowPoll (void)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
burn(buffer, sizeof (buffer));
|
burn(buffer, sizeof (buffer));
|
||||||
|
|
||||||
/* Mix the pool */
|
|
||||||
Randmix();
|
Randmix();
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|||||||
Reference in New Issue
Block a user