#include "pch.hpp"
//===========================================================================//
// File:	 MemoryManager.cpp												 //
// Project:  GameOS					                                         //
// Contents: MemoryManager subsyystem										 //
//---------------------------------------------------------------------------//
// Date      Who    Modification                                             //
// 12/12/97  JTR    Created													 //
// 12/27/97  JTR	Documented												 //
// 01/07/98  JTR	Cleaned up reporting. Added timestamp.					 //
// 06/28/99  JTR	Very different.											 //
// 07/19/99  JTR    If you thought it was different before, oy!				 //
//---------------------------------------------------------------------------//
// Copyright (C) 1999 Microsoft Corp.						                 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//
#include "MemoryManager.hpp"
#include "ErrorHandler.hpp"
#include "LinkedList.hpp"
#include "Windows.hpp"
#include "Debugger.hpp"

extern DWORD gLanguageDLL;
//
// This value is
//
// 1) A #SNAN - so will cause a floating point exception if used as a float
// 2) Odd, so can be spotted as a potential invalid pointer
// 3) High enough to cause an exception under Win9x and WinNT if used as a pointer
//
#define MEMORY_FILL_VALUE	0xff81ff81

//
// When this is true the memory manager decommits as much memory as possible
//
int	AgressiveManager=1;

//
// These are the default memory pool sizes - last entry must be 'LargestMemoryBlock'
// If you need to add more pools the const int 'MemoryPools' must be increased
//
WORD PoolSizes[MemoryPools]={12,16,20,24,28,32,40,48,68,88,128,196,256,348,384,512,768,LargestMemoryBlock};


MEMORYPOOL* gMemoryPool[MemoryPools];

// for memory tracking
extern bool g_MMInitialized;
extern bool gShowAllocations;
extern DWORD gNoFree;
#ifdef _DEBUG
void* g_pMemCheck = NULL;
#endif // _DEBUG

DWORD MemoryLeaks;
DWORD LeakedMemory;

DWORD LargeMemorySize=0;
DWORD LargeMemoryAllocations=0;

HANDLE HWSProcess=0;

DWORD MemoryAllocated=0;
DWORD MemoryFreed=0;
DWORD SystemMemoryAllocated=0;
DWORD SystemMemoryAllocs=0;

extern DWORD MemoryAllocs,MemoryFrees;

HGOSHEAP Heap_Font=0;
HGOSHEAP ParentGameOSHeap=0;
HGOSHEAP ParentClientHeap=0;
HGOSHEAP DefaultHeap=0;
HGOSHEAP CurrentHeap=0;
HGOSHEAP ClientHeap=0;
HGOSHEAP Heap_Network=0;

HGOSHEAP Heap_Texture=0;
HGOSHEAP Heap_Texture32=0;
HGOSHEAP Heap_Texture16=0;
HGOSHEAP Heap_TextureOther=0;

gos_Heap* AllHeaps=0;
HGOSHEAP HeapList[256];

LARGEBLOCKHEADER* gMemoryBigAllocList = 0;

HGOSHEAP gCurrentHeapStack[64];
DWORD gCurrentHeapStackLevel = 0;

DWORD gMemoryBlockOverhead = MEMORY_FILL_VALUE;

DWORD gMemoryStackWalkLevel = 0;
HGOSHEAP gExaminingHeap = NULL;
MEMORYPOOL* gExaminingPool = NULL;

//prototypes - only used from within this file
MEMORYPOOL * MM_CreateMemoryPool( WORD blockSize );
void MM_DestroyMemoryPool(MEMORYPOOL * pPool);
void MM_UpdateHeap(gos_Heap * ptr);
void SpewAndFreeHeapAllocs(HGOSHEAP pHeap, bool vociferous);
void DestroySelfAndChildren(HGOSHEAP toDestroy, bool vociferous);
void DisplayLeakInformation( LARGEBLOCKHEADER*, POOLBLOCK*, SMALLPOOLBLOCK*, BYTE* Address );

void WalkHeapAllocs(HGOSHEAP pHeap, bool vociferous);
void WalkSelfAndChildren(HGOSHEAP toWalk, bool vociferous);

bool gViaNew = false;

//
// Used for overrun checking, contains list of free areas of memory
//
typedef struct _MemStack
{
	_MemStack*	pNext;
	BYTE*		MemoryBase;
	DWORD		MemorySize;
	BYTE*		AllocationBase;
} MemStack;

MemStack*	pMemoryStack=0;				// Pointer to free memory

DWORD VirtualMemoryAllocated=0;
DWORD MemoryUsed=0;
DWORD MemoryAllocations=0;

//
//
// Setup the virtual memory manager - Get total code and data size of program (rough estimates)
//
//
void SetupVirtualMemory()
{
/*	MEMORY_BASIC_INFORMATION mbi;
	PBYTE pRegion=(PBYTE)hInstance;

	VirtualQuery( pRegion,&mbi,sizeof(mbi) );	// Get start of memory blocks

	while( mbi.AllocationBase==hInstance )
	{
		if( mbi.Protect&(PAGE_READONLY|PAGE_EXECUTE_READ|PAGE_EXECUTE|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY) )
			CodeSize+=mbi.RegionSize;
		else
			DataSize+=mbi.RegionSize;
		VirtualQuery( pRegion+=mbi.RegionSize,&mbi,sizeof(mbi) );	// Move to next region
	}
*/

//
// Code to detect same memory footprint as task manager (work in progress)
//
/*	mbi.AllocationBase=0;	//(BYTE*)hInstance;

	VirtualQuery( (PBYTE)hInstance,&mbi,sizeof(mbi) );

	void* BaseAddress=0;

	DWORD TotalMemCommit=0;
	DWORD TotalMemRes=0;

	char Buffer[256];

	while( BaseAddress!=mbi.BaseAddress )
	{
		char Name[256];

		BaseAddress=mbi.BaseAddress;

		Name[0]=0;
		GetModuleFileName( (HMODULE)mbi.AllocationBase, Name, 256);

		char drive[_MAX_DRIVE];
		char dir[_MAX_DIR];
		char fname[_MAX_FNAME];
		char ext[_MAX_EXT];
   		_splitpath( Name, drive, dir, fname, ext );
		
		sprintf( Buffer, "0x%08x 0x%08x 0x%08x %s %s\n",mbi.AllocationBase, mbi.BaseAddress, mbi.RegionSize, (mbi.State==MEM_COMMIT ? "COMMIT  " : mbi.State==MEM_FREE ? "FREE    " : "RESERVED"), fname );

		if( mbi.State!=MEM_FREE )
		{
			if( mbi.State==MEM_COMMIT )
				TotalMemCommit+=mbi.RegionSize;
			else
				TotalMemRes+=mbi.RegionSize;
			OutputDebugString( Buffer );
		}

		VirtualQuery( pRegion+=mbi.RegionSize,&mbi,sizeof(mbi) );	// Move to next region
	}

	sprintf( Buffer, "Committed=%dk, Reserved=%dk\n",TotalMemCommit/1024, TotalMemRes/1024 );
	OutputDebugString( Buffer );
	_asm int 3;
*/
//
// Make sure enough virtual memory exists on the system for the game (64Meg required)
//
	MEMORYSTATUS MemStatus;
	GlobalMemoryStatus( &MemStatus );
	if( MemStatus.dwAvailPhys + MemStatus.dwAvailPageFile < Environment.MegMemoryRequired*1024*1024 )
	{
		if( IDCANCEL==MessageBox( hWindow, gos_GetResourceString(gLanguageDLL, GOS_NEED_MORE_MEMORY), ApplicationName,MB_ICONHAND|MB_OKCANCEL|MB_TASKMODAL ))
		{
			ExitGameOS();
		}
	}
}


void MM_Startup()
{
	AllHeaps=0;
	memset( HeapList, 0, sizeof(HeapList) );

	memset( gMemoryPool, 0, sizeof(gMemoryPool));

	MemoryFrees = MemoryFreed = 0;
	SystemMemoryAllocs = MemoryAllocs = MemoryAllocated = SystemMemoryAllocated = 0;

	LargeMemorySize=0;
	LargeMemoryAllocations=0;

	MemoryLeaks=0;
	LeakedMemory=0;

	ParentGameOSHeap = gos_CreateMemoryHeap( "GameOS Parent Heap", 0, 0 );
	ParentClientHeap = gos_CreateMemoryHeap( "Client Parent Heap", 0, 0 );

	ClientHeap=gos_CreateMemoryHeap( "Client Default", 0, ParentClientHeap );
	DefaultHeap=gos_CreateMemoryHeap( "GameOS Default", 0, ParentGameOSHeap );
//
// More heaps
//
	Heap_Texture=gos_CreateMemoryHeap( "Textures", 0, ParentGameOSHeap );
	Heap_Texture16=gos_CreateMemoryHeap( "Ram copy", 0, Heap_Texture );
	Heap_Texture32=gos_CreateMemoryHeap( "32 bit copy", 0, Heap_Texture );
	Heap_TextureOther=gos_CreateMemoryHeap( "Other", 0, Heap_Texture );
	Heap_Network=gos_CreateMemoryHeap( "Network", 0, ParentGameOSHeap );
	Heap_Font=gos_CreateMemoryHeap( "Fonts", 0, ParentGameOSHeap );

	#ifdef _DEBUG
		gMemoryStackWalkLevel = Environment.memoryTraceLevel;
	#else
		gMemoryStackWalkLevel = 0;
	#endif
//
// Create the default memory pools
//
	gosASSERT( PoolSizes[MemoryPools-1]==LargestMemoryBlock );		// Last entry in list of pools must be largest size block
	gosASSERT( LargestMemoryBlock<4096 );							// Blocks managed by GameOS must be smaller than system page size

	for(int i=0; i < MemoryPools; i++)
	{
		#ifdef _DEBUG
		if( (i>0 && PoolSizes[i-1]>=PoolSizes[i]) || PoolSizes[i]>LargestMemoryBlock )
			STOP(( "Error in pool sizes" ));
		if( PoolSizes[i]&3 )
			STOP(( "Pools should have 4 byte alignment at least!" ));
		#endif
	}

	g_MMInitialized = true;
}



//
// Reset the counters for bytes and allocations in a single frame
//
#ifdef LAB_ONLY
	void MM_UpdateStatistics( HGOSHEAP Heap )
	{
		if( Heap->pChild )
			MM_UpdateStatistics( Heap->pChild );
		if( Heap->pNext )
			MM_UpdateStatistics( Heap->pNext );

		Heap->AllocationsLastLoop = Heap->AllocationsThisLoop;
		Heap->AllocationsThisLoop = 0;
		Heap->TotalAllocatedLastLoop = Heap->BytesAllocated;
		Heap->BytesAllocatedThisLoop = 0;
		Heap->BytesFreedThisLoop = 0;
	}
#endif


