 /*
  * UAE - The Un*x Amiga Emulator
  *
  * Debugger
  *
  * (c) 1995 Bernd Schmidt
  *
  */

#include "sysconfig.h"
#include "sysdeps.h"

#include <ctype.h>

#include "options.h"
#include "uae.h"
#include "memory.h"
#include "custom.h"
#include "readcpu.h"
#include "newcpu.h"
#include "debug.h"
#include "cia.h"

static int debugger_active = 0;
static uaecptr skipaddr;
static int do_skip;
static int wait_interrupt;
int debugging = 0;
int debug_interrupt_happened;

void activate_debugger (void)
{
    do_skip = 0;
    if (debugger_active)
	return;
    debugger_active = 1;
    regs.spcflags |= SPCFLAG_BRK;
    debugging = 1;
}

int firsthist = 0;
int lasthist = 0;
#ifdef NEED_TO_DEBUG_BADLY
struct regstruct history[MAX_HIST];
union flagu historyf[MAX_HIST];
#else
uaecptr history[MAX_HIST];
#endif


static void ignore_ws (char **c)
{
    while (**c && isspace(**c)) (*c)++;
}


static uae_u32 readany (char **c)
{
  uae_u32 val = 0;
  char nc;
  int numbase = 10;    /* Default numeric base is 10 */

  ignore_ws (c);
  
  /* Check if the value is given in hexadecimal ('$' or "0x") */
  if (**c=='$' || strncmp("0x",*c,2)==0 ) {
    numbase = 16;
    (*c) += 1 + (strncmp("0x",*c,2)==0 ? 1 : 0);
  }

  while (isxdigit(nc = **c)) {
    (*c)++;
    val *= numbase;
    nc = toupper(nc);
    if (isdigit(nc)) {
      val += nc - '0';
    } else if (numbase == 16) {
      val += nc - 'A' + 10;
    }
  }
  return val;
}

static uae_u32 readhex (char **c)
{
  uae_u32 val = 0;
  char nc;
  
  ignore_ws (c);
  /* Check if there is '$' or "0x" prefix and skip them ... */
  if( **c=='$' || strncmp("0x",*c,2)==0 ) {
    (*c) += 1 + (strncmp("0x",*c,2)==0 ? 1 : 0);
  }

  while (isxdigit(nc = **c)) {
    (*c)++;
    val *= 16;
    nc = toupper(nc);
    if (isdigit(nc)) {
      val += nc - '0';
    } else {
      val += nc - 'A' + 10;
    }
  }
  return val;
}

static char next_char( char **c)
{
    ignore_ws (c);
    return *(*c)++;
}

static int more_params (char **c)
{
    ignore_ws (c);
    return (**c) != 0;
}

static void dumpmem (uaecptr addr, uaecptr *nxmem, int lines)
{
    char c;
    uaecptr tmpaddr;
    broken_in = 0;
    for (;lines-- && !broken_in;) {
	int i;
	printf ("%08lx ", addr);
	tmpaddr = addr;
	for (i = 0; i < 8; i++) {
	    printf ("%04x ", get_word(addr)); addr += 2;
	}
	printf ("|");
	for (; tmpaddr < addr; tmpaddr++) {
	    c = get_byte(tmpaddr);
	    if (c < 0x20 || c > 0x7e)
  	        c = '.';
	    printf ("%c", c);
	}
	printf ("|\n");
    }
    *nxmem = addr;
}

static uae_u32 uade_debug_search (char *name) {
  uae_u8 *p = get_real_address(0);
  uae_u32 baseptr = 0;
  uae_u32 ptr;
  uae_u32 infoptr;
  uae_u32 infoname;
  uae_s32 infoval;
  int infolen, i;

  for (ptr = 0; ptr < 0x10000; ptr += 2) {
    if (!strcmp((char *) chipmemory + ptr, "uade debug info")) {
      baseptr = ptr;
      break;
    }
  }
  if (!baseptr) {
    fprintf (stderr, "uade debug info not found\n");
    return 0;
  }

  infoptr = baseptr;

  i = 0;

  while (1) {
    if (!p[infoptr])
      break;
    infoname = infoptr;
    infolen = strlen((char *) p + infoptr) + 1;
    if (infolen %2 == 1)
      infolen++;
    infoptr += infolen;
    infoval = get_long(infoptr) + baseptr;
    infoptr += 4;
    if (i > 0) {
      if (!name) {
	printf("%.8x: %s\n", infoval, p + infoname);
      } else {
	if (strlen(name) > 0) {
	  if (!strncmp ((char *) p + infoname, name, strlen(name))) {
	    return infoval;
	  }
	}
      }
    }
    i++;
  }
  return 0;
}

