| // MemDebugStats.cpp : Implementation of the heap memory-dump library
// Author: Juan Carlos "JCAB" Arévalo Baeza <mailto:jcab@roningames.com>
// (C) 2000-2001 Ronin Entertainment <http://www.roningames.com>
// You may use this as you like, as long as you don't remove the copyright.
#include "stdafx.h"
 #include "MemDebugStats.h"
 
 #pragma warning(disable: 4786)	// identifier was truncated to '255' characters in the debug information
#include <windows.h>
#include <CrtDbg.h>
#include <TLHelp32.h>
 
 #include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
 
 #include <map>
#include <string>
 
 
 
 // Unrelated common definitions
typedef unsigned char uchar;
typedef unsigned int  uint;
 
 #define RONIN_PRINTF(s,n) { \
	char ronin__s[n];					\
	va_list ronin__list;				\
										\
	va_start(ronin__list, s);			\
	vsprintf(ronin__s, s, ronin__list);	\
	va_end(ronin__list);				\
	s = ronin__s;						\
}
 
 
 
 // Prints out to the file AND to OutputDebugString().
static void MultiPrintf(FILE *f, const char *fmt, ...)
{
	RONIN_PRINTF(fmt, 4096);
	if (f) {
		fprintf(f, "%s", fmt);
	}
	OutputDebugString(fmt);
}
 
 static void DumpProcessInfo(FILE *f)
{
	SYSTEM_INFO si;
	GetSystemInfo(&si);
 
 MultiPrintf(f, "\n-------------------- Current process info\n\n");
 
 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST | TH32CS_SNAPMODULE, 0);
 
 DWORD dataAlloc	= 0;
 
 MODULEENTRY32 me;
	me.dwSize = sizeof(me);
	bool moduleValid = !!Module32First(snapshot, &me);
	while (moduleValid) {
		if ((DWORD)me.modBaseAddr >= (DWORD)si.lpMinimumApplicationAddress && (DWORD)me.modBaseAddr < (DWORD)si.lpMaximumApplicationAddress) {
			MultiPrintf(f, "Process module: %8x, %13.3f Kbytes, %-32s : %s\n", me.modBaseAddr, me.modBaseSize / 1024.F, me.szModule, me.szExePath);
 
 dataAlloc += me.modBaseSize;
		}
 
 moduleValid = !!Module32Next(snapshot, &me);
	}
 
 MultiPrintf(f, "\nTotal process data: %13.3f Kbytes\n\n", dataAlloc / 1024.F);
 
 me.dwSize = sizeof(me);
	moduleValid = !!Module32First(snapshot, &me);
	while (moduleValid) {
		if (!((DWORD)me.modBaseAddr >= (DWORD)si.lpMinimumApplicationAddress && (DWORD)me.modBaseAddr < (DWORD)si.lpMaximumApplicationAddress)) {
			MultiPrintf(f, "System module: %8x, %13.3f Kbytes, %-32s : %s\n", me.modBaseAddr, me.modBaseSize / 1024.F, me.szModule, me.szExePath);
		}
 
 moduleValid = !!Module32Next(snapshot, &me);
	}
 
 // The following is VERY slow on Win2000, so I usually keep it disabled.
	// Win98 is reasonably fast, thoguh. Dunno why.