//
// Shut down the memory manager and look for blocks that have leaked
//
void MM_Shutdown()
{
	if( !g_MMInitialized || ProcessingError )
		return;

	if (ClientHeap)
	{
		gos_DestroyMemoryHeap(ClientHeap, true );
		ClientHeap = 0;
	}

	if (Heap_TextureOther)
	{
		gos_DestroyMemoryHeap(Heap_TextureOther, true );
		Heap_TextureOther = 0;
	}

	if (Heap_Texture16)
	{
		gos_DestroyMemoryHeap(Heap_Texture16, true );
		Heap_Texture16 = 0;
	}

	if (Heap_Texture32)
	{
		gos_DestroyMemoryHeap(Heap_Texture32, true );
		Heap_Texture32 = 0;
	}

	if (Heap_Texture)
	{
		gos_DestroyMemoryHeap(Heap_Texture, true );
		Heap_Texture = 0;
	}

	if (DefaultHeap)
	{
		gos_DestroyMemoryHeap(DefaultHeap, true );
		DefaultHeap = 0;
	}

	if (ParentGameOSHeap)
	{
		gos_DestroyMemoryHeap(ParentGameOSHeap, true );
		ParentGameOSHeap = 0;
	}

	if (ParentClientHeap)
	{
		gos_DestroyMemoryHeap(ParentClientHeap, true );
		ParentClientHeap = 0;
	}

	// look for undeleted memory heaps and delete them
	for(int i=0; i < MemoryPools; i++)
		MM_DestroyMemoryPool(gMemoryPool[i]);

	gMemoryBigAllocList = 0;

	g_MMInitialized = false;

	#ifdef LAB_ONLY
	if( MemoryLeaks )
	{
		char Buffer[128];
		sprintf( Buffer, "There %s %d byte%s leaked in %d allocation%s\n",(LeakedMemory==1 ? "was" : "were"), LeakedMemory, (LeakedMemory==1 ? "" : "s"), MemoryLeaks, (MemoryLeaks==1 ? "." : "s.") );
		OutputDebugString( Buffer );
	}
	#endif
}



//
//
// CLIENT VISIBLE
//
#ifdef _DEBUG // jcem
void* __stdcall gos_Malloc( size_t bytes, HGOSHEAP theHeap )
{
    extern void* __stdcall __gos_Malloc( size_t bytes, HGOSHEAP theHeap );

    void* MemoryPtr = __gos_Malloc( bytes, theHeap );
    if (g_pMemCheck == MemoryPtr) {
        PAUSE(("MemoryPtr(0x%x) gos_Malloc(%d bytes)!", MemoryPtr, bytes));
    }

    return MemoryPtr;
}

void* __stdcall __gos_Malloc( size_t bytes, HGOSHEAP theHeap )
#else // !_DEBUG
void* __stdcall gos_Malloc( size_t bytes, HGOSHEAP theHeap )
#endif // _DEBUG
{
//
// GameOS does not allow 0 byte allocations and assume more than 512Meg is an error!
//
	if( bytes==0 || bytes>512*1024*1024 )
		STOP(( "Invalid allocation, %d bytes",bytes ));
//
// To walk the stack correctly, detect if we are being called from new or malloc
//
	#ifdef _DEBUG
	int FromNew=gViaNew ? 1 : 0;
	gViaNew=false;
	#endif
//
// If not in the main thread, use the OS
//
	if( (status!=BeforeInit && ThreadId!=GetCurrentThreadId()) )
	{
		return malloc( bytes );
	}
//
// Is memory manager overridden?
//
	if( Environment.MemoryManager )
	{
//
// Command line switch  /gosNoMEMORY   - Disable memory management
//
		if( 1==Environment.MemoryManager )
		{
			return malloc( bytes );
		}
		else
//
// Command line switch  /gosCheckMEMORY	 - Look for memory overruns
//
		{
			DWORD Offset=4096-((bytes+12)&4095);		// Offset to start of block in 1st 4K page. (So last byte is the end of a 4K page)
			DWORD BytesNeeded=bytes+Offset+12+4096;		// Room for 12 byte header, app memory and 4K guard page
//
// Updated totals for memory used
//
			MemoryUsed+=bytes;
			MemoryAllocations++;
//
// See if a large enough memory block already exists for memory
//
			MemStack* pBestFit=0;
			DWORD BytesWasted=1<<31;
			MemStack* pThis=pMemoryStack;

			while( pThis )
			{
				if( pThis->MemorySize-BytesNeeded < BytesWasted )
				{
					BytesWasted=pThis->MemorySize-BytesNeeded;
					pBestFit=pThis;
				}
				pThis=pThis->pNext;
			}
//
// No free pages? - Allocate another Megabyte (or bytes needed, whatever smaller)
//
			if( 0==pBestFit )
			{
				MemoryAllocated=BytesNeeded > 1024*1024 ? BytesNeeded : 1024*1024;
				BYTE* MemoryBase=(BYTE*)VirtualAlloc( NULL, MemoryAllocated, MEM_RESERVE, PAGE_NOACCESS );

				if( 0==MemoryBase )
				{
					MemoryAllocated=BytesNeeded;
					MemoryBase=(BYTE*)VirtualAlloc( NULL, MemoryAllocated, MEM_RESERVE, PAGE_NOACCESS );
				}

				if( 0==MemoryBase )
					STOP(( "Out of Virtual Memory!" ));

				VirtualMemoryAllocated+=MemoryAllocated;

				pBestFit=(MemStack*)malloc(sizeof(MemStack));
				pBestFit->MemoryBase=MemoryBase;
				pBestFit->MemorySize=MemoryAllocated;
				pBestFit->AllocationBase=MemoryBase;
				pBestFit->pNext=pMemoryStack;

				pMemoryStack=pBestFit;
				BytesWasted=MemoryAllocated-BytesNeeded;
			}
//
// Put any excess pages back on list as free
//
			if( BytesWasted >= 4096 )
			{
				MemStack* pRemainer=(MemStack*)malloc(sizeof(MemStack));
				pRemainer->MemoryBase=pBestFit->MemoryBase+BytesNeeded;
				pRemainer->MemorySize=BytesWasted;
				pRemainer->AllocationBase=pBestFit->AllocationBase;
				pRemainer->pNext=pMemoryStack;
				pMemoryStack=pRemainer;
			}
//
// Now mark all but last page as read/write
//
			VirtualAlloc( pBestFit->MemoryBase, BytesNeeded-4096, MEM_COMMIT, PAGE_READWRITE );
//
// Under Win2K it's possible to mark the last page as GUARD, so exceptions can be continued
//
			if( WindowsNT )
				VirtualAlloc( pBestFit->MemoryBase+BytesNeeded-4096, 4096, MEM_COMMIT, PAGE_READWRITE+PAGE_GUARD );
//
// Put header before memory block
//			
			DWORD* MemoryPointer=(DWORD*)(pBestFit->MemoryBase+Offset);
			MemoryPointer[0]=(DWORD)pBestFit->MemoryBase;
			MemoryPointer[1]=(DWORD)bytes;
			MemoryPointer[2]=(DWORD)pBestFit->AllocationBase;
//
// Remove original free block pointer from list
//
			pThis=pMemoryStack;
			if( pThis==pBestFit )
			{
				pMemoryStack=pBestFit->pNext;
			}
			else
			{
				while( pThis )
				{
					if( pThis->pNext==pBestFit )
					{
						pThis->pNext=pBestFit->pNext;
						break;
					}
					pThis=pThis->pNext;
				}
			}
			free( pBestFit );
//
// Return pointer to start of application block
//
			return &MemoryPointer[3];
		}
	}
//
//
//***************************************************************
// Memory block is allocated before the memory manager is inited
//***************************************************************
//
//
	if( status==BeforeInit || !g_MMInitialized )
	{
		int Header=sizeof(LARGEBLOCK) + sizeof(LARGEBLOCKHEADER);

		int Padding=32-(Header&31);

		LARGEBLOCK* pBlock = (LARGEBLOCK*) malloc(Header + Padding + bytes);

		LARGEBLOCKHEADER* pHeader = (LARGEBLOCKHEADER*)((char*)pBlock+sizeof(LARGEBLOCK)+Padding);

		void* base=&pHeader[1];

		#ifdef _DEBUG
		DWORD FillSize= bytes >> 2;
		_asm{
			mov edi,base
			mov ecx,FillSize
			mov eax,MEMORY_FILL_VALUE
			rep stosd
		}
		pBlock->dTimeStamp=0;
		#endif

		pBlock->Size=bytes;
		pBlock->pNext=0;
		pBlock->pLast=0;
		pBlock->Heap=0;
		pHeader->pLargeBlockInfo = pBlock;
		pHeader->LargeMagicNumber = Magic_BeforeInit;
		return base;
	}
//
// Showing memory allocations during game?
//
	#ifdef _DEBUG
	if( gShowMemoryUseage==1 || gShowAllocations )
	{
		SPEW(( 0, "Allocated %d bytes", bytes ));
		DWORD Heap[11];
		DWORD MemoryLevels=Environment.memoryTraceLevel;
		if( MemoryLevels<5 )
		{
			MemoryLevels=5;
		}
		else if( MemoryLevels>10 )
		{
			MemoryLevels=10;
		}
		WalkStack( Heap, MemoryLevels, 1 + FromNew );
		for( int t0=0; t0<MemoryLevels; t0++ )
		{
			if( Heap[t0] )
			{
				SPEW(( 0, DecodeAddress( Heap[t0], false ) ));
			}
		}
	}
	#endif
//
// Find which heap block belongs in
//
	if( (DWORD)theHeap==0xffffffff )
	{
		theHeap = CurrentHeap;
	}
	else
	{
		if( (DWORD)theHeap==0 )
			theHeap = ClientHeap;
	}
//
// Is the heap valid?
//
	#ifdef _ARMOR
		if( theHeap == NULL || (theHeap && (theHeap->Magic!=0x7fbadbad)) )
			STOP(( "Invalid heap" ));
		if( theHeap->pChild )
			STOP(( "Heap is a parent" ));
	#endif
//
// Update the heap size
//
	gos_ChangeHeapSize( theHeap, bytes );
//
//
//************************************************************************
// Allocation is less than 'LargestMemoryBlock' bytes in one of the pools
//************************************************************************
//
//
	#ifdef _ARMOR
		int tmp = bytes+1+4;								// Room for 4 byte end marker and header byte
	#else
		int tmp = bytes+1;									// Room for header byte
	#endif
	if( tmp <= LargestMemoryBlock )
	{	
//
// Which is the smallest size memory pool the block fits in?
//
		for( int whichSize=0; whichSize < MemoryPools; whichSize++ )
		{
			if( tmp <= PoolSizes[whichSize] )
				break;
		}
//
// Scan through memory pools, looking for one with space
//
		MEMORYPOOL* targetPool = gMemoryPool[whichSize];
		while( targetPool )
		{
			if( (int)targetPool&0xffff )										// Simple test to make sure pool is valid
				STOP(( "Memory corruption in %d byte pool (1st block=0x%x)", PoolSizes[whichSize], gMemoryPool[whichSize] ));
			if( targetPool->wTotalBlocks!=targetPool->FreeBlockPtr )
				break;
			targetPool = targetPool->pNext;
		}
//
// If came to end of pools, create a new pool and make it FIRST to be searched next time
//
		if( targetPool == NULL )
		{
			targetPool = MM_CreateMemoryPool( PoolSizes[whichSize] );
			targetPool->pNext=gMemoryPool[whichSize];
			targetPool->pLast=0;
			if( gMemoryPool[whichSize] )
			{
				gMemoryPool[whichSize]->pLast=targetPool;
			}
			gMemoryPool[whichSize]=targetPool;
		}
		else
//
// Make sure if the pool has free spaces it is found first the next time an allocation is requested
//
		{
			if( targetPool->FreeBlockPtr > 1 && gMemoryPool[whichSize]!=targetPool )
			{
				targetPool->pLast->pNext=targetPool->pNext;
				if( targetPool->pNext )
				{
					targetPool->pNext->pLast=targetPool->pLast;		// Remove pool from linked list
				}

				gMemoryPool[whichSize]->pLast=targetPool;
				targetPool->pNext=gMemoryPool[whichSize];			// Move pool to start of list
				targetPool->pLast=0;
				gMemoryPool[whichSize]=targetPool;
			}
		}
//
// Get pointer to start of new block
//
		BYTE* memory = (BYTE*)( targetPool->FreeBlockStack[targetPool->FreeBlockPtr] + (int)targetPool );
//
// Make sure memory for the whole block is committed (may be more than one page)
//
		if( AgressiveManager )
		{
			DWORD startBlockNumber=( (int)memory - (int)targetPool ) / 4096;
			DWORD endBlockNumber=( (int)memory - (int)targetPool + targetPool->wBlockSize ) / 4096;

			if( 0==targetPool->AllocCount[startBlockNumber] || 0==targetPool->AllocCount[endBlockNumber] )
			{
				VirtualAlloc( memory, targetPool->wBlockSize, MEM_COMMIT, PAGE_READWRITE );
			}

			targetPool->AllocCount[startBlockNumber]++;
			targetPool->AllocCount[endBlockNumber]++;
		}
//
// Update block information
//
		if( targetPool->wBlockSize < 256 )
		{
			#ifdef LAB_ONLY
			SMALLPOOLBLOCK* pBlock = (SMALLPOOLBLOCK*)((int)targetPool->pInfoBlocks + (gMemoryStackWalkLevel*4 + sizeof(SMALLPOOLBLOCK)) * (((int)memory-(int)targetPool->pMemoryPool) / targetPool->wBlockSize ));
			
			pBlock->Size = (BYTE)bytes;
			#endif

			#ifdef _DEBUG
				pBlock->dTimeStamp = gos_GetElapsedTime();
				if( gMemoryStackWalkLevel )
					WalkStack( pBlock->pContext, gMemoryStackWalkLevel, 1 + FromNew );
			#endif
		}
		else
		{
			#ifdef LAB_ONLY
			POOLBLOCK* pBlock = (POOLBLOCK*)((int)targetPool->pInfoBlocks + (gMemoryStackWalkLevel*4 + sizeof(POOLBLOCK)) * (((int)memory-(int)targetPool->pMemoryPool) / targetPool->wBlockSize ));
			
			pBlock->Size = (WORD)bytes;
			#endif

			#ifdef _DEBUG
				pBlock->dTimeStamp = gos_GetElapsedTime();
				if( gMemoryStackWalkLevel )
					WalkStack( pBlock->pContext, gMemoryStackWalkLevel, 1 + FromNew );
			#endif
		}

		targetPool->FreeBlockStack[ targetPool->FreeBlockPtr++ ]=0;
		#ifdef LAB_ONLY
		targetPool->wUserBytes = (WORD)(targetPool->wUserBytes + bytes);
		#endif
//
// On DEBUG build, flood unused memory with #SNAN
//
		*memory++=theHeap->HeapNumber;							// Heap number is stored before block

		#ifdef _DEBUG
		DWORD FillSize=bytes >> 2;
		_asm{
			mov edi,memory
			mov ecx,FillSize
			mov eax,MEMORY_FILL_VALUE
			rep stosd
		}
		#endif

		#ifdef _ARMOR
			*(DWORD*)(memory+bytes)=MemoryEndMarker;			// Mark end of block to check for overruns
		#endif

		return memory;
	}
//
//
//****************************************************
// Allocation is more than 4k bytes and not in a pool
//****************************************************
//
//
	int Header=sizeof(LARGEBLOCK) + gMemoryStackWalkLevel*4 + sizeof(LARGEBLOCKHEADER);

	#ifdef _ARMOR
		int EndOfBlock=4;
	#else
		int EndOfBlock=0;			
	#endif

	char* MemoryPtr = (char*)malloc( Header + 32 + bytes + EndOfBlock );
	if( 0==MemoryPtr )
		STOP(( "Out of Memory" ));
	
	int Padding=32-((Header+(int)MemoryPtr)&31);

	char* MemoryBlock=MemoryPtr + Header + Padding;
//
// On DEBUG build, flood unused memory with #SNAN
//
	#ifdef _DEBUG
	DWORD FillSize=bytes >> 2;
	_asm{
		mov edi,MemoryBlock
		mov ecx,FillSize
		mov eax,MEMORY_FILL_VALUE
		rep stosd
	}
	#endif

	LARGEBLOCK* pLargeBlock=(LARGEBLOCK*)MemoryPtr;
	LARGEBLOCKHEADER* pLargeHeader=(LARGEBLOCKHEADER*)(MemoryBlock-sizeof(LARGEBLOCKHEADER));

	pLargeHeader->LargeMagicNumber=Magic_LargeBlock;
	pLargeHeader->pLargeBlockInfo=pLargeBlock;							// Setup pointer to main data structure

	#ifdef LAB_ONLY
	pLargeBlock->Size=bytes;
	#endif

	pLargeBlock->pLast=0;
	pLargeBlock->pNext=gMemoryBigAllocList;
	if( gMemoryBigAllocList )
	{
		gMemoryBigAllocList->pLargeBlockInfo->pLast=pLargeHeader;
	}
	gMemoryBigAllocList = pLargeHeader;									// Add to chain of big blocks

	pLargeBlock->Heap=theHeap->HeapNumber;

	LargeMemorySize+=bytes;
	LargeMemoryAllocations++;
//
// On debug builds, store a stack walk
//
	#ifdef _DEBUG
		pLargeBlock->dTimeStamp = gos_GetElapsedTime();
		if( gMemoryStackWalkLevel )
			WalkStack( pLargeBlock->pContext, gMemoryStackWalkLevel, 1 + FromNew );
	#endif

	#ifdef _ARMOR
		*(DWORD*)(MemoryBlock+bytes)=MemoryEndMarker;				// here is the far extent delimiter
	#endif

	return MemoryBlock;
}