static void foundmod (uae_u32 ptr, char *type)
{
    char name[21];
    uae_u8 *ptr2 = chipmemory + ptr;
    int i,length;

    printf ("Found possible %s module at 0x%lx.\n", type, ptr);
    memcpy (name, ptr2, 20);
    name[20] = '\0';

    /* Browse playlist */
    length = 0;
    for (i = 0x3b8; i < 0x438; i++)
	if (ptr2[i] > length)
	    length = ptr2[i];

    length = (length+1)*1024 + 0x43c;

    /* Add sample lengths */
    ptr2 += 0x2A;
    for (i = 0; i < 31; i++, ptr2 += 30)
	length += 2*((ptr2[0]<<8)+ptr2[1]);
    
    printf ("Name \"%s\", Length 0x%lx bytes.\n", name, length);
}

static void modulesearch (void)
{
    uae_u8 *p = get_real_address (0);
    uae_u32 ptr;

    for (ptr = 0; ptr < allocated_chipmem - 40; ptr += 2, p += 2) {
	/* Check for Mahoney & Kaktus */
	/* Anyone got the format of old 15 Sample (SoundTracker)modules? */
	if (ptr >= 0x438 && p[0] == 'M' && p[1] == '.' && p[2] == 'K' && p[3] == '.')
	    foundmod (ptr - 0x438, "ProTracker (31 samples)");

	if (ptr >= 0x438 && p[0] == 'F' && p[1] == 'L' && p[2] == 'T' && p[3] == '4')
	    foundmod (ptr - 0x438, "Startrekker");

	if (strncmp ((char *)p, "SMOD", 4) == 0) {
	    printf ("Found possible FutureComposer 1.3 module at 0x%lx, length unknown.\n", ptr);
	}
	if (strncmp ((char *)p, "FC14", 4) == 0) {
	    printf ("Found possible FutureComposer 1.4 module at 0x%lx, length unknown.\n", ptr);
	}
	if (p[0] == 0x48 && p[1] == 0xe7 && p[4] == 0x61 && p[5] == 0
	    && p[8] == 0x4c && p[9] == 0xdf && p[12] == 0x4e && p[13] == 0x75
	    && p[14] == 0x48 && p[15] == 0xe7 && p[18] == 0x61 && p[19] == 0
	    && p[22] == 0x4c && p[23] == 0xdf && p[26] == 0x4e && p[27] == 0x75) {
	    printf ("Found possible Whittaker module at 0x%lx, length unknown.\n", ptr);
	}
	if (p[4] == 0x41 && p[5] == 0xFA) {
	    int i;

	    for (i = 0; i < 0x240; i += 2)
		if (p[i] == 0xE7 && p[i + 1] == 0x42 && p[i + 2] == 0x41 && p[i + 3] == 0xFA)
		    break;
	    if (i < 0x240) {
		uae_u8 *p2 = p + i + 4;
		for (i = 0; i < 0x30; i += 2)
		    if (p2[i] == 0xD1 && p2[i + 1] == 0xFA) {
			printf ("Found possible MarkII module at %lx, length unknown.\n", ptr);
		    }
	    }
		
	}
    }
}

