/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Ben Parnell
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "common.h"
#include "..\..\types.h"
#include "..\..\debug.h"
#include "..\..\fceu.h"
#include "..\..\cheat.h"
#include "..\..\cart.h"
#include "..\..\ines.h"
#include "memview.h"
#include "debugger.h"
#include "cdlogger.h"
#include "memviewsp.h"
#include "cheat.h"
#include <assert.h>
//#include "string.h"

#define MODE_NES_MEMORY   0
#define MODE_NES_PPU      1
#define MODE_NES_FILE     2


// This defines all of our right click popup menus
struct
{
     int   minaddress;  //The minimum address where this popup will appear
     int   maxaddress;  //The maximum address where this popup will appear
	 int   editingmode; //The editing mode which this popup appears in
	 int   id;          //The menu ID for this popup
	 char  *text;    //the text for the menu item (some of these need to be dynamic)
}
popupmenu[] =
{
	{0,0x2000,0,1,"Freeze/Unfreeze This Address"},
	{0x6000,0x7FFF,0,1,"Freeze/Unfreeze This Address"},
	{0,0xFFFF,0,2,"Add Debugger Read Breakpoint"},
	{0,0x3FFF,1,2,"Add Debugger Read Breakpoint"},
	{0,0xFFFF,0,3,"Add Debugger Write Breakpoint"},
	{0,0x3FFF,1,3,"Add Debugger Write Breakpoint"},
	{0,0xFFFF,0,4,"Add Debugger Execute Breakpoint"},
	{0x8000,0xFFFF,0,5,"Go Here In Rom File"},
	{0x8000,0xFFFF,0,6,"Create Game Genie Code At This Address"},
	//{0,0xFFFFFF,2,7,"Create Game Genie Code At This Address"}
// ################################## Start of SP CODE ###########################
	{0, 0xFFFF, 0, 20, "Add / Remove bookmark"},
// ################################## End of SP CODE ###########################
} ;

#define POPUPNUM (sizeof popupmenu / sizeof popupmenu[0])

