/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***                            Confidential - Westwood Studios                              ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Commando                                                     *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Combat/debug.cpp                             $*
 *                                                                                             *
 *                      $Author:: Bhayes                                                      $*
 *                                                                                             *
 *                     $Modtime:: 2/16/02 8:44p                                               $*
 *                                                                                             *
 *                    $Revision:: 90                                                          $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "debug.h"
#include "input.h"
#include "ww3dtrig.h"
#include "wwphystrig.h"
#include "timemgr.h"
#include "ww3d.h"
#include "mono.h"
#include "registry.h"
#include <stdio.h>
#include "wwaudio.h"
#include "combat.h"
#include "wwmemlog.h"
#include "fastallocator.h"

#ifndef 	STEVES_NEW_CATCHER
#define LOG_MEMORY 1		// enable this to turn on memory logging
#endif	//STEVES_NEW_CATCHER

/*
**
*/
int		DebugManager::EnabledDevices;
int		DebugManager::EnabledTypes;
int		DebugManager::EnabledOptions;
bool	DebugManager::EnableFileLogging;
bool	DebugManager::EnableDiagLogging;
bool	DebugManager::LoadDebugScripts;
int	DebugManager::VersionNumber = 0;
bool	DebugManager::IsSlave = false;
bool	DebugManager::AllowCinematicKeys = false;

CriticalSectionClass DebugManager::CriticalSection;

DebugDisplayHandlerClass *	DebugManager::DisplayHandler		= NULL;
#define DEFAULT_LOGFILE_NAME "_logfile.txt"
LPSTR DebugManager::LOGFILE = DEFAULT_LOGFILE_NAME;
char DebugManager::LogfileNameBuffer[256];

MonoClass			ScrollingScreen;

/*
** local prototypes
*/
void wwdebug_message_handler(DebugType type, const char * message);
void wwdebug_assert_handler(const char * message);
bool wwdebug_trigger_handler(int trigger_num);
void wwdebug_profile_start_handler( const char * title );
void wwdebug_profile_stop_handler( const char * title );

char DefaultRegistryModifier[1024] = {""};

/*
**
*/
void	DebugManager::Init( void )
{
	// Enable Mono Screen
	ScrollingScreen.Enable();

	// Install message handler functions for the WWDebug messages
	// and assertion failures.
	WWDebug_Install_Message_Handler(wwdebug_message_handler);
	WWDebug_Install_Assert_Handler(wwdebug_assert_handler);
	WWDebug_Install_Trigger_Handler(wwdebug_trigger_handler);
	WWDebug_Install_Profile_Start_Handler(wwdebug_profile_start_handler);
	WWDebug_Install_Profile_Stop_Handler(wwdebug_profile_stop_handler);

	// Clear all of the debug devices, types, and options
	EnabledDevices = -1;
	EnabledTypes	= -1;
	EnabledOptions	= 0;

	EnableFileLogging = false;
	EnableDiagLogging = false;
	LoadDebugScripts = false;

	//
	// By default let us turn these off
	//
	Disable_Device(DEBUG_DEVICE_SCREEN);
	Disable_Type(DEBUG_TYPE_NETWORK_PROLIFIC);

   //
	// Clear the logfile
	//
   Init_Logfile();

	Debug_Say(( "\n" ));
}

/*
**
*/
void	DebugManager::Shutdown( void )
{
	// Remove message handler functions for the WWDebug messages
	// and assertion failures.
	WWDebug_Install_Message_Handler(NULL);
	WWDebug_Install_Assert_Handler(NULL);
	WWDebug_Install_Trigger_Handler(NULL);
	WWDebug_Install_Profile_Start_Handler(NULL);
	WWDebug_Install_Profile_Stop_Handler(NULL);

	// Disable mono screen
	ScrollingScreen.Disable();
}

