/* 
Copyright (C) 1995-2001 Activision, Inc.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

/***********************************************************************
 * Methods for finding module functions involved during a crash using  *
 *  map files.                                                   *
 ***********************************************************************/

#ifndef NO_NETWORK
#define NO_NETWORK /* needed to shield dp stuff in dpprivy.h */
#endif
#ifndef AEHMAP_PRIVATE
#define AEHMAP_PRIVATE
#endif

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "aeh.h"

#ifdef _WIN32
#include <windows.h>
#include <imagehlp.h>
#include "tlstuff.h"
#else
#define GetTickCount() (0)
#endif

/* add a name to mapfile's function linked list;
 * sorted by address from high to low */
static void aeh_map_addFunc(aeh_map_t *aehmap, char *name, unsigned address)
{
	aeh_map_func_t *new_function;
	aeh_map_func_t *last = NULL;
	aeh_map_func_t *curr;

	if (!name) return;
	new_function = (aeh_map_func_t *)malloc(sizeof(aeh_map_func_t));
	if (!new_function) return;
	memset(new_function, 0, sizeof(aeh_map_func_t));
	new_function->start_addr = address;

	/* CRA: do some extra processing on C++ functions
	 * after this processing C++ functions are in the format Class::Function :)
	 * also do extra processing for fast call functions */
#ifdef _WIN32
	if (name[0] == '?') {
		char buff[BUFFER_SIZE];
		int len;
		UNDECORATESYMNAME pUnDecorateSymbolName = InitUnDecorateSymbolName();
		if (pUnDecorateSymbolName &&
			(len=pUnDecorateSymbolName(name, buff, BUFFER_SIZE-1, 0))) {
			buff[len] = '\0';
			new_function->name = strdup(buff);
		} else
			new_function->name = strdup(name);
	} else
#endif
		new_function->name = strdup(name);
	if (aehmap->firstfunc == NULL) { /* no entries for this mod */
		new_function->next = NULL;
		aehmap->firstfunc = new_function;
		aehmap->lastfunc = new_function;
		return;
	}
	if (address >= (aehmap->firstfunc)->start_addr) {
		new_function->next = aehmap->firstfunc;
		aehmap->firstfunc = new_function;
		return;
	}
	if (address <= (aehmap->lastfunc)->start_addr) {
		(aehmap->lastfunc)->next = new_function;
		new_function->next = NULL;
		aehmap->lastfunc = new_function;
		return;
	}
	curr = aehmap->firstfunc;
	while (curr && (address < curr->start_addr)) {
		last = curr;
		curr = curr->next;
	}
	if (last == NULL)
		return;
	new_function->next = last->next;
	last->next = new_function;
}

#define aeh_map_func_alg_MAGIC 0x7865
static void aeh_map_Unload(aeh_map_t *aehmap, int bWriteAlg);

/* Open binary .alg mapfile generated by previous aeh_map_Unload call;
 * generates a list of function names and their start addresses
 * returns aeh_RES_EMPTY if could not load file */
