1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
|
#include "emulate.h"
#include <glib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "Core/gb.h"
// Auto-generated via `glib-compile-resources` from `resources.gresource.xml`.
#include "build/obj/XdgThumbnailer/resources.h"
#define NB_FRAMES_TO_EMULATE (60 * 10)
#define BOOT_ROM_SIZE (0x100 + 0x800) // The two "parts" of it, which are stored contiguously.
/* --- */
static char *async_input_callback(GB_gameboy_t *gb)
{
(void)gb;
return NULL;
}
static void log_callback(GB_gameboy_t *gb, const char *string, GB_log_attributes_t attributes)
{
(void)gb, (void)string, (void)attributes; // Swallow any logs.
}
static void vblank_callback(GB_gameboy_t *gb, GB_vblank_type_t type)
{
(void)type; // Ignore the type, we use VBlank counting as a kind of pacing (and to avoid tearing).
unsigned *nb_frames_left = GB_get_user_data(gb);
(*nb_frames_left)--;
// *Do* render the very last frame.
if (*nb_frames_left == 1) {
GB_set_rendering_disabled(gb, false);
}
}
static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
{
uint32_t rgba;
// The GdkPixbuf that will be created from the screen buffer later, expects components in the
// order [red, green, blue, alpha], from a uint8_t[] buffer.
// But SameBoy requires a uint32_t[] buffer, and don't know the endianness of `uint32_t`.
// So we treat each uint32_t as a 4-byte buffer, and write the bytes accordingly.
// This is guaranteed to not be UB, because casting a `T*` to any flavour of `char*` accesses
// and modifies the `T`'s "object representation".
uint8_t *bytes = (uint8_t *)&rgba;
bytes[0] = r;
bytes[1] = g;
bytes[2] = b;
bytes[3] = 0xFF;
return rgba;
}
uint8_t emulate(const char *path, uint32_t screen[static GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT])
{
GB_gameboy_t gb;
GB_init(&gb, GB_MODEL_CGB_E);
const char *last_dot = strrchr(path, '.');
bool is_isx = last_dot && strcmp(last_dot + 1, "isx") == 0;
if (is_isx ? GB_load_isx(&gb, path) : GB_load_rom(&gb, path)) {
exit(EXIT_FAILURE);
}
GError *error = NULL;
GBytes *boot_rom = g_resource_lookup_data(resources_get_resource(), "/thumbnailer/cgb_boot_fast.bin",
G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
g_assert_no_error(error); // This shouldn't be able to fail.
size_t boot_rom_size;
const uint8_t *boot_rom_data = g_bytes_get_data(boot_rom, &boot_rom_size);
g_assert_cmpuint(boot_rom_size, ==, BOOT_ROM_SIZE);
GB_load_boot_rom_from_buffer(&gb, boot_rom_data, boot_rom_size);
g_bytes_unref(boot_rom);
GB_set_vblank_callback(&gb, vblank_callback);
GB_set_pixels_output(&gb, screen);
GB_set_rgb_encode_callback(&gb, rgb_encode);
GB_set_async_input_callback(&gb, async_input_callback);
GB_set_log_callback(&gb, log_callback); // Anything bizarre the ROM does during emulation, we don't care about.
GB_set_color_correction_mode(&gb, GB_COLOR_CORRECTION_MODERN_BALANCED);
unsigned nb_frames_left = NB_FRAMES_TO_EMULATE;
GB_set_user_data(&gb, &nb_frames_left);
GB_set_rendering_disabled(&gb, true);
GB_set_turbo_mode(&gb, true, true);
while (nb_frames_left) {
GB_run(&gb);
}
int cgb_flag = GB_read_memory(&gb, 0x143) & 0xC0;
GB_free(&gb);
return cgb_flag;
}
|