/*
**
*/
void	DebugManager::Update( void )
{
	// Tell the profiler that a frame has passed

	if ( Input::Get_State( INPUT_FUNCTION_MAKE_SCREEN_SHOT) ) {
		WW3D::Make_Screen_Shot();
	}

	if ( Input::Get_State( INPUT_FUNCTION_TOGGLE_MOVIE_CAPTURE ) ) {
#ifdef WWDEBUG
		WW3D::Toggle_Movie_Capture();
#endif
	}

#if 0
	// Single Step code
	if (WWDEBUG_TRIGGER(0x53 /*S*/)) {
		while (!WWDEBUG_TRIGGER(0x20/*SPACE*/))		Input::Update();
		while (WWDEBUG_TRIGGER(0x20/*SPACE*/))			Input::Update();
	}
#endif
}

void	DebugManager::Load_Registry_Settings( const char * sub_key )
{
	RegistryClass registry( sub_key );
	if ( registry.Is_Valid() ) {
		EnabledDevices = registry.Get_Int( "EnabledDevices",	EnabledDevices );
		EnabledTypes	= registry.Get_Int( "EnabledTypes",	EnabledTypes );
		EnabledOptions	= registry.Get_Int( "EnabledOptions", EnabledOptions );
		EnableFileLogging	= registry.Get_Bool( "EnableFileLogging",	EnableFileLogging );
		EnableDiagLogging	= registry.Get_Bool( "EnableDiagLogging",	EnableDiagLogging );
		LoadDebugScripts = registry.Get_Bool( "LoadDebugScripts",	LoadDebugScripts );
		AllowCinematicKeys = registry.Get_Bool( "AllowCinematicKeys",	AllowCinematicKeys );
	}

#ifdef LOG_MEMORY
		Debug_Say(( "*** Memory Logging Enabled ***\n" ));
#endif

}

void	DebugManager::Save_Registry_Settings( const char * sub_key )
{
	RegistryClass registry( sub_key );
	if ( registry.Is_Valid() ) {
		registry.Set_Int( "EnabledDevices",			EnabledDevices );
		registry.Set_Int( "EnabledTypes",		EnabledTypes );
		registry.Set_Int( "EnabledOptions",	EnabledOptions );
		registry.Set_Bool( "EnableFileLogging",	EnableFileLogging );
		registry.Set_Bool( "EnableDiagLogging",	EnableDiagLogging );
		registry.Set_Bool( "LoadDebugScripts",	LoadDebugScripts );
		registry.Set_Bool( "AllowCinematicKeys",	AllowCinematicKeys );
	}
}

/*
**
*/
void	DebugManager::Display( char const *buffer )
{
	CriticalSectionClass::LockClass lock(CriticalSection);

	if ( EnabledDevices & DEBUG_DEVICE_SCREEN ) {
		Display_Text( buffer );
	}

	if ( EnabledDevices & DEBUG_DEVICE_MONO ) {
		ScrollingScreen.Printf( buffer );
	}

#ifdef WWDEBUG
   if ( EnabledDevices & DEBUG_DEVICE_DBWIN32 ) {
		WWDebug_DBWin32_Message_Handler( buffer );
	}
#endif // WWDEBUG

	if ( EnabledDevices & DEBUG_DEVICE_LOG ) {
      Write_To_File(buffer);
	}

	if ( EnabledDevices & DEBUG_DEVICE_WINDOWS ) {
		OutputDebugString( buffer );		// puts it in the MSVC debug window
	}
}

//
//
//
void	DebugManager::Display_Script( char const *text, ... )
{
	if ( !(EnabledTypes & DEBUG_TYPE_SCRIPT) )	return;
	va_list	va;
	char		buffer[256];
	va_start(va, text);
	vsprintf(buffer, text, va);
	buffer[sizeof(buffer)-1] = '\0';
	char		buffer2[256];
	sprintf( buffer2, "SCRIPT:%s", buffer );
	Display( buffer2 );
	va_end(va);
}

/*
**
*/
void DebugManager::Display_Network_Admin(char const *text, ...)
{
   if (!(EnabledTypes & DEBUG_TYPE_NETWORK_ADMIN))	{
      return;
   }

	va_list va;
	char buffer[1024];
	va_start(va, text);
	vsprintf(buffer, text, va);
	buffer[sizeof(buffer)-1] = '\0';
	char buffer2[1024];
	sprintf(buffer2, "NET ADMIN:%s\n", buffer);
	Display(buffer2);
	va_end(va);
}