static int aeh_map_algRead(aeh_map_t *aehmap, char *algpath) {
	FILE *fh;
	int nread, nsize;
	unsigned short tag;
	char buffer[BUFFER_SIZE], *ptr;
	char name[BUFFER_SIZE];
	unsigned address;
	unsigned prefadr = 0;
	fh = fopen(algpath, "rb");
	if (!fh)
		return aeh_RES_EMPTY;
	if ((fread(&tag, sizeof(unsigned short), 1, fh) != 1) ||
		(tag != aeh_map_func_alg_MAGIC) ||
		(fread(&prefadr, sizeof(unsigned long), 1, fh) != 1)) {
		aehDPRINT(("aeh_map_Load: corrupted .alg file\n"));
		fclose(fh);
		remove(algpath);
		return aeh_RES_BAD;
	}
	aehmap->load_addr = prefadr;
	ptr = buffer;
	nsize = BUFFER_SIZE;
	while ((nread = fread(ptr, 1, nsize, fh)) > 0) {
		char *eobuf = ptr + nread;
		ptr = buffer;
		while (ptr + (sizeof(unsigned)+sizeof(unsigned short)) < eobuf){
			unsigned short namelen;
			address = *((unsigned*)ptr);
			namelen = *((unsigned short*)(ptr + sizeof(unsigned)));
			if (ptr+(sizeof(unsigned)+sizeof(unsigned short)+namelen) < eobuf) {
				ptr += sizeof(unsigned) + sizeof(unsigned short);
				memcpy(name, ptr, namelen);
				name[namelen] = '\0';
				ptr += namelen;
				aeh_map_addFunc(aehmap, name, address);
			} else
				break;
		}
		if (ptr < eobuf) {
			int nleft = eobuf - ptr;
			memcpy(buffer, ptr, nleft);
			ptr = buffer + nleft;
			nsize = BUFFER_SIZE - nleft;
		}
	}
	fclose (fh);
	if (*((unsigned short*)buffer) != aeh_map_func_alg_MAGIC) {
		/* .alg corrupted; delete it */
		aehDPRINT(("aeh_map_Load: corrupted .alg file\n"));
		aeh_map_Unload(aehmap, 0);
		remove(algpath);
		return aeh_RES_BAD;
	}
	aehDPRINT(("exit aeh_map_Load time %d\n", GetTickCount()));
	return aeh_RES_OK;
}

static int aeh_map_algWrite(aeh_map_t *aehmap, char *algpath) {
	FILE *fh;
	int bWriteError = 0;
	aeh_map_func_t *curr = aehmap->firstfunc;
	char buffer[BUFFER_SIZE], *ptr;
	unsigned short tag = aeh_map_func_alg_MAGIC;
	fh = fopen(algpath, "rb");
	if (fh) { /* .alg already exists */
		fclose(fh);
		return aeh_RES_ALREADY;
	}
	fh = fopen(algpath, "wb");
	if (!fh || (fwrite(&tag, sizeof(unsigned short), 1, fh) != 1) ||
		(fwrite(&(aehmap->load_addr), sizeof(unsigned long), 1, fh) != 1)) {
		aehDPRINT(("aeh_map_Unload: error writing %s\n", algpath));
		if (fh)
			fclose(fh);
		remove(algpath);
		return aeh_RES_BAD;
	}
	ptr = buffer;
	while (curr) {
		aeh_map_func_t *next = curr->next;
		unsigned short namelen = strlen(curr->name);
		if (ptr + (sizeof(unsigned) + sizeof(unsigned short) + namelen) >
			buffer + BUFFER_SIZE) {
			unsigned int len = ptr - buffer;
			if (fwrite(buffer, 1, len, fh) != len) {
				aehDPRINT(("aeh_map_Unload: error writing %s\n", algpath));
				bWriteError = 1;
				break;
			}
			ptr = buffer;
		}
		*((unsigned*)ptr) = curr->start_addr; ptr += sizeof(unsigned);
		*((unsigned short*)ptr) = namelen; ptr += sizeof(unsigned short);
		memcpy(ptr, curr->name, namelen); ptr += namelen;
		curr = next;
	}
	if (!bWriteError) {
		if (ptr > buffer) {
			unsigned int len = ptr - buffer;
			if (fwrite(buffer, 1, len, fh) != len)
				bWriteError = 1;
		}
		if (!bWriteError && (fwrite(&tag, sizeof(unsigned short), 1, fh) != 1))
			bWriteError = 1;
	}
	fclose(fh);
	if (bWriteError) {
		aehDPRINT(("aeh_map_Unload: error writing %s\n", algpath));
		remove(algpath);
		return aeh_RES_BAD;
	}
	return aeh_RES_OK;
}


/* Open a msdevstudio msvc4.2 .map file and parse it
 * generates a list of function names and their start addresses
 * returns aeh_RES_EMPTY if could not load file / file out of date */