//
// Realloc - for flight sim!
//
void* __stdcall gos_Realloc( void* MemoryPtr, int NewSize )
{
//
// Allocated before init?
//
	if( 1==Environment.MemoryManager || (status!=BeforeInit && ThreadId!=GetCurrentThreadId()) )
	{
		return realloc( MemoryPtr, NewSize );
	}
//
// Current address 0?
//
	if( 0==MemoryPtr )
	{
		if( 0==NewSize )
			return 0;
		return (void*)gos_Malloc( NewSize );
	}
//
// New size 0?
//
	if( 0==NewSize )
	{
		gos_Free( MemoryPtr );
		return 0;
	}
#ifdef _DEBUG
    if (g_pMemCheck == MemoryPtr) {
        PAUSE(("MemoryPtr(0x%x) gos_Realloc!", MemoryPtr));
    }
#endif // _DEBUG
//
// Find size of old memory block
//
	int OldSize=0;

	#ifdef _ARMOR
		if( 0!=IsBadReadPtr((BYTE*)MemoryPtr-1,1) )
			STOP(("Bad memory pointer passed to gos_Realloc (0x%x)",MemoryPtr ));
	#endif

	BYTE BlockType=*((BYTE*)MemoryPtr-1);

	if( BlockType==Magic_BeforeInit )
	{
		LARGEBLOCKHEADER* pHeader = (LARGEBLOCKHEADER*) ((BYTE*)MemoryPtr-(sizeof(LARGEBLOCKHEADER)));

		#ifdef _ARMOR		
			if( 0!=IsBadReadPtr(pHeader,sizeof(LARGEBLOCKHEADER)) )
					STOP(("Bad memory pointer passed to gos_Realloc (0x%x)",MemoryPtr ));
		#endif

		LARGEBLOCK* pBlock=pHeader->pLargeBlockInfo;

		#ifdef _ARMOR		
			if( 0!=IsBadReadPtr(pBlock,sizeof(LARGEBLOCK)) )
					STOP(("Bad memory pointer passed to gos_Realloc (0x%x)",MemoryPtr ));
		#endif

		OldSize=pBlock->Size;
	}
	else
	{
		if( BlockType>0 && BlockType<Magic_LargeBlock )
		{
			MEMORYPOOL* pMemoryPool = (MEMORYPOOL*)((int)MemoryPtr&0xffff0000);

			#ifdef _ARMOR		
				if( 0!=IsBadReadPtr(pMemoryPool,sizeof(MEMORYPOOL)) )
						STOP(("Bad memory pointer passed to gos_Realloc (0x%x)",MemoryPtr ));
			#endif

			if( pMemoryPool->wBlockSize < 256 )
			{
				#ifdef LAB_ONLY
				SMALLPOOLBLOCK* pBlock = (SMALLPOOLBLOCK*)((int)pMemoryPool->pInfoBlocks + (gMemoryStackWalkLevel*4 + sizeof(SMALLPOOLBLOCK)) * (((int)MemoryPtr-(int)pMemoryPool->pMemoryPool) / pMemoryPool->wBlockSize ));
				#endif
				#ifdef _ARMOR		
					if( 0!=IsBadReadPtr(pBlock,sizeof(SMALLPOOLBLOCK)) )
							STOP(("Bad memory pointer passed to gos_Realloc (0x%x)",MemoryPtr ));
				#endif
				#ifdef LAB_ONLY
				OldSize=(int)pBlock->Size;
				#else
				OldSize=pMemoryPool->wBlockSize;
				#endif
			}
			else
			{
				#ifdef LAB_ONLY
				POOLBLOCK* pBlock = (POOLBLOCK*)((int)pMemoryPool->pInfoBlocks + (gMemoryStackWalkLevel*4 + sizeof(POOLBLOCK)) * (((int)MemoryPtr-(int)pMemoryPool->pMemoryPool) / pMemoryPool->wBlockSize ));
				#endif
				#ifdef _ARMOR		
					if( 0!=IsBadReadPtr(pBlock,sizeof(POOLBLOCK)) )
							STOP(("Bad memory pointer passed to gos_Realloc (0x%x)",MemoryPtr ));
				#endif
				#ifdef LAB_ONLY
				OldSize=(int)pBlock->Size;
				#else
				OldSize=pMemoryPool->wBlockSize;
				#endif
			}
		}
		else
		{
			STOP(("Bad memory pointer passed to gos_Realloc (0x%x)",MemoryPtr ));
		}
	}
//
// Allocate a new memory block
//
	void* NewBlock=(void*)gos_Malloc( NewSize );
//
// Copy as much data as we can from the old block
//
	if( OldSize>NewSize )
		OldSize=NewSize;
	memcpy( NewBlock, MemoryPtr, OldSize );
//
// Free old block, return new block
//
	gos_Free( MemoryPtr );
	return NewBlock;
}





