/*
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--;
}