#include "vram_viewer.h" #include #include "util.h" #define tileset_buffer_length 256 * 192 * 4 #define tilemap_buffer_length 256 * 256 * 4 struct _VramViewerWindow { GtkWindowClass parent_class; GtkStack *stack; GtkComboBoxText *tileset_palette_selector; GtkToggleButton *tileset_toggle_grid_button; GtkDrawingArea *tileset_canvas; GtkComboBoxText *tilemap_palette_selector; GtkComboBoxText *tilemap_tilemap_selector; GtkToggleButton *tilemap_toggle_grid_button; GtkToggleButton *tilemap_toggle_scrolling_button; GtkComboBoxText *tilemap_tileset_selector; GtkDrawingArea *tilemap_canvas; GtkTreeView *sprites; GtkTreeView *palettes; GtkCellRendererText *palette_cell_renderer_0; GtkCellRendererText *palette_cell_renderer_1; GtkCellRendererText *palette_cell_renderer_2; GtkCellRendererText *palette_cell_renderer_3; GtkLabel *status; bool is_cgb; uint16_t oam_count; uint8_t oam_height; uint8_t palette_data[16][0x40]; GB_oam_info_t oam_info[40]; Rect scroll_rect; uint8_t gb_lcdc; uint8_t *gb_vram; uint32_t *tileset_buffer; uint32_t *tilemap_buffer; }; G_DEFINE_TYPE(VramViewerWindow, vram_viewer_window, GTK_TYPE_WINDOW); static void visible_tab_changed(GObject *stack, GParamSpec *pspec, VramViewerWindow *window) { if (gtk_widget_in_destruction (GTK_WIDGET (stack))) return; gtk_label_set_text(window->status, ""); } static gboolean draw_tileset_canvas(GtkWidget *widget, cairo_t *cr, VramViewerWindow *window) { GtkStyleContext *context = gtk_widget_get_style_context(widget); guint width = gtk_widget_get_allocated_width(widget); guint height = gtk_widget_get_allocated_height(widget); gtk_render_background(context, cr, 0, 0, width, height); gtk_render_frame(context, cr, 0, 0, width, height); cairo_surface_t *surface = cairo_image_surface_create_for_data( (unsigned char *) window->tileset_buffer, CAIRO_FORMAT_RGB24, 256, 192, cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, 256) ); cairo_scale(cr, 2.0, 2.0); cairo_set_source_surface(cr, surface, 0, 0); cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); cairo_paint(cr); if (gtk_toggle_button_get_active(window->tileset_toggle_grid_button)) { cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.25); cairo_set_line_width(cr, 1); const int divisions_x = 256 / 8; const int divisions_y = 192 / 8; for (int i = 0; i < divisions_x; i++) { const int j = 256 * i; cairo_move_to(cr, j / divisions_x, 0); cairo_line_to(cr, j / divisions_x, 192); } for (int i = 0; i < divisions_y; i++) { const int j = 192 * i; cairo_move_to(cr, 0, j / divisions_y); cairo_line_to(cr, 256, j / divisions_y); } cairo_stroke(cr); } return false; } static gboolean draw_tilemap_canvas(GtkWidget *widget, cairo_t *cr, VramViewerWindow *window) { GtkStyleContext *context = gtk_widget_get_style_context(widget); guint width = gtk_widget_get_allocated_width(widget); guint height = gtk_widget_get_allocated_height(widget); gtk_render_background(context, cr, 0, 0, width, height); gtk_render_frame(context, cr, 0, 0, width, height); cairo_surface_t *surface = cairo_image_surface_create_for_data( (unsigned char *) window->tilemap_buffer, CAIRO_FORMAT_RGB24, 256, 256, cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, 256) ); cairo_scale(cr, 2.0, 2.0); cairo_set_source_surface(cr, surface, 0, 0); cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); cairo_paint(cr); if (gtk_toggle_button_get_active(window->tilemap_toggle_grid_button)) { cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.25); cairo_set_line_width(cr, 1); const int divisions = 256 / 8; for (int i = 0; i < divisions; i++) { const int j = 256 * i; cairo_move_to(cr, j / divisions, 0); cairo_line_to(cr, j / divisions, 256); cairo_move_to(cr, 0, j / divisions); cairo_line_to(cr, 256, j / divisions); } cairo_stroke(cr); } if (gtk_toggle_button_get_active(window->tilemap_toggle_scrolling_button)) { cairo_rectangle(cr, -2, -2, width + 2, height + 2); for (unsigned x = 0; x < 2; x++) { for (unsigned y = 0; y < 2; y++) { Rect rect = window->scroll_rect; rect.x -= 256 * x; rect.y -= 256 * y; cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height); } } cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); cairo_set_line_width(cr, 2); cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.5); cairo_fill_preserve(cr); cairo_clip_preserve(cr); cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.6); cairo_stroke(cr); } return false; } static gboolean tileset_canvas_motion(GtkWidget *widget, GdkEventMotion *event, VramViewerWindow *window) { int x, y; if (event->is_hint) { gdk_window_get_pointer(event->window, &x, &y, NULL); } else { x = event->x; y = event->y; } // Compensate for our canvas scale x /= 2; y /= 2; uint8_t bank = x >= 128? 1 : 0; x &= 127; uint16_t tile = x / 8 + y / 8 * 16; const char *format = "Tile number" " $%02x" " at" " %d:$%04x"; g_autofree char *markup = g_markup_printf_escaped( format, tile & 0xFF, bank, 0x8000 + tile * 0x10 ); gtk_label_set_markup(window->status, markup); return true; } static gboolean tilemap_canvas_motion(GtkWidget *widget, GdkEventMotion *event, VramViewerWindow *window) { if (!window->gb_vram) { gtk_label_set_text(window->status, ""); return false; } int x, y; if (event->is_hint) { gdk_window_get_pointer (event->window, &x, &y, NULL); } else { x = event->x; y = event->y; } // Compensate for our canvas scale x /= 2; y /= 2; uint16_t map_offset = x / 8 + y / 8 * 32; uint16_t map_base = 0x1800; GB_map_type_t map_type = GB_MAP_AUTO; const gchar *map_type_id = vram_viewer_get_tilemap_type_id(window); if (g_strcmp0("auto", map_type_id) != 0) { map_type = (g_strcmp0("9800", map_type_id) == 0)? GB_MAP_9800 : GB_MAP_9C00; } GB_tileset_type_t tileset_type = GB_TILESET_AUTO; const gchar *tileset_type_id = vram_viewer_get_tileset_type_id(window); if (g_strcmp0("auto", tileset_type_id) != 0) { tileset_type = (g_strcmp0("8800", tileset_type_id) == 0)? GB_TILESET_8800 : GB_TILESET_8000; } if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && window->gb_lcdc & 0x08)) { map_base = 0x1c00; } if (tileset_type == GB_TILESET_AUTO) { tileset_type = (window->gb_lcdc & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; } uint8_t tile = window->gb_vram[map_base + map_offset]; uint16_t tile_address = 0; if (tileset_type == GB_TILESET_8000) { tile_address = 0x8000 + tile * 0x10; } else { tile_address = 0x9000 + (int8_t)tile * 0x10; } if (window->is_cgb) { uint8_t attributes = window->gb_vram[map_base + map_offset + 0x2000]; const char *format = "Tile number" " $%02x" " (%d:$%04x)" " at map address" " $%04x" " (Attributes: %c%c%c%d%d)"; g_autofree char *markup = g_markup_printf_escaped( format, tile, attributes & 0x8? 1 : 0, tile_address, 0x8000 + map_base + map_offset, (attributes & 0x80) ? 'P' : '-', (attributes & 0x40) ? 'V' : '-', (attributes & 0x20) ? 'H' : '-', attributes & 0x8? 1 : 0, attributes & 0x7 ); gtk_label_set_markup(window->status, markup); } else { const char *format = "Tile number" " $%02x ($%04x)" " at map address" " $%04x"; g_autofree char *markup = g_markup_printf_escaped( format, tile, tile_address, 0x8000 + map_base + map_offset ); gtk_label_set_markup(window->status, markup); } return true; } static void vram_viewer_window_init(VramViewerWindow *window) { gtk_widget_init_template(GTK_WIDGET(window)); window->tileset_buffer = g_new0(uint32_t, tileset_buffer_length); window->tilemap_buffer = g_new0(uint32_t, tilemap_buffer_length); window->is_cgb = false; window->oam_count = 0; window->oam_height = 0; gtk_widget_add_events(GTK_WIDGET(window->tileset_canvas), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); gtk_widget_add_events(GTK_WIDGET(window->tilemap_canvas), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); } static void vram_viewer_finalize(GObject *object) { VramViewerWindow *window = (VramViewerWindow *) object; g_free(window->tilemap_buffer); g_free(window->tileset_buffer); window->gb_vram = NULL; G_OBJECT_CLASS(vram_viewer_window_parent_class)->finalize(object); } static void vram_viewer_window_class_init(VramViewerWindowClass *class) { gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), RESOURCE_PREFIX "ui/vram_viewer.ui"); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, stack); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tileset_palette_selector); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tileset_toggle_grid_button); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tileset_canvas); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_palette_selector); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_tilemap_selector); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_toggle_grid_button); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_toggle_scrolling_button); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_tileset_selector); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, tilemap_canvas); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, sprites); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palettes); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palette_cell_renderer_0); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palette_cell_renderer_1); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palette_cell_renderer_2); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, palette_cell_renderer_3); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), VramViewerWindow, status); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), visible_tab_changed); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), draw_tileset_canvas); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), draw_tilemap_canvas); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), tileset_canvas_motion); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), tilemap_canvas_motion); G_OBJECT_CLASS(class)->finalize = vram_viewer_finalize; } VramViewerWindow *vram_viewer_new(void) { return g_object_new (VRAM_VIEWER_WINDOW_TYPE, NULL); } const gchar *vram_viewer_active_tab_name(VramViewerWindow *window) { return gtk_stack_get_visible_child_name(window->stack); } const gchar *vram_viewer_get_tileset_palette_id(VramViewerWindow *window) { return gtk_combo_box_get_active_id(GTK_COMBO_BOX(window->tileset_palette_selector)); } const gchar *vram_viewer_get_tilemap_palette_id(VramViewerWindow *window) { return gtk_combo_box_get_active_id(GTK_COMBO_BOX(window->tilemap_palette_selector)); } const gchar *vram_viewer_get_tilemap_type_id(VramViewerWindow *window) { return gtk_combo_box_get_active_id(GTK_COMBO_BOX(window->tilemap_tilemap_selector)); } const gchar *vram_viewer_get_tileset_type_id(VramViewerWindow *window) { return gtk_combo_box_get_active_id(GTK_COMBO_BOX(window->tilemap_tileset_selector)); } static gboolean update_sprite_list(VramViewerWindow *window) { GtkTreeIter iter; GtkTreeModel *model = gtk_tree_view_get_model(window->sprites); GtkListStore *store; if (!model) { GtkListStore *new_store = gtk_list_store_new(7, GDK_TYPE_PIXBUF, // Preview image G_TYPE_STRING, // X position G_TYPE_STRING, // Y position G_TYPE_STRING, // Tile G_TYPE_STRING, // Tile Address G_TYPE_STRING, // OAM Address G_TYPE_STRING // Attributes ); gtk_tree_view_set_model(window->sprites, GTK_TREE_MODEL(new_store)); store = new_store; } else { store = GTK_LIST_STORE(model); gtk_list_store_clear(store); } gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter); for (unsigned row = 0; row < window->oam_count; ++row) { g_autoptr(GBytes) bytes = g_bytes_new(window->oam_info[row].image, 128 * sizeof(uint32_t)); g_autoptr(GdkPixbuf) pixbuf = gdk_pixbuf_new_from_bytes( bytes, GDK_COLORSPACE_RGB, true, 8, 8, window->oam_height, 8 * sizeof(uint32_t) ); g_autoptr(GdkPixbuf) dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 8 * 2, window->oam_height * 2); gdk_pixbuf_scale(pixbuf, dest, 0, 0, 8 * 2, window->oam_height * 2, 0, 0, 2.0, 2.0, GDK_INTERP_NEAREST ); g_autofree gchar *str_1 = g_strdup_printf("%i", window->oam_info[row].x - 8); g_autofree gchar *str_2 = g_strdup_printf("%i", window->oam_info[row].y - 16); g_autofree gchar *str_3 = g_strdup_printf("$%02x", window->oam_info[row].tile); g_autofree gchar *str_4 = g_strdup_printf("$%04x", 0x8000 + window->oam_info[row].tile * 0x10); g_autofree gchar *str_5 = g_strdup_printf("$%04x", window->oam_info[row].oam_addr); g_autofree gchar *str_6 = window->is_cgb ? g_strdup_printf("%c%c%c%d%d", window->oam_info[row].flags & 0x80? 'P' : '-', window->oam_info[row].flags & 0x40? 'Y' : '-', window->oam_info[row].flags & 0x20? 'X' : '-', window->oam_info[row].flags & 0x08? 1 : 0, window->oam_info[row].flags & 0x07) : g_strdup_printf("%c%c%c%d", window->oam_info[row].flags & 0x80? 'P' : '-', window->oam_info[row].flags & 0x40? 'Y' : '-', window->oam_info[row].flags & 0x20? 'X' : '-', window->oam_info[row].flags & 0x10? 1 : 0); gtk_list_store_insert_with_values(store, &iter, -1, 0, dest, 1, str_1, 2, str_2, 3, str_3, 4, str_4, 5, str_5, 6, str_6, -1 ); } return false; } static void palette_color_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data_ptr) { const gchar *title = gtk_tree_view_column_get_title(col); const uint8_t color_index = g_ascii_strtoll(&title[6], NULL, 10); const uint8_t column_index = 2 + (2 * color_index); GValue color_val = G_VALUE_INIT; gtk_tree_model_get_value(model, iter, column_index, &color_val); gint color = g_value_get_int(&color_val); gchar *color_string = g_strdup_printf("#%06x", color); gint lightness = 0.299 * ((color >> 16) & 0xFF) + 0.587 * ((color >> 8) & 0xFF) + 0.114 * (color & 0xFF); GValue color_str = G_VALUE_INIT; g_value_init(&color_str, G_TYPE_STRING); g_value_set_string(&color_str, color_string); g_object_set_property(G_OBJECT(renderer), "background", &color_str); GValue fg_color_str = G_VALUE_INIT; g_value_init(&fg_color_str, G_TYPE_STRING); g_value_set_static_string(&fg_color_str, (lightness > 0x7F)? "#000000" : "#FFFFFF"); g_object_set_property(G_OBJECT(renderer), "foreground", &fg_color_str); g_value_unset(&color_val); g_value_unset(&color_str); g_value_unset(&fg_color_str); g_free(color_string); } static gboolean update_palettes(VramViewerWindow *window) { GtkTreeIter iter; GtkTreeModel *model = gtk_tree_view_get_model(window->palettes); GtkListStore *store; if (!model) { GtkListStore *new_store = gtk_list_store_new(9, G_TYPE_STRING, // Name G_TYPE_STRING, // Color 0 string G_TYPE_INT, // Color 0 integer G_TYPE_STRING, // Color 1 string G_TYPE_INT, // Color 1 integer G_TYPE_STRING, // Color 2 string G_TYPE_INT, // Color 2 integer G_TYPE_STRING, // Color 3 string G_TYPE_INT // Color 3 integer ); gtk_tree_view_set_model(window->palettes, GTK_TREE_MODEL(new_store)); store = new_store; GtkTreeViewColumn *column_0 = gtk_tree_view_get_column(window->palettes, 1); GtkTreeViewColumn *column_1 = gtk_tree_view_get_column(window->palettes, 2); GtkTreeViewColumn *column_2 = gtk_tree_view_get_column(window->palettes, 3); GtkTreeViewColumn *column_3 = gtk_tree_view_get_column(window->palettes, 4); gtk_tree_view_column_set_cell_data_func(column_0, GTK_CELL_RENDERER(window->palette_cell_renderer_0), palette_color_data_func, NULL, NULL); gtk_tree_view_column_set_cell_data_func(column_1, GTK_CELL_RENDERER(window->palette_cell_renderer_1), palette_color_data_func, NULL, NULL); gtk_tree_view_column_set_cell_data_func(column_2, GTK_CELL_RENDERER(window->palette_cell_renderer_2), palette_color_data_func, NULL, NULL); gtk_tree_view_column_set_cell_data_func(column_3, GTK_CELL_RENDERER(window->palette_cell_renderer_3), palette_color_data_func, NULL, NULL); } else { store = GTK_LIST_STORE(model); gtk_list_store_clear(store); } gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter); for (unsigned row = 0; row < 16; ++row) { uint8_t offset = (row & 7) * 4; uint16_t color_0 = (window->palette_data[row][((0 + offset) << 1) + 1] << 8) | window->palette_data[row][((0 + offset) << 1)]; uint16_t color_1 = (window->palette_data[row][((1 + offset) << 1) + 1] << 8) | window->palette_data[row][((1 + offset) << 1)]; uint16_t color_2 = (window->palette_data[row][((2 + offset) << 1) + 1] << 8) | window->palette_data[row][((2 + offset) << 1)]; uint16_t color_3 = (window->palette_data[row][((3 + offset) << 1) + 1] << 8) | window->palette_data[row][((3 + offset) << 1)]; g_autofree gchar *str_0 = g_strdup_printf("%s %d", row >=8 ? "Object" : "Background", row & 7); g_autofree gchar *str_1 = g_strdup_printf("$%04x", color_0 & 0x7FFF); g_autofree gchar *str_3 = g_strdup_printf("$%04x", color_1 & 0x7FFF); g_autofree gchar *str_5 = g_strdup_printf("$%04x", color_2 & 0x7FFF); g_autofree gchar *str_7 = g_strdup_printf("$%04x", color_3 & 0x7FFF); gtk_list_store_insert_with_values(store, &iter, -1, 0, str_0, 1, str_1, 2, convert_color(color_0), 3, str_3, 4, convert_color(color_1), 5, str_5, 6, convert_color(color_2), 7, str_7, 8, convert_color(color_3), -1 ); } return false; } void vram_viewer_update(VramViewerWindow *window, GB_gameboy_t *gb) { if (!gtk_widget_is_visible(GTK_WIDGET(window))) return; window->is_cgb = GB_is_cgb(gb); window->gb_lcdc = ((uint8_t *)GB_get_direct_access(gb, GB_DIRECT_ACCESS_IO, NULL, NULL))[GB_IO_LCDC]; window->gb_vram = GB_get_direct_access(gb, GB_DIRECT_ACCESS_VRAM, NULL, NULL); const gchar *active_tab_name = vram_viewer_active_tab_name(window); if (g_strcmp0(active_tab_name, VRAM_VIEWER_TAB_TILESET) == 0) { const gchar *palette_id = vram_viewer_get_tileset_palette_id(window); GB_palette_type_t palette_type = g_str_has_prefix(palette_id, "bg")? GB_PALETTE_BACKGROUND : GB_PALETTE_OAM; uint8_t palette_index = g_ascii_digit_value(palette_id[palette_type == GB_PALETTE_OAM ? 3 : 2]); GB_draw_tileset(gb, window->tileset_buffer, palette_type, palette_index ); } else if (g_strcmp0(active_tab_name, VRAM_VIEWER_TAB_TILEMAP) == 0) { const gchar *palette_id = vram_viewer_get_tilemap_palette_id(window); uint8_t palette_index = 0; GB_palette_type_t palette_type = GB_PALETTE_AUTO; if (g_strcmp0("auto", palette_id) != 0) { palette_type = g_str_has_prefix(palette_id, "bg")? GB_PALETTE_BACKGROUND : GB_PALETTE_OAM; palette_index = g_ascii_digit_value(palette_id[palette_type == GB_PALETTE_OAM ? 3 : 2]); } GB_map_type_t map_type = GB_MAP_AUTO; const gchar *map_type_id = vram_viewer_get_tilemap_type_id(window); if (g_strcmp0("auto", map_type_id) != 0) { map_type = (g_strcmp0("9800", map_type_id) == 0)? GB_MAP_9800 : GB_MAP_9C00; } GB_tileset_type_t tileset_type = GB_TILESET_AUTO; const gchar *tileset_type_id = vram_viewer_get_tileset_type_id(window); if (g_strcmp0("auto", tileset_type_id) != 0) { tileset_type = (g_strcmp0("8800", tileset_type_id) == 0)? GB_TILESET_8800 : GB_TILESET_8000; } GB_draw_tilemap(gb, window->tilemap_buffer, palette_type, palette_index, map_type, tileset_type ); window->scroll_rect = (Rect){ GB_read_memory(gb, 0xFF00 | GB_IO_SCX), GB_read_memory(gb, 0xFF00 | GB_IO_SCY), 160, 144 }; } else if (g_strcmp0(active_tab_name, VRAM_VIEWER_TAB_SPRITES) == 0) { window->oam_count = GB_get_oam_info(gb, (GB_oam_info_t *)window->oam_info, &window->oam_height); g_idle_add((GSourceFunc) update_sprite_list, window); } else if (g_strcmp0(active_tab_name, VRAM_VIEWER_TAB_PALETTES) == 0) { size_t size; for (unsigned row = 0; row < 16; ++row) { uint8_t *palette_data = GB_get_direct_access(gb, row >= 8? GB_DIRECT_ACCESS_OBP : GB_DIRECT_ACCESS_BGP, &size, NULL); memcpy(window->palette_data[row], palette_data, size); } g_idle_add((GSourceFunc) update_palettes, window); } } void vram_viewer_clear(VramViewerWindow *window) { g_idle_remove_by_data(window); memset(window->tilemap_buffer, 0, tilemap_buffer_length * sizeof(uint32_t)); memset(window->tileset_buffer, 0, tileset_buffer_length * sizeof(uint32_t)); g_autoptr(GtkTreeModel) sprites_model = gtk_tree_view_get_model(window->sprites); if (sprites_model) gtk_list_store_clear(GTK_LIST_STORE(sprites_model)); g_autoptr(GtkTreeModel) palettes_model = gtk_tree_view_get_model(window->palettes); if (palettes_model) gtk_list_store_clear(GTK_LIST_STORE(palettes_model)); }