//
//
// CLIENT VISIBLE
//
// This function frees the entire memory block, tags and all,
// and removes the respective pointer from the registered pointer
// list
//
//
//
void __stdcall gos_Free(void * MemoryPtr)
{
//
// Allow free(0) - Or ignore all frees when quitting the game (for speed)
//
#ifdef _DEBUG
    if (MemoryPtr && (g_pMemCheck == MemoryPtr)) {
        PAUSE(("MemoryPtr(0x%x) gos_Free!", MemoryPtr));
    }
#endif // _DEBUG
	if( NULL==MemoryPtr || (ProcessingError && TerminateGame) ) 
		return;
//
// If not in the main thread, use the OS
//
	if( status!=BeforeInit && ThreadId!=GetCurrentThreadId() )
	{
		if( !gNoFree )
			free(MemoryPtr);
		return;
	}
//
// Check for command line switches:
//
// /gosNoMEMORY		 - Disable memory management
// /gosCheckMEMORY	 - Look for memory overruns
//
	if( Environment.MemoryManager )
	{
		if( 1==Environment.MemoryManager )
		{
			if( !gNoFree )
				free(MemoryPtr);			// /gosNoMEMORY
			return;
		}
		else
		{
//
// Read header with block information
//
			DWORD* MemoryHeader=(DWORD*)MemoryPtr-3;	// /gosCheckMEMORY
			BYTE* MemoryBase=(BYTE*)MemoryHeader[0];
			DWORD bytes=MemoryHeader[1];
			BYTE* AllocationBase=(BYTE*)MemoryHeader[2];

			DWORD Offset=4096-((bytes+12)&4095);		// Offset to start of block in 1st 4K page. (So last byte is the end of a 4K page)
			DWORD MemorySize=bytes+Offset+12+4096;		// Room for 12 byte header, app memory and 4K guard page
//
// Updated totals for memory used
//
			MemoryUsed-=bytes;
			MemoryAllocations--;
//
// Mark whole block as no access again
//
			VirtualFree( MemoryBase, MemorySize-4096, MEM_DECOMMIT );
//
// See if the block below or above can be increased in size (is allocated in the same VirtualAlloc block)
//
			MemStack* pFound=0;
			MemStack* pLast=0;
			MemStack* pThis=pMemoryStack;
			while( pThis )
			{
				if( MemoryBase==pThis->MemoryBase+pThis->MemorySize && AllocationBase==pThis->AllocationBase )
				{
					if( pFound )
					{
						pFound->MemoryBase=pThis->MemoryBase;
						pFound->MemorySize+=pThis->MemorySize;
						if( pLast )
						{
							pLast->pNext=pThis->pNext;
							free( pThis );
							return;
						}
						else
						{
							pMemoryStack=pThis->pNext;
							free( pThis );
							return;
						}
					}
					else
					{
						pFound=pThis;
						pFound->MemorySize+=MemorySize;
						MemoryBase=pFound->MemoryBase;
						MemorySize=pFound->MemorySize;
					}
				}
				if( MemoryBase+MemorySize==pThis->MemoryBase && AllocationBase==pThis->AllocationBase )
				{
					if( pFound )
					{
						pFound->MemorySize+=pThis->MemorySize;
						if( pLast )
						{
							pLast->pNext=pThis->pNext;
							free( pThis );
							return;
						}
						else
						{
							pMemoryStack=pThis->pNext;
							free( pThis );
							return;
						}
					}
					else
					{
						pFound=pThis;
						pFound->MemoryBase=MemoryBase;
						pFound->MemorySize+=MemorySize;
						MemoryBase=pFound->MemoryBase;
						MemorySize=pFound->MemorySize;
					}
				}
				pLast=pThis;
				pThis=pThis->pNext;
			}
//
// If already included block in current free list, just exit
//
			if( pFound )
				return;
//
// Add this block to the free blocks list
//
			MemStack* MemoryBlock=(MemStack*)malloc(sizeof(MemStack));
			MemoryBlock->MemoryBase=MemoryBase;
			MemoryBlock->MemorySize=MemorySize;
			MemoryBlock->AllocationBase=AllocationBase;
			MemoryBlock->pNext=pMemoryStack;
			pMemoryStack=MemoryBlock;
			return;
		}
	}
//
// Do simple check on pointer
//
	if( (int)MemoryPtr&3 )
		STOP(( "Invalid pointer passed to gos_Free (0x%x)", MemoryPtr ));
//
// Make sure header byte can be read
//
	#ifdef _ARMOR
		if( 0!=IsBadReadPtr((BYTE*)MemoryPtr-1,1) )
			STOP(("Bad memory pointer passed to gos_Free (Address 0x%x)",MemoryPtr ));
	#endif
	BYTE BlockType=*((char*)MemoryPtr-1);
//
//
//*****************************************************************
// Memory block was allocated before the memory manager was inited
//*****************************************************************
//
//
	if( BlockType==Magic_BeforeInit )
	{
		LARGEBLOCKHEADER* pHeader = (LARGEBLOCKHEADER*) ((BYTE*)MemoryPtr-(sizeof(LARGEBLOCKHEADER)));

		#ifdef _ARMOR		
			if( 0!=IsBadReadPtr(pHeader,sizeof(LARGEBLOCKHEADER)) )
					STOP(("Bad memory pointer passed to gos_Free (0x%x)",MemoryPtr ));
		#endif

		LARGEBLOCK* pBlock=pHeader->pLargeBlockInfo;

		#ifdef _ARMOR		
			if( 0!=IsBadReadPtr(pBlock,sizeof(LARGEBLOCK)) )
					STOP(("Bad memory pointer passed to gos_Free (0x%x)",MemoryPtr ));
		#endif

		#ifdef _DEBUG
		DWORD FillSize= pBlock->Size >> 2;
		_asm{
			mov edi,MemoryPtr
			mov ecx,FillSize
			mov eax,MEMORY_FILL_VALUE
			rep stosd
		}
		#endif

		if( !gNoFree )
			free(pBlock);
		return;
	}
//
//
//******************************************************
// Allocation is less than 4k bytes in one of the pools
//******************************************************
//
//
	if( BlockType>0 && BlockType<Magic_LargeBlock )
	{
		BYTE Heap=BlockType;

		MEMORYPOOL* pMemoryPool = (MEMORYPOOL*)((int)MemoryPtr&0xffff0000);

		#ifdef _ARMOR		
			if( 0!=IsBadReadPtr(pMemoryPool,sizeof(MEMORYPOOL)) )
					STOP(("Bad memory pointer passed to gos_Free (0x%x)",MemoryPtr ));
		#endif

		int Size=0;
		#ifdef _DEBUG
		DWORD* pContext;
		double TimeStamp;
		#endif

		if( pMemoryPool->wBlockSize < 256 )
		{
			#ifdef LAB_ONLY
			SMALLPOOLBLOCK* pBlock = (SMALLPOOLBLOCK*)((int)pMemoryPool->pInfoBlocks + (gMemoryStackWalkLevel*4 + sizeof(SMALLPOOLBLOCK)) * (((int)MemoryPtr-(int)pMemoryPool->pMemoryPool) / pMemoryPool->wBlockSize ));
			#endif
			#ifdef _ARMOR		
				if( 0!=IsBadReadPtr(pBlock,sizeof(SMALLPOOLBLOCK)) )
						STOP(("Bad memory pointer passed to gos_Free (0x%x)",MemoryPtr ));
			#endif
			#ifdef LAB_ONLY
			Size=(int)pBlock->Size;
			#endif
			#ifdef _DEBUG
			pContext=pBlock->pContext;
			TimeStamp=pBlock->dTimeStamp;
			#endif
		}
		else
		{
			#ifdef LAB_ONLY
			POOLBLOCK* pBlock = (POOLBLOCK*)((int)pMemoryPool->pInfoBlocks + (gMemoryStackWalkLevel*4 + sizeof(POOLBLOCK)) * (((int)MemoryPtr-(int)pMemoryPool->pMemoryPool) / pMemoryPool->wBlockSize ));
			#endif
			#ifdef _ARMOR		
				if( 0!=IsBadReadPtr(pBlock,sizeof(POOLBLOCK)) )
						STOP(("Bad memory pointer passed to gos_Free (0x%x)",MemoryPtr ));
			#endif
			#ifdef LAB_ONLY
			Size=(int)pBlock->Size;
			#endif
			#ifdef _DEBUG
			pContext=pBlock->pContext;
			TimeStamp=pBlock->dTimeStamp;
			#endif
		}
//
// Showing memory allocations during game?
//
		#ifdef _DEBUG
			if( gShowMemoryUseage==1 || gShowAllocations )
			{
				SPEW(( 0, "Freeing %d bytes", Size ));
				DWORD Heap[11];
				DWORD MemoryLevels=Environment.memoryTraceLevel;
				if( MemoryLevels<5 )
				{
					MemoryLevels=5;
				}
				else if( MemoryLevels>10 )
				{
					MemoryLevels=10;
				}
				WalkStack( Heap, MemoryLevels, 1 );
				if( (Heap[0]-(int)&operator delete) < 256 )					// Is the first entry the GameOS delete operator?
				{
					WalkStack( Heap, MemoryLevels, 2 );
				}
				for( int t0=0; t0<MemoryLevels; t0++ )
				{
					if( Heap[t0] )
					{
						SPEW(( 0, DecodeAddress( Heap[t0], false ) ));
					}
				}
			}
		#endif
//
// Check the memory has not been corrupted after the end of the block
//
		#ifdef _ARMOR
			if( MemoryEndMarker != *(DWORD*)((BYTE*)MemoryPtr+Size) )
			{
				#ifdef _DEBUG
					if( gMemoryStackWalkLevel )
					{
						SPEW((0, " " ));
						SPEW((0, "Stack dump of original allocation (with corruption after block)" ));
						for(int i = 0; i < gMemoryStackWalkLevel; i++)
						{
							char* stack = DecodeAddress(pContext[i], false );
							if (stack)
							{
								SPEW((0, "%s", stack));
							}
							else
								break;
						}
					}
					STOP(( "Memory block corrupted after end of block at address 0x%x (%d %s originally allocated at %fs)",MemoryPtr, Size, (Size==1 ? "byte" : "bytes"), TimeStamp ));
				#else
					STOP(( "Memory block corrupted after end of block at address 0x%x (%d %s)",MemoryPtr, Size, (Size==1 ? "byte" : "bytes") ));
				#endif
			}
		#endif
//
// Make sure heap valid
//
		if( !HeapList[Heap] )
		{
			#ifdef _DEBUG
				if( gMemoryStackWalkLevel )
				{
					for(int i = 0; i < gMemoryStackWalkLevel; i++)
					{
						char * stack = DecodeAddress(pContext[i], false );
						if (stack)
						{
							SPEW((GROUP_MEMORY, "%s", stack));
						}
						else
							break;
					}
					SPEW((GROUP_MEMORY, ""));
				}
				STOP(( "Duplicate free detected at address 0x%x (%d %s originally allocated at %fs))", MemoryPtr, Size, (Size==1 ? "byte" : "bytes"), TimeStamp ));
			#else
				#ifdef LAB_ONLY
					STOP(( "Duplicate free detected at address 0x%x (%d %s)", MemoryPtr, Size, (Size==1 ? "byte" : "bytes") ));
				#else
					STOP(( "Duplicate free detected at address 0x%x", MemoryPtr ));
				#endif
			#endif
		}
//
// Reduce memory useage of heap
//
		gos_ChangeHeapSize( HeapList[Heap], -Size );
//
// Mark heap as NULL - so we can detect duplicate frees
//
		*((BYTE*)MemoryPtr-1)=0;
//
// On a DEBUG build, fill freed memory with #SNAN
//
		#ifdef _DEBUG
			DWORD FillSize=Size >> 2;
			_asm{
				mov edi,MemoryPtr
				mov ecx,FillSize
				mov eax,MEMORY_FILL_VALUE
				rep stosd
			}
		#endif
//
// If 'not freeing' blocks, don't actually free the memory
//
		if( gNoFree )
			return;
//
// Update pool information
//
		#ifdef LAB_ONLY
		pMemoryPool->wUserBytes = (WORD)(pMemoryPool->wUserBytes - Size);
		#endif
		pMemoryPool->FreeBlockStack[ --pMemoryPool->FreeBlockPtr ] = (WORD)((int)MemoryPtr-1-(int)pMemoryPool );
//
// Sanity check some pointers (cause exception if bad so that registers can be examined)
//
	if( ((int)pMemoryPool->pMemoryPool&0xffff0000)!=((int)pMemoryPool->pInfoBlocks&0xffff0000) ||
		((int)pMemoryPool->pMemoryPool&0xffff0000)!=((int)pMemoryPool&0xffff0000) ||
		((int)pMemoryPool->pLast&0xffff)!=0 ||
		((int)pMemoryPool->pNext&0xffff)!=0 ||
		(pMemoryPool->HeapTag!=(int)'SOG*') )
	{
		_asm mov eax,gMemoryStackWalkLevel
		_asm mov esi,pMemoryPool
		_asm int 3;
	}
//
// If there are no more blocks in the pool, delete it
//
		for(int j=0; j < MemoryPools; j++)
		{
			if( pMemoryPool->wBlockSize==PoolSizes[j] )				// Find which size pool block is from
				break;
		}

		if( 0==pMemoryPool->FreeBlockPtr )
		{
			if( pMemoryPool->pLast )
			{
				pMemoryPool->pLast->pNext=pMemoryPool->pNext;
				if( pMemoryPool->pNext )
				{
					pMemoryPool->pNext->pLast=pMemoryPool->pLast;
				}
			}
			else
			{
				if( gMemoryPool[j]!=pMemoryPool )
					STOP(( "List of pools corrupted" ));

				gMemoryPool[j]=pMemoryPool->pNext;
				if( pMemoryPool->pNext )
				{
					pMemoryPool->pNext->pLast=0;
				}
			}

			VirtualFree( pMemoryPool, 0, MEM_DECOMMIT );
			VirtualFree( pMemoryPool, 0, MEM_RELEASE );				// Free memory used
			return;
		}
//
// See if any memory can be decommitted
//
		if( AgressiveManager )
		{
			BYTE* memory=(BYTE*)MemoryPtr-1;
			DWORD startBlockNumber=( (int)memory - (int)pMemoryPool ) / 4096;
			DWORD endBlockNumber=( (int)memory - (int)pMemoryPool + pMemoryPool->wBlockSize ) / 4096;

			pMemoryPool->AllocCount[startBlockNumber]--;
			pMemoryPool->AllocCount[endBlockNumber]--;

			if( 0==pMemoryPool->AllocCount[startBlockNumber] && 0==pMemoryPool->AllocCount[endBlockNumber] )
			{
				VirtualFree( memory, pMemoryPool->wBlockSize, MEM_DECOMMIT );
				return;												// Do not use this for the next allocation if just decommitted
			}
		}
//
// Otherwise make sure this pool is used for the next allocation
//
		if( gMemoryPool[j]!=pMemoryPool )
		{
			pMemoryPool->pLast->pNext=pMemoryPool->pNext;
			if( pMemoryPool->pNext )
			{
				pMemoryPool->pNext->pLast=pMemoryPool->pLast;		// Remove pool from linked list
			}

			gMemoryPool[j]->pLast=pMemoryPool;
			pMemoryPool->pNext=gMemoryPool[j];						// Move pool to start of list
			pMemoryPool->pLast=0;
			gMemoryPool[j]=pMemoryPool;
		}
		return;
	}
//
//
//****************************************************
// Allocation is more than 4k bytes and not in a pool
//****************************************************
//
//
	if( BlockType==Magic_LargeBlock )
	{
		LARGEBLOCKHEADER* pHeader = (LARGEBLOCKHEADER*) ((BYTE*)MemoryPtr-(sizeof(LARGEBLOCKHEADER)));

		#ifdef _ARMOR		
			if( 0!=IsBadReadPtr(pHeader,sizeof(LARGEBLOCKHEADER)) )
					STOP(("Bad memory pointer passed to gos_Free (0x%x)",MemoryPtr ));
		#endif

		LARGEBLOCK* pBlock=pHeader->pLargeBlockInfo;

		#ifdef _ARMOR		
			if( 0!=IsBadReadPtr(pBlock,sizeof(LARGEBLOCK)) )
					STOP(("Bad memory pointer passed to gos_Free (0x%x)",MemoryPtr ));
		#endif
//
// Showing memory allocations during game?
//
		#ifdef _DEBUG
			if( gShowMemoryUseage==1 || gShowAllocations )
			{
				SPEW(( 0, "Freeing %d bytes", pBlock->Size ));
				DWORD Heap[11];
				DWORD MemoryLevels=Environment.memoryTraceLevel;
				if( MemoryLevels<5 )
				{
					MemoryLevels=5;
				}
				else if( MemoryLevels>10 )
				{
					MemoryLevels=10;
				}
				WalkStack( Heap, MemoryLevels, 1 );
				if( (Heap[0]-(int)&operator delete) < 256 )					// Is the first entry the GameOS delete operator?
				{
					WalkStack( Heap, MemoryLevels, 2 );
				}
				for( int t0=0; t0<MemoryLevels; t0++ )
				{
					if( Heap[t0] )
					{
						SPEW(( 0, DecodeAddress( Heap[t0], false ) ));
					}
				}
			}
		#endif
//
// Check the memory has not been corrupted after the end of the block
//
		#ifdef _ARMOR
			if(	MemoryEndMarker != *(DWORD*)(((BYTE*)MemoryPtr+pBlock->Size)) )
			{
				#ifdef _DEBUG
					if( gMemoryStackWalkLevel )
					{
						SPEW((0, " " ));
						SPEW((0, "Stack dump of original allocation (with corruption after block)" ));
						for(int i = 0; i < gMemoryStackWalkLevel; i++)
						{
							char * stack = DecodeAddress(pBlock->pContext[i], false );
							if (stack)
							{
								SPEW((0, "%s", stack));
							}
							else
								break;
						}
					}
					STOP(( "Memory block corrupted after end of block at address 0x%x (%d %s originally allocated at %fs)",MemoryPtr, pBlock->Size, (pBlock->Size==1 ? "byte" : "bytes"), pBlock->dTimeStamp ));
				#else
					STOP(( "Memory block corrupted after end of block at address 0x%x (%d %s)",MemoryPtr, pBlock->Size, (pBlock->Size==1 ? "byte" : "bytes") ));
				#endif
			}
		#endif
//
// Reduce memory useage of heap
//
		gos_ChangeHeapSize( HeapList[pBlock->Heap], -(signed)pBlock->Size );
//
// If 'not freeing' blocks, don't actually free the memory
//
		if( gNoFree )
			return;
//
// Delete the block from the large block list
//
		if( pHeader->pLargeBlockInfo->pLast )
		{
			pHeader->pLargeBlockInfo->pLast->pLargeBlockInfo->pNext = pHeader->pLargeBlockInfo->pNext;
			if( pHeader->pLargeBlockInfo->pNext )
			{
				pHeader->pLargeBlockInfo->pNext->pLargeBlockInfo->pLast = pHeader->pLargeBlockInfo->pLast;
			}
		}
		else
		{
			gMemoryBigAllocList = pHeader->pLargeBlockInfo->pNext;
			if( pHeader->pLargeBlockInfo->pNext )
			{
				pHeader->pLargeBlockInfo->pNext->pLargeBlockInfo->pLast = 0;
			}
		}
//
// Update information about memory blocks
//
		LargeMemorySize-=pBlock->Size;
		LargeMemoryAllocations--;
		free( pBlock );
		return;
	}
//
// Unknown header byte before the memory block
//
	STOP(( "Attempted to free invalid memory, or memory corrupted before block at address 0x%x",MemoryPtr ));
}





