From d94c7aa4501d91c36427ebf9ec230ead2abedffb Mon Sep 17 00:00:00 2001 From: zeromus Date: Wed, 19 Jul 2006 05:57:00 +0000 Subject: [PATCH] began merging XD stuff there are still bugs in the emulator core :( [[Split portion of a mixed commit.]] --- cdlogger.h | 10 + conddebug.c | 412 +++++++++ conddebug.h | 62 ++ debugger.h | 141 +++ driver.h | 1 + drivers/win/asm.h | 210 +++++ drivers/win/cdlogger.c | 299 +++++++ drivers/win/cheat.c | 1080 +++++++++++++---------- drivers/win/cheat.h | 11 +- drivers/win/common.h | 1 + drivers/win/config.c | 2 +- drivers/win/dasm.h | 252 ++++++ drivers/win/debugger.c | 1432 +++++++++++++++++++++++++++++++ drivers/win/debuggersp.c | 874 +++++++++++++++++++ drivers/win/debuggersp.h | 46 + drivers/win/input.c | 1 + drivers/win/main.c | 20 +- drivers/win/memview.c | 1668 ++++++++++++++++++++++++++++++++++++ drivers/win/memviewsp.c | 235 +++++ drivers/win/memviewsp.h | 34 + drivers/win/pref.c | 279 ++++++ drivers/win/pref.h | 27 + drivers/win/res.rc | Bin 69662 -> 130594 bytes drivers/win/res/ICON_1.ico | Bin 2238 -> 2238 bytes drivers/win/res/ICON_2.ico | Bin 2238 -> 2238 bytes drivers/win/resource.h | 24 + drivers/win/sound.c | 6 +- drivers/win/video.c | 3 +- drivers/win/window.c | 23 +- fceu.c | 11 + memview.h | 14 + nsf.c | 2 +- nsf.h | 1 + tracer.h | 16 + 34 files changed, 6746 insertions(+), 451 deletions(-) create mode 100644 cdlogger.h create mode 100644 conddebug.c create mode 100644 conddebug.h create mode 100644 debugger.h create mode 100644 drivers/win/asm.h create mode 100644 drivers/win/cdlogger.c create mode 100644 drivers/win/dasm.h create mode 100644 drivers/win/debugger.c create mode 100644 drivers/win/debuggersp.c create mode 100644 drivers/win/debuggersp.h create mode 100644 drivers/win/memview.c create mode 100644 drivers/win/memviewsp.c create mode 100644 drivers/win/memviewsp.h create mode 100644 drivers/win/pref.c create mode 100644 drivers/win/pref.h create mode 100644 drivers/win/resource.h create mode 100644 memview.h create mode 100644 tracer.h diff --git a/cdlogger.h b/cdlogger.h new file mode 100644 index 00000000..f2e999bf --- /dev/null +++ b/cdlogger.h @@ -0,0 +1,10 @@ +#include + +void DoCDLogger(); +void UpdateCDLogger(); +void LogPCM(int romaddress); + +extern HWND hCDLogger; +extern volatile int codecount, datacount, undefinedcount; +extern volatile int loggingcodedata; +extern unsigned char *cdloggerdata; diff --git a/conddebug.c b/conddebug.c new file mode 100644 index 00000000..1a66e1ae --- /dev/null +++ b/conddebug.c @@ -0,0 +1,412 @@ +/* FCEUXD SP - NES/Famicom Emulator + * + * Copyright notice for this file: + * Copyright (C) 2005 Sebastian Porst + * + * 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 + */ + +/* +* The parser was heavily inspired by the following two websites: + +* http://www.cs.utsa.edu/~wagner/CS5363/rdparse.html +* http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm +* +* Grammar of the parser: +* +* P -> Connect +* Connect -> Compare {('||' | '&&') Compare} +* Compare -> Sum {('==' | '!=' | '<=' | '>=' | '<' | '>') Sum} +* Sum -> Product {('+' | '-') Product} +* Product -> Primitive {('*' | '/') Primitive} +* Primitive -> Number | Address | Register | Flag | '(' Connect ')' +* Number -> '#' [1-9A-F]* +* Address -> '$' [1-9A-F]* | '$' '[' Connect ']' +* Register -> 'A' | 'X' | 'Y' | 'R' +* Flag -> 'N' | 'C' | 'Z' | 'I' | 'B' | 'V' +*/ + +#include +#include +#include +#include +#include + +#include "conddebug.h" + +// Next non-whitespace character in string +char next; + +int ishex(char c) +{ + return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +void scan(const char** str) +{ + do + { + next = **str; + (*str)++; + } while (isspace(next)); +} + +// Frees a condition and all of it's sub conditions +void freeTree(Condition* c) +{ + if (c->lhs) freeTree(c->lhs); + if (c->rhs) freeTree(c->rhs); + + free(c); +} + +// Generic function to handle all infix operators but the last one in the precedence hierarchy. : '(' E ')' +Condition* InfixOperator(const char** str, Condition(*nextPart(const char**)), int(*operators)(const char**)) +{ + Condition* t = nextPart(str); + Condition* t1; + Condition* mid; + int op; + + while ((op = operators(str))) + { + scan(str); + + t1 = nextPart(str); + + if (t1 == 0) + { + freeTree(t); + return 0; + } + + mid = (Condition*)malloc(sizeof(Condition)); + memset(mid, 0, sizeof(Condition)); + + mid->lhs = t; + mid->rhs = t1; + mid->op = op; + + t = mid; + } + + return t; +} + +// Generic handler for two-character operators +int TwoCharOperator(const char** str, char c1, char c2, int op) +{ + if (next == c1 && **str == c2) + { + scan(str); + return op; + } + else + { + return 0; + } +} + +// Determines if a character is a flag +int isFlag(char c) +{ + return c == 'N' || c == 'I' || c == 'C' || c == 'V' || c == 'Z' || c == 'B' || c == 'U' || c == 'D'; +} + +// Determines if a character is a register +int isRegister(char c) +{ + return c == 'A' || c == 'X' || c == 'Y' || c == 'P'; +} + +// Reads a hexadecimal number from str +int getNumber(unsigned int* number, const char** str) +{ +// char buffer[5]; + + if (sscanf(*str, "%X", number) == EOF || *number > 0xFFFF) + { + return 0; + } + +// Older, inferior version which doesn't work with leading zeros +// sprintf(buffer, "%X", *number); +// *str += strlen(buffer); + while (ishex(**str)) (*str)++; + scan(str); + + return 1; +} + +Condition* Connect(const char** str); + +// Handles the following part of the grammar: '(' E ')' +Condition* Parentheses(const char** str, Condition* c, char openPar, char closePar) +{ + if (next == openPar) + { + scan(str); + + c->lhs = Connect(str); + + if (!c) return 0; + + if (next == closePar) + { + scan(str); + return c; + } + else + { + return 0; + } + } + + return 0; +} + +/* +* Check for primitives +* Flags, Registers, Numbers, Addresses and parentheses +*/ +Condition* Primitive(const char** str, Condition* c) +{ + if (isFlag(next)) /* Flags */ + { + if (c->type1 == TYPE_NO) + { + c->type1 = TYPE_FLAG; + c->value1 = next; + } + else + { + c->type2 = TYPE_FLAG; + c->value2 = next; + } + + scan(str); + + return c; + } + else if (isRegister(next)) /* Registers */ + { + if (c->type1 == TYPE_NO) + { + c->type1 = TYPE_REG; + c->value1 = next; + } + else + { + c->type2 = TYPE_REG; + c->value2 = next; + } + + scan(str); + + return c; + } + else if (next == '#') /* Numbers */ + { + unsigned int number = 0; + if (!getNumber(&number, str)) + { + return 0; + } + + if (c->type1 == TYPE_NO) + { + c->type1 = TYPE_NUM; + c->value1 = number; + } + else + { + c->type2 = TYPE_NUM; + c->value2 = number; + } + + return c; + } + else if (next == '$') /* Addresses */ + { + if ((**str >= '0' && **str <= '9') || (**str >= 'A' && **str <= 'F')) /* Constant addresses */ + { + unsigned int number = 0; + if (!getNumber(&number, str)) + { + return 0; + } + + if (c->type1 == TYPE_NO) + { + c->type1 = TYPE_ADDR; + c->value1 = number; + } + else + { + c->type2 = TYPE_ADDR; + c->value2 = number; + } + + return c; + } + else if (**str == '[') /* Dynamic addresses */ + { + scan(str); + Parentheses(str, c, '[', ']'); + + if (c->type1 == TYPE_NO) + { + c->type1 = TYPE_ADDR; + } + else + { + c->type2 = TYPE_ADDR; + } + + return c; + } + else + { + return 0; + } + } + else if (next == '(') + { + return Parentheses(str, c, '(', ')'); + } + + return 0; +} + +/* Handle * and / operators */ +Condition* Term(const char** str) +{ + Condition* t = (Condition*)malloc(sizeof(Condition)); + Condition* t1; + Condition* mid; + + memset(t, 0, sizeof(Condition)); + + if (!Primitive(str, t)) + { + freeTree(t); + return 0; + } + + while (next == '*' || next == '/') + { + int op = next == '*' ? OP_MULT : OP_DIV; + + scan(str); + + t1 = (Condition*)malloc(sizeof(Condition)); + memset(t1, 0, sizeof(Condition)); + + if (!Primitive(str, t1)) + { + freeTree(t); + freeTree(t1); + return 0; + } + + mid = (Condition*)malloc(sizeof(Condition)); + memset(mid, 0, sizeof(Condition)); + + mid->lhs = t; + mid->rhs = t1; + mid->op = op; + + t = mid; + } + + return t; +} + +/* Check for + and - operators */ +int SumOperators(const char** str) +{ + switch (next) + { + case '+': return OP_PLUS; + case '-': return OP_MINUS; + default: return OP_NO; + } +} + +/* Handle + and - operators */ +Condition* Sum(const char** str) +{ + return InfixOperator(str, Term, SumOperators); +} + +/* Check for <=, =>, ==, !=, > and < operators */ +int CompareOperators(const char** str) +{ + int val = TwoCharOperator(str, '=', '=', OP_EQ); + if (val) return val; + + val = TwoCharOperator(str, '!', '=', OP_NE); + if (val) return val; + + val = TwoCharOperator(str, '>', '=', OP_GE); + if (val) return val; + + val = TwoCharOperator(str, '<', '=', OP_LE); + if (val) return val; + + val = next == '>' ? OP_G : 0; + if (val) return val; + + val = next == '<' ? OP_L : 0; + if (val) return val; + + return OP_NO; +} + +/* Handle <=, =>, ==, !=, > and < operators */ +Condition* Compare(const char** str) +{ + return InfixOperator(str, Sum, CompareOperators); +} + +/* Check for || or && operators */ +int ConnectOperators(const char** str) +{ + int val = TwoCharOperator(str, '|', '|', OP_OR); + if(val) return val; + + val = TwoCharOperator(str, '&', '&', OP_AND); + if(val) return val; + + return OP_NO; +} + +/* Handle || and && operators */ +Condition* Connect(const char** str) +{ + return InfixOperator(str, Compare, ConnectOperators); +} + +/* Root of the parser generator */ +Condition* generateCondition(const char* str) +{ + Condition* c; + + scan(&str); + c = Connect(&str); + + if (!c || next != 0) return 0; + else return c; +} diff --git a/conddebug.h b/conddebug.h new file mode 100644 index 00000000..20ed106c --- /dev/null +++ b/conddebug.h @@ -0,0 +1,62 @@ +/* FCEUXD SP - NES/Famicom Emulator + * + * Copyright notice for this file: + * Copyright (C) 2005 Sebastian Porst + * + * 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 + */ + +#ifndef CONDDEBUG_H +#define CONDDEBUG_H + +#define TYPE_NO 0 +#define TYPE_REG 1 +#define TYPE_FLAG 2 +#define TYPE_NUM 3 +#define TYPE_ADDR 4 + +#define OP_NO 0 +#define OP_EQ 1 +#define OP_NE 2 +#define OP_GE 3 +#define OP_LE 4 +#define OP_G 5 +#define OP_L 6 +#define OP_PLUS 7 +#define OP_MINUS 8 +#define OP_MULT 9 +#define OP_DIV 10 +#define OP_OR 11 +#define OP_AND 12 + +//mbg merge 7/18/06 turned into sane c++ +struct Condition +{ + Condition* lhs; + Condition* rhs; + + unsigned int type1; + unsigned int value1; + + unsigned int op; + + unsigned int type2; + unsigned int value2; +}; + +void freeTree(Condition* c); +Condition* generateCondition(const char* str); + +#endif diff --git a/debugger.h b/debugger.h new file mode 100644 index 00000000..cfbcb0a4 --- /dev/null +++ b/debugger.h @@ -0,0 +1,141 @@ +#ifndef DEBUGGER_H +#define DEBUGGER_H + +//#define GetMem(x) (((x < 0x2000) || (x >= 0x4020))?ARead[x](x):0xFF) +#include +#include "conddebug.h" + +//watchpoint stuffs +#define WP_E 0x01 //watchpoint, enable +#define WP_W 0x02 //watchpoint, write +#define WP_R 0x04 //watchpoint, read +#define WP_X 0x08 //watchpoint, execute + +#define BT_C 0x00 //break type, cpu mem +#define BT_P 0x10 //break type, ppu mem +#define BT_S 0x20 //break type, sprite mem + +typedef struct { + uint16 address; + uint16 endaddress; + uint8 flags; +// ################################## Start of SP CODE ########################### + + Condition* cond; + char* condText; + char* desc; + +// ################################## End of SP CODE ########################### +} watchpointinfo; + +//mbg merge 7/18/06 had to make this extern +extern watchpointinfo watchpoint[65]; //64 watchpoints, + 1 reserved for step over + +extern uint8 *vnapage[4],*VPage[8]; +extern uint8 PPU[4],PALRAM[0x20],SPRAM[0x100],VRAMBuffer,PPUGenLatch,XOffset; +extern uint32 RefreshAddr; +//extern volatile int userpause; //mbg merge 7/18/06 removed for merging +extern int scanline; //current scanline! :D +extern int badopbreak; +extern HWND hDebug; + +extern int step,stepout,jsrcount; +extern int childwnd,numWPs; //mbg merge 7/18/06 had to make extern + +BOOL CenterWindow(HWND hwndDlg); +void DoPatcher(int address,HWND hParent); +void UpdatePatcher(HWND hwndDlg); +int GetNesFileAddress(int A); +int GetPRGAddress(int A); +int GetRomAddress(int A); +int GetEditHex(HWND hwndDlg, int id); +uint8 *GetNesPRGPointer(int A); +uint8 *GetNesCHRPointer(int A); +extern void AddBreakList(); + +char *BinToASM(int addr, uint8 *opcode); + +void UpdateDebugger(); +void DoDebug(uint8 halt); +void KillDebugger(); +uint8 GetMem(uint16 A); +uint8 GetPPUMem(uint8 A); + + +//the opsize table is used to quickly grab the instruction sizes (in bytes) +static const uint8 opsize[256] = { +/*0x00*/ 1,2,0,0,0,2,2,0,1,2,1,0,0,3,3,0, +/*0x10*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0, +/*0x20*/ 3,2,0,0,2,2,2,0,1,2,1,0,3,3,3,0, +/*0x30*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0, +/*0x40*/ 1,2,0,0,0,2,2,0,1,2,1,0,3,3,3,0, +/*0x50*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0, +/*0x60*/ 1,2,0,0,0,2,2,0,1,2,1,0,3,3,3,0, +/*0x70*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0, +/*0x80*/ 0,2,0,0,2,2,2,0,1,0,1,0,3,3,3,0, +/*0x90*/ 2,2,0,0,2,2,2,0,1,3,1,0,0,3,0,0, +/*0xA0*/ 2,2,2,0,2,2,2,0,1,2,1,0,3,3,3,0, +/*0xB0*/ 2,2,0,0,2,2,2,0,1,3,1,0,3,3,3,0, +/*0xC0*/ 2,2,0,0,2,2,2,0,1,2,1,0,3,3,3,0, +/*0xD0*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0, +/*0xE0*/ 2,2,0,0,2,2,2,0,1,2,1,0,3,3,3,0, +/*0xF0*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0 +}; + + +/* +the optype table is a quick way to grab the addressing mode for any 6502 opcode + + 0 = Implied\Accumulator\Immediate\Branch\NULL + 1 = (Indirect,X) + 2 = Zero Page + 3 = Absolute + 4 = (Indirect),Y + 5 = Zero Page,X + 6 = Absolute,Y + 7 = Absolute,X + 8 = Zero Page,Y +*/ +static const uint8 optype[256] = { +/*0x00*/ 0,1,0,0,0,2,2,0,0,0,0,0,0,3,3,0, +/*0x10*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0, +/*0x20*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0, +/*0x30*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0, +/*0x40*/ 0,1,0,0,0,2,2,0,0,0,0,0,0,3,3,0, +/*0x50*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0, +/*0x60*/ 0,1,0,0,0,2,2,0,0,0,0,0,3,3,3,0, +/*0x70*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0, +/*0x80*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0, +/*0x90*/ 0,4,0,0,5,5,8,0,0,6,0,0,0,7,0,0, +/*0xA0*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0, +/*0xB0*/ 0,4,0,0,5,5,8,0,0,6,0,0,7,7,6,0, +/*0xC0*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0, +/*0xD0*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0, +/*0xE0*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0, +/*0xF0*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0 +}; + + +//opbrktype is used to grab the breakpoint type that each instruction will cause. +//WP_X is not used because ALL opcodes will have the execute bit set. +static const uint8 opbrktype[256] = { + /*0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F*/ +/*0x00*/ 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, 0, 0, 0, 0, 0, WP_R, WP_R|WP_W, 0, +/*0x10*/ 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, +/*0x20*/ 0, WP_R, 0, 0, WP_R, WP_R, WP_R|WP_W, 0, 0, 0, 0, 0, WP_R, WP_R, WP_R|WP_W, 0, +/*0x30*/ 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, +/*0x40*/ 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, 0, 0, 0, 0, 0, WP_R, WP_R|WP_W, 0, +/*0x50*/ 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, +/*0x60*/ 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, 0, 0, 0, 0, WP_R, WP_R, WP_R|WP_W, 0, +/*0x70*/ 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, +/*0x80*/ 0, WP_W, 0, 0, WP_W, WP_W, WP_W, 0, 0, 0, 0, 0, WP_W, WP_W, WP_W, 0, +/*0x90*/ 0, WP_W, 0, 0, WP_W, WP_W, WP_W, 0, 0, WP_W, 0, 0, 0, WP_W, 0, 0, +/*0xA0*/ 0, WP_R, 0, 0, WP_R, WP_R, WP_R, 0, 0, 0, 0, 0, WP_R, WP_R, WP_R, 0, +/*0xB0*/ 0, WP_R, 0, 0, WP_R, WP_R, WP_R, 0, 0, WP_R, 0, 0, WP_R, WP_R, WP_R, 0, +/*0xC0*/ 0, WP_R, 0, 0, WP_R, WP_R, WP_R|WP_W, 0, 0, 0, 0, 0, WP_R, WP_R, WP_R|WP_W, 0, +/*0xD0*/ 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, +/*0xE0*/ 0, WP_R, 0, 0, WP_R, WP_R, WP_R|WP_W, 0, 0, 0, 0, 0, WP_R, WP_R, WP_R|WP_W, 0, +/*0xF0*/ 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0, 0, WP_R, 0, 0, 0, WP_R, WP_R|WP_W, 0 +}; + +#endif diff --git a/driver.h b/driver.h index 862fba09..b158a8b2 100644 --- a/driver.h +++ b/driver.h @@ -276,6 +276,7 @@ void FCEUI_FDSSelect(void); int FCEUI_DatachSet(const uint8 *rcode); int FCEUI_EmulationPaused(void); +void FCEUI_SetEmulationPaused(int val); //mbg merge 7/18/06 added void FCEUI_ToggleEmulationPause(void); void FCEUI_FrameAdvance(void); diff --git a/drivers/win/asm.h b/drivers/win/asm.h new file mode 100644 index 00000000..e279a37b --- /dev/null +++ b/drivers/win/asm.h @@ -0,0 +1,210 @@ + +if (!strlen(astr)) { + //Implied instructions + if (!strcmp(ins,"BRK")) opcode[0] = 0x00; + else if (!strcmp(ins,"PHP")) opcode[0] = 0x08; + else if (!strcmp(ins,"ASL")) opcode[0] = 0x0A; + else if (!strcmp(ins,"CLC")) opcode[0] = 0x18; + else if (!strcmp(ins,"PLP")) opcode[0] = 0x28; + else if (!strcmp(ins,"ROL")) opcode[0] = 0x2A; + else if (!strcmp(ins,"SEC")) opcode[0] = 0x38; + else if (!strcmp(ins,"RTI")) opcode[0] = 0x40; + else if (!strcmp(ins,"PHA")) opcode[0] = 0x48; + else if (!strcmp(ins,"LSR")) opcode[0] = 0x4A; + else if (!strcmp(ins,"CLI")) opcode[0] = 0x58; + else if (!strcmp(ins,"RTS")) opcode[0] = 0x60; + else if (!strcmp(ins,"PLA")) opcode[0] = 0x68; + else if (!strcmp(ins,"ROR")) opcode[0] = 0x6A; + else if (!strcmp(ins,"SEI")) opcode[0] = 0x78; + else if (!strcmp(ins,"DEY")) opcode[0] = 0x88; + else if (!strcmp(ins,"TXA")) opcode[0] = 0x8A; + else if (!strcmp(ins,"TYA")) opcode[0] = 0x98; + else if (!strcmp(ins,"TXS")) opcode[0] = 0x9A; + else if (!strcmp(ins,"TAY")) opcode[0] = 0xA8; + else if (!strcmp(ins,"TAX")) opcode[0] = 0xAA; + else if (!strcmp(ins,"CLV")) opcode[0] = 0xB8; + else if (!strcmp(ins,"TSX")) opcode[0] = 0xBA; + else if (!strcmp(ins,"INY")) opcode[0] = 0xC8; + else if (!strcmp(ins,"DEX")) opcode[0] = 0xCA; + else if (!strcmp(ins,"CLD")) opcode[0] = 0xD8; + else if (!strcmp(ins,"INX")) opcode[0] = 0xE8; + else if (!strcmp(ins,"NOP")) opcode[0] = 0xEA; + else if (!strcmp(ins,"SED")) opcode[0] = 0xF8; + else return 1; +} +else { + //Instructions with Operands + if (!strcmp(ins,"ORA")) opcode[0] = 0x01; + else if (!strcmp(ins,"ASL")) opcode[0] = 0x06; + else if (!strcmp(ins,"BPL")) opcode[0] = 0x10; + else if (!strcmp(ins,"JSR")) opcode[0] = 0x20; + else if (!strcmp(ins,"AND")) opcode[0] = 0x21; + else if (!strcmp(ins,"BIT")) opcode[0] = 0x24; + else if (!strcmp(ins,"ROL")) opcode[0] = 0x26; + else if (!strcmp(ins,"BMI")) opcode[0] = 0x30; + else if (!strcmp(ins,"EOR")) opcode[0] = 0x41; + else if (!strcmp(ins,"LSR")) opcode[0] = 0x46; + else if (!strcmp(ins,"JMP")) opcode[0] = 0x4C; + else if (!strcmp(ins,"BVC")) opcode[0] = 0x50; + else if (!strcmp(ins,"ADC")) opcode[0] = 0x61; + else if (!strcmp(ins,"ROR")) opcode[0] = 0x66; + else if (!strcmp(ins,"BVS")) opcode[0] = 0x70; + else if (!strcmp(ins,"STA")) opcode[0] = 0x81; + else if (!strcmp(ins,"STY")) opcode[0] = 0x84; + else if (!strcmp(ins,"STX")) opcode[0] = 0x86; + else if (!strcmp(ins,"BCC")) opcode[0] = 0x90; + else if (!strcmp(ins,"LDY")) opcode[0] = 0xA0; + else if (!strcmp(ins,"LDA")) opcode[0] = 0xA1; + else if (!strcmp(ins,"LDX")) opcode[0] = 0xA2; + else if (!strcmp(ins,"BCS")) opcode[0] = 0xB0; + else if (!strcmp(ins,"CPY")) opcode[0] = 0xC0; + else if (!strcmp(ins,"CMP")) opcode[0] = 0xC1; + else if (!strcmp(ins,"DEC")) opcode[0] = 0xC6; + else if (!strcmp(ins,"BNE")) opcode[0] = 0xD0; + else if (!strcmp(ins,"CPX")) opcode[0] = 0xE0; + else if (!strcmp(ins,"SBC")) opcode[0] = 0xE1; + else if (!strcmp(ins,"INC")) opcode[0] = 0xE6; + else if (!strcmp(ins,"BEQ")) opcode[0] = 0xF0; + else return 1; + + { + //Parse Operands + // It's not the sexiest thing ever, but it works well enough! + + //TODO: + // Add branches. + // Fix certain instructions. (Setting bits is not 100% perfect.) + // Fix instruction/operand matching. (Instructions like "jmp ($94),Y" are no good!) + // Optimizations? + int tmpint; + char tmpchr,tmpstr[20]; + + if (sscanf(astr,"#$%2X%c",&tmpint,&tmpchr) == 1) { //#Immediate + switch (opcode[0]) { + case 0x20: case 0x4C: //Jumps + case 0x10: case 0x30: case 0x50: case 0x70: //Branches + case 0x90: case 0xB0: case 0xD0: case 0xF0: + case 0x06: case 0x24: case 0x26: case 0x46: //Other instructions incapable of #Immediate + case 0x66: case 0x81: case 0x84: case 0x86: + case 0xC6: case 0xE6: + return 1; + default: + //cheap hack for certain instructions + switch (opcode[0]) { + case 0xA0: case 0xA2: case 0xC0: case 0xE0: + break; + default: + opcode[0] |= 0x08; + break; + } + opcode[1] = tmpint; + break; + } + } + else if (sscanf(astr,"$%4X%c",&tmpint,&tmpchr) == 1) { //Absolute, Zero Page, Branch, or Jump + switch (opcode[0]) { + case 0x20: case 0x4C: //Jumps + opcode[1] = (tmpint & 0xFF); + opcode[2] = (tmpint >> 8); + break; + case 0x10: case 0x30: case 0x50: case 0x70: //Branches + case 0x90: case 0xB0: case 0xD0: case 0xF0: + tmpint -= (addr+2); + if ((tmpint < -128) || (tmpint > 127)) return 1; + opcode[1] = (tmpint & 0xFF); + break; + //return 1; //FIX ME + default: + if (tmpint > 0xFF) { //Absolute + opcode[0] |= 0x0C; + opcode[1] = (tmpint & 0xFF); + opcode[2] = (tmpint >> 8); + } + else { //Zero Page + opcode[0] |= 0x04; + opcode[1] = (tmpint & 0xFF); + } + break; + } + } + else if (sscanf(astr,"$%4X%s",&tmpint,tmpstr) == 2) { //Absolute,X, Zero Page,X, Absolute,Y or Zero Page,Y + if (!strcmp(tmpstr,",X")) { //Absolute,X or Zero Page,X + switch (opcode[0]) { + case 0x20: case 0x4C: //Jumps + case 0x10: case 0x30: case 0x50: case 0x70: //Branches + case 0x90: case 0xB0: case 0xD0: case 0xF0: + case 0x24: case 0x86: case 0xA2: case 0xC0: //Other instructions incapable of Absolute,X or Zero Page,X + case 0xE0: + return 1; + default: + if (tmpint > 0xFF) { //Absolute + if (opcode[0] == 0x84) return 1; //No STY Absolute,X! + opcode[0] |= 0x1C; + opcode[1] = (tmpint & 0xFF); + opcode[2] = (tmpint >> 8); + } + else { //Zero Page + opcode[0] |= 0x14; + opcode[1] = (tmpint & 0xFF); + } + break; + } + } + else if (!strcmp(tmpstr,",Y")) { //Absolute,Y or Zero Page,Y + switch (opcode[0]) { + case 0x20: case 0x4C: //Jumps + case 0x10: case 0x30: case 0x50: case 0x70: //Branches + case 0x90: case 0xB0: case 0xD0: case 0xF0: + case 0x06: case 0x24: case 0x26: case 0x46: //Other instructions incapable of Absolute,Y or Zero Page,Y + case 0x66: case 0x84: case 0x86: case 0xA0: + case 0xC0: case 0xC6: case 0xE0: case 0xE6: + return 1; + case 0xA2: //cheap hack for LDX + opcode[0] |= 0x04; + default: + if (tmpint > 0xFF) { //Absolute + if (opcode[0] == 0x86) return 1; //No STX Absolute,Y! + opcode[0] |= 0x18; + opcode[1] = (tmpint & 0xFF); + opcode[2] = (tmpint >> 8); + } + else { //Zero Page + if ((opcode[0] != 0x86) || (opcode[0] != 0xA2)) return 1; //only STX and LDX Absolute,Y! + opcode[0] |= 0x10; + opcode[1] = (tmpint & 0xFF); + } + break; + } + } + else return 1; + } + else if (sscanf(astr,"($%4X%s",&tmpint,tmpstr) == 2) { //Jump (Indirect), (Indirect,X) or (Indirect),Y + switch (opcode[0]) { + case 0x20: //Jumps + case 0x10: case 0x30: case 0x50: case 0x70: //Branches + case 0x90: case 0xB0: case 0xD0: case 0xF0: + case 0x06: case 0x24: case 0x26: case 0x46: //Other instructions incapable of Jump (Indirect), (Indirect,X) or (Indirect),Y + case 0x66: case 0x84: case 0x86: case 0xA0: + case 0xA2: case 0xC0: case 0xC6: case 0xE0: + case 0xE6: + return 1; + default: + if ((!strcmp(tmpstr,")")) && (opcode[0] == 0x4C)) { //Jump (Indirect) + opcode[0] = 0x6C; + opcode[1] = (tmpint & 0xFF); + opcode[2] = (tmpint >> 8); + } + else if ((!strcmp(tmpstr,",X)")) && (tmpint <= 0xFF) && (opcode[0] != 0x4C)) { //(Indirect,X) + opcode[1] = (tmpint & 0xFF); + } + else if ((!strcmp(tmpstr,"),Y")) && (tmpint <= 0xFF) && (opcode[0] != 0x4C)) { //(Indirect),Y + opcode[0] |= 0x10; + opcode[1] = (tmpint & 0xFF); + } + else return 1; + break; + } + } + else return 1; + } +} diff --git a/drivers/win/cdlogger.c b/drivers/win/cdlogger.c new file mode 100644 index 00000000..828c1afe --- /dev/null +++ b/drivers/win/cdlogger.c @@ -0,0 +1,299 @@ +/* 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 "..\..\fceu.h" +#include "..\..\cart.h" //mbg merge 7/18/06 moved beneath fceu.h +#include "..\..\x6502.h" +#include "..\..\debugger.h" +#include "..\..\tracer.h" +#include "..\..\cdlogger.h" + +#define INESPRIV +#include "..\..\ines.h" + +void LoadCDLogFile(); +void SaveCDLogFileAs(); +void SaveCDLogFile(); +void SaveStrippedRom(); + +extern iNES_HEADER head; //defined in ines.c +extern uint8 *trainerpoo; +//extern uint8 *ROM; +//extern uint8 *VROM; + +volatile int loggingcodedata; +//int cdlogger_open; +volatile int codecount, datacount, undefinedcount; +HWND hCDLogger=0; +unsigned char *cdloggerdata; +char *cdlogfilename; +char loadedcdfile[MAX_PATH]; + +BOOL CALLBACK CDLoggerCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch(uMsg) { + case WM_INITDIALOG: + hCDLogger = hwndDlg; + codecount = datacount = 0; + undefinedcount = PRGsize[0]; + cdloggerdata = (unsigned char*)malloc(PRGsize[0]); //mbg merge 7/18/06 added cast + ZeroMemory(cdloggerdata,PRGsize[0]); + break; + case WM_CLOSE: + case WM_QUIT: + if((logging) && (logging_options & LOG_NEW_INSTRUCTIONS)){ + StopSound(); + MessageBox(hCDLogger, +"The Trace logger is currently using this for some of its features.\ + Please turn the trace logger off and try again.","Unable to Pause Code/Data Logger", +MB_OK); + break; + } + loggingcodedata = 0; + free(cdloggerdata); + cdloggerdata=0; + hCDLogger = 0; + EndDialog(hwndDlg,0); + break; + case WM_COMMAND: + switch(HIWORD(wParam)) { + case BN_CLICKED: + switch(LOWORD(wParam)) { + case 103: + codecount = datacount = 0; + undefinedcount = PRGsize[0]; + ZeroMemory(cdloggerdata,PRGsize[0]); + UpdateCDLogger(); + break; + case 104: + LoadCDLogFile(); + break; + case 105: + if(loggingcodedata){ + if((logging) && (logging_options & LOG_NEW_INSTRUCTIONS)){ + MessageBox(hCDLogger, +"The Trace logger is currently using this for some of its features.\ + Please turn the trace logger off and try again.","Unable to Pause Code/Data Logger", +MB_OK); + break; + } + loggingcodedata = 0; + EnableTracerMenuItems(); + SetDlgItemText(hCDLogger, 105, "Start"); + } + else{ + loggingcodedata = 1; + EnableTracerMenuItems(); + SetDlgItemText(hCDLogger, 105, "Pause"); + } + break; + case 106: + SaveCDLogFileAs(); + break; + case 107: + SaveCDLogFile(); + break; + case 108: + SaveStrippedRom(); + break; + } + break; + } + break; + case WM_MOVING: + StopSound(); + break; + } + return FALSE; +} + +void LoadCDLogFile(){ + FILE *FP; + int i, j; + const char filter[]="Code Data Log File(*.CDL)\0*.cdl\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 Code Data Log 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; + strcpy(loadedcdfile,nameo); + //FCEUD_PrintError(loadedcdfile); + + //fseek(FP,0,SEEK_SET); + //codecount = datacount = 0; + //undefinedcount = PRGsize[0]; + + //cdloggerdata = malloc(PRGsize[0]); + + + FP = fopen(loadedcdfile,"rb"); + if(FP == NULL){ + FCEUD_PrintError("Error Opening File"); + return; + } + + //codecount = datacount = 0; + //undefinedcount = PRGsize[0]; + + for(i = 0;i < (int)PRGsize[0];i++){ + j = fgetc(FP); + if(j == EOF)break; + if((j & 1) && !(cdloggerdata[i] & 1))codecount++; //if the new byte has something logged and + if((j & 2) && !(cdloggerdata[i] & 2))datacount++; //and the old one doesn't. Then increment + if((j & 3) && !(cdloggerdata[i] & 3))undefinedcount--; //the appropriate counter. + cdloggerdata[i] |= j; + } + fclose(FP); + UpdateCDLogger(); + return; +} + +void SaveCDLogFileAs(){ + const char filter[]="Code Data Log File(*.CDL)\0*.cdl\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="Save Code Data Log File As..."; + ofn.lpstrFilter=filter; + nameo[0]=0; + ofn.lpstrFile=nameo; + ofn.nMaxFile=256; + ofn.Flags=OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; + ofn.hwndOwner = hCDLogger; + if(!GetSaveFileName(&ofn))return; + strcpy(loadedcdfile,nameo); + SaveCDLogFile(); + return; +} + +void SaveCDLogFile(){ //todo make this button work before you've saved as + FILE *FP; + //if(loadedcdfile[0] == 0)SaveCDLogFileAs(); + //FCEUD_PrintError(loadedcdfile); + + FP = fopen(loadedcdfile,"wb"); + if(FP == NULL){ + FCEUD_PrintError("Error Opening File"); + return; + } + fwrite(cdloggerdata,PRGsize[0],1,FP); + fclose(FP); + return; +} + +void DoCDLogger(){ + if (!GI) { + FCEUD_PrintError("You must have a game loaded before you can use the Code Data Logger."); + return; + } + if (GI->type==GIT_NSF) { //todo: NSF support! + FCEUD_PrintError("Sorry, you can't yet use the Code Data Logger with NSFs."); + return; + } + + if(!hCDLogger){ + CreateDialog(fceu_hInstance,"CDLOGGER",NULL,CDLoggerCallB); + } else { + SetWindowPos(hCDLogger,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER); + } +} + +void UpdateCDLogger(){ + char str[50]; + float fcodecount = codecount, fdatacount = datacount, + fundefinedcount = undefinedcount, fromsize = PRGsize[0]; + if(!hCDLogger)return; + sprintf(str,"0x%06x %.2f%%",codecount,fcodecount/fromsize*100); + SetDlgItemText(hCDLogger,100,str); + sprintf(str,"0x%06x %.2f%%",datacount,fdatacount/fromsize*100); + SetDlgItemText(hCDLogger,101,str); + sprintf(str,"0x%06x %.2f%%",undefinedcount,fundefinedcount/fromsize*100); + SetDlgItemText(hCDLogger,102,str); + return; +} + +void LogPCM(int romaddress){ + int i = GetPRGAddress(romaddress); + if(i == -1)return; + if(cdloggerdata[i] & 0x40)return; + cdloggerdata[i] |= 0x40; + + if(!(cdloggerdata[i] & 2)){ + datacount++; + cdloggerdata[i] |= 2; + if(!(cdloggerdata[i] & 1))undefinedcount--; + } + return; +} + +void SaveStrippedRom(){ //this is based off of iNesSave() + //todo: make this support nsfs + const char filter[]="Stripped iNes Rom file(*.NES)\0*.nes\0"; + char sromfilename[MAX_PATH]; + FILE *fp; + OPENFILENAME ofn; + int i; + if(codecount == 0){ + MessageBox(NULL, "Unable to Generate Stripped Rom. Get Something Logged and try again.", "Error", MB_OK); + return; + } + StopSound(); + memset(&ofn,0,sizeof(ofn)); + ofn.lStructSize=sizeof(ofn); + ofn.hInstance=fceu_hInstance; + ofn.lpstrTitle="Save Stripped Rom File As..."; + ofn.lpstrFilter=filter; + sromfilename[0]=0; + ofn.lpstrFile=sromfilename; + ofn.nMaxFile=256; + ofn.Flags=OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; + ofn.hwndOwner = hCDLogger; + if(!GetSaveFileName(&ofn))return; + + fp = fopen(sromfilename,"wb"); + + if(fwrite(&head,1,16,fp)!=16)return; + + if(head.ROM_type&4) /* Trainer */ + { + fwrite(trainerpoo,512,1,fp); + } + + for(i = 0;i < head.ROM_size*0x4000;i++){ + if(cdloggerdata[i] & 3)fputc(ROM[i],fp); + else fputc(0,fp); + } + //fwrite(ROM,0x4000,head.ROM_size,fp); + + if(head.VROM_size)fwrite(VROM,0x2000,head.VROM_size,fp); + fclose(fp); +} diff --git a/drivers/win/cheat.c b/drivers/win/cheat.c index cff14a83..70f52257 100644 --- a/drivers/win/cheat.c +++ b/drivers/win/cheat.c @@ -1,7 +1,7 @@ /* FCE Ultra - NES/Famicom Emulator * * Copyright notice for this file: - * Copyright (C) 2002 Xodnizel + * 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 @@ -19,469 +19,685 @@ */ #include "common.h" - #include "cheat.h" +#include "..\..\memview.h" +#include "..\..\debugger.h" +#include "..\..\fceu.h" +#include "..\..\cart.h" -static HWND acwin=0; +#define GGLISTSIZE 128 //hopefully this is enough for all cases -static int selcheat; -static int scheatmethod=0; -static uint8 cheatval1=0; -static uint8 cheatval2=0; - -#define CSTOD 24 - -static uint16 StrToU16(char *s) -{ - unsigned int ret=0; - sscanf(s,"%4x",&ret); - return ret; -} - -static uint8 StrToU8(char *s) -{ - unsigned int ret=0; - sscanf(s,"%d",&ret); - return ret; -} - -static int StrToI(char *s) -{ - int ret=0; - sscanf(s,"%d",&ret); - return ret; -} - - -//This is slow -static char *U16ToStr(uint16 a) -{ - static char TempArray[16]; - sprintf(TempArray,"%04X",a); - return TempArray; -} - -//Rewritten to be fast for MemWatch (--Luke) -static char *U8ToStr(uint8 a) -{ - static char TempArray[8]; - TempArray[0] = '0' + a/100; - TempArray[1] = '0' + (a%100)/10; - TempArray[2] = '0' + (a%10); - TempArray[3] = 0; - return TempArray; -} - -static char *IToStr(int a) -{ - static char TempArray[32]; - sprintf(TempArray,"%d",a); - return TempArray; -} - -static HWND RedoCheatsWND; -static int RedoCheatsCallB(char *name, uint32 a, uint8 v, int compare, int s, int type, void *data) -{ - char tmp[512]; - sprintf(tmp,"%s %s",s?"+":"-",name); - SendDlgItemMessage(RedoCheatsWND,300,LB_ADDSTRING,0,(LPARAM)(LPSTR)tmp); - return(1); -} - -static void RedoCheatsLB(HWND hwndDlg) -{ - SendDlgItemMessage(hwndDlg,300,LB_RESETCONTENT,0,0); - RedoCheatsWND=hwndDlg; - FCEUI_ListCheats(RedoCheatsCallB, 0); -} - -int cfcallb(uint32 a, uint8 last, uint8 current) -{ - char temp[16]; - - sprintf(temp,"%04X:%03d:%03d",(unsigned int)a,last,current); - SendDlgItemMessage(acwin,108,LB_ADDSTRING,0,(LPARAM)(LPSTR)temp); - return(1); -} +int selcheat; +int ChtPosX,ChtPosY; +static HFONT hFont,hNewFont; static int scrollindex; static int scrollnum; static int scrollmax; -int cfcallbinsert(uint32 a, uint8 last, uint8 current) -{ - char temp[16]; +int lbfocus=0; +int searchdone; +static int knownvalue=0; - sprintf(temp,"%04X:%03d:%03d",(unsigned int)a,last,current); - SendDlgItemMessage(acwin,108,LB_INSERTSTRING,(CSTOD-1),(LPARAM)(LPSTR)temp); - return(1); +int GGaddr, GGcomp, GGval; +char GGcode[10]; +int GGlist[GGLISTSIZE]; +static int dontupdateGG; //this eliminates recursive crashing + +HWND hGGConv; + +void EncodeGG(char *str, int a, int v, int c); +void ListGGAddresses(); + +uint16 StrToU16(char *s) { + unsigned int ret=0; + sscanf(s,"%4x",&ret); + return ret; } -int cfcallbinsertt(uint32 a, uint8 last, uint8 current) -{ - char temp[16]; +uint8 StrToU8(char *s) { + unsigned int ret=0; + sscanf(s,"%2x",&ret); + return ret; +} - sprintf(temp,"%04X:%03d:%03d",(unsigned int)a,last,current); - SendDlgItemMessage(acwin,108,LB_INSERTSTRING,0,(LPARAM)(LPSTR)temp); - return(1); +char *U16ToStr(uint16 a) { + static char str[5]; + sprintf(str,"%04X",a); + return str; +} + +char *U8ToStr(uint8 a) { + static char str[3]; + sprintf(str,"%02X",a); + return str; +} + +static HWND hwndLB; +//int RedoCheatsCallB(char *name, uint32 a, uint8 v, int s) { //bbit edited: this commented out line was changed to the below for the new fceud +int RedoCheatsCallB(char *name, uint32 a, uint8 v, int compare,int s,int type,void*data) { + char str[259] = { 0 }; + + //strcpy(str,(s?"* ":" ")); + //strcat(str,name); + if(name[0] == 0)sprintf(str,"%s%04X=%02X",s?"* ":" ",(int)a,(int)v); + else sprintf(str,"%s%s",s?"* ":" ",name); + + SendDlgItemMessage(hwndLB,101,LB_ADDSTRING,0,(LPARAM)(LPSTR)str); + return 1; +} + +void RedoCheatsLB(HWND hwndDlg) { + SendDlgItemMessage(hwndDlg,101,LB_RESETCONTENT,0,0); + hwndLB=hwndDlg; + FCEUI_ListCheats(RedoCheatsCallB,0); +} + +int ShowResultsCallB(uint32 a, uint8 last, uint8 current) { + char temp[16]; + + sprintf(temp,"$%04X: %02X | %02X",(unsigned int)a,last,current); + SendDlgItemMessage(hwndLB,106,LB_ADDSTRING,0,(LPARAM)(LPSTR)temp); + return 1; +} + +void ShowResults(HWND hwndDlg) { + int n=FCEUI_CheatSearchGetCount(); + int t; + char str[20]; + + scrollnum=n; + scrollindex=-32768; + + hwndLB=hwndDlg; + SendDlgItemMessage(hwndDlg,106,LB_RESETCONTENT,0,0); + FCEUI_CheatSearchGetRange(0,16,ShowResultsCallB); + + t=-32768+n-17; + if (t<-32768) t=-32768; + scrollmax=t; + SendDlgItemMessage(hwndDlg,107,SBM_SETRANGE,-32768,t); + SendDlgItemMessage(hwndDlg,107,SBM_SETPOS,-32768,1); + + sprintf(str,"%d Possibilities",n); + SetDlgItemText(hwndDlg,203,str); +} + +void EnableCheatButtons(HWND hwndDlg, int enable) { + EnableWindow(GetDlgItem(hwndDlg,105),enable); + EnableWindow(GetDlgItem(hwndDlg,305),enable); + EnableWindow(GetDlgItem(hwndDlg,306),enable); + EnableWindow(GetDlgItem(hwndDlg,307),enable); + EnableWindow(GetDlgItem(hwndDlg,308),enable); + EnableWindow(GetDlgItem(hwndDlg,309),enable); +} + +extern void StopSound(void); + +BOOL CALLBACK CheatConsoleCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + LOGFONT lf; + RECT wrect; + + char str[259] = { 0 },str2[259] = { 0 }; + + char *name; + uint32 a; + uint8 v; + int s; + + switch (uMsg) { + case WM_INITDIALOG: + StopSound(); + SetWindowPos(hwndDlg,0,ChtPosX,ChtPosY,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER); + + //setup font + hFont = (HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0); + GetObject(hFont, sizeof(LOGFONT), &lf); + strcpy(lf.lfFaceName,"Courier"); + hNewFont = CreateFontIndirect(&lf); + + SendDlgItemMessage(hwndDlg,102,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,103,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,105,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,106,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,108,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,109,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,110,WM_SETFONT,(WPARAM)hNewFont,FALSE); + + //text limits + SendDlgItemMessage(hwndDlg,102,EM_SETLIMITTEXT,4,0); + SendDlgItemMessage(hwndDlg,103,EM_SETLIMITTEXT,2,0); + SendDlgItemMessage(hwndDlg,104,EM_SETLIMITTEXT,256,0); + SendDlgItemMessage(hwndDlg,105,EM_SETLIMITTEXT,2,0); + SendDlgItemMessage(hwndDlg,108,EM_SETLIMITTEXT,2,0); + SendDlgItemMessage(hwndDlg,109,EM_SETLIMITTEXT,2,0); + SendDlgItemMessage(hwndDlg,110,EM_SETLIMITTEXT,2,0); + + //disable or enable buttons + EnableWindow(GetDlgItem(hwndDlg,105),FALSE); + EnableWindow(GetDlgItem(hwndDlg,302),FALSE); + EnableWindow(GetDlgItem(hwndDlg,303),FALSE); + if (scrollnum) { + EnableCheatButtons(hwndDlg,TRUE); + ShowResults(hwndDlg); + sprintf(str,"%d Possibilities",(int)FCEUI_CheatSearchGetCount()); + SetDlgItemText(hwndDlg,203,str); + } + else EnableCheatButtons(hwndDlg,FALSE); + + //misc setup + RedoCheatsLB(hwndDlg); + searchdone=0; + SetDlgItemText(hwndDlg,105,(LPTSTR)U8ToStr(knownvalue)); + break; + + case WM_NCACTIVATE: + if ((CheatStyle) && (scrollnum)) { + if ((!wParam) && (searchdone)) { + searchdone=0; + FCEUI_CheatSearchSetCurrentAsOriginal(); + } + ShowResults(hwndDlg); + } + break; + + case WM_CLOSE: + case WM_QUIT: + CheatWindow=0; + hCheat = 0; + if (CheatStyle) DestroyWindow(hwndDlg); + else EndDialog(hwndDlg,0); + DeleteObject(hFont); + DeleteObject(hNewFont); + if (searchdone) FCEUI_CheatSearchSetCurrentAsOriginal(); + break; + + case WM_MOVE: + GetWindowRect(hwndDlg,&wrect); + ChtPosX = wrect.left; + ChtPosY = wrect.top; + break; + + case WM_VSCROLL: + if (scrollnum > 16) { + StopSound(); + switch (LOWORD(wParam)) { + case SB_TOP: + scrollindex=-32768; + SendDlgItemMessage(hwndDlg,107,SBM_SETPOS,scrollindex,1); + SendDlgItemMessage(hwndDlg,106,LB_RESETCONTENT,16,0); + FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768+16,ShowResultsCallB); + break; + case SB_BOTTOM: + scrollindex=scrollmax; + SendDlgItemMessage(hwndDlg,107,SBM_SETPOS,scrollindex,1); + SendDlgItemMessage(hwndDlg,106,LB_RESETCONTENT,16,0); + FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768+16,ShowResultsCallB); + break; + case SB_LINEUP: + if (scrollindex > -32768) { + scrollindex--; + SendDlgItemMessage(hwndDlg,107,SBM_SETPOS,scrollindex,1); + SendDlgItemMessage(hwndDlg,106,LB_RESETCONTENT,16,0); + FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768+16,ShowResultsCallB); + } + break; + case SB_PAGEUP: + scrollindex-=17; + if(scrollindex<-32768) scrollindex=-32768; + SendDlgItemMessage(hwndDlg,107,SBM_SETPOS,scrollindex,1); + SendDlgItemMessage(hwndDlg,106,LB_RESETCONTENT,16,0); + FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768+16,ShowResultsCallB); + break; + + case SB_LINEDOWN: + if (scrollindexscrollmax) scrollindex=scrollmax; + SendDlgItemMessage(hwndDlg,107,SBM_SETPOS,scrollindex,1); + SendDlgItemMessage(hwndDlg,106,LB_RESETCONTENT,0,0); + FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768+16,ShowResultsCallB); + break; + case SB_THUMBPOSITION: + case SB_THUMBTRACK: + scrollindex=(short int)HIWORD(wParam); + SendDlgItemMessage(hwndDlg,107,SBM_SETPOS,scrollindex,1); + SendDlgItemMessage(hwndDlg,106,LB_RESETCONTENT,0,0); + FCEUI_CheatSearchGetRange(32768+scrollindex,32768+scrollindex+16,ShowResultsCallB); + break; + } + + } + break; + + case WM_VKEYTOITEM: + if (lbfocus) { + int real; + + real=SendDlgItemMessage(hwndDlg,106,LB_GETCURSEL,0,0); + switch (LOWORD(wParam)) { + case VK_UP: + // mmmm....recursive goodness + if (real == 0) SendMessage(hwndDlg,WM_VSCROLL,SB_LINEUP,0); + return -1; + break; + case VK_DOWN: + if (real == 16) { + SendMessage(hwndDlg,WM_VSCROLL,SB_LINEDOWN,0); + SendDlgItemMessage(hwndDlg,106,LB_SETCURSEL,real,0); + } + return -1; + break; + case VK_PRIOR: + SendMessage(hwndDlg,WM_VSCROLL,SB_PAGEUP,0); + break; + case VK_NEXT: + SendMessage(hwndDlg,WM_VSCROLL,SB_PAGEDOWN,0); + break; + case VK_HOME: + SendMessage(hwndDlg,WM_VSCROLL,SB_TOP,0); + break; + case VK_END: + SendMessage(hwndDlg,WM_VSCROLL,SB_BOTTOM,0); + break; + } + return -2; + } + break; + + case WM_COMMAND: + switch (HIWORD(wParam)) { + case BN_CLICKED: + switch (LOWORD(wParam)) { + case 301: //Add + GetDlgItemText(hwndDlg,102,str,5); + a=StrToU16(str); + GetDlgItemText(hwndDlg,103,str,3); + v=StrToU8(str); + GetDlgItemText(hwndDlg,104,str,256); +// if (FCEUI_AddCheat(str,a,v)) { //bbit edited: replaced this with the line below + if (FCEUI_AddCheat(str,a,v,-1,1)) { + if(str[0] == 0)sprintf(str,"%04X=%02X",(int)a,(int)v); //bbit edited: added this line to give your cheat a name if you didn't supply one + strcpy(str2,"* "); + strcat(str2,str); + SendDlgItemMessage(hwndDlg,101,LB_ADDSTRING,0,(LPARAM)(LPSTR)str2); + selcheat = (SendDlgItemMessage(hwndDlg,101,LB_GETCOUNT,0,0) - 1); + SendDlgItemMessage(hwndDlg,101,LB_SETCURSEL,selcheat,0); + + SetDlgItemText(hwndDlg,102,(LPTSTR)U16ToStr(a)); + SetDlgItemText(hwndDlg,103,(LPTSTR)U8ToStr(v)); + + EnableWindow(GetDlgItem(hwndDlg,302),TRUE); + EnableWindow(GetDlgItem(hwndDlg,303),TRUE); + } + if(hMemView)UpdateColorTable(); //if the memory viewer is open then update any blue freeze locations in it as well + break; + case 302: //Delete + if (selcheat >= 0) { + FCEUI_DelCheat(selcheat); + SendDlgItemMessage(hwndDlg,101,LB_DELETESTRING,selcheat,0); + selcheat=-1; + SetDlgItemText(hwndDlg,102,(LPTSTR)""); + SetDlgItemText(hwndDlg,103,(LPTSTR)""); + SetDlgItemText(hwndDlg,104,(LPTSTR)""); + } + EnableWindow(GetDlgItem(hwndDlg,302),FALSE); + EnableWindow(GetDlgItem(hwndDlg,303),FALSE); + if(hMemView)UpdateColorTable(); //if the memory viewer is open then update any blue freeze locations in it as well + break; + case 303: //Update + if (selcheat < 0) break; + + GetDlgItemText(hwndDlg,102,str,5); + a=StrToU16(str); + GetDlgItemText(hwndDlg,103,str,3); + v=StrToU8(str); + GetDlgItemText(hwndDlg,104,str,256); +// FCEUI_SetCheat(selcheat,str,a,v,-1); //bbit edited: replaced this with the line below + FCEUI_SetCheat(selcheat,str,a,v,-1,-1,1); +// FCEUI_GetCheat(selcheat,&name,&a,&v,&s); //bbit edited: replaced this with the line below + FCEUI_GetCheat(selcheat,&name,&a,&v,NULL,&s,NULL); + strcpy(str2,(s?"* ":" ")); + if(str[0] == 0)sprintf(str,"%04X=%02X",(int)a,(int)v); //bbit edited: added this line to give your cheat a name if you didn't supply one + strcat(str2,str); + + SendDlgItemMessage(hwndDlg,101,LB_DELETESTRING,selcheat,0); + SendDlgItemMessage(hwndDlg,101,LB_INSERTSTRING,selcheat,(LPARAM)(LPSTR)str2); + SendDlgItemMessage(hwndDlg,101,LB_SETCURSEL,selcheat,0); + + SetDlgItemText(hwndDlg,102,(LPTSTR)U16ToStr(a)); + SetDlgItemText(hwndDlg,103,(LPTSTR)U8ToStr(v)); + if(hMemView)UpdateColorTable(); //if the memory viewer is open then update any blue freeze locations in it as well + break; + case 304: //Reset + FCEUI_CheatSearchBegin(); + ShowResults(hwndDlg); + EnableCheatButtons(hwndDlg,TRUE); + break; + case 305: //Known Value + searchdone=1; + GetDlgItemText(hwndDlg,105,str,3); + knownvalue=StrToU8(str); + FCEUI_CheatSearchEnd(4,knownvalue,0); + ShowResults(hwndDlg); + break; + case 306: //Equal + searchdone=1; + FCEUI_CheatSearchEnd(2,0,0); + ShowResults(hwndDlg); + break; + case 307: //Not Equal + searchdone=1; + if (IsDlgButtonChecked(hwndDlg,401) == BST_CHECKED) { + GetDlgItemText(hwndDlg,108,str,3); + FCEUI_CheatSearchEnd(2,0,StrToU8(str)); + } + else FCEUI_CheatSearchEnd(3,0,0); + ShowResults(hwndDlg); + break; + case 308: //Greater Than + searchdone=1; + if (IsDlgButtonChecked(hwndDlg,402) == BST_CHECKED) { + GetDlgItemText(hwndDlg,109,str,3); + FCEUI_CheatSearchEnd(7,0,StrToU8(str)); + } + else FCEUI_CheatSearchEnd(5,0,0); + ShowResults(hwndDlg); + break; + case 309: //Less Than + searchdone=1; + if (IsDlgButtonChecked(hwndDlg,403) == BST_CHECKED) { + GetDlgItemText(hwndDlg,110,str,3); + FCEUI_CheatSearchEnd(8,0,StrToU8(str)); + } + else FCEUI_CheatSearchEnd(6,0,0); + ShowResults(hwndDlg); + break; + } + break; + case LBN_DBLCLK: + switch (LOWORD(wParam)) { //disable/enable cheat + case 101: +// FCEUI_GetCheat(selcheat,&name,&a,&v,&s); //bbit edited: replaced this with the line below + FCEUI_GetCheat(selcheat,&name,&a,&v,NULL,&s,NULL); +// FCEUI_SetCheat(selcheat,0,-1,-1,s^=1);//bbit edited: replaced this with the line below + FCEUI_SetCheat(selcheat,0,-1,-1,-1,s^=1,1); + if(name[0] == 0)sprintf(str,"%s%04X=%02X",s?"* ":" ",(unsigned int)a,(unsigned int)v); + else sprintf(str,"%s%s",s?"* ":" ",name); + //strcpy(str,(s?"* ":" ")); + //strcat(str,name); + SendDlgItemMessage(hwndDlg,101,LB_DELETESTRING,selcheat,0); + SendDlgItemMessage(hwndDlg,101,LB_INSERTSTRING,selcheat,(LPARAM)(LPSTR)str); + SendDlgItemMessage(hwndDlg,101,LB_SETCURSEL,selcheat,0); + UpdateColorTable(); + break; + } + break; + case LBN_SELCHANGE: + switch (LOWORD(wParam)) { + case 101: + selcheat = SendDlgItemMessage(hwndDlg,101,LB_GETCURSEL,0,0); + if (selcheat < 0) break; + + FCEUI_GetCheat(selcheat,&name,&a,&v,NULL,&s,NULL); + SetDlgItemText(hwndDlg,104,(LPTSTR)name); + SetDlgItemText(hwndDlg,102,(LPTSTR)U16ToStr(a)); + SetDlgItemText(hwndDlg,103,(LPTSTR)U8ToStr(v)); + + EnableWindow(GetDlgItem(hwndDlg,302),TRUE); + EnableWindow(GetDlgItem(hwndDlg,303),TRUE); + break; + case 106: + lbfocus=1; + SendDlgItemMessage(hwndDlg,106,LB_GETTEXT,SendDlgItemMessage(hwndDlg,106,LB_GETCURSEL,0,0),(LPARAM)(LPCTSTR)str); + strcpy(str2,str+1); + str2[4] = 0; + SetDlgItemText(hwndDlg,102,(LPTSTR)str2); + strcpy(str2,str+13); + SetDlgItemText(hwndDlg,103,(LPTSTR)str2); + break; + } + break; + case LBN_SELCANCEL: + switch(LOWORD(wParam)) { + case 101: + EnableWindow(GetDlgItem(hwndDlg,302),FALSE); + EnableWindow(GetDlgItem(hwndDlg,303),FALSE); + break; + case 106: + lbfocus=0; + break; + } + break; + + } + break; + } + return 0; } -void AddTheThing(HWND hwndDlg, char *s, int a, int v) -{ - if(FCEUI_AddCheat(s,a,v,-1,0)) - MessageBox(hwndDlg,"Cheat Added","Cheat Added",MB_OK); +void ConfigCheats(HWND hParent) { + if (!GI) { + FCEUD_PrintError("You must have a game loaded before you can manipulate cheats."); + return; + } + if (GI->type==GIT_NSF) { + FCEUD_PrintError("Sorry, you can't cheat with NSFs."); + return; + } + + if (!CheatWindow) { + selcheat=-1; + CheatWindow=1; + if (CheatStyle) hCheat = CreateDialog(fceu_hInstance,"CHEATCONSOLE",NULL,CheatConsoleCallB); + else DialogBox(fceu_hInstance,"CHEATCONSOLE",hParent,CheatConsoleCallB); + } } +BOOL CALLBACK GGConvCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + char str[100]; + int i; -static void DoGet(void) -{ - int n=FCEUI_CheatSearchGetCount(); - int t; - scrollnum=n; - scrollindex=-32768; + switch(uMsg) { + case WM_INITDIALOG: + //todo: set text limits + break; + case WM_CREATE: - SendDlgItemMessage(acwin,108,LB_RESETCONTENT,0,0); - FCEUI_CheatSearchGetRange(0,(CSTOD-1),cfcallb); + break; + case WM_PAINT: + break; + case WM_CLOSE: + case WM_QUIT: + DestroyWindow(hGGConv); + hGGConv = 0; + break; - t=-32768+n-1-(CSTOD-1); - if(t<-32768) - t=-32768; - scrollmax=t; - SendDlgItemMessage(acwin,120,SBM_SETRANGE,-32768,t); - SendDlgItemMessage(acwin,120,SBM_SETPOS,-32768,1); + case WM_COMMAND: + switch(HIWORD(wParam)) { + case EN_UPDATE: + if(dontupdateGG)break; + dontupdateGG = 1; + switch(LOWORD(wParam)){ //lets find out what edit control got changed + case 1000: //The Game Genie Code - in this case decode it. + GetDlgItemText(hGGConv,1000,GGcode,9); + if((strlen(GGcode) != 8) && (strlen(GGcode) != 6))break; + + FCEUI_DecodeGG(GGcode, (uint16 *)&GGaddr, (uint8 *)&GGval, &GGcomp); + + sprintf(str,"%04X",GGaddr); + SetDlgItemText(hGGConv,1001,str); + + if(GGcomp != -1) + sprintf(str,"%02X",GGcomp); + else str[0] = 0; + SetDlgItemText(hGGConv,1002,str); + + sprintf(str,"%02X",GGval); + SetDlgItemText(hGGConv,1003,str); + //ListGGAddresses(); + break; + + case 1001: + case 1002: + case 1003: + + GetDlgItemText(hGGConv,1001,str,5); + if(strlen(str) != 4) break; + + GetDlgItemText(hGGConv,1003,str,5); + if(strlen(str) != 2) {GGval = -1; break;} + + GGaddr = GetEditHex(hGGConv,1001); + GGval = GetEditHex(hGGConv,1003); + + GetDlgItemText(hGGConv,1002,str,5); + if(strlen(str) != 2) GGcomp = -1; + else GGcomp = GetEditHex(hGGConv,1002); + + EncodeGG(GGcode, GGaddr, GGval, GGcomp); + SetDlgItemText(hGGConv,1000,GGcode); + //ListGGAddresses(); + break; + } + ListGGAddresses(); + dontupdateGG = 0; + break; + case BN_CLICKED: + switch (LOWORD(wParam)) { + case 1005: //Add Cheat + //ConfigCheats(fceu_hInstance); + + if(GGaddr < 0x8000)GGaddr += 0x8000; + + if (FCEUI_AddCheat(GGcode,GGaddr,GGval,GGcomp,1) && (hCheat != 0)) { + strcpy(str,"* "); + strcat(str,GGcode); + SendDlgItemMessage(hCheat,101,LB_ADDSTRING,0,(LPARAM)(LPSTR)str); + selcheat = (SendDlgItemMessage(hCheat,101,LB_GETCOUNT,0,0) - 1); + SendDlgItemMessage(hCheat,101,LB_SETCURSEL,selcheat,0); + + SetDlgItemText(hCheat,102,(LPTSTR)U16ToStr(GGaddr)); + SetDlgItemText(hCheat,103,(LPTSTR)U8ToStr(GGval)); + + EnableWindow(GetDlgItem(hCheat,302),TRUE); + EnableWindow(GetDlgItem(hCheat,303),TRUE); + } + } + break; + + case LBN_DBLCLK: + switch (LOWORD(wParam)) { + case 1004: + i = SendDlgItemMessage(hwndDlg,1004,LB_GETCURSEL,0,0); + ChangeMemViewFocus(2,GGlist[i],-1); + break; + } + break; + } + break; + case WM_MOUSEMOVE: + break; + case WM_HSCROLL: + break; + } + return FALSE; } -static void FixCheatSelButtons(HWND hwndDlg, int how) -{ - /* Update Cheat Button */ - EnableWindow(GetDlgItem(hwndDlg,251),how); +//The code in this function is a modified version +//of Chris Covell's work - I'd just like to point that out +void EncodeGG(char *str, int a, int v, int c){ + uint8 num[8]; + static char lets[16]={'A','P','Z','L','G','I','T','Y','E','O','X','U','K','S','V','N'}; + int i; + if(a > 0x8000)a-=0x8000; - /* Delete Cheat Button */ - EnableWindow(GetDlgItem(hwndDlg,252),how); + num[0]=(v&7)+((v>>4)&8); + num[1]=((v>>4)&7)+((a>>4)&8); + num[2]=((a>>4)&7); + num[3]=(a>>12)+(a&8); + num[4]=(a&7)+((a>>8)&8); + num[5]=((a>>8)&7); + + if (c == -1){ + num[5]+=v&8; + for(i = 0;i < 6;i++)str[i] = lets[num[i]]; + str[6] = 0; + } else { + num[2]+=8; + num[5]+=c&8; + num[6]=(c&7)+((c>>4)&8); + num[7]=((c>>4)&7)+(v&8); + for(i = 0;i < 8;i++)str[i] = lets[num[i]]; + str[8] = 0; + } + return; } -static BOOL CALLBACK AddCheatCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - static int lbfocus; - static HWND hwndLB; +void ListGGAddresses(){ + uint32 i, j = 0; //mbg merge 7/18/06 changed from int + char str[20]; + SendDlgItemMessage(hGGConv,1004,LB_RESETCONTENT,0,0); - switch(uMsg) - { - case WM_VSCROLL: - if(scrollnum>(CSTOD-1)) - { - switch((int)LOWORD(wParam)) - { - case SB_TOP: - scrollindex=-32768; - SendDlgItemMessage(hwndDlg,120,SBM_SETPOS,scrollindex,1); - SendDlgItemMessage(hwndDlg,108,LB_RESETCONTENT,(CSTOD-1),0); - FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768+(CSTOD-1),cfcallb); - break; - case SB_BOTTOM: - scrollindex=scrollmax; - SendDlgItemMessage(hwndDlg,120,SBM_SETPOS,scrollindex,1); - SendDlgItemMessage(hwndDlg,108,LB_RESETCONTENT,(CSTOD-1),0); - FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768+(CSTOD-1),cfcallb); - break; - case SB_LINEUP: - if(scrollindex>-32768) - { - scrollindex--; - SendDlgItemMessage(hwndDlg,120,SBM_SETPOS,scrollindex,1); - SendDlgItemMessage(hwndDlg,108,LB_DELETESTRING,(CSTOD-1),0); - FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768,cfcallbinsertt); - } - break; + //also enable/disable the add GG button here + GetDlgItemText(hGGConv,1000,GGcode,9); - case SB_PAGEUP: - scrollindex-=CSTOD; - if(scrollindex<-32768) scrollindex=-32768; - SendDlgItemMessage(hwndDlg,120,SBM_SETPOS,scrollindex,1); - SendDlgItemMessage(hwndDlg,108,LB_RESETCONTENT,(CSTOD-1),0); - FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768+(CSTOD-1),cfcallb); - break; + if((GGaddr < 0) || ((strlen(GGcode) != 8) && (strlen(GGcode) != 6)))EnableWindow(GetDlgItem(hGGConv,1005),FALSE); + else EnableWindow(GetDlgItem(hGGConv,1005),TRUE); - case SB_LINEDOWN: - if(scrollindex GGLISTSIZE)return; + sprintf(str,"%06X",i+(GGaddr&0x1FFF)+0x10); + SendDlgItemMessage(hGGConv,1004,LB_ADDSTRING,0,(LPARAM)(LPSTR)str); + } + } - case SB_PAGEDOWN: - scrollindex+=CSTOD; - if(scrollindex>scrollmax) - scrollindex=scrollmax; - SendDlgItemMessage(hwndDlg,120,SBM_SETPOS,scrollindex,1); - SendDlgItemMessage(hwndDlg,108,LB_RESETCONTENT,0,0); - FCEUI_CheatSearchGetRange(scrollindex+32768,scrollindex+32768+(CSTOD-1),cfcallb); - break; - - case SB_THUMBPOSITION: - case SB_THUMBTRACK: - scrollindex=(short int)HIWORD(wParam); - SendDlgItemMessage(hwndDlg,120,SBM_SETPOS,scrollindex,1); - SendDlgItemMessage(hwndDlg,108,LB_RESETCONTENT,0,0); - FCEUI_CheatSearchGetRange(32768+scrollindex,32768+scrollindex+(CSTOD-1),cfcallb); - break; - } - - } - break; - - case WM_INITDIALOG: - selcheat = -1; - FixCheatSelButtons(hwndDlg, 0); - acwin=hwndDlg; - SetDlgItemText(hwndDlg,110,(LPTSTR)U8ToStr(cheatval1)); - SetDlgItemText(hwndDlg,111,(LPTSTR)U8ToStr(cheatval2)); - DoGet(); - CheckRadioButton(hwndDlg,115,120,scheatmethod+115); - lbfocus=0; - hwndLB=0; - - RedoCheatsLB(hwndDlg); - break; - - case WM_VKEYTOITEM: - if(lbfocus) - { - int real; - - real=SendDlgItemMessage(hwndDlg,108,LB_GETCURSEL,0,(LPARAM)(LPSTR)0); - switch((int)LOWORD(wParam)) - { - case VK_UP: - /* mmmm....recursive goodness */ - if(!real) - SendMessage(hwndDlg,WM_VSCROLL,SB_LINEUP,0); - return(-1); - break; - case VK_DOWN: - if(real==(CSTOD-1)) - SendMessage(hwndDlg,WM_VSCROLL,SB_LINEDOWN,0); - return(-1); - break; - case VK_PRIOR: - SendMessage(hwndDlg,WM_VSCROLL,SB_PAGEUP,0); - break; - case VK_NEXT: - SendMessage(hwndDlg,WM_VSCROLL,SB_PAGEDOWN,0); - break; - case VK_HOME: - SendMessage(hwndDlg,WM_VSCROLL,SB_TOP,0); - break; - case VK_END: - SendMessage(hwndDlg,WM_VSCROLL,SB_BOTTOM,0); - break; - } - return(-2); - } - break; - - case WM_CLOSE: - case WM_QUIT: goto gornk; - case WM_COMMAND: - switch(LOWORD(wParam)) - { - case 300: /* List box selection changed. */ - if(HIWORD(wParam)==LBN_SELCHANGE) - { - char *s; - uint32 a; - uint8 v; - int status; - int c,type; - - selcheat=SendDlgItemMessage(hwndDlg,300,LB_GETCURSEL,0,(LPARAM)(LPSTR)0); - if(selcheat<0) - { - FixCheatSelButtons(hwndDlg,0); - break; - } - FixCheatSelButtons(hwndDlg, 1); - - FCEUI_GetCheat(selcheat,&s,&a,&v,&c,&status,&type); - SetDlgItemText(hwndDlg,200,(LPTSTR)s); - SetDlgItemText(hwndDlg,201,(LPTSTR)U16ToStr(a)); - SetDlgItemText(hwndDlg,202,(LPTSTR)U8ToStr(v)); - SetDlgItemText(hwndDlg,203,(c==-1)?(LPTSTR)"":(LPTSTR)IToStr(c)); - CheckDlgButton(hwndDlg,204,type?BST_CHECKED:BST_UNCHECKED); - } - break; - case 108: - switch(HIWORD(wParam)) - { - case LBN_SELCHANGE: - { - char TempArray[32]; - SendDlgItemMessage(hwndDlg,108,LB_GETTEXT,SendDlgItemMessage(hwndDlg,108,LB_GETCURSEL,0,(LPARAM)(LPSTR)0),(LPARAM)(LPCTSTR)TempArray); - TempArray[4]=0; - SetDlgItemText(hwndDlg,201,(LPTSTR)TempArray); - } - break; - case LBN_SETFOCUS: - lbfocus=1; - break; - case LBN_KILLFOCUS: - lbfocus=0; - break; - } - break; - } - - switch(HIWORD(wParam)) - { - case LBN_DBLCLK: - if(selcheat>=0) - { - if(LOWORD(wParam)==300) - FCEUI_ToggleCheat(selcheat); - RedoCheatsLB(hwndDlg); - SendDlgItemMessage(hwndDlg,300,LB_SETCURSEL,selcheat,0); - } - break; - - case BN_CLICKED: - if(LOWORD(wParam)>=115 && LOWORD(wParam)<=120) - scheatmethod=LOWORD(wParam)-115; - else switch(LOWORD(wParam)) - { - case 112: - FCEUI_CheatSearchBegin(); - DoGet(); - break; - case 113: - FCEUI_CheatSearchEnd(scheatmethod,cheatval1,cheatval2); - DoGet(); - break; - case 114: - FCEUI_CheatSearchSetCurrentAsOriginal(); - DoGet(); - break; - case 107: - FCEUI_CheatSearchShowExcluded(); - DoGet(); - break; - case 250: /* Add Cheat Button */ - { - int a,v,c,t; - char name[257]; - char temp[16]; - - GetDlgItemText(hwndDlg,200,name,256+1); - GetDlgItemText(hwndDlg,201,temp,4+1); - a=StrToU16(temp); - GetDlgItemText(hwndDlg,202,temp,3+1); - v=StrToU8(temp); - GetDlgItemText(hwndDlg,203,temp,3+1); - if(temp[0]==0) - c=-1; - else - c=StrToI(temp); - t=(IsDlgButtonChecked(hwndDlg,204)==BST_CHECKED)?1:0; - FCEUI_AddCheat(name,a,v,c,t); - RedoCheatsLB(hwndDlg); - SendDlgItemMessage(hwndDlg,300,LB_SETCURSEL,selcheat,0); - } - break; - case 253: /* Add GG Cheat Button */ - { - uint16 a; - int c; - uint8 v; - char name[257]; - - GetDlgItemText(hwndDlg,200,name,256+1); - - if(FCEUI_DecodeGG(name,&a,&v,&c)) - { - FCEUI_AddCheat(name,a,v,c,1); - RedoCheatsLB(hwndDlg); - SendDlgItemMessage(hwndDlg,300,LB_SETCURSEL,selcheat,0); - } - } - break; - - case 251: /* Update Cheat Button */ - if(selcheat>=0) - { - int a,v,c,t; - char name[257]; - char temp[16]; - - GetDlgItemText(hwndDlg,200,name,256+1); - GetDlgItemText(hwndDlg,201,temp,4+1); - a=StrToU16(temp); - GetDlgItemText(hwndDlg,202,temp,3+1); - v=StrToU8(temp); - GetDlgItemText(hwndDlg,203,temp,3+1); - if(temp[0]==0) - c=-1; - else - c=StrToI(temp); - t=(IsDlgButtonChecked(hwndDlg,204)==BST_CHECKED)?1:0; - FCEUI_SetCheat(selcheat,name,a,v,c,-1,t); - RedoCheatsLB(hwndDlg); - SendDlgItemMessage(hwndDlg,300,LB_SETCURSEL,selcheat,0); - } - break; - case 252: /* Delete cheat button */ - if(selcheat>=0) - { - FCEUI_DelCheat(selcheat); - SendDlgItemMessage(hwndDlg,300,LB_DELETESTRING,selcheat,0); - FixCheatSelButtons(hwndDlg, 0); - selcheat=-1; - SetDlgItemText(hwndDlg,200,(LPTSTR)""); - SetDlgItemText(hwndDlg,201,(LPTSTR)""); - SetDlgItemText(hwndDlg,202,(LPTSTR)""); - SetDlgItemText(hwndDlg,203,(LPTSTR)""); - CheckDlgButton(hwndDlg,204,BST_UNCHECKED); - } - break; - case 106: - gornk: - EndDialog(hwndDlg,0); - acwin=0; - break; - } - break; - case EN_CHANGE: - { - char TempArray[256]; - GetDlgItemText(hwndDlg,LOWORD(wParam),TempArray,256); - switch(LOWORD(wParam)) - { - case 110:cheatval1=StrToU8(TempArray);break; - case 111:cheatval2=StrToU8(TempArray);break; - } - } - break; - } - } - return 0; + } -void ConfigAddCheat(HWND wnd) -{ - if(!GI) - { - FCEUD_PrintError("You must have a game loaded before you can manipulate cheats."); - return; - } +//A different model for this could be to have everything +//set in the INITDIALOG message based on the internal +//variables, and have this simply call that. +void SetGGConvFocus(int address,int compare){ + char str[10]; + if(!hGGConv)DoGGConv(); + GGaddr = address; + GGcomp = compare; - if(GI->type==GIT_NSF) - { - FCEUD_PrintError("Sorry, you can't cheat with NSFs."); - return; - } + dontupdateGG = 1; //little hack to fix a nasty bug - DialogBox(fceu_hInstance,"ADDCHEAT",wnd,AddCheatCallB); + sprintf(str,"%04X",address); + SetDlgItemText(hGGConv,1001,str); + + dontupdateGG = 0; + + sprintf(str,"%02X",GGcomp); + SetDlgItemText(hGGConv,1002,str); + + + if(GGval < 0)SetDlgItemText(hGGConv,1000,""); + else { + EncodeGG(GGcode, GGaddr, GGval, GGcomp); + SetDlgItemText(hGGConv,1000,GGcode); + } + + SetFocus(GetDlgItem(hGGConv,1003)); + + return; } +void DoGGConv(){ + if(hGGConv)return; + hGGConv = CreateDialog(fceu_hInstance,"GGCONV",NULL,GGConvCallB); + return; +} diff --git a/drivers/win/cheat.h b/drivers/win/cheat.h index d6aa7302..4add4a1a 100644 --- a/drivers/win/cheat.h +++ b/drivers/win/cheat.h @@ -1,2 +1,11 @@ +//-- +//mbg merge 7/18/06 had to make these extern +extern int CheatWindow,CheatStyle; //bbit edited: this line added +extern HWND hCheat; + +void RedoCheatsLB(HWND hwndDlg); + void ConfigCheats(HWND hParent); -void ConfigAddCheat(HWND wnd); +void DoGGConv(); +void SetGGConvFocus(int address,int compare); +//void ConfigAddCheat(HWND wnd); //bbit edited:commented out this line diff --git a/drivers/win/common.h b/drivers/win/common.h index 7620c46c..bb3110d6 100644 --- a/drivers/win/common.h +++ b/drivers/win/common.h @@ -18,6 +18,7 @@ #include "../../driver.h" #include "../common/vidblit.h" //mbg merge 7/17/06 added #include "../common/config.h" +#include "resource.h" //mbg merge 7/18/06 added /* Message logging(non-netplay messages, usually) for all. */ #include "log.h" diff --git a/drivers/win/config.c b/drivers/win/config.c index f03ec78f..5893935d 100644 --- a/drivers/win/config.c +++ b/drivers/win/config.c @@ -117,7 +117,7 @@ static CFGSTRUCT fceuconfig[]={ AC(autoHoldClearKey), AC(frame_display), AC(input_display), - ACS(MemWatchDir), + //ACS(MemWatchDir), //mbg merge 7/18/06 removed ACS(BasicBotDir), AC(EnableBackgroundInput), ENDCFGSTRUCT diff --git a/drivers/win/dasm.h b/drivers/win/dasm.h new file mode 100644 index 00000000..59ceb671 --- /dev/null +++ b/drivers/win/dasm.h @@ -0,0 +1,252 @@ + +#define relative(a) { \ + if (((a)=opcode[1])&0x80) (a) = addr-(((a)-1)^0xFF); \ + else (a)+=addr; \ +} +#define absolute(a) { \ + (a) = opcode[1] | opcode[2]<<8; \ +} +#define zpIndex(a,i) { \ + (a) = opcode[1]+(i); \ +} +#define indirectX(a) { \ + (a) = (opcode[1]+X.X)&0xFF; \ + (a) = GetMem((a)) | (GetMem((a)+1))<<8; \ +} +#define indirectY(a) { \ + (a) = GetMem(opcode[1]) | (GetMem(opcode[1]+1))<<8; \ + (a) += X.Y; \ +} + + +//odd, 1-byte opcodes +case 0x00: strcpy(str,"BRK"); break; +case 0x08: strcpy(str,"PHP"); break; +case 0x0A: strcpy(str,"ASL"); break; +case 0x18: strcpy(str,"CLC"); break; +case 0x28: strcpy(str,"PLP"); break; +case 0x2A: strcpy(str,"ROL"); break; +case 0x38: strcpy(str,"SEC"); break; +case 0x40: strcpy(str,"RTI"); break; +case 0x48: strcpy(str,"PHA"); break; +case 0x4A: strcpy(str,"LSR"); break; +case 0x58: strcpy(str,"CLI"); break; +case 0x60: strcpy(str,"RTS"); break; +case 0x68: strcpy(str,"PLA"); break; +case 0x6A: strcpy(str,"ROR"); break; +case 0x78: strcpy(str,"SEI"); break; +case 0x88: strcpy(str,"DEY"); break; +case 0x8A: strcpy(str,"TXA"); break; +case 0x98: strcpy(str,"TYA"); break; +case 0x9A: strcpy(str,"TXS"); break; +case 0xA8: strcpy(str,"TAY"); break; +case 0xAA: strcpy(str,"TAX"); break; +case 0xB8: strcpy(str,"CLV"); break; +case 0xBA: strcpy(str,"TSX"); break; +case 0xC8: strcpy(str,"INY"); break; +case 0xCA: strcpy(str,"DEX"); break; +case 0xD8: strcpy(str,"CLD"); break; +case 0xE8: strcpy(str,"INX"); break; +case 0xEA: strcpy(str,"NOP"); break; +case 0xF8: strcpy(str,"SED"); break; + +//(Indirect,X) +case 0x01: strcpy(chr,"ORA"); goto _indirectx; +case 0x21: strcpy(chr,"AND"); goto _indirectx; +case 0x41: strcpy(chr,"EOR"); goto _indirectx; +case 0x61: strcpy(chr,"ADC"); goto _indirectx; +case 0x81: strcpy(chr,"STA"); goto _indirectx; +case 0xA1: strcpy(chr,"LDA"); goto _indirectx; +case 0xC1: strcpy(chr,"CMP"); goto _indirectx; +case 0xE1: strcpy(chr,"SBC"); goto _indirectx; +_indirectx: + indirectX(tmp); + sprintf(str,"%s ($%02X,X) @ $%04X = #$%02X", chr,opcode[1],tmp,GetMem(tmp)); + break; + +//Zero Page +case 0x05: strcpy(chr,"ORA"); goto _zeropage; +case 0x06: strcpy(chr,"ASL"); goto _zeropage; +case 0x24: strcpy(chr,"BIT"); goto _zeropage; +case 0x25: strcpy(chr,"AND"); goto _zeropage; +case 0x26: strcpy(chr,"ROL"); goto _zeropage; +case 0x45: strcpy(chr,"EOR"); goto _zeropage; +case 0x46: strcpy(chr,"LSR"); goto _zeropage; +case 0x65: strcpy(chr,"ADC"); goto _zeropage; +case 0x66: strcpy(chr,"ROR"); goto _zeropage; +case 0x84: strcpy(chr,"STY"); goto _zeropage; +case 0x85: strcpy(chr,"STA"); goto _zeropage; +case 0x86: strcpy(chr,"STX"); goto _zeropage; +case 0xA4: strcpy(chr,"LDY"); goto _zeropage; +case 0xA5: strcpy(chr,"LDA"); goto _zeropage; +case 0xA6: strcpy(chr,"LDX"); goto _zeropage; +case 0xC4: strcpy(chr,"CPY"); goto _zeropage; +case 0xC5: strcpy(chr,"CMP"); goto _zeropage; +case 0xC6: strcpy(chr,"DEC"); goto _zeropage; +case 0xE4: strcpy(chr,"CPX"); goto _zeropage; +case 0xE5: strcpy(chr,"SBC"); goto _zeropage; +case 0xE6: strcpy(chr,"INC"); goto _zeropage; +_zeropage: +// ################################## Start of SP CODE ########################### +// Change width to %04X + sprintf(str,"%s $%04X = #$%02X", chr,opcode[1],GetMem(opcode[1])); +// ################################## End of SP CODE ########################### + break; + +//#Immediate +case 0x09: strcpy(chr,"ORA"); goto _immediate; +case 0x29: strcpy(chr,"AND"); goto _immediate; +case 0x49: strcpy(chr,"EOR"); goto _immediate; +case 0x69: strcpy(chr,"ADC"); goto _immediate; +//case 0x89: strcpy(chr,"STA"); goto _immediate; //baka, no STA #imm!! +case 0xA0: strcpy(chr,"LDY"); goto _immediate; +case 0xA2: strcpy(chr,"LDX"); goto _immediate; +case 0xA9: strcpy(chr,"LDA"); goto _immediate; +case 0xC0: strcpy(chr,"CPY"); goto _immediate; +case 0xC9: strcpy(chr,"CMP"); goto _immediate; +case 0xE0: strcpy(chr,"CPX"); goto _immediate; +case 0xE9: strcpy(chr,"SBC"); goto _immediate; +_immediate: + sprintf(str,"%s #$%02X", chr,opcode[1]); + break; + +//Absolute +case 0x0D: strcpy(chr,"ORA"); goto _absolute; +case 0x0E: strcpy(chr,"ASL"); goto _absolute; +case 0x2C: strcpy(chr,"BIT"); goto _absolute; +case 0x2D: strcpy(chr,"AND"); goto _absolute; +case 0x2E: strcpy(chr,"ROL"); goto _absolute; +case 0x4D: strcpy(chr,"EOR"); goto _absolute; +case 0x4E: strcpy(chr,"LSR"); goto _absolute; +case 0x6D: strcpy(chr,"ADC"); goto _absolute; +case 0x6E: strcpy(chr,"ROR"); goto _absolute; +case 0x8C: strcpy(chr,"STY"); goto _absolute; +case 0x8D: strcpy(chr,"STA"); goto _absolute; +case 0x8E: strcpy(chr,"STX"); goto _absolute; +case 0xAC: strcpy(chr,"LDY"); goto _absolute; +case 0xAD: strcpy(chr,"LDA"); goto _absolute; +case 0xAE: strcpy(chr,"LDX"); goto _absolute; +case 0xCC: strcpy(chr,"CPY"); goto _absolute; +case 0xCD: strcpy(chr,"CMP"); goto _absolute; +case 0xCE: strcpy(chr,"DEC"); goto _absolute; +case 0xEC: strcpy(chr,"CPX"); goto _absolute; +case 0xED: strcpy(chr,"SBC"); goto _absolute; +case 0xEE: strcpy(chr,"INC"); goto _absolute; +_absolute: + absolute(tmp); + sprintf(str,"%s $%04X = #$%02X", chr,tmp,GetMem(tmp)); + break; + +//branches +case 0x10: strcpy(chr,"BPL"); goto _branch; +case 0x30: strcpy(chr,"BMI"); goto _branch; +case 0x50: strcpy(chr,"BVC"); goto _branch; +case 0x70: strcpy(chr,"BVS"); goto _branch; +case 0x90: strcpy(chr,"BCC"); goto _branch; +case 0xB0: strcpy(chr,"BCS"); goto _branch; +case 0xD0: strcpy(chr,"BNE"); goto _branch; +case 0xF0: strcpy(chr,"BEQ"); goto _branch; +_branch: + relative(tmp); + sprintf(str,"%s $%04X", chr,tmp); + break; + +//(Indirect),Y +case 0x11: strcpy(chr,"ORA"); goto _indirecty; +case 0x31: strcpy(chr,"AND"); goto _indirecty; +case 0x51: strcpy(chr,"EOR"); goto _indirecty; +case 0x71: strcpy(chr,"ADC"); goto _indirecty; +case 0x91: strcpy(chr,"STA"); goto _indirecty; +case 0xB1: strcpy(chr,"LDA"); goto _indirecty; +case 0xD1: strcpy(chr,"CMP"); goto _indirecty; +case 0xF1: strcpy(chr,"SBC"); goto _indirecty; +_indirecty: + indirectY(tmp); + sprintf(str,"%s ($%02X),Y @ $%04X = #$%02X", chr,opcode[1],tmp,GetMem(tmp)); + break; + +//Zero Page,X +case 0x15: strcpy(chr,"ORA"); goto _zeropagex; +case 0x16: strcpy(chr,"ASL"); goto _zeropagex; +case 0x35: strcpy(chr,"AND"); goto _zeropagex; +case 0x36: strcpy(chr,"ROL"); goto _zeropagex; +case 0x55: strcpy(chr,"EOR"); goto _zeropagex; +case 0x56: strcpy(chr,"LSR"); goto _zeropagex; +case 0x75: strcpy(chr,"ADC"); goto _zeropagex; +case 0x76: strcpy(chr,"ROR"); goto _zeropagex; +case 0x94: strcpy(chr,"STY"); goto _zeropagex; +case 0x95: strcpy(chr,"STA"); goto _zeropagex; +case 0xB4: strcpy(chr,"LDY"); goto _zeropagex; +case 0xB5: strcpy(chr,"LDA"); goto _zeropagex; +case 0xD5: strcpy(chr,"CMP"); goto _zeropagex; +case 0xD6: strcpy(chr,"DEC"); goto _zeropagex; +case 0xF5: strcpy(chr,"SBC"); goto _zeropagex; +case 0xF6: strcpy(chr,"INC"); goto _zeropagex; +_zeropagex: + zpIndex(tmp,X.X); +// ################################## Start of SP CODE ########################### +// Change width to %04X + sprintf(str,"%s $%02X,X @ $%04X = #$%02X", chr,opcode[1],tmp,GetMem(tmp)); +// ################################## End of SP CODE ########################### + break; + +//Absolute,Y +case 0x19: strcpy(chr,"ORA"); goto _absolutey; +case 0x39: strcpy(chr,"AND"); goto _absolutey; +case 0x59: strcpy(chr,"EOR"); goto _absolutey; +case 0x79: strcpy(chr,"ADC"); goto _absolutey; +case 0x99: strcpy(chr,"STA"); goto _absolutey; +case 0xB9: strcpy(chr,"LDA"); goto _absolutey; +case 0xBE: strcpy(chr,"LDX"); goto _absolutey; +case 0xD9: strcpy(chr,"CMP"); goto _absolutey; +case 0xF9: strcpy(chr,"SBC"); goto _absolutey; +_absolutey: + absolute(tmp); + tmp2=(tmp+X.Y); + sprintf(str,"%s $%04X,Y @ $%04X = #$%02X", chr,tmp,tmp2,GetMem(tmp2)); + break; + +//Absolute,X +case 0x1D: strcpy(chr,"ORA"); goto _absolutex; +case 0x1E: strcpy(chr,"ASL"); goto _absolutex; +case 0x3D: strcpy(chr,"AND"); goto _absolutex; +case 0x3E: strcpy(chr,"ROL"); goto _absolutex; +case 0x5D: strcpy(chr,"EOR"); goto _absolutex; +case 0x5E: strcpy(chr,"LSR"); goto _absolutex; +case 0x7D: strcpy(chr,"ADC"); goto _absolutex; +case 0x7E: strcpy(chr,"ROR"); goto _absolutex; +case 0x9D: strcpy(chr,"STA"); goto _absolutex; +case 0xBC: strcpy(chr,"LDY"); goto _absolutex; +case 0xBD: strcpy(chr,"LDA"); goto _absolutex; +case 0xDD: strcpy(chr,"CMP"); goto _absolutex; +case 0xDE: strcpy(chr,"DEC"); goto _absolutex; +case 0xFD: strcpy(chr,"SBC"); goto _absolutex; +case 0xFE: strcpy(chr,"INC"); goto _absolutex; +_absolutex: + absolute(tmp); + tmp2=(tmp+X.X); + sprintf(str,"%s $%04X,X @ $%04X = #$%02X", chr,tmp,tmp2,GetMem(tmp2)); + break; + +//jumps +case 0x20: strcpy(chr,"JSR"); goto _jump; +case 0x4C: strcpy(chr,"JMP"); goto _jump; +case 0x6C: absolute(tmp); sprintf(str,"JMP ($%04X) = $%04X", tmp,GetMem(tmp)|GetMem(tmp+1)<<8); break; +_jump: + absolute(tmp); + sprintf(str,"%s $%04X", chr,tmp); + break; + +//Zero Page,Y +case 0x96: strcpy(chr,"STX"); goto _zeropagey; +case 0xB6: strcpy(chr,"LDX"); goto _zeropagey; +_zeropagey: + zpIndex(tmp,X.Y); +// ################################## Start of SP CODE ########################### +// Change width to %04X + sprintf(str,"%s $%04X,Y @ $%04X = #$%02X", chr,opcode[1],tmp,GetMem(tmp)); +// ################################## End of SP CODE ########################### + break; + +//UNDEFINED +default: strcpy(str,"ERROR"); break; diff --git a/drivers/win/debugger.c b/drivers/win/debugger.c new file mode 100644 index 00000000..8a8699bb --- /dev/null +++ b/drivers/win/debugger.c @@ -0,0 +1,1432 @@ +/* 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 "..\..\xstring.h" +#include "..\..\debugger.h" +#include "..\..\x6502.h" +#include "..\..\fceu.h" +#include "..\..\nsf.h" +#include "..\..\cart.h" +#include "..\..\ines.h" +#include "..\..\tracer.h" +#include "..\..\memview.h" +#include "cheat.h" +#include + +// ################################## Start of SP CODE ########################### + +#include "debuggersp.h" + +extern Name* lastBankNames; +extern Name* loadedBankNames; +extern Name* ramBankNames; +extern int lastBank; +extern int loadedBank; +extern int myNumWPs; + +// ################################## End of SP CODE ########################### + +//mbg merge 7/18/06 had to add these +watchpointinfo watchpoint[65]; //64 watchpoints, + 1 reserved for step over +int childwnd,numWPs; + +extern readfunc ARead[0x10000]; +int DbgPosX,DbgPosY; +int WP_edit=-1; +int ChangeWait=0,ChangeWait2=0; +uint8 debugger_open=0; +HWND hDebug; +static HFONT hFont,hNewFont; +static SCROLLINFO si; + +int badopbreak; +int iaPC; +uint32 iapoffset; //mbg merge 7/18/06 changed from int +int step,stepout,jsrcount; +int u; //deleteme +int skipdebug; //deleteme +int GetNesFileAddress(int A){ + unsigned int result; + if((A < 0x8000) || (A > 0xFFFF))return -1; + result = &Page[A>>11][A]-PRGptr[0]; + if((result > PRGsize[0]) || (result < 0))return -1; + else return result+16; //16 bytes for the header remember +} + +int GetPRGAddress(int A){ + unsigned int result; + if((A < 0x8000) || (A > 0xFFFF))return -1; + result = &Page[A>>11][A]-PRGptr[0]; + if((result > PRGsize[0]) || (result < 0))return -1; + else return result; +} + +int GetRomAddress(int A){ + int i; + uint8 *p = GetNesPRGPointer(A-=16); + for(i = 16;i < 32;i++){ + if((&Page[i][i<<11] <= p) && (&Page[i][(i+1)<<11] > p))break; + } + if(i == 32)return -1; //not found + + return (i<<11) + (p-&Page[i][i<<11]); + +} + +uint8 *GetNesPRGPointer(int A){ + return PRGptr[0]+A; +} + +uint8 *GetNesCHRPointer(int A){ + return CHRptr[0]+A; +} + +uint8 GetMem(uint16 A) { + if ((A >= 0x2000) && (A < 0x4000)) { + switch (A&7) { + case 0: return PPU[0]; + case 1: return PPU[1]; + case 2: return PPU[2]|(PPUGenLatch&0x1F); + case 3: return PPU[3]; + case 4: return SPRAM[PPU[3]]; + case 5: return XOffset; + case 6: return RefreshAddr&0xFF; + case 7: return VRAMBuffer; + } + } + else if ((A >= 0x4000) && (A < 0x6000)) return 0xFF; //fix me + return ARead[A](A); +} + +uint8 GetPPUMem(uint8 A) { + uint16 tmp=RefreshAddr&0x3FFF; + + if (tmp<0x2000) return VPage[tmp>>10][tmp]; + if (tmp>=0x3F00) return PALRAM[tmp&0x1F]; + return vnapage[(tmp>>10)&0x3][tmp&0x3FF]; +} + + +BOOL CenterWindow(HWND hwndDlg) { + HWND hwndParent; + RECT rect, rectP; + int width, height; + int screenwidth, screenheight; + int x, y; + + //move the window relative to its parent + hwndParent = GetParent(hwndDlg); + + GetWindowRect(hwndDlg, &rect); + GetWindowRect(hwndParent, &rectP); + + width = rect.right - rect.left; + height = rect.bottom - rect.top; + + x = ((rectP.right-rectP.left) - width) / 2 + rectP.left; + y = ((rectP.bottom-rectP.top) - height) / 2 + rectP.top; + + screenwidth = GetSystemMetrics(SM_CXSCREEN); + screenheight = GetSystemMetrics(SM_CYSCREEN); + + //make sure that the dialog box never moves outside of the screen + if(x < 0) x = 0; + if(y < 0) y = 0; + if(x + width > screenwidth) x = screenwidth - width; + if(y + height > screenheight) y = screenheight - height; + + MoveWindow(hwndDlg, x, y, width, height, FALSE); + + return TRUE; +} + +int NewBreak(HWND hwndDlg, int num, int enable) { + unsigned int brk=0,ppu=0,sprite=0; + char str[5]; + char buffer[201]; + + GetDlgItemText(hwndDlg,200,str,5); + if (IsDlgButtonChecked(hwndDlg,106) == BST_CHECKED) ppu = 1; + else if (IsDlgButtonChecked(hwndDlg,107) == BST_CHECKED) sprite = 1; + if ((!ppu) && (!sprite)) { + if (GI->type == GIT_NSF) { //NSF Breakpoint keywords + if (strcmp(str,"LOAD") == 0) brk = (NSFHeader.LoadAddressLow | (NSFHeader.LoadAddressHigh<<8)); + if (strcmp(str,"INIT") == 0) brk = (NSFHeader.InitAddressLow | (NSFHeader.InitAddressHigh<<8)); + if (strcmp(str,"PLAY") == 0) brk = (NSFHeader.PlayAddressLow | (NSFHeader.PlayAddressHigh<<8)); + } + else if (GI->type == GIT_FDS) { //FDS Breakpoint keywords + if (strcmp(str,"NMI1") == 0) brk = (GetMem(0xDFF6) | (GetMem(0xDFF7)<<8)); + if (strcmp(str,"NMI2") == 0) brk = (GetMem(0xDFF8) | (GetMem(0xDFF9)<<8)); + if (strcmp(str,"NMI3") == 0) brk = (GetMem(0xDFFA) | (GetMem(0xDFFB)<<8)); + if (strcmp(str,"RST") == 0) brk = (GetMem(0xDFFC) | (GetMem(0xDFFD)<<8)); + if ((strcmp(str,"IRQ") == 0) || (strcmp(str,"BRK") == 0)) brk = (GetMem(0xDFFE) | (GetMem(0xDFFF)<<8)); + } + else { //NES Breakpoint keywords + if ((strcmp(str,"NMI") == 0) || (strcmp(str,"VBL") == 0)) brk = (GetMem(0xFFFA) | (GetMem(0xFFFB)<<8)); + if (strcmp(str,"RST") == 0) brk = (GetMem(0xFFFC) | (GetMem(0xFFFD)<<8)); + if ((strcmp(str,"IRQ") == 0) || (strcmp(str,"BRK") == 0)) brk = (GetMem(0xFFFE) | (GetMem(0xFFFF)<<8)); + } + } + if ((brk == 0) && (sscanf(str,"%4X",&brk) == EOF)) return 1; + if (ppu) brk &= 0x3FFF; + if (sprite) brk &= 0x00FF; + watchpoint[num].address = brk; + + watchpoint[num].endaddress = 0; + GetDlgItemText(hwndDlg,201,str,5); + sscanf(str,"%4X",&brk); + if (ppu) brk &= 0x3FFF; + if (sprite) brk &= 0x00FF; + if ((brk != 0) && (watchpoint[num].address < brk)) watchpoint[num].endaddress = brk; + + watchpoint[num].flags = 0; + if (enable) watchpoint[num].flags|=WP_E; + if (IsDlgButtonChecked(hwndDlg,102) == BST_CHECKED) watchpoint[num].flags|=WP_R; + if (IsDlgButtonChecked(hwndDlg,103) == BST_CHECKED) watchpoint[num].flags|=WP_W; + if (IsDlgButtonChecked(hwndDlg,104) == BST_CHECKED) watchpoint[num].flags|=WP_X; + if (ppu) { + watchpoint[num].flags|=BT_P; + watchpoint[num].flags&=~WP_X; //disable execute flag! + } + if (sprite) { + watchpoint[num].flags|=BT_S; + watchpoint[num].flags&=~WP_X; //disable execute flag! + } + +// ################################## Start of SP CODE ########################### + + buffer[200] = 0; + GetDlgItemText(hwndDlg, 203, buffer, 200); + + if (watchpoint[num].desc) + free(watchpoint[num].desc); + + watchpoint[num].desc = (char*)malloc(strlen(buffer)); + strcpy(watchpoint[num].desc, buffer); + + GetDlgItemText(hwndDlg, 202, buffer, 200); + + return checkCondition(buffer, num); + +// ################################## End of SP CODE ########################### +} + +int AddBreak(HWND hwndDlg) { + int val; + if (numWPs == 64) return 1; + val = NewBreak(hwndDlg,numWPs,1); + if (val == 1) return 2; + else if (val == 2) return 3; + numWPs++; +// ################################## Start of SP CODE ########################### + myNumWPs++; +// ################################## End of SP CODE ########################### + return 0; +} + +BOOL CALLBACK AddbpCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + char str[8]={0}; + int tmp; + + switch(uMsg) { + case WM_INITDIALOG: + CenterWindow(hwndDlg); + SendDlgItemMessage(hwndDlg,200,EM_SETLIMITTEXT,4,0); + SendDlgItemMessage(hwndDlg,201,EM_SETLIMITTEXT,4,0); + if (WP_edit >= 0) { + SetWindowText(hwndDlg,"Edit Breakpoint..."); + + sprintf(str,"%04X",watchpoint[WP_edit].address); + SetDlgItemText(hwndDlg,200,str); + sprintf(str,"%04X",watchpoint[WP_edit].endaddress); + if (strcmp(str,"0000") != 0) SetDlgItemText(hwndDlg,201,str); + if (watchpoint[WP_edit].flags&WP_R) CheckDlgButton(hwndDlg, 102, BST_CHECKED); + if (watchpoint[WP_edit].flags&WP_W) CheckDlgButton(hwndDlg, 103, BST_CHECKED); + if (watchpoint[WP_edit].flags&WP_X) CheckDlgButton(hwndDlg, 104, BST_CHECKED); + + if (watchpoint[WP_edit].flags&BT_P) { + CheckDlgButton(hwndDlg, 106, BST_CHECKED); + EnableWindow(GetDlgItem(hwndDlg,104),FALSE); + } + else if (watchpoint[WP_edit].flags&BT_S) { + CheckDlgButton(hwndDlg, 107, BST_CHECKED); + EnableWindow(GetDlgItem(hwndDlg,104),FALSE); + } + else CheckDlgButton(hwndDlg, 105, BST_CHECKED); + +// ################################## Start of SP CODE ########################### + + SendDlgItemMessage(hwndDlg,202,EM_SETLIMITTEXT,200,0); + SendDlgItemMessage(hwndDlg,203,EM_SETLIMITTEXT,200,0); + + if (watchpoint[WP_edit].cond) + { + SetDlgItemText(hwndDlg, 202, watchpoint[WP_edit].condText); + } + else + { + SetDlgItemText(hwndDlg, 202, ""); + } + + if (watchpoint[WP_edit].desc) + { + SetDlgItemText(hwndDlg, 203, watchpoint[WP_edit].desc); + } + else + { + SetDlgItemText(hwndDlg, 203, ""); + } + +// ################################## End of SP CODE ########################### + } + else CheckDlgButton(hwndDlg, 105, BST_CHECKED); + break; + case WM_CLOSE: + case WM_QUIT: + break; + case WM_COMMAND: + switch(HIWORD(wParam)) { + case BN_CLICKED: + switch(LOWORD(wParam)) { + case 100: + if (WP_edit >= 0) { + int tmp = NewBreak(hwndDlg,WP_edit,(BOOL)(watchpoint[WP_edit].flags&WP_E)); + if (tmp == 2) + { + MessageBox(hwndDlg, "Invalid breakpoint condition", "Error", MB_OK); + break; + } + EndDialog(hwndDlg,1); + break; + } + if ((tmp=AddBreak(hwndDlg)) == 1) { + MessageBox(hwndDlg, "Too many breakpoints, please delete one and try again", "Breakpoint Error", MB_OK); + goto endaddbrk; + } + if (tmp == 2) goto endaddbrk; + else if (tmp == 3) + { + MessageBox(hwndDlg, "Invalid breakpoint condition", "Error", MB_OK); + break; + } + EndDialog(hwndDlg,1); + break; + case 101: + endaddbrk: + EndDialog(hwndDlg,0); + break; + case 105: //CPU Mem + EnableWindow(GetDlgItem(hwndDlg,104),TRUE); + break; + case 106: //PPU Mem + case 107: //Sprtie Mem + EnableWindow(GetDlgItem(hwndDlg,104),FALSE); + break; + } + break; + } + break; + } + return FALSE; //TRUE; +} + +char *BinToASM(int addr, uint8 *opcode) { + static char str[64]={0},chr[5]={0}; + uint16 tmp,tmp2; + + switch (opcode[0]) { + #include "dasm.h" + } + + return str; +} + +void Disassemble(HWND hWnd, int id, int scrollid, unsigned int addr) { +// ################################## Start of SP CODE ########################### + +// Changed 25 to 33 in this function for more disassembly lines +// Changed size of str (TODO: Better calculation of max size) +// Changed the buffer size of str to 35000 + +// ################################## End of SP CODE ########################### + + char str[35000]={0},chr[34]={0}; + int size,i,j; + uint8 opcode[3]; + +// ################################## Start of SP CODE ########################### + + loadNameFiles(); + +// ################################## End of SP CODE ########################### + + si.nPos = addr; + SetScrollInfo(GetDlgItem(hWnd,scrollid),SB_CTL,&si,TRUE); + + for (i = 0; i < 34; i++) { + if (addr > 0xFFFF) break; + if(addr >= 0x8000) + sprintf(chr, "%02X:%04X", getBank(addr), addr); + else sprintf(chr, " :%04X", addr); + +// ################################## Start of SP CODE ########################### + + symbDebugEnabled = IsDlgButtonChecked(hWnd, 208); + + decorateAddress(addr, str, chr, symbDebugEnabled); + +// ################################## End of SP CODE ########################### + + if ((size = opsize[GetMem(addr)]) == 0) { + sprintf(chr, "%02X UNDEFINED", GetMem(addr++)); + strcat(str,chr); + } + else { + char* a; + if ((addr+size) > 0x10000) { //should this be 0xFFFF? + while (addr < 0x10000) { + sprintf(chr, "%02X OVERFLOW\r\n", GetMem(addr++)); + strcat(str,chr); + } + break; + } + for (j = 0; j < size; j++) { + sprintf(chr, "%02X ", opcode[j] = GetMem(addr++)); + strcat(str,chr); + } + while (size < 3) { + strcat(str," "); //pad output to align ASM + size++; + } + +// ################################## Start of SP CODE ########################### + + a = BinToASM(addr,opcode); + + if (symbDebugEnabled) + { + replaceNames(ramBankNames, a); + replaceNames(loadedBankNames, a); + replaceNames(lastBankNames, a); + } + +// ################################## End of SP CODE ########################### + + strcat(strcat(str," "),a); + } + strcat(str,"\r\n"); + } + SetDlgItemText(hWnd,id,str); +} + +char *DisassembleLine(int addr) { + static char str[64]={0},chr[25]={0}; + char *c; + int size,j; + uint8 opcode[3]; + + sprintf(str, "%02X:%04X:", getBank(addr),addr); + if ((size = opsize[GetMem(addr)]) == 0) { + sprintf(chr, "%02X UNDEFINED", GetMem(addr++)); + strcat(str,chr); + } + else { + if ((addr+size) > 0x10000) { + sprintf(chr, "%02X OVERFLOW", GetMem(addr)); + strcat(str,chr); + } + else { + for (j = 0; j < size; j++) { + sprintf(chr, "%02X ", opcode[j] = GetMem(addr++)); + strcat(str,chr); + } + while (size < 3) { + strcat(str," "); //pad output to align ASM + size++; + } + strcat(strcat(str," "),BinToASM(addr,opcode)); + } + } + if ((c=strchr(str,'='))) *(c-1) = 0; + if ((c=strchr(str,'@'))) *(c-1) = 0; + return str; +} + +char *DisassembleData(int addr, uint8 *opcode) { + static char str[64]={0},chr[25]={0}; + char *c; + int size,j; + + sprintf(str, "%02X:%04X:", getBank(addr), addr); + if ((size = opsize[opcode[0]]) == 0) { + sprintf(chr, "%02X UNDEFINED", opcode[0]); + strcat(str,chr); + } + else { + if ((addr+size) > 0x10000) { + sprintf(chr, "%02X OVERFLOW", opcode[0]); + strcat(str,chr); + } + else { + for (j = 0; j < size; j++) { + sprintf(chr, "%02X ", opcode[j]); + addr++; + strcat(str,chr); + } + while (size < 3) { + strcat(str," "); //pad output to align ASM + size++; + } + strcat(strcat(str," "),BinToASM(addr,opcode)); + } + } + if ((c=strchr(str,'='))) *(c-1) = 0; + if ((c=strchr(str,'@'))) *(c-1) = 0; + return str; +} + + +int GetEditHex(HWND hwndDlg, int id) { + char str[9]; + int tmp; + GetDlgItemText(hwndDlg,id,str,9); + tmp = strtol(str,NULL,16); + return tmp; +} + +int *GetEditHexData(HWND hwndDlg, int id){ + static int data[31]; + char str[60]; + int i,j, k; + + GetDlgItemText(hwndDlg,id,str,60); + memset(data,0,30*sizeof(int)); + j=0; + for(i = 0;i < 60;i++){ + if(str[i] == 0)break; + if((str[i] >= '0') && (str[i] <= '9'))j++; + if((str[i] >= 'A') && (str[i] <= 'F'))j++; + if((str[i] >= 'a') && (str[i] <= 'f'))j++; + } + + j=j&1; + for(i = 0;i < 60;i++){ + if(str[i] == 0)break; + k = -1; + if((str[i] >= '0') && (str[i] <= '9'))k=str[i]-'0'; + if((str[i] >= 'A') && (str[i] <= 'F'))k=(str[i]-'A')+10; + if((str[i] >= 'a') && (str[i] <= 'f'))k=(str[i]-'a')+10; + if(k != -1){ + if(j&1)data[j>>1] |= k; + else data[j>>1] |= k<<4; + j++; + } + } + data[j>>1]=-1; + return data; +} + +/* +int GetEditStack(HWND hwndDlg) { + char str[85]; + int tmp; + GetDlgItemText(hwndDlg,308,str,85); + sscanf(str,"%2x,%2x,%2x,%2x,\r\n",&tmp); + return tmp; +} +*/ + +void UpdateRegs(HWND hwndDlg) { + X.A = GetEditHex(hwndDlg,304); + X.X = GetEditHex(hwndDlg,305); + X.Y = GetEditHex(hwndDlg,306); + X.PC = GetEditHex(hwndDlg,307); +} + +void UpdateDebugger() { + char str[256]={0},chr[8]; + int tmp,ret,i; + + Disassemble(hDebug, 300, 301, X.PC); + + sprintf(str, "%02X", X.A); + SetDlgItemText(hDebug, 304, str); + sprintf(str, "%02X", X.X); + SetDlgItemText(hDebug, 305, str); + sprintf(str, "%02X", X.Y); + SetDlgItemText(hDebug, 306, str); + sprintf(str, "%04X", (int)X.PC); + SetDlgItemText(hDebug, 307, str); + + sprintf(str, "%04X", (int)RefreshAddr); + SetDlgItemText(hDebug, 310, str); + sprintf(str, "%02X", PPU[3]); + SetDlgItemText(hDebug, 311, str); + + sprintf(str, "Scanline: %d", scanline); + SetDlgItemText(hDebug, 501, str); + + tmp = X.S|0x0100; + sprintf(str, "Stack $%04X", tmp); + SetDlgItemText(hDebug, 403, str); + tmp = ((tmp+1)|0x0100)&0x01FF; + sprintf(str, "%02X", GetMem(tmp)); + for (i = 1; i < 28; i++) { + tmp = ((tmp+1)|0x0100)&0x01FF; //increment and fix pointer to $0100-$01FF range + if ((i%4) == 0) sprintf(chr, ",\r\n%02X", GetMem(tmp)); + else sprintf(chr, ",%02X", GetMem(tmp)); + strcat(str,chr); + } + SetDlgItemText(hDebug, 308, str); + + GetDlgItemText(hDebug,309,str,5); + if (((ret = sscanf(str,"%4X",&tmp)) == EOF) || (ret != 1)) tmp = 0; + sprintf(str,"%04X",tmp); + SetDlgItemText(hDebug,309,str); + + CheckDlgButton(hDebug, 200, BST_UNCHECKED); + CheckDlgButton(hDebug, 201, BST_UNCHECKED); + CheckDlgButton(hDebug, 202, BST_UNCHECKED); + CheckDlgButton(hDebug, 203, BST_UNCHECKED); + CheckDlgButton(hDebug, 204, BST_UNCHECKED); + CheckDlgButton(hDebug, 205, BST_UNCHECKED); + CheckDlgButton(hDebug, 206, BST_UNCHECKED); + CheckDlgButton(hDebug, 207, BST_UNCHECKED); + + tmp = X.P; + if (tmp & N_FLAG) CheckDlgButton(hDebug, 200, BST_CHECKED); + if (tmp & V_FLAG) CheckDlgButton(hDebug, 201, BST_CHECKED); + if (tmp & U_FLAG) CheckDlgButton(hDebug, 202, BST_CHECKED); + if (tmp & B_FLAG) CheckDlgButton(hDebug, 203, BST_CHECKED); + if (tmp & D_FLAG) CheckDlgButton(hDebug, 204, BST_CHECKED); + if (tmp & I_FLAG) CheckDlgButton(hDebug, 205, BST_CHECKED); + if (tmp & Z_FLAG) CheckDlgButton(hDebug, 206, BST_CHECKED); + if (tmp & C_FLAG) CheckDlgButton(hDebug, 207, BST_CHECKED); +} + +char *BreakToText(unsigned int num) { + static char str[230],chr[8]; + + sprintf(str, "$%04X", watchpoint[num].address); + if (watchpoint[num].endaddress) { + sprintf(chr, "-$%04X", watchpoint[num].endaddress); + strcat(str,chr); + } + if (watchpoint[num].flags&WP_E) strcat(str,": E"); else strcat(str,": -"); + if (watchpoint[num].flags&BT_P) strcat(str,"P"); else if (watchpoint[num].flags&BT_S) strcat(str,"S"); else strcat(str,"C"); + if (watchpoint[num].flags&WP_R) strcat(str,"R"); else strcat(str,"-"); + if (watchpoint[num].flags&WP_W) strcat(str,"W"); else strcat(str,"-"); + if (watchpoint[num].flags&WP_X) strcat(str,"X"); else strcat(str,"-"); + +// ################################## Start of SP CODE ########################### + if (watchpoint[num].desc) + { + strcat(str, " - "); + strcat(str, watchpoint[num].desc); + } +// ################################## End of SP CODE ########################### + + return str; +} + +void AddBreakList() { + SendDlgItemMessage(hDebug,302,LB_INSERTSTRING,-1,(LPARAM)(LPSTR)BreakToText(numWPs-1)); +} + +void EditBreakList() { + if (WP_edit >= 0) { + SendDlgItemMessage(hDebug,302,LB_DELETESTRING,WP_edit,0); + SendDlgItemMessage(hDebug,302,LB_INSERTSTRING,WP_edit,(LPARAM)(LPSTR)BreakToText(WP_edit)); + SendDlgItemMessage(hDebug,302,LB_SETCURSEL,WP_edit,0); + } +} + +void FillBreakList(HWND hwndDlg) { + int i; + + for (i = 0; i < numWPs; i++) { + SendDlgItemMessage(hwndDlg,302,LB_INSERTSTRING,-1,(LPARAM)(LPSTR)BreakToText(i)); + } +} + +void EnableBreak(int sel) { + watchpoint[sel].flags^=WP_E; + SendDlgItemMessage(hDebug,302,LB_DELETESTRING,sel,0); + SendDlgItemMessage(hDebug,302,LB_INSERTSTRING,sel,(LPARAM)(LPSTR)BreakToText(sel)); + SendDlgItemMessage(hDebug,302,LB_SETCURSEL,sel,0); +} + +void DeleteBreak(int sel) { + int i; + + for (i = sel; i < numWPs; i++) { + watchpoint[i].address = watchpoint[i+1].address; + watchpoint[i].endaddress = watchpoint[i+1].endaddress; + watchpoint[i].flags = watchpoint[i+1].flags; +// ################################## Start of SP CODE ########################### + watchpoint[i].cond = watchpoint[i+1].cond; + watchpoint[i].condText = watchpoint[i+1].condText; + watchpoint[i].desc = watchpoint[i+1].desc; +// ################################## End of SP CODE ########################### + } + numWPs--; +// ################################## Start of SP CODE ########################### + myNumWPs--; +// ################################## End of SP CODE ########################### + SendDlgItemMessage(hDebug,302,LB_DELETESTRING,sel,0); + EnableWindow(GetDlgItem(hDebug,102),FALSE); + EnableWindow(GetDlgItem(hDebug,103),FALSE); +} + +void KillDebugger() { + SendDlgItemMessage(hDebug,302,LB_RESETCONTENT,0,0); + numWPs = 0; + step = 0; + stepout = 0; + jsrcount = 0; + FCEUI_SetEmulationPaused(0); //mbg merge 7/18/06 changed from userpause +} + + +int AddAsmHistory(HWND hwndDlg, int id, char *str) { + int index; + index = SendDlgItemMessage(hwndDlg,id,CB_FINDSTRINGEXACT,-1,(LPARAM)(LPSTR)str); + if (index == CB_ERR) { + SendDlgItemMessage(hwndDlg,id,CB_INSERTSTRING,-1,(LPARAM)(LPSTR)str); + return 0; + } + return 1; +} + +int ParseASM(unsigned char *output, int addr, char *str) { + unsigned char opcode[3] = { 0,0,0 }; + char astr[128],ins[4]; + + if ((!strlen(str)) || (strlen(str) > 0x127)) return 1; + + strcpy(astr,str); + str_ucase(astr); + sscanf(astr,"%3s",ins); //get instruction + if (strlen(ins) != 3) return 1; + strcpy(astr,strstr(astr,ins)+3); //heheh, this is probably a bad idea, but let's do it anyway! + if ((astr[0] != ' ') && (astr[0] != 0)) return 1; + + //remove all whitespace + str_strip(astr,STRIP_SP|STRIP_TAB|STRIP_CR|STRIP_LF); + + //repair syntax + chr_replace(astr,'[','('); //brackets + chr_replace(astr,']',')'); + chr_replace(astr,'{','('); + chr_replace(astr,'}',')'); + chr_replace(astr,';',0); //comments + str_replace(astr,"0X","$"); //miscellaneous + + //This does the following: + // 1) Sets opcode[0] on success, else returns 1. + // 2) Parses text in *astr to build the rest of the assembled + // data in 'opcode', else returns 1 on error. + #include "asm.h" + + memcpy(output,opcode,3); + return 0; +} + +BOOL CALLBACK AssemblerCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + int romaddr,count,i,j; + char str[128],*dasm; + static int patchlen,applied,saved,lastundo; + static uint8 patchdata[64][3],undodata[64*3]; + uint8 *ptr; + + switch(uMsg) { + case WM_INITDIALOG: + CenterWindow(hwndDlg); + + //set font + SendDlgItemMessage(hwndDlg,101,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,102,WM_SETFONT,(WPARAM)hNewFont,FALSE); + + //set limits + SendDlgItemMessage(hwndDlg,100,CB_LIMITTEXT,20,0); + + SetDlgItemText(hwndDlg,101,DisassembleLine(iaPC)); + SetFocus(GetDlgItem(hwndDlg,100)); + + patchlen = 0; + applied = 0; + saved = 0; + lastundo = 0; + break; + case WM_CLOSE: + case WM_QUIT: + EndDialog(hwndDlg,0); + break; + case WM_COMMAND: + switch (HIWORD(wParam)) { + case BN_CLICKED: + switch (LOWORD(wParam)) { + case 201: //Apply + if (patchlen) { + ptr = GetNesPRGPointer(GetNesFileAddress(iaPC)-16); + count = 0; + for (i = 0; i < patchlen; i++) { + for (j = 0; j < opsize[patchdata[i][0]]; j++) { + if (count == lastundo) undodata[lastundo++] = ptr[count]; + ptr[count++] = patchdata[i][j]; + } + } + SetWindowText(hwndDlg, "Inline Assembler *Patches Applied*"); + //MessageBeep(MB_OK); + applied = 1; + } + break; + case 202: //Save... + if (applied) { + count = romaddr = GetNesFileAddress(iaPC); + for (i = 0; i < patchlen; i++) count += opsize[patchdata[i][0]]; + if (patchlen) sprintf(str,"Write patch data to file at addresses 0x%06X - 0x%06X?",romaddr,count-1); + else sprintf(str,"Undo all previously applied patches?"); + if (MessageBox(hwndDlg, str, "Save changes to file?", MB_YESNO|MB_ICONINFORMATION) == IDYES) { + if (iNesSave()) { + saved = 1; + applied = 0; + } + else MessageBox(hwndDlg, "Unable to save changes to file", "Error saving to file", MB_OK); + } + } + break; + case 203: //Undo + if ((count = SendDlgItemMessage(hwndDlg,102,LB_GETCOUNT,0,0))) { + SendDlgItemMessage(hwndDlg,102,LB_DELETESTRING,count-1,0); + patchlen--; + count = 0; + for (i = 0; i < patchlen; i++) count += opsize[patchdata[i][0]]; + if (count < lastundo) { + ptr = GetNesPRGPointer(GetNesFileAddress(iaPC)-16); + j = opsize[patchdata[patchlen][0]]; + for (i = count; i < (count+j); i++) { + ptr[i] = undodata[i]; + } + lastundo -= j; + applied = 1; + } + SetDlgItemText(hwndDlg,101,DisassembleLine(iaPC+count)); + } + break; + case 300: //Hidden default button + count = 0; + for (i = 0; i < patchlen; i++) count += opsize[patchdata[i][0]]; + GetDlgItemText(hwndDlg,100,str,21); + if (!ParseASM(patchdata[patchlen],(iaPC+count),str)) { + count = iaPC; + for (i = 0; i <= patchlen; i++) count += opsize[patchdata[i][0]]; + if (count > 0x10000) { //note: don't use 0xFFFF! + MessageBox(hwndDlg, "Patch data cannot exceed address 0xFFFF", "Address error", MB_OK); + break; + } + SetDlgItemText(hwndDlg,100,""); + if (count < 0x10000) SetDlgItemText(hwndDlg,101,DisassembleLine(count)); + else SetDlgItemText(hwndDlg,101,"OVERFLOW"); + dasm = DisassembleData((count-opsize[patchdata[patchlen][0]]),patchdata[patchlen]); + SendDlgItemMessage(hwndDlg,102,LB_INSERTSTRING,-1,(LPARAM)(LPSTR)dasm); + AddAsmHistory(hwndDlg,100,dasm+16); + SetWindowText(hwndDlg, "Inline Assembler"); + patchlen++; + } + else { //ERROR! + SetWindowText(hwndDlg, "Inline Assembler *Syntax Error*"); + MessageBeep(MB_ICONEXCLAMATION); + } + break; + } + SetFocus(GetDlgItem(hwndDlg,100)); //set focus to combo box after anything is pressed! + break; + } + break; + } + return FALSE; +} + +BOOL CALLBACK PatcherCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + char str[64]; //mbg merge 7/18/06 changed from unsigned char + uint8 *c; + int i; + int *p; + + switch(uMsg) { + case WM_INITDIALOG: + CenterWindow(hwndDlg); + + //set limits + SendDlgItemMessage(hwndDlg,102,EM_SETLIMITTEXT,6,0); + SendDlgItemMessage(hwndDlg,109,EM_SETLIMITTEXT,30,0); + UpdatePatcher(hwndDlg); + + if(iapoffset != -1){ + CheckDlgButton(hwndDlg, 101, BST_CHECKED); + sprintf((char*)str,"%X",iapoffset); //mbg merge 7/18/06 added cast + SetDlgItemText(hwndDlg,102,str); + } + + SetFocus(GetDlgItem(hwndDlg,100)); + break; + case WM_CLOSE: + case WM_QUIT: + EndDialog(hwndDlg,0); + break; + case WM_COMMAND: + switch(HIWORD(wParam)) { + case BN_CLICKED: + switch(LOWORD(wParam)) { + case 103: //todo: maybe get rid of this button and cause iapoffset to update every time you change the text + if(IsDlgButtonChecked(hwndDlg,101) == BST_CHECKED){ + iapoffset = GetEditHex(hwndDlg,102); + } else iapoffset = GetNesFileAddress(GetEditHex(hwndDlg,102)); + if((iapoffset < 16) && (iapoffset != -1)){ + MessageBox(hDebug, "Sorry, iNes Header editing isn't supported", "Error", MB_OK); + iapoffset = -1; + } + if((iapoffset > PRGsize[0]) && (iapoffset != -1)){ + MessageBox(hDebug, "Error: .Nes offset outside of PRG rom", "Error", MB_OK); + iapoffset = -1; + } + UpdatePatcher(hwndDlg); + break; + case 110: + p = GetEditHexData(hwndDlg,109); + i=0; + c = GetNesPRGPointer(iapoffset-16); + while(p[i] != -1){ + c[i] = p[i]; + i++; + } + UpdatePatcher(hwndDlg); + break; + case 111: + if(!iNesSave())MessageBox(NULL,"Error Saving","Error",MB_OK); + break; + } + break; + } + break; + } + return FALSE; +} + +extern void StopSound(); +extern char *iNesShortFName(); + +BOOL CALLBACK DebuggerCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + LOGFONT lf; + RECT wrect; + char str[256]={0},*ptr,dotdot[4]; + int tmp,tmp2; + int mouse_x,mouse_y; + int ret,i; + //FILE *fp; + + //these messages get handled at any time + switch(uMsg) { + case WM_INITDIALOG: { + extern int loadDebugDataFailed; + + SetWindowPos(hwndDlg,0,DbgPosX,DbgPosY,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER); + + si.cbSize = sizeof(SCROLLINFO); + si.fMask = SIF_ALL; + si.nMin = 0; + si.nMax = 0x10000; + si.nPos = 0; + si.nPage = 20; + SetScrollInfo(GetDlgItem(hwndDlg,301),SB_CTL,&si,TRUE); + + //setup font + hFont = (HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0); + GetObject(hFont, sizeof(LOGFONT), &lf); + strcpy(lf.lfFaceName,"Courier"); + hNewFont = CreateFontIndirect(&lf); + + SendDlgItemMessage(hwndDlg,300,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,304,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,305,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,306,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,307,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,308,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,309,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,310,WM_SETFONT,(WPARAM)hNewFont,FALSE); + SendDlgItemMessage(hwndDlg,311,WM_SETFONT,(WPARAM)hNewFont,FALSE); + + //text limits + SendDlgItemMessage(hwndDlg,304,EM_SETLIMITTEXT,2,0); + SendDlgItemMessage(hwndDlg,305,EM_SETLIMITTEXT,2,0); + SendDlgItemMessage(hwndDlg,306,EM_SETLIMITTEXT,2,0); + SendDlgItemMessage(hwndDlg,307,EM_SETLIMITTEXT,4,0); + SendDlgItemMessage(hwndDlg,308,EM_SETLIMITTEXT,83,0); + SendDlgItemMessage(hwndDlg,309,EM_SETLIMITTEXT,4,0); + SendDlgItemMessage(hwndDlg,310,EM_SETLIMITTEXT,4,0); + SendDlgItemMessage(hwndDlg,311,EM_SETLIMITTEXT,2,0); + + //I'm lazy, disable the controls which I can't mess with right now + SendDlgItemMessage(hwndDlg,310,EM_SETREADONLY,TRUE,0); + SendDlgItemMessage(hwndDlg,311,EM_SETREADONLY,TRUE,0); + +// ################################## Start of SP CODE ########################### + + SendDlgItemMessage(hwndDlg,312,EM_SETLIMITTEXT,4,0); + + if (!loadDebugDataFailed) + { + if (bookmarks) + { + unsigned int i; + for (i=0;i si.nMax) si.nPos = si.nMax-si.nPage; //mbg merge 7/18/06 added cast + SetScrollInfo((HWND)lParam,SB_CTL,&si,TRUE); + Disassemble(hDebug, 300, 301, si.nPos); + } + break; + + case WM_MOUSEWHEEL: //just handle page up/down and mousewheel messages together + i = (short)HIWORD(wParam);///WHEEL_DELTA; + GetScrollInfo((HWND)lParam,SB_CTL,&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; //mbg merge 7/18/06 added cast + SetScrollInfo((HWND)lParam,SB_CTL,&si,TRUE); + Disassemble(hDebug, 300, 301, si.nPos); + break; + + case WM_KEYDOWN: + MessageBox(hwndDlg,"Die!","I'm dead!",MB_YESNO|MB_ICONINFORMATION); + //i=0; + //if(uMsg == WM_KEYDOWN){ + // if(wParam == VK_PRIOR) i = -1; + // if(wParam == VK_NEXT) i = 1; + //} + break; + + case WM_MOUSEMOVE: + mouse_x = GET_X_LPARAM(lParam); + mouse_y = GET_Y_LPARAM(lParam); +// ################################## Start of SP CODE ########################### + + // mouse_y < 538 + // > 33) tmp = 33 + + if ((mouse_x > 8) && (mouse_x < 22) && (mouse_y > 10) && (mouse_y < 538)) { + if ((tmp=((mouse_y - 10) / 13)) > 33) tmp = 33; + +// ################################## End of SP CODE ########################### + + i = si.nPos; + while (tmp > 0) { + if ((tmp2=opsize[GetMem(i)]) == 0) tmp2++; + if ((i+=tmp2) > 0xFFFF) { + i = 0xFFFF; + break; + } + tmp--; + } + if (i >= 0x8000) { + dotdot[0] = 0; + if (!(ptr = iNesShortFName())) ptr = "..."; + if (strlen(ptr) > 60) strcpy(dotdot,"..."); + if (GetNesFileAddress(i) == -1) sprintf(str,"CPU Address $%04X, Error retreiving ROM File Address!",i); +// ################################## Start of SP CODE ########################### + else sprintf(str,"CPU Address %02X:%04X, Offset 0x%06X in file \"%.20s%s\" (NL file: %X)",getBank(i),i,GetNesFileAddress(i),ptr,dotdot,getBank(i)); +// ################################## End of SP CODE ########################### + SetDlgItemText(hwndDlg,502,str); + } + else SetDlgItemText(hwndDlg,502,""); + } + else SetDlgItemText(hwndDlg,502,""); + break; + case WM_LBUTTONDOWN: + mouse_x = GET_X_LPARAM(lParam); + mouse_y = GET_Y_LPARAM(lParam); +// ################################## Start of SP CODE ########################### + + // mouse_y < 538 + // > 33) tmp = 33 + //mbg merge 7/18/06 changed pausing check + if (FCEUI_EmulationPaused() && (mouse_x > 8) && (mouse_x < 22) && (mouse_y > 10) && (mouse_y < 538)) { + if ((tmp=((mouse_y - 10) / 13)) > 33) tmp = 33; +// ################################## End of SP CODE ########################### + i = si.nPos; + while (tmp > 0) { + if ((tmp2=opsize[GetMem(i)]) == 0) tmp2++; + if ((i+=tmp2) > 0xFFFF) { + i = 0xFFFF; + break; + } + tmp--; + } + //DoPatcher(GetNesFileAddress(i),hwndDlg); + iaPC=i; + if (iaPC >= 0x8000) { + DialogBox(fceu_hInstance,"ASSEMBLER",hwndDlg,AssemblerCallB); + UpdateDebugger(); + } + } + break; + case WM_RBUTTONDOWN: + mouse_x = GET_X_LPARAM(lParam); + mouse_y = GET_Y_LPARAM(lParam); + //mbg merge 7/18/06 changed pausing check + if (FCEUI_EmulationPaused() && (mouse_x > 8) && (mouse_x < 22) && (mouse_y > 10) && (mouse_y < 338)) + { + if ((tmp=((mouse_y - 10) / 13)) > 24) tmp = 24; + i = si.nPos; + while (tmp > 0) { + if ((tmp2=opsize[GetMem(i)]) == 0) tmp2++; + if ((i+=tmp2) > 0xFFFF) { + i = 0xFFFF; + break; + } + tmp--; + } + ChangeMemViewFocus(2,GetNesFileAddress(i),-1); + } else {StopSound();} + break; + case WM_MBUTTONDOWN: + mouse_x = GET_X_LPARAM(lParam); + mouse_y = GET_Y_LPARAM(lParam); + //mbg merge 7/18/06 changed pausing check + if (FCEUI_EmulationPaused() && (mouse_x > 8) && (mouse_x < 22) && (mouse_y > 10) && (mouse_y < 338)) + { + if ((tmp=((mouse_y - 10) / 13)) > 24) tmp = 24; + i = si.nPos; + while (tmp > 0) { + if ((tmp2=opsize[GetMem(i)]) == 0) tmp2++; + if ((i+=tmp2) > 0xFFFF) { + i = 0xFFFF; + break; + } + tmp--; + } + SetGGConvFocus(i,GetMem(i)); + } else {StopSound();} + break; + case WM_INITMENUPOPUP: + case WM_INITMENU: + StopSound(); + break; + case WM_COMMAND: + switch(HIWORD(wParam)) { + case BN_CLICKED: + switch(LOWORD(wParam)) { + case 101: //Add + StopSound(); + childwnd = 1; + if (DialogBox(fceu_hInstance,"ADDBP",hwndDlg,AddbpCallB)) AddBreakList(); + childwnd = 0; + UpdateDebugger(); + break; + case 102: //Delete + DeleteBreak(SendDlgItemMessage(hwndDlg,302,LB_GETCURSEL,0,0)); + break; + case 103: //Edit + StopSound(); + WP_edit = SendDlgItemMessage(hwndDlg,302,LB_GETCURSEL,0,0); + if (DialogBox(fceu_hInstance,"ADDBP",hwndDlg,AddbpCallB)) EditBreakList(); + WP_edit = -1; + UpdateDebugger(); + break; + case 104: //Run + //mbg merge 7/18/06 changed pausing check and set + if (FCEUI_EmulationPaused()) { + UpdateRegs(hwndDlg); + FCEUI_ToggleEmulationPause(); + UpdateDebugger(); + } + break; + case 105: //Step Into + //mbg merge 7/18/06 changed pausing check and set + if (FCEUI_EmulationPaused()) + UpdateRegs(hwndDlg); + step = 1; + FCEUI_SetEmulationPaused(0); + UpdateDebugger(); + break; + case 106: //Step Out + //mbg merge 7/18/06 changed pausing check and set + if (FCEUI_EmulationPaused() > 0) { + UpdateRegs(hwndDlg); + if ((stepout) && (MessageBox(hwndDlg,"Step Out is currently in process. Cancel it and setup a new Step Out watch?","Step Out Already Active",MB_YESNO|MB_ICONINFORMATION) != IDYES)) break; + if (GetMem(X.PC) == 0x20) jsrcount = 1; + else jsrcount = 0; + stepout = 1; + FCEUI_SetEmulationPaused(0); + UpdateDebugger(); + } + break; + case 107: //Step Over + //mbg merge 7/18/06 changed pausing check and set + if (FCEUI_EmulationPaused()) { + UpdateRegs(hwndDlg); + if (GetMem(tmp=X.PC) == 0x20) { + if ((watchpoint[64].flags) && (MessageBox(hwndDlg,"Step Over is currently in process. Cancel it and setup a new Step Over watch?","Step Over Already Active",MB_YESNO|MB_ICONINFORMATION) != IDYES)) break; + watchpoint[64].address = (tmp+3); + watchpoint[64].flags = WP_E|WP_X; + } + else step = 1; + FCEUI_SetEmulationPaused(0); + } + break; + case 108: //Seek PC + //mbg merge 7/18/06 changed pausing check + if (FCEUI_EmulationPaused()) { + UpdateRegs(hwndDlg); + UpdateDebugger(); + } + break; + case 109: //Seek To: + //mbg merge 7/18/06 changed pausing check + if (FCEUI_EmulationPaused()) UpdateRegs(hwndDlg); + GetDlgItemText(hwndDlg,309,str,5); + if (((ret = sscanf(str,"%4X",&tmp)) == EOF) || (ret != 1)) tmp = 0; + sprintf(str,"%04X",tmp); + SetDlgItemText(hwndDlg,309,str); + Disassemble(hDebug, 300, 301, tmp); + break; + + case 110: //Break on bad opcode + badopbreak ^=1; + break; + case 200: X.P^=N_FLAG; UpdateDebugger(); break; + case 201: X.P^=V_FLAG; UpdateDebugger(); break; + case 202: X.P^=U_FLAG; UpdateDebugger(); break; + case 203: X.P^=B_FLAG; UpdateDebugger(); break; + case 204: X.P^=D_FLAG; UpdateDebugger(); break; + case 205: X.P^=I_FLAG; UpdateDebugger(); break; + case 206: X.P^=Z_FLAG; UpdateDebugger(); break; + case 207: X.P^=C_FLAG; UpdateDebugger(); break; +// ################################## Start of SP CODE ########################### + + case 111: lastBank = loadedBank = -1; loadNameFiles(); UpdateDebugger(); break; + case 112: AddDebuggerBookmark(hwndDlg); break; + case 113: DeleteDebuggerBookmark(hwndDlg); break; + case 208: UpdateDebugger(); break; + +// ################################## End of SP CODE ########################### + + case 602: DoPatcher(-1,hwndDlg); break; + //case 603: DoTracer(hwndDlg); break; + } + //UpdateDebugger(); + break; + case LBN_DBLCLK: + switch(LOWORD(wParam)) { + case 302: EnableBreak(SendDlgItemMessage(hwndDlg,302,LB_GETCURSEL,0,0)); break; +// ################################## Start of SP CODE ########################### + + case 701: GoToDebuggerBookmark(hwndDlg); break; + +// ################################## End of SP CODE ########################### + } + break; + case LBN_SELCANCEL: + switch(LOWORD(wParam)) { + case 302: + EnableWindow(GetDlgItem(hwndDlg,102),FALSE); + EnableWindow(GetDlgItem(hwndDlg,103),FALSE); + break; + } + break; + case LBN_SELCHANGE: + switch(LOWORD(wParam)) { + case 302: + EnableWindow(GetDlgItem(hwndDlg,102),TRUE); + EnableWindow(GetDlgItem(hwndDlg,103),TRUE); + break; + } + break; + } + break;/* + default: + if( + (uMsg == 312) || + (uMsg == 309) || + (uMsg == 308) || + (uMsg == 307) || + (uMsg == 311) || + (uMsg == 71) || + (uMsg == 310) || + (uMsg == 20) || + (uMsg == 13) || + (uMsg == 133) || + (uMsg == 70) || + (uMsg == 24) || + (uMsg == 296) || + (uMsg == 295) || + (uMsg == 15) || + (uMsg == 272) || + (uMsg == 49) || + (uMsg == 3) + )break;*/ + + /* + if(skipdebug)break; + if( + (uMsg == 32) || + (uMsg == 127))break; + fp = fopen("debug.txt","a"); + u++; + sprintf(str,"%d = %06d",u,uMsg); + skipdebug=1; + SetWindowText(hDebug,str); + skipdebug=0; + fprintf(fp,"%s\n",str); + fclose(fp); + break;*/ + } + } + + + return FALSE; //TRUE; +} + +extern void iNESGI(int h); + +void DoPatcher(int address,HWND hParent){ + StopSound(); + iapoffset=address; + if(GameInterface==iNESGI)DialogBox(fceu_hInstance,"ROMPATCHER",hParent,PatcherCallB); + else MessageBox(hDebug, "Sorry, The Patcher only works on INES rom images", "Error", MB_OK); + UpdateDebugger(); +} + +void UpdatePatcher(HWND hwndDlg){ + char str[75]; //mbg merge 7/18/06 changed from unsigned + uint8 *p; + if(iapoffset != -1){ + EnableWindow(GetDlgItem(hwndDlg,109),TRUE); + EnableWindow(GetDlgItem(hwndDlg,110),TRUE); + + if(GetRomAddress(iapoffset) != -1)sprintf(str,"Current Data at NES ROM Address: %04X, .NES file Address: %04X",GetRomAddress(iapoffset),iapoffset); + else sprintf(str,"Current Data at .NES file Address: %04X",iapoffset); + + SetDlgItemText(hwndDlg,104,str); + + sprintf(str,"%04X",GetRomAddress(iapoffset)); + SetDlgItemText(hwndDlg,107,str); + + if(GetRomAddress(iapoffset) != -1)SetDlgItemText(hwndDlg,107,DisassembleLine(GetRomAddress(iapoffset))); + else SetDlgItemText(hwndDlg,107,"Not Currently Loaded in ROM for disassembly"); + + p = GetNesPRGPointer(iapoffset-16); + sprintf(str,"%02X %02X %02X %02X %02X %02X %02X %02X", + p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]); + SetDlgItemText(hwndDlg,105,str); + + } else { + SetDlgItemText(hwndDlg,104,"No Offset Selected"); + SetDlgItemText(hwndDlg,105,""); + SetDlgItemText(hwndDlg,107,""); + EnableWindow(GetDlgItem(hwndDlg,109),FALSE); + EnableWindow(GetDlgItem(hwndDlg,110),FALSE); + } + if(FCEUGameInfo->type != GIT_CART)EnableWindow(GetDlgItem(hwndDlg,111),FALSE); + else EnableWindow(GetDlgItem(hwndDlg,111),TRUE); +} + +void DoDebug(uint8 halt) { + if (!debugger_open) hDebug = CreateDialog(fceu_hInstance,"DEBUGGER",NULL,DebuggerCallB); + if (hDebug) { + SetWindowPos(hDebug,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER); + if (GI) UpdateDebugger(); + } +} diff --git a/drivers/win/debuggersp.c b/drivers/win/debuggersp.c new file mode 100644 index 00000000..a179eac7 --- /dev/null +++ b/drivers/win/debuggersp.c @@ -0,0 +1,874 @@ +/* FCEUXD SP - NES/Famicom Emulator + * + * Copyright notice for this file: + * Copyright (C) 2005 Sebastian Porst + * + * 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 "debuggersp.h" +#include "..\..\debugger.h" +#include "..\..\conddebug.h" + +#include +#include +#include + +int GetNesFileAddress(int A); + +Name* lastBankNames = 0; +Name* loadedBankNames = 0; +Name* ramBankNames = 0; +int lastBank = -1; +int loadedBank = -1; +extern char LoadedRomFName[2048]; +char symbDebugEnabled = 0; +int debuggerWasActive = 0; + +/** +* Tests whether a char is a valid hexadecimal character. +* +* @param c The char to test +* @return True or false. +**/ +int isHex(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +/** +* Replaces all occurences of a substring in a string with a new string. +* The maximum size of the string after replacing is 1000. +* The caller must ensure that the src buffer is large enough. +* +* @param src Source string +* @param r String to replace +* @param w New string +**/ +void replaceString(char* src, const char* r, const char* w) +{ + char buff[1001] = {0}; + char* pos = src; + char* beg = src; + + // Check parameters + + if (!src || !r || !w) + { + MessageBox(0, "Error: Invalid parameter in function replaceString", "Error", MB_OK | MB_ICONERROR); + return; + } + + // Replace sub strings + + while ((pos = strstr(src, r))) + { + *pos = 0; + strcat(buff, src); + strcat(buff, w ? w : r); + src = pos + strlen(r); + } + + strcat(buff, src); + + strcpy(beg, buff); +} + +/** +* Returns the bank for a given offset. +* Technically speaking this function does not calculate the actual bank +* where the offset resides but the 0x4000 bytes large chunk of the ROM of the offset. +* +* @param offs The offset +* @return The bank of that offset or -1 if the offset is not part of the ROM. +**/ +int getBank(int offs) +{ + int addr = 0; //MBG TODO GetNesFileAddress(offs); + return addr != -1 ? addr / 0x4000 : -1; +} + +/** +* Parses a line from a NL file. +* @param line The line to parse +* @param n The name structure to write the information to +* +* @return 0 if everything went OK. Otherwise an error code is returned. +**/ +int parseLine(char* line, Name* n) +{ + char* pos; + int llen; + + // Check parameters + + if (!line) + { + MessageBox(0, "Invalid parameter \"line\" in function parseLine", "Error", MB_OK | MB_ICONERROR); + return 1; + } + + if (!n) + { + MessageBox(0, "Invalid parameter \"n\" in function parseLine", "Error", MB_OK | MB_ICONERROR); + return 2; + } + + // Allow empty lines + if (*line == '\r' || *line == '\n') + { + return -1; + } + + // Attempt to tokenize the given line + + pos = strstr(line, "#"); + + if (!pos) + { + // Found an invalid line + return 3; + } + + // Check if the first tokenized part (the offset) is valid + + *pos = 0; + + llen = (int)strlen(line); + + if (llen == 5) // Offset size of normal lines of the form $XXXX + { + if (line[0] != '$' + || !isHex(line[1]) + || !isHex(line[2]) + || !isHex(line[3]) + || !isHex(line[4]) + ) + { + return 4; + } + } + else if (llen >= 7) // Offset size of array definition lines of the form $XXXX/YY + { + int i; + if (line[0] != '$' + || !isHex(line[1]) + || !isHex(line[2]) + || !isHex(line[3]) + || !isHex(line[4]) + || line[5] != '/' + ) + { + return 5; + } + + for (i=6;line[i];i++) + { + if (!isHex(line[i])) + { + return 6; + } + } + } + else // Lines that have an invalid size + { + return 7; + } + + + // TODO: Validate if the offset is in the correct NL file. + + // After validating the offset it's OK to store it + n->offset = (char*)malloc(strlen(line) + 1); + strcpy(n->offset, line); + line = pos + 1; + + // Attempt to tokenize the string again to find the name of the address + + pos = strstr(line, "#"); + + if (!pos) + { + // Found an invalid line + return 8; + } + + *pos = 0; + + // The only requirement for a name of an address is that it's not of size 0 + + if (*line) + { + n->name = (char*)malloc(strlen(line) + 1); + strcpy(n->name, line); + } + else + { + // Properly initialize zero-length names too + n->name = 0; + } + + // Now it's clear that the line was valid. The rest of the line is the comment + // that belongs to that line. Once again a real string is required though. + + line = pos + 1; + + if (*line > 0x0D) + { + n->comment = (char*)malloc(strlen(line) + 1); + strcpy(n->comment, line); + } + else + { + // Properly initialize zero-length comments too + n->comment = 0; + } + + // Everything went fine. + return 0; +} + +/** +* Parses an array of lines read from a NL file. +* @param lines The lines to parse +* @param filename The name of the file the lines were read from. +* +* @return A pointer to the first Name structure built from the parsed lines. +**/ +Name* parse(char* lines, const char* filename) +{ + char* pos, *size; + Name* prev = 0, *cur, *first = 0; + + // Check the parameters + if (!lines) + { + MessageBox(0, "Invalid parameter \"lines\" in function parse", "Error", MB_OK | MB_ICONERROR); + return 0; + } + + if (!filename) + { + MessageBox(0, "Invalid parameter \"filename\" in function parse", "Error", MB_OK | MB_ICONERROR); + return 0; + } + + // Begin the actual parsing + + do + { + int fail; + + // Allocate a name structure to hold the parsed data from the next line + + cur = (Name*)malloc(sizeof(Name)); + cur->offset = 0; + cur->next = 0; + cur->name = 0; + cur->comment = 0; + + pos = lines; + + // This first loop attempts to read potential multi-line comments and add them + // into a single comment. + for(;;) + { + // Get the end of the next line + pos = strstr(pos, "\n"); + + // If there's no end of line or if the next line does not begin with a \ character + // we can stop. + if (!pos || pos[1] != '\\') break; + + // At this point we have the following situation. pos[0] and pos[1] can be overwritten + /* + pos -1 0 1 2 + ? \n \ ? + */ + + // \r\n is needed in text boxes + if (pos[-1] != '\r') + { + pos[0] = '\r'; + pos[1] = '\n'; + pos += 2; + } + else + { + // Remove backslash + pos[1] = ' '; + pos += 1; + } + } + + if (!pos) + { + // All lines were parsed + break; + } + + *pos = 0; + + // Attempt to parse the current line + fail = parseLine(lines, cur); + + if (fail == -1) + { + continue; + } + else if (fail) // Show an error to allow the user to correct the defect line + { + const char* fmtString = "Error (Code: %d): Invalid line \"%s\" in NL file \"%s\""; + char* msg = (char*)malloc(strlen(fmtString) + 8 + strlen(lines) + strlen(filename) + 1); + sprintf(msg, fmtString, fail, lines, filename); + MessageBox(0, msg, "Error", MB_OK | MB_ICONERROR); + free(msg); + lines = pos + 1; + MessageBox(0, lines, "Error", MB_OK | MB_ICONERROR); + continue; + } + + lines = pos + 1; + + // Check if the line is an array definition line + + size = strstr(cur->offset, "/"); + + if (size) // Array definition line + { + int arrlen, offset; + + *size = 0; + + // Attempt to read the length of the array and the array offset + if (sscanf(size + 1, "%x", &arrlen) > 0 && sscanf(cur->offset + 1, "%x", &offset) > 0) + { + Name* nn = 0; + int i; + + // Create a node for each element of the array + for (i=0;i<=arrlen;i++) + { + char numbuff[10] = {0}; + + nn = (Name*)malloc(sizeof(Name)); + nn->next = 0; + + // The comment is the same for each array element + nn->comment = strdup(cur->comment); + + // The offset of the node + nn->offset = (char*)malloc(10); + sprintf(nn->offset, "$%04X", offset + i); + + // The name of an array address is of the form NAME[INDEX] + sprintf(numbuff, "[%X]", i); + + nn->name = (char*)malloc(strlen(cur->name) + strlen(numbuff) + 1); + strcpy(nn->name, cur->name); + strcat(nn->name, numbuff); + + // Add the new node to the list of address nodes + if (prev) + { + prev->next = nn; + prev = prev->next; + } + else + { + first = prev = nn; + } + } + + // Free the allocated node + free(cur->name); + free(cur->comment); + free(cur->offset); + free(cur); + + cur = nn; + } + else + { + // I don't think it's possible to get here as the validity of + // offset and array size has already been validated in parseLine + continue; + } + } + else + { + // Add the node to the list of address nodes + if (prev) + { + prev->next = cur; + prev = prev->next; + } + else + { + first = prev = cur; + } + } + } while (pos); + + // Return the first node in the list of address nodes + return first; +} + +/** +* Load and parse an entire NL file +* @param filename Name of the file to parse +* +* @return A pointer to the first Name structure built from the file data. +**/ +Name* parseNameFile(const char* filename) +{ + char* buffer; + Name* n = 0; + + // Attempt to read the file + FILE* f = fopen(filename, "rb"); + + if (f) + { +// __asm (".byte 0xcc"); + // Get the file size + int lSize; + fseek (f , 0 , SEEK_END); + lSize = ftell(f) + 1; + + // Allocate sufficient buffer space + rewind (f); + + buffer = (char*)malloc(lSize); + + if (buffer) + { + // Read the file and parse it + memset(buffer, 0, lSize); + fread(buffer, 1, lSize - 1, f); + n = parse(buffer, filename); + + fclose(f); + + free(buffer); + } + } + + return n; +} + +/** +* Frees an entire list of Name nodes starting with the given node. +* +* @param n The node to start with (0 is a valid value) +**/ +void freeList(Name* n) +{ + Name* next; + + while (n) + { + if (n->offset) free(n->offset); + if (n->name) free(n->name); + if (n->comment) free(n->comment); + + next = n->next; + free(n); + n = next; + } +} + +/** +* Replaces all offsets in a string with the names that were given to those offsets +* The caller needs to make sure that str is large enough. +* +* @param list NL list of address definitions +* @param str The string where replacing takes place. +**/ +void replaceNames(Name* list, char* str) +{ + Name* beg = list; + + if (!str) + { + MessageBox(0, "Error: Invalid parameter \"str\" in function replaceNames", "Error", MB_OK | MB_ICONERROR); + return; + } + + while (beg) + { + if (beg->name) + { + replaceString(str, beg->offset, beg->name); + } + + beg = beg->next; + } +} + +/** +* Searches an address node in a list of address nodes. The found node +* has the same offset as the passed parameter offs. +* +* @param node The address node list +* @offs The offset to search +* @return The node that has the given offset or 0. +**/ +Name* searchNode(Name* node, const char* offs) +{ + while (node) + { + if (!strcmp(node->offset, offs)) + { + return node; + } + + node = node->next; + } + + return 0; +} + +/** +* Loads the necessary NL files +**/ +void loadNameFiles() +{ + int cb; + char* fn = (char*)malloc(strlen(LoadedRomFName) + 20); + + if (ramBankNames) + free(ramBankNames); + + // The NL file for the RAM addresses has the name nesrom.nes.ram.nl + strcpy(fn, LoadedRomFName); + strcat(fn, ".ram.nl"); + + // Load the address descriptions for the RAM addresses + ramBankNames = parseNameFile(fn); + + free(fn); + + // Find out which bank is loaded at 0xC000 + cb = getBank(0xC000); + + if (cb == -1) // No bank was loaded at that offset + { + free(lastBankNames); + lastBankNames = 0; + } + else if (cb != lastBank) + { + char* fn = (char*)malloc(strlen(LoadedRomFName) + 12); + + // If the bank changed since loading the NL files the last time it's necessary + // to load the address descriptions of the new bank. + + lastBank = cb; + + // Get the name of the NL file + sprintf(fn, "%s.%X.nl", LoadedRomFName, lastBank); + + if (lastBankNames) + freeList(lastBankNames); + + // Load new address definitions + lastBankNames = parseNameFile(fn); + + free(fn); + } + + // Find out which bank is loaded at 0x8000 + cb = getBank(0x8000); + + if (cb == -1) // No bank is loaded at that offset + { + free(loadedBankNames); + loadedBankNames = 0; + } + else if (cb != loadedBank) + { + char* fn = (char*)malloc(strlen(LoadedRomFName) + 12); + + // If the bank changed since loading the NL files the last time it's necessary + // to load the address descriptions of the new bank. + + loadedBank = cb; + + // Get the name of the NL file + sprintf(fn, "%s.%X.nl", LoadedRomFName, loadedBank); + + if (loadedBankNames) + freeList(loadedBankNames); + + // Load new address definitions + loadedBankNames = parseNameFile(fn); + + free(fn); + } +} + +/** +* Adds label and comment to an offset in the disassembly output string +* +* @param addr Address of the currently processed line +* @param str Disassembly output string +* @param chr Address in string format +* @param decorate Flag that indicates whether label and comment should actually be added +**/ +void decorateAddress(unsigned int addr, char* str, const char* chr, UINT decorate) +{ + if (decorate) + { + Name* n; + + if (addr < 0x8000) + { + // Search address definition node for a RAM address + n = searchNode(ramBankNames, chr); + } + else + { + // Search address definition node for a ROM address + n = addr >= 0xC000 ? searchNode(lastBankNames, chr) : searchNode(loadedBankNames, chr); + } + + // If a node was found there's a name or comment to add do so + if (n && (n->name || n->comment)) + { + // Add name + if (n->name && *n->name) + { + strcat(str, "Name: "); + strcat(str, n->name); + strcat(str,"\r\n"); + } + + // Add comment + if (n->comment && *n->comment) + { + strcat(str, "Comment: "); + strcat(str, n->comment); + strcat(str, "\r\n"); + } + } + } + + // Add address + strcat(str, chr); + strcat(str, ":"); +} + +/** +* Checks whether a breakpoint condition is syntactically valid +* and creates a breakpoint condition object if everything's OK. +* +* @param condition Condition to parse +* @param num Number of the breakpoint in the BP list the condition belongs to +* @return 0 in case of an error; 2 if everything went fine +**/ +int checkCondition(char* condition, int num) +{ + char* b = condition; + + // Check if the condition isn't just all spaces. + + int onlySpaces = 1; + + while (*b) + { + if (*b != ' ') + { + onlySpaces = 0; + break; + } + + ++b; + } + + // Remove the old breakpoint condition before + // adding a new condition. + + if (watchpoint[num].cond) + { + freeTree(watchpoint[num].cond); + free(watchpoint[num].condText); + + watchpoint[num].cond = 0; + watchpoint[num].condText = 0; + } + + // If there's an actual condition create the BP condition object now + + if (*condition && !onlySpaces) + { + Condition* c = generateCondition(condition); + + // If the creation of the BP condition object was succesful + // the condition is apparently valid. It can be added to the + // breakpoint now. + + if (c) + { + watchpoint[num].cond = c; + watchpoint[num].condText = (char*)malloc(strlen(condition) + 1); + strcpy(watchpoint[num].condText, condition); + } + else + { + watchpoint[num].cond = 0; + } + + return watchpoint[num].cond == 0 ? 2 : 0; + } + else + { + return 0; + } +} + +/** +* Returns the bookmark address of a CPU bookmark identified by its index. +* The caller must make sure that the index is valid. +* +* @param hwnd HWND of the debugger window +* @param index Index of the bookmark +**/ +unsigned int getBookmarkAddress(HWND hwnd, unsigned int index) +{ + int n; + char buffer[5] = {0}; + + SendDlgItemMessage(hwnd, 701, LB_GETTEXT, index, (LPARAM)buffer); + + sscanf(buffer, "%x", &n); + + return n; +} + +unsigned int bookmarks; +unsigned short* bookmarkData = 0; + +/** +* Stores all CPU bookmarks in a simple array to be able to store +* them between debugging sessions. +* +* @param hwnd HWND of the debugger window. +**/ +void dumpBookmarks(HWND hwnd) +{ + unsigned int i; + if (bookmarkData) + free(bookmarkData); + + bookmarks = SendDlgItemMessage(hwnd, 701, LB_GETCOUNT, 0, 0); + bookmarkData = (unsigned short*)malloc(bookmarks * sizeof(unsigned short)); + + for (i=0;i 0xFFFF) + { + MessageBox(hwnd, "Invalid offset", "Error", MB_OK | MB_ICONERROR); + return; + } + + AddDebuggerBookmark2(hwnd, buffer); +} + +/** +* Removes a debugger bookmark +* +* @param hwnd HWND of the debugger window +**/ +void DeleteDebuggerBookmark(HWND hwnd) +{ + // Get the selected bookmark + int selectedItem = SendDlgItemMessage(hwnd, 701, LB_GETCURSEL, 0, 0); + + if (selectedItem == LB_ERR) + { + MessageBox(hwnd, "Please select a bookmark from the list", "Error", MB_OK | MB_ICONERROR); + return; + } + else + { + // Remove the selected bookmark + + SendDlgItemMessage(hwnd, 701, LB_DELETESTRING, selectedItem, 0); + dumpBookmarks(hwnd); + } +} + +void Disassemble(HWND hWnd, int id, int scrollid, unsigned int addr); + +/** +* Shows the code at the bookmark address in the disassembly window +* +* @param hwnd HWND of the debugger window +**/ +void GoToDebuggerBookmark(HWND hwnd) +{ + unsigned int n; + int selectedItem = SendDlgItemMessage(hwnd, 701, LB_GETCURSEL, 0, 0); + + // If no bookmark is selected just return + if (selectedItem == LB_ERR) return; + + n = getBookmarkAddress(hwnd, selectedItem); + + //Disassemble(hwnd, 300, 301, n); + //MBG TODO +} diff --git a/drivers/win/debuggersp.h b/drivers/win/debuggersp.h new file mode 100644 index 00000000..e744ea3e --- /dev/null +++ b/drivers/win/debuggersp.h @@ -0,0 +1,46 @@ +/* FCEUXD SP - NES/Famicom Emulator + * + * Copyright notice for this file: + * Copyright (C) 2005 Sebastian Porst + * + * 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 + +//mbg merge 7/17/06 made struct sane c++ +struct Name +{ + Name* next; + char* offset; + char* name; + char* comment; +}; + +extern char symbDebugEnabled; +extern unsigned int bookmarks; +extern unsigned short* bookmarkData; +extern int debuggerWasActive; + +int checkCondition(char* buffer, int num); +void loadNameFiles(); +void decorateAddress(unsigned int addr, char* str, const char* chr, UINT); +void replaceNames(Name* list, char* str); +void AddDebuggerBookmark(HWND hwnd); +void AddDebuggerBookmark2(HWND hwnd, char* buffer); +void DeleteDebuggerBookmark(HWND hwnd); +void GoToDebuggerBookmark(HWND hwnd); +int getBank(int offs); +void dumpBookmarks(HWND hwmd); diff --git a/drivers/win/input.c b/drivers/win/input.c index 0b531dff..279819a1 100644 --- a/drivers/win/input.c +++ b/drivers/win/input.c @@ -1016,6 +1016,7 @@ static BOOL CALLBACK InputConCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARA switch(uMsg) { case WM_INITDIALOG: + SetDlgItemText(hwndDlg,65488,"Select the device you want to be enabled on input ports 1 and 2, and the Famicom expansion port. You may configure the device listed above each drop-down list by pressing \"Configure\", if applicable. The device currently being emulated on the each port is listed above the drop down list; loading certain games will override your settings, but only temporarily. If you select a device to be on the emulated Famicom Expansion Port, you should probably have emulated gamepads on the emulated NES-style input ports."); for(x=0;x<2;x++) { int y; diff --git a/drivers/win/main.c b/drivers/win/main.c index 7acb46fb..698db214 100644 --- a/drivers/win/main.c +++ b/drivers/win/main.c @@ -35,6 +35,7 @@ #include "../../types.h" //mbg merge 7/17/06 added #include "../../fceu.h" //mbg merge 7/17/06 added +#include "../../debugger.h" //mbg merge 7/18/06 added #include "input.h" #include "netplay.h" #include "joystick.h" @@ -166,7 +167,8 @@ static int winwidth,winheight; static int ismaximized = 0; static volatile int nofocus=0; -static volatile int userpause=0; +//static volatile int userpause=0; //mbg merge 7/18/06 removed. this has been replaced with FCEU_EmulationPaused stuff +static volatile int _userpause=0; //mbg merge 7/18/06 changed tasbuild was using this only in a couple of places #define SO_FORCE8BIT 1 #define SO_SECONDARY 2 @@ -351,6 +353,12 @@ void FCEUD_Update(uint8 *XBuf, int32 *Buffer, int Count); void ApplyDefaultCommandMapping(void); +//mbg merge 7/18/06 - the function that contains the code that used to just be UpdateMemWatch() +void _updateMemWatch() { + //UpdateMemWatch() + //but soon we will do more! +} + int main(int argc,char *argv[]) { char *t; @@ -435,7 +443,7 @@ doloopy: if(FCEUI_EmulationPaused() & 1) { if(stopCount==0) - UpdateMemWatch(); + _updateMemWatch(); stopCount++; if(stopCount > 8) @@ -459,7 +467,7 @@ doloopy: } else { - //UpdateMemWatch(); + //_updateMemWatch(); stopCount=0; } } @@ -524,7 +532,7 @@ void FCEUD_Update(uint8 *XBuf, int32 *Buffer, int Count) { skipcount = 0; FCEUD_BlitScreen(XBuf); - UpdateMemWatch(); + _updateMemWatch(); } else skipcount++; @@ -563,7 +571,7 @@ void FCEUD_Update(uint8 *XBuf, int32 *Buffer, int Count) { skipcount = 0; FCEUD_BlitScreen(XBuf); - UpdateMemWatch(); + _updateMemWatch(); } else { @@ -647,7 +655,7 @@ void FCEUD_Update(uint8 *XBuf, int32 *Buffer, int Count) if((!skipthis && !NoWaiting) || (skipcount >= maxskip)) { FCEUD_BlitScreen(XBuf); - UpdateMemWatch(); + _updateMemWatch(); skipcount = 0; } else diff --git a/drivers/win/memview.c b/drivers/win/memview.c new file mode 100644 index 00000000..0c2e80be --- /dev/null +++ b/drivers/win/memview.c @@ -0,0 +1,1668 @@ +/* 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 "..\..\memview.h" +#include "..\..\debugger.h" +#include "..\..\cdlogger.h" +#include "..\..\fceu.h" +#include "..\..\cheat.h" +#include "..\..\cart.h" +#include "..\..\ines.h" +#include "cheat.h" +#include "memviewsp.h" +#include +//#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= 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>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; +} diff --git a/drivers/win/memviewsp.c b/drivers/win/memviewsp.c new file mode 100644 index 00000000..014e0f43 --- /dev/null +++ b/drivers/win/memviewsp.c @@ -0,0 +1,235 @@ +/* FCEUXD SP - NES/Famicom Emulator + * + * Copyright notice for this file: + * Copyright (C) 2005 Sebastian Porst + * + * 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 +#include +#include +#include +#include "memviewsp.h" +#include "common.h" + +HexBookmark hexBookmarks[64]; +int nextBookmark = 0; + +/** +* Finds the bookmark for a given address +* +* @param address The address to find. +* @return The index of the bookmark at that address or -1 if there's no bookmark at that address. +**/ +int findBookmark(unsigned int address) +{ + int i; + + if (address > 0xFFFF) + { + MessageBox(0, "Error: Invalid address was specified as parameter to findBookmark", "Error", MB_OK | MB_ICONERROR); + return -1; + } + + for (i=0;i +#include +#include +#include +#include "debuggersp.h" +#include "memviewsp.h" +#include "common.h" +#include "../../debugger.h" + +extern char symbDebugEnabled; + +/** +* Stores debugger preferences in a file +* +* @param f File to write the preferences to +* @return 0 if everything went fine. An error code if something went wrong. +**/ +int storeDebuggerPreferences(FILE* f) +{ + int i; + + // Flag that says whether symbolic debugging should be enabled + if (fwrite(&symbDebugEnabled, 1, 1, f) != 1) return 1; + + // Write the number of active CPU bookmarks + if (fwrite(&bookmarks, sizeof(unsigned int), 1, f) != 1) return 1; + // Write the addresses of those bookmarks + if (fwrite(bookmarkData, sizeof(unsigned short), bookmarks, f) != bookmarks) return 1; + + // Write all breakpoints + for (i=0;i<65;i++) + { + unsigned int len; + + // Write the start address of a BP + if (fwrite(&watchpoint[i].address, sizeof(watchpoint[i].address), 1, f) != 1) return 1; + // Write the end address of a BP + if (fwrite(&watchpoint[i].endaddress, sizeof(watchpoint[i].endaddress), 1, f) != 1) return 1; + // Write the flags of a BP + if (fwrite(&watchpoint[i].flags, sizeof(watchpoint[i].flags), 1, f) != 1) return 1; + + // Write the length of the BP condition + len = watchpoint[i].condText ? strlen(watchpoint[i].condText) : 0; + if (fwrite(&len, sizeof(len), 1, f) != 1) return 1; + + // Write the text of the BP condition + if (len) + { + if (fwrite(watchpoint[i].condText, 1, len, f) != len) return 1; + } + + len = watchpoint[i].desc ? strlen(watchpoint[i].desc) : 0; + + // Write the length of the BP description + if (fwrite(&len, sizeof(len), 1, f) != 1) return 1; + + // Write the actual BP description + if (len) + { + if (fwrite(watchpoint[i].desc, 1, len, f) != len) return 1; + } + } + + return 0; +} + +/** +* Stores the preferences from the Hex window +* +* @param f File to write the preferences to +* @return 0 if everything went fine. An error code if something went wrong. +**/ +int storeHexPreferences(FILE* f) +{ + int i; + + // Writes the number of bookmarks to save + if (fwrite(&nextBookmark, sizeof(nextBookmark), 1, f) != 1) return 1; + + for (i=0;i + +extern int myNumWPs; + +int storePreferences(char* romname); +int loadPreferences(char* romname); + diff --git a/drivers/win/res.rc b/drivers/win/res.rc index cf31ecb6836b2629a3dc0ef5f69870c270aa9572..a7c76c9c407e11fd4e1d4bf6f23d3803a985ed61 100644 GIT binary patch literal 130594 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 diff --git a/drivers/win/res/ICON_1.ico b/drivers/win/res/ICON_1.ico index 2fed1178b6fa6ccb375a1a02bc8724a51030904a..7b652f4a095f8dd7b66bd4ddb7880c3fc56279e9 100644 GIT binary patch delta 27 fcmdldxKEImfq@YS6%-hNfMX)B9wXyMnF31H(jKJw}F&HccD=I*F31H(jKJw}F&HccD=I*