/** * Mupen64Plus main/gui_gtk/debugger/varlist.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 "varlist.h" #include //#include enum { //Treeview columns Col_Name, //Name of the variable. Col_Value, //Current value. Col_Address, //RAM address. Col_Type, //Type/size. Col_Offset, //For pointers, address=(*address)+offset Num_Columns }; enum { //Variable types Type_int8, Type_int16, Type_int32, Type_int64, Type_uint8, Type_uint16, Type_uint32, Type_uint64, Type_hex8, Type_hex16, Type_hex32, Type_hex64, Type_float, Type_double, Num_Types, Type_pointer = 0x80 //flag, OR with one of above values }; const char* type_name[Num_Types] = { "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "hex8", "hex16", "hex32", "hex64", "float", "double" }; const char* col_name[Num_Columns] = { "Name", "Value", "Address", "Type", "Offset" }; int iter_stamp; GtkWidget *tree; GtkTreeStore *store; GtkTreeSelection *tree_selection; GtkCellRenderer *cell_renderer[Num_Columns]; void value_data_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data); void address_data_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data); void offset_data_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data); void type_data_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data); static void on_add(); static void on_remove(); static void on_import(); static void on_cell_edit(GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data); static void on_auto_refresh_toggle(GtkToggleButton *togglebutton, gpointer user_data); static void on_close(); static void add_value(char* name, uint32 address, int type, uint32 offset); static void import_file(char* filename); void update_varlist() { gtk_widget_queue_draw(tree); } //]=-=-=-=-=-=-=-=-=-=-=-=[ Variable List Initialisation ]=-=-=-=-=-=-=-=-=-=-=-=[ void init_varlist() { int i; GtkTreeViewColumn *col[Num_Columns]; GtkWidget *hbox, *vbox, *buAdd, *buRemove, *buAutoRefresh, *buImport; GValue cell_editable, type_val, model_val, model_col; GtkTreeStore *type_list; GtkTreeIter iter; varlist_auto_update = 0; iter_stamp = 0xD06FECE5; //unique stamp value (increments) //Create the window. winVarList = gtk_window_new( GTK_WINDOW_TOPLEVEL ); gtk_window_set_title( GTK_WINDOW(winVarList), "Variables"); gtk_window_set_default_size( GTK_WINDOW(winVarList), 300, 380); gtk_container_set_border_width( GTK_CONTAINER(winVarList), 2); //Create the containers. hbox = gtk_hbox_new(FALSE, 0); //hbox holds the treeview and hbox. vbox = gtk_vbox_new(FALSE, 0); //vbox holds the buttons at the right. //hbox, vbox, no xbox? //Create the treeview. store = gtk_tree_store_new(Num_Columns, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); //Create the cell renderers. memset(&cell_editable, 0, sizeof(cell_editable)); g_value_init(&cell_editable, G_TYPE_BOOLEAN); g_value_set_boolean(&cell_editable, TRUE); for(i=0; idata)) break; gtk_tree_store_remove(store, &iter); element = g_list_previous(element); } g_list_foreach(selected, (GFunc)gtk_tree_path_free, NULL); g_list_free(selected); } //'Import' button handler. static void on_import() { GtkWidget *dialog; char* filename; //Display file selector //This is currently broken so I've hardcoded a path. //import_file("/home/hyperhacker/Desktop/mupenvars.txt"); //return; dialog = gtk_file_chooser_dialog_new("Import Variable List", GTK_WINDOW(winVarList), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); //dialog = gtk_file_chooser_dialog_new(NULL, NULL, GTK_FILE_CHOOSER_ACTION_OPEN, NULL); if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); import_file(filename); g_free(filename); } gtk_widget_destroy(dialog); } //Cell edit handler. //Todo: why is this getting called on double-click when the text box hasn't //popped up yet? static void on_cell_edit(GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data) { int i, j; GtkTreeIter iter; GValue newval, val_addr, val_type, val_offset; uint32 addr, type, value, offset; uint64 value64; float valuef; double valued; char formatted[18]; //for reading hex64 values gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path); memset(&newval, 0, sizeof(newval)); memset(&val_addr, 0, sizeof(val_addr)); memset(&val_type, 0, sizeof(val_type)); memset(&val_offset, 0, sizeof(val_offset)); switch((long)user_data) //column number { case Col_Name: g_value_init(&newval, G_TYPE_STRING); g_value_set_string(&newval, new_text); break; case Col_Value: //Get address gtk_tree_model_get_value(GTK_TREE_MODEL(store), &iter, Col_Address, &val_addr); addr = g_value_get_int(&val_addr); //Get type gtk_tree_model_get_value(GTK_TREE_MODEL(store), &iter, Col_Type, &val_type); type = g_value_get_int(&val_type); //Get offset gtk_tree_model_get_value(GTK_TREE_MODEL(store), &iter, Col_Offset, &val_offset); offset = g_value_get_int(&val_offset); //If pointer, follow it. if(type & Type_pointer) { type &= ~Type_pointer; addr = read_memory_32(addr) + offset; } //Get new value and write. //Todo: should not write if value is invalid, or strip any non- //numeric characters from value, to avoid the annoying bug where //you make a typo and it gets set to zero. //If we just copy the current value into the variables sscanf() is //supposed to set, it won't change them when the format string is //invalid. Not sure what happens if it's too long. switch(type) { case Type_int8: sscanf(new_text, "%d", &value); write_memory_8(addr, value); break; case Type_int16: sscanf(new_text, "%d", &value); write_memory_16(addr, value); break; case Type_int32: sscanf(new_text, "%d", &value); write_memory_32(addr, value); break; case Type_int64: sscanf(new_text, "%" PRId64, &value64); write_memory_64(addr, value64); break; case Type_uint8: sscanf(new_text, "%u", &value); write_memory_8(addr, value); break; case Type_uint16: sscanf(new_text, "%u", &value); write_memory_16(addr, value); break; case Type_uint32: sscanf(new_text, "%u", &value); write_memory_32(addr, value); break; case Type_uint64: sscanf(new_text, "%" PRIu64, &value64); write_memory_64(addr, value64); break; case Type_hex8: sscanf(new_text, "%X", &value); write_memory_8(addr, value); break; case Type_hex16: sscanf(new_text, "%X", &value); write_memory_16(addr, value); break; case Type_hex32: sscanf(new_text, "%X", &value); write_memory_32(addr, value); break; case Type_hex64: //Copy new text without spaces so it can be parsed correctly. j = 0; for(i=0; new_text[i]; i++) { if(new_text[i] == ' ') continue; formatted[j] = new_text[i]; j++; } formatted[j] = '\0'; sscanf(formatted, "%" PRIX64, &value64); write_memory_64(addr, value64); break; case Type_float: //todo: the value needs to be converted to IEEE 754 somehow. sscanf(new_text, "%f", &valuef); write_memory_32(addr, (int)value); break; case Type_double: sscanf(new_text, "%lf", &valued); write_memory_64(addr, (uint64)value); break; default: printf("on_cell_edit(): unknown type %d in \"%s\", col %d\n", type, path, (int)(long)user_data); return; break; } break; case Col_Address: g_value_init(&newval, G_TYPE_INT); sscanf(new_text, "%X", &addr); g_value_set_int(&newval, addr); break; case Col_Type: //todo - this should actually be a dropdown list, not editable, //if I had any idea how to do that. if(strlen(new_text) > 2) return; //so "float" doesn't get parsed as 0xF addr = 0x7F; g_value_init(&newval, G_TYPE_INT); sscanf(new_text, "%X", &addr); if((addr & 0x7F) >= Num_Types) return; g_value_set_int(&newval, addr); break; case Col_Offset: g_value_init(&newval, G_TYPE_INT); sscanf(new_text, "%X", &addr); g_value_set_int(&newval, addr); break; } if((long)user_data != Col_Value) gtk_tree_store_set_value(store, &iter, (int)(long)user_data, &newval); } //Auto-refresh checkbox handler. static void on_auto_refresh_toggle(GtkToggleButton *togglebutton, gpointer user_data) { varlist_auto_update = gtk_toggle_button_get_active(togglebutton) ? 1 : 0; } //Called when window is closed. static void on_close() { varlist_opened = 0; } //Add a value to the variable list. static void add_value(char* name, uint32 address, int type, uint32 offset) { GValue nameval, addrval, typeval, offsetval; GtkTreeIter iter; iter.stamp = iter_stamp; iter_stamp++; //We store the variable info in the treeview itself. //Believe it or not, it's easier that way. memset(&nameval, 0, sizeof(nameval)); memset(&addrval, 0, sizeof(addrval)); memset(&typeval, 0, sizeof(typeval)); memset(&offsetval, 0, sizeof(offsetval)); g_value_init(&nameval, G_TYPE_STRING); g_value_init(&addrval, G_TYPE_INT); g_value_init(&typeval, G_TYPE_INT); g_value_init(&offsetval, G_TYPE_INT); g_value_set_string(&nameval, name); g_value_set_int(&addrval, address); g_value_set_int(&typeval, type); g_value_set_int(&offsetval, offset); gtk_tree_store_append(store, &iter, NULL); gtk_tree_store_set_value(store, &iter, Col_Name, &nameval); //We don't bother to set a value for the "value" column, it gets //filled in on refresh. gtk_tree_store_set_value(store, &iter, Col_Address, &addrval); gtk_tree_store_set_value(store, &iter, Col_Type, &typeval); gtk_tree_store_set_value(store, &iter, Col_Offset, &offsetval); } //Imports a variable list from a file. static void import_file(char* filename) { FILE *file; char line[1024]; char *data, *next; int type = 0; uint32 address = 0x80000000, offset = 0; file = fopen(filename, "rt"); if(!file) { //todo: show error message sprintf(line, "Cannot open file \"%s\"\n", filename); error_message(tr(line)); return; } while(!feof(file)) { fgets(line, sizeof(line), file); if((line[0] == '\n') || (line[0] == ';') || (line[0] == '#') || (line[0] == '\0')) continue; //Get type. //This is fairly lazy checking that doesn't check for invalid types. next = strchr(line, ' '); if(!next) continue; next[0] = '\0'; if((line[0] == 'f') || (line[0] == 'F')) type = Type_float; else if((line[0] == 'd') || (line[0] == 'D')) type = Type_double; else { switch(line[1]) { case '8': type = Type_int8; break; case '1': type = Type_int16; break; case '3': type = Type_int32; break; case '6': type = Type_int64; break; } if((line[0] == 'u') || (line[0] == 'U')) type += 4; //unsigned else if((line[0] == 'h') || (line[0] == 'H')) type += 8; //hex } if(next[-1] == '*') //pointer type |= Type_pointer; //Get address. data = &next[1]; next = strchr(data, ' '); if(!next) continue; next[0] = '\0'; sscanf(data, "%X", &address); //Get offset if pointer. if(type & Type_pointer) { data = &next[1]; next = strchr(data, ' '); if(!next) continue; next[0] = '\0'; sscanf(data, "%X", &offset); } else offset = 0; //Strip line break. data = &next[1]; next = strchr(data, '\n'); if(next) (*next) = '\0'; //Add to list. add_value(data, address, type, offset); } fclose(file); }