//
// Create a GameOS managed memory heap (for blocks less than 4K)
//
HGOSHEAP __stdcall gos_CreateMemoryHeap( char* HeapName, DWORD MaximumSize, HGOSHEAP parentHeap )
{
	if( !HeapName || strlen(HeapName)>127 )
		STOP(( "Invalid heap name" ));
	if (parentHeap && parentHeap->Magic != 0x7fbadbad)
		STOP(( "Parent Heap is invalid" ));

	gos_Heap* pHeap=0;
	gos_Heap* pLast=0;
	gos_Heap* pThis= parentHeap == 0 ? AllHeaps : parentHeap->pChild;

	while( pThis )
	{
		if( 0==stricmp( HeapName, pThis->Name ) )
		{
			pThis->Instances++;
			#ifdef _ARMOR
				char tmp[512];
				tmp[0] = 0;
				sprintf(tmp, "Incrementing instance counter for heap \"");
				strcat(tmp,pThis->Name);
				gos_Heap * pPrint = pThis->pParent;
				while (pPrint)
				{
					strcat(tmp,"<-");
					strcat(tmp,pPrint->Name);
					pPrint = pPrint->pParent;
				}
				SPEW((GROUP_MEMORYVERBOSE, "%s\"", tmp ));
			#endif
			return pThis;
		}
		pLast=pThis;
		pThis=pThis->pNext;
	}

	pHeap=(gos_Heap*)malloc(sizeof(gos_Heap));						// MUST NOT USE GAMEOS MEMORY FUNCTIONS!
	pHeap->pNext = 0;

	pHeap->Magic=0x7fbadbad;
	
	// setup hierarchical heaps
	pHeap->pParent = parentHeap;
	if (parentHeap)
	{
		pHeap->pNext = parentHeap->pChild;
		parentHeap->pChild = pHeap;
	}
	else
	{
		if (pLast)
			pLast->pNext = pHeap;
		else
			AllHeaps = pHeap;
	}

	pHeap->Instances=1;
	pHeap->pChild = 0;
	strcpy( pHeap->Name, HeapName );
	#ifdef LAB_ONLY
		pHeap->MaximumSize=MaximumSize;
		pHeap->BytesAllocated=0;
		pHeap->AllocationsThisLoop = 0;
		pHeap->AllocationsLastLoop = 0;
		pHeap->TotalAllocations = 0;
		pHeap->TotalAllocatedLastLoop = 0;
		pHeap->LargeAllocated=0;
		pHeap->LargeAllocations=0;
		pHeap->BytesAllocatedThisLoop = 0;
		pHeap->BytesFreedThisLoop = 0;
		pHeap->PeakSize = 0;
		pHeap->DXAllocations = 0;
		pHeap->DXAllocated = 0;
		pHeap->bExpanded = false;
		pHeap->bExamined = false;
	#endif

	#ifdef _ARMOR
		char tmp[512];
		tmp[0] = 0;
		sprintf(tmp, "Created heap \"");
		strcat(tmp,pHeap->Name);
		gos_Heap * pPrint = pHeap->pParent;
		while (pPrint)
		{
			strcat(tmp,"<-");
			strcat(tmp,pPrint->Name);
			pPrint = pPrint->pParent;
		}
		SPEW((GROUP_MEMORYVERBOSE, "%s\"", tmp ));
	#endif
//
// Add to list of heaps - heap 0 is invalid.
//
	for( BYTE t0=1; t0<Magic_LargeBlock; t0++ )
	{
		if( 0==HeapList[t0] )
		{
			HeapList[t0]=(HGOSHEAP)pHeap;
			pHeap->HeapNumber=(BYTE)t0;
			break;
		}
	}
	gosASSERT( t0!=Magic_LargeBlock && "Too many heaps" );

	return (HGOSHEAP)pHeap;
}











//
// Deletes a memory heap, will display any memory still allocated
//
void __stdcall gos_DestroyMemoryHeap( HGOSHEAP Heap, bool vociferous )
{
	// heap doesn't exist yet
	if( Heap == 0 )
		return;

	gos_Heap* pHeap = (gos_Heap*)Heap;
	if( pHeap->Magic!=0x7fbadbad )
			STOP(( "Attempt to destroy invalid heap" ));

	gos_Heap * myParent = pHeap->pParent;
	gos_Heap * ptr = myParent ? myParent->pChild : AllHeaps ;
	gos_Heap * prev = NULL;
	while (ptr)
	{
		if( pHeap == ptr )
		{
			// found it!
			pHeap->Instances--;
			if (!pHeap->Instances)
				DestroySelfAndChildren(pHeap, vociferous);
			else
			{
				#ifdef _ARMOR
					char tmp[512];
					tmp[0] = 0;
					sprintf(tmp, "Decrementing instance counter for heap \"");
					strcat(tmp,pHeap->Name);
					gos_Heap * pPrint = pHeap->pParent;
					while (pPrint)
					{
						strcat(tmp,"<-");
						strcat(tmp,pPrint->Name);
						pPrint = pPrint->pParent;
					}
					SPEW((GROUP_MEMORYVERBOSE, "%s\"", tmp ));
				#endif
				return;
			}
			break;
		}
		prev = ptr;
		ptr = ptr->pNext;
	}

	if (ptr == NULL)
		STOP(( "Could not find heap \"%s\" in heap \"%s!\"", pHeap->Name, pHeap->pParent ? pHeap->pParent->Name : "" ));

	// remove this heap from the parent's list
	if (prev)
		prev->pNext = pHeap->pNext;
	else
	{
	   	if (myParent)
			myParent->pChild = pHeap->pNext;
		else
			AllHeaps = pHeap->pNext;
	}

	#ifdef _ARMOR
		char tmp[512];
		tmp[0] = 0;
		sprintf(tmp, "Destroyed heap \"");
		strcat(tmp,pHeap->Name);
		gos_Heap * pPrint = pHeap->pParent;
		while (pPrint)
		{
			strcat(tmp,"<-");
			strcat(tmp,pPrint->Name);
			pPrint = pPrint->pParent;
		}
		SPEW((GROUP_MEMORYVERBOSE, "%s\"", tmp ));
	#endif
	free(pHeap);
}










