Back to index

lightning-sunbird  0.9+nobinonly
win_rand.c
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is the Netscape security libraries.
00015  *
00016  * The Initial Developer of the Original Code is
00017  * Netscape Communications Corporation.
00018  * Portions created by the Initial Developer are Copyright (C) 1994-2000
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *
00023  * Alternatively, the contents of this file may be used under the terms of
00024  * either the GNU General Public License Version 2 or later (the "GPL"), or
00025  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00026  * in which case the provisions of the GPL or the LGPL are applicable instead
00027  * of those above. If you wish to allow use of your version of this file only
00028  * under the terms of either the GPL or the LGPL, and not to allow others to
00029  * use your version of this file under the terms of the MPL, indicate your
00030  * decision by deleting the provisions above and replace them with the notice
00031  * and other provisions required by the GPL or the LGPL. If you do not delete
00032  * the provisions above, a recipient may use your version of this file under
00033  * the terms of any one of the MPL, the GPL or the LGPL.
00034  *
00035  * ***** END LICENSE BLOCK ***** */
00036 
00037 #include "secrng.h"
00038 #include "secerr.h"
00039 #ifdef XP_WIN
00040 #include <windows.h>
00041 
00042 #if defined(_WIN32_WCE)
00043 #include <stdlib.h>  /* Win CE puts lots of stuff here. */
00044 #include "prprf.h"   /* for PR_snprintf */
00045 #else
00046 #include <time.h>
00047 #include <io.h>
00048 #include <sys/types.h>
00049 #include <sys/stat.h>
00050 #endif
00051 #include <stdio.h>
00052 
00053 #ifndef _WIN32
00054 #define VTD_Device_ID   5
00055 #define OP_OVERRIDE     _asm _emit 0x66
00056 #include <dos.h>
00057 #endif
00058 
00059 #include "prio.h"
00060 #include "prerror.h"
00061 
00062 static PRInt32  filesToRead;
00063 static DWORD    totalFileBytes;
00064 static DWORD    maxFileBytes       = 250000;     /* 250 thousand */
00065 static DWORD    dwNumFiles, dwReadEvery;
00066 
00067 static BOOL
00068 CurrentClockTickTime(LPDWORD lpdwHigh, LPDWORD lpdwLow)
00069 {
00070 #ifdef _WIN32
00071     LARGE_INTEGER   liCount;
00072 
00073     if (!QueryPerformanceCounter(&liCount))
00074         return FALSE;
00075 
00076     *lpdwHigh = liCount.u.HighPart;
00077     *lpdwLow = liCount.u.LowPart;
00078     return TRUE;
00079 
00080 #else   /* is WIN16 */
00081     BOOL    bRetVal;
00082     FARPROC lpAPI;
00083     WORD    w1, w2, w3, w4;
00084 
00085     // Get direct access to the VTD and query the current clock tick time
00086     _asm {
00087         xor   di, di
00088         mov   es, di
00089         mov   ax, 1684h
00090         mov   bx, VTD_Device_ID
00091         int   2fh
00092         mov   ax, es
00093         or    ax, di
00094         jz    EnumerateFailed
00095 
00096         ; VTD API is available. First store the API address (the address actually
00097         ; contains an instruction that causes a fault, the fault handler then
00098         ; makes the ring transition and calls the API in the VxD)
00099         mov   word ptr lpAPI, di
00100         mov   word ptr lpAPI+2, es
00101         mov   ax, 100h      ; API function to VTD_Get_Real_Time
00102 ;       call  dword ptr [lpAPI]
00103         call  [lpAPI]
00104 
00105         ; Result is in EDX:EAX which we will get 16-bits at a time
00106         mov   w2, dx
00107         OP_OVERRIDE
00108         shr   dx,10h        ; really "shr edx, 16"
00109         mov   w1, dx
00110 
00111         mov   w4, ax
00112         OP_OVERRIDE
00113         shr   ax,10h        ; really "shr eax, 16"
00114         mov   w3, ax
00115 
00116         mov   bRetVal, 1    ; return TRUE
00117         jmp   EnumerateExit
00118 
00119       EnumerateFailed:
00120         mov   bRetVal, 0    ; return FALSE
00121 
00122       EnumerateExit:
00123     }
00124 
00125     *lpdwHigh = MAKELONG(w2, w1);
00126     *lpdwLow = MAKELONG(w4, w3);
00127 
00128     return bRetVal;
00129 #endif  /* is WIN16 */
00130 }
00131 
00132 size_t RNG_GetNoise(void *buf, size_t maxbuf)
00133 {
00134     DWORD   dwHigh, dwLow, dwVal;
00135     int     n = 0;
00136     int     nBytes;
00137 
00138     if (maxbuf <= 0)
00139         return 0;
00140 
00141     CurrentClockTickTime(&dwHigh, &dwLow);
00142 
00143     // get the maximally changing bits first
00144     nBytes = sizeof(dwLow) > maxbuf ? maxbuf : sizeof(dwLow);
00145     memcpy((char *)buf, &dwLow, nBytes);
00146     n += nBytes;
00147     maxbuf -= nBytes;
00148 
00149     if (maxbuf <= 0)
00150         return n;
00151 
00152     nBytes = sizeof(dwHigh) > maxbuf ? maxbuf : sizeof(dwHigh);
00153     memcpy(((char *)buf) + n, &dwHigh, nBytes);
00154     n += nBytes;
00155     maxbuf -= nBytes;
00156 
00157     if (maxbuf <= 0)
00158         return n;
00159 
00160     // get the number of milliseconds that have elapsed since Windows started
00161     dwVal = GetTickCount();
00162 
00163     nBytes = sizeof(dwVal) > maxbuf ? maxbuf : sizeof(dwVal);
00164     memcpy(((char *)buf) + n, &dwVal, nBytes);
00165     n += nBytes;
00166     maxbuf -= nBytes;
00167 
00168     if (maxbuf <= 0)
00169         return n;
00170 
00171 #if defined(_WIN32_WCE)
00172     {
00173     // get the number of milliseconds elapsed since Windows CE was started. 
00174     DWORD  tickCount = GetTickCount();
00175     nBytes = (sizeof tickCount) > maxbuf ? maxbuf : (sizeof tickCount);
00176     memcpy(((char *)buf) + n, &tickCount, nBytes);
00177     n += nBytes;
00178     }
00179 #else
00180     {
00181     time_t  sTime;
00182     // get the time in seconds since midnight Jan 1, 1970
00183     time(&sTime);
00184     nBytes = sizeof(sTime) > maxbuf ? maxbuf : sizeof(sTime);
00185     memcpy(((char *)buf) + n, &sTime, nBytes);
00186     n += nBytes;
00187     }
00188 #endif
00189 
00190     return n;
00191 }
00192 
00193 #if defined(_WIN32_WCE)
00194 static BOOL
00195 EnumSystemFilesWithNSPR(const char * dirName, 
00196                      BOOL recursive,
00197                         PRInt32 (*func)(const char *))
00198 {
00199     PRDir *      pDir;
00200     PRDirEntry * pEntry;
00201     BOOL         rv         = FALSE;
00202 
00203     pDir = PR_OpenDir(dirName);
00204     if (!pDir)
00205        return rv;
00206     while ((pEntry = PR_ReadDir(pDir, PR_SKIP_BOTH|PR_SKIP_HIDDEN)) != NULL) {
00207        PRStatus    status;
00208        PRInt32     count;
00209        PRInt32     stop;
00210        PRFileInfo  fileInfo;
00211        char        szFileName[_MAX_PATH];
00212 
00213         count = (PRInt32)PR_snprintf(szFileName, sizeof szFileName, "%s\\%s", 
00214                                     dirName, PR_DirName(pEntry));
00215        if (count < 1)
00216            continue;
00217        status = PR_GetFileInfo(szFileName, &fileInfo);
00218        if (status != PR_SUCCESS)
00219            continue;
00220        if (fileInfo.type == PR_FILE_FILE) {
00221            stop = (*func)(szFileName);
00222            rv = TRUE;
00223            if (stop)
00224                break;
00225            continue;
00226        }
00227        if (recursive && fileInfo.type == PR_FILE_DIRECTORY) {
00228            rv |= EnumSystemFilesWithNSPR(szFileName, recursive, func);
00229        }
00230     }
00231     PR_CloseDir(pDir);
00232     return rv;
00233 }
00234 #endif
00235 
00236 static BOOL
00237 EnumSystemFiles(PRInt32 (*func)(const char *))
00238 {
00239 #if defined(_WIN32_WCE)
00240     BOOL rv = FALSE;
00241     rv |= EnumSystemFilesWithNSPR("\\Windows\\Temporary Internet Files", TRUE, func);
00242     rv |= EnumSystemFilesWithNSPR("\\Temp",    FALSE, func);
00243     rv |= EnumSystemFilesWithNSPR("\\Windows", FALSE, func);
00244     return rv;
00245 #else
00246     int                 iStatus;
00247     char                szSysDir[_MAX_PATH];
00248     char                szFileName[_MAX_PATH];
00249 #ifdef _WIN32
00250     WIN32_FIND_DATA     fdData;
00251     HANDLE              lFindHandle;
00252 #else
00253     struct _find_t  fdData;
00254 #endif
00255 
00256     if (!GetSystemDirectory(szSysDir, sizeof(szSysDir)))
00257         return FALSE;
00258 
00259     // tack *.* on the end so we actually look for files. this will
00260     // not overflow
00261     strcpy(szFileName, szSysDir);
00262     strcat(szFileName, "\\*.*");
00263 
00264 #ifdef _WIN32
00265     lFindHandle = FindFirstFile(szFileName, &fdData);
00266     if (lFindHandle == INVALID_HANDLE_VALUE)
00267         return FALSE;
00268     do {
00269         // pass the full pathname to the callback
00270         sprintf(szFileName, "%s\\%s", szSysDir, fdData.cFileName);
00271         (*func)(szFileName);
00272         iStatus = FindNextFile(lFindHandle, &fdData);
00273     } while (iStatus != 0);
00274     FindClose(lFindHandle);
00275 #else
00276     if (_dos_findfirst(szFileName, 
00277              _A_NORMAL | _A_RDONLY | _A_ARCH | _A_SUBDIR, &fdData) != 0)
00278         return FALSE;
00279     do {
00280         // pass the full pathname to the callback
00281         sprintf(szFileName, "%s\\%s", szSysDir, fdData.name);
00282         (*func)(szFileName);
00283         iStatus = _dos_findnext(&fdData);
00284     } while (iStatus == 0);
00285     _dos_findclose(&fdData);
00286 #endif
00287 
00288     return TRUE;
00289 #endif
00290 }
00291 
00292 static PRInt32
00293 CountFiles(const char *file)
00294 {
00295     dwNumFiles++;
00296     return 0;
00297 }
00298 
00299 static PRInt32
00300 ReadFiles(const char *file)
00301 {
00302     if ((dwNumFiles % dwReadEvery) == 0) {
00303        ++filesToRead;
00304     }
00305     if (filesToRead) {
00306        DWORD    prevFileBytes = totalFileBytes;
00307         RNG_FileForRNG(file);
00308        if (prevFileBytes < totalFileBytes) {
00309            --filesToRead;
00310        }
00311     }
00312     dwNumFiles++;
00313     return (totalFileBytes >= maxFileBytes);
00314 }
00315 
00316 static void
00317 ReadSystemFiles()
00318 {
00319     // first count the number of files
00320     dwNumFiles = 0;
00321     if (!EnumSystemFiles(CountFiles))
00322         return;
00323 
00324     RNG_RandomUpdate(&dwNumFiles, sizeof(dwNumFiles));
00325 
00326     // now read 10 files
00327     filesToRead = 10;
00328     if (dwNumFiles == 0)
00329         return;
00330 
00331     dwReadEvery = dwNumFiles / 10;
00332     if (dwReadEvery == 0)
00333         dwReadEvery = 1;  // less than 10 files
00334 
00335     dwNumFiles = 0;
00336     EnumSystemFiles(ReadFiles);
00337 }
00338 
00339 void RNG_SystemInfoForRNG(void)
00340 {
00341     DWORD           dwVal;
00342     char            buffer[256];
00343     int             nBytes;
00344 #ifdef _WIN32
00345     MEMORYSTATUS    sMem;
00346     HANDLE          hVal;
00347 #if !defined(_WIN32_WCE)
00348     DWORD           dwSerialNum;
00349     DWORD           dwComponentLen;
00350     DWORD           dwSysFlags;
00351     char            volName[128];
00352     DWORD           dwSectors, dwBytes, dwFreeClusters, dwNumClusters;
00353 #endif
00354 #else
00355     int             iVal;
00356     HTASK           hTask;
00357     WORD            wDS, wCS;
00358     LPSTR           lpszEnv;
00359 #endif
00360 
00361     nBytes = RNG_GetNoise(buffer, 20);  // get up to 20 bytes
00362     RNG_RandomUpdate(buffer, nBytes);
00363 
00364 #ifdef _WIN32
00365     sMem.dwLength = sizeof(sMem);
00366     GlobalMemoryStatus(&sMem);                // assorted memory stats
00367     RNG_RandomUpdate(&sMem, sizeof(sMem));
00368 #if !defined(_WIN32_WCE)
00369     dwVal = GetLogicalDrives();
00370     RNG_RandomUpdate(&dwVal, sizeof(dwVal));  // bitfields in bits 0-25
00371 #endif
00372 #else
00373     dwVal = GetFreeSpace(0);
00374     RNG_RandomUpdate(&dwVal, sizeof(dwVal));
00375 
00376     _asm    mov wDS, ds;
00377     _asm    mov wCS, cs;
00378     RNG_RandomUpdate(&wDS, sizeof(wDS));
00379     RNG_RandomUpdate(&wCS, sizeof(wCS));
00380 #endif
00381 
00382 #ifdef _WIN32
00383 #if !defined(_WIN32_WCE)
00384     dwVal = sizeof(buffer);
00385     if (GetComputerName(buffer, &dwVal))
00386         RNG_RandomUpdate(buffer, dwVal);
00387 #endif
00388 /* XXX This is code that got yanked because of NSPR20.  We should put it
00389  * back someday.
00390  */
00391 #ifdef notdef
00392     {
00393     POINT ptVal;
00394     GetCursorPos(&ptVal);
00395     RNG_RandomUpdate(&ptVal, sizeof(ptVal));
00396     }
00397 
00398     dwVal = GetQueueStatus(QS_ALLINPUT);      // high and low significant
00399     RNG_RandomUpdate(&dwVal, sizeof(dwVal));
00400 
00401     {
00402     HWND hWnd;
00403     hWnd = GetClipboardOwner();               // 2 or 4 bytes
00404     RNG_RandomUpdate((void *)&hWnd, sizeof(hWnd));
00405     }
00406 
00407     {
00408     UUID sUuid;
00409     UuidCreate(&sUuid);                       // this will fail on machines with no ethernet
00410     RNG_RandomUpdate(&sUuid, sizeof(sUuid));  // boards. shove the bits in regardless
00411     }
00412 #endif
00413 
00414     hVal = GetCurrentProcess();               // 4 byte handle of current task
00415     RNG_RandomUpdate(&hVal, sizeof(hVal));
00416 
00417     dwVal = GetCurrentProcessId();            // process ID (4 bytes)
00418     RNG_RandomUpdate(&dwVal, sizeof(dwVal));
00419 
00420 #if !defined(_WIN32_WCE)
00421     volName[0] = '\0';
00422     buffer[0] = '\0';
00423     GetVolumeInformation(NULL,
00424                          volName,
00425                          sizeof(volName),
00426                          &dwSerialNum,
00427                          &dwComponentLen,
00428                          &dwSysFlags,
00429                          buffer,
00430                          sizeof(buffer));
00431 
00432     RNG_RandomUpdate(volName,         strlen(volName));
00433     RNG_RandomUpdate(&dwSerialNum,    sizeof(dwSerialNum));
00434     RNG_RandomUpdate(&dwComponentLen, sizeof(dwComponentLen));
00435     RNG_RandomUpdate(&dwSysFlags,     sizeof(dwSysFlags));
00436     RNG_RandomUpdate(buffer,          strlen(buffer));
00437 
00438     if (GetDiskFreeSpace(NULL, &dwSectors, &dwBytes, &dwFreeClusters, &dwNumClusters)) {
00439         RNG_RandomUpdate(&dwSectors,      sizeof(dwSectors));
00440         RNG_RandomUpdate(&dwBytes,        sizeof(dwBytes));
00441         RNG_RandomUpdate(&dwFreeClusters, sizeof(dwFreeClusters));
00442         RNG_RandomUpdate(&dwNumClusters,  sizeof(dwNumClusters));
00443     }
00444 #endif
00445 #else   /* is WIN16 */
00446     hTask = GetCurrentTask();
00447     RNG_RandomUpdate((void *)&hTask, sizeof(hTask));
00448 
00449     iVal = GetNumTasks();
00450     RNG_RandomUpdate(&iVal, sizeof(iVal));      // number of running tasks
00451 
00452     lpszEnv = GetDOSEnvironment();
00453     while (*lpszEnv != '\0') {
00454         RNG_RandomUpdate(lpszEnv, strlen(lpszEnv));
00455 
00456         lpszEnv += strlen(lpszEnv) + 1;
00457     }
00458 #endif  /* is WIN16 */
00459 
00460     // now let's do some files
00461     ReadSystemFiles();
00462 
00463     nBytes = RNG_GetNoise(buffer, 20);  // get up to 20 bytes
00464     RNG_RandomUpdate(buffer, nBytes);
00465 }
00466 
00467 #if defined(_WIN32_WCE)
00468 void RNG_FileForRNG(const char *filename)
00469 {
00470     PRFileDesc *    file;
00471     int             nBytes;
00472     PRFileInfo      infoBuf;
00473     unsigned char   buffer[1024];
00474 
00475     /* windows doesn't initialize all the bytes in the stat buf,
00476      * so initialize them all here to avoid UMRs.
00477      */
00478     memset(&infoBuf, 0, sizeof infoBuf);
00479 
00480     if (PR_GetFileInfo(filename, &infoBuf) < 0)
00481         return;
00482 
00483     RNG_RandomUpdate((unsigned char*)&infoBuf, sizeof(infoBuf));
00484 
00485     file = PR_Open(filename, PR_RDONLY, 0);
00486     if (file != NULL) {
00487         for (;;) {
00488             PRInt32 bytes = PR_Read(file, buffer, sizeof buffer);
00489 
00490             if (bytes <= 0)
00491                 break;
00492 
00493             RNG_RandomUpdate(buffer, bytes);
00494             totalFileBytes += bytes;
00495             if (totalFileBytes > maxFileBytes)
00496                 break;
00497         }
00498 
00499         PR_Close(file);
00500     }
00501 
00502     nBytes = RNG_GetNoise(buffer, 20);  // get up to 20 bytes
00503     RNG_RandomUpdate(buffer, nBytes);
00504 }
00505 
00506 #else /* not WinCE */
00507 
00508 void RNG_FileForRNG(const char *filename)
00509 {
00510     FILE*           file;
00511     int             nBytes;
00512     struct stat     stat_buf;
00513     unsigned char   buffer[1024];
00514 
00515    /* static DWORD    totalFileBytes = 0; */
00516 
00517     /* windows doesn't initialize all the bytes in the stat buf,
00518      * so initialize them all here to avoid UMRs.
00519      */
00520     memset(&stat_buf, 0, sizeof stat_buf);
00521 
00522     if (stat((char *)filename, &stat_buf) < 0)
00523         return;
00524 
00525     RNG_RandomUpdate((unsigned char*)&stat_buf, sizeof(stat_buf));
00526 
00527     file = fopen((char *)filename, "r");
00528     if (file != NULL) {
00529         for (;;) {
00530             size_t  bytes = fread(buffer, 1, sizeof(buffer), file);
00531 
00532             if (bytes == 0)
00533                 break;
00534 
00535             RNG_RandomUpdate(buffer, bytes);
00536             totalFileBytes += bytes;
00537             if (totalFileBytes > maxFileBytes)
00538                 break;
00539         }
00540 
00541         fclose(file);
00542     }
00543 
00544     nBytes = RNG_GetNoise(buffer, 20);  // get up to 20 bytes
00545     RNG_RandomUpdate(buffer, nBytes);
00546 }
00547 
00548 #endif  /* not WinCE */
00549 
00550 /*
00551  * CryptoAPI requires Windows NT 4.0 or Windows 95 OSR2 and later.
00552  * Until we drop support for Windows 95, we need to emulate some
00553  * definitions and declarations in <wincrypt.h> and look up the
00554  * functions in advapi32.dll at run time.
00555  */
00556 
00557 #ifndef WIN64
00558 typedef unsigned long HCRYPTPROV;
00559 #endif
00560 
00561 #define CRYPT_VERIFYCONTEXT 0xF0000000
00562 
00563 #define PROV_RSA_FULL 1
00564 
00565 typedef BOOL
00566 (WINAPI *CryptAcquireContextAFn)(
00567     HCRYPTPROV *phProv,
00568     LPCSTR pszContainer,
00569     LPCSTR pszProvider,
00570     DWORD dwProvType,
00571     DWORD dwFlags);
00572 
00573 typedef BOOL
00574 (WINAPI *CryptReleaseContextFn)(
00575     HCRYPTPROV hProv,
00576     DWORD dwFlags);
00577 
00578 typedef BOOL
00579 (WINAPI *CryptGenRandomFn)(
00580     HCRYPTPROV hProv,
00581     DWORD dwLen,
00582     BYTE *pbBuffer);
00583 
00584 /*
00585  * Windows XP and Windows Server 2003 and later have RtlGenRandom,
00586  * which must be looked up by the name SystemFunction036.
00587  */
00588 typedef BOOLEAN
00589 (APIENTRY *RtlGenRandomFn)(
00590     PVOID RandomBuffer,
00591     ULONG RandomBufferLength);
00592 
00593 size_t RNG_SystemRNG(void *dest, size_t maxLen)
00594 {
00595     HMODULE hModule;
00596     RtlGenRandomFn pRtlGenRandom;
00597     CryptAcquireContextAFn pCryptAcquireContextA;
00598     CryptReleaseContextFn pCryptReleaseContext;
00599     CryptGenRandomFn pCryptGenRandom;
00600     HCRYPTPROV hCryptProv;
00601     size_t bytes = 0;
00602 
00603     hModule = LoadLibrary("advapi32.dll");
00604     if (hModule == NULL) {
00605        PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
00606        return 0;
00607     }
00608     pRtlGenRandom = (RtlGenRandomFn)
00609        GetProcAddress(hModule, "SystemFunction036");
00610     if (pRtlGenRandom) {
00611        if (pRtlGenRandom(dest, maxLen)) {
00612            bytes = maxLen;
00613        } else {
00614            PORT_SetError(SEC_ERROR_NEED_RANDOM);  /* system RNG failed */
00615        }
00616        goto done;
00617     }
00618     pCryptAcquireContextA = (CryptAcquireContextAFn)
00619        GetProcAddress(hModule, "CryptAcquireContextA");
00620     pCryptReleaseContext = (CryptReleaseContextFn)
00621        GetProcAddress(hModule, "CryptReleaseContext");
00622     pCryptGenRandom = (CryptGenRandomFn)
00623        GetProcAddress(hModule, "CryptGenRandom");
00624     if (!pCryptAcquireContextA || !pCryptReleaseContext || !pCryptGenRandom) {
00625        PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
00626        goto done;
00627     }
00628     if (pCryptAcquireContextA(&hCryptProv, NULL, NULL,
00629        PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
00630        if (pCryptGenRandom(hCryptProv, maxLen, dest)) {
00631            bytes = maxLen;
00632        }
00633        pCryptReleaseContext(hCryptProv, 0);
00634     }
00635     if (bytes == 0) {
00636        PORT_SetError(SEC_ERROR_NEED_RANDOM);  /* system RNG failed */
00637     }
00638 done:
00639     FreeLibrary(hModule);
00640     return bytes;
00641 }
00642 
00643 #endif  /* is XP_WIN */