/* cheat-search by Holger Jakob */
static void cheatsearch (char **c)
{
    uae_u8 *p = get_real_address (0);
    static uae_u32 *vlist = NULL;
    uae_u32 ptr;
    uae_u32 val = 0;
    uae_u32 type = 0; /* not yet */
    uae_u32 count = 0;
    uae_u32 fcount = 0;
    uae_u32 full = 0;
    char nc;

    val = readany(c);

    if (vlist == NULL) {
	vlist = malloc (256*4);
	if (vlist != 0) {
	    for (count = 0; count<255; count++)
		vlist[count] = 0;
	    count = 0;
	    for (ptr = 0; ptr < allocated_chipmem - 40; ptr += 2, p += 2) {
		if (ptr >= 0x438 && p[3] == (val & 0xff)
		    && p[2] == (val >> 8 & 0xff)
		    && p[1] == (val >> 16 & 0xff)
		    && p[0] == (val >> 24 & 0xff))
		{
		    if (count < 255) {
			vlist[count++]=ptr;
			printf ("%08x: %x%x%x%x\n",ptr,p[0],p[1],p[2],p[3]);
		    } else
			full = 1;
		}
	    }
	    printf ("Found %d possible addresses with %d\n",count,val);
	    printf ("Now continue with 'g' and use 'C' with a different value\n");
	}
    } else {
	for (count = 0; count<255; count++) {
	    if (p[vlist[count]+3] == (val & 0xff)
		&& p[vlist[count]+2] == (val>>8 & 0xff) 
		&& p[vlist[count]+1] == (val>>16 & 0xff)
		&& p[vlist[count]] == (val>>24 & 0xff))
	    {
		fcount++;
		printf ("%08x: %x%x%x%x\n", vlist[count], p[vlist[count]],
			p[vlist[count]+1], p[vlist[count]+2], p[vlist[count]+3]);
	    }
	}
	printf ("%d hits of %d found\n",fcount,val);
	free (vlist);
	vlist = NULL;
    }
}

static void writeintomem (char **c)
{
    uae_u8 *p = get_real_address (0);
    uae_u32 addr = 0;
    uae_u32 val = 0;
    char nc;
    int numbase=10;     /* Numeric base for value is 10 by default ! */
    int opsize=4;       /* write operation is 32 bits by default ! */

    if(strncasecmp(".B",*c,2)==0) {
      opsize=1;
      *c+=2;
    }
    if(strncasecmp(".W",*c,2)==0) {
      opsize=2;
      *c+=2;
    }
    if(strncasecmp(".L",*c,2)==0) {
      opsize=4;
      *c+=2;
    }

    addr = readhex(c);
    val = readany(c);

    /* Check for value overflow */
    if(opsize==1 && val>255) {
      printf("Value too big for byte operation !\n");
      return;
    }
    if(opsize==2 && val>65535) {
      printf("Value too big for word operation !\n");
      return;
    }

    if (addr < allocated_chipmem) {
      switch(opsize) {
      case 1:
	p[addr] = val & 0xff;
	break;
      case 2:
	p[addr] = val>>8 & 0xff;
	p[addr+1] = val & 0xff;
	break;
      default:
	p[addr] = val>>24 & 0xff;
	p[addr+1] = val>>16 & 0xff;
	p[addr+2] = val>>8 & 0xff;
	p[addr+3] = val & 0xff;
	break;
      }
      printf("Wrote (");
      if(opsize==1) printf("byte");
      else if (opsize==2) printf("word");
      else printf("long");
      printf(") %d == 0x%x at %08x\n",val,val,addr);
    } else
      printf("Invalid address %08x\n",addr);
}

static void set_break(uae_u32 addr) {
  skipaddr = addr;
  do_skip = 1;
  regs.spcflags |= SPCFLAG_BRK;
}

static void history_log(void) {
#ifdef NEED_TO_DEBUG_BADLY
  history[lasthist] = regs;
  historyf[lasthist] = regflags;
#else
  history[lasthist] = m68k_getpc();
#endif
  if (++lasthist == MAX_HIST) lasthist = 0;
  if (lasthist == firsthist) {
    if (++firsthist == MAX_HIST) firsthist = 0;
  }
}

static void history_flush(void) {
  lasthist = firsthist = 0;
}