//
// Updates heap memory used size (used for heaps that track memory usage but do not actually allocate memory, like the DirectDraw heaps)
//
void gos_ChangeHeapSize( HGOSHEAP pHeap, int Change, bool SystemAllocation )
{
	#ifdef LAB_ONLY
//
// (0) No heap means client default heap
//
	if( 0==pHeap )
		pHeap=ClientHeap;

	SPEW(( GROUP_MEMORYVERBOSE, "gos_ChangeHeapSize(\"%s\",%d)", pHeap->Name, Change ));
//
// Heap maximum checks only occur on debug/armor builds when memory INCREASED
//
	if( Change > 0 )
	{
		MemoryAllocs++;
		MemoryAllocated+=Change;
		if( SystemAllocation )
		{
			SystemMemoryAllocs++;
			SystemMemoryAllocated+=Change;
		}

		do
		{
			pHeap->BytesAllocated+=Change;
			
			pHeap->BytesAllocatedThisLoop+=Change;

			pHeap->AllocationsThisLoop++;
			pHeap->TotalAllocations++;

			if( SystemAllocation )
			{
				pHeap->DXAllocated+=Change;
				pHeap->DXAllocations++;
			}
			else
			{
				#ifdef _DEBUG
					if( Change+5 > LargestMemoryBlock )
				#else
					if( Change+1 > LargestMemoryBlock )
				#endif
				{
					pHeap->LargeAllocated+=Change;
					pHeap->LargeAllocations++;
				}
			}

			if( pHeap->BytesAllocated > pHeap->PeakSize )
				pHeap->PeakSize = pHeap->BytesAllocated;

			if( pHeap->MaximumSize && pHeap->BytesAllocated > pHeap->MaximumSize )
			{
				PAUSE(( "Allocation of %d bytes exceeded %s's heaps maximum size (%d).", Change, pHeap->Name, pHeap->MaximumSize ));
			}

			pHeap = pHeap->pParent;

		} while( pHeap );
	}
	else
//
// Memory freed - cannot break any heap sizes
//
	{
		MemoryFrees++;
		MemoryFreed+= -Change;
		if( SystemAllocation )
		{
			SystemMemoryAllocs--;
			SystemMemoryAllocated+=Change;
		}
		do
		{
			pHeap->AllocationsThisLoop--;
			pHeap->TotalAllocations--;

			pHeap->BytesAllocated+=Change;
			
			pHeap->BytesFreedThisLoop+=-Change;

			if( SystemAllocation )
			{
				pHeap->DXAllocated+=Change;
				pHeap->DXAllocations--;
			}
			else
			{
				#ifdef _DEBUG
					if( -Change+5 > LargestMemoryBlock )
				#else
					if( -Change+1 > LargestMemoryBlock )
				#endif
				{
					pHeap->LargeAllocated+=Change;
					pHeap->LargeAllocations--;
				}
			}

			pHeap = pHeap->pParent;

		} while( pHeap );
	}
	#endif
}













//
// Save the current heap and select a new one
//
void __stdcall gos_PushCurrentHeap( HGOSHEAP Heap )
{
	gCurrentHeapStack[gCurrentHeapStackLevel++] = CurrentHeap;
	CurrentHeap = Heap;
	if( gCurrentHeapStackLevel==64 )
		STOP(( "Heap stack overflowed" ));
}



//
// Return the currently selected memory heap
//
HGOSHEAP __stdcall gos_GetCurrentHeap()
{
	return CurrentHeap;
}


//
// Restore a previously saved memory heap
//
void __stdcall gos_PopCurrentHeap()
{
	gCurrentHeapStackLevel--;
	if( gCurrentHeapStackLevel==-1 )
		STOP(( "Heap stack underflow" ));
	CurrentHeap = gCurrentHeapStack[gCurrentHeapStackLevel];
}









MEMORYPOOL* MM_CreateMemoryPool( WORD blockSize )
{
//
// BlockSize + WORD pointer + stack walk data + block infomation
//
	DWORD AllocationSize=blockSize + 2 + gMemoryStackWalkLevel*4 + (blockSize < 256 ? sizeof(SMALLPOOLBLOCK) : sizeof(POOLBLOCK));
//
// On _ARMOR builds add an end marker
//
	#ifdef _ARMOR
	AllocationSize+=4;
	#endif
//
// Calulate total memory needed from system
//
// 64K allocated - Memorypool header - room for 8 byte aligment - single byte block header
//
	DWORD NumberOfBlocks=(65536-sizeof(MEMORYPOOL)-8-1) / AllocationSize;
//
// Reserve all 64K of memory from system
//
	BYTE* BaseMemory;

	if( AgressiveManager )
	{
		BaseMemory=(BYTE*)VirtualAlloc( NULL, 65536, MEM_RESERVE, PAGE_READWRITE );
	}
	else
	{
		BaseMemory=(BYTE*)VirtualAlloc( NULL, 65536, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE );
	}
	if( 0==BaseMemory )
		STOP(( "Out of Memory" ));
//
// 1) Memory pool header and a WORD for each block
//
	MEMORYPOOL* newPool = (MEMORYPOOL*)BaseMemory;
	BaseMemory+=sizeof(MEMORYPOOL)+NumberOfBlocks*2;
//
// 2) BlockInfo and walk stack info
//
	POOLBLOCK* BaseBlocks=(POOLBLOCK*)BaseMemory;
	BaseMemory+=NumberOfBlocks * (gMemoryStackWalkLevel*4 + (blockSize < 256 ? sizeof(SMALLPOOLBLOCK) : sizeof(POOLBLOCK)) );
//
// 4) Align pointer so that memory heap + byte header is 8 byte aligned (This is where blocks start)
//
	BaseMemory=(BYTE*) (((int)BaseMemory+8) & -8) - 1;
//
// Only commit the pool structures and at least the first block
//
	if( AgressiveManager )
	{
		DWORD requiredBlocks=( (int)BaseMemory- (int)newPool + blockSize + 4095 ) / 4096;
		VirtualAlloc( newPool, requiredBlocks*4096, MEM_COMMIT, PAGE_READWRITE );

		memset( newPool->AllocCount, 0, sizeof(newPool->AllocCount) );
		for( int t0=0; t0<requiredBlocks; t0++ )
		{
			newPool->AllocCount[t0]=32768;								// Use high value so block will never be decommitted
		}
	}
//
// Setup pointers to areas of memory
//
	newPool->wBlockSize = (WORD)blockSize;
	#ifdef LAB_ONLY
	newPool->wUserBytes = 0;
	#endif
	newPool->HeapTag=(int)'SOG*';
	newPool->pNext = 0;
	newPool->pLast = 0;
	newPool->pMemoryPool = BaseMemory;
	newPool->pInfoBlocks = BaseBlocks;
	newPool->wTotalBlocks=(WORD)NumberOfBlocks;
	newPool->FreeBlockPtr = 0;
//
// Sanity check some pointers (cause exception if bad so that registers can be examined)
//
	if( ((int)newPool->pMemoryPool&0xffff0000)!=((int)newPool->pInfoBlocks&0xffff0000) ||
		((int)newPool->pMemoryPool&0xffff0000)!=((int)newPool&0xffff0000) ||
		((int)newPool->pLast&0xffff)!=0 ||
		((int)newPool->pNext&0xffff)!=0 ||
		(newPool->HeapTag!=(int)'SOG*') )
	{
		_asm mov eax,gMemoryStackWalkLevel
		_asm mov esi,newPool
		_asm int 3;
	}
//
// Clear HEAPBLOCKINFO and stack walk data
//
	memset( newPool->pInfoBlocks, 0, (gMemoryStackWalkLevel*4 + (blockSize < 256 ? sizeof(SMALLPOOLBLOCK) : sizeof(POOLBLOCK))) * NumberOfBlocks );
//
// Create pointers to individual memory blocks
//
	for( int block=0; block < NumberOfBlocks; block++ )
	{
		if( !AgressiveManager )
		{
			*BaseMemory=0;											// Setup invalid type byte (block may not be paged in with agressive manager)
		}

		newPool->FreeBlockStack[block]=(WORD)((int)BaseMemory-(int)newPool);

		BaseMemory+=blockSize;
	}
//
// Return a pointer to the base of the pool
//
	return newPool;
}




//
// Destroy a memory pool - show any leaks if there are still blocks allocated
//
void MM_DestroyMemoryPool( MEMORYPOOL* pPool )
{
	while( pPool )
	{
//
// Sanity check some pointers (cause exception if bad so that registers can be examined)
//
		if( ((int)pPool->pMemoryPool&0xffff0000)!=((int)pPool->pInfoBlocks&0xffff0000) ||
			((int)pPool->pMemoryPool&0xffff0000)!=((int)pPool&0xffff0000) ||
			((int)pPool->pLast&0xffff)!=0 ||
			((int)pPool->pNext&0xffff)!=0 ||
			(pPool->HeapTag!=(int)'SOG*') )
		{
			_asm mov eax,gMemoryStackWalkLevel
			_asm mov esi,pPool
			_asm int 3;
		}

		MEMORYPOOL* Next=pPool->pNext;

		#ifdef _DEBUG
			if( pPool->FreeBlockPtr )
			{
				if( pPool->wBlockSize < 256 )
				{
					if( !SpewSilence )
					{
						SMALLPOOLBLOCK* pBlock= (SMALLPOOLBLOCK*)pPool->pInfoBlocks;
						BYTE* memory = pPool->pMemoryPool;
					
						for( int t0=0; t0<pPool->wTotalBlocks; t0++ )
						{
							BYTE Heap;
							
							if( AgressiveManager )
							{
								DWORD startBlockNumber=( (int)memory - (int)pPool ) / 4096;
								DWORD endBlockNumber=( (int)memory - (int)pPool + pPool->wBlockSize ) / 4096;

								if( 0==pPool->AllocCount[startBlockNumber] || 0==pPool->AllocCount[endBlockNumber] )
									Heap=0;									// Block is decomitted, don't check it.
								else
									Heap=*memory;
							}
							else
							{
								Heap=*memory;
							}
							
							if( Heap )
								DisplayLeakInformation( 0, 0, pBlock, 0 );

							pBlock = (SMALLPOOLBLOCK*)((int)pBlock + sizeof(SMALLPOOLBLOCK) + gMemoryStackWalkLevel*4);
							memory += pPool->wBlockSize;
						}
					}
				}
				else
				{
					if( !SpewSilence )
					{
						POOLBLOCK* pBlock= pPool->pInfoBlocks;
						BYTE* memory = pPool->pMemoryPool;
					
						for( int t0=0; t0<pPool->wTotalBlocks; t0++ )
						{
							BYTE Heap;
							
							if( AgressiveManager )
							{
								DWORD startBlockNumber=( (int)memory - (int)pPool ) / 4096;
								DWORD endBlockNumber=( (int)memory - (int)pPool + pPool->wBlockSize ) / 4096;

								if( 0==pPool->AllocCount[startBlockNumber] || 0==pPool->AllocCount[endBlockNumber] )
									Heap=0;									// Block is decomitted, don't check it.
								else
									Heap=*memory;
							}
							else
							{
								Heap=*memory;
							}

							if( Heap )
								DisplayLeakInformation( 0, pBlock, 0, 0 );

							pBlock = (POOLBLOCK*)((int)pBlock + sizeof(POOLBLOCK) + gMemoryStackWalkLevel*4);
							memory += pPool->wBlockSize;
						}
					}
				}
			}
		#endif
	
		// free memory related to this pool memory
		VirtualFree( pPool, 0, MEM_DECOMMIT );
		VirtualFree( pPool, 0, MEM_RELEASE );
		pPool = Next;
	}
}