/*
**
*/
void DebugManager::Display_Network_Basic(char const *text, ...)
{
   if (!(EnabledTypes & DEBUG_TYPE_NETWORK_BASIC))	{
      return;
   }

	va_list va;
	char buffer[1024];
	va_start(va, text);
	vsprintf(buffer, text, va);
	buffer[sizeof(buffer)-1] = '\0';
	char buffer2[1024];
	sprintf(buffer2, "NET BASIC:%s\n", buffer);
	Display(buffer2);
	va_end(va);
}

/*
**
*/
void DebugManager::Display_Network_Prolific(char const *text, ...)
{
   if (!(EnabledTypes & DEBUG_TYPE_NETWORK_PROLIFIC))	{
      return;
   }

	va_list va;
	char buffer[1024];
	va_start(va, text);
	vsprintf(buffer, text, va);
	buffer[sizeof(buffer)-1] = '\0';
	char buffer2[1024];
	sprintf(buffer2, "NET PROLIFIC:%s\n", buffer);
	Display(buffer2);
	va_end(va);
}

/*
**
*/
void	DebugManager::Measure_Frame_Textures( void )
{
//	WW3D::Flush_Texture_Cache();
}

/*
**
*/
void wwdebug_message_handler(DebugType type, const char * message)
{
	/*
	** Hand the message off to the scrolling debug screen
	*/
	if ( !DebugManager::Is_Type_Enabled( (DebugManager::DebugType)(1<<type ) ) )	return;

	if ( type == WWDEBUG_TYPE_ERROR ) {
		DebugManager::Display( "ERROR:" );
	}

	if ( type == WWDEBUG_TYPE_WARNING ) {
		DebugManager::Display( "WARNING:" );
	}

	DebugManager::Display( message );
}

void wwdebug_assert_handler(const char * message)
{
	/*
	** Hand the message off to the scrolling debug screen
	*/
	DebugManager::Display( message );
}

bool wwdebug_trigger_handler(int trigger_num)
{
#ifdef WWDEBUG
	switch( trigger_num ) {
		case ' ':	return 	Input::Get_State( INPUT_FUNCTION_DEBUG_SINGLE_STEP_STEP );
		case 'S':	return 	Input::Get_State( INPUT_FUNCTION_DEBUG_SINGLE_STEP );

		case WWDEBUG_TRIGGER_GENERIC0:				return Input::Get_State( INPUT_FUNCTION_DEBUG_GENERIC0 );
		case WWDEBUG_TRIGGER_GENERIC1:				return Input::Get_State( INPUT_FUNCTION_DEBUG_GENERIC1 );
		case WWPHYS_TRIGGER_COLLISION_DEBUGGING:	return DebugManager::Option_Is_Enabled(DebugManager::DEBUG_COLLISION_MESSAGES); //Input::Get_State( INPUT_FUNCTION_DEBUG_COLLISION_MESSAGES );
		case WWPHYS_TRIGGER_COLLISION_DISPLAY:		return DebugManager::Option_Is_Enabled(DebugManager::DEBUG_COLLISION_DISPLAY); //Input::Get_State( INPUT_FUNCTION_DEBUG_SHOW_COLLISIONS );
		case WWPHYS_TRIGGER_INVERT_VIS:				return DebugManager::Option_Is_Enabled(DebugManager::DEBUG_INVERT_VIS); //Input::Get_State( INPUT_FUNCTION_DEBUG_VIS_INVERT );
		case WWPHYS_TRIGGER_DISABLE_VIS:				return DebugManager::Option_Is_Enabled(DebugManager::DEBUG_DISABLE_VIS); //Input::Get_State( INPUT_FUNCTION_DEBUG_VIS_DISABLE );
		case WW3D_TRIGGER_PROCESS_STATS:				return DebugManager::Option_Is_Enabled(DebugManager::DEBUG_PROCESS_STATS); //Input::Get_State( INPUT_FUNCTION_DEBUG_RENDER_STATS );
		case WW3D_TRIGGER_RENDER_STATS:				return false; //Input::Get_State( INPUT_FUNCTION_DEBUG_RENDER_STATS );
		case WW3D_TRIGGER_SURFACE_CACHE_STATS:		return DebugManager::Option_Is_Enabled(DebugManager::DEBUG_SURFACE_CACHE); //Input::Get_State( INPUT_FUNCTION_DEBUG_SURFACE_CACHE );
		default:												Debug_Say(( "Unhandled Trigger %d %c\n", trigger_num, trigger_num));
	}
#endif
	return false;
}

