2020-05-18 15:50:19 +00:00
|
|
|
#include "vram_viewer_window.h"
|
2020-05-17 03:33:41 +00:00
|
|
|
#include <stdbool.h>
|
2021-01-07 13:36:46 +00:00
|
|
|
#include "../types.h"
|
|
|
|
#include "../util.h"
|
2020-05-17 03:33:41 +00:00
|
|
|
|
|
|
|
#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) {
|
2020-05-20 01:41:33 +00:00
|
|
|
if (gtk_widget_in_destruction(GTK_WIDGET (stack))) return;
|
2020-05-17 03:33:41 +00:00
|
|
|
|
|
|
|
gtk_label_set_text(window->status, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean draw_tileset_canvas(GtkWidget *widget, cairo_t *cr, VramViewerWindow *window) {
|
2020-05-17 23:26:01 +00:00
|
|
|
GtkStyleContext *context = gtk_widget_get_style_context(widget);
|
2020-05-19 00:31:31 +00:00
|
|
|
int width = gtk_widget_get_allocated_width(widget);
|
|
|
|
int height = gtk_widget_get_allocated_height(widget);
|
2020-05-17 03:33:41 +00:00
|
|
|
|
|
|
|
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) {
|
2020-05-17 23:26:01 +00:00
|
|
|
GtkStyleContext *context = gtk_widget_get_style_context(widget);
|
2020-05-19 00:31:31 +00:00
|
|
|
int width = gtk_widget_get_allocated_width(widget);
|
|
|
|
int height = gtk_widget_get_allocated_height(widget);
|
2020-05-17 03:33:41 +00:00
|
|
|
|
|
|
|
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;
|
2020-05-17 23:26:01 +00:00
|
|
|
rect.y -= 256 * y;
|
|
|
|
|
2020-05-17 03:33:41 +00:00
|
|
|
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;
|
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
const char *format =
|
|
|
|
"Tile number"
|
|
|
|
" <span font_family=\"monospace\">$%02x</span>"
|
|
|
|
" at"
|
|
|
|
" <span font_family=\"monospace\">%d:$%04x</span>";
|
|
|
|
|
|
|
|
g_autofree char *markup = g_markup_printf_escaped(
|
|
|
|
format,
|
|
|
|
tile & 0xFF,
|
|
|
|
bank, 0x8000 + tile * 0x10
|
|
|
|
);
|
|
|
|
|
|
|
|
gtk_label_set_markup(window->status, markup);
|
2020-05-17 03:33:41 +00:00
|
|
|
|
|
|
|
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) {
|
2020-05-20 01:41:33 +00:00
|
|
|
gdk_window_get_pointer(event->window, &x, &y, NULL);
|
2020-05-17 03:33:41 +00:00
|
|
|
}
|
|
|
|
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];
|
2020-05-17 20:51:27 +00:00
|
|
|
|
|
|
|
const char *format =
|
|
|
|
"Tile number"
|
|
|
|
" <span font_family=\"monospace\">$%02x</span>"
|
|
|
|
" <span font_family=\"monospace\">(%d:$%04x)</span>"
|
|
|
|
" at map address"
|
|
|
|
" <span font_family=\"monospace\">$%04x</span>"
|
|
|
|
" (Attributes: <span font_family=\"monospace\">%c%c%c%d%d</span>)";
|
|
|
|
|
|
|
|
g_autofree char *markup = g_markup_printf_escaped(
|
|
|
|
format,
|
2020-05-17 03:33:41 +00:00
|
|
|
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
|
2020-05-17 20:51:27 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
gtk_label_set_markup(window->status, markup);
|
2020-05-17 03:33:41 +00:00
|
|
|
}
|
|
|
|
else {
|
2020-05-17 20:51:27 +00:00
|
|
|
const char *format =
|
|
|
|
"Tile number"
|
|
|
|
" <span font_family=\"monospace\">$%02x ($%04x)</span>"
|
|
|
|
" at map address"
|
|
|
|
" <span font_family=\"monospace\">$%04x</span>";
|
|
|
|
|
|
|
|
g_autofree char *markup = g_markup_printf_escaped(
|
|
|
|
format,
|
2020-05-17 03:33:41 +00:00
|
|
|
tile,
|
|
|
|
tile_address,
|
|
|
|
0x8000 + map_base + map_offset
|
2020-05-17 20:51:27 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
gtk_label_set_markup(window->status, markup);
|
2020-05-17 03:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-05-20 01:41:33 +00:00
|
|
|
|
|
|
|
set_combo_box_row_separator_func(GTK_CONTAINER(window));
|
2020-05-17 03:33:41 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 23:26:01 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-05-17 03:33:41 +00:00
|
|
|
static void vram_viewer_window_class_init(VramViewerWindowClass *class) {
|
2020-05-18 15:50:19 +00:00
|
|
|
gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), RESOURCE_PREFIX "ui/vram_viewer_window.ui");
|
2020-05-17 03:33:41 +00:00
|
|
|
|
|
|
|
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);
|
2020-05-17 23:26:01 +00:00
|
|
|
|
|
|
|
G_OBJECT_CLASS(class)->finalize = vram_viewer_finalize;
|
2020-05-17 03:33:41 +00:00
|
|
|
}
|
|
|
|
|
2020-05-20 01:41:33 +00:00
|
|
|
VramViewerWindow *vram_viewer_window_new(void) {
|
|
|
|
return g_object_new(VRAM_VIEWER_WINDOW_TYPE, NULL);
|
2020-05-17 03:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
static gboolean update_sprite_list(VramViewerWindow *window) {
|
2020-05-17 03:33:41 +00:00
|
|
|
GtkTreeIter iter;
|
2020-05-17 20:51:27 +00:00
|
|
|
GtkTreeModel *model = gtk_tree_view_get_model(window->sprites);
|
|
|
|
GtkListStore *store;
|
|
|
|
|
|
|
|
if (!model) {
|
2020-05-17 21:13:59 +00:00
|
|
|
GtkListStore *new_store = gtk_list_store_new(7,
|
2020-05-17 20:51:27 +00:00
|
|
|
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
|
|
|
|
);
|
2020-05-17 03:33:41 +00:00
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-05-17 03:33:41 +00:00
|
|
|
|
|
|
|
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
|
|
|
|
|
|
|
|
for (unsigned row = 0; row < window->oam_count; ++row) {
|
2020-05-17 20:51:27 +00:00
|
|
|
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,
|
2020-05-17 03:33:41 +00:00
|
|
|
GDK_COLORSPACE_RGB, true, 8, 8, window->oam_height, 8 * sizeof(uint32_t)
|
|
|
|
);
|
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
g_autoptr(GdkPixbuf) dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 8 * 2, window->oam_height * 2);
|
2020-05-17 03:33:41 +00:00
|
|
|
|
|
|
|
gdk_pixbuf_scale(pixbuf, dest,
|
|
|
|
0, 0, 8 * 2, window->oam_height * 2,
|
|
|
|
0, 0, 2.0, 2.0,
|
|
|
|
GDK_INTERP_NEAREST
|
|
|
|
);
|
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
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);
|
|
|
|
|
2020-05-17 03:33:41 +00:00
|
|
|
gtk_list_store_insert_with_values(store, &iter, -1,
|
|
|
|
0, dest,
|
2020-05-17 20:51:27 +00:00
|
|
|
1, str_1,
|
|
|
|
2, str_2,
|
|
|
|
3, str_3,
|
|
|
|
4, str_4,
|
|
|
|
5, str_5,
|
|
|
|
6, str_6,
|
2020-05-17 03:33:41 +00:00
|
|
|
-1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
return false;
|
2020-05-17 03:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
static gboolean update_palettes(VramViewerWindow *window) {
|
2020-05-17 03:33:41 +00:00
|
|
|
GtkTreeIter iter;
|
2020-05-17 20:51:27 +00:00
|
|
|
GtkTreeModel *model = gtk_tree_view_get_model(window->palettes);
|
|
|
|
GtkListStore *store;
|
2020-05-17 03:33:41 +00:00
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
if (!model) {
|
2020-05-17 21:13:59 +00:00
|
|
|
GtkListStore *new_store = gtk_list_store_new(9,
|
2020-05-17 20:51:27 +00:00
|
|
|
G_TYPE_STRING, // Name
|
2020-05-17 03:33:41 +00:00
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
G_TYPE_STRING, // Color 0 string
|
|
|
|
G_TYPE_INT, // Color 0 integer
|
2020-05-17 03:33:41 +00:00
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
G_TYPE_STRING, // Color 1 string
|
|
|
|
G_TYPE_INT, // Color 1 integer
|
2020-05-17 03:33:41 +00:00
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
G_TYPE_STRING, // Color 2 string
|
|
|
|
G_TYPE_INT, // Color 2 integer
|
2020-05-17 03:33:41 +00:00
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-05-17 03:33:41 +00:00
|
|
|
|
|
|
|
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)];
|
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
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);
|
|
|
|
|
2020-05-17 03:33:41 +00:00
|
|
|
gtk_list_store_insert_with_values(store, &iter, -1,
|
2020-05-17 20:51:27 +00:00
|
|
|
0, str_0,
|
|
|
|
1, str_1,
|
2020-05-17 03:33:41 +00:00
|
|
|
2, convert_color(color_0),
|
2020-05-17 20:51:27 +00:00
|
|
|
3, str_3,
|
2020-05-17 03:33:41 +00:00
|
|
|
4, convert_color(color_1),
|
2020-05-17 20:51:27 +00:00
|
|
|
5, str_5,
|
2020-05-17 03:33:41 +00:00
|
|
|
6, convert_color(color_2),
|
2020-05-17 20:51:27 +00:00
|
|
|
7, str_7,
|
2020-05-17 03:33:41 +00:00
|
|
|
8, convert_color(color_3),
|
|
|
|
-1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-17 20:51:27 +00:00
|
|
|
return false;
|
2020-05-17 03:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2020-05-17 21:13:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|