void debug (void) {
  char input[80];
  uaecptr nextpc,nxdis,nxmem;
  
  bogusframe = 1;

  history_log();

  if (do_skip && (m68k_getpc() != skipaddr) &&
      (wait_interrupt == 0 || debug_interrupt_happened == 0)) {
    regs.spcflags |= SPCFLAG_BRK;
    return;
  }

  do_skip = 0;
  debug_interrupt_happened = 0;
  wait_interrupt = 0;

  m68k_dumpstate (&nextpc);
  nxdis = nextpc; nxmem = 0;
  
  for (;;) {
    char cmd, *inptr;
    
    printf (">");
    fflush (stdout);
    if (fgets (input, 80, stdin) == 0) {
      quit_program = 1;
      return;
    }
    inptr = input;
    cmd = next_char (&inptr);
    switch (cmd) {
    case 'c': dumpcia (); dumpcustom (); break;
    case 'r': m68k_dumpstate (&nextpc); break;
    case 'M': modulesearch (); break;
    case 'C': cheatsearch (&inptr); break; 
    case 'W': writeintomem (&inptr); break;
      
    case 'R':
      {
	uae_u8 *memp;
	uae_u32 dst, len = 0;
	char *name;
	FILE *fp;
	int havesize=0;
	struct stat *st;
	uae_u32 fsize=0;
	
	if (!more_params (&inptr))
	  goto R_argh;
	
	name = inptr;
	while (*inptr != '\0' && !isspace (*inptr))
	  inptr++;
	if (!isspace (*inptr))
	  goto R_argh;
	
	*inptr = '\0';
	inptr++;
	if (!more_params (&inptr))
	  goto R_argh;
	dst = readhex (&inptr);
	
	if (more_params (&inptr)) {
	  len = readany (&inptr);
	  havesize=1;
	}
	
	if ((st=malloc(sizeof(struct stat)))) if(!stat(name,st)) fsize=st->st_size;
	if (!fsize)
	  return;
	
	if (!havesize)
	  len = fsize;
	
	if (! valid_address (dst, len)) {
	  printf ("Invalid memory block (0x %.8x - %.8x\n",dst,dst+len-1);
	  return;
	}
	memp = get_real_address (dst);
	
	fp = fopen (name, "r");
	if (fp == NULL) {
	  printf ("Couldn't open file\n");
	  break;
	}
	
	if (fread (memp, 1, len, fp) == len) {
	  printf("Read %d == 0x%x bytes from file into memory (0x %.8x - %.8x)\n",len,len,dst,dst+len-1); 
	} else {
	  printf ("Error reading file !\n");
	}
      R_fclose:
	fclose (fp);
	break;
	
      R_argh:
	printf ("R command needs more arguments!\n");
	break;
      }
      
    case 'S':
      {
	uae_u8 *memp;
	uae_u32 src, len;
	char *name;
	FILE *fp;
	
	if (!more_params (&inptr))
	  goto S_argh;
	
	name = inptr;
	while (*inptr != '\0' && !isspace (*inptr))
	  inptr++;
	if (!isspace (*inptr))
	  goto S_argh;
	
	*inptr = '\0';
	inptr++;
	if (!more_params (&inptr))
	  goto S_argh;
	src = readhex (&inptr);
	if (!more_params (&inptr))
	  goto S_argh;
	len = readany (&inptr);
	if (! valid_address (src, len)) {
	  printf ("Invalid memory block\n");
	  break;
	}
	memp = get_real_address (src);
	fp = fopen (name, "w");
	if (fp == NULL) {
	  printf ("Couldn't open file\n");
	  break;
	}
	if (fwrite (memp, 1, len, fp) == len) {
	  printf("Wrote %d == 0x%x bytes into file from memory (0x %.8x - %.8x)\n",len,len,src,src+len-1); 
	} else {
	  printf ("Error writing file\n");
	}
	fclose (fp);
	break;
	
      S_argh:
	printf ("S command needs more arguments!\n");
	break;
      }

    case 'd':
      {
	uae_u32 daddr;
	int count;

	if (more_params(&inptr)) {
	  if (!strncmp(inptr, "pc", 2)) {
	    daddr = m68k_getpc();
	    inptr += 2;
	  } else if (!strncmp(inptr, "rd", 2)) {
	    daddr = m68k_dreg(regs, inptr[2] - '0');
	    inptr += 3;
	  } else if (!strncmp(inptr, "ra", 2)) {
	    daddr = m68k_areg(regs, inptr[2] - '0');
	    inptr += 3;
	  } else {
	    daddr = readhex(&inptr);
	  }
	} else
	  daddr = nxdis;
	if (more_params(&inptr))
	  count = readany(&inptr);
	else
	  count = 10;
	m68k_disasm(daddr, &nxdis, count);
      }
      break;

    case 't':
      regs.spcflags |= SPCFLAG_BRK;
      return;

    case 'z':
      set_break(nextpc);
      return;

    case 'f':
      set_break(readhex(&inptr));
      return;

      /* Run until next interrupt */
    case 'i':
      set_break(0x13371337);
      wait_interrupt = 1;
      return;

    case 'u':
      set_break(m68k_getpc());
      return;

    case 'l':
      {
	uae_u32 traceaddr;
	if (inptr[strlen(inptr) - 1] == '\n') {
	  inptr[strlen(inptr) - 1] = 0;
	}
	if (strlen(inptr) <= 1) {
	  uade_debug_search(0);
	  break;
	}
	traceaddr = uade_debug_search (inptr + 1);
	if (!traceaddr)
	  break;
	set_break(traceaddr);
	return;
      }
      break;
      
    case 'q':
      uae_quit();
      debugger_active = 0;
      debugging = 0;
      return;
      
    case 'g':
      if (more_params (&inptr))
	m68k_setpc (readhex (&inptr));
      fill_prefetch_0 ();
      debugger_active = 0;
      debugging = 0;

      history_flush();

      return;

    case 'F':
      history_flush();
      break;
      
    case 'H':
      {
	int count;
	int temp;
#ifdef NEED_TO_DEBUG_BADLY
	struct regstruct save_regs = regs;
	union flagu save_flags = regflags;
#endif
	
	if (more_params(&inptr))
	  count = readany(&inptr);
	else
	  count = 10;
	if (count < 0)
	  break;
	temp = lasthist;
	while (count-- > 0 && temp != firsthist) {
	  if (temp == 0) temp = MAX_HIST-1; else temp--;
	}
	while (temp != lasthist) {
#ifdef NEED_TO_DEBUG_BADLY
	  regs = history[temp];
	  regflags = historyf[temp];
	  m68k_dumpstate(NULL);
#else
	  m68k_disasm(history[temp], NULL, 1);
#endif
	  if (++temp == MAX_HIST) temp = 0;
	}
#ifdef NEED_TO_DEBUG_BADLY
	regs = save_regs;
	regflags = save_flags;
#endif
      }
      break;
    case 'm':
      {
	uae_u32 maddr; int lines;
	if (more_params(&inptr))
	  maddr = readhex(&inptr);
	else
	  maddr = nxmem;
	if (more_params(&inptr))
	  lines = readany(&inptr);
	else
	  lines = 16;
	dumpmem(maddr, &nxmem, lines);
      }
      break;
    case 'h':
    case '?':
      {
	printf ("          HELP for UAE Debugger\n");
	printf ("         -----------------------\n\n");
	printf (" Address values are in hexadecimal, other values are in decimal by default.\n");
	printf (" Values can be written in hexadecimal with prefixes '$' or '0x'. (eg: g $100)\n\n");
	printf ("  g: <address>          Start execution at the current address or <address>\n");
	printf ("  c:                    Dump state of the CIA and custom chips\n");
	printf ("  r:                    Dump state of the CPU\n");
	printf ("  m <address> <lines>:  Memory dump starting at <address>\n");
	printf ("  d <address> <lines>:  Disassembly starting at <address>\n");
	printf ("  t:                    Step one instruction\n");
	printf ("  z:                    Step through one instruction - useful for JSR, DBRA etc\n");
	printf ("  u:                    Run until here again (same as f current pc)\n");
	printf ("  f <address>:          Step forward until PC == <address>\n");
	printf ("  i:                    Run until next interrupt\n");
	printf ("  H <count>:            Show PC history <count> instructions\n");
	printf ("  M:                    Search for *Tracker sound modules\n");
	printf ("  C <value>:            Search for values like energy or lifes in games\n");
	printf ("  W[.bwl] <addr> <val>: Write into memory with op.size (b,w,l), default is LONG\n");
	printf ("  R <file> <addr> <n>:  Read a file into Amiga memory (<n> is optional)\n");
	printf ("  S <file> <addr> <n>:  Save a block of Amiga memory\n");
	printf ("  l [<name>]:           Trace to an uade debug point or list them\n"); 
	printf ("  h,?:                  Show this help page\n");
	printf ("  q:                    Quit the emulator. You don't want to use this command.\n\n");
      }
      break;
      
    }
  }
}