/*
	DWORD totalAlloc	= dataAlloc;
	DWORD totalFree		= 0;
 
 HEAPLIST32 hl;
	hl.dwSize = sizeof(hl);
	bool heapValid = !!Heap32ListFirst(snapshot, &hl);
	while (heapValid) {
		if (hl.dwFlags & HF32_DEFAULT) {
			MultiPrintf(f, "Default heap found!!\n");
		} else {
			MultiPrintf(f, "Heap found!!\n");
		}
 
 DWORD heapAlloc	= 0;
		DWORD heapFree	= 0;
 
 DWORD numHeapAlloc	= 0;
		DWORD numHeapFree	= 0;
 
 HEAPENTRY32 he;
		he.dwSize = sizeof(he);
		bool entryValid = !!Heap32First(&he, hl.th32ProcessID, hl.th32HeapID);
		while (entryValid) {
			if (he.dwFlags == LF32_FIXED) {
				// Uncomment to log out every single "fixed" block.
//				MultiPrintf(f, "    Block: %8x, %13.3f Kbytes, fixed\n", he.dwAddress, he.dwBlockSize / 1024.F);
				heapAlloc += he.dwBlockSize;
				++numHeapAlloc;
			} else if (he.dwFlags == LF32_FREE) {
				// Uncomment to log out every single "freed" block.
//				MultiPrintf(f, "    Block: %8x, %13.3f Kbytes, free\n", he.dwAddress, he.dwBlockSize / 1024.F);
				heapFree += he.dwBlockSize;
				++numHeapFree;
			} else if (he.dwFlags == LF32_MOVEABLE) {
				// Uncomment to log out every single "moveable" block.
//				MultiPrintf(f, "    Block: %8x, %13.3f Kbytes, moveable\n", he.dwAddress, he.dwBlockSize / 1024.F);
				heapAlloc += he.dwBlockSize;
				++numHeapAlloc;
			} else {
				MultiPrintf(f, "    Block: %8x, %13.3f Kbytes, <unknown: %8x>\n", he.dwAddress, he.dwBlockSize / 1024.F, he.dwFlags);
			}
 
 entryValid = !!Heap32Next(&he);
		}
 
 MultiPrintf(f, "    Heap: %13.3f Kbytes allocated, %13.3f Kbytes free\n", heapAlloc / 1024.F, heapFree / 1024.F);
 
 totalAlloc	+= heapAlloc;
		totalFree	+= heapFree;
 
 heapValid = !!Heap32ListNext(snapshot, &hl);
	}
 
 MultiPrintf(f, "Total: %13.3f Kbytes allocated, %13.3f Kbytes free\n", totalAlloc / 1024.F, totalFree / 1024.F);
//*/
	CloseHandle(snapshot);
}
 
 static void DumpGlobalInfo(FILE *f)
{
	MEMORYSTATUS ms;
	GlobalMemoryStatus(&ms);
	
	MultiPrintf(f, "\n-------------------- System info\n\n");
	
	MultiPrintf(f, "Memory load:         %8d %%\n", ms.dwMemoryLoad);
	MultiPrintf(f, "Memory total:        %12.3f MB\n",  ms.dwTotalPhys								/ (1024.F * 1024.F));
	MultiPrintf(f, "Memory used:         %12.3f MB\n", (ms.dwTotalPhys - ms.dwAvailPhys)			/ (1024.F * 1024.F));
	MultiPrintf(f, "Memory available:    %12.3f MB\n",  ms.dwAvailPhys								/ (1024.F * 1024.F));
	MultiPrintf(f, "Swap file total:     %12.3f MB\n",  ms.dwTotalPageFile							/ (1024.F * 1024.F));
	MultiPrintf(f, "Swap file in use:    %12.3f MB\n", (ms.dwTotalPageFile - ms.dwAvailPageFile)	/ (1024.F * 1024.F));
	MultiPrintf(f, "Swap file available: %12.3f MB\n",  ms.dwAvailPageFile							/ (1024.F * 1024.F));
	MultiPrintf(f, "Virtual total:       %12.3f MB\n",  ms.dwTotalVirtual							/ (1024.F * 1024.F));
	MultiPrintf(f, "Virtual in use:      %12.3f MB\n", (ms.dwTotalVirtual - ms.dwAvailVirtual)		/ (1024.F * 1024.F));
	MultiPrintf(f, "Virtual available:   %12.3f MB\n",  ms.dwAvailVirtual							/ (1024.F * 1024.F));
	MultiPrintf(f, "\n");
}
 
 void GetMemSnapshot(MemDebugSnapshot &snap)
{
	delete snap.hookedBlock;
	snap.hookedBlock = new int(0xBAADF0F0);
	_CrtMemCheckpoint(&snap.memState);
}
 
 #ifndef NDEBUG
 
 struct FileLine {
	std::string	name;
	int			line;
 
 FileLine(): name(), line(0) {}
	FileLine(const std::string &name_, int line_): name(name_), line(line_) {}
 
 friend bool operator <(const FileLine &fl1, const FileLine &fl2) {
		const int comp = strcmp(fl1.name.c_str(), fl2.name.c_str());
		if (comp < 0) return true;
		if (comp > 0) return false;
		return fl1.line < fl2.line;
	}
};
 
 struct SizeInfo {
	int numBlocksPerSize[32];
	int totalSizePerSize[32];
	int totalBlocks;
	int totalSize;
 
 SizeInfo(): totalBlocks(0), totalSize(0) {
		memset(&numBlocksPerSize, 0, sizeof(numBlocksPerSize));
		memset(&totalSizePerSize, 0, sizeof(totalSizePerSize));
	}
 
 SizeInfo &operator+=(const SizeInfo &other) {
		uint i;
		for (i = 0; i < 32; ++i) {
			numBlocksPerSize[i] += other.numBlocksPerSize[i];
			totalSizePerSize[i] += other.totalSizePerSize[i];
		}
		totalBlocks	+= other.totalBlocks;
		totalSize	+= other.totalSize;
		return *this;
	}
 
 SizeInfo &operator-=(const SizeInfo &other) {
		uint i;
		for (i = 0; i < 32; ++i) {
			numBlocksPerSize[i] -= other.numBlocksPerSize[i];
			totalSizePerSize[i] -= other.totalSizePerSize[i];
		}
		totalBlocks	-= other.totalBlocks;
		totalSize	-= other.totalSize;
		return *this;
	}
};
 
 struct FileLineSizeInfo: FileLine, SizeInfo {
	FileLineSizeInfo() {}
	FileLineSizeInfo(const FileLine &fl, const SizeInfo &si):
		FileLine(fl),
		SizeInfo(si)
	{}
};
 
 struct FileSizeInfo: SizeInfo {
	std::string	name;
 
 FileSizeInfo() {}
	FileSizeInfo(const std::string &n, const SizeInfo &si):
		name(n),
		SizeInfo(si)
	{}
};
 
 // There's no way to do without the file-scope variables,