//
// Display information about the leaked memory block (If address passed, this is information, otherwise a LEAK)
//							
#ifdef _DEBUG
void DisplayLeakInformation( LARGEBLOCKHEADER* pLargeblockInfo, POOLBLOCK* pSmallblockInfo, SMALLPOOLBLOCK* pTinyblockInfo, BYTE* Address )
{
//
// Try and find start address of block
//
	BYTE* Location;
	BYTE Heap;
	DWORD* Context;
	DWORD Size;
	double Time;

	if( pLargeblockInfo )
	{
		Heap=pLargeblockInfo->pLargeBlockInfo->Heap;
		Context=pLargeblockInfo->pLargeBlockInfo->pContext;
		Size=pLargeblockInfo->pLargeBlockInfo->Size;
		Time=pLargeblockInfo->pLargeBlockInfo->dTimeStamp;
		Location=(BYTE*)&pLargeblockInfo[1];
	}
	else
	{
		if( pTinyblockInfo )
		{
			MEMORYPOOL* pPool=(MEMORYPOOL*)((int)pTinyblockInfo&0xffff0000);
			Context=pTinyblockInfo->pContext;
			Size=pTinyblockInfo->Size;
			Time=pTinyblockInfo->dTimeStamp;
			Location=(BYTE*)( 1 + pPool->pMemoryPool + (pPool->wBlockSize*(((int)pTinyblockInfo-(int)pPool->pInfoBlocks) / (sizeof(SMALLPOOLBLOCK)+gMemoryStackWalkLevel*4))));
			Heap=*(Location-1);
		}
		else
		{
			MEMORYPOOL* pPool=(MEMORYPOOL*)((int)pSmallblockInfo&0xffff0000);
			Context=pSmallblockInfo->pContext;
			Size=pSmallblockInfo->Size;
			Time=pSmallblockInfo->dTimeStamp;
			Location=(BYTE*)( 1 + pPool->pMemoryPool + (pPool->wBlockSize*(((int)pSmallblockInfo-(int)pPool->pInfoBlocks) / (sizeof(POOLBLOCK)+gMemoryStackWalkLevel*4))));
			Heap=*(Location-1);
		}
	}
//
// Show information about allocation
//
	SPEW((GROUP_MEMORY, "MEMORY %s at address 0x%x from heap '%s' (%d %s originally allocated at %fs)", (Address ? "BLOCK" : "LEAK"), Location, HeapList[Heap]->Name, Size, (Size==1 ? "byte" : "bytes"), Time ));
//
// Show first 16 bytes of memory at the location
//
	if( Location )
	{
		char Buffer[128];
		char* pBuffer=Buffer;
		char* HexChar="0123456789ABCDEF";

		DWORD NumBytes=Size < 16 ? Size : 16;
		Buffer[NumBytes*3]='\"';
		Buffer[NumBytes*4+1]='\"';
		Buffer[NumBytes*4+2]=0;
		
		for( int t0=0; t0<NumBytes; t0++ )
		{
			*pBuffer++=HexChar[(Location[t0]>>4)];
			*pBuffer++=HexChar[(Location[t0]&15)];
			*pBuffer++=' ';
			char Ascii=(Location[t0]>=32 && Location[t0]<=127) ? Location[t0] : '.';
			Buffer[NumBytes*3+1+t0]=Ascii;
		}

		SPEW(( GROUP_MEMORY, "Data : %s",Buffer ));
	}
//
// Show stack walk if requested
//
	if (gMemoryStackWalkLevel)
	{
		for(int i = 0; i < gMemoryStackWalkLevel; i++)
		{
			char* stack = DecodeAddress(Context[i], false );
			if (stack)
			{
				SPEW((GROUP_MEMORY, "%s", stack));
			}
			else
				break;
		}
		SPEW((GROUP_MEMORY, ""));
	}
}
#else
void DisplayLeakInformation( LARGEBLOCKHEADER*, POOLBLOCK* , SMALLPOOLBLOCK*, BYTE* Address )
{
}
#endif







void SpewAndFreeHeapAllocs( HGOSHEAP pHeap, bool vociferous )
{
	if( pHeap->pChild )
		SpewAndFreeHeapAllocs( pHeap->pChild, vociferous );

	if( pHeap->pParent==0 && pHeap->pNext )
		SpewAndFreeHeapAllocs( pHeap->pNext, vociferous );
//
// Free all small memory blocks
//
	for( int j=0; j<MemoryPools; j++ )
	{
		MEMORYPOOL* pPool = gMemoryPool[j];
		MEMORYPOOL* pNext = NULL;
		while(pPool)
		{
			if( pPool->FreeBlockPtr )
			{
				if( pPool->wBlockSize<256 )
				{
					pNext = pPool->pNext;
					SMALLPOOLBLOCK* pBlock= (SMALLPOOLBLOCK*)pPool->pInfoBlocks;
					BYTE* memory = pPool->pMemoryPool;
					bool Ignore;

					for( int t0=0; t0<pPool->wTotalBlocks; t0++ )
					{
						Ignore=0;
						
						if( AgressiveManager )
						{
							DWORD startBlockNumber=( (int)memory - (int)pPool ) / 4096;
							DWORD endBlockNumber=( (int)memory - (int)pPool + pPool->wBlockSize ) / 4096;

							if( 0==pPool->AllocCount[startBlockNumber] || 0==pPool->AllocCount[endBlockNumber] )
								Ignore=1;									// Block is decomitted, don't check it.
						}

						if( !Ignore && HeapList[*memory]==pHeap )
						{
							#ifdef LAB_ONLY
							if (vociferous)
							{
								MemoryLeaks++;
								LeakedMemory+=pBlock->Size;
								DisplayLeakInformation( 0, 0, pBlock, 0 );
							}
							#endif
							if( 1==pPool->FreeBlockPtr )
							{
								gos_Free(memory+1);							// If last allocation in pool, the pool will now be invalid
								break;
							}
							else
							{
								gos_Free(memory+1);
							}
						}

						pBlock = (SMALLPOOLBLOCK*)((int)pBlock + sizeof(SMALLPOOLBLOCK) + gMemoryStackWalkLevel*4);
						memory += pPool->wBlockSize;
					}
				}
				else
				{
					pNext = pPool->pNext;
					POOLBLOCK* pBlock= pPool->pInfoBlocks;
					BYTE* memory = pPool->pMemoryPool;
					bool Ignore;
				
					for( int t0=0; t0<pPool->wTotalBlocks; t0++ )
					{
						Ignore=0;
						
						if( AgressiveManager )
						{
							DWORD startBlockNumber=( (int)memory - (int)pPool ) / 4096;
							DWORD endBlockNumber=( (int)memory - (int)pPool + pPool->wBlockSize ) / 4096;

							if( 0==pPool->AllocCount[startBlockNumber] || 0==pPool->AllocCount[endBlockNumber] )
								Ignore=1;									// Block is decomitted, don't check it.
						}

						if( !Ignore && HeapList[*memory]==pHeap )
						{
							#ifdef LAB_ONLY
							if (vociferous)
							{
								MemoryLeaks++;
								LeakedMemory+=pBlock->Size;
								DisplayLeakInformation( 0, pBlock, 0, 0 );
							}
							#endif
							if( 1==pPool->FreeBlockPtr )
							{
								gos_Free(memory+1);							// If last allocation in pool, the pool will now be invalid
								break;
							}
							else
							{
								gos_Free(memory+1);
							}
						}

						pBlock = (POOLBLOCK*)((int)pBlock + sizeof(POOLBLOCK) + gMemoryStackWalkLevel*4);
						memory += pPool->wBlockSize;
					}
				}
			}
			pPool = pNext;
		}
	}
//
// Free all large memory blocks
//
	LARGEBLOCKHEADER* ba = gMemoryBigAllocList;
	LARGEBLOCKHEADER* next = NULL;
	while (ba)
	{
		next = ba->pLargeBlockInfo->pNext;
		if( HeapList[ba->pLargeBlockInfo->Heap] == pHeap )
		{
			#ifdef LAB_ONLY
			if (vociferous)
			{
				MemoryLeaks++;
				LeakedMemory+=ba->pLargeBlockInfo->Size;
				DisplayLeakInformation( ba, 0, 0, 0 );
			}
			#endif
			gos_Free(&ba[1]);
		}
		ba = next;
	}
}

void DestroySelfAndChildren(HGOSHEAP toDestroy, bool vociferous)
{
//
// Disable any spews or errors if exiting a game
//
	if( gNoFree || (ProcessingError && TerminateGame) )
		vociferous=0;
//
// recursively kill child heaps!
//
	HGOSHEAP itr = toDestroy->pChild;
	HGOSHEAP bnext = NULL;

	while (itr)
	{
		bnext = itr->pNext;
		if (itr)
		{
			itr->Instances = 1;
			gos_DestroyMemoryHeap(itr, vociferous);
		}
		itr = bnext;
	}

//
// SPEW if there's still bytes alloc'd
//
#ifdef _DEBUG
	if (vociferous && toDestroy->BytesAllocated)
		SPEW(( GROUP_MEMORY, "Destroying heap '%s'. There are %d %s still allocated", toDestroy->Name,toDestroy->BytesAllocated, (toDestroy->BytesAllocated==1 ? "byte" : "bytes") ));
#endif
//
// iterate through every pool in existance, looking for items that are alloc'd from this heap!
//
	SpewAndFreeHeapAllocs(toDestroy, vociferous);
//
// Remove from list of heaps
//
	for( int t0=0; t0<256; t0++ )
	{
		if( HeapList[t0]==toDestroy )
		{
			HeapList[t0]=0;
		}
	}
}


void __stdcall gos_WalkMemoryHeap( HGOSHEAP Heap, bool vociferous )
{
	#ifdef _DEBUG
		// set up a pointer to the actual heap, check it, and err out if the heap handle is bogus
		// heap doesn't exist yet
		if( Heap == 0 )
		{
			HGOSHEAP toCheck = AllHeaps;
			while(toCheck)
			{
				gos_WalkMemoryHeap(toCheck, vociferous);
				toCheck = toCheck->pNext;
			}
			return;
		}

		gos_Heap* pHeap = (gos_Heap*)Heap;
		if( pHeap->Magic!=0x7fbadbad )
				STOP(( "Attempt to walk invalid heap" ));

		char tmp[512];
		tmp[0] = 0;
		sprintf(tmp, "Walking heap \"");
		strcat(tmp,pHeap->Name);
		gos_Heap * pPrint = pHeap->pParent;
		while (pPrint)
		{
			strcat(tmp,"<-");
			strcat(tmp,pPrint->Name);
			pPrint = pPrint->pParent;
		}
		SPEW((GROUP_MEMORYVERBOSE, "%s\"", tmp ));

		gos_Heap * myParent = pHeap->pParent;
		gos_Heap * ptr = myParent ? myParent->pChild : AllHeaps ;
		gos_Heap * prev = NULL;
		while (ptr)
		{
			if( pHeap == ptr )
			{
				WalkSelfAndChildren(pHeap, vociferous);
				return;
			}
			prev = ptr;
			ptr = ptr->pNext;
		}

		if (ptr == NULL)
			STOP(( "Could not find heap \"%s\" in heap \"%s\"", pHeap->Name, pHeap->pParent ? pHeap->pParent->Name : "" ));
	#endif
}