void wwdebug_profile_start_handler( const char * title )
{
	// Perhaps switch to the ProfileManager calls...
}

void wwdebug_profile_stop_handler( const char * title )
{
}

/*
**
*/
void	DebugManager::Display_Text( const char * string, const Vector4 & color )
{
	if (DisplayHandler != NULL) {
		DisplayHandler->Display_Text( string, color );
	}
}

void	DebugManager::Display_Text( const char * string, const Vector3 & color )
{
	if (DisplayHandler != NULL) {
		DisplayHandler->Display_Text( string, Vector4(color[0],color[1],color[2],1.0f) );
	}
}


void	DebugManager::Display_Text( const WideStringClass & string, const Vector4 & color )
{
	if (DisplayHandler != NULL) {
		DisplayHandler->Display_Text( string, color );
	}
}

void	DebugManager::Display_Text( const WideStringClass & string, const Vector3 & color )
{
	if (DisplayHandler != NULL) {
		DisplayHandler->Display_Text( string, Vector4(color[0],color[1],color[2],1.0f) );
	}
}

//---------------------------------------------------------------------------
void DebugManager::Init_Logfile(void)
{
	if (IsSlave) {
		sprintf(LogfileNameBuffer, "%s%s", DefaultRegistryModifier, DEFAULT_LOGFILE_NAME);
		LOGFILE = LogfileNameBuffer;
	}

	//
   // Destroy contents
   //
   FILE * file = fopen(LOGFILE, "wt");
	if ( file ) {
		fclose(file);
	}
}

//---------------------------------------------------------------------------
void DebugManager::Write_To_File(LPCSTR str)
{
	//
   // I open/close for each write so as to maximize integrity of this file.
   //

   FILE * file = fopen(LOGFILE, "at");
   if (file != NULL) {
	   fwrite(str, 1, strlen(str), file);
	   fclose(file);
   }
}

/*****************************************************************************************************
**
** WWMEMLOG support - replacement new and delete operators.  See the wwmemlog modules in WWDEBUG.LIB
** for more info!
**
*****************************************************************************************************/
/*
** Only install the custom new and delete handlers if WWDEBUG is enabled (debug and profile builds)
** AND _CRTDBG_MAP_ALLOC is not defined (this causes link errors).  If you are using _CRTDBG_MAP_ALLOC,
** the memory log stuff cannot be used...
*/
//#ifdef LOG_MEMORY
//#ifdef WWDEBUG
#ifndef _CRTDBG_MAP_ALLOC
#ifndef PARAM_EDITING_ON
#ifndef STEVES_NEW_CATCHER

extern "C" {
void *gsimalloc(size_t size)
{
	WWMEMLOG(MEM_BINK);
	return(WWMemoryLogClass::Allocate_Memory(size));
}
void gsifree(void *ptr)
{
	WWMEMLOG(MEM_BINK);
	WWMemoryLogClass::Release_Memory(ptr);
}
}


void * ::operator new (size_t size)
{
	void* memory=NULL;
#ifdef LOG_MEMORY
	#ifdef WWDEBUG
		memory=WWMemoryLogClass::Allocate_Memory(size);
	#else
		memory=FastAllocatorGeneral::Get_Allocator()->Alloc(size);
	#endif
#else
	memory=FastAllocatorGeneral::Get_Allocator()->Alloc(size);
#endif
	return memory;
}

void ::operator delete (void *ptr)
{
#ifdef LOG_MEMORY
	#ifdef WWDEBUG
		WWMemoryLogClass::Release_Memory(ptr);
	#else
		FastAllocatorGeneral::Get_Allocator()->Free(ptr);
	#endif
#else
	FastAllocatorGeneral::Get_Allocator()->Free(ptr);
#endif
}