// because the report hook function doesn't have the extra
// "custom data" pointer that EVERY C-style callback must have.
// Bad move by the boys at Microsoft.
static std::string	g_LastFName	= std::string();
static int			g_LastLine	= 0;
 
 static SizeInfo g_UnnamedSizeInfo;
static SizeInfo g_TotalSizeInfo;
 
 static std::map<FileLine, SizeInfo>	g_FileLineMap;
 
 static FILE *g_Fileo = NULL;
 
 static void ClearFileData()
{
	g_LastFName	= std::string();
	g_LastLine	= 0;
 
 g_UnnamedSizeInfo;
	g_TotalSizeInfo;
 
 g_FileLineMap.clear();
 
 g_Fileo = NULL;
}
 
 static int GatherStatsReportHook(int reportType, char *message, int *returnValue)
{
	if (returnValue) {
		*returnValue = 0;
	}
	int n = strlen(message) - 4;
	if (n > 0 && message[n] == ')' && message[n+2] == ':') {
		int e = n-1;
		while (e > 0 && message[e] != '(') {
			--e;
		}
		if (message[e] == '(') {
			g_LastFName.assign(message, e);
			sscanf(message+e+1, "%d", &g_LastLine);
			return true;
		}
	}
	void *ptr;
	int size;
	int subType = 0;
	if (sscanf(message, "normal block at 0x%x, %d", &ptr, &size) == 2 ||
		sscanf(message, "crt block at 0x%x, subtype %d, %d", &ptr, &subType, &size) == 3) {
		SizeInfo *si;
		if (!g_LastFName.empty()) {
			si = &g_FileLineMap[FileLine(g_LastFName, g_LastLine)];
		} else {
			si = &g_UnnamedSizeInfo;
		}
		int block = 0;
		int bsize = size;
		while (bsize > 0 && block < 31) {
			bsize >>= 1;
			++block;
		}
		++si->numBlocksPerSize[block];
		si->totalSizePerSize[block] += size;
		++si->totalBlocks;
		si->totalSize += size;
		++g_TotalSizeInfo.numBlocksPerSize[block];
		g_TotalSizeInfo.totalSizePerSize[block] += size;
		++g_TotalSizeInfo.totalBlocks;
		g_TotalSizeInfo.totalSize += size;
		g_LastFName[0] = 0;
		return true;
	}
	return true;
}
 
 static int OutputReportHook(int reportType, char *message, int *returnValue)
{
	if (returnValue) {
		*returnValue = 0;
	}
	MultiPrintf(g_Fileo, "%s", message);
	return true;
}
 
 static void ReportBlock(FILE *f, const SizeInfo &si)
{
	if (si.numBlocksPerSize[0] > 0) {
		MultiPrintf(f, "    Blocks of size 0           : %6d blocks\n", si.numBlocksPerSize[0]);
	}
 
 if (si.numBlocksPerSize[1] > 0) {
		MultiPrintf(f, "    Blocks of size 1           : %6d blocks\n", si.numBlocksPerSize[1]);
	}
 
 int bsize = 2;
	int i;
	for (i = 2; i < 32; ++i) {
		if (si.numBlocksPerSize[i] > 0) {
			MultiPrintf(f, "    Between %7d and %7d: %6d blocks, %10d bytes total, %11.2f avg. bytes/block\n", bsize, (bsize << 1) - 1, si.numBlocksPerSize[i], si.totalSizePerSize[i], (float) si.totalSizePerSize[i] / si.numBlocksPerSize[i]);
		}
		bsize <<= 1;
	}
}
 
 static void DumpSingleBlock(FILE *f, const char *header, const SizeInfo &si, bool fmt)
{
	if (fmt) {
		if (si.totalBlocks > 0) {
			MultiPrintf(f, "%-75s : %6d | %8d | %11.2f\n", header, si.totalBlocks, si.totalSize, (float)si.totalSize / si.totalBlocks);
		} else {
			MultiPrintf(f, "%-75s : %6d | %8d | %11.2f\n", header, 0, 0, 0);
		}
	} else {
		if (si.totalBlocks > 0) {
			MultiPrintf(f, "%s : Total %d blocks, %d bytes, %.2f avg bytes/block\n", header, si.totalBlocks, si.totalSize, (float)si.totalSize / si.totalBlocks);
		} else {
			MultiPrintf(f, "%s : Total 0 blocks, 0 bytes, 0 avg bytes/block\n", header);
		}
	}
	if (!fmt) {
		int n = 0;
		int j;
		for (j = 0; j < 32; ++j) {
			if (si.numBlocksPerSize[j] > 0) {
				++n;
			}
		}
		if (n > 1) {
			ReportBlock(f, si);
		}
	}
}
#endif
 
 static FILE *OpenLogFile()
{
	int j;
	for (j = 0; j < 0x10000; ++j) {
		char fname[4096];
		sprintf(fname, "c:\\MEM%04x.log", j);
		FILE *f = fopen(fname, "r");
		if (f == NULL) {
			return fopen(fname, "wt");
		} else {
			fclose(f);
		}
	}
	return NULL;
}
 
 void DumpMemStats(MemDebugSnapshot &snap, const char *file, unsigned int line)
{
	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
 
 FILE *f = OpenLogFile();
 
 MultiPrintf(f, "\n-------------------------------------------------------------------------------\n");
 
 if (file) {
		MultiPrintf(f, "\n%s(%d): Code line that executed the dump\n\n", file, line);
	} else {
		MultiPrintf(f, "\nPlease, use the macros to execute the dump!!\n\n");
	}
 
 #ifndef NDEBUG
	{
		MultiPrintf(f, "-------------------- Begin DumpMemStats\n\n");
 
 _CRT_REPORT_HOOK g_OldReportHook = _CrtSetReportHook(GatherStatsReportHook);
		_CrtMemDumpAllObjectsSince(&snap.memState);
		_CrtSetReportHook(g_OldReportHook);
 
 {
			DumpSingleBlock(f, "Total blocks", g_TotalSizeInfo, false);
			DumpSingleBlock(f, "Unnamed blocks", g_UnnamedSizeInfo, false);
			SizeInfo namedSizeInfo = g_TotalSizeInfo;
			namedSizeInfo += g_UnnamedSizeInfo;
			DumpSingleBlock(f, "Named blocks", namedSizeInfo, false);
		}
 
 MultiPrintf(f, "\n-------------------- Stats sorted by file name and line number\n\n");
 
 std::map<std::string, SizeInfo> FileMap;
 
 {
			SizeInfo FileAccum;
			std::string lastFile;
			int FileLines = 0;
 
 SizeInfo totals;
 
 std::map<FileLine, SizeInfo>::const_iterator i;
			for (i = g_FileLineMap.begin(); i != g_FileLineMap.end(); ++i) {
				if (lastFile != i->first.name) {
					if (FileLines > 0) {
						FileMap[lastFile] = FileAccum;
					}
					FileAccum = SizeInfo();
					lastFile = i->first.name;
					FileLines = 0;
				}
 
 char header[4096];
				sprintf(header, "%s(%d)", i->first.name.c_str(), i->first.line);
				DumpSingleBlock(f, header, i->second, false);
				++FileLines;
				FileAccum += i->second;
				totals += i->second;
			}
 
 if (FileLines > 0) {
				FileMap[lastFile] = FileAccum;
			}
 
 if (totals.totalBlocks > 0) {
				MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
			} else {
				MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
			}
		}
 
 {
			MultiPrintf(f, "\n--------- Formatted\n\n");
			MultiPrintf(f, "%-75s : blocks |    bytes | avg bytes/block\n", " ");
			MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
 
 SizeInfo totals;
 
 std::map<FileLine, SizeInfo>::const_iterator i;
			for (i = g_FileLineMap.begin(); i != g_FileLineMap.end(); ++i) {
				char header[4096];
				sprintf(header, "%-68s(%5d)", i->first.name.c_str(), i->first.line);
				DumpSingleBlock(f, header, i->second, true);
				totals += i->second;
			}
 
 if (totals.totalBlocks > 0) {
				MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
			} else {
				MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
			}
		}
 
 std::multimap<int, FileSizeInfo>	BlockFilesMap;
 
 {
			MultiPrintf(f, "\n-------------------- Stats sorted by file name\n\n");
			MultiPrintf(f, "%-75s : blocks |    bytes | avg bytes/block\n", " ");
			MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
 
 SizeInfo totals;
 
 std::map<std::string, SizeInfo>::const_iterator i;
			for (i = FileMap.begin(); i != FileMap.end(); ++i) {
				char header[4096];
				sprintf(header, "%-75s", i->first.c_str());
				DumpSingleBlock(f, header, i->second, true);
				BlockFilesMap.insert(std::pair<const int, FileSizeInfo>(i->second.totalBlocks, FileSizeInfo(i->first, i->second)));
				totals += i->second;
			}
 
 if (totals.totalBlocks > 0) {
				MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
			} else {
				MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
			}
		}
 
 std::multimap<int, FileSizeInfo>	SizeFilesMap;
 
 {
			MultiPrintf(f, "\n-------------------- File stats sorted by number of blocks\n\n");
			MultiPrintf(f, "%-75s : blocks |    bytes | avg bytes/block\n", " ");
			MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
 
 SizeInfo totals;
 
 std::multimap<int, FileSizeInfo>::const_iterator i;
			for (i = BlockFilesMap.begin(); i != BlockFilesMap.end(); ++i) {
				char header[4096];
				sprintf(header, "%-75s", i->second.name.c_str());
				DumpSingleBlock(f, header, i->second, true);
				SizeFilesMap.insert(std::pair<const int, FileSizeInfo>(i->second.totalSize, i->second));
				totals += i->second;
			}
 
 if (totals.totalBlocks > 0) {
				MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
			} else {
				MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
			}
		}
 
 std::map<std::string, SizeInfo> DirMap;
 
 {
			MultiPrintf(f, "\n-------------------- File stats sorted by total size\n\n");
			MultiPrintf(f, "%-75s : blocks |    bytes | avg bytes/block\n", " ");
			MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
 
 SizeInfo totals;
 
 std::multimap<int, FileSizeInfo>::const_iterator i;
			for (i = SizeFilesMap.begin(); i != SizeFilesMap.end(); ++i) {
				char header[4096];
				sprintf(header, "%-75s", i->second.name.c_str());
				DumpSingleBlock(f, header, i->second, true);
				{
					const char * const name = i->second.name.c_str();
					const char *d = name + i->second.name.size()-1;
					while (d > name && *d != '\\' && *d != '/') {
						--d;
					}
					while (*d == '\\' || *d == '/') {
						std::string dir(name, d-name);
						std::map<std::string, SizeInfo>::iterator it = DirMap.find(dir);
						if (it == DirMap.end()) {
							DirMap.insert(std::pair<std::string, SizeInfo>(dir, i->second));
						} else {
							it->second += i->second;
						}
						--d;
						while (d > name && *d != '\\' && *d != '/') {
							--d;
						}
					}
				}
				totals += i->second;
			}
 
 if (totals.totalBlocks > 0) {
				MultiPrintf(f, "\nTotal %d blocks, %d bytes, %.2f avg bytes/block\n\n", totals.totalBlocks, totals.totalSize, (float)totals.totalSize / totals.totalBlocks);
			} else {
				MultiPrintf(f, "\nTotal 0 blocks, 0 bytes, 0 avg bytes/block\n\n");
			}
		}
 
 {
			MultiPrintf(f, "\n-------------------- Directory stats\n\n");
			MultiPrintf(f, "%-75s : blocks |    bytes | avg bytes/block\n", " ");
			MultiPrintf(f, "%-75s-:--------|----------|----------------\n", " ");
 
 std::map<std::string, SizeInfo>::const_iterator i;
			for (i = DirMap.begin(); i != DirMap.end(); ++i) {
				char header[4096];
				sprintf(header, "%-75s", i->first.c_str());
				DumpSingleBlock(f, header, i->second, true);
			}
		}
 
 MultiPrintf(f, "\n-------------------- End DumpMemStats\n\n");
 
 _CrtMemState dif;
		if (&snap) {
			_CrtMemState cur;
			_CrtMemCheckpoint(&cur);
			_CrtMemDifference(&dif, &snap.memState, &cur);
		} else {
			_CrtMemCheckpoint(&dif);
		}
		g_Fileo = f;
		_CrtSetReportHook(OutputReportHook);
		_CrtMemDumpStatistics(&dif);
		_CrtSetReportHook(g_OldReportHook);
		g_Fileo = NULL;
 
 memset(&g_UnnamedSizeInfo,  0, sizeof(g_UnnamedSizeInfo));
		memset(&g_TotalSizeInfo, 0, sizeof(g_TotalSizeInfo));
		g_LastFName[0] = 0;
		g_FileLineMap.clear();
	}
 
 ClearFileData();
 
 #else
	MultiPrintf(f, "-------------------- Executed from Release mode, so no debug heap stats\n\n");
#endif
 
 DumpProcessInfo(f);
	DumpGlobalInfo(f);
 
 fclose(f);
}
 
 void DumpShortMemStats(const char *file, unsigned int line)
{
	FILE *f = OpenLogFile();
 
 if (file) {
		MultiPrintf(f, "\n%s(%d): Code line that executed the short dump\n\n", file, line);
	} else {
		MultiPrintf(f, "\nPlease, use the macros to execute the short dump!!\n\n");
	}
 
 DumpGlobalInfo(f);
 
 fclose(f);
}
 
 void DumpMemStats(const char *file, unsigned int line)
{
	DumpMemStats(*(MemDebugSnapshot *)0, file, line);
}
 |