void WalkSelfAndChildren( HGOSHEAP toWalk, bool vociferous )
{
	//
	// recursively kill child heaps!
	//
	HGOSHEAP itr = toWalk->pChild;
	HGOSHEAP bnext = NULL;

	while( itr )
	{
		bnext = itr->pNext;
		if( itr )
		{
			itr->Instances = 1;
			gos_WalkMemoryHeap( itr, vociferous );
		}
		itr = bnext;
	}

	//
	// SPEW if there's still bytes alloc'd
	//
	#ifdef _ARMOR
		if (vociferous && toWalk->BytesAllocated)
			SPEW(( GROUP_MEMORY, "Walking heap '%s'. There are %d %s allocated", toWalk->Name,toWalk->BytesAllocated, (toWalk->BytesAllocated==1 ? "byte" : "bytes") ));
	#endif
	//
	// iterate through every pool in existance, looking for items that are alloc'd from this heap!
	//
	WalkHeapAllocs( toWalk, vociferous );
}


void WalkHeapAllocs( HGOSHEAP pHeap, bool vociferous )
{
	if( pHeap->pChild )
		WalkHeapAllocs( pHeap->pChild, vociferous );

	if( pHeap->pParent==0 && pHeap->pNext )
		WalkHeapAllocs( pHeap->pNext, vociferous );
//
// Iterate through small blocks
//
	if( vociferous )
	{
		for( int j=0; j<MemoryPools; j++ )
		{
			MEMORYPOOL* pPool = gMemoryPool[j];
			MEMORYPOOL* pNext = NULL;
			while(pPool)
			{
				if( pPool->FreeBlockPtr )
				{
					if( pPool->wBlockSize<256 )
					{
						pNext = pPool->pNext;
						SMALLPOOLBLOCK* pBlock= (SMALLPOOLBLOCK*)pPool->pInfoBlocks;
						BYTE* memory = pPool->pMemoryPool;
						bool Ignore;

						for( int t0=0; t0<pPool->wTotalBlocks; t0++ )
						{
							Ignore=0;
							
							if( AgressiveManager )
							{
								DWORD startBlockNumber=( (int)memory - (int)pPool ) / 4096;
								DWORD endBlockNumber=( (int)memory - (int)pPool + pPool->wBlockSize ) / 4096;

								if( 0==pPool->AllocCount[startBlockNumber] || 0==pPool->AllocCount[endBlockNumber] )
									Ignore=1;									// Block is decomitted, don't check it.
							}

							if( !Ignore && HeapList[*memory]==pHeap )
							{
								if( vociferous )
								{
									DisplayLeakInformation( 0, 0, pBlock, memory+1 );
								}
							}

							pBlock = (SMALLPOOLBLOCK*)((int)pBlock + sizeof(SMALLPOOLBLOCK) + gMemoryStackWalkLevel*4);
							memory += pPool->wBlockSize;
						}
					}
					else
					{
						pNext = pPool->pNext;
						POOLBLOCK* pBlock= pPool->pInfoBlocks;
						BYTE* memory = pPool->pMemoryPool;
						bool Ignore;
					
						for( int t0=0; t0<pPool->wTotalBlocks; t0++ )
						{
							Ignore=0;
							
							if( AgressiveManager )
							{
								DWORD startBlockNumber=( (int)memory - (int)pPool ) / 4096;
								DWORD endBlockNumber=( (int)memory - (int)pPool + pPool->wBlockSize ) / 4096;

								if( 0==pPool->AllocCount[startBlockNumber] || 0==pPool->AllocCount[endBlockNumber] )
									Ignore=1;									// Block is decomitted, don't check it.
							}

							if( !Ignore && HeapList[*memory]==pHeap )
							{
								DisplayLeakInformation( 0, pBlock, 0, memory+1 );
							}

							pBlock = (POOLBLOCK*)((int)pBlock + sizeof(POOLBLOCK) + gMemoryStackWalkLevel*4);
							memory += pPool->wBlockSize;
						}
					}
				}
				pPool = pNext;
			}
		}
	}
//
// Iterate through large blocks
//
	LARGEBLOCKHEADER* ba = gMemoryBigAllocList;
	LARGEBLOCKHEADER* next = NULL;
	while (ba)
	{
		#ifdef _ARMOR
			if( 0!=IsBadReadPtr( ba, sizeof(LARGEBLOCKHEADER)) )
				STOP(( "Big allocs list has been corrupted" ));
		#endif

		if( Magic_LargeBlock!=ba->LargeMagicNumber )
			STOP(( "Big allocs list has been corrupted" ));

		next=ba->pLargeBlockInfo->pNext;
		
		if( HeapList[ba->pLargeBlockInfo->Heap] == pHeap )
		{
			if( vociferous )
			{
				#ifdef _DEBUG
					SPEW((GROUP_MEMORY, "Memory block 0x%x from heap \"%s\" (%d %s originally allocated at %fs)", &ba[1], pHeap->Name, ba->pLargeBlockInfo->Size, (ba->pLargeBlockInfo->Size==1 ? "byte" : "bytes"), ba->pLargeBlockInfo->dTimeStamp ));
					if( gMemoryStackWalkLevel )
					{
						for( int i=0; i<gMemoryStackWalkLevel; i++ )
						{
							char* stack = DecodeAddress( ba->pLargeBlockInfo->pContext[i], false );
							if( stack )
							{
								SPEW((GROUP_MEMORY, "%s", stack));
							}
							else
								break;
						}
						SPEW(( GROUP_MEMORY, "" ));
					}
				#endif		
			}
		}
		ba = next;
	}
}


PSAPI_WS_WATCH_INFORMATION* pMemBlockInfo=0;
//
// Used to show current working set information
//
void AnalyzeWS( char* Title )
{
	#ifdef LAB_ONLY
	PSAPI_WS_WATCH_INFORMATION* ppMemBlock=pMemBlockInfo;
//
// Get up to 16K of page hits
//	
	BOOL result=_GetWsChanges(HWSProcess, ppMemBlock, 16*1024);
//
// Iterate through them and work our usage
//
	if( result )
	{
		char Buffer[1024];
//
// Show title
//
		Spew( "\nInformation about working set during - " );
		Spew( Title );
		Spew( "\n\n" );
//
// Work out which heaps all the allocations belong to
//
		PSAPI_WS_WATCH_INFORMATION* temp=ppMemBlock;

		DWORD CodePages=0;
		DWORD DataPages=0;
		DWORD CodePagesOther=0;
		DWORD DataPagesOther=0;
		DWORD MMFmemory=0;

		MEMORY_BASIC_INFORMATION Mymbi;
		VirtualQueryEx( HWSProcess, AnalyzeWS, &Mymbi, sizeof(Mymbi) );

		while( (ppMemBlock!=pMemBlockInfo+16*1024) && ppMemBlock->FaultingPc )
		{
			if( (ppMemBlock->FaultingVa>((char*)pMemBlockInfo+16*1024)) || ppMemBlock->FaultingVa<pMemBlockInfo )
			{
				MEMORY_BASIC_INFORMATION mbi;
				VirtualQueryEx( HWSProcess, ppMemBlock->FaultingVa, &mbi, sizeof(mbi) );

				if( mbi.AllocationBase!=Mymbi.AllocationBase )
				{
					if( mbi.Protect & (PAGE_EXECUTE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY) )
						CodePagesOther++;
					else
						DataPagesOther++;
				}
				else
				{
					if( mbi.Protect & (PAGE_EXECUTE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY) )
						CodePages++;
					else
						DataPages++;
				}

				if( _GetMappedFileName( HWSProcess, ppMemBlock->FaultingVa, Buffer, MAX_PATH) )
					MMFmemory++;
			}
			ppMemBlock++;
		}
		ppMemBlock=temp;
//
// Show information gathered
//
		sprintf( Buffer, "Total memory used\t\t\t\t%dk  (Total of code and data inside and outside app)\n",(CodePages+DataPages+CodePagesOther+DataPagesOther)*4 );
		Spew( Buffer );
		sprintf( Buffer, "Total memory used in app\t\t %dk  (Total of code+data below)\n",(CodePages+DataPages)*4 );
		Spew( Buffer );
		sprintf( Buffer, "Code memory used\t\t\t\t  %dk\n",CodePages*4 );
		Spew( Buffer );
		sprintf( Buffer, "Data memory used\t\t\t\t  %dk\n",DataPages*4 );
		Spew( Buffer );
		sprintf( Buffer, "Total memory used outside app\t %dk  (Total of code+data below)\n",(CodePagesOther+DataPagesOther)*4 );
		Spew( Buffer );
		sprintf( Buffer, "Code used outside app\t\t\t  %dk\n",CodePagesOther*4 );
		Spew( Buffer );
		sprintf( Buffer, "Data used outside app\t\t\t  %dk\n\n",DataPagesOther*4 );
		Spew( Buffer );
		sprintf( Buffer, "Memory mapped files memory used\t%dk\n",MMFmemory*4 );
		Spew( Buffer );
//
// Now display all allocations
//
		#ifdef _DEBUG
			Spew( "\nFault address\t\tCode address\tLine number\n\n" );
		#else
			Spew( "\nFault address\t\tCode address\tRoutine name\n\n" );
		#endif

		while( (ppMemBlock!=pMemBlockInfo+16*1024) && ppMemBlock->FaultingPc )
		{
//
// Do not show faults in the fault buffer!
//
			if( (ppMemBlock->FaultingVa>((char*)pMemBlockInfo+16*1024)) || ppMemBlock->FaultingVa<pMemBlockInfo )
			{
				char SymBuffer[256];

				#ifdef _DEBUG
				char* Line=DecodeAddress( (DWORD)ppMemBlock->FaultingPc, false );
				if( Line )
					strcpy( SymBuffer, Line );
				else
					SymBuffer[0]=0;
				#else
				GetSymbolFromAddress( SymBuffer, (int)ppMemBlock->FaultingPc );
				#endif

				char* MemType=" ";

				if( _GetMappedFileName( HWSProcess, ppMemBlock->FaultingVa, Buffer, MAX_PATH) )
					MemType="M";
				else
				{
					MEMORY_BASIC_INFORMATION mbi;
					VirtualQueryEx( HWSProcess, ppMemBlock->FaultingVa, &mbi, sizeof(mbi) );
					if( 0==(mbi.Protect & (PAGE_EXECUTE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY)) )
						MemType="D";
				}

				sprintf( Buffer, "0x%08x %s\t\t0x%08x\t\t%s\n",ppMemBlock->FaultingVa, MemType, ppMemBlock->FaultingPc, SymBuffer );

				Spew( Buffer );
			}
			ppMemBlock++;
		}
		Spew( "\n\n" );	
		if( !_EmptyWorkingSet(HWSProcess) )
		{
			gShowWSUseage=0;
		}
		else
		{
			if( !_InitializeProcessForWsWatch(HWSProcess) )
				gShowWSUseage=0;
			else
			{
				PSAPI_WS_WATCH_INFORMATION* ppMemBlock=pMemBlockInfo;
				_GetWsChanges(HWSProcess, ppMemBlock, 16*1024);
			}
		}
	}
	#endif
}





