/** * Mupen64Plus main/gui_gtk/debugger/memedit.c * * Copyright (C) 2008 HyperHacker (at gmail, dot com) * * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ * * This program is free software; you can redistribute it and/ * or modify it under the terms of the GNU General Public Li- * cence as published by the Free Software Foundation; either * version 2 of the Licence. * * This program is distributed in the hope that it will be use- * ful, but WITHOUT ANY WARRANTY; without even the implied war- * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public Licence for more details. * * You should have received a copy of the GNU General Public * Licence along with this program; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, * USA. * **/ #include "memedit.h" #include #include GtkTextBuffer *textbuf; GtkTextTag *textfont; //note: just changing these constants is not (yet) enough to properly change the display. const uint32 num_menu_items = 6; const int bytesperline = 16; const int hexstartpos = 9; //apparently the compiler isn't smart enough to figure out that these equations are constant. -_- const int linelen = 74; //hexstartpos + (bytesperline * 3) + bytesperline + 1; //address, separator, hex, text, line break (hex/text separator included in hex) const int textstartpos = 57; //hexstartpos + (bytesperline * 3); //address, separator, hex uint32 memedit_address, memedit_numlines; GtkWidget *menu; GtkWidget* menuitem[6]; //DO NOT FORGET TO UPDATE THIS WHEN YOU ADD ITEMS GtkClipboard* clipboard; static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data); static gboolean on_mouse_down(GtkWidget *widget, GdkEventButton *event, gpointer user_data); static void on_menu_select(GtkMenuItem *menuitem, gpointer user_data); static void on_auto_refresh_toggle(GtkToggleButton *togglebutton, gpointer user_data); static void on_close(); void update_memory_editor() { int i, j, k; char line[linelen + 1]; char* buf; uint32 addr; uint8 byte[bytesperline]; GtkTextIter textstart, textend; buf = (char*)malloc(memedit_numlines * sizeof(line) * sizeof(char)); if(!buf) { printf("update_memory_editor: out of memory at %s:%u\n", __FILE__, __LINE__); return; } //Read memory //todo: display 'XX' or something for unreadable memory, //maybe colour the text addr = memedit_address; buf[0] = 0; for(i=0; i= 0x20) && (byte[j] <= 0x7E)) line[j] = byte[j]; else line[j] = '.'; } if(i < (memedit_numlines - 1)) { line[bytesperline] = '\n'; line[bytesperline + 1] = 0; } else line[bytesperline] = 0; strcat(buf, line); addr += bytesperline; } //Update textbox gtk_text_buffer_set_text(textbuf, buf, -1); gtk_text_buffer_get_bounds(textbuf, &textstart, &textend); //todo: there must be a better way to keep it in monospace gtk_text_buffer_apply_tag(textbuf, textfont, &textstart, &textend); free(buf); } //]=-=-=-=-=-=-=-=-=-=-=-=[ Memory Editor Initialisation ]=-=-=-=-=-=-=-=-=-=-=-=[ void init_memedit() { int i; GtkWidget *textbox, *hbox, *vbox, *buAutoRefresh; memedit_address = 0x80000000; memedit_numlines = 16; memedit_auto_update = 0; //=== Creation of Memory Editor ===========/ //Create the window. winMemEdit = gtk_window_new( GTK_WINDOW_TOPLEVEL ); gtk_window_set_title( GTK_WINDOW(winMemEdit), "Memory"); gtk_window_set_default_size( GTK_WINDOW(winMemEdit), 500, 200); gtk_container_set_border_width( GTK_CONTAINER(winMemEdit), 2); //Create the containers. hbox = gtk_hbox_new(FALSE, 0); //hbox holds the buttons at the bottom. vbox = gtk_vbox_new(FALSE, 0); //vbox holds the textbox and hbox. //Create the textbox. textbox = gtk_text_view_new(); textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textbox)); textfont = gtk_text_buffer_create_tag(textbuf, "font", "font", "monospace", NULL); update_memory_editor(); //Add textbox to container. gtk_box_pack_start(GTK_BOX(vbox), textbox, TRUE, TRUE, 0); //Create buttons and add to container. buAutoRefresh = gtk_check_button_new_with_label("Auto Refresh"); gtk_box_pack_start(GTK_BOX(hbox), buAutoRefresh, FALSE, FALSE, 0); //Add container to window. gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(winMemEdit), vbox); //Get handle to clipboard. clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); //Create right-click menu menu = gtk_menu_new(); menuitem[0] = gtk_menu_item_new_with_mnemonic("_Copy"); menuitem[1] = gtk_menu_item_new_with_mnemonic("Copy as _Binary"); menuitem[2] = gtk_menu_item_new_with_mnemonic("Copy as _ASCII"); menuitem[3] = gtk_menu_item_new_with_mnemonic("Add _Read Breakpoint"); menuitem[4] = gtk_menu_item_new_with_mnemonic("Add _Write Breakpoint"); menuitem[5] = gtk_menu_item_new_with_mnemonic("_Disable Breakpoints"); for(i=0; ikeyval) { case GDK_0: case GDK_1: case GDK_2: case GDK_3: case GDK_4: case GDK_5: case GDK_6: case GDK_7: case GDK_8: case GDK_9: key = event->keyval - GDK_0; break; case GDK_KP_0: case GDK_KP_1: case GDK_KP_2: case GDK_KP_3: case GDK_KP_4: case GDK_KP_5: case GDK_KP_6: case GDK_KP_7: case GDK_KP_8: case GDK_KP_9: key = event->keyval - GDK_KP_0; break; case GDK_A: case GDK_B: case GDK_C: case GDK_D: case GDK_E: case GDK_F: key = (event->keyval - GDK_A) + 10; break; case GDK_a: case GDK_b: case GDK_c: case GDK_d: case GDK_e: case GDK_f: key = (event->keyval - GDK_a) + 10; break; default: key = event->keyval; break; } //Get the cursor position. cursor = gtk_text_buffer_get_insert(textbuf); gtk_text_buffer_get_iter_at_mark(textbuf, &cursor_iter, cursor); cursorpos = gtk_text_iter_get_offset(&cursor_iter); //React to the keypress. //todo: skip between-bytes and separator areas when //navigating with arrow keys or mouse. if(key == GDK_Up) { cursorpos -= linelen; if(cursorpos < 0) { memedit_address -= bytesperline; cursorpos += linelen; } } else if(key == GDK_Down) { cursorpos += linelen; if(cursorpos >= (linelen * memedit_numlines)) { memedit_address += bytesperline; cursorpos -= linelen; } } else if(key == GDK_Left) { if(cursorpos) cursorpos--; } else if(key == GDK_Right) { if(cursorpos < (linelen * memedit_numlines)) cursorpos++; } else { //Determine where we are. linenum = cursorpos / linelen; linepos = cursorpos % linelen; if((event->keyval != GDK_BackSpace) && ((linepos == (hexstartpos - 1)) || (linepos == (textstartpos - 1)))) //address/hex or hex/text separators { linepos++; cursorpos++; } //printf("line %u, pos %u: ", linenum, linepos); if(linepos >= (linelen - 1)); //end of line; do nothing //If cursor is in address else if(linepos < (hexstartpos - 1)) { if(key < 16) //hex number entered { //yes, I probably _could_ have made this line uglier. memedit_address = (memedit_address & ~(0xF << (4 * (7 - linepos)))) | (key << (4 * (7 - linepos))); cursorpos++; if((cursorpos % linelen) == (hexstartpos - 1)) cursorpos++; //skip address/hex separator } else if(linepos && (event->keyval == GDK_BackSpace)) //back one character { cursorpos--; if(cursorpos < 0) { memedit_address -= bytesperline; cursorpos++; } } //todo: else, beep or something } //If cursor is in text else if(linepos >= (textstartpos - 1)) { //If a non-special key, except Enter, was pressed if((event->keyval <= 0xFF) || (event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter)) { linepos -= textstartpos; //Character index addr = memedit_address + (linenum * bytesperline) + linepos; //Address to edit if(event->keyval <= 0xFF) byte = event->keyval; else if((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter)) byte = 0x0A; //Enter inserts line break write_memory_8(addr, byte); cursorpos++; if((cursorpos % linelen) == (linelen - 1)) //wrote past last character { cursorpos += (textstartpos + 1); //to first char on next line (+1 for line break) if(cursorpos >= (linelen * memedit_numlines)) //past end of box { memedit_address += bytesperline; cursorpos -= linelen; } } } else if(event->keyval == GDK_BackSpace) //back one character { cursorpos--; if((cursorpos % linelen) < textstartpos) //before first character { cursorpos -= (textstartpos + 1); //to last char on prev line if(cursorpos < 0) { memedit_address -= bytesperline; cursorpos += linelen; } } } } //If cursor is in hex else { linepos -= (hexstartpos - 1); if((event->keyval != GDK_BackSpace) && !(linepos % 3)) //between bytes { cursorpos++; linepos++; } addr = memedit_address + (linenum * bytesperline) + (linepos / 3); //printf("byte %u (%08X) nybble %u, ", linepos / 3, addr, (linepos % 3) - 1); if(key < 16) //hex number entered { byte = read_memory_8(addr); //printf("%02X -> ", byte); if((linepos % 3) - 1) //low nybble { byte = (byte & 0xF0) | (uint8)key; cursorpos++; //skip the between-bytes area } else byte = (byte & 0x0F) | ((uint8)key << 4); //printf("%02X\n", byte); write_memory_8(addr, byte); cursorpos++; if((cursorpos % linelen) == textstartpos) //wrote past last byte { cursorpos += bytesperline + 10; //to first byte on next line - skip text (bytesperline chars), line break, address (8 chars), separator if(cursorpos >= (linelen * memedit_numlines)) //past end of box { memedit_address += 16; cursorpos -= linelen; } } } //if hex number entered else if(event->keyval == GDK_BackSpace) //back one character { if(linepos < 2) cursorpos -= linepos + 1; //end of address (before/after separator) else { cursorpos--; if(!(((cursorpos % linelen) - (hexstartpos - 1)) % 3)) cursorpos--; //between bytes if((cursorpos % linelen) < hexstartpos) //before first character { cursorpos -= (hexstartpos + bytesperline + 1); //to last char on prev line if(cursorpos < 0) { memedit_address -= bytesperline; cursorpos += linelen; } } } } //if backspace } //if in hex } //0.........1.........2.........3.........4.........5.........6.........7.. //|00 01 02 03|04 05 06 07|08 09 0A 0B|0C 0D 0E 0F|0123456789ABCDEF //80000000|00 01 02 03|04 05 06 07|08 09 0A 0B|0C 0D 0E 0F|0123456789ABCDEF update_memory_editor(); //Restore the cursor position. gtk_text_buffer_get_iter_at_offset(textbuf, &cursor_iter, cursorpos); gtk_text_buffer_place_cursor(textbuf, &cursor_iter); return TRUE; } static gboolean on_mouse_down(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { if(event->button != 3) return FALSE; gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, 0, event->button, event->time); return TRUE; } static void on_menu_select(GtkMenuItem *menuitem, gpointer user_data) { gchar* text; GtkTextIter start, end; uint32 startaddr, endaddr, numbytes; uint8 byte; int i; breakpoint newbp; switch((long)user_data) { case 0: //Copy if(!gtk_text_buffer_get_selection_bounds(textbuf, &start, &end)) break; //nothing selected if false text = gtk_text_iter_get_text(&start, &end); gtk_clipboard_set_text(clipboard, text, -1); break; case 1: //Copy as Binary //TODO - not sure how to put raw binary data on clipboard <_< error_message(tr("not implemented yet")); break; case 2: //Copy as ASCII numbytes = GetMemEditSelectionRange(&startaddr, 0); if(!numbytes) break; text = (gchar*)malloc((numbytes + 1) * sizeof(gchar)); if(!text) break; for(i=0; i= 0x20) && (byte <= 0x7E)) text[i] = byte; else text[i] = '.'; startaddr++; } text[i] = 0; gtk_clipboard_set_text(clipboard, text, i); free(text); break; case 3: //Break on read case 4: //Break on write numbytes = GetMemEditSelectionRange(&startaddr, 1); if(!numbytes) break; endaddr = startaddr + (numbytes - 1); //see if there's already a breakpoint for this range, //and if so, just add the read/write flag. for(i=0; i= startaddr)) || ((g_Breakpoints[i].address <= endaddr) && (g_Breakpoints[i].endaddr >= endaddr))) { g_Breakpoints[i].flags &= ~BPT_FLAG_ENABLED; printf("Disabled breakpoint %d.\n", i); } } update_breakpoints(); break; } } //Auto-refresh checkbox handler. static void on_auto_refresh_toggle(GtkToggleButton *togglebutton, gpointer user_data) { memedit_auto_update = gtk_toggle_button_get_active(togglebutton) ? 1 : 0; } static void on_close() { memedit_opened = 0; } //Determines range of bytes selected. //Returns # of bytes selected, sets StartAddr to address of first byte if any. //A byte is considered selected if the selection covers either the hex OR text view of it. //In the hex view, a byte is considered selected if either nybble and/or the space/pipe //after it is selected. I consider this a good thing, as it means if you accidentally don't //select an entire byte, it still gets copied. //When AllowEmpty is set, if there is no selection, but the cursor is in the hex or text, //the byte next to it is considered selected. int GetMemEditSelectionRange(uint32* StartAddr, int AllowEmpty) { GtkTextIter start, end; GtkTextMark* cursor; int startpos, endpos, startline, endline, startlinepos, endlinepos, numbytes; if(!memedit_opened) return 0; //Determine what byte range is selected. if(gtk_text_buffer_get_selection_bounds(textbuf, &start, &end)) { startpos = gtk_text_iter_get_offset(&start); endpos = gtk_text_iter_get_offset(&end); } else //nothing selected { if(!AllowEmpty) return 0; cursor = gtk_text_buffer_get_insert(textbuf); gtk_text_buffer_get_iter_at_mark(textbuf, &start, cursor); startpos = gtk_text_iter_get_offset(&start); endpos = startpos + 1; } //printf("selected %d - %d\n", startpos, endpos); startline = startpos / linelen; startlinepos = startpos % linelen; endline = endpos / linelen; endlinepos = endpos % linelen; if((startlinepos == (textstartpos - 1)) && ((endpos - startpos) == 1)) return 0; //Only hex/text separator selected if(startlinepos >= (linelen - 1)) //past end of line { startline++; startlinepos = hexstartpos; } if(endlinepos <= hexstartpos) //before hex { endline--; endlinepos = textstartpos - 1; } //Quiet, it works. if(startline > endline) return 0; //no actual hex/text selected //else if((startline == endline) && ((endlinepos - startlinepos) < 2) && (startlinepos < (textstartpos - 1))) return 0; //no entire bytes if(startlinepos >= textstartpos) //selection begins in text (*StartAddr) = (startline * bytesperline) + (startlinepos - textstartpos); else (*StartAddr) = (startline * bytesperline) + ((startlinepos - hexstartpos) / 3); if(endlinepos >= textstartpos) //selection ends in text numbytes = ((endline * bytesperline) + (endlinepos - textstartpos)) - (*StartAddr); else numbytes = ((endline * bytesperline) + (int)ceil((float)(endlinepos - hexstartpos) / 3.0)) - (*StartAddr); (*StartAddr) += memedit_address; //printf("%u bytes from %08X\n", numbytes, (*StartAddr)); return numbytes; }