]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/sys/win32/win_shared.cpp
hello world
[icculus/iodoom3.git] / neo / sys / win32 / win_shared.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 #include "../../idlib/precompiled.h"
30 #pragma hdrstop
31
32 #include "win_local.h"
33 #include <lmerr.h>
34 #include <lmcons.h>
35 #include <lmwksta.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <direct.h>
39 #include <io.h>
40 #include <conio.h>
41
42 #ifndef ID_DEDICATED
43 #include <comdef.h>
44 #include <comutil.h>
45 #include <Wbemidl.h>
46
47 #pragma comment (lib, "wbemuuid.lib")
48 #endif
49
50 /*
51 ================
52 Sys_Milliseconds
53 ================
54 */
55 int Sys_Milliseconds( void ) {
56         int sys_curtime;
57         static int sys_timeBase;
58         static bool     initialized = false;
59
60         if ( !initialized ) {
61                 sys_timeBase = timeGetTime();
62                 initialized = true;
63         }
64         sys_curtime = timeGetTime() - sys_timeBase;
65
66         return sys_curtime;
67 }
68
69 /*
70 ================
71 Sys_GetSystemRam
72
73         returns amount of physical memory in MB
74 ================
75 */
76 int Sys_GetSystemRam( void ) {
77         MEMORYSTATUSEX statex;
78         statex.dwLength = sizeof ( statex );
79         GlobalMemoryStatusEx (&statex);
80         int physRam = statex.ullTotalPhys / ( 1024 * 1024 );
81         // HACK: For some reason, ullTotalPhys is sometimes off by a meg or two, so we round up to the nearest 16 megs
82         physRam = ( physRam + 8 ) & ~15;
83         return physRam;
84 }
85
86
87 /*
88 ================
89 Sys_GetDriveFreeSpace
90 returns in megabytes
91 ================
92 */
93 int Sys_GetDriveFreeSpace( const char *path ) {
94         DWORDLONG lpFreeBytesAvailable;
95         DWORDLONG lpTotalNumberOfBytes;
96         DWORDLONG lpTotalNumberOfFreeBytes;
97         int ret = 26;
98         //FIXME: see why this is failing on some machines
99         if ( ::GetDiskFreeSpaceEx( path, (PULARGE_INTEGER)&lpFreeBytesAvailable, (PULARGE_INTEGER)&lpTotalNumberOfBytes, (PULARGE_INTEGER)&lpTotalNumberOfFreeBytes ) ) {
100                 ret = ( double )( lpFreeBytesAvailable ) / ( 1024.0 * 1024.0 );
101         }
102         return ret;
103 }
104
105
106 /*
107 ================
108 Sys_GetVideoRam
109 returns in megabytes
110 ================
111 */
112 int Sys_GetVideoRam( void ) {
113 #ifdef  ID_DEDICATED
114         return 0;
115 #else
116         unsigned int retSize = 64;
117
118         CComPtr<IWbemLocator> spLoc = NULL;
119         HRESULT hr = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_SERVER, IID_IWbemLocator, ( LPVOID * ) &spLoc );
120         if ( hr != S_OK || spLoc == NULL ) {
121                 return retSize;
122         }
123
124         CComBSTR bstrNamespace( _T( "\\\\.\\root\\CIMV2" ) );
125         CComPtr<IWbemServices> spServices;
126
127         // Connect to CIM
128         hr = spLoc->ConnectServer( bstrNamespace, NULL, NULL, 0, NULL, 0, 0, &spServices );
129         if ( hr != WBEM_S_NO_ERROR ) {
130                 return retSize;
131         }
132
133         // Switch the security level to IMPERSONATE so that provider will grant access to system-level objects.  
134         hr = CoSetProxyBlanket( spServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE );
135         if ( hr != S_OK ) {
136                 return retSize;
137         }
138
139         // Get the vid controller
140         CComPtr<IEnumWbemClassObject> spEnumInst = NULL;
141         hr = spServices->CreateInstanceEnum( CComBSTR( "Win32_VideoController" ), WBEM_FLAG_SHALLOW, NULL, &spEnumInst ); 
142         if ( hr != WBEM_S_NO_ERROR || spEnumInst == NULL ) {
143                 return retSize;
144         }
145
146         ULONG uNumOfInstances = 0;
147         CComPtr<IWbemClassObject> spInstance = NULL;
148         hr = spEnumInst->Next( 10000, 1, &spInstance, &uNumOfInstances );
149
150         if ( hr == S_OK && spInstance ) {
151                 // Get properties from the object
152                 CComVariant varSize;
153                 hr = spInstance->Get( CComBSTR( _T( "AdapterRAM" ) ), 0, &varSize, 0, 0 );
154                 if ( hr == S_OK ) {
155                         retSize = varSize.intVal / ( 1024 * 1024 );
156                         if ( retSize == 0 ) {
157                                 retSize = 64;
158                         }
159                 }
160         }
161         return retSize;
162 #endif
163 }
164
165 /*
166 ================
167 Sys_GetCurrentMemoryStatus
168
169         returns OS mem info
170         all values are in kB except the memoryload
171 ================
172 */
173 void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ) {
174         MEMORYSTATUSEX statex;
175         unsigned __int64 work;
176
177         memset( &statex, sizeof( statex ), 0 );
178         statex.dwLength = sizeof( statex );
179         GlobalMemoryStatusEx( &statex );
180
181         memset( &stats, 0, sizeof( stats ) );
182
183         stats.memoryLoad = statex.dwMemoryLoad;
184
185         work = statex.ullTotalPhys >> 20;
186         stats.totalPhysical = *(int*)&work;
187
188         work = statex.ullAvailPhys >> 20;
189         stats.availPhysical = *(int*)&work;
190
191         work = statex.ullAvailPageFile >> 20;
192         stats.availPageFile = *(int*)&work;
193
194         work = statex.ullTotalPageFile >> 20;
195         stats.totalPageFile = *(int*)&work;
196
197         work = statex.ullTotalVirtual >> 20;
198         stats.totalVirtual = *(int*)&work;
199
200         work = statex.ullAvailVirtual >> 20;
201         stats.availVirtual = *(int*)&work;
202
203         work = statex.ullAvailExtendedVirtual >> 20;
204         stats.availExtendedVirtual = *(int*)&work;
205 }
206
207 /*
208 ================
209 Sys_LockMemory
210 ================
211 */
212 bool Sys_LockMemory( void *ptr, int bytes ) {
213         return ( VirtualLock( ptr, (SIZE_T)bytes ) != FALSE );
214 }
215
216 /*
217 ================
218 Sys_UnlockMemory
219 ================
220 */
221 bool Sys_UnlockMemory( void *ptr, int bytes ) {
222         return ( VirtualUnlock( ptr, (SIZE_T)bytes ) != FALSE );
223 }
224
225 /*
226 ================
227 Sys_SetPhysicalWorkMemory
228 ================
229 */
230 void Sys_SetPhysicalWorkMemory( int minBytes, int maxBytes ) {
231         ::SetProcessWorkingSetSize( GetCurrentProcess(), minBytes, maxBytes );
232 }
233
234 /*
235 ================
236 Sys_GetCurrentUser
237 ================
238 */
239 char *Sys_GetCurrentUser( void ) {
240         static char s_userName[1024];
241         unsigned long size = sizeof( s_userName );
242
243
244         if ( !GetUserName( s_userName, &size ) ) {
245                 strcpy( s_userName, "player" );
246         }
247
248         if ( !s_userName[0] ) {
249                 strcpy( s_userName, "player" );
250         }
251
252         return s_userName;
253 }       
254
255
256 /*
257 ===============================================================================
258
259         Call stack
260
261 ===============================================================================
262 */
263
264
265 #define PROLOGUE_SIGNATURE 0x00EC8B55
266
267 #include <dbghelp.h>
268
269 const int UNDECORATE_FLAGS =    UNDNAME_NO_MS_KEYWORDS |
270                                                                 UNDNAME_NO_ACCESS_SPECIFIERS |
271                                                                 UNDNAME_NO_FUNCTION_RETURNS |
272                                                                 UNDNAME_NO_ALLOCATION_MODEL |
273                                                                 UNDNAME_NO_ALLOCATION_LANGUAGE |
274                                                                 UNDNAME_NO_MEMBER_TYPE;
275
276 #if defined(_DEBUG) && 1
277
278 typedef struct symbol_s {
279         int                                     address;
280         char *                          name;
281         struct symbol_s *       next;
282 } symbol_t;
283
284 typedef struct module_s {
285         int                                     address;
286         char *                          name;
287         symbol_t *                      symbols;
288         struct module_s *       next;
289 } module_t;
290
291 module_t *modules;
292
293 /*
294 ==================
295 SkipRestOfLine
296 ==================
297 */
298 void SkipRestOfLine( const char **ptr ) {
299         while( (**ptr) != '\0' && (**ptr) != '\n' && (**ptr) != '\r' ) {
300                 (*ptr)++;
301         }
302         while( (**ptr) == '\n' || (**ptr) == '\r' ) {
303                 (*ptr)++;
304         }
305 }
306
307 /*
308 ==================
309 SkipWhiteSpace
310 ==================
311 */
312 void SkipWhiteSpace( const char **ptr ) {
313         while( (**ptr) == ' ' ) {
314                 (*ptr)++;
315         }
316 }
317
318 /*
319 ==================
320 ParseHexNumber
321 ==================
322 */
323 int ParseHexNumber( const char **ptr ) {
324         int n = 0;
325         while( (**ptr) >= '0' && (**ptr) <= '9' || (**ptr) >= 'a' && (**ptr) <= 'f' ) {
326                 n <<= 4;
327                 if ( **ptr >= '0' && **ptr <= '9' ) {
328                         n |= ( (**ptr) - '0' );
329                 } else {
330                         n |= 10 + ( (**ptr) - 'a' );
331                 }
332                 (*ptr)++;
333         }
334         return n;
335 }
336
337 /*
338 ==================
339 Sym_Init
340 ==================
341 */
342 void Sym_Init( long addr ) {
343         TCHAR moduleName[MAX_STRING_CHARS];
344         MEMORY_BASIC_INFORMATION mbi;
345
346         VirtualQuery( (void*)addr, &mbi, sizeof(mbi) );
347
348         GetModuleFileName( (HMODULE)mbi.AllocationBase, moduleName, sizeof( moduleName ) );
349
350         char *ext = moduleName + strlen( moduleName );
351         while( ext > moduleName && *ext != '.' ) {
352                 ext--;
353         }
354         if ( ext == moduleName ) {
355                 strcat( moduleName, ".map" );
356         } else {
357                 strcpy( ext, ".map" );
358         }
359
360         module_t *module = (module_t *) malloc( sizeof( module_t ) );
361         module->name = (char *) malloc( strlen( moduleName ) + 1 );
362         strcpy( module->name, moduleName );
363         module->address = (int)mbi.AllocationBase;
364         module->symbols = NULL;
365         module->next = modules;
366         modules = module;
367
368         FILE *fp = fopen( moduleName, "rb" );
369         if ( fp == NULL ) {
370                 return;
371         }
372
373         int pos = ftell( fp );
374         fseek( fp, 0, SEEK_END );
375         int length = ftell( fp );
376         fseek( fp, pos, SEEK_SET );
377
378         char *text = (char *) malloc( length+1 );
379         fread( text, 1, length, fp );
380         text[length] = '\0';
381         fclose( fp );
382
383         const char *ptr = text;
384
385         // skip up to " Address" on a new line
386         while( *ptr != '\0' ) {
387                 SkipWhiteSpace( &ptr );
388                 if ( idStr::Cmpn( ptr, "Address", 7 ) == 0 ) {
389                         SkipRestOfLine( &ptr );
390                         break;
391                 }
392                 SkipRestOfLine( &ptr );
393         }
394
395         int symbolAddress;
396         int symbolLength;
397         char symbolName[MAX_STRING_CHARS];
398         symbol_t *symbol;
399
400         // parse symbols
401         while( *ptr != '\0' ) {
402
403                 SkipWhiteSpace( &ptr );
404
405                 ParseHexNumber( &ptr );
406                 if ( *ptr == ':' ) {
407                         ptr++;
408                 } else {
409                         break;
410                 }
411                 ParseHexNumber( &ptr );
412
413                 SkipWhiteSpace( &ptr );
414
415                 // parse symbol name
416                 symbolLength = 0;
417                 while( *ptr != '\0' && *ptr != ' ' ) {
418                         symbolName[symbolLength++] = *ptr++;
419                         if ( symbolLength >= sizeof( symbolName ) - 1 ) {
420                                 break;
421                         }
422                 }
423                 symbolName[symbolLength++] = '\0';
424
425                 SkipWhiteSpace( &ptr );
426
427                 // parse symbol address
428                 symbolAddress = ParseHexNumber( &ptr );
429
430                 SkipRestOfLine( &ptr );
431
432                 symbol = (symbol_t *) malloc( sizeof( symbol_t ) );
433                 symbol->name = (char *) malloc( symbolLength );
434                 strcpy( symbol->name, symbolName );
435                 symbol->address = symbolAddress;
436                 symbol->next = module->symbols;
437                 module->symbols = symbol;
438         }
439
440         free( text );
441 }
442
443 /*
444 ==================
445 Sym_Shutdown
446 ==================
447 */
448 void Sym_Shutdown( void ) {
449         module_t *m;
450         symbol_t *s;
451
452         for ( m = modules; m != NULL; m = modules ) {
453                 modules = m->next;
454                 for ( s = m->symbols; s != NULL; s = m->symbols ) {
455                         m->symbols = s->next;
456                         free( s->name );
457                         free( s );
458                 }
459                 free( m->name );
460                 free( m );
461         }
462         modules = NULL;
463 }
464
465 /*
466 ==================
467 Sym_GetFuncInfo
468 ==================
469 */
470 void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) {
471         MEMORY_BASIC_INFORMATION mbi;
472         module_t *m;
473         symbol_t *s;
474
475         VirtualQuery( (void*)addr, &mbi, sizeof(mbi) );
476
477         for ( m = modules; m != NULL; m = m->next ) {
478                 if ( m->address == (int) mbi.AllocationBase ) {
479                         break;
480                 }
481         }
482         if ( !m ) {
483                 Sym_Init( addr );
484                 m = modules;
485         }
486
487         for ( s = m->symbols; s != NULL; s = s->next ) {
488                 if ( s->address == addr ) {
489
490                         char undName[MAX_STRING_CHARS];
491                         if ( UnDecorateSymbolName( s->name, undName, sizeof(undName), UNDECORATE_FLAGS ) ) {
492                                 funcName = undName;
493                         } else {
494                                 funcName = s->name;
495                         }
496                         for ( int i = 0; i < funcName.Length(); i++ ) {
497                                 if ( funcName[i] == '(' ) {
498                                         funcName.CapLength( i );
499                                         break;
500                                 }
501                         }
502                         module = m->name;
503                         return;
504                 }
505         }
506
507         sprintf( funcName, "0x%08x", addr );
508         module = "";
509 }
510
511 #elif defined(_DEBUG)
512
513 DWORD lastAllocationBase = -1;
514 HANDLE processHandle;
515 idStr lastModule;
516
517 /*
518 ==================
519 Sym_Init
520 ==================
521 */
522 void Sym_Init( long addr ) {
523         TCHAR moduleName[MAX_STRING_CHARS];
524         TCHAR modShortNameBuf[MAX_STRING_CHARS];
525         MEMORY_BASIC_INFORMATION mbi;
526
527         if ( lastAllocationBase != -1 ) {
528                 Sym_Shutdown();
529         }
530
531         VirtualQuery( (void*)addr, &mbi, sizeof(mbi) );
532
533         GetModuleFileName( (HMODULE)mbi.AllocationBase, moduleName, sizeof( moduleName ) );
534         _splitpath( moduleName, NULL, NULL, modShortNameBuf, NULL );
535         lastModule = modShortNameBuf;
536
537         processHandle = GetCurrentProcess();
538         if ( !SymInitialize( processHandle, NULL, FALSE ) ) {
539                 return;
540         }
541         if ( !SymLoadModule( processHandle, NULL, moduleName, NULL, (DWORD)mbi.AllocationBase, 0 ) ) {
542                 SymCleanup( processHandle );
543                 return;
544         }
545
546         SymSetOptions( SymGetOptions() & ~SYMOPT_UNDNAME );
547
548         lastAllocationBase = (DWORD) mbi.AllocationBase;
549 }
550
551 /*
552 ==================
553 Sym_Shutdown
554 ==================
555 */
556 void Sym_Shutdown( void ) {
557         SymUnloadModule( GetCurrentProcess(), lastAllocationBase );
558         SymCleanup( GetCurrentProcess() );
559         lastAllocationBase = -1;
560 }
561
562 /*
563 ==================
564 Sym_GetFuncInfo
565 ==================
566 */
567 void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) {
568         MEMORY_BASIC_INFORMATION mbi;
569
570         VirtualQuery( (void*)addr, &mbi, sizeof(mbi) );
571
572         if ( (DWORD) mbi.AllocationBase != lastAllocationBase ) {
573                 Sym_Init( addr );
574         }
575
576         BYTE symbolBuffer[ sizeof(IMAGEHLP_SYMBOL) + MAX_STRING_CHARS ];
577         PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)&symbolBuffer[0];
578         pSymbol->SizeOfStruct = sizeof(symbolBuffer);
579         pSymbol->MaxNameLength = 1023;
580         pSymbol->Address = 0;
581         pSymbol->Flags = 0;
582         pSymbol->Size =0;
583
584         DWORD symDisplacement = 0;
585         if ( SymGetSymFromAddr( processHandle, addr, &symDisplacement, pSymbol ) ) {
586                 // clean up name, throwing away decorations that don't affect uniqueness
587             char undName[MAX_STRING_CHARS];
588                 if ( UnDecorateSymbolName( pSymbol->Name, undName, sizeof(undName), UNDECORATE_FLAGS ) ) {
589                         funcName = undName;
590                 } else {
591                         funcName = pSymbol->Name;
592                 }
593                 module = lastModule;
594         }
595         else {
596                 LPVOID lpMsgBuf;
597                 FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
598                                                 NULL,
599                                                 GetLastError(),
600                                                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
601                                                 (LPTSTR) &lpMsgBuf,
602                                                 0,
603                                                 NULL 
604                                                 );
605                 LocalFree( lpMsgBuf );
606
607                 // Couldn't retrieve symbol (no debug info?, can't load dbghelp.dll?)
608                 sprintf( funcName, "0x%08x", addr );
609                 module = "";
610     }
611 }
612
613 #else
614
615 /*
616 ==================
617 Sym_Init
618 ==================
619 */
620 void Sym_Init( long addr ) {
621 }
622
623 /*
624 ==================
625 Sym_Shutdown
626 ==================
627 */
628 void Sym_Shutdown( void ) {
629 }
630
631 /*
632 ==================
633 Sym_GetFuncInfo
634 ==================
635 */
636 void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) {
637         module = "";
638         sprintf( funcName, "0x%08x", addr );
639 }
640
641 #endif
642
643 /*
644 ==================
645 GetFuncAddr
646 ==================
647 */
648 address_t GetFuncAddr( address_t midPtPtr ) {
649         long temp;
650         do {
651                 temp = (long)(*(long*)midPtPtr);
652                 if ( (temp&0x00FFFFFF) == PROLOGUE_SIGNATURE ) {
653                         break;
654                 }
655                 midPtPtr--;
656         } while(true);
657
658         return midPtPtr;
659 }
660
661 /*
662 ==================
663 GetCallerAddr
664 ==================
665 */
666 address_t GetCallerAddr( long _ebp ) {
667         long midPtPtr;
668         long res = 0;
669
670         __asm {
671                 mov             eax, _ebp
672                 mov             ecx, [eax]              // check for end of stack frames list
673                 test    ecx, ecx                // check for zero stack frame
674                 jz              label
675                 mov             eax, [eax+4]    // get the ret address
676                 test    eax, eax                // check for zero return address
677                 jz              label
678                 mov             midPtPtr, eax
679         }
680         res = GetFuncAddr( midPtPtr );
681 label:
682         return res;
683 }
684
685 /*
686 ==================
687 Sys_GetCallStack
688
689  use /Oy option
690 ==================
691 */
692 void Sys_GetCallStack( address_t *callStack, const int callStackSize ) {
693 #if 1 //def _DEBUG
694         int i;
695         long m_ebp;
696
697         __asm {
698                 mov eax, ebp
699                 mov m_ebp, eax
700         }
701         // skip last two functions
702         m_ebp = *((long*)m_ebp);
703         m_ebp = *((long*)m_ebp);
704         // list functions
705         for ( i = 0; i < callStackSize; i++ ) {
706                 callStack[i] = GetCallerAddr( m_ebp );
707                 if ( callStack[i] == 0 ) {
708                         break;
709                 }
710                 m_ebp = *((long*)m_ebp);
711         }
712 #else
713         int i = 0;
714 #endif
715         while( i < callStackSize ) {
716                 callStack[i++] = 0;
717         }
718 }
719
720 /*
721 ==================
722 Sys_GetCallStackStr
723 ==================
724 */
725 const char *Sys_GetCallStackStr( const address_t *callStack, const int callStackSize ) {
726         static char string[MAX_STRING_CHARS*2];
727         int index, i;
728         idStr module, funcName;
729
730         index = 0;
731         for ( i = callStackSize-1; i >= 0; i-- ) {
732                 Sym_GetFuncInfo( callStack[i], module, funcName );
733                 index += sprintf( string+index, " -> %s", funcName.c_str() );
734         }
735         return string;
736 }
737
738 /*
739 ==================
740 Sys_GetCallStackCurStr
741 ==================
742 */
743 const char *Sys_GetCallStackCurStr( int depth ) {
744         address_t *callStack;
745
746         callStack = (address_t *) _alloca( depth * sizeof( address_t ) );
747         Sys_GetCallStack( callStack, depth );
748         return Sys_GetCallStackStr( callStack, depth );
749 }
750
751 /*
752 ==================
753 Sys_GetCallStackCurAddressStr
754 ==================
755 */
756 const char *Sys_GetCallStackCurAddressStr( int depth ) {
757         static char string[MAX_STRING_CHARS*2];
758         address_t *callStack;
759         int index, i;
760
761         callStack = (address_t *) _alloca( depth * sizeof( address_t ) );
762         Sys_GetCallStack( callStack, depth );
763
764         index = 0;
765         for ( i = depth-1; i >= 0; i-- ) {
766                 index += sprintf( string+index, " -> 0x%08x", callStack[i] );
767         }
768         return string;
769 }
770
771 /*
772 ==================
773 Sys_ShutdownSymbols
774 ==================
775 */
776 void Sys_ShutdownSymbols( void ) {
777         Sym_Shutdown();
778 }