int LoadTableFile();
void UnloadTableFile();
void InputData(char *input);
int GetMemViewData(int i);
void UpdateCaption();
int UpdateCheatColorCallB(char *name, uint32 a, uint8 v, int compare,int s,int type, void *data); //mbg merge 6/29/06 - added arg
int DeleteCheatCallB(char *name, uint32 a, uint8 v, int compare,int s,int type); //mbg merge 6/29/06 - added arg
// ################################## Start of SP CODE ###########################
void FreezeRam(int address, int mode, int final);
// ################################## End of SP CODE ###########################
int GetHexScreenCoordx(int offset);
int GetHexScreenCoordy(int offset);
int GetAddyFromCoord(int x,int y);
void AutoScrollFromCoord(int x,int y);
LRESULT CALLBACK MemViewCallB(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK MemFindCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
void FindNext();
void OpenFindDialog();

HWND hMemView, hMemFind;
HDC mDC;
//int tempdummy;
//char dummystr[100];
HFONT hMemFont;
int CurOffset;
int MemFontHeight;
int MemFontWidth;
int ClientHeight;
int NoColors;
int EditingMode;
int EditingText;
int AddyWasText; //used by the GetAddyFromCoord() function.
int TableFileLoaded;

char chartable[256];

//SCROLLINFO memsi;
//HBITMAP HDataBmp;
//HGDIOBJ HDataObj;
HDC HDataDC;
int CursorX=2, CursorY=9;
int CursorStartAddy, CursorEndAddy=-1;
int CursorDragPoint;//, CursorShiftPoint = -1;
//int CursorStartNibble=1, CursorEndNibble; //1 means that only half of the byte is selected
int TempData=-1;
int DataAmount;
int MaxSize;

COLORREF *BGColorList;
COLORREF *TextColorList;
int *OldValues; //this will be used for a speed hack
int OldCurOffset;

int lbuttondown, lbuttondownx, lbuttondowny;
int mousex, mousey;

int FindAsText;
int FindDirectionUp;
char FindTextBox[60];

extern iNES_HEADER head;

//undo structure
struct UNDOSTRUCT {
	int addr;
	int size;
	unsigned char *data;
	UNDOSTRUCT *last; //mbg merge 7/18/06 removed struct qualifier
};

struct UNDOSTRUCT *undo_list=0;

void ApplyPatch(int addr,int size, uint8* data){
	UNDOSTRUCT *tmp=(UNDOSTRUCT*)malloc(sizeof(UNDOSTRUCT)); //mbg merge 7/18/06 removed struct qualifiers and added cast

	int i;

	//while(tmp != 0){tmp=tmp->next;x++;};
	//tmp = malloc(sizeof(struct UNDOSTRUCT));
	//sprintf(str,"%d",x);
	//MessageBox(hMemView,str,"info", MB_OK);
	tmp->addr = addr;
	tmp->size = size;
	tmp->data = (uint8*)malloc(sizeof(uint8)*size);
	tmp->last=undo_list;

	for(i = 0;i < size;i++){
		tmp->data[i] = GetFileData(addr+i);
		WriteFileData(addr+i,data[i]);
	}

	undo_list=tmp;

	//UpdateColorTable();
	return;
}

void UndoLastPatch(){
	struct UNDOSTRUCT *tmp=undo_list;
	int i;
	if(undo_list == 0)return;
	//while(tmp->next != 0){tmp=tmp->next;}; //traverse to the one before the last one

	for(i = 0;i < tmp->size;i++){
		WriteFileData(tmp->addr+i,tmp->data[i]);
	}
	
	undo_list=undo_list->last;
	
	ChangeMemViewFocus(2,tmp->addr, -1); //move to the focus to where we are undoing at.
	
	free(tmp->data);
	free(tmp);
	return;
}

void FlushUndoBuffer(){
	struct UNDOSTRUCT *tmp;
	while(undo_list!= 0){
		tmp=undo_list;
		undo_list=undo_list->last;
		free(tmp->data);
		free(tmp);
	}
	UpdateColorTable();
	return;
}


int GetFileData(int offset){
	if(offset < 16) return *((unsigned char *)&head+offset);
	if(offset < 16+PRGsize[0])return PRGptr[0][offset-16];
	if(offset < 16+PRGsize[0]+CHRsize[0])return CHRptr[0][offset-16-PRGsize[0]];
	return -1;
}

int WriteFileData(int addr,int data){
	if (addr < 16)MessageBox(hMemView,"Sorry", "Go bug bbit if you really want to edit the header.", MB_OK);
	if((addr >= 16) && (addr < PRGsize[0]+16)) *(uint8 *)(GetNesPRGPointer(addr-16)) = data;
	if((addr >= PRGsize[0]+16) && (addr < CHRsize[0]+PRGsize[0]+16)) *(uint8 *)(GetNesCHRPointer(addr-16-PRGsize[0])) = data;

	return 0;
}

int GetRomFileSize(){ //todo: fix or remove this?
	return 0;
}

//should return -1, otherwise returns the line number it had the error on
int LoadTableFile(){
	char str[50];
	FILE *FP;
	int i, line, charcode1, charcode2;

	const char filter[]="Table Files (*.TBL)\0*.tbl\0";
	char nameo[2048]; //todo: possibly no need for this? can lpstrfilter point to loadedcdfile instead?
	OPENFILENAME ofn;
	StopSound();
	memset(&ofn,0,sizeof(ofn));
	ofn.lStructSize=sizeof(ofn);
	ofn.hInstance=fceu_hInstance;
	ofn.lpstrTitle="Load Table File...";
	ofn.lpstrFilter=filter;
	nameo[0]=0;
	ofn.lpstrFile=nameo;
	ofn.nMaxFile=256;
	ofn.Flags=OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;
	ofn.hwndOwner = hCDLogger;
	if(!GetOpenFileName(&ofn))return -1;

	for(i = 0;i < 256;i++){
		chartable[i] = 0;
	}

	FP = fopen(nameo,"r");
	line = 0;
	while((fgets(str, 45, FP)) != NULL){/* get one line from the file */
		line++;

		if(strlen(str) < 3)continue;

		charcode1 = charcode2 = -1;

		if((str[0] >= 'a') && (str[0] <= 'f')) charcode1 = str[0]-('a'-0xA);
		if((str[0] >= 'A') && (str[0] <= 'F')) charcode1 = str[0]-('A'-0xA);
		if((str[0] >= '0') && (str[0] <= '9')) charcode1 = str[0]-'0';

		if((str[1] >= 'a') && (str[1] <= 'f')) charcode2 = str[1]-('a'-0xA);
		if((str[1] >= 'A') && (str[1] <= 'F')) charcode2 = str[1]-('A'-0xA);
		if((str[1] >= '0') && (str[1] <= '9')) charcode2 = str[1]-'0';
		
		if(charcode1 == -1){
			UnloadTableFile();
			fclose(FP);
			return line; //we have an error getting the first input
		}

		if(charcode2 != -1) charcode1 = (charcode1<<4)|charcode2;

		for(i = 0;i < (int)strlen(str);i++)if(str[i] == '=')break;

		if(i == strlen(str)){
			UnloadTableFile();
			fclose(FP);
			return line; //error no '=' found
		}

		i++;
			//ORing i with 32 just converts it to lowercase if it isn't
		if(((str[i]|32) == 'r') && ((str[i+1]|32) == 'e') && ((str[i+2]|32) == 't'))
			charcode2 = 0x0D;
		else charcode2 = str[i];

		chartable[charcode1] = charcode2;
	}
	TableFileLoaded = 1;
	fclose(FP);
	return -1;

}

void UnloadTableFile(){
	int i, j;
	for(i = 0;i < 256;i++){
		j = i;
		if(j < 0x20)j = 0x2E;
		if(j > 0x7e)j = 0x2E;
		chartable[i] = j;
	}
	TableFileLoaded = 0;
	return;
}
void UpdateMemoryView(int draw_all){
	int i, j;
	//LPVOID lpMsgBuf;
	//int curlength;
	char str[100];
	char str2[100];
	if (!hMemView) return;

/*
	if(draw_all){
		for(i = CurOffset;i < CurOffset+DataAmount;i+=16){
			MoveToEx(HDataDC,0,MemFontHeight*((i-CurOffset)/16),NULL);
			sprintf(str,"%06X: ",i);
			for(j = 0;j < 16;j++){
				sprintf(str2,"%02X ",GetMem(i+j));
				strcat(str,str2);
			}
			strcat(str," : ");
			k = strlen(str);
			for(j = 0;j < 16;j++){
				str[k+j] = GetMem(i+j);
				if(str[k+j] < 0x20)str[k+j] = 0x2E;
				if(str[k+j] > 0x7e)str[k+j] = 0x2E;
			}
			str[k+16] = 0;
			TextOut(HDataDC,0,0,str,strlen(str));
		}
	} else {*/
	for(i = CurOffset;i < CurOffset+DataAmount;i+=16){
		if((OldCurOffset != CurOffset) || draw_all){
			MoveToEx(HDataDC,0,MemFontHeight*((i-CurOffset)/16),NULL);
			SetTextColor(HDataDC,RGB(0,0,0));
			SetBkColor(HDataDC,RGB(255,255,255));
			sprintf(str,"%06X: ",i);
			TextOut(HDataDC,0,0,str,strlen(str));
		}
		for(j = 0;j < 16;j++){
			if((CursorEndAddy == -1) && (CursorStartAddy == i+j)){ //print up single highlighted text
				sprintf(str,"%02X",GetMemViewData(CursorStartAddy));
				OldValues[i+j-CurOffset] = -1; //set it to redraw this one next time
				MoveToEx(HDataDC,8*MemFontWidth+(j*3*MemFontWidth),MemFontHeight*((i-CurOffset)/16),NULL);
				if(TempData != -1){
					sprintf(str2,"%X",TempData);
					SetBkColor(HDataDC,RGB(255,255,255));
					SetTextColor(HDataDC,RGB(255,0,0));
					TextOut(HDataDC,0,0,str2,1);
					SetTextColor(HDataDC,RGB(255,255,255));
					SetBkColor(HDataDC,RGB(0,0,0));
					TextOut(HDataDC,0,0,&str[1],1);
				} else {
					SetTextColor(HDataDC,RGB(255,255,255));
					SetBkColor(HDataDC,RGB(0,0,0));
					TextOut(HDataDC,0,0,str,1);
					SetTextColor(HDataDC,RGB(0,0,0));
					SetBkColor(HDataDC,RGB(255,255,255));
					TextOut(HDataDC,0,0,&str[1],1);
				}
				TextOut(HDataDC,0,0," ",1);

				SetTextColor(HDataDC,RGB(255,255,255));
				SetBkColor(HDataDC,RGB(0,0,0));
				MoveToEx(HDataDC,(59+j)*MemFontWidth,MemFontHeight*((i-CurOffset)/16),NULL); //todo: try moving this above the for loop
				str[0] = chartable[GetMemViewData(i+j)];
				if(str[0] < 0x20)str[0] = 0x2E;
				if(str[0] > 0x7e)str[0] = 0x2E;
				str[1] = 0;
				TextOut(HDataDC,0,0,str,1);

				continue;
			}
			if((OldValues[i+j-CurOffset] != GetMemViewData(i+j)) || draw_all){
				MoveToEx(HDataDC,8*MemFontWidth+(j*3*MemFontWidth),MemFontHeight*((i-CurOffset)/16),NULL);
				SetTextColor(HDataDC,TextColorList[i+j-CurOffset]);//(8+j*3)*MemFontWidth
				SetBkColor(HDataDC,BGColorList[i+j-CurOffset]);
				sprintf(str,"%02X ",GetMemViewData(i+j));
				TextOut(HDataDC,0,0,str,strlen(str));

				MoveToEx(HDataDC,(59+j)*MemFontWidth,MemFontHeight*((i-CurOffset)/16),NULL); //todo: try moving this above the for loop
				str[0] = chartable[GetMemViewData(i+j)];
				if(str[0] < 0x20)str[0] = 0x2E;
				if(str[0] > 0x7e)str[0] = 0x2E;
				str[1] = 0;
				TextOut(HDataDC,0,0,str,1);
				if(CursorStartAddy != i+j)OldValues[i+j-CurOffset] = GetMemViewData(i+j);
			}
		}
		if(draw_all){
			MoveToEx(HDataDC,56*MemFontWidth,MemFontHeight*((i-CurOffset)/16),NULL);
			SetTextColor(HDataDC,RGB(0,0,0));
			SetBkColor(HDataDC,RGB(255,255,255));
			TextOut(HDataDC,0,0," : ",3);
		}/*
		for(j = 0;j < 16;j++){
			if((OldValues[i+j-CurOffset] != GetMem(i+j)) || draw_all){
				MoveToEx(HDataDC,(59+j)*MemFontWidth,MemFontHeight*((i-CurOffset)/16),NULL); //todo: try moving this above the for loop
				SetTextColor(HDataDC,TextColorList[i+j-CurOffset]);
				SetBkColor(HDataDC,BGColorList[i+j-CurOffset]);
				str[0] = GetMem(i+j);
				if(str[0] < 0x20)str[0] = 0x2E;
				if(str[0] > 0x7e)str[0] = 0x2E;
				str[1] = 0;
				TextOut(HDataDC,0,0,str,1);
				if(CursorStartAddy != i+j)OldValues[i+j-CurOffset] = GetMem(i+j);
			}
		}*/
	}
//	}

	SetTextColor(HDataDC,RGB(0,0,0));
	SetBkColor(HDataDC,RGB(255,255,255));

	MoveToEx(HDataDC,0,0,NULL);	
	OldCurOffset = CurOffset;
	return;
}

void UpdateCaption(){
	char str[100];
	char EditString[3][20] = {"RAM","PPU Memory","ROM"};

	if(CursorEndAddy == -1){
		sprintf(str,"Hex Editor - Editing %s Offset 0x%06x",EditString[EditingMode],CursorStartAddy);
	} else {
		sprintf(str,"Hex Editor - Editing %s Offset 0x%06x - 0x%06x, 0x%x bytes selected ",
			EditString[EditingMode],CursorStartAddy,CursorEndAddy,CursorEndAddy-CursorStartAddy+1);
	}
	SetWindowText(hMemView,str);
	return;
}

int GetMemViewData(int i){
	if(EditingMode == 0)return GetMem(i);
	if(EditingMode == 1){
		i &= 0x3FFF;
		if(i < 0x2000)return VPage[(i)>>10][(i)];
		if(i < 0x3F00)return vnapage[(i>>10)&0x3][i&0x3FF];
		return PALRAM[i&0x1F];
	}
	if(EditingMode == 2){ //todo: use getfiledata() here
		if(i < 16) return *((unsigned char *)&head+i);
		if(i < 16+PRGsize[0])return PRGptr[0][i-16];
		if(i < 16+PRGsize[0]+CHRsize[0])return CHRptr[0][i-16-PRGsize[0]];
	}
	return 0;
}

void UpdateColorTable(){
	UNDOSTRUCT *tmp; //mbg merge 7/18/06 removed struct qualifier
	int i,j;
	if(!hMemView)return;
	for(i = 0;i < DataAmount;i++){
		if((i+CurOffset >= CursorStartAddy) && (i+CurOffset <= CursorEndAddy)){
			BGColorList[i] = RGB(0,0,0);
			TextColorList[i] = RGB(255,255,255);
			continue;
		}

		BGColorList[i] = RGB(255,255,255);
		TextColorList[i] = RGB(0,0,0);
	}

	//mbg merge 6/29/06 - added argument
	if(EditingMode == 0)FCEUI_ListCheats(UpdateCheatColorCallB,0);
	
// ################################## Start of SP CODE ###########################

	for (j=0;j<nextBookmark;j++)
	{
		if((hexBookmarks[j].address >= CurOffset) && (hexBookmarks[j].address < CurOffset+DataAmount))
			TextColorList[hexBookmarks[j].address - CurOffset] = RGB(0,0xCC,0);
	}

// ################################## End of SP CODE ###########################

	if(EditingMode == 2){
		if(cdloggerdata) {
			for(i = 0;i < DataAmount;i++){
				if((CurOffset+i >= 16) && (CurOffset+i < 16+PRGsize[0])) {
					if((cdloggerdata[i+CurOffset-16]&3) == 3)TextColorList[i]=RGB(0,192,0);
					if((cdloggerdata[i+CurOffset-16]&3) == 1)TextColorList[i]=RGB(192,192,0);
					if((cdloggerdata[i+CurOffset-16]&3) == 2)TextColorList[i]=RGB(0,0,192);
				}
			}
		}

		tmp=undo_list;
		while(tmp!= 0){
			//if((tmp->addr < CurOffset+DataAmount) && (tmp->addr+tmp->size > CurOffset))
				for(i = tmp->addr;i < tmp->addr+tmp->size;i++){
					if((i > CurOffset) && (i < CurOffset+DataAmount))
						TextColorList[i-CurOffset] = RGB(255,0,0);
				}
			tmp=tmp->last;
		}
	}

	UpdateMemoryView(1); //anytime the colors change, the memory viewer needs to be completely redrawn
}

//mbg merge 6/29/06 - added argument
int UpdateCheatColorCallB(char *name, uint32 a, uint8 v, int compare,int s,int type, void *data) {

	if((a >= CurOffset) && (a < CurOffset+DataAmount)){
	if(s)TextColorList[a-CurOffset] = RGB(0,0,255);
	}
	return 1;
}

int addrtodelete;    // This is a very ugly hackish method of doing this
int cheatwasdeleted; // but it works and that is all that matters here.
int DeleteCheatCallB(char *name, uint32 a, uint8 v, int compare,int s,int type, void *data){  //mbg merge 6/29/06 - added arg
	if(cheatwasdeleted == -1)return 1;
	cheatwasdeleted++;
	if(a == addrtodelete){
		FCEUI_DelCheat(cheatwasdeleted-1);
		cheatwasdeleted = -1;
		return 0;
	}
	return 1;
}

// ################################## Start of SP CODE ###########################

void dumpToFile(const char* buffer, unsigned int size)
{
	char name[257] = {0};
	
	OPENFILENAME ofn;
	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize=sizeof(ofn);
	ofn.hInstance=fceu_hInstance;
	ofn.lpstrTitle="Save to file ...";
	ofn.lpstrFilter="All files (*.*)\0*.*\0";
	ofn.lpstrFile=name;
	ofn.nMaxFile=256;
	ofn.Flags=OFN_EXPLORER|OFN_HIDEREADONLY;

	if (GetOpenFileName(&ofn))
	{
		FILE* memfile = fopen(ofn.lpstrFile, "wb");
		
		if (!memfile || fwrite(buffer, 1, size, memfile) != size)
		{
			MessageBox(0, "Saving failed", "Error", 0);
		}

		if (memfile)
			fclose(memfile);
	}
}

void FreezeRam(int address, int mode, int final){
	// mode: -1 == Unfreeze; 0 == Toggle; 1 == Freeze
// ################################## End of SP CODE ###########################
	if((address < 0x2000) || ((address >= 0x6000) && (address <= 0x7FFF))){
		addrtodelete = address;
		cheatwasdeleted = 0;
		
// ################################## Start of SP CODE ###########################
		if (mode == 0 || mode == -1)
		{
			//mbg merge 6/29/06 - added argument
			FCEUI_ListCheats(DeleteCheatCallB,0);
			if(mode == 0 && cheatwasdeleted != -1)FCEUI_AddCheat("",address,GetMem(address),-1,1);
		}
		else
		{
			//mbg merge 6/29/06 - added argument
			FCEUI_ListCheats(DeleteCheatCallB,0);
			FCEUI_AddCheat("",address,GetMem(address),-1,1);
		}
// ################################## End of SP CODE ###########################
		
		/*if (final)
		{
			if(hCheat)RedoCheatsLB(hCheat);
			UpdateColorTable();
		}*/
		//mbg merge 6/29/06 - WTF
	}
}

//input is expected to be an ASCII string
void InputData(char *input){
	//CursorEndAddy = -1;
	int addr, i, j, datasize = 0;
	unsigned char *data;
	char inputc;
	//char str[100];
	//mbg merge 7/18/06 added cast:
	data = (uint8*)malloc(strlen(input)); //it can't be larger than the input string, so use that as the size

	for(i = 0;input[i] != 0;i++){
		if(!EditingText){
			inputc = -1;
			if((input[i] >= 'a') && (input[i] <= 'f')) inputc = input[i]-('a'-0xA);
			if((input[i] >= 'A') && (input[i] <= 'F')) inputc = input[i]-('A'-0xA);
			if((input[i] >= '0') && (input[i] <= '9')) inputc = input[i]-'0';
			if(inputc == -1)continue;

			if(TempData != -1){
				data[datasize++] = inputc|(TempData<<4);
				TempData = -1;
			} else {
				TempData = inputc;
			}
		} else {
			for(j = 0;j < 256;j++)if(chartable[j] == input[i])break;
			if(j == 256)continue;
			data[datasize++] = j;
		}
	}

	if(datasize+CursorStartAddy >= MaxSize){ //too big
		datasize = MaxSize-CursorStartAddy;
		//free(data);
		//return;
	}
	
	//its possible for this loop not to get executed at all
//	for(addr = CursorStartAddy;addr < datasize+CursorStartAddy;addr++){
	//sprintf(str,"datasize = %d",datasize);
	//MessageBox(hMemView,str, "debug", MB_OK);

	for(i = 0;i < datasize;i++){
		addr = CursorStartAddy+i;

		if(EditingMode == 0)BWrite[addr](addr,data[i]);
		if(EditingMode == 1){
			addr &= 0x3FFF;
			if(addr < 0x2000)VPage[addr>>10][addr] = data[i]; //todo: detect if this is vrom and turn it red if so
			if((addr > 0x2000) && (addr < 0x3F00))vnapage[(addr>>10)&0x3][addr&0x3FF] = data[i]; //todo: this causes 0x3000-0x3f00 to mirror 0x2000-0x2f00, is this correct?
			if((addr > 0x3F00) && (addr < 0x3FFF))PALRAM[addr&0x1F] = data[i];
		}
		if(EditingMode == 2){
			ApplyPatch(addr,datasize,data);
			break;
		}
	}
	CursorStartAddy+=datasize;
	CursorEndAddy=-1;
	if(CursorStartAddy >= MaxSize)CursorStartAddy = MaxSize-1;

	free(data);
	ChangeMemViewFocus(EditingMode,CursorStartAddy,-1);
	UpdateColorTable();
	return;
}
/*
	if(!EditingText){
		if((input >= 'a') && (input <= 'f')) input-=('a'-0xA);
		if((input >= 'A') && (input <= 'F')) input-=('A'-0xA);
		if((input >= '0') && (input <= '9')) input-='0';
		if(input > 0xF)return;

		if(TempData != -1){
			addr = CursorStartAddy;
			data = input|(TempData<<4);
			if(EditingMode == 0)BWrite[addr](addr,data);
			if(EditingMode == 1){
				addr &= 0x3FFF;
				if(addr < 0x2000)VPage[addr>>10][addr] = data; //todo: detect if this is vrom and turn it red if so
				if((addr > 0x2000) && (addr < 0x3F00))vnapage[(addr>>10)&0x3][addr&0x3FF] = data; //todo: this causes 0x3000-0x3f00 to mirror 0x2000-0x2f00, is this correct?
				if((addr > 0x3F00) && (addr < 0x3FFF))PALRAM[addr&0x1F] = data;
			}
			if(EditingMode == 2)ApplyPatch(addr,1,(uint8 *)&data);
			CursorStartAddy++;
			TempData = -1;
		} else {
			TempData = input;
		}
	} else {
		for(i = 0;i < 256;i++)if(chartable[i] == input)break;
		if(i == 256)return;

		addr = CursorStartAddy;
		data = i;
		if(EditingMode == 0)BWrite[addr](addr,data);
		if(EditingMode == 2)ApplyPatch(addr,1,(uint8 *)&data);
		CursorStartAddy++;
	}
	*/


void ChangeMemViewFocus(int newEditingMode, int StartOffset,int EndOffset){
	SCROLLINFO si;
	
	if (GI->type==GIT_NSF) {
		FCEUD_PrintError("Sorry, you can't yet use the Memory Viewer with NSFs.");
		return;
	}

	if(!hMemView)DoMemView();
	if(EditingMode != newEditingMode)
		MemViewCallB(hMemView,WM_COMMAND,300+newEditingMode,0); //let the window handler change this for us

	if((EndOffset == StartOffset) || (EndOffset == -1)){
		CursorEndAddy = -1;
		CursorStartAddy = StartOffset;
	} else {
		CursorStartAddy = min(StartOffset,EndOffset);
		CursorEndAddy = max(StartOffset,EndOffset);
	}


	if(min(StartOffset,EndOffset) >= MaxSize)return; //this should never happen

	if(StartOffset < CurOffset){
		CurOffset = (StartOffset/16)*16;
	}

	if(StartOffset >= CurOffset+DataAmount){
		CurOffset = ((StartOffset/16)*16)-DataAmount+0x10;
		if(CurOffset < 0)CurOffset = 0;
	}

	SetFocus(hMemView);	
	ZeroMemory(&si, sizeof(SCROLLINFO));
	si.fMask = SIF_POS;
	si.cbSize = sizeof(SCROLLINFO);
	si.nPos = CurOffset/16;
	SetScrollInfo(hMemView,SB_VERT,&si,TRUE);
	UpdateCaption();
	UpdateColorTable();
	return;
}


int GetHexScreenCoordx(int offset){
	return (8*MemFontWidth)+((offset%16)*3*MemFontWidth); //todo: add Curoffset to this and to below function
}

int GetHexScreenCoordy(int offset){
	return (offset/16)*MemFontHeight;
}

//0000E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  : ................

//if the mouse is in the text field, this function will set AddyWasText to 1 otherwise it is 0
//if the mouse wasn't in any range, this function returns -1
int GetAddyFromCoord(int x,int y){
	if(y < 0)y = 0;
	if(x < 8*MemFontWidth)x = 8*MemFontWidth+1;

	if(y > DataAmount*MemFontHeight) return -1;

	if(x < 55*MemFontWidth){
		AddyWasText = 0;
		return ((y/MemFontHeight)*16)+((x-(8*MemFontWidth))/(3*MemFontWidth))+CurOffset;
	}

	if((x > 59*MemFontWidth) && (x < 75*MemFontWidth)){
		AddyWasText = 1;
		return ((y/MemFontHeight)*16)+((x-(59*MemFontWidth))/(MemFontWidth))+CurOffset;
	}
	
	return -1;
}

void AutoScrollFromCoord(int x,int y){
	SCROLLINFO si;
	if(y < 0){
		ZeroMemory(&si, sizeof(SCROLLINFO));
		si.fMask = SIF_ALL;
		si.cbSize = sizeof(SCROLLINFO);
		GetScrollInfo(hMemView,SB_VERT,&si);
		si.nPos += y / 16;
		if (si.nPos < si.nMin) si.nPos = si.nMin;
		if ((si.nPos+si.nPage) > si.nMax) si.nPos = si.nMax-si.nPage;
		CurOffset = si.nPos*16;
		SetScrollInfo(hMemView,SB_VERT,&si,TRUE);
		return;
	}

	if(y > ClientHeight){
		ZeroMemory(&si, sizeof(SCROLLINFO));
		si.fMask = SIF_ALL;
		si.cbSize = sizeof(SCROLLINFO);
		GetScrollInfo(hMemView,SB_VERT,&si);
		si.nPos -= (ClientHeight-y) / 16;
		if (si.nPos < si.nMin) si.nPos = si.nMin;
		if ((si.nPos+si.nPage) > si.nMax) si.nPos = si.nMax-si.nPage;
		CurOffset = si.nPos*16;
		SetScrollInfo(hMemView,SB_VERT,&si,TRUE);
		return;
	}
}

void KillMemView(){
	DeleteObject(hMemFont);
	ReleaseDC(hMemView,mDC);
	DestroyWindow(hMemView);
	UnregisterClass("MEMVIEW",fceu_hInstance);
	hMemView = 0;
	return;
}

LRESULT CALLBACK MemViewCallB(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
	HDC          hdc;
	HGLOBAL      hGlobal ;
	PTSTR        pGlobal ;
	HMENU        hMenu;
	MENUITEMINFO MenuInfo;
	POINT        point;
	PAINTSTRUCT ps ;
	TEXTMETRIC tm;
	SCROLLINFO si;
	int x, y, i, j;

	char c[2];
	char str[100];
// ################################## Start of SP CODE ###########################
			extern int debuggerWasActive;
// ################################## End of SP CODE ###########################

	switch (message) {
		case WM_ENTERMENULOOP:StopSound();return 0;
		case WM_INITMENUPOPUP:
			if(undo_list != 0)EnableMenuItem(GetMenu(hMemView),200,MF_BYCOMMAND | MF_ENABLED);
			else EnableMenuItem(GetMenu(hMemView),200,MF_BYCOMMAND | MF_GRAYED);
			
			if(TableFileLoaded)EnableMenuItem(GetMenu(hMemView),103,MF_BYCOMMAND | MF_ENABLED);
			else EnableMenuItem(GetMenu(hMemView),103,MF_BYCOMMAND | MF_GRAYED);

		return 0;

		case WM_CREATE:
// ################################## Start of SP CODE ###########################
			debuggerWasActive = 1;
// ################################## End of SP CODE ###########################
			mDC = GetDC(hwnd);
			HDataDC = mDC;//deleteme
			hMemFont = CreateFont(13,8, /*Height,Width*/
				0,0, /*escapement,orientation*/
				400,FALSE,FALSE,FALSE, /*weight, italic,, underline, strikeout*/
				ANSI_CHARSET,OUT_DEVICE_PRECIS,CLIP_MASK, /*charset, precision, clipping*/
				DEFAULT_QUALITY, DEFAULT_PITCH, /*quality, and pitch*/
				"Courier"); /*font name*/
			SelectObject (HDataDC, hMemFont);
			SetTextAlign(HDataDC,TA_UPDATECP | TA_TOP | TA_LEFT);

			GetTextMetrics (HDataDC, &tm) ;
			MemFontWidth = 8;
			MemFontHeight = 13;

			MaxSize = 0x10000;
			//Allocate Memory for color lists
			DataAmount = 0x100;
			//mbg merge 7/18/06 added casts:
			TextColorList = (COLORREF*)malloc(DataAmount*sizeof(COLORREF));
			BGColorList = (COLORREF*)malloc(DataAmount*sizeof(COLORREF));
			OldValues = (int*)malloc(DataAmount*sizeof(int));
			EditingText = EditingMode = CurOffset = 0;
			
			//set the default table
			UnloadTableFile();
			UpdateColorTable(); //draw it
			updateBookmarkMenus(GetSubMenu(GetMenu(hwnd), 3));
			return 0;
		case WM_PAINT:
			hdc = BeginPaint(hwnd, &ps);
			EndPaint(hwnd, &ps);
			UpdateMemoryView(1);
			return 0;
		case WM_VSCROLL:
			
				StopSound();
				ZeroMemory(&si, sizeof(SCROLLINFO));
				si.fMask = SIF_ALL;
				si.cbSize = sizeof(SCROLLINFO);
				GetScrollInfo(hwnd,SB_VERT,&si);
				switch(LOWORD(wParam)) {
					case SB_ENDSCROLL:
					case SB_TOP:
					case SB_BOTTOM: break;
					case SB_LINEUP: si.nPos--; break;
					case SB_LINEDOWN:si.nPos++; break;
					case SB_PAGEUP: si.nPos-=si.nPage; break;
					case SB_PAGEDOWN: si.nPos+=si.nPage; break;
					case SB_THUMBPOSITION: //break;
					case SB_THUMBTRACK: si.nPos = si.nTrackPos; break;
				}
				if (si.nPos < si.nMin) si.nPos = si.nMin;
				if ((si.nPos+(int)si.nPage) > si.nMax) si.nPos = si.nMax-si.nPage; //mbg merge 7/18/06 added cast
				CurOffset = si.nPos*16;
				SetScrollInfo(hwnd,SB_VERT,&si,TRUE);
				UpdateColorTable();
			return 0;
		case WM_CHAR:
				if(GetKeyState(VK_CONTROL) & 0x8000)return 0; //prevents input when pressing ctrl+c
				c[0] = (char)(wParam&0xFF);
				c[1] = 0;
				//sprintf(str,"c[0] = %c c[1] = %c",c[0],c[1]);
				//MessageBox(hMemView,str, "debug", MB_OK);
				InputData(c);
				UpdateColorTable();
				UpdateCaption();
			return 0;

		case WM_KEYDOWN:
				//if((wParam >= 0x30) && (wParam <= 0x39))InputData(wParam-0x30);
				//if((wParam >= 0x41) && (wParam <= 0x46))InputData(wParam-0x41+0xA);
				/*if(!((GetKeyState(VK_LSHIFT) & 0x8000) || (GetKeyState(VK_RSHIFT) & 0x8000))){
					//MessageBox(hMemView,"nobody", "mouse wheel dance!", MB_OK);
					CursorShiftPoint = -1;
				}
				if(((GetKeyState(VK_LSHIFT) & 0x8000) || (GetKeyState(VK_RSHIFT) & 0x8000)) &&
					(CursorShiftPoint == -1)){
					CursorShiftPoint = CursorStartAddy;
					//MessageBox(hMemView,"somebody", "mouse wheel dance!", MB_OK);
				}*/

				if(GetKeyState(VK_CONTROL) & 0x8000){
					
// ################################## Start of SP CODE ###########################

					if (wParam >= '0' && wParam <= '9')
					{
						int newValue = handleBookmarkMenu(wParam - '0');
						
						if (newValue != -1)
						{
							CurOffset = newValue;
							CursorEndAddy = -1;
							CursorStartAddy = hexBookmarks[wParam - '0'].address;
							UpdateColorTable();
						}
					}
					
// ################################## End of SP CODE ###########################

					switch(wParam){
						case 0x43: //Ctrl+C
							MemViewCallB(hMemView,WM_COMMAND,201,0); //recursion at work
							return 0;
						case 0x56: //Ctrl+V
							MemViewCallB(hMemView,WM_COMMAND,202,0);
							return 0;
						case 0x5a: //Ctrl+Z
							UndoLastPatch();
					}
				}

				//if(CursorShiftPoint == -1){
					if(wParam == VK_LEFT)CursorStartAddy--;
					if(wParam == VK_RIGHT)CursorStartAddy++;
					if(wParam == VK_UP)CursorStartAddy-=16;
					if(wParam == VK_DOWN)CursorStartAddy+=16;
				/*} else {
					if(wParam == VK_LEFT)CursorShiftPoint--;
					if(wParam == VK_RIGHT)CursorShiftPoint++;
					if(wParam == VK_UP)CursorShiftPoint-=16;
					if(wParam == VK_DOWN)CursorShiftPoint+=16;
					if(CursorShiftPoint < CursorStartAddy){
						if(CursorEndAddy == -1)CursorEndAddy = CursorStartAddy;
						CursorStartAddy = CursorShiftPoint;
					}
					//if(CursorShiftPoint > CursorEndAddy)CursorEndAddy = CursorShiftPoint;
				}*/

				//if(CursorStartAddy == CursorEndAddy)CursorEndAddy = -1;
				if(CursorStartAddy < 0)CursorStartAddy = 0;
				if(CursorStartAddy >= MaxSize)CursorStartAddy = MaxSize-1; //todo: fix this up when I add support for editing more stuff

				if((wParam == VK_DOWN) || (wParam == VK_UP) ||
					(wParam == VK_RIGHT) || (wParam == VK_LEFT)){
					CursorEndAddy = -1;
					TempData = -1;
					if(CursorStartAddy < CurOffset) CurOffset = (CursorStartAddy/16)*16;
					if(CursorStartAddy > CurOffset+DataAmount-0x10)CurOffset = ((CursorStartAddy-DataAmount+0x10)/16)*16;
				}
				
				if(wParam == VK_PRIOR)CurOffset-=DataAmount;
				if(wParam == VK_NEXT)CurOffset+=DataAmount;
				if(CurOffset < 0)CurOffset = 0;
				if(CurOffset >= MaxSize)CurOffset = MaxSize-1;
			/*
				if((wParam == VK_PRIOR) || (wParam == VK_NEXT)){
					ZeroMemory(&si, sizeof(SCROLLINFO));
					si.fMask = SIF_ALL;
					si.cbSize = sizeof(SCROLLINFO);
					GetScrollInfo(hwnd,SB_VERT,&si);
					if(wParam == VK_PRIOR)si.nPos-=si.nPage;
					if(wParam == VK_NEXT)si.nPos+=si.nPage;
					if (si.nPos < si.nMin) si.nPos = si.nMin;
					if ((si.nPos+si.nPage) > si.nMax) si.nPos = si.nMax-si.nPage;
					CurOffset = si.nPos*16;
				}
				*/
				
				//This updates the scroll bar to curoffset
				ZeroMemory(&si, sizeof(SCROLLINFO));
				si.fMask = SIF_POS;
				si.cbSize = sizeof(SCROLLINFO);
				si.nPos = CurOffset/16;
				SetScrollInfo(hwnd,SB_VERT,&si,TRUE);
				UpdateColorTable();
				UpdateCaption();
			return 0;
/*		case WM_KEYUP:
				if((wParam == VK_LSHIFT) || (wParam == VK_RSHIFT)){
					CursorShiftPoint = -1;
				}
				return 0;*/
		case WM_LBUTTONDOWN:
				//CursorShiftPoint = -1;
				SetCapture(hwnd);
				lbuttondown = 1;
				x = GET_X_LPARAM(lParam);
				y = GET_Y_LPARAM(lParam);
				if((i = GetAddyFromCoord(x,y)) == -1)return 0;
				EditingText = AddyWasText;
				lbuttondownx = x;
				lbuttondowny = y;
				CursorStartAddy = CursorDragPoint = i;
				CursorEndAddy = -1;
				UpdateCaption();
				UpdateColorTable();
			return 0;
		case WM_MOUSEMOVE:
				mousex = x = GET_X_LPARAM(lParam); 
				mousey = y = GET_Y_LPARAM(lParam); 
				if(lbuttondown){
					AutoScrollFromCoord(x,y);
					i = GetAddyFromCoord(x,y);
					if (i >= MaxSize)i = MaxSize-1;
					EditingText = AddyWasText;
					if(i != -1){
						CursorStartAddy = min(i,CursorDragPoint);
						CursorEndAddy = max(i,CursorDragPoint);
						if(CursorEndAddy == CursorStartAddy)CursorEndAddy = -1;
					}

					UpdateCaption();
					UpdateColorTable();
				}
				//sprintf(str,"%d %d",mousex, mousey);
				//SetWindowText(hMemView,str);
			return 0;
		case WM_LBUTTONUP:
				lbuttondown = 0;
				if(CursorEndAddy == CursorStartAddy)CursorEndAddy = -1;
				if((CursorEndAddy < CursorStartAddy) && (CursorEndAddy != -1)){ //this reverses them if they're not right
					i = CursorStartAddy;
					CursorStartAddy = CursorEndAddy;
					CursorEndAddy = i;
				}
				UpdateCaption();
				UpdateColorTable();
				ReleaseCapture();
			return 0;
		case WM_CONTEXTMENU:
				point.x = x = GET_X_LPARAM(lParam);
				point.y = y = GET_Y_LPARAM(lParam);
				ScreenToClient(hMemView,&point);
				mousex = point.x;
				mousey = point.y;
				j = GetAddyFromCoord(mousex,mousey);
				//sprintf(str,"x = %d, y = %d, j = %d",mousex,mousey,j);
				//MessageBox(hMemView,str, "mouse wheel dance!", MB_OK);
				hMenu = CreatePopupMenu();
				for(i = 0;i < POPUPNUM;i++){
					if((j >= popupmenu[i].minaddress) && (j <= popupmenu[i].maxaddress)
					&& (EditingMode == popupmenu[i].editingmode)){
						memset(&MenuInfo,0,sizeof(MENUITEMINFO));
						switch(popupmenu[i].id){ //this will set the text for the menu dynamically based on the id
// ################################## Start of SP CODE ###########################
							case 1:
							{
								HMENU sub = CreatePopupMenu();
								AppendMenu(hMenu, MF_POPUP | MF_STRING, (UINT)sub, "Freeze / Unfreeze Address");
								AppendMenu(sub, MF_STRING, 1, "Toggle state");
								AppendMenu(sub, MF_STRING, 50, "Freeze");
								AppendMenu(sub, MF_STRING, 51, "Unfreeze");
								AppendMenu(sub, MF_SEPARATOR, 52, "-");
								AppendMenu(sub, MF_STRING, 53, "Unfreeze all");
								
								continue;
							}
// ################################## End of SP CODE ###########################
							case 2 : //We want this to give the address to add the read breakpoint for
								if((j <= CursorEndAddy) && (j >= CursorStartAddy))
									sprintf(str,"Add Read Breakpoint For Address 0x%04X-0x%04X",CursorStartAddy,CursorEndAddy);
								else
									sprintf(str,"Add Read Breakpoint For Address 0x%04X",j);
								popupmenu[i].text = str;
							break;

							case 3 :
								if((j <= CursorEndAddy) && (j >= CursorStartAddy))
									sprintf(str,"Add Write Breakpoint For Address 0x%04X-0x%04X",CursorStartAddy,CursorEndAddy);
								else
									sprintf(str,"Add Write Breakpoint For Address 0x%04X",j);
								popupmenu[i].text = str;
							break;
							case 4 :
								if((j <= CursorEndAddy) && (j >= CursorStartAddy))
									sprintf(str,"Add Execute Breakpoint For Address 0x%04X-0x%04X",CursorStartAddy,CursorEndAddy);
								else
									sprintf(str,"Add Execute Breakpoint For Address 0x%04X",j);
								popupmenu[i].text = str;
							break;
						}
						MenuInfo.cbSize = sizeof(MENUITEMINFO);
						MenuInfo.fMask = MIIM_TYPE | MIIM_ID | MIIM_DATA;
						MenuInfo.fType = MF_STRING;
						MenuInfo.dwTypeData = popupmenu[i].text;
						MenuInfo.cch = strlen(popupmenu[i].text);
						MenuInfo.wID = popupmenu[i].id;
						InsertMenuItem(hMenu,i+1,1,&MenuInfo);
					}
				}
				//InsertMenu(hMenu, 1, MF_STRING, 892, "Test");
				if(i != 0)i = TrackPopupMenuEx(hMenu, TPM_RETURNCMD, x, y, hMemView, NULL);
				switch(i){
					case 1 : //1 = Freeze Ram Address
// ################################## Start of SP CODE ###########################
					{
						int n;
						for (n=CursorStartAddy;(CursorEndAddy == -1 && n == CursorStartAddy) || n<=CursorEndAddy;n++)
						{
							FreezeRam(n, 0, n == CursorEndAddy);
						}
						break;
					}
					case 50:
					{
						int n;
						for (n=CursorStartAddy;(CursorEndAddy == -1 && n == CursorStartAddy) || n<=CursorEndAddy;n++)
						{
							FreezeRam(n, 1, n == CursorEndAddy);
						}
						break;
					}
					case 51:
					{
						int n;
						for (n=CursorStartAddy;(CursorEndAddy == -1 && n == CursorStartAddy) || n<=CursorEndAddy;n++)
						{
							FreezeRam(n, -1, n == CursorEndAddy);
						}
						break;
					}
					case 53:
					{
						int n;
						for (n=0;n<0x2000;n++)
						{
							FreezeRam(n, -1, 0);
						}
						for (n=0x6000;n<0x8000;n++)
						{
							FreezeRam(n, -1, n == 0x7FFF);
						}
						break;
					}
// ################################## End of SP CODE ###########################
					break;

					case 2 : //2 = Add Read Breakpoint
						watchpoint[numWPs].flags = WP_E | WP_R;
						if(EditingMode == 1)watchpoint[numWPs].flags |= BT_P;
							if((j <= CursorEndAddy) && (j >= CursorStartAddy)){
								watchpoint[numWPs].address = CursorStartAddy;
								watchpoint[numWPs].endaddress = CursorEndAddy;
							}			
							else{
								watchpoint[numWPs].address = j;
								watchpoint[numWPs].endaddress = 0;
							}
						numWPs++;
// ################################## Start of SP CODE ###########################
						{ extern int myNumWPs;
						myNumWPs++; }
// ################################## End of SP CODE ###########################
						if(hDebug)AddBreakList();
						else DoDebug(0);
					break;

					case 3 : //3 = Add Write Breakpoint
						watchpoint[numWPs].flags = WP_E | WP_W;
						if(EditingMode == 1)watchpoint[numWPs].flags |= BT_P;
							if((j <= CursorEndAddy) && (j >= CursorStartAddy)){
								watchpoint[numWPs].address = CursorStartAddy;
								watchpoint[numWPs].endaddress = CursorEndAddy;
							}			
							else{
								watchpoint[numWPs].address = j;
								watchpoint[numWPs].endaddress = 0;
							}
						numWPs++;
// ################################## Start of SP CODE ###########################
						{ extern int myNumWPs;
						myNumWPs++; }
// ################################## End of SP CODE ###########################
						if(hDebug)AddBreakList();
						else DoDebug(0);
					break;
					case 4 : //4 = Add Execute Breakpoint
						watchpoint[numWPs].flags = WP_E | WP_X;
							if((j <= CursorEndAddy) && (j >= CursorStartAddy)){
								watchpoint[numWPs].address = CursorStartAddy;
								watchpoint[numWPs].endaddress = CursorEndAddy;
							}			
							else{
								watchpoint[numWPs].address = j;
								watchpoint[numWPs].endaddress = 0;
							}
						numWPs++;
// ################################## Start of SP CODE ###########################
						{ extern int myNumWPs;
						myNumWPs++; }
// ################################## End of SP CODE ###########################
						if(hDebug)AddBreakList();
						else DoDebug(0);
					break;
					case 5 : //5 = Go Here In Rom File
						ChangeMemViewFocus(2,GetNesFileAddress(j),-1);
					break;
					case 6 : //6 = Create GG Code
						SetGGConvFocus(j,GetMem(j));
					break;
// ################################## Start of SP CODE ###########################
					case 20:
					{
						if (toggleBookmark(hwnd, CursorStartAddy))
						{
							MessageBox(hDebug, "Can't set more than 64 breakpoints", "Error", MB_OK | MB_ICONERROR);
						}
						else
						{
							updateBookmarkMenus(GetSubMenu(GetMenu(hwnd), 3));
							UpdateColorTable();
						}
					}
					break;
// ################################## End of SP CODE ###########################
				}
//6 = Create GG Code

			return 0;
		case WM_MBUTTONDOWN:
				x = GET_X_LPARAM(lParam);
				y = GET_Y_LPARAM(lParam);
				i = GetAddyFromCoord(x,y);
				if(i == -1)return 0;
// ################################## Start of SP CODE ###########################
				FreezeRam(i, 0, 1);
// ################################## End of SP CODE ###########################
			return 0;
		case WM_MOUSEWHEEL:
				i = (short)HIWORD(wParam);///WHEEL_DELTA;
				ZeroMemory(&si, sizeof(SCROLLINFO));
				si.fMask = SIF_ALL;
				si.cbSize = sizeof(SCROLLINFO);
				GetScrollInfo(hwnd,SB_VERT,&si);
				if(i < 0)si.nPos+=si.nPage;
				if(i > 0)si.nPos-=si.nPage;
				if (si.nPos < si.nMin) si.nPos = si.nMin;
				if ((si.nPos+(int)si.nPage) > si.nMax) si.nPos = si.nMax-si.nPage; //added cast
				CurOffset = si.nPos*16;
				SetScrollInfo(hwnd,SB_VERT,&si,TRUE);
				UpdateColorTable();
			return 0;

		case WM_SIZE:
			StopSound();
			ClientHeight = HIWORD (lParam) ;
			if(DataAmount != ((ClientHeight/MemFontHeight)*16)){
				DataAmount = ((ClientHeight/MemFontHeight)*16);
				if(DataAmount+CurOffset > MaxSize)CurOffset = MaxSize-DataAmount;
				//mbg merge 7/18/06 added casts:
				TextColorList = (COLORREF*)realloc(TextColorList,DataAmount*sizeof(COLORREF));
				BGColorList = (COLORREF*)realloc(BGColorList,DataAmount*sizeof(COLORREF));
				OldValues = (int*)realloc(OldValues,(DataAmount)*sizeof(int)); 
				for(i = 0;i < DataAmount;i++)OldValues[i] = -1;
			}
			//Set vertical scroll bar range and page size
			ZeroMemory(&si, sizeof(SCROLLINFO));
			si.cbSize = sizeof (si) ;
			si.fMask  = (SIF_RANGE|SIF_PAGE) ;
			si.nMin   = 0 ;
			si.nMax   = MaxSize/16 ;
			si.nPage  = ClientHeight/MemFontHeight;
			SetScrollInfo (hwnd, SB_VERT, &si, TRUE);
			UpdateColorTable();
			return 0 ;

		case WM_COMMAND:
			StopSound();
			
// ################################## Start of SP CODE ###########################
			if (wParam >= 30 && wParam <= 39)
			{
				int newValue = handleBookmarkMenu(wParam - 30);
				
				if (newValue != -1)
				{
					CurOffset = newValue;
					CursorEndAddy = -1;
					CursorStartAddy = hexBookmarks[wParam - 30].address;
					UpdateColorTable();
				}
			}
			else if (wParam == 400)
			{
				removeAllBookmarks(GetSubMenu(GetMenu(hwnd), 3));
				UpdateColorTable();
			}
			else if (wParam == 600)
			{
				MessageBox(0, "", "", 0);
			}
// ################################## End of SP CODE ###########################
			
			switch(wParam)
			{
				case 100:
					FlushUndoBuffer();
					iNesSave();
					UpdateColorTable();
				return 0;

				case 101:
				return 0;

				case 102:
					if((i = LoadTableFile()) != -1){
						sprintf(str,"Error Loading Table File At Line %d",i);
						MessageBox(hMemView,str,"error", MB_OK);
					}
					UpdateColorTable();
				return 0;

				case 103:
					UnloadTableFile();
					UpdateColorTable();
				return 0;
				
// ################################## Start of SP CODE ###########################
				case 104:
					{
						char bar[0x800];
						unsigned int i;
						for (i=0;i<sizeof(bar);i++) bar[i] = GetMem(i);
							
						dumpToFile(bar, sizeof(bar));
						return 0;
					}
				case 105:
					{
						char bar[0x4000];
						unsigned int i;
						for (i=0;i<sizeof(bar);i++)
						{
//							bar[i] = GetPPUMem(i);
							i &= 0x3FFF;
							if(i < 0x2000) bar[i] = VPage[(i)>>10][(i)];
							else if(i < 0x3F00) bar[i] = vnapage[(i>>10)&0x3][i&0x3FF];
							else bar[i] = PALRAM[i&0x1F];
						}
						dumpToFile(bar, sizeof(bar));
						return 0;
					}
// ################################## End of SP CODE ###########################

				case 200: //undo
					UndoLastPatch();
				return 0;

				case 201: //copy
						if(CursorEndAddy == -1)i = 1;
						else i = CursorEndAddy-CursorStartAddy+1;

						hGlobal = GlobalAlloc (GHND, 
							(i*2)+1); //i*2 is two characters per byte, plus terminating null

						pGlobal = (char*)GlobalLock (hGlobal) ; //mbg merge 7/18/06 added cast
						if(!EditingText){
							for(j = 0;j < i;j++){
								str[0] = 0;
								sprintf(str,"%02X",GetMemViewData(j+CursorStartAddy));
								strcat(pGlobal,str);
							}
						} else {
							for(j = 0;j < i;j++){
								str[0] = 0;
								sprintf(str,"%c",chartable[GetMemViewData(j+CursorStartAddy)]);
								strcat(pGlobal,str);
							}
						}
						GlobalUnlock (hGlobal);
						OpenClipboard (hwnd) ;
						EmptyClipboard () ;
						SetClipboardData (CF_TEXT, hGlobal) ;
						CloseClipboard () ;
				return 0;

				case 202: //paste
							
							OpenClipboard(hwnd);
							hGlobal = GetClipboardData(CF_TEXT);
							if(hGlobal == NULL){
								CloseClipboard();
								return 0;
							}
							pGlobal = (char*)GlobalLock (hGlobal) ; //mbg merge 7/18/06 added cast
							//for(i = 0;pGlobal[i] != 0;i++){
							InputData(pGlobal);
							//}
							GlobalUnlock (hGlobal);
							CloseClipboard();
							return 0;
				return 0;

				case 203: //find
							OpenFindDialog();
				return 0;


				case 300:
				case 301:
				case 302:
					EditingMode = wParam-300;
					for(i = 0;i < 3;i++){
						if(EditingMode == i)CheckMenuItem(GetMenu(hMemView),300+i,MF_CHECKED);
						else CheckMenuItem(GetMenu(hMemView),300+i,MF_UNCHECKED);
					}
					if(EditingMode == 0)MaxSize = 0x10000;
					if(EditingMode == 1)MaxSize = 0x4000;
					if(EditingMode == 2)MaxSize = 16+CHRsize[0]+PRGsize[0]; //todo: add trainer size
					if(DataAmount+CurOffset > MaxSize)CurOffset = MaxSize-DataAmount;
					if(CursorEndAddy > MaxSize)CursorEndAddy = -1;
					if(CursorStartAddy > MaxSize)CursorStartAddy= MaxSize-1;

					//Set vertical scroll bar range and page size
					ZeroMemory(&si, sizeof(SCROLLINFO));
					si.cbSize = sizeof (si) ;
					si.fMask  = (SIF_RANGE|SIF_PAGE) ;
					si.nMin   = 0 ;
					si.nMax   = MaxSize/16 ;
					si.nPage  = ClientHeight/MemFontHeight;
					SetScrollInfo (hwnd, SB_VERT, &si, TRUE);

					for(i = 0;i < DataAmount;i++)OldValues[i] = -1;
					
					UpdateColorTable();
					return 0;
			}

		case WM_MOVE:
			StopSound();
			return 0;

		case WM_DESTROY :
			KillMemView();
			//ReleaseDC (hwnd, mDC) ;
			//DestroyWindow(hMemView);
			//UnregisterClass("MEMVIEW",fceu_hInstance);
			//hMemView = 0;
			return 0;
	 }
	return DefWindowProc (hwnd, message, wParam, lParam) ;
}



void DoMemView() {
	WNDCLASSEX     wndclass ;
	//static RECT al;

	if (!GI) {
		FCEUD_PrintError("You must have a game loaded before you can use the Memory Viewer.");
		return;
	}
	if (GI->type==GIT_NSF) {
		FCEUD_PrintError("Sorry, you can't yet use the Memory Viewer with NSFs.");
		return;
	}

	if (!hMemView){
		memset(&wndclass,0,sizeof(wndclass));
		wndclass.cbSize=sizeof(WNDCLASSEX);
		wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
		wndclass.lpfnWndProc   = MemViewCallB ;
		wndclass.cbClsExtra    = 0 ;
		wndclass.cbWndExtra    = 0 ;
		wndclass.hInstance     = fceu_hInstance;
		wndclass.hIcon         = LoadIcon(fceu_hInstance, "FCEUXD_ICON");
		wndclass.hIconSm       = LoadIcon(fceu_hInstance, "FCEUXD_ICON");
		wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
		wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH) ;
		wndclass.lpszMenuName  = "MEMVIEWMENU" ; //TODO: add a menu
		wndclass.lpszClassName = "MEMVIEW" ;

		if(!RegisterClassEx(&wndclass)) {FCEUD_PrintError("Error Registering MEMVIEW Window Class."); return;}

		hMemView = CreateWindowEx(0,"MEMVIEW","Memory Editor",
                        //WS_OVERLAPPEDWINDOW|WS_CLIPSIBLINGS,  /* Style */
						WS_SYSMENU|WS_THICKFRAME|WS_VSCROLL,
                        CW_USEDEFAULT,CW_USEDEFAULT,625,242,  /* X,Y ; Width, Height */
                        NULL,NULL,fceu_hInstance,NULL ); 
		ShowWindow (hMemView, SW_SHOW) ;
		UpdateCaption();
		//hMemView = CreateDialog(fceu_hInstance,"MEMVIEW",NULL,MemViewCallB);
	}
	if (hMemView) {
		SetWindowPos(hMemView,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
		//UpdateMemView(0);
		//MemViewDoBlit();
	}
}

BOOL CALLBACK MemFindCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {

	switch(uMsg) {
		case WM_INITDIALOG:
			if(FindDirectionUp) CheckDlgButton(hwndDlg, 1003, BST_CHECKED);
			else CheckDlgButton(hwndDlg, 1004, BST_CHECKED);

			if(FindAsText) CheckDlgButton(hwndDlg, 1002, BST_CHECKED);
			else CheckDlgButton(hwndDlg, 1001, BST_CHECKED);
			
			if(FindTextBox[0])SetDlgItemText(hwndDlg,1000,FindTextBox);

			SendDlgItemMessage(hwndDlg,1000,EM_SETLIMITTEXT,59,0);
			break;
		case WM_CREATE:

			break;
		case WM_PAINT:
			break;
		case WM_CLOSE:
		case WM_QUIT:
			GetDlgItemText(hMemFind,1000,FindTextBox,59);
			DestroyWindow(hMemFind);
			hMemFind = 0;
			break;
		case WM_MOVING:
			break;
		case WM_MOVE:
			break;
		case WM_RBUTTONDBLCLK:
		case WM_RBUTTONDOWN:
			break;
		case WM_MOUSEMOVE:
			break;

		case WM_COMMAND:
			switch(HIWORD(wParam)) {
				case BN_CLICKED:
					switch(LOWORD(wParam)) {
						case 1001 :
							FindAsText=0;
						break;
						case 1002 :
							FindAsText=1;
						break;

						case 1003 :
							FindDirectionUp = 1;
						break;
						case 1004 :
							FindDirectionUp = 0;
						break;
						case 1005 :
							FindNext();
						break;
					}
					break;
			}
			break;
		case WM_HSCROLL:
			break;
	}
	return FALSE;
}
void FindNext(){
	char str[60];
	unsigned char data[60];
	int datasize = 0, i, j, inputc = -1, found;

	if(hMemFind) GetDlgItemText(hMemFind,1000,str,59);
	else strcpy(str,FindTextBox);

	for(i = 0;str[i] != 0;i++){
		if(!FindAsText){
			if(inputc == -1){
				if((str[i] >= 'a') && (str[i] <= 'f')) inputc = str[i]-('a'-0xA);
				if((str[i] >= 'A') && (str[i] <= 'F')) inputc = str[i]-('A'-0xA);
				if((str[i] >= '0') && (str[i] <= '9')) inputc = str[i]-'0';
			} else {
				if((str[i] >= 'a') && (str[i] <= 'f')) inputc = (inputc<<4)|(str[i]-('a'-0xA));
				if((str[i] >= 'A') && (str[i] <= 'F')) inputc = (inputc<<4)|(str[i]-('A'-0xA));
				if((str[i] >= '0') && (str[i] <= '9')) inputc = (inputc<<4)|(str[i]-'0');
				
				if(((str[i] >= 'a') && (str[i] <= 'f')) ||
				((str[i] >= 'A') && (str[i] <= 'F')) ||
				((str[i] >= '0') && (str[i] <= '9'))){
					data[datasize++] = inputc;
					inputc = -1;
				}
			}
		} else {
			for(j = 0;j < 256;j++)if(chartable[j] == str[i])break;
			if(j == 256)continue;
			data[datasize++] = j;
		}
	}
	
	if(datasize < 1){
		MessageBox(hMemView,"Invalid String","Error", MB_OK);
		return;
	}
	if(!FindDirectionUp){
		for(i = CursorStartAddy+1;i+datasize < MaxSize;i++){
			found = 1;
			for(j = 0;j < datasize;j++){
				if(GetMemViewData(i+j) != data[j])found = 0;
			}
			if(found == 1){
				ChangeMemViewFocus(EditingMode,i, i+datasize-1);
				return;
			}
		}
		for(i = 0;i < CursorStartAddy;i++){
			found = 1;
			for(j = 0;j < datasize;j++){
				if(GetMemViewData(i+j) != data[j])found = 0;
			}
			if(found == 1){
				ChangeMemViewFocus(EditingMode,i, i+datasize-1);
				return;
			}
		}
	} else { //FindDirection is up
		for(i = CursorStartAddy-1;i > 0;i--){
			found = 1;
			for(j = 0;j < datasize;j++){
				if(GetMemViewData(i+j) != data[j])found = 0;
			}
			if(found == 1){
				ChangeMemViewFocus(EditingMode,i, i+datasize-1);
				return;
			}
		}
		for(i = MaxSize-datasize;i > CursorStartAddy;i--){
			found = 1;
			for(j = 0;j < datasize;j++){
				if(GetMemViewData(i+j) != data[j])found = 0;
			}
			if(found == 1){
				ChangeMemViewFocus(EditingMode,i, i+datasize-1);
				return;
			}
		}
	}


	MessageBox(hMemView,"String Not Found","Error", MB_OK);
	return;
}


void OpenFindDialog(){
	if((!hMemView) || (hMemFind))return;
	hMemFind = CreateDialog(fceu_hInstance,"MEMVIEWFIND",hMemView,MemFindCallB);
	return;
}