/* PokeMini - Pokémon-Mini Emulator Copyright (C) 2009-2015 JustBurn 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 3 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, see . */ #include #include #include #include #include #include #include #include "SDL.h" #include "PokeMini.h" #include "PokeMini_Debug.h" #include "Hardware_Debug.h" #include "CPUWindow.h" #include "MemWindow.h" #include "PokeMiniIcon_96x128.h" #include "InstructionProc.h" #include "InstructionInfo.h" #include #include "GtkXDialogs.h" #include "SGtkXDrawingView.h" static int MemWindow_InConfigs = 0; GtkWindow *MemWindow; static int MemWindow_minimized; static GtkItemFactory *ItemFactory; static GtkAccelGroup *AccelGroup; static GtkBox *VBox1; static GtkMenuBar *MenuBar; static GtkLabel *LabelRunFull; static SGtkXDrawingView MemView; static int CustomFont8x12Display = 0; static uint32_t CustomFont8x12Addr = 0; static uint8_t CustomFont8x12[8*12*256]; void WriteToPMMem(uint32_t addr, uint8_t data) { if (addr < 0x1000) { // BIOS PM_BIOS[addr & 4095] = data; } else if (addr < 0x2000) { // RAM PM_RAM[addr & 4095] = data; } else if (addr < 0x2100) { // Hardware IO MinxCPU_OnWrite(0, addr, data); } else { // ROM PM_ROM[addr & PM_ROM_Mask] = data; } } static void GenerateFont8x12FromAddr(uint8_t *CustomFont8x12, uint32_t addr) { int ch, tx; uint8_t dat, *ptr; memset(CustomFont8x12, 0, 8*12*256); for (ch=0; ch<256; ch++) { ptr = &CustomFont8x12[(ch >> 4) * 1536 + (ch & 15) * 8 + 2 * 128]; for (tx=0; tx<8; tx++) { dat = MinxCPU_OnRead(0, addr++); ptr[0*128+tx] = dat & 0x01; ptr[1*128+tx] = dat & 0x02; ptr[2*128+tx] = dat & 0x04; ptr[3*128+tx] = dat & 0x08; ptr[4*128+tx] = dat & 0x10; ptr[5*128+tx] = dat & 0x20; ptr[6*128+tx] = dat & 0x40; ptr[7*128+tx] = dat & 0x80; } } } static void GenerateFont8x12FromPtr(uint8_t *CustomFont8x12, uint8_t *fontptr) { int ch, tx; uint8_t dat, *ptr; memset(CustomFont8x12, 0, 8*12*256); for (ch=0; ch<256; ch++) { ptr = &CustomFont8x12[(ch >> 4) * 1536 + (ch & 15) * 8 + 2 * 128]; for (tx=0; tx<8; tx++) { dat = *fontptr++; ptr[0*128+tx] = dat & 0x01; ptr[1*128+tx] = dat & 0x02; ptr[2*128+tx] = dat & 0x04; ptr[3*128+tx] = dat & 0x08; ptr[4*128+tx] = dat & 0x10; ptr[5*128+tx] = dat & 0x20; ptr[6*128+tx] = dat & 0x40; ptr[7*128+tx] = dat & 0x80; } } } // ------- // Viewers // ------- // Memory view go to addr static void MemView_GotoAddr(uint32_t addr, int highlight) { int set_line = addr >> 4; // Don't do anything if it's outside range if ((addr < 0) || (addr > PM_ROM_Size)) return; // Highlight address if (highlight) { MemView.highlight_addr = addr; MemView.highlight_rem = 16; } // Try to point at the top if ((set_line < MemView.sboffset) || (set_line >= (MemView.sboffset + MemView.total_lines - 1))) { sgtkx_drawing_view_sbvalue(&MemView, set_line - (MemView.total_lines >> 1)); } } // Memory view exposure event static int MemView_exposure(SGtkXDrawingView *widg, int width, int height, int pitch) { uint32_t color; uint8_t dat, sdat, watch; int x, y, pp, spp; int yc, xs = -1, ys = -1; // Calculate height and selected item yc = height / 12; if ((widg->mouseinside) && (widg->mousey >= 12)) { if ((widg->mousex >= 72) && (widg->mousex < 392)) { xs = (widg->mousex - 72) / 20; ys = (widg->mousey - 12) / 12; } if ((widg->mousex >= 392) && (widg->mousex < 520)) { xs = (widg->mousex - 392) / 8; ys = (widg->mousey - 12) / 12; } } // Decrement highlighting remain timer if (MemView.highlight_rem) { MemView.highlight_rem--; sgtkx_drawing_view_repaint_after(&MemView, 250); } // Draw content pp = spp = widg->sboffset * 16; for (y=1; ywidth, 12, color); if (MemView.highlight_rem && ((MemView.highlight_addr & ~15) == pp)) { dat = MemView.highlight_addr & 15; sgtkx_drawing_view_drawfrect(widg, 72+dat*20, y * 12, 16, 12, 0xFFE060); sgtkx_drawing_view_drawfrect(widg, 392+dat*8, y * 12, 8, 12, 0xFFE060); } if (((y-1) == ys) && (xs >= 0)) { sgtkx_drawing_view_drawfrect(widg, 72+xs*20, y * 12, 16, 12, color - 0x181818); sgtkx_drawing_view_drawfrect(widg, 392+xs*8, y * 12, 8, 12, color - 0x181818); } if (pp >= PM_ROM_Size) continue; sgtkx_drawing_view_drawtext(widg, 4, y * 12, 0x4C3000, "$%06X:", pp); for (x=0; x<16; x++) { dat = MinxCPU_OnRead(0, pp); sdat = (dat < 0x01) ? '.' : dat; watch = PMD_TrapPoints[pp & PM_ROM_Mask] & TRAPPOINT_WATCH; if (watch) { sgtkx_drawing_view_drawfrect(widg, 72 + x * 20, y * 12, 16, 12, AnyView_TrapColor[watch]); } sgtkx_drawing_view_drawtext(widg, 72 + x * 20, y * 12, 0x4C4C00, "%02X", (int)dat); if (CustomFont8x12Display) sgtkx_drawing_view_setfont(CustomFont8x12); sgtkx_drawing_view_drawchar(widg, 392 + x * 8, y * 12, 0x304C00, sdat); sgtkx_drawing_view_setfont(NULL); pp++; } } // Draw top bar if (emumode != EMUMODE_RUNFULL) { sgtkx_drawing_view_drawfrect(widg, 0, 0, widg->width, 12, 0xD8D8CC); } else { sgtkx_drawing_view_drawfrect(widg, 0, 0, widg->width, 12, 0x68685C); } pp = spp + ys * 16 + xs; if ((ys >= 0) && (pp < PM_ROM_Size)) { dat = MinxCPU_OnRead(0, pp); sgtkx_drawing_view_drawtext(widg, 4, 0, 0x00004C, "Memory Content [ $%04X => $%02X, %i ]", pp, dat, dat); } else { sgtkx_drawing_view_drawtext(widg, 4, 0, 0x00004C, "Memory Content"); } return 1; } // Memory view button press static int MemView_buttonpress(SGtkXDrawingView *widg, int button, int press, int _c) { int result = 0, pp; int xs = -1, ys = -1; // Calculate selected item if (emumode == EMUMODE_RUNFULL) return 0; if ((!widg->mouseinside) || (widg->mousey < 12)) return 0; if ((widg->mousex >= 72) && (widg->mousex < 392)) { xs = (widg->mousex - 72) / 20; ys = (widg->mousey - 12) / 12; } else if ((widg->mousex >= 392) && (widg->mousex < 520)) { xs = (widg->mousex - 392) / 8; ys = (widg->mousey - 12) / 12; } else return 0; set_emumode(EMUMODE_STOP, 1); // Process selected item pp = widg->sboffset * 16 + ys * 16 + xs; if (button == SGTKXDV_BLEFT) { if (pp < PM_ROM_Size) { AnyView_NewValue_CD[1].number = (int)MinxCPU_OnRead(0, pp); AnyView_NewValue_CD[3].number = PMD_TrapPoints[pp] & TRAPPOINT_WATCHREAD; AnyView_NewValue_CD[4].number = PMD_TrapPoints[pp] & TRAPPOINT_WATCHWRITE; sprintf(AnyView_NewValue_CD[0].text, "Set new value for $%04X:", pp); result = CustomDialog(MemWindow, "Change memory", AnyView_NewValue_CD); if (result == 1) { WriteToPMMem(pp, AnyView_NewValue_CD[1].number); PMD_TrapPoints[pp] &= ~TRAPPOINT_WATCH; if (AnyView_NewValue_CD[3].number) PMD_TrapPoints[pp] |= TRAPPOINT_WATCHREAD; if (AnyView_NewValue_CD[4].number) PMD_TrapPoints[pp] |= TRAPPOINT_WATCHWRITE; refresh_debug(1); } else if (result == -1) { MessageDialog(MemWindow, "Invalid number", "Change register", GTK_MESSAGE_ERROR, NULL); } } } set_emumode(EMUMODE_RESTORE, 1); return 1; } // -------------- // Menu callbacks // -------------- static void MemW_Goto_Address(GtkWidget *widget, gpointer data) { int val = MemView.sboffset * 16; set_emumode(EMUMODE_STOP, 1); if (EnterNumberDialog(MemWindow, "Go to address", "Set address to top of view:", &val, val, 6, 1, 0, PM_ROM_Size-1)) { MemView_GotoAddr(val, 1); } set_emumode(EMUMODE_RESTORE, 1); } static void MemW_Goto_SAddress(GtkWidget *widget, gpointer data) { int addr = (int)data; sgtkx_drawing_view_sbvalue(&MemView, addr/16); } static void MemW_Goto_BAddress(GtkWidget *widget, gpointer data) { int val = MemView.sboffset * 16 / 0x8000; int mbanks = PM_ROM_Mask / 0x8000; set_emumode(EMUMODE_STOP, 1); if (EnterNumberDialog(MemWindow, "Go to bank", "Set bank to top of view:", &val, val, 2, 0, 0, mbanks)) { if (val) sgtkx_drawing_view_sbvalue(&MemView, val*0x8000/16); else sgtkx_drawing_view_sbvalue(&MemView, 0x2100/16); } set_emumode(EMUMODE_RESTORE, 1); } static void MemW_Break_AddWPAt(GtkWidget *widget, gpointer data) { int i, num; set_emumode(EMUMODE_STOP, 1); AnyView_AddWPAt_CD[1].number = 0x1000; AnyView_AddWPAt_CD[3].number = 1; AnyView_AddWPAt_CD[5].number = 1; AnyView_AddWPAt_CD[6].number = 1; if (CustomDialog(MainWindow, "Add watchpoing at...", AnyView_AddWPAt_CD)) { for (i=0; i= PM_ROM_Size)) { MessageDialog(MemWindow, "Address out of range", "Add watchpoint at...", GTK_MESSAGE_ERROR, NULL); set_emumode(EMUMODE_RESTORE, 1); return; } PMD_TrapPoints[num] &= ~TRAPPOINT_WATCH; if (AnyView_AddWPAt_CD[5].number) PMD_TrapPoints[num] |= TRAPPOINT_WATCHREAD; if (AnyView_AddWPAt_CD[6].number) PMD_TrapPoints[num] |= TRAPPOINT_WATCHWRITE; } refresh_debug(1); } set_emumode(EMUMODE_RESTORE, 1); } static void MemW_Break_DelAllWP(GtkWidget *widget, gpointer data) { int i; for (i=0; i> 8); else WriteToPMMem(MemView_FillData16[1].number + i, dat & 0xFF); } } set_emumode(EMUMODE_RESTORE, 1); } static void MemW_RefreshNow(GtkWidget *widget, gpointer data) { refresh_debug(1); } static void MemW_Refresh(GtkWidget *widget, gpointer data) { int val, index = (int)data; static int lastrefrindex = -2; if (lastrefrindex == index) return; lastrefrindex = index; if (MemWindow_InConfigs) return; if (index >= 0) { dclc_memwin_refresh = index; } else { set_emumode(EMUMODE_STOP, 1); if (EnterNumberDialog(MemWindow, "Custom refresh rate", "Frames skip per refresh:", &val, dclc_memwin_refresh, 4, 0, 0, 1000)) { dclc_memwin_refresh = val; } set_emumode(EMUMODE_RESTORE, 1); } } static gint MemWindow_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { gtk_widget_hide(GTK_WIDGET(MemWindow)); return TRUE; } static gboolean MemWindow_state_event(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) { if (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) MemWindow_minimized = event->new_window_state & GDK_WINDOW_STATE_ICONIFIED; return TRUE; } static GtkItemFactoryEntry MemWindow_MenuItems[] = { { "/_Go to", NULL, NULL, 0, "" }, { "/Go to/_Address...", "G", MemW_Goto_Address, 0, "" }, { "/Go to/_BIOS top", "B", MemW_Goto_SAddress, 0x0000, "" }, { "/Go to/RA_M top", "M", MemW_Goto_SAddress, 0x1000, "" }, { "/Go to/_Hardware IO top", "H", MemW_Goto_SAddress, 0x2000, "" }, { "/Go to/Cartridge R_OM top", "O", MemW_Goto_SAddress, 0x2100, "" }, { "/Go to/Cartridge _Bank n top", "B", MemW_Goto_BAddress, 0, "" }, { "/_Break", NULL, NULL, 0, "" }, { "/Break/_Add Watchpoint at...", "W", MemW_Break_AddWPAt, 0, "" }, { "/Break/_Delete all watchpoints", NULL, MemW_Break_DelAllWP, 0, "" }, { "/_Character set", NULL, NULL, 0, "" }, { "/Character set/_Default", "C", MemW_CharSet_Default, 0, "" }, { "/Character set/From _ROM...", "C", MemW_CharSet_FromROM, 0, "" }, { "/Character set/From _file...", "C",MemW_CharSet_FromFile, 0, "" }, { "/_Memory data", NULL, NULL, 0, "" }, { "/Memory data/_Import from file...", "I", MemW_ImportData, 0, "" }, { "/Memory data/_Export to file...", "E", MemW_ExportData, 0, "" }, { "/Memory data/_Copy block", "V", MemW_CopyData, 0, "" }, { "/Memory data/_Fill 8-bits value", "X", MemW_FillData8, 0, "" }, { "/Memory data/_Fill 16-bits value", "Y", MemW_FillData16, 0, "" }, { "/_Debugger", NULL, NULL, 0, "" }, { "/Debugger/Run full speed", "F5", Menu_Debug_RunFull, 0, "" }, { "/Debugger/Run debug frames (Sound)", "F5", Menu_Debug_RunDFrameSnd, 0, "" }, { "/Debugger/Run debug frames", "F5", Menu_Debug_RunDFrame, 0, "" }, { "/Debugger/Run debug steps", "F3", Menu_Debug_RunDStep, 0, "" }, { "/Debugger/Single frame", "F4", Menu_Debug_SingleFrame, 0, "" }, { "/Debugger/Single step", "F3", Menu_Debug_SingleStep, 0, "" }, { "/Debugger/Step skip", "F3", Menu_Debug_StepSkip, 0, "" }, { "/Debugger/Stop", "F2", Menu_Debug_Stop, 0, "" }, { "/_Refresh", NULL, NULL, 0, "" }, { "/Refresh/Now!", NULL, MemW_RefreshNow, 0, "" }, { "/Refresh/sep1", NULL, NULL, 0, "" }, { "/Refresh/100% 72fps", NULL, MemW_Refresh, 0, "" }, { "/Refresh/ 50% 36fps", NULL, MemW_Refresh, 1, "/Refresh/100% 72fps" }, { "/Refresh/ 33% 24fps", NULL, MemW_Refresh, 2, "/Refresh/100% 72fps" }, { "/Refresh/ 25% 18fps", NULL, MemW_Refresh, 3, "/Refresh/100% 72fps" }, { "/Refresh/ 17% 12fps", NULL, MemW_Refresh, 5, "/Refresh/100% 72fps" }, { "/Refresh/ 12% 9fps", NULL, MemW_Refresh, 7, "/Refresh/100% 72fps" }, { "/Refresh/ 8% 6fps", NULL, MemW_Refresh, 11, "/Refresh/100% 72fps" }, { "/Refresh/ 3% 2fps", NULL, MemW_Refresh, 35, "/Refresh/100% 72fps" }, { "/Refresh/ 1% 1fps", NULL, MemW_Refresh, 71, "/Refresh/100% 72fps" }, { "/Refresh/Custom...", NULL, MemW_Refresh, -1, "/Refresh/100% 72fps" }, }; static gint MemWindow_MenuItemsNum = sizeof(MemWindow_MenuItems) / sizeof(*MemWindow_MenuItems); // ---------- // Mem Window // ---------- int MemWindow_Create(void) { // Window MemWindow = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gtk_window_set_title(GTK_WINDOW(MemWindow), "Memory View"); gtk_widget_set_size_request(GTK_WIDGET(MemWindow), 200, 100); gtk_window_set_default_size(MemWindow, 420, 200); g_signal_connect(MemWindow, "delete_event", G_CALLBACK(MemWindow_delete_event), NULL); g_signal_connect(MemWindow, "window-state-event", G_CALLBACK(MemWindow_state_event), NULL); VBox1 = GTK_BOX(gtk_vbox_new(FALSE, 0)); gtk_container_add(GTK_CONTAINER(MemWindow), GTK_WIDGET(VBox1)); gtk_widget_show(GTK_WIDGET(VBox1)); // Menu bar AccelGroup = gtk_accel_group_new(); ItemFactory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "
", AccelGroup); gtk_item_factory_create_items(ItemFactory, MemWindow_MenuItemsNum, MemWindow_MenuItems, NULL); gtk_window_add_accel_group(MemWindow, AccelGroup); MenuBar = GTK_MENU_BAR(gtk_item_factory_get_widget(ItemFactory, "
")); gtk_box_pack_start(VBox1, GTK_WIDGET(MenuBar), FALSE, TRUE, 0); gtk_widget_show(GTK_WIDGET(MenuBar)); // Label that warn when running full speed LabelRunFull = GTK_LABEL(gtk_label_new("To view content you must stop emulation or run in debug frames/steps.")); gtk_box_pack_start(VBox1, GTK_WIDGET(LabelRunFull), FALSE, TRUE, 0); // Mem View MemView.on_exposure = SGtkXDVCB(MemView_exposure); MemView.on_scroll = SGtkXDVCB(AnyView_scroll); MemView.on_resize = SGtkXDVCB(AnyView_resize); MemView.on_motion = SGtkXDVCB(AnyView_motion); MemView.on_buttonpress = SGtkXDVCB(MemView_buttonpress); MemView.on_enterleave = SGtkXDVCB(AnyView_enterleave); sgtkx_drawing_view_new(&MemView, 1); sgtkx_drawing_view_sbminmax(&MemView, 0, 255); // $1000 to $1FFF gtk_widget_set_size_request(GTK_WIDGET(MemView.box), 64, 64); gtk_box_pack_start(VBox1, GTK_WIDGET(MemView.box), TRUE, TRUE, 0); gtk_widget_show(GTK_WIDGET(MemView.box)); return 1; } void MemWindow_Destroy(void) { int x, y, width, height; if (gtk_widget_get_realized(GTK_WIDGET(MemWindow))) { gtk_widget_show(GTK_WIDGET(MemWindow)); gtk_window_deiconify(MemWindow); gtk_window_get_position(MemWindow, &x, &y); gtk_window_get_size(MemWindow, &width, &height); dclc_memwin_winx = x; dclc_memwin_winy = y; dclc_memwin_winw = width; dclc_memwin_winh = height; } } void MemWindow_Activate(void) { gtk_widget_realize(GTK_WIDGET(MemWindow)); if ((dclc_memwin_winx > -15) && (dclc_memwin_winy > -16)) { gtk_window_move(MemWindow, dclc_memwin_winx, dclc_memwin_winy); } if ((dclc_memwin_winw > 0) && (dclc_memwin_winh > 0)) { gtk_window_resize(MemWindow, dclc_memwin_winw, dclc_memwin_winh); } gtk_widget_show(GTK_WIDGET(MemWindow)); gtk_window_present(MemWindow); } void MemWindow_UpdateConfigs(void) { MemWindow_InConfigs = 1; switch (dclc_memwin_refresh) { case 0: gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(ItemFactory, "/Refresh/100% 72fps")), 1); break; case 1: gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(ItemFactory, "/Refresh/ 50% 36fps")), 1); break; case 2: gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(ItemFactory, "/Refresh/ 33% 24fps")), 1); break; case 3: gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(ItemFactory, "/Refresh/ 25% 18fps")), 1); break; case 5: gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(ItemFactory, "/Refresh/ 17% 12fps")), 1); break; case 7: gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(ItemFactory, "/Refresh/ 12% 9fps")), 1); break; case 11: gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(ItemFactory, "/Refresh/ 8% 6fps")), 1); break; case 19: gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(ItemFactory, "/Refresh/ 6% 3fps")), 1); break; default: gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item(ItemFactory, "/Refresh/Custom...")), 1); break; } MemWindow_InConfigs = 0; } void MemWindow_ROMResized(void) { sgtkx_drawing_view_sbminmax(&MemView, 0, (PM_ROM_Size/16)-1); } void MemWindow_Sensitive(int enabled) { if (enabled) { gtk_widget_hide(GTK_WIDGET(LabelRunFull)); gtk_widget_show(GTK_WIDGET(MemView.box)); } else { gtk_widget_show(GTK_WIDGET(LabelRunFull)); gtk_widget_hide(GTK_WIDGET(MemView.box)); } } void MemWindow_Refresh(int now) { static int refreshcnt = 0; if ((refreshcnt <= 0) || (now)) { refreshcnt = dclc_memwin_refresh; if (!now) { if (!gtk_widget_get_visible(GTK_WIDGET(MemWindow)) || MemWindow_minimized) return; } sgtkx_drawing_view_refresh(&MemView); } else refreshcnt--; }