#endif //STEVES_NEW_CATCHER
#endif //PARAM_EDITING_ON
#endif //!_CRTDBG_MAP_ALLOC
//#endif //WWDEBUG
//#endif //LOG_MEMORY






/*
**
**
** Added 'new' operator. Walks the stack to help track memory leaks.
**
**
**
**
**
**
**
*/



#include <imagehlp.h>


#define WALK_FRAMES 8

struct NewCallerStruct {
	unsigned long Addresses[WALK_FRAMES];
	char AddressName[768];
	int AddressLine;
};


unsigned long ReturnAddresses[20];
int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *);
bool Lookup_Symbol(void *code_ptr, char *symbol, int &displacement);
void *NewMutex = NULL;

#ifdef _DEBUG
#ifdef STEVES_NEW_CATCHER

extern _CRTIMP void * __cdecl operator new(unsigned int, int, const char *, int);

/*
** List of addresses to 'watch'. You can stuff them in here and compile or manually poke them in with the debugger at run time.
*/
#define NUM_WATCH_ADDRESSES 8
unsigned long WatchAddresses[NUM_WATCH_ADDRESSES] = {
	0x00000000,
	0x00000000,
	0x00000000,
	0x00000000,
	0x00000000,
	0x00000000,
	0x00000000,
	0x00000000
};

/*
** This assumes no more than 100000 places in the code that call 'new'
*/
NewCallerStruct NewAddressList[16384];

/***********************************************************************************************
 * operator new -- New operator overload to catch memory leaks.                                *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    size of block to allocate                                                         *
 *                                                                                             *
 * OUTPUT:   ptr to allocated memory                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   6/12/2001 4:31PM ST : Created                                                             *
 *=============================================================================================*/