static int aeh_map_Load(aeh_map_t *aehmap)
{
	FILE *fp;
	char buffer[BUFFER_SIZE];
	char algpath[aeh_MAX_PATH], *ptr;
	int done;
	int crap;
	char name[BUFFER_SIZE];
	unsigned address;
	unsigned prefadr = 0;
#ifdef REQUIRE_CORRECT_LOG
	int log_correct;
#endif

	if (!aehmap) return aeh_RES_BAD;

	/* check if have .alg for this mapfile; use if it exists */
	strcpy(algpath, aehmap->path);
	if ((ptr = strstr(algpath, ".map"))) {
		strcpy(ptr, ".alg");
		if (aeh_map_algRead(aehmap, algpath) == aeh_RES_OK)
			return aeh_RES_OK;
	}

	/* read .map for this mapfile */
	fp = fopen(aehmap->path, "r");
	if (!fp)
		return aeh_RES_EMPTY;

	/* search through until '  Address' found; also get preferred address */
	aehDPRINT(("call aeh_map_Load time %d\n", GetTickCount()));
	done = 0;
	while (!done) {
		fgets (buffer, BUFFER_SIZE, fp);
		if (feof (fp))
			done = 1;
		if (strncmp(buffer, " Preferred", strlen(" Preferred")-1) == 0)
			if (sscanf(buffer, " %*s %*s %*s %*s %x", &prefadr) != 1)
				aehDPRINT(("aeh_map_Load: error getting preferred address\n"));
		if (sscanf (buffer, "%s", name) == 1) {
			if (strcmp (name, "Address") == 0)
				done = 1;
		}
	}

	if (prefadr)
		done = 0;
	else
		aehDPRINT(("didn't get preferred adr\n"));
	aehmap->load_addr = prefadr;
	fgets (buffer, BUFFER_SIZE, fp);  /* ignore single blank line */

	/* get every line containing a function name and an address */
	while (!done) {
		fgets (buffer, BUFFER_SIZE, fp);

		/* line must start with ' 0nnn:' where n is a number */
		if ((buffer[0] == ' ') && (buffer[1] == '0') && (buffer[5] == ':') &&
		   (sscanf(buffer, "%x:%x %s %x", &crap, &crap, name, &address) == 4)) {
			address = (address - prefadr);
			aeh_map_addFunc(aehmap, name, address);
		}
		if (feof (fp))
			done = 1;
	}
	fclose (fp);
	aehDPRINT(("exit aeh_map_Load time %d\n", GetTickCount()));
	return aeh_RES_OK;
}

/* Free memory allocated during aeh_map_Load() and if bWriteAlg set,
 * write aehmap function info to binary .alg file */
static void aeh_map_Unload(aeh_map_t *aehmap, int bWriteAlg)
{
	aeh_map_func_t *curr = aehmap->firstfunc;
	char algpath[aeh_MAX_PATH], *ptr;
	strcpy(algpath, aehmap->path);
	if (bWriteAlg && curr && (ptr = strstr(algpath, ".map"))) {
		strcpy(ptr, ".alg");
		aeh_map_algWrite(aehmap, algpath);
	}
	while (curr) {
		aeh_map_func_t *next = curr->next;
		/* free all entries */
		if (curr->name)
			free(curr->name);
		free(curr);
		curr = next;
	}
}

/* Find map entry corresponding the module in aehmodfile */
int aeh_map_Find(aeh_mapcat_t *aehmapcat, aeh_modfile_t *aehmodfile)
{
	unsigned int i;
	if (!aehmapcat || !aehmodfile) {
		aehDPRINT(("aeh_map_Find: err: null arguments\n"));
		return -1;
	}
	for (i = 0; i < aehmapcat->nmap; i++) {
		aeh_map_t *mapbuf = (aehmapcat->map)+i;
		if (mapbuf->crc == aehmodfile->crc) {
			if (!mapbuf->firstfunc) {
				int err = aeh_map_Load(mapbuf);
				if (err != aeh_RES_OK) {
					aehDPRINT(("aeh_map_Find: aeh_map_Load return err %d\n", err));
					return -1;
				}
			}
			return i;
		}
	}
	return -1;
}

