bsnes-libretro/Core/gb.c
2021-04-16 16:35:21 +03:00

1829 lines
57 KiB
C

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#ifndef _WIN32
#include <sys/select.h>
#include <unistd.h>
#endif
#include "random.h"
#include "gb.h"
#ifdef GB_DISABLE_REWIND
#define GB_rewind_free(...)
#define GB_rewind_push(...)
#endif
void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args)
{
char *string = NULL;
vasprintf(&string, fmt, args);
if (string) {
if (gb->log_callback) {
gb->log_callback(gb, string, attributes);
}
else {
/* Todo: Add ANSI escape sequences for attributed text */
printf("%s", string);
}
}
free(string);
}
void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
GB_attributed_logv(gb, attributes, fmt, args);
va_end(args);
}
void GB_log(GB_gameboy_t *gb, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
GB_attributed_logv(gb, 0, fmt, args);
va_end(args);
}
#ifndef GB_DISABLE_DEBUGGER
static char *default_input_callback(GB_gameboy_t *gb)
{
char *expression = NULL;
size_t size = 0;
if (gb->debug_stopped) {
printf(">");
}
if (getline(&expression, &size, stdin) == -1) {
/* The user doesn't have STDIN or used ^D. We make sure the program keeps running. */
GB_set_async_input_callback(gb, NULL); /* Disable async input */
return strdup("c");
}
if (!expression) {
return strdup("");
}
size_t length = strlen(expression);
if (expression[length - 1] == '\n') {
expression[length - 1] = 0;
}
if (expression[0] == '\x03') {
gb->debug_stopped = true;
free(expression);
return strdup("");
}
return expression;
}
static char *default_async_input_callback(GB_gameboy_t *gb)
{
#ifndef _WIN32
fd_set set;
FD_ZERO(&set);
FD_SET(STDIN_FILENO, &set);
struct timeval time = {0,};
if (select(1, &set, NULL, NULL, &time) == 1) {
if (feof(stdin)) {
GB_set_async_input_callback(gb, NULL); /* Disable async input */
return NULL;
}
return default_input_callback(gb);
}
#endif
return NULL;
}
#endif
static void load_default_border(GB_gameboy_t *gb)
{
if (gb->has_sgb_border) return;
#define LOAD_BORDER() do { \
memcpy(gb->borrowed_border.map, tilemap, sizeof(tilemap));\
memcpy(gb->borrowed_border.palette, palette, sizeof(palette));\
memcpy(gb->borrowed_border.tiles, tiles, sizeof(tiles));\
} while (false);
#ifdef GB_BIG_ENDIAN
for (unsigned i = 0; i < sizeof(gb->borrowed_border.map) / 2; i++) {
gb->borrowed_border.map[i] = LE16(gb->borrowed_border.map[i]);
}
for (unsigned i = 0; i < sizeof(gb->borrowed_border.palette) / 2; i++) {
gb->borrowed_border.palette[i] = LE16(gb->borrowed_border.palette[i]);
}
#endif
if (gb->model == GB_MODEL_AGB) {
#include "graphics/agb_border.inc"
LOAD_BORDER();
}
else if (GB_is_cgb(gb)) {
#include "graphics/cgb_border.inc"
LOAD_BORDER();
}
else {
#include "graphics/dmg_border.inc"
LOAD_BORDER();
}
}
void GB_init(GB_gameboy_t *gb, GB_model_t model)
{
memset(gb, 0, sizeof(*gb));
gb->model = model;
if (GB_is_cgb(gb)) {
gb->ram = malloc(gb->ram_size = 0x1000 * 8);
gb->vram = malloc(gb->vram_size = 0x2000 * 2);
}
else {
gb->ram = malloc(gb->ram_size = 0x2000);
gb->vram = malloc(gb->vram_size = 0x2000);
}
#ifndef GB_DISABLE_DEBUGGER
gb->input_callback = default_input_callback;
gb->async_input_callback = default_async_input_callback;
#endif
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
gb->clock_multiplier = 1.0;
if (model & GB_MODEL_NO_SFC_BIT) {
/* Disable time syncing. Timing should be done by the SFC emulator. */
gb->turbo = true;
}
GB_reset(gb);
load_default_border(gb);
}
GB_model_t GB_get_model(GB_gameboy_t *gb)
{
return gb->model;
}
void GB_free(GB_gameboy_t *gb)
{
gb->magic = 0;
if (gb->ram) {
free(gb->ram);
}
if (gb->vram) {
free(gb->vram);
}
if (gb->mbc_ram) {
free(gb->mbc_ram);
}
if (gb->rom) {
free(gb->rom);
}
if (gb->breakpoints) {
free(gb->breakpoints);
}
if (gb->sgb) {
free(gb->sgb);
}
if (gb->nontrivial_jump_state) {
free(gb->nontrivial_jump_state);
}
if (gb->undo_state) {
free(gb->undo_state);
}
#ifndef GB_DISABLE_DEBUGGER
GB_debugger_clear_symbols(gb);
#endif
GB_rewind_free(gb);
#ifndef GB_DISABLE_CHEATS
while (gb->cheats) {
GB_remove_cheat(gb, gb->cheats[0]);
}
#endif
memset(gb, 0, sizeof(*gb));
}
int GB_load_boot_rom(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "rb");
if (!f) {
GB_log(gb, "Could not open boot ROM: %s.\n", strerror(errno));
return errno;
}
fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f);
fclose(f);
return 0;
}
void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size)
{
if (size > sizeof(gb->boot_rom)) {
size = sizeof(gb->boot_rom);
}
memset(gb->boot_rom, 0xFF, sizeof(gb->boot_rom));
memcpy(gb->boot_rom, buffer, size);
}
void GB_borrow_sgb_border(GB_gameboy_t *gb)
{
if (GB_is_sgb(gb)) return;
if (gb->border_mode != GB_BORDER_ALWAYS) return;
if (gb->tried_loading_sgb_border) return;
gb->tried_loading_sgb_border = true;
if (gb->rom && gb->rom[0x146] != 3) return; // Not an SGB game, nothing to borrow
if (!gb->boot_rom_load_callback) return; // Can't borrow a border without this callback
GB_gameboy_t sgb;
GB_init(&sgb, GB_MODEL_SGB);
sgb.cartridge_type = gb->cartridge_type;
sgb.rom = gb->rom;
sgb.rom_size = gb->rom_size;
sgb.turbo = true;
sgb.turbo_dont_skip = true;
// sgb.disable_rendering = true;
/* Load the boot ROM using the existing gb object */
typeof(gb->boot_rom) boot_rom_backup;
memcpy(boot_rom_backup, gb->boot_rom, sizeof(gb->boot_rom));
gb->boot_rom_load_callback(gb, GB_BOOT_ROM_SGB);
memcpy(sgb.boot_rom, gb->boot_rom, sizeof(gb->boot_rom));
memcpy(gb->boot_rom, boot_rom_backup, sizeof(gb->boot_rom));
sgb.sgb->intro_animation = -1;
for (unsigned i = 600; i--;) {
GB_run_frame(&sgb);
if (sgb.sgb->border_animation) {
gb->has_sgb_border = true;
memcpy(&gb->borrowed_border, &sgb.sgb->pending_border, sizeof(gb->borrowed_border));
gb->borrowed_border.palette[0] = sgb.sgb->effective_palettes[0];
break;
}
}
sgb.rom = NULL;
sgb.rom_size = 0;
GB_free(&sgb);
}
int GB_load_rom(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "rb");
if (!f) {
GB_log(gb, "Could not open ROM: %s.\n", strerror(errno));
return errno;
}
fseek(f, 0, SEEK_END);
gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */
/* And then round to a power of two */
while (gb->rom_size & (gb->rom_size - 1)) {
/* I promise this works. */
gb->rom_size |= gb->rom_size >> 1;
gb->rom_size++;
}
if (gb->rom_size == 0) {
gb->rom_size = 0x8000;
}
fseek(f, 0, SEEK_SET);
if (gb->rom) {
free(gb->rom);
}
gb->rom = malloc(gb->rom_size);
memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */
fread(gb->rom, 1, gb->rom_size, f);
fclose(f);
GB_configure_cart(gb);
gb->tried_loading_sgb_border = false;
gb->has_sgb_border = false;
load_default_border(gb);
return 0;
}
void GB_gbs_switch_track(GB_gameboy_t *gb, uint8_t track)
{
GB_reset(gb);
GB_write_memory(gb, 0xFF00 + GB_IO_LCDC, 0x80);
GB_write_memory(gb, 0xFF00 + GB_IO_TAC, gb->gbs_header.TAC);
GB_write_memory(gb, 0xFF00 + GB_IO_TMA, gb->gbs_header.TMA);
GB_write_memory(gb, 0xFF00 + GB_IO_NR52, 0x80);
GB_write_memory(gb, 0xFF00 + GB_IO_NR51, 0xFF);
GB_write_memory(gb, 0xFF00 + GB_IO_NR50, 0x77);
memset(gb->ram, 0, gb->ram_size);
memset(gb->hram, 0, sizeof(gb->hram));
memset(gb->oam, 0, sizeof(gb->oam));
if (gb->gbs_header.TAC || gb->gbs_header.TMA) {
GB_write_memory(gb, 0xFFFF, 0x04);
}
else {
GB_write_memory(gb, 0xFFFF, 0x01);
}
if (gb->gbs_header.TAC & 0x80) {
gb->cgb_double_speed = true; // Might mean double speed mode on a DMG
}
gb->sp = LE16(gb->gbs_header.sp);
gb->pc = 0x100;
gb->boot_rom_finished = true;
gb->a = track;
if (gb->sgb) {
gb->sgb->intro_animation = GB_SGB_INTRO_ANIMATION_LENGTH;
gb->sgb->disable_commands = true;
}
}
int GB_load_gbs(GB_gameboy_t *gb, const char *path, GB_gbs_info_t *info)
{
FILE *f = fopen(path, "rb");
if (!f) {
GB_log(gb, "Could not open GBS: %s.\n", strerror(errno));
return errno;
}
fread(&gb->gbs_header, sizeof(gb->gbs_header), 1, f);
if (gb->gbs_header.magic != BE32('GBS\x01') ||
LE16(gb->gbs_header.load_address) < 0x400 ||
LE16(gb->gbs_header.load_address) >= 0x8000) {
GB_log(gb, "Not a valid GBS file: %s.\n", strerror(errno));
fclose(f);
return -1;
}
fseek(f, 0, SEEK_END);
size_t data_size = ftell(f) - sizeof(gb->gbs_header);
gb->rom_size = (data_size + LE16(gb->gbs_header.load_address) + 0x3FFF) & ~0x3FFF; /* Round to bank */
/* And then round to a power of two */
while (gb->rom_size & (gb->rom_size - 1)) {
/* I promise this works. */
gb->rom_size |= gb->rom_size >> 1;
gb->rom_size++;
}
if (gb->rom_size == 0) {
gb->rom_size = 0x8000;
}
fseek(f, sizeof(gb->gbs_header), SEEK_SET);
if (gb->rom) {
free(gb->rom);
}
gb->rom = malloc(gb->rom_size);
memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */
fread(gb->rom + LE16(gb->gbs_header.load_address), 1, data_size, f);
fclose(f);
gb->cartridge_type = &GB_cart_defs[0x11];
if (gb->mbc_ram) {
free(gb->mbc_ram);
gb->mbc_ram = NULL;
}
if (gb->cartridge_type->has_ram) {
gb->mbc_ram_size = 0x2000;
gb->mbc_ram = malloc(gb->mbc_ram_size);
memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size);
}
// Generate interrupt handlers
for (unsigned i = 0; i <= 0x38; i += 8) {
gb->rom[i] = 0xc3; // jp $XXXX
gb->rom[i + 1] = (LE16(gb->gbs_header.load_address) + i);
gb->rom[i + 2] = (LE16(gb->gbs_header.load_address) + i) >> 8;
}
for (unsigned i = 0x40; i <= 0x60; i += 8) {
gb->rom[i] = 0xc9; // ret
}
// Generate entry
memcpy(gb->rom + 0x100, (uint8_t[]) {
0xCD, // Call $XXXX
LE16(gb->gbs_header.init_address),
LE16(gb->gbs_header.init_address) >> 8,
0x76, // HALT
0x00, // NOP
0xAF, // XOR a
0xE0, // LDH [$FFXX], a
GB_IO_IF,
0xCD, // Call $XXXX
LE16(gb->gbs_header.play_address),
LE16(gb->gbs_header.play_address) >> 8,
0x18, // JR pc ± $XX
-10 // To HALT
}, 13);
GB_gbs_switch_track(gb, gb->gbs_header.first_track - 1);
if (info) {
memset(info, 0, sizeof(*info));
info->first_track = gb->gbs_header.first_track - 1;
info->track_count = gb->gbs_header.track_count;
memcpy(info->title, gb->gbs_header.title, sizeof(gb->gbs_header.title));
memcpy(info->author, gb->gbs_header.author, sizeof(gb->gbs_header.author));
memcpy(info->copyright, gb->gbs_header.copyright, sizeof(gb->gbs_header.copyright));
}
gb->tried_loading_sgb_border = true; // Don't even attempt on GBS files
gb->has_sgb_border = false;
load_default_border(gb);
return 0;
}
int GB_load_isx(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "rb");
if (!f) {
GB_log(gb, "Could not open ISX file: %s.\n", strerror(errno));
return errno;
}
char magic[4];
#define READ(x) if (fread(&x, sizeof(x), 1, f) != 1) goto error
fread(magic, 1, sizeof(magic), f);
#ifdef GB_BIG_ENDIAN
bool extended = *(uint32_t *)&magic == 'ISX ';
#else
bool extended = *(uint32_t *)&magic == __builtin_bswap32('ISX ');
#endif
fseek(f, extended? 0x20 : 0, SEEK_SET);
uint8_t *old_rom = gb->rom;
uint32_t old_size = gb->rom_size;
gb->rom = NULL;
gb->rom_size = 0;
while (true) {
uint8_t record_type = 0;
if (fread(&record_type, sizeof(record_type), 1, f) != 1) break;
switch (record_type) {
case 0x01: { // Binary
uint16_t bank;
uint16_t address;
uint16_t length;
uint8_t byte;
READ(byte);
bank = byte;
if (byte >= 0x80) {
READ(byte);
bank |= byte << 8;
}
READ(address);
#ifdef GB_BIG_ENDIAN
address = __builtin_bswap16(address);
#endif
address &= 0x3FFF;
READ(length);
#ifdef GB_BIG_ENDIAN
length = __builtin_bswap16(length);
#endif
size_t needed_size = bank * 0x4000 + address + length;
if (needed_size > 1024 * 1024 * 32) goto error;
if (gb->rom_size < needed_size) {
gb->rom = realloc(gb->rom, needed_size);
memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size);
gb->rom_size = needed_size;
}
if (fread(gb->rom + (bank * 0x4000 + address), length, 1, f) != 1) goto error;
break;
}
case 0x11: { // Extended Binary
uint32_t address;
uint32_t length;
READ(address);
#ifdef GB_BIG_ENDIAN
address = __builtin_bswap32(address);
#endif
READ(length);
#ifdef GB_BIG_ENDIAN
length = __builtin_bswap32(length);
#endif
size_t needed_size = address + length;
if (needed_size > 1024 * 1024 * 32) goto error;
if (gb->rom_size < needed_size) {
gb->rom = realloc(gb->rom, needed_size);
memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size);
gb->rom_size = needed_size;
}
if (fread(gb->rom + address, length, 1, f) != 1) goto error;
break;
}
case 0x04: { // Symbol
uint16_t count;
uint8_t length;
char name[257];
uint8_t flag;
uint16_t bank;
uint16_t address;
uint8_t byte;
READ(count);
#ifdef GB_BIG_ENDIAN
count = __builtin_bswap16(count);
#endif
while (count--) {
READ(length);
if (fread(name, length, 1, f) != 1) goto error;
name[length] = 0;
READ(flag); // unused
READ(byte);
bank = byte;
if (byte >= 0x80) {
READ(byte);
bank |= byte << 8;
}
READ(address);
#ifdef GB_BIG_ENDIAN
address = __builtin_bswap16(address);
#endif
GB_debugger_add_symbol(gb, bank, address, name);
}
break;
}
case 0x14: { // Extended Binary
uint16_t count;
uint8_t length;
char name[257];
uint8_t flag;
uint32_t address;
READ(count);
#ifdef GB_BIG_ENDIAN
count = __builtin_bswap16(count);
#endif
while (count--) {
READ(length);
if (fread(name, length + 1, 1, f) != 1) goto error;
name[length] = 0;
READ(flag); // unused
READ(address);
#ifdef GB_BIG_ENDIAN
address = __builtin_bswap32(address);
#endif
// TODO: How to convert 32-bit addresses to Bank:Address? Needs to tell RAM and ROM apart
}
break;
}
default:
goto done;
}
}
done:;
#undef READ
if (gb->rom_size == 0) goto error;
size_t needed_size = (gb->rom_size + 0x3FFF) & ~0x3FFF; /* Round to bank */
/* And then round to a power of two */
while (needed_size & (needed_size - 1)) {
/* I promise this works. */
needed_size |= needed_size >> 1;
needed_size++;
}
if (needed_size < 0x8000) {
needed_size = 0x8000;
}
if (gb->rom_size < needed_size) {
gb->rom = realloc(gb->rom, needed_size);
memset(gb->rom + gb->rom_size, 0, needed_size - gb->rom_size);
gb->rom_size = needed_size;
}
GB_configure_cart(gb);
// Fix a common wrong MBC error
if (gb->rom[0x147] == 3) { // MBC1 + RAM + Battery
bool needs_fix = false;
if (gb->rom_size >= 0x21 * 0x4000) {
for (unsigned i = 0x20 * 0x4000; i < 0x21 * 0x4000; i++) {
if (gb->rom[i]) {
needs_fix = true;
break;
}
}
}
if (!needs_fix && gb->rom_size >= 0x41 * 0x4000) {
for (unsigned i = 0x40 * 0x4000; i < 0x41 * 0x4000; i++) {
if (gb->rom[i]) {
needs_fix = true;
break;
}
}
}
if (!needs_fix && gb->rom_size >= 0x61 * 0x4000) {
for (unsigned i = 0x60 * 0x4000; i < 0x61 * 0x4000; i++) {
if (gb->rom[i]) {
needs_fix = true;
break;
}
}
}
if (needs_fix) {
gb->rom[0x147] = 0x10; // MBC3 + RTC + RAM + Battery
GB_configure_cart(gb);
gb->rom[0x147] = 0x3;
GB_log(gb, "ROM claims to use MBC1 but appears to require MBC3 or 5, assuming MBC3.\n");
}
}
if (old_rom) {
free(old_rom);
}
return 0;
error:
GB_log(gb, "Invalid or unsupported ISX file.\n");
if (gb->rom) {
free(gb->rom);
gb->rom = old_rom;
gb->rom_size = old_size;
}
fclose(f);
gb->tried_loading_sgb_border = false;
gb->has_sgb_border = false;
load_default_border(gb);
return -1;
}
void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
{
gb->rom_size = (size + 0x3fff) & ~0x3fff;
while (gb->rom_size & (gb->rom_size - 1)) {
gb->rom_size |= gb->rom_size >> 1;
gb->rom_size++;
}
if (gb->rom_size == 0) {
gb->rom_size = 0x8000;
}
if (gb->rom) {
free(gb->rom);
}
gb->rom = malloc(gb->rom_size);
memset(gb->rom, 0xff, gb->rom_size);
memcpy(gb->rom, buffer, size);
GB_configure_cart(gb);
gb->tried_loading_sgb_border = false;
gb->has_sgb_border = false;
load_default_border(gb);
}
typedef struct {
uint8_t seconds;
uint8_t padding1[3];
uint8_t minutes;
uint8_t padding2[3];
uint8_t hours;
uint8_t padding3[3];
uint8_t days;
uint8_t padding4[3];
uint8_t high;
uint8_t padding5[3];
} GB_vba_rtc_time_t;
typedef struct __attribute__((packed)) {
uint32_t magic;
uint16_t version;
uint8_t mr4;
uint8_t reserved;
uint64_t last_rtc_second;
uint8_t rtc_data[4];
} GB_tpp1_rtc_save_t;
typedef union {
struct __attribute__((packed)) {
GB_rtc_time_t rtc_real;
time_t last_rtc_second; /* Platform specific endianess and size */
} sameboy_legacy;
struct {
/* Used by VBA versions with 32-bit timestamp*/
GB_vba_rtc_time_t rtc_real, rtc_latched;
uint32_t last_rtc_second; /* Always little endian */
} vba32;
struct {
/* Used by BGB and VBA versions with 64-bit timestamp*/
GB_vba_rtc_time_t rtc_real, rtc_latched;
uint64_t last_rtc_second; /* Always little endian */
} vba64;
} GB_rtc_save_t;
static void GB_fill_tpp1_save_data(GB_gameboy_t *gb, GB_tpp1_rtc_save_t *data)
{
data->magic = BE32('TPP1');
data->version = BE16(0x100);
data->mr4 = gb->tpp1_mr4;
data->reserved = 0;
data->last_rtc_second = LE64(time(NULL));
unrolled for (unsigned i = 4; i--;) {
data->rtc_data[i] = gb->rtc_real.data[i ^ 3];
}
}
int GB_save_battery_size(GB_gameboy_t *gb)
{
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.
if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
if (gb->cartridge_type->mbc_type == GB_HUC3) {
return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t);
}
if (gb->cartridge_type->mbc_type == GB_TPP1) {
return gb->mbc_ram_size + sizeof(GB_tpp1_rtc_save_t);
}
GB_rtc_save_t rtc_save_size;
return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0);
}
int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size)
{
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.
if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
if (size < GB_save_battery_size(gb)) return EIO;
memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size);
if (gb->cartridge_type->mbc_type == GB_TPP1) {
buffer += gb->mbc_ram_size;
GB_tpp1_rtc_save_t rtc_save;
GB_fill_tpp1_save_data(gb, &rtc_save);
memcpy(buffer, &rtc_save, sizeof(rtc_save));
}
else if (gb->cartridge_type->mbc_type == GB_HUC3) {
buffer += gb->mbc_ram_size;
#ifdef GB_BIG_ENDIAN
GB_huc3_rtc_time_t rtc_save = {
__builtin_bswap64(gb->last_rtc_second),
__builtin_bswap16(gb->huc3_minutes),
__builtin_bswap16(gb->huc3_days),
__builtin_bswap16(gb->huc3_alarm_minutes),
__builtin_bswap16(gb->huc3_alarm_days),
gb->huc3_alarm_enabled,
};
#else
GB_huc3_rtc_time_t rtc_save = {
gb->last_rtc_second,
gb->huc3_minutes,
gb->huc3_days,
gb->huc3_alarm_minutes,
gb->huc3_alarm_days,
gb->huc3_alarm_enabled,
};
#endif
memcpy(buffer, &rtc_save, sizeof(rtc_save));
}
else if (gb->cartridge_type->has_rtc) {
GB_rtc_save_t rtc_save = {{{{0,}},},};
rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds;
rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes;
rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours;
rtc_save.vba64.rtc_real.days = gb->rtc_real.days;
rtc_save.vba64.rtc_real.high = gb->rtc_real.high;
rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds;
rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes;
rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours;
rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days;
rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high;
#ifdef GB_BIG_ENDIAN
rtc_save.vba64.last_rtc_second = __builtin_bswap64(time(NULL));
#else
rtc_save.vba64.last_rtc_second = time(NULL);
#endif
memcpy(buffer + gb->mbc_ram_size, &rtc_save.vba64, sizeof(rtc_save.vba64));
}
errno = 0;
return errno;
}
int GB_save_battery(GB_gameboy_t *gb, const char *path)
{
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.
if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
FILE *f = fopen(path, "wb");
if (!f) {
GB_log(gb, "Could not open battery save: %s.\n", strerror(errno));
return errno;
}
if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
fclose(f);
return EIO;
}
if (gb->cartridge_type->mbc_type == GB_TPP1) {
GB_tpp1_rtc_save_t rtc_save;
GB_fill_tpp1_save_data(gb, &rtc_save);
if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
fclose(f);
return EIO;
}
}
else if (gb->cartridge_type->mbc_type == GB_HUC3) {
#ifdef GB_BIG_ENDIAN
GB_huc3_rtc_time_t rtc_save = {
__builtin_bswap64(gb->last_rtc_second),
__builtin_bswap16(gb->huc3_minutes),
__builtin_bswap16(gb->huc3_days),
__builtin_bswap16(gb->huc3_alarm_minutes),
__builtin_bswap16(gb->huc3_alarm_days),
gb->huc3_alarm_enabled,
};
#else
GB_huc3_rtc_time_t rtc_save = {
gb->last_rtc_second,
gb->huc3_minutes,
gb->huc3_days,
gb->huc3_alarm_minutes,
gb->huc3_alarm_days,
gb->huc3_alarm_enabled,
};
#endif
if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
fclose(f);
return EIO;
}
}
else if (gb->cartridge_type->has_rtc) {
GB_rtc_save_t rtc_save = {{{{0,}},},};
rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds;
rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes;
rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours;
rtc_save.vba64.rtc_real.days = gb->rtc_real.days;
rtc_save.vba64.rtc_real.high = gb->rtc_real.high;
rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds;
rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes;
rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours;
rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days;
rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high;
#ifdef GB_BIG_ENDIAN
rtc_save.vba64.last_rtc_second = __builtin_bswap64(time(NULL));
#else
rtc_save.vba64.last_rtc_second = time(NULL);
#endif
if (fwrite(&rtc_save.vba64, 1, sizeof(rtc_save.vba64), f) != sizeof(rtc_save.vba64)) {
fclose(f);
return EIO;
}
}
errno = 0;
fclose(f);
return errno;
}
static void GB_load_tpp1_save_data(GB_gameboy_t *gb, const GB_tpp1_rtc_save_t *data)
{
gb->last_rtc_second = LE64(data->last_rtc_second);
unrolled for (unsigned i = 4; i--;) {
gb->rtc_real.data[i ^ 3] = data->rtc_data[i];
}
}
void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
{
memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size));
if (size <= gb->mbc_ram_size) {
goto reset_rtc;
}
if (gb->cartridge_type->mbc_type == GB_TPP1) {
GB_tpp1_rtc_save_t rtc_save;
if (size - gb->mbc_ram_size < sizeof(rtc_save)) {
goto reset_rtc;
}
memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save));
GB_load_tpp1_save_data(gb, &rtc_save);
if (gb->last_rtc_second > time(NULL)) {
/* We must reset RTC here, or it will not advance. */
goto reset_rtc;
}
return;
}
if (gb->cartridge_type->mbc_type == GB_HUC3) {
GB_huc3_rtc_time_t rtc_save;
if (size - gb->mbc_ram_size < sizeof(rtc_save)) {
goto reset_rtc;
}
memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save));
#ifdef GB_BIG_ENDIAN
gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second);
gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes);
gb->huc3_days = __builtin_bswap16(rtc_save.days);
gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes);
gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days);
gb->huc3_alarm_enabled = rtc_save.alarm_enabled;
#else
gb->last_rtc_second = rtc_save.last_rtc_second;
gb->huc3_minutes = rtc_save.minutes;
gb->huc3_days = rtc_save.days;
gb->huc3_alarm_minutes = rtc_save.alarm_minutes;
gb->huc3_alarm_days = rtc_save.alarm_days;
gb->huc3_alarm_enabled = rtc_save.alarm_enabled;
#endif
if (gb->last_rtc_second > time(NULL)) {
/* We must reset RTC here, or it will not advance. */
goto reset_rtc;
}
return;
}
GB_rtc_save_t rtc_save;
memcpy(&rtc_save, buffer + gb->mbc_ram_size, MIN(sizeof(rtc_save), size));
switch (size - gb->mbc_ram_size) {
case sizeof(rtc_save.sameboy_legacy):
memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real));
memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real));
gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second;
break;
case sizeof(rtc_save.vba32):
gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds;
gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes;
gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours;
gb->rtc_real.days = rtc_save.vba32.rtc_real.days;
gb->rtc_real.high = rtc_save.vba32.rtc_real.high;
gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds;
gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes;
gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours;
gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days;
gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high;
#ifdef GB_BIG_ENDIAN
gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second);
#else
gb->last_rtc_second = rtc_save.vba32.last_rtc_second;
#endif
break;
case sizeof(rtc_save.vba64):
gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds;
gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes;
gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours;
gb->rtc_real.days = rtc_save.vba64.rtc_real.days;
gb->rtc_real.high = rtc_save.vba64.rtc_real.high;
gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds;
gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes;
gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours;
gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days;
gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high;
#ifdef GB_BIG_ENDIAN
gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second);
#else
gb->last_rtc_second = rtc_save.vba64.last_rtc_second;
#endif
break;
default:
goto reset_rtc;
}
if (gb->last_rtc_second > time(NULL)) {
/* We must reset RTC here, or it will not advance. */
goto reset_rtc;
}
if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time,
so if the value we read is lower it means it wasn't
really RTC data. */
goto reset_rtc;
}
goto exit;
reset_rtc:
gb->last_rtc_second = time(NULL);
gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */
gb->huc3_days = 0xFFFF;
gb->huc3_minutes = 0xFFF;
gb->huc3_alarm_enabled = false;
exit:
return;
}
/* Loading will silently stop if the format is incomplete */
void GB_load_battery(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "rb");
if (!f) {
return;
}
if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
goto reset_rtc;
}
if (gb->cartridge_type->mbc_type == GB_TPP1) {
GB_tpp1_rtc_save_t rtc_save;
if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
goto reset_rtc;
}
GB_load_tpp1_save_data(gb, &rtc_save);
if (gb->last_rtc_second > time(NULL)) {
/* We must reset RTC here, or it will not advance. */
goto reset_rtc;
}
return;
}
if (gb->cartridge_type->mbc_type == GB_HUC3) {
GB_huc3_rtc_time_t rtc_save;
if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
goto reset_rtc;
}
#ifdef GB_BIG_ENDIAN
gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second);
gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes);
gb->huc3_days = __builtin_bswap16(rtc_save.days);
gb->huc3_alarm_minutes = __builtin_bswap16(rtc_save.alarm_minutes);
gb->huc3_alarm_days = __builtin_bswap16(rtc_save.alarm_days);
gb->huc3_alarm_enabled = rtc_save.alarm_enabled;
#else
gb->last_rtc_second = rtc_save.last_rtc_second;
gb->huc3_minutes = rtc_save.minutes;
gb->huc3_days = rtc_save.days;
gb->huc3_alarm_minutes = rtc_save.alarm_minutes;
gb->huc3_alarm_days = rtc_save.alarm_days;
gb->huc3_alarm_enabled = rtc_save.alarm_enabled;
#endif
if (gb->last_rtc_second > time(NULL)) {
/* We must reset RTC here, or it will not advance. */
goto reset_rtc;
}
return;
}
GB_rtc_save_t rtc_save;
switch (fread(&rtc_save, 1, sizeof(rtc_save), f)) {
case sizeof(rtc_save.sameboy_legacy):
memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real));
memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real));
gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second;
break;
case sizeof(rtc_save.vba32):
gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds;
gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes;
gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours;
gb->rtc_real.days = rtc_save.vba32.rtc_real.days;
gb->rtc_real.high = rtc_save.vba32.rtc_real.high;
gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds;
gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes;
gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours;
gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days;
gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high;
#ifdef GB_BIG_ENDIAN
gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second);
#else
gb->last_rtc_second = rtc_save.vba32.last_rtc_second;
#endif
break;
case sizeof(rtc_save.vba64):
gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds;
gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes;
gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours;
gb->rtc_real.days = rtc_save.vba64.rtc_real.days;
gb->rtc_real.high = rtc_save.vba64.rtc_real.high;
gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds;
gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes;
gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours;
gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days;
gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high;
#ifdef GB_BIG_ENDIAN
gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second);
#else
gb->last_rtc_second = rtc_save.vba64.last_rtc_second;
#endif
break;
default:
goto reset_rtc;
}
if (gb->last_rtc_second > time(NULL)) {
/* We must reset RTC here, or it will not advance. */
goto reset_rtc;
}
if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time,
so if the value we read is lower it means it wasn't
really RTC data. */
goto reset_rtc;
}
goto exit;
reset_rtc:
gb->last_rtc_second = time(NULL);
gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */
gb->huc3_days = 0xFFFF;
gb->huc3_minutes = 0xFFF;
gb->huc3_alarm_enabled = false;
exit:
fclose(f);
return;
}
uint8_t GB_run(GB_gameboy_t *gb)
{
gb->vblank_just_occured = false;
if (gb->sgb && gb->sgb->intro_animation < 140) {
/* On the SGB, the GB is halted after finishing the boot ROM.
Then, after the boot animation is almost done, it's reset.
Since the SGB HLE does not perform any header validity checks,
we just halt the CPU (with hacky code) until the correct time.
This ensures the Nintendo logo doesn't flash on screen, and
the game does "run in background" while the animation is playing. */
GB_display_run(gb, 228);
gb->cycles_since_last_sync += 228;
return 228;
}
GB_debugger_run(gb);
gb->cycles_since_run = 0;
GB_cpu_run(gb);
if (gb->vblank_just_occured) {
GB_debugger_handle_async_commands(gb);
GB_rewind_push(gb);
}
return gb->cycles_since_run;
}
uint64_t GB_run_frame(GB_gameboy_t *gb)
{
/* Configure turbo temporarily, the user wants to handle FPS capping manually. */
bool old_turbo = gb->turbo;
bool old_dont_skip = gb->turbo_dont_skip;
gb->turbo = true;
gb->turbo_dont_skip = true;
gb->cycles_since_last_sync = 0;
while (true) {
GB_run(gb);
if (gb->vblank_just_occured) {
break;
}
}
gb->turbo = old_turbo;
gb->turbo_dont_skip = old_dont_skip;
return gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */
}
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output)
{
gb->screen = output;
}
void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback)
{
gb->vblank_callback = callback;
}
void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback)
{
gb->log_callback = callback;
}
void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback)
{
#ifndef GB_DISABLE_DEBUGGER
if (gb->input_callback == default_input_callback) {
gb->async_input_callback = NULL;
}
gb->input_callback = callback;
#endif
}
void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback)
{
#ifndef GB_DISABLE_DEBUGGER
gb->async_input_callback = callback;
#endif
}
const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xaa, 0xaa, 0xaa}, {0xff, 0xff, 0xff}, {0xff, 0xff, 0xff}}};
const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xa5, 0x63}, {0xc6, 0xde, 0x8c}, {0xd2, 0xe6, 0xa6}}};
const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0e}, {0x3a, 0x4c, 0x3a}, {0x81, 0x8d, 0x66}, {0xc2, 0xce, 0x93}, {0xcf, 0xda, 0xac}}};
const GB_palette_t GB_PALETTE_GBL = {{{0x0a, 0x1c, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xb4, 0x95}, {0x7f, 0xe2, 0xc3}, {0x91, 0xea, 0xd0}}};
static void update_dmg_palette(GB_gameboy_t *gb)
{
const GB_palette_t *palette = gb->dmg_palette ?: &GB_PALETTE_GREY;
if (gb->rgb_encode_callback && !GB_is_cgb(gb)) {
gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] =
gb->rgb_encode_callback(gb, palette->colors[3].r, palette->colors[3].g, palette->colors[3].b);
gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] =
gb->rgb_encode_callback(gb, palette->colors[2].r, palette->colors[2].g, palette->colors[2].b);
gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] =
gb->rgb_encode_callback(gb, palette->colors[1].r, palette->colors[1].g, palette->colors[1].b);
gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] =
gb->rgb_encode_callback(gb, palette->colors[0].r, palette->colors[0].g, palette->colors[0].b);
// LCD off color
gb->background_palettes_rgb[4] =
gb->rgb_encode_callback(gb, palette->colors[4].r, palette->colors[4].g, palette->colors[4].b);
}
}
void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette)
{
gb->dmg_palette = palette;
update_dmg_palette(gb);
}
void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback)
{
gb->rgb_encode_callback = callback;
update_dmg_palette(gb);
for (unsigned i = 0; i < 32; i++) {
GB_palette_changed(gb, true, i * 2);
GB_palette_changed(gb, false, i * 2);
}
}
void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback)
{
gb->infrared_callback = callback;
}
void GB_set_infrared_input(GB_gameboy_t *gb, bool state)
{
gb->infrared_input = state;
}
void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback)
{
gb->rumble_callback = callback;
}
void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback)
{
gb->serial_transfer_bit_start_callback = callback;
}
void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback)
{
gb->serial_transfer_bit_end_callback = callback;
}
bool GB_serial_get_data_bit(GB_gameboy_t *gb)
{
if (gb->io_registers[GB_IO_SC] & 1) {
/* Internal Clock */
GB_log(gb, "Serial read request while using internal clock. \n");
return 0xFF;
}
return gb->io_registers[GB_IO_SB] & 0x80;
}
void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data)
{
if (gb->io_registers[GB_IO_SC] & 1) {
/* Internal Clock */
GB_log(gb, "Serial write request while using internal clock. \n");
return;
}
gb->io_registers[GB_IO_SB] <<= 1;
gb->io_registers[GB_IO_SB] |= data;
gb->serial_count++;
if (gb->serial_count == 8) {
gb->io_registers[GB_IO_IF] |= 8;
gb->serial_count = 0;
}
}
void GB_disconnect_serial(GB_gameboy_t *gb)
{
gb->serial_transfer_bit_start_callback = NULL;
gb->serial_transfer_bit_end_callback = NULL;
/* Reset any internally-emulated device. */
memset(&gb->printer, 0, sizeof(gb->printer));
memset(&gb->workboy, 0, sizeof(gb->workboy));
}
bool GB_is_inited(GB_gameboy_t *gb)
{
return gb->magic == state_magic();
}
bool GB_is_cgb(GB_gameboy_t *gb)
{
return (gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY;
}
bool GB_is_sgb(GB_gameboy_t *gb)
{
return (gb->model & ~GB_MODEL_PAL_BIT & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB || (gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB2;
}
bool GB_is_hle_sgb(GB_gameboy_t *gb)
{
return (gb->model & ~GB_MODEL_PAL_BIT) == GB_MODEL_SGB || gb->model == GB_MODEL_SGB2;
}
void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip)
{
gb->turbo = on;
gb->turbo_dont_skip = no_frame_skip;
}
void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled)
{
gb->disable_rendering = disabled;
}
void *GB_get_user_data(GB_gameboy_t *gb)
{
return gb->user_data;
}
void GB_set_user_data(GB_gameboy_t *gb, void *data)
{
gb->user_data = data;
}
static void reset_ram(GB_gameboy_t *gb)
{
switch (gb->model) {
case GB_MODEL_CGB_E:
case GB_MODEL_AGB: /* Unverified */
for (unsigned i = 0; i < gb->ram_size; i++) {
gb->ram[i] = GB_random();
}
break;
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
for (unsigned i = 0; i < gb->ram_size; i++) {
gb->ram[i] = GB_random();
if (i & 0x100) {
gb->ram[i] &= GB_random();
}
else {
gb->ram[i] |= GB_random();
}
}
break;
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
for (unsigned i = 0; i < gb->ram_size; i++) {
gb->ram[i] = 0x55;
gb->ram[i] ^= GB_random() & GB_random() & GB_random();
}
break;
case GB_MODEL_CGB_C:
for (unsigned i = 0; i < gb->ram_size; i++) {
if ((i & 0x808) == 0x800 || (i & 0x808) == 0x008) {
gb->ram[i] = 0;
}
else {
gb->ram[i] = GB_random() | GB_random() | GB_random() | GB_random();
}
}
break;
}
/* HRAM */
switch (gb->model) {
case GB_MODEL_CGB_C:
// case GB_MODEL_CGB_D:
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
for (unsigned i = 0; i < sizeof(gb->hram); i++) {
gb->hram[i] = GB_random();
}
break;
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
for (unsigned i = 0; i < sizeof(gb->hram); i++) {
if (i & 1) {
gb->hram[i] = GB_random() | GB_random() | GB_random();
}
else {
gb->hram[i] = GB_random() & GB_random() & GB_random();
}
}
break;
}
/* OAM */
switch (gb->model) {
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
/* Zero'd out by boot ROM anyway*/
break;
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified */
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
for (unsigned i = 0; i < 8; i++) {
if (i & 2) {
gb->oam[i] = GB_random() & GB_random() & GB_random();
}
else {
gb->oam[i] = GB_random() | GB_random() | GB_random();
}
}
for (unsigned i = 8; i < sizeof(gb->oam); i++) {
gb->oam[i] = gb->oam[i - 8];
}
break;
}
/* Wave RAM */
switch (gb->model) {
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_E:
case GB_MODEL_AGB:
/* Initialized by CGB-A and newer, 0s in CGB-0*/
break;
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC: {
uint8_t temp;
for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) {
if (i & 1) {
temp = GB_random() & GB_random() & GB_random();
}
else {
temp = GB_random() | GB_random() | GB_random();
}
gb->apu.wave_channel.wave_form[i * 2] = temp >> 4;
gb->apu.wave_channel.wave_form[i * 2 + 1] = temp & 0xF;
gb->io_registers[GB_IO_WAV_START + i] = temp;
}
break;
}
}
for (unsigned i = 0; i < sizeof(gb->extra_oam); i++) {
gb->extra_oam[i] = GB_random();
}
if (GB_is_cgb(gb)) {
for (unsigned i = 0; i < 64; i++) {
gb->background_palettes_data[i] = GB_random(); /* Doesn't really matter as the boot ROM overrides it anyway*/
gb->sprite_palettes_data[i] = GB_random();
}
for (unsigned i = 0; i < 32; i++) {
GB_palette_changed(gb, true, i * 2);
GB_palette_changed(gb, false, i * 2);
}
}
}
static void request_boot_rom(GB_gameboy_t *gb)
{
if (gb->boot_rom_load_callback) {
GB_boot_rom_t type = 0;
switch (gb->model) {
case GB_MODEL_DMG_B:
type = GB_BOOT_ROM_DMG;
break;
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
type = GB_BOOT_ROM_SGB;
break;
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
type = GB_BOOT_ROM_SGB2;
break;
case GB_MODEL_CGB_C:
case GB_MODEL_CGB_E:
type = GB_BOOT_ROM_CGB;
break;
case GB_MODEL_AGB:
type = GB_BOOT_ROM_AGB;
break;
}
gb->boot_rom_load_callback(gb, type);
}
}
void GB_reset(GB_gameboy_t *gb)
{
uint32_t mbc_ram_size = gb->mbc_ram_size;
GB_model_t model = gb->model;
memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved));
gb->model = model;
gb->version = GB_STRUCT_VERSION;
gb->mbc_rom_bank = 1;
gb->last_rtc_second = time(NULL);
gb->cgb_ram_bank = 1;
gb->io_registers[GB_IO_JOYP] = 0xCF;
gb->mbc_ram_size = mbc_ram_size;
if (GB_is_cgb(gb)) {
gb->ram_size = 0x1000 * 8;
gb->vram_size = 0x2000 * 2;
memset(gb->vram, 0, gb->vram_size);
gb->cgb_mode = true;
gb->object_priority = GB_OBJECT_PRIORITY_INDEX;
}
else {
gb->ram_size = 0x2000;
gb->vram_size = 0x2000;
memset(gb->vram, 0, gb->vram_size);
gb->object_priority = GB_OBJECT_PRIORITY_X;
update_dmg_palette(gb);
}
reset_ram(gb);
/* The serial interrupt always occur on the 0xF7th cycle of every 0x100 cycle since boot. */
gb->serial_cycles = 0x100-0xF7;
gb->io_registers[GB_IO_SC] = 0x7E;
/* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */
gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF;
gb->accessed_oam_row = -1;
if (GB_is_hle_sgb(gb)) {
if (!gb->sgb) {
gb->sgb = malloc(sizeof(*gb->sgb));
}
memset(gb->sgb, 0, sizeof(*gb->sgb));
memset(gb->sgb_intro_jingle_phases, 0, sizeof(gb->sgb_intro_jingle_phases));
gb->sgb_intro_sweep_phase = 0;
gb->sgb_intro_sweep_previous_sample = 0;
gb->sgb->intro_animation = -10;
gb->sgb->player_count = 1;
GB_sgb_load_default_data(gb);
}
else {
if (gb->sgb) {
free(gb->sgb);
gb->sgb = NULL;
}
}
/* Todo: Ugly, fixme, see comment in the timer state machine */
gb->div_state = 3;
GB_apu_update_cycles_per_sample(gb);
if (gb->nontrivial_jump_state) {
free(gb->nontrivial_jump_state);
gb->nontrivial_jump_state = NULL;
}
gb->magic = state_magic();
request_boot_rom(gb);
}
void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model)
{
gb->model = model;
if (GB_is_cgb(gb)) {
gb->ram = realloc(gb->ram, gb->ram_size = 0x1000 * 8);
gb->vram = realloc(gb->vram, gb->vram_size = 0x2000 * 2);
}
else {
gb->ram = realloc(gb->ram, gb->ram_size = 0x2000);
gb->vram = realloc(gb->vram, gb->vram_size = 0x2000);
}
if (gb->undo_state) {
free(gb->undo_state);
gb->undo_state = NULL;
}
GB_rewind_free(gb);
GB_reset(gb);
load_default_border(gb);
}
void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank)
{
/* Set size and bank to dummy pointers if not set */
size_t dummy_size;
uint16_t dummy_bank;
if (!size) {
size = &dummy_size;
}
if (!bank) {
bank = &dummy_bank;
}
switch (access) {
case GB_DIRECT_ACCESS_ROM:
*size = gb->rom_size;
*bank = gb->mbc_rom_bank;
return gb->rom;
case GB_DIRECT_ACCESS_RAM:
*size = gb->ram_size;
*bank = gb->cgb_ram_bank;
return gb->ram;
case GB_DIRECT_ACCESS_CART_RAM:
*size = gb->mbc_ram_size;
*bank = gb->mbc_ram_bank;
return gb->mbc_ram;
case GB_DIRECT_ACCESS_VRAM:
*size = gb->vram_size;
*bank = gb->cgb_vram_bank;
return gb->vram;
case GB_DIRECT_ACCESS_HRAM:
*size = sizeof(gb->hram);
*bank = 0;
return &gb->hram;
case GB_DIRECT_ACCESS_IO:
*size = sizeof(gb->io_registers);
*bank = 0;
return &gb->io_registers;
case GB_DIRECT_ACCESS_BOOTROM:
*size = GB_is_cgb(gb)? sizeof(gb->boot_rom) : 0x100;
*bank = 0;
return &gb->boot_rom;
case GB_DIRECT_ACCESS_OAM:
*size = sizeof(gb->oam);
*bank = 0;
return &gb->oam;
case GB_DIRECT_ACCESS_BGP:
*size = sizeof(gb->background_palettes_data);
*bank = 0;
return &gb->background_palettes_data;
case GB_DIRECT_ACCESS_OBP:
*size = sizeof(gb->sprite_palettes_data);
*bank = 0;
return &gb->sprite_palettes_data;
case GB_DIRECT_ACCESS_IE:
*size = sizeof(gb->interrupt_enable);
*bank = 0;
return &gb->interrupt_enable;
default:
*size = 0;
*bank = 0;
return NULL;
}
}
void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier)
{
gb->clock_multiplier = multiplier;
GB_apu_update_cycles_per_sample(gb);
}
uint32_t GB_get_clock_rate(GB_gameboy_t *gb)
{
return GB_get_unmultiplied_clock_rate(gb) * gb->clock_multiplier;
}
uint32_t GB_get_unmultiplied_clock_rate(GB_gameboy_t *gb)
{
if (gb->model & GB_MODEL_PAL_BIT) {
return SGB_PAL_FREQUENCY;
}
if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) {
return SGB_NTSC_FREQUENCY;
}
return CPU_FREQUENCY;
}
void GB_set_border_mode(GB_gameboy_t *gb, GB_border_mode_t border_mode)
{
if (gb->border_mode > GB_BORDER_ALWAYS) return;
gb->border_mode = border_mode;
}
unsigned GB_get_screen_width(GB_gameboy_t *gb)
{
switch (gb->border_mode) {
default:
case GB_BORDER_SGB:
return GB_is_hle_sgb(gb)? 256 : 160;
case GB_BORDER_NEVER:
return 160;
case GB_BORDER_ALWAYS:
return 256;
}
}
unsigned GB_get_screen_height(GB_gameboy_t *gb)
{
switch (gb->border_mode) {
default:
case GB_BORDER_SGB:
return GB_is_hle_sgb(gb)? 224 : 144;
case GB_BORDER_NEVER:
return 144;
case GB_BORDER_ALWAYS:
return 224;
}
}
unsigned GB_get_player_count(GB_gameboy_t *gb)
{
return GB_is_hle_sgb(gb)? gb->sgb->player_count : 1;
}
void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback)
{
gb->update_input_hint_callback = callback;
}
double GB_get_usual_frame_rate(GB_gameboy_t *gb)
{
return GB_get_clock_rate(gb) / (double)LCDC_PERIOD;
}
void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback)
{
gb->joyp_write_callback = callback;
}
void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback)
{
gb->icd_pixel_callback = callback;
}
void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback)
{
gb->icd_hreset_callback = callback;
}
void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback)
{
gb->icd_vreset_callback = callback;
}
void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback)
{
gb->boot_rom_load_callback = callback;
request_boot_rom(gb);
}
unsigned GB_time_to_alarm(GB_gameboy_t *gb)
{
if (gb->cartridge_type->mbc_type != GB_HUC3) return 0;
if (!gb->huc3_alarm_enabled) return 0;
if (!(gb->huc3_alarm_days & 0x2000)) return 0;
unsigned current_time = (gb->huc3_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_minutes * 60 + (time(NULL) % 60);
unsigned alarm_time = (gb->huc3_alarm_days & 0x1FFF) * 24 * 60 * 60 + gb->huc3_alarm_minutes * 60;
if (current_time > alarm_time) return 0;
return alarm_time - current_time;
}
void GB_set_rtc_mode(GB_gameboy_t *gb, GB_rtc_mode_t mode)
{
if (gb->rtc_mode != mode) {
gb->rtc_mode = mode;
gb->rtc_cycles = 0;
gb->last_rtc_second = time(NULL);
}
}