[OPENGL32]
[reactos.git] / reactos / dll / win32 / opengl32 / opengl32.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: lib/opengl32/opengl32.c
5 * PURPOSE: OpenGL32 lib
6 * PROGRAMMER: Anich Gregor (blight), Royce Mitchell III
7 * UPDATE HISTORY:
8 * Feb 1, 2004: Created
9 */
10
11 #include "opengl32.h"
12
13 /* function prototypes */
14 static void OPENGL32_AppendICD( GLDRIVERDATA *icd );
15 static void OPENGL32_RemoveICD( GLDRIVERDATA *icd );
16 static GLDRIVERDATA *OPENGL32_LoadDriver( LPCWSTR regKey );
17 static DWORD OPENGL32_InitializeDriver( GLDRIVERDATA *icd );
18 static BOOL OPENGL32_UnloadDriver( GLDRIVERDATA *icd );
19 static DWORD OPENGL32_RegGetDriverInfo( LPCWSTR driver, GLDRIVERDATA *icd );
20
21
22 /* global vars */
23 /* Do not assume it have the free value MAXDWORD set, any value can be in here */
24 DWORD OPENGL32_tls = MAXDWORD;
25 GLPROCESSDATA OPENGL32_processdata;
26
27
28 static BOOL
29 OPENGL32_ThreadAttach( void )
30 {
31 GLTHREADDATA* lpData = NULL;
32 PROC *dispatchTable = NULL;
33 TEB *teb = NULL;
34
35 dispatchTable = (PROC*)HeapAlloc( GetProcessHeap(),
36 HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY,
37 sizeof (((ICDTable *)(0))->dispatch_table) );
38 if (dispatchTable == NULL)
39 {
40 DBGPRINT( "Error: Couldn't allocate GL dispatch table" );
41 return FALSE;
42 }
43
44 lpData = (GLTHREADDATA*)HeapAlloc( GetProcessHeap(),
45 HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY,
46 sizeof (GLTHREADDATA) );
47 if (lpData == NULL)
48 {
49 DBGPRINT( "Error: Couldn't allocate GLTHREADDATA" );
50 HeapFree( GetProcessHeap(), 0, dispatchTable );
51 return FALSE;
52 }
53
54 teb = NtCurrentTeb();
55
56 /* initialize dispatch table with empty functions */
57 #define X(func, ret, typeargs, args, icdidx, tebidx, stack) \
58 dispatchTable[icdidx] = (PROC)glEmptyFunc##stack; \
59 if (tebidx >= 0) \
60 teb->glDispatchTable[tebidx] = (PVOID)glEmptyFunc##stack;
61 GLFUNCS_MACRO
62 #undef X
63
64 teb->glTable = dispatchTable;
65 TlsSetValue( OPENGL32_tls, lpData );
66
67 return TRUE;
68 }
69
70
71 static void
72 OPENGL32_ThreadDetach( void )
73 {
74 GLTHREADDATA* lpData = NULL;
75 TEB* teb = NtCurrentTeb();
76
77 rosglMakeCurrent( NULL, NULL );
78
79 lpData = (GLTHREADDATA*)TlsGetValue( OPENGL32_tls );
80 if (lpData != NULL)
81 {
82 if (!HeapFree( GetProcessHeap(), 0, lpData ))
83 DBGPRINT( "Warning: HeapFree() on GLTHREADDATA failed (%d)",
84 GetLastError() );
85 lpData = NULL;
86 }
87
88 if (teb->glTable != NULL)
89 {
90 if (!HeapFree( GetProcessHeap(), 0, teb->glTable ))
91 {
92 DBGPRINT( "Warning: HeapFree() on dispatch table failed (%d)",
93 GetLastError() );
94 }
95 /* NULL-ify it. Even if something went wrong, it's not a good idea to keep it non NULL */
96 teb->glTable = NULL;
97 }
98 }
99
100
101 static BOOL
102 OPENGL32_ProcessAttach( void )
103 {
104 SECURITY_ATTRIBUTES attrib = { sizeof (SECURITY_ATTRIBUTES), /* nLength */
105 NULL, /* lpSecurityDescriptor */
106 TRUE /* bInheritHandle */ };
107
108 OPENGL32_tls = TlsAlloc();
109 if (OPENGL32_tls == MAXDWORD)
110 return FALSE;
111
112 memset( &OPENGL32_processdata, 0, sizeof (OPENGL32_processdata) );
113
114 /* create driver, glrc & dcdata list mutex */
115 OPENGL32_processdata.driver_mutex = CreateMutex( &attrib, FALSE, NULL );
116 if (OPENGL32_processdata.driver_mutex == NULL)
117 {
118 DBGPRINT( "Error: Couldn't create driver_list mutex (%d)",
119 GetLastError() );
120 return FALSE;
121 }
122 OPENGL32_processdata.glrc_mutex = CreateMutex( &attrib, FALSE, NULL );
123 if (OPENGL32_processdata.glrc_mutex == NULL)
124 {
125 DBGPRINT( "Error: Couldn't create glrc_list mutex (%d)",
126 GetLastError() );
127 return FALSE;
128 }
129 OPENGL32_processdata.dcdata_mutex = CreateMutex( &attrib, FALSE, NULL );
130 if (OPENGL32_processdata.dcdata_mutex == NULL)
131 {
132 DBGPRINT( "Error: Couldn't create dcdata_list mutex (%d)",
133 GetLastError() );
134 return FALSE;
135 }
136
137 return TRUE;
138 }
139
140
141 static void
142 OPENGL32_ProcessDetach( void )
143 {
144 GLDRIVERDATA *icd, *icd2;
145 GLDCDATA *dcdata, *dcdata2;
146 GLRC *glrc, *glrc2;
147
148 /* free lists */
149 for (dcdata = OPENGL32_processdata.dcdata_list; dcdata != NULL;)
150 {
151 dcdata2 = dcdata;
152 dcdata = dcdata->next;
153 if (!HeapFree( GetProcessHeap(), 0, dcdata2 ))
154 DBGPRINT( "Warning: HeapFree() on DCDATA 0x%08x failed (%d)",
155 dcdata2, GetLastError() );
156 }
157
158 for (glrc = OPENGL32_processdata.glrc_list; glrc != NULL;)
159 {
160 glrc2 = glrc;
161 glrc = glrc->next;
162 if (!HeapFree( GetProcessHeap(), 0, glrc2 ))
163 DBGPRINT( "Warning: HeapFree() on GLRC 0x%08x failed (%d)",
164 glrc2, GetLastError() );
165 }
166
167 for (icd = OPENGL32_processdata.driver_list; icd != NULL;)
168 {
169 icd2 = icd;
170 icd = icd->next;
171 if (!HeapFree( GetProcessHeap(), 0, icd2 ))
172 DBGPRINT( "Warning: HeapFree() on DRIVERDATA 0x%08x failed (%d)",
173 icd2, GetLastError() );
174 }
175
176 /* free mutexes */
177 if (OPENGL32_processdata.driver_mutex != NULL)
178 CloseHandle( OPENGL32_processdata.driver_mutex );
179 if (OPENGL32_processdata.glrc_mutex != NULL)
180 CloseHandle( OPENGL32_processdata.glrc_mutex );
181 if (OPENGL32_processdata.dcdata_mutex != NULL)
182 CloseHandle( OPENGL32_processdata.dcdata_mutex );
183
184 /* free TLS */
185 if (OPENGL32_tls != MAXDWORD)
186 TlsFree(OPENGL32_tls);
187 }
188
189
190 BOOL WINAPI
191 DllMain(HINSTANCE hInstance, DWORD Reason, LPVOID Reserved)
192 {
193 DBGPRINT( "Info: Called!" );
194 switch ( Reason )
195 {
196 /* The DLL is loading due to process
197 * initialization or a call to LoadLibrary.
198 */
199 case DLL_PROCESS_ATTACH:
200 DBGTRACE( "Process attach" );
201 if (!OPENGL32_ProcessAttach())
202 return FALSE;
203 /* No break: Initialize the index for first thread. */
204
205 /* The attached process creates a new thread. */
206 case DLL_THREAD_ATTACH:
207 DBGTRACE( "Thread attach" );
208 if (!OPENGL32_ThreadAttach())
209 return FALSE;
210 break;
211
212 /* The thread of the attached process terminates. */
213 case DLL_THREAD_DETACH:
214 DBGTRACE( "Thread detach" );
215 /* Release the allocated memory for this thread.*/
216 OPENGL32_ThreadDetach();
217 break;
218
219 /* DLL unload due to process termination or FreeLibrary. */
220 case DLL_PROCESS_DETACH:
221 DBGTRACE( "Process detach" );
222 OPENGL32_ThreadDetach();
223 OPENGL32_ProcessDetach();
224 break;
225 }
226
227 return TRUE;
228 }
229
230
231 /*! \brief Append ICD to linked list.
232 *
233 * \param icd GLDRIVERDATA to append to list
234 *
235 * \note Only call this when you hold the driver_mutex.
236 */
237 static void
238 OPENGL32_AppendICD( GLDRIVERDATA *icd )
239 {
240 if (OPENGL32_processdata.driver_list == NULL)
241 OPENGL32_processdata.driver_list = icd;
242 else
243 {
244 GLDRIVERDATA *p = OPENGL32_processdata.driver_list;
245 while (p->next != NULL)
246 p = p->next;
247 p->next = icd;
248 }
249 }
250
251
252 /*! \brief Remove ICD from linked list.
253 *
254 * \param icd GLDRIVERDATA to remove from list
255 *
256 * \note Only call this when you hold the driver_mutex.
257 */
258 static void
259 OPENGL32_RemoveICD( GLDRIVERDATA *icd )
260 {
261 if (icd == OPENGL32_processdata.driver_list)
262 OPENGL32_processdata.driver_list = icd->next;
263 else
264 {
265 GLDRIVERDATA *p = OPENGL32_processdata.driver_list;
266 while (p != NULL)
267 {
268 if (p->next == icd)
269 {
270 p->next = icd->next;
271 return;
272 }
273 p = p->next;
274 }
275 DBGPRINT( "Error: ICD 0x%08x not found in list!", icd );
276 }
277 }
278
279
280 /*! \brief Load an ICD.
281 *
282 * \param driver Name of installable client driver.
283 *
284 * \return Pointer to an allocated GLDRIVERDATA struct.
285 * \retval NULL Failure.
286 *
287 * \todo Call SetLastError() where appropriate.
288 */
289 static GLDRIVERDATA *
290 OPENGL32_LoadDriver( LPCWSTR driver )
291 {
292 LONG ret;
293 GLDRIVERDATA *icd;
294
295 DBGPRINT( "Info: Loading driver %ws...", driver );
296
297 /* allocate driver data */
298 icd = (GLDRIVERDATA*)HeapAlloc( GetProcessHeap(),
299 HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY,
300 sizeof (GLDRIVERDATA) );
301 if (icd == NULL)
302 {
303 DBGPRINT( "Error: Couldn't allocate GLDRIVERDATA! (%d)", GetLastError() );
304 return NULL;
305 }
306
307 ret = OPENGL32_RegGetDriverInfo( driver, icd );
308 if (ret != ERROR_SUCCESS)
309 {
310 DBGPRINT( "Error: Couldn't query driver information (%d)", ret );
311 if (!HeapFree( GetProcessHeap(), 0, icd ))
312 DBGPRINT( "Error: HeapFree() returned false, error code = %d",
313 GetLastError() );
314 return NULL;
315 }
316
317 DBGPRINT( "Info: Dll = %ws", icd->dll );
318 DBGPRINT( "Info: Version = 0x%08x", icd->version );
319 DBGPRINT( "Info: DriverVersion = 0x%08x", icd->driver_version );
320 DBGPRINT( "Info: Flags = 0x%08x", icd->flags );
321
322 /* load/initialize ICD */
323 ret = OPENGL32_InitializeDriver( icd );
324 if (ret != ERROR_SUCCESS)
325 {
326 DBGPRINT( "Error: Couldnt initialize ICD!" );
327 if (!HeapFree( GetProcessHeap(), 0, icd ))
328 DBGPRINT( "Error: HeapFree() returned false, error code = %d",
329 GetLastError() );
330 return NULL;
331 }
332
333 /* append ICD to list */
334 OPENGL32_AppendICD( icd );
335 DBGPRINT( "Info: ICD loaded." );
336
337 return icd;
338 }
339
340
341 /*! \brief Initialize a driver (Load DLL and DrvXxx procs)
342 *
343 * \param icd ICD to initialize with the dll, version, driverVersion
344 * and flags already filled.
345 * \return Error code.
346 * \retval ERROR_SUCCESS Success
347 */
348 #define LOAD_DRV_PROC( icd, proc, required ) \
349 *(char**)&icd->proc = (char*)GetProcAddress( icd->handle, #proc ); \
350 if (required && icd->proc == NULL) { \
351 DBGPRINT( "Error: GetProcAddress(\"%s\") failed!", #proc ); \
352 FreeLibrary( icd->handle ); \
353 return GetLastError(); \
354 }
355
356 static DWORD
357 OPENGL32_InitializeDriver( GLDRIVERDATA *icd )
358 {
359 /* check version */
360 if (icd->version > 2)
361 DBGPRINT( "Warning: ICD version > 2 (%d)", icd->version );
362
363 /* load dll */
364 icd->handle = LoadLibraryW( icd->dll );
365 if (icd->handle == NULL)
366 {
367 DWORD err = GetLastError();
368 DBGPRINT( "Error: Couldn't load DLL! (%d)", err );
369 return err;
370 }
371
372 /* validate version */
373 if (icd->driver_version > 1)
374 {
375 LOAD_DRV_PROC(icd, DrvValidateVersion, FALSE);
376 if (icd->DrvValidateVersion != NULL)
377 {
378 if (!icd->DrvValidateVersion( icd->driver_version ))
379 {
380 DBGPRINT( "Error: DrvValidateVersion failed!" );
381 DBGBREAK();
382 FreeLibrary( icd->handle );
383 return ERROR_INVALID_FUNCTION; /* FIXME: use better error code */
384 }
385 }
386 else
387 DBGPRINT( "Info: DrvValidateVersion not exported by ICD" );
388 }
389
390 /* load DrvXXX procs */
391 LOAD_DRV_PROC(icd, DrvCopyContext, TRUE);
392 LOAD_DRV_PROC(icd, DrvCreateContext, FALSE);
393 LOAD_DRV_PROC(icd, DrvCreateLayerContext, FALSE);
394 LOAD_DRV_PROC(icd, DrvDeleteContext, TRUE);
395 LOAD_DRV_PROC(icd, DrvDescribeLayerPlane, TRUE);
396 LOAD_DRV_PROC(icd, DrvDescribePixelFormat, TRUE);
397 LOAD_DRV_PROC(icd, DrvGetLayerPaletteEntries, TRUE);
398 LOAD_DRV_PROC(icd, DrvGetProcAddress, TRUE);
399 LOAD_DRV_PROC(icd, DrvReleaseContext, TRUE);
400 LOAD_DRV_PROC(icd, DrvRealizeLayerPalette, TRUE);
401 LOAD_DRV_PROC(icd, DrvSetContext, TRUE);
402 LOAD_DRV_PROC(icd, DrvSetLayerPaletteEntries, TRUE);
403 LOAD_DRV_PROC(icd, DrvSetPixelFormat, TRUE);
404 LOAD_DRV_PROC(icd, DrvShareLists, TRUE);
405 LOAD_DRV_PROC(icd, DrvSwapBuffers, TRUE);
406 LOAD_DRV_PROC(icd, DrvSwapLayerBuffers, TRUE);
407
408 /* we require at least one of DrvCreateContext and DrvCreateLayerContext */
409 if (icd->DrvCreateContext == NULL && icd->DrvCreateLayerContext == NULL)
410 {
411 DBGPRINT( "Error: One of DrvCreateContext/DrvCreateLayerContext is required!" );
412 FreeLibrary( icd->handle );
413 return ERROR_INVALID_FUNCTION; /* FIXME: use better error code... */
414 }
415
416 return ERROR_SUCCESS;
417 }
418
419
420 /*! \brief Unload ICD.
421 *
422 * \retval TRUE Success.
423 * \retval FALSE Failure.
424 */
425 static BOOL
426 OPENGL32_UnloadDriver( GLDRIVERDATA *icd )
427 {
428 BOOL allOk = TRUE;
429
430 DBGPRINT( "Info: Unloading driver %ws...", icd->driver_name );
431 if (icd->refcount != 0)
432 DBGPRINT( "Warning: ICD refcount = %d (should be 0)", icd->refcount );
433
434 /* unload dll */
435 if (!FreeLibrary( icd->handle ))
436 {
437 allOk = FALSE;
438 DBGPRINT( "Warning: FreeLibrary on ICD %ws failed! (%d)", icd->dll,
439 GetLastError() );
440 }
441
442 /* free resources */
443 OPENGL32_RemoveICD( icd );
444 if (!HeapFree( GetProcessHeap(), 0, icd ))
445 {
446 allOk = FALSE;
447 DBGPRINT( "Warning: HeapFree() returned FALSE, error code = %d",
448 GetLastError() );
449 }
450
451 return allOk;
452 }
453
454
455 /*! \brief Load ICD (shared ICD data)
456 *
457 * \return Pointer to an allocated GLDRIVERDATA on success.
458 * \retval NULL Failure.
459 */
460 GLDRIVERDATA *
461 OPENGL32_LoadICD( LPCWSTR driver )
462 {
463 GLDRIVERDATA *icd;
464
465 /* synchronize */
466 if (WaitForSingleObject( OPENGL32_processdata.driver_mutex, INFINITE ) ==
467 WAIT_FAILED)
468 {
469 DBGPRINT( "Error: WaitForSingleObject() failed (%d)", GetLastError() );
470 return NULL; /* FIXME: do we have to expect such an error and handle it? */
471 }
472
473 /* look if ICD is already loaded */
474 for (icd = OPENGL32_processdata.driver_list; icd; icd = icd->next)
475 {
476 if (!_wcsicmp( driver, icd->driver_name )) /* found */
477 {
478 /* release mutex */
479 if (!ReleaseMutex( OPENGL32_processdata.driver_mutex ))
480 DBGPRINT( "Error: ReleaseMutex() failed (%d)", GetLastError() );
481
482 return icd;
483 }
484 }
485
486 /* not found - try to load */
487 icd = OPENGL32_LoadDriver( driver );
488
489 /* release mutex */
490 if (!ReleaseMutex( OPENGL32_processdata.driver_mutex ))
491 DBGPRINT( "Error: ReleaseMutex() failed (%d)", GetLastError() );
492
493 return icd;
494 }
495
496
497 /*! \brief Unload ICD (shared ICD data)
498 *
499 * \retval TRUE Success.
500 * \retval FALSE Failure.
501 */
502 BOOL
503 OPENGL32_UnloadICD( GLDRIVERDATA *icd )
504 {
505 BOOL ret = TRUE;
506
507 /* synchronize */
508 if (WaitForSingleObject( OPENGL32_processdata.driver_mutex, INFINITE ) ==
509 WAIT_FAILED)
510 {
511 DBGPRINT( "Error: WaitForSingleObject() failed (%d)", GetLastError() );
512 return FALSE; /* FIXME: do we have to expect such an error and handle it? */
513 }
514
515 if (icd->refcount == 0)
516 ret = OPENGL32_UnloadDriver( icd );
517
518 /* release mutex */
519 if (!ReleaseMutex( OPENGL32_processdata.driver_mutex ))
520 DBGPRINT( "Error: ReleaseMutex() failed (%d)", GetLastError() );
521
522 return ret;
523 }
524
525
526 /*! \brief Enumerate OpenGLDrivers (from registry)
527 *
528 * \param idx Index of the driver to get information about.
529 * \param name Pointer to an array of WCHARs (can be NULL)
530 * \param cName Pointer to a DWORD. Input is len of name array.
531 * Output is length of the drivername.
532 * Can be NULL if name is NULL.
533 *
534 * \return Error code
535 * \retval ERROR_NO_MORE_ITEMS End of driver list.
536 * \retval ERROR_SUCCESS Success.
537 */
538 #if 0 /* unused */
539 DWORD
540 OPENGL32_RegEnumDrivers( DWORD idx, LPWSTR name, LPDWORD cName )
541 {
542 HKEY hKey;
543 LPCWSTR subKey = OPENGL_DRIVERS_SUBKEY;
544 LONG ret;
545 DWORD size;
546 WCHAR driver[256];
547
548 if (name == NULL)
549 return ERROR_SUCCESS; /* nothing to do */
550
551 if (cName == NULL)
552 return ERROR_INVALID_FUNCTION; /* we need cName when name is given */
553
554 /* open OpenGLDrivers registry key */
555 ret = RegOpenKeyExW( HKEY_LOCAL_MACHINE, subKey, 0, KEY_READ, &hKey );
556 if (ret != ERROR_SUCCESS)
557 {
558 DBGPRINT( "Error: Couldn't open registry key '%ws'", subKey );
559 return ret;
560 }
561
562 /* get subkey name */
563 size = sizeof (driver) / sizeof (driver[0]);
564 ret = RegEnumKeyW( hKey, idx, name, *cName );
565 if (ret != ERROR_SUCCESS)
566 {
567 DBGPRINT( "Error: Couldn't get OpenGLDrivers subkey name (%d)", ret );
568 RegCloseKey( hKey );
569 return ret;
570 }
571 *cName = wcslen( name );
572
573 /* close key */
574 RegCloseKey( hKey );
575 return ERROR_SUCCESS;
576 }
577 #endif /* 0 -- unused */
578
579
580 /*! \brief Get registry values for a driver given a name.
581 *
582 * \param driver Name of the driver to get information about.
583 * \param icd Pointer to GLDRIVERDATA.
584 *
585 * \return Error code.
586 * \retval ERROR_SUCCESS Success.
587 *
588 * \note On success the following fields of \a icd are filled: \a driver_name,
589 * \a dll, \a version, \a driver_version and \a flags.
590 */
591 static DWORD
592 OPENGL32_RegGetDriverInfo( LPCWSTR driver, GLDRIVERDATA *icd )
593 {
594 HKEY hKey;
595 WCHAR subKey[1024] = OPENGL_DRIVERS_SUBKEY2;
596 LONG ret;
597 DWORD type, size;
598
599 /* drivers registry values */
600 DWORD version = 1, driverVersion = 0, flags = 0;
601 WCHAR dll[256];
602
603 /* open driver registry key */
604 wcsncat( subKey, driver, 1024 );
605 ret = RegOpenKeyExW( HKEY_LOCAL_MACHINE, subKey, 0, KEY_READ, &hKey );
606 if (ret != ERROR_SUCCESS)
607 {
608 DBGPRINT( "Error: Couldn't open registry key '%ws'", subKey );
609 return ret;
610 }
611
612 /* query values */
613 size = sizeof (dll);
614 ret = RegQueryValueExW( hKey, L"Dll", 0, &type, (LPBYTE)dll, &size );
615 if (ret != ERROR_SUCCESS || type != REG_SZ)
616 {
617 DBGPRINT( "Error: Couldn't query Dll value or not a string" );
618 RegCloseKey( hKey );
619 return ret;
620 }
621
622 size = sizeof (DWORD);
623 ret = RegQueryValueExW( hKey, L"Version", 0, &type, (LPBYTE)&version, &size );
624 if (ret != ERROR_SUCCESS || type != REG_DWORD)
625 DBGPRINT( "Warning: Couldn't query Version value or not a DWORD" );
626
627 size = sizeof (DWORD);
628 ret = RegQueryValueExW( hKey, L"DriverVersion", 0, &type,
629 (LPBYTE)&driverVersion, &size );
630 if (ret != ERROR_SUCCESS || type != REG_DWORD)
631 DBGPRINT( "Warning: Couldn't query DriverVersion value or not a DWORD" );
632
633 size = sizeof (DWORD);
634 ret = RegQueryValueExW( hKey, L"Flags", 0, &type, (LPBYTE)&flags, &size );
635 if (ret != ERROR_SUCCESS || type != REG_DWORD)
636 DBGPRINT( "Warning: Couldn't query Flags value or not a DWORD" );
637
638 /* close key */
639 RegCloseKey( hKey );
640
641 /* output data */
642 /* FIXME: NUL-terminate strings? */
643 wcsncpy( icd->driver_name, driver,
644 sizeof (icd->driver_name) / sizeof (icd->driver_name[0]) - 1 );
645 wcsncpy( icd->dll, dll,
646 sizeof (icd->dll) / sizeof (icd->dll[0]) );
647 icd->version = version;
648 icd->driver_version = driverVersion;
649 icd->flags = flags;
650
651 return ERROR_SUCCESS;
652 }
653
654 /* EOF */
655