/*--------------------------------------------------------------------------
 Get information from the mapfiles in the mapfile catalog file.
 Call aeh_mapcat_Destroy() after finished using the returned value of aehmapcat.
 Input: catpath (path to mapfile catalog file)
 Output: aehmapcat
 Return: aeh_RES_NOMEM if memory couldn't be allocated
         aeh_RES_BUG if catalog file couldn't be opened
         aeh_RES_BAD if couldn't get catalog info
         aeh_RES_OK on success
--------------------------------------------------------------------------*/
int aeh_mapcat_Create(aeh_mapcat_t *aehmapcat, const char *catpath)
{
	FILE *fp;
	unsigned int iloaded;
	char buf[BUFFER_SIZE];
	char path[aeh_MAX_PATH];
	aeh_map_t mapbuf;

	if (!aehmapcat || !catpath) return aeh_RES_BAD;
	if (!(fp = fopen(catpath, "r"))) return aeh_RES_BUG;
	mapbuf.path = &path[0];
	aehmapcat->nmap = 0;
	while (fgets(buf, BUFFER_SIZE, fp)) {
		if (sscanf(buf, "%lx %[^\n\r\f]", &mapbuf.crc, mapbuf.path) == 2)
			aehmapcat->nmap++;
	}
	if (!((aehmapcat->map) = (aeh_map_t *)malloc((aehmapcat->nmap) * sizeof(aeh_map_t))))
		return aeh_RES_NOMEM;
	memset(aehmapcat->map, 0, (aehmapcat->nmap) * sizeof(aeh_map_t));

	/* load mapfile entries */
	if (fseek(fp, 0L, SEEK_SET)) {
		aehDPRINT(("aeh_mapcat_Create: can't reset file position\n"));
		return aeh_RES_BAD;
	}
	iloaded = 0;
	while (fgets(buf, BUFFER_SIZE, fp)) {
		char fullpath[aeh_MAX_PATH];
		char *ptr;
		if (iloaded >= aehmapcat->nmap) {
			aehDPRINT(("aeh_mapcat_Create: iloaded %d bigger than nmap %d\n", iloaded, aehmapcat->nmap));
			return aeh_RES_BAD;
		}
		if (sscanf(buf, "%lx %[^\n\r\f]", &mapbuf.crc, mapbuf.path) != 2) {
			aehDPRINT(("aeh_mapcat_Create: error reading mapfile entry %s\n", buf));
			continue;
		}
		strcpy(fullpath, catpath);
		if ((ptr = strrchr(fullpath, aeh_PATH_DELIMIT))) {
			ptr++;
			strcpy(ptr, mapbuf.path);
			(aehmapcat->map)[iloaded].path = strdup(fullpath);
		} else
			(aehmapcat->map)[iloaded].path = strdup(mapbuf.path);
		if ((ptr = strrchr(mapbuf.path, aeh_PATH_DELIMIT))) {
			*ptr = '\0';
			(aehmapcat->map)[iloaded].reldir = strdup(mapbuf.path);
		} else
			(aehmapcat->map)[iloaded].reldir = strdup(".");
		(aehmapcat->map)[iloaded].crc = mapbuf.crc;
		iloaded++;
	}
	fclose(fp);
	if (iloaded != aehmapcat->nmap) {
		aehDPRINT(("aeh_mapcat_Create: iloaded %d is not equal to nmap %d\n", iloaded, aehmapcat->nmap));
		aeh_mapcat_Destroy(aehmapcat);
		return aeh_RES_BAD;
	}
	return aeh_RES_OK;
}

/*--------------------------------------------------------------------------
 Free memory allocated to aeh_mapcat_t during aehmapcat_Create().
 Input: aehmapcat
--------------------------------------------------------------------------*/
void aeh_mapcat_Destroy(aeh_mapcat_t *aehmapcat)
{
	unsigned int i;
	if (aehmapcat && aehmapcat->map) {
		for (i = 0; i < aehmapcat->nmap; i++) {
			aeh_map_t *mapbuf = (aehmapcat->map)+i;
			aeh_map_Unload(mapbuf, 1);
			if (mapbuf->path)
				free(mapbuf->path);
			if (mapbuf->reldir)
				free(mapbuf->reldir);
		}
		free(aehmapcat->map);
	}
}