void* __cdecl operator new(unsigned int s)
{
	static char _localstr[32];
	static unsigned long _return_addr;
	static int _address_strings = 0;
	static int _index = -1;
	static int _i, _j;
	static char _temp_str[512];
	static char _compose_str[512];
	static int _displacement;
	static unsigned long _temp_addr;
	static char _spaces[33] = {"                                "};
	static int _wa;
	//static CriticalSectionClass *_new_mutex = NULL;

	/*
	** Length of all locals.
	*/
	static int _locals_size = 0;

	/*
	** Wait for exclusive access.
	*/
	//if (_new_mutex == NULL) {
	//	void *newmem = ::operator new(s, 1, __FILE__, __LINE__);
	//	_new_mutex = new(newmem) CriticalSectionClass;
	//
	//}
	//CriticalSectionClass::LockClass mutex(*_new_mutex);
	if (NewMutex == NULL) {
		NewMutex = CreateMutex(NULL, false, NULL);
		assert(NewMutex != NULL);
	}
	if (NewMutex) {
		WaitForSingleObject(NewMutex, INFINITE);
	}


	/*
	** Extract the return address. This is the default worst case amount of info we can get about the
	** stack.
	*/
	__asm
	{
		mov		eax,_locals_size
		mov		eax,[ebp+eax+4]
		mov		[_return_addr],eax
	}

	/*
	** Try using imagehlp.dll to walk the stack and get real return addresses for multiple calls.
	*/
	memset(ReturnAddresses, 0, WALK_FRAMES * sizeof(ReturnAddresses[0]));
	int num_frames = 0;

	/*
	** If this is an address we are looking for then do the stack walk.
	*/
#ifdef ONLY_WATCH_ADDRESSES
	for (_wa = 0 ; _wa<NUM_WATCH_ADDRESSES ; _wa++) {
		if (_return_addr == WatchAddresses[_wa]) {
			num_frames = Stack_Walk(ReturnAddresses, WALK_FRAMES, NULL);
			break;
		}
	}
#else	//ONLY_WATCH_ADDRESSES
	num_frames = Stack_Walk(ReturnAddresses, WALK_FRAMES, NULL);
#endif //ONLY_WATCH_ADDRESSES

	if (num_frames == 0) {
		ReturnAddresses[0] = _return_addr;
	}

	/*
	** Find or create the address string.
	*/
	_index = -1;
	for (_i=0 ; _i<min(_address_strings, ARRAY_SIZE(NewAddressList)) ; _i++) {
		if (memcmp(NewAddressList[_i].Addresses, ReturnAddresses, WALK_FRAMES * sizeof(ReturnAddresses[0])) == 0) {
		//if (NewAddressList[_i].Address == _return_addr) {
			_index = _i;
			break;
		}
	}

	if (_index == -1) {
		_index = _address_strings++;
	}

	if (_index >= ARRAY_SIZE(NewAddressList)) {
		void *newmem = ::operator new(s, 1, __FILE__, _address_strings);
		if (NewMutex) {
			ReleaseMutex(NewMutex);
		}
		return (newmem);
	}

	/*
	** If we got no good stack info and just have the return address then print that into this
	** entry.
	*/
	memcpy(NewAddressList[_index].Addresses, ReturnAddresses, WALK_FRAMES * sizeof(ReturnAddresses[0]));
	if (num_frames == 0) {
		//NewAddressList[_index].Address = _return_addr;
		NewAddressList[_index].AddressLine = ReturnAddresses[0];
		sprintf(NewAddressList[_index].AddressName, "Addr: %08X ", ReturnAddresses[0]);
	} else {

		/*
		** Otherwise try and get symbol info for each return address.
		*/
		char *ptr = NewAddressList[_index].AddressName;

		bool ok = true;
		for (_i=0 ; _i<WALK_FRAMES ; _i++) {
			if (!ok) {
				break;
			}
			_temp_addr = NewAddressList[_index].Addresses[_i];
			_displacement = 0;
			_temp_str[0] = 0;
			ok = Lookup_Symbol((void*)_temp_addr, _temp_str, _displacement);
			if (_i == 0) {
				sprintf(ptr, "\nAddr: %08X (%s + %d)\n", NewAddressList[_index].Addresses[_i], _temp_str, _displacement);
			} else {
				strcpy(_compose_str, &_spaces[(sizeof(_spaces) -2) -_i]);
				//_compose_str[0] = 0;
				//for (_j=0 ; _j<_i ; _j++) {
				//	strcat(_compose_str, " ");
				//}
				sprintf(_compose_str + strlen(_compose_str), "Called from: %08X (%s + %d)\n", NewAddressList[_index].Addresses[_i], _temp_str, _displacement);
				if (strlen(ptr) + strlen(_compose_str) >= sizeof(NewAddressList[_index].AddressName)) {
					break;
				}
				strcat(ptr, _compose_str);
			}
		}
	}

	assert(strlen(NewAddressList[_index].AddressName) < sizeof(NewAddressList[_index].AddressName));

	/*
	** Call the CRT new to actually allocate the memory. Pass in the string we composed.
	*/
	void *newmem = ::operator new(s, 1, NewAddressList[_index].AddressName, NewAddressList[_index].AddressLine);
	if (NewMutex) {
		ReleaseMutex(NewMutex);
	}
	return(newmem);
	//return (::operator new(s, 1, __FILE__, __LINE__));
}


#endif	//STEVES_NEW_CATCHER
#endif	//_DEBUG


#if (0)
/*
** Definitions to allow run-time linking to the Imagehlp.dll functions.
**
*/
typedef BOOL  (WINAPI *SymCleanupType) (HANDLE hProcess);
typedef BOOL  (WINAPI *SymGetSymFromAddrType) (HANDLE hProcess, DWORD Address, LPDWORD Displacement, PIMAGEHLP_SYMBOL Symbol);
typedef BOOL  (WINAPI *SymInitializeType) (HANDLE hProcess, LPSTR UserSearchPath, BOOL fInvadeProcess);
typedef BOOL  (WINAPI *SymLoadModuleType) (HANDLE hProcess, HANDLE hFile, LPSTR ImageName, LPSTR ModuleName, DWORD BaseOfDll, DWORD SizeOfDll);
typedef DWORD (WINAPI *SymSetOptionsType) (DWORD SymOptions);
typedef BOOL  (WINAPI *SymUnloadModuleType) (HANDLE hProcess, DWORD BaseOfDll);
typedef BOOL  (WINAPI *StackWalkType) (DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME StackFrame, LPVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress);
typedef LPVOID (WINAPI *SymFunctionTableAccessType) (HANDLE hProcess, DWORD AddrBase);
typedef DWORD (WINAPI *SymGetModuleBaseType) (HANDLE hProcess, DWORD dwAddr);


SymCleanupType						_SymCleanup = NULL;
SymGetSymFromAddrType			_SymGetSymFromAddr = NULL;
SymInitializeType					_SymInitialize = NULL;
SymLoadModuleType					_SymLoadModule = NULL;
SymSetOptionsType					_SymSetOptions = NULL;
SymUnloadModuleType				_SymUnloadModule = NULL;
StackWalkType						_StackWalk = NULL;
SymFunctionTableAccessType		_SymFunctionTableAccess = NULL;
SymGetModuleBaseType				_SymGetModuleBase = NULL;

static char const *ImagehelpFunctionNames[] = {
	"SymCleanup",
	"SymGetSymFromAddr",
	"SymInitialize",
	"SymLoadModule",
	"SymSetOptions",
	"SymUnloadModule",
	"StackWalk",
	"SymFunctionTableAccess",
	"SymGetModuleBaseType",
	NULL
};


bool SymbolsAvailable = false;
HINSTANCE ImageHelp = (HINSTANCE) -1;



/***********************************************************************************************
 * Load_Image_Helper -- Load imagehlp.dll and retrieve the programs symbols                    *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   6/12/2001 4:27PM ST : Created                                                             *
 *=============================================================================================*/
void Load_Image_Helper(void)
{
	/*
	** If this is the first time through then fix up the imagehelp function pointers since imagehlp.dll
	** can't be statically linked.
	*/
	if (ImageHelp == (HINSTANCE)-1) {
		ImageHelp = LoadLibrary("IMAGEHLP.DLL");

		if (ImageHelp != NULL) {
			char const *function_name = NULL;
			unsigned long *fptr = (unsigned long *) &_SymCleanup;
			int count = 0;

			do {
				function_name = ImagehelpFunctionNames[count];
				if (function_name) {
					*fptr = (unsigned long) GetProcAddress(ImageHelp, function_name);
					fptr++;
					count++;
				}
			}
			while (function_name);
		}

		/*
		** Retrieve the programs symbols if they are available. This can be a .pdb or a .dbg file.
		*/
		if (_SymSetOptions != NULL) {
			_SymSetOptions(SYMOPT_DEFERRED_LOADS);
		}

		int symload = 0;

		if (_SymInitialize != NULL && _SymInitialize(GetCurrentProcess(), NULL, FALSE)) {

			if (_SymSetOptions != NULL) {
				_SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME);
			}

			char exe_name[_MAX_PATH];
			GetModuleFileName(NULL, exe_name, sizeof(exe_name));

			if (_SymLoadModule != NULL) {
				symload = _SymLoadModule(GetCurrentProcess(), NULL, exe_name, NULL, 0, 0);
			}

			if (symload) {
				SymbolsAvailable = true;
			} else {
				//assert (_SymLoadModule != NULL);
				//DebugString ("SymLoad failed for module %s with code %d - %s\n", szModuleName, GetLastError(), Last_Error_Text());
			}
		}
	}
}







/***********************************************************************************************
 * Lookup_Symbol -- Get the symbol for a given code address                                    *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Address of code to get symbol for                                                 *
 *           Ptr to buffer to return symbol in                                                 *
 *           Reference to int to return displacement                                           *
 *                                                                                             *
 * OUTPUT:   True if symbol found                                                              *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   6/12/2001 4:47PM ST : Created                                                             *
 *=============================================================================================*/
bool Lookup_Symbol(void *code_ptr, char *symbol, int &displacement)
{
	/*
	** Locals.
	*/
	char symbol_struct_buf[1024];
	IMAGEHLP_SYMBOL *symbol_struct_ptr = (IMAGEHLP_SYMBOL *)symbol_struct_buf;

	/*
	** Set default values in case of early exit.
	*/
	displacement = 0;
	*symbol = '\0';

	/*
	** Make sure symbols are available.
	*/
	if (!SymbolsAvailable || _SymGetSymFromAddr == NULL) {
		return(false);
	}

	/*
	** If it's a bad code pointer then there is no point in trying to match it with a symbol.
	*/
	if (IsBadCodePtr((FARPROC)code_ptr)) {
		strcpy(symbol, "Bad code pointer");
		return(false);
	}

	/*
	** Set up the parameters for the call to SymGetSymFromAddr
	*/
	memset (symbol_struct_ptr, 0, sizeof (symbol_struct_buf));
	symbol_struct_ptr->SizeOfStruct = sizeof (symbol_struct_buf);
	symbol_struct_ptr->MaxNameLength = sizeof(symbol_struct_buf)-sizeof (IMAGEHLP_SYMBOL);
	symbol_struct_ptr->Size = 0;
	symbol_struct_ptr->Address = (unsigned long)code_ptr;

	/*
	** See if we have the symbol for that address.
	*/
	if (_SymGetSymFromAddr(GetCurrentProcess(), (unsigned long)code_ptr, (unsigned long *)&displacement, symbol_struct_ptr)) {

		/*
		** Copy it back into the buffer provided.
		*/
		strcpy(symbol, symbol_struct_ptr->Name);
		return(true);
	}
	return(false);
}




/***********************************************************************************************
 * Stack_Walk -- Walk the stack and get the last n return addresses                            *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Ptr to return address list                                                        *
 *           Number of return addresses to fetch                                               *
 *           Ptr to optional context. NULL means use current                                   *
 *                                                                                             *
 * OUTPUT:   Number of return addresses found                                                  *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   6/12/2001 11:57AM ST : Created                                                            *
 *=============================================================================================*/
int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *context)
{
	static HINSTANCE _imagehelp = (HINSTANCE) -1;

	/*
	** If this is the first time through then fix up the imagehelp function pointers since imagehlp.dll
	** can't be statically linked.
	*/
	if (ImageHelp == (HINSTANCE)-1) {
		Load_Image_Helper();
	}

	/*
	** If there is no debug support .dll available then we can't walk the stack.
	*/
	if (ImageHelp == NULL) {
		return(0);
	}

	/*
	** Set up the stack frame structure for the start point of the stack walk (i.e. here).
	*/
	STACKFRAME stack_frame;
	memset(&stack_frame, 0, sizeof(stack_frame));

	unsigned long reg_eip, reg_ebp, reg_esp;

	__asm {
here:
		lea	eax,here
		mov	reg_eip,eax
		mov	reg_ebp,ebp
		mov	reg_esp,esp
	}

	stack_frame.AddrPC.Mode = AddrModeFlat;
	stack_frame.AddrPC.Offset = reg_eip;
	stack_frame.AddrStack.Mode = AddrModeFlat;
	stack_frame.AddrStack.Offset = reg_esp;
	stack_frame.AddrFrame.Mode = AddrModeFlat;
	stack_frame.AddrFrame.Offset = reg_ebp;

	/*
	** Use the context struct if it was provided.
	*/
	if (context) {
		stack_frame.AddrPC.Offset = context->Eip;
		stack_frame.AddrStack.Offset = context->Esp;
		stack_frame.AddrFrame.Offset = context->Ebp;
	}

	int pointer_index = 0;

	/*
	** Walk the stack by the requested number of return address iterations.
	*/
	for (int i = 0; i < num_addresses + 1; i++) {
		if (_StackWalk(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(), &stack_frame, NULL, NULL, _SymFunctionTableAccess, _SymGetModuleBase, NULL)) {

			/*
			** First result will always be the return address we were called from.
			*/
			if (i==0 && context == NULL) {
				continue;
			}
			unsigned long return_address = stack_frame.AddrReturn.Offset;
			return_addresses[pointer_index++] = return_address;
		} else {
			break;
		}
	}

	return(pointer_index);
}

#endif	//(0)