2016-03-30 20:07:55 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <sys/time.h>
|
2016-07-18 10:10:19 +00:00
|
|
|
#include <sys/select.h>
|
2016-03-30 20:07:55 +00:00
|
|
|
#include "gb.h"
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
2016-06-18 17:29:11 +00:00
|
|
|
GB_attributed_logv(gb, attributes, fmt, args);
|
2016-03-30 20:07:55 +00:00
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_log(GB_gameboy_t *gb,const char *fmt, ...)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
2016-06-18 17:29:11 +00:00
|
|
|
GB_attributed_logv(gb, 0, fmt, args);
|
2016-03-30 20:07:55 +00:00
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *default_input_callback(GB_gameboy_t *gb)
|
|
|
|
{
|
|
|
|
char *expression = NULL;
|
|
|
|
size_t size = 0;
|
2016-07-18 10:10:19 +00:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
2016-04-06 19:57:37 +00:00
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
if (!expression) {
|
|
|
|
return strdup("");
|
|
|
|
}
|
2016-04-06 19:57:37 +00:00
|
|
|
|
|
|
|
size_t length = strlen(expression);
|
|
|
|
if (expression[length - 1] == '\n') {
|
|
|
|
expression[length - 1] = 0;
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
return expression;
|
|
|
|
}
|
|
|
|
|
2016-07-18 10:10:19 +00:00
|
|
|
static char *default_async_input_callback(GB_gameboy_t *gb)
|
|
|
|
{
|
2016-08-20 14:51:17 +00:00
|
|
|
#ifndef _WIN32
|
2016-07-18 10:10:19 +00:00
|
|
|
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);
|
|
|
|
}
|
2016-08-20 14:51:17 +00:00
|
|
|
#endif
|
2016-07-18 10:10:19 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_init(GB_gameboy_t *gb)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
memset(gb, 0, sizeof(*gb));
|
|
|
|
gb->ram = malloc(gb->ram_size = 0x2000);
|
|
|
|
gb->vram = malloc(gb->vram_size = 0x2000);
|
|
|
|
|
|
|
|
gb->input_callback = default_input_callback;
|
2016-07-18 10:10:19 +00:00
|
|
|
gb->async_input_callback = default_async_input_callback;
|
2016-07-09 14:34:55 +00:00
|
|
|
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
2017-05-12 14:11:55 +00:00
|
|
|
gb->audio_quality = 4;
|
2017-04-17 17:16:17 +00:00
|
|
|
|
|
|
|
GB_reset(gb);
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_init_cgb(GB_gameboy_t *gb)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
memset(gb, 0, sizeof(*gb));
|
|
|
|
gb->ram = malloc(gb->ram_size = 0x2000 * 8);
|
|
|
|
gb->vram = malloc(gb->vram_size = 0x2000 * 2);
|
|
|
|
gb->is_cgb = true;
|
|
|
|
|
|
|
|
gb->input_callback = default_input_callback;
|
2016-07-18 10:10:19 +00:00
|
|
|
gb->async_input_callback = default_async_input_callback;
|
2016-07-09 14:34:55 +00:00
|
|
|
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
|
2017-05-12 14:11:55 +00:00
|
|
|
gb->audio_quality = 4;
|
2016-06-18 12:01:51 +00:00
|
|
|
|
2017-04-17 17:16:17 +00:00
|
|
|
GB_reset(gb);
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_free(GB_gameboy_t *gb)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
2017-02-24 13:14:00 +00:00
|
|
|
gb->magic = 0;
|
2016-03-30 20:07:55 +00:00
|
|
|
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->audio_buffer) {
|
|
|
|
free(gb->audio_buffer);
|
|
|
|
}
|
2016-04-06 21:25:41 +00:00
|
|
|
if (gb->breakpoints) {
|
|
|
|
free(gb->breakpoints);
|
|
|
|
}
|
2016-07-14 17:46:00 +00:00
|
|
|
for (int i = 0x200; i--;) {
|
2016-07-13 20:00:50 +00:00
|
|
|
if (gb->bank_symbols[i]) {
|
|
|
|
GB_map_free(gb->bank_symbols[i]);
|
|
|
|
}
|
|
|
|
}
|
2016-07-15 11:31:27 +00:00
|
|
|
for (int i = 0x400; i--;) {
|
|
|
|
if (gb->reversed_symbol_map.buckets[i]) {
|
|
|
|
GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next;
|
|
|
|
free(gb->reversed_symbol_map.buckets[i]);
|
|
|
|
gb->reversed_symbol_map.buckets[i] = next;
|
|
|
|
}
|
|
|
|
}
|
2017-02-24 13:14:00 +00:00
|
|
|
memset(gb, 0, sizeof(*gb));
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
int GB_load_boot_rom(GB_gameboy_t *gb, const char *path)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
2016-08-20 14:51:17 +00:00
|
|
|
FILE *f = fopen(path, "rb");
|
2016-03-30 20:07:55 +00:00
|
|
|
if (!f) return errno;
|
2016-06-18 17:29:11 +00:00
|
|
|
fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f);
|
2016-03-30 20:07:55 +00:00
|
|
|
fclose(f);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
int GB_load_rom(GB_gameboy_t *gb, const char *path)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
2016-08-20 14:51:17 +00:00
|
|
|
FILE *f = fopen(path, "rb");
|
2016-03-30 20:07:55 +00:00
|
|
|
if (!f) return errno;
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
|
|
gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */
|
2016-07-09 14:34:55 +00:00
|
|
|
/* 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++;
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
fseek(f, 0, SEEK_SET);
|
2017-04-17 17:16:17 +00:00
|
|
|
if (gb->rom) {
|
|
|
|
free(gb->rom);
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
gb->rom = malloc(gb->rom_size);
|
|
|
|
memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */
|
|
|
|
fread(gb->rom, gb->rom_size, 1, f);
|
|
|
|
fclose(f);
|
2016-07-09 16:25:13 +00:00
|
|
|
GB_configure_cart(gb);
|
2016-03-30 20:07:55 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-11 11:52:09 +00:00
|
|
|
static bool dump_section(FILE *f, const void *src, uint32_t size)
|
|
|
|
{
|
|
|
|
if (fwrite(&size, 1, sizeof(size), f) != sizeof(size)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fwrite(src, 1, size, f) != size) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
/* Todo: we need a sane and protable save state format. */
|
2016-06-18 17:29:11 +00:00
|
|
|
int GB_save_state(GB_gameboy_t *gb, const char *path)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
2017-03-24 11:39:39 +00:00
|
|
|
FILE *f = fopen(path, "wb");
|
2016-03-30 20:07:55 +00:00
|
|
|
if (!f) {
|
|
|
|
return errno;
|
|
|
|
}
|
|
|
|
|
2016-06-11 11:52:09 +00:00
|
|
|
if (fwrite(GB_GET_SECTION(gb, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
|
|
|
|
if (!DUMP_SECTION(gb, f, core_state)) goto error;
|
2016-08-03 20:31:10 +00:00
|
|
|
if (!DUMP_SECTION(gb, f, dma )) goto error;
|
2016-06-11 11:52:09 +00:00
|
|
|
if (!DUMP_SECTION(gb, f, mbc )) goto error;
|
|
|
|
if (!DUMP_SECTION(gb, f, hram )) goto error;
|
|
|
|
if (!DUMP_SECTION(gb, f, timing )) goto error;
|
|
|
|
if (!DUMP_SECTION(gb, f, apu )) goto error;
|
|
|
|
if (!DUMP_SECTION(gb, f, rtc )) goto error;
|
|
|
|
if (!DUMP_SECTION(gb, f, video )) goto error;
|
|
|
|
|
2016-03-30 20:07:55 +00:00
|
|
|
|
|
|
|
if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
|
2016-06-11 11:52:09 +00:00
|
|
|
goto error;
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
|
2016-06-11 11:52:09 +00:00
|
|
|
goto error;
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
|
2016-06-11 11:52:09 +00:00
|
|
|
goto error;
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
errno = 0;
|
2016-06-11 11:52:09 +00:00
|
|
|
|
|
|
|
error:
|
2016-03-30 20:07:55 +00:00
|
|
|
fclose(f);
|
|
|
|
return errno;
|
|
|
|
}
|
|
|
|
|
2016-06-11 11:52:09 +00:00
|
|
|
/* Best-effort read function for maximum future compatibility. */
|
|
|
|
static bool read_section(FILE *f, void *dest, uint32_t size)
|
|
|
|
{
|
|
|
|
uint32_t saved_size = 0;
|
|
|
|
if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (saved_size <= size) {
|
|
|
|
if (fread(dest, 1, saved_size, f) != saved_size) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (fread(dest, 1, size, f) != size) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
fseek(f, saved_size - size, SEEK_CUR);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
int GB_load_state(GB_gameboy_t *gb, const char *path)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
GB_gameboy_t save;
|
|
|
|
|
2016-06-11 11:52:09 +00:00
|
|
|
/* Every unread value should be kept the same. */
|
|
|
|
memcpy(&save, gb, sizeof(save));
|
|
|
|
|
2017-03-24 11:39:39 +00:00
|
|
|
FILE *f = fopen(path, "rb");
|
2016-03-30 20:07:55 +00:00
|
|
|
if (!f) {
|
|
|
|
return errno;
|
|
|
|
}
|
|
|
|
|
2016-06-11 11:52:09 +00:00
|
|
|
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
|
|
|
|
if (!READ_SECTION(&save, f, core_state)) goto error;
|
2016-08-03 20:31:10 +00:00
|
|
|
if (!READ_SECTION(&save, f, dma )) goto error;
|
2016-06-11 11:52:09 +00:00
|
|
|
if (!READ_SECTION(&save, f, mbc )) goto error;
|
|
|
|
if (!READ_SECTION(&save, f, hram )) goto error;
|
|
|
|
if (!READ_SECTION(&save, f, timing )) goto error;
|
|
|
|
if (!READ_SECTION(&save, f, apu )) goto error;
|
|
|
|
if (!READ_SECTION(&save, f, rtc )) goto error;
|
|
|
|
if (!READ_SECTION(&save, f, video )) goto error;
|
2016-03-30 20:07:55 +00:00
|
|
|
|
|
|
|
if (gb->magic != save.magic) {
|
2016-06-18 17:29:11 +00:00
|
|
|
GB_log(gb, "File is not a save state, or is from an incompatible operating system.\n");
|
2016-06-11 11:52:09 +00:00
|
|
|
errno = -1;
|
|
|
|
goto error;
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (gb->version != save.version) {
|
2016-06-18 17:29:11 +00:00
|
|
|
GB_log(gb, "Save state is for a different version of SameBoy.\n");
|
2016-06-11 11:52:09 +00:00
|
|
|
errno = -1;
|
|
|
|
goto error;
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (gb->mbc_ram_size != save.mbc_ram_size) {
|
2016-06-18 17:29:11 +00:00
|
|
|
GB_log(gb, "Save state has non-matching MBC RAM size.\n");
|
2016-06-11 11:52:09 +00:00
|
|
|
errno = -1;
|
|
|
|
goto error;
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (gb->ram_size != save.ram_size) {
|
2016-06-18 17:29:11 +00:00
|
|
|
GB_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n");
|
2016-06-11 11:52:09 +00:00
|
|
|
errno = -1;
|
|
|
|
goto error;
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (gb->vram_size != save.vram_size) {
|
2016-06-18 17:29:11 +00:00
|
|
|
GB_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n");
|
2016-06-11 11:52:09 +00:00
|
|
|
errno = -1;
|
|
|
|
goto error;
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
|
|
|
|
fclose(f);
|
|
|
|
return EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fread(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
|
|
|
|
fclose(f);
|
|
|
|
return EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fread(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
|
|
|
|
fclose(f);
|
|
|
|
return EIO;
|
|
|
|
}
|
|
|
|
|
2016-06-11 11:52:09 +00:00
|
|
|
memcpy(gb, &save, sizeof(save));
|
2016-03-30 20:07:55 +00:00
|
|
|
errno = 0;
|
2016-10-22 12:37:03 +00:00
|
|
|
|
|
|
|
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
|
|
|
|
gb->rumble_callback(gb, gb->rumble_state);
|
|
|
|
}
|
|
|
|
|
2016-06-11 11:52:09 +00:00
|
|
|
error:
|
2016-03-30 20:07:55 +00:00
|
|
|
fclose(f);
|
|
|
|
return errno;
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
int GB_save_battery(GB_gameboy_t *gb, const char *path)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
|
2016-04-16 11:09:56 +00:00
|
|
|
if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
|
2017-03-24 11:39:39 +00:00
|
|
|
FILE *f = fopen(path, "wb");
|
2016-03-30 20:07:55 +00:00
|
|
|
if (!f) {
|
|
|
|
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->has_rtc) {
|
2016-08-21 19:33:57 +00:00
|
|
|
if (fwrite(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) {
|
2016-03-30 20:07:55 +00:00
|
|
|
fclose(f);
|
|
|
|
return EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fwrite(&gb->last_rtc_second, 1, sizeof(gb->last_rtc_second), f) != sizeof(gb->last_rtc_second)) {
|
|
|
|
fclose(f);
|
|
|
|
return EIO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
fclose(f);
|
|
|
|
return errno;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Loading will silently stop if the format is incomplete */
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_load_battery(GB_gameboy_t *gb, const char *path)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
2017-03-24 11:39:39 +00:00
|
|
|
FILE *f = fopen(path, "rb");
|
2016-03-30 20:07:55 +00:00
|
|
|
if (!f) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
|
|
|
|
goto reset_rtc;
|
|
|
|
}
|
|
|
|
|
2016-08-21 19:33:57 +00:00
|
|
|
if (fread(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) {
|
2016-03-30 20:07:55 +00:00
|
|
|
goto reset_rtc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fread(&gb->last_rtc_second, 1, sizeof(gb->last_rtc_second), f) != sizeof(gb->last_rtc_second)) {
|
|
|
|
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);
|
2016-08-21 19:33:57 +00:00
|
|
|
gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */
|
2016-03-30 20:07:55 +00:00
|
|
|
exit:
|
|
|
|
fclose(f);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_run(GB_gameboy_t *gb)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
2016-06-18 17:29:11 +00:00
|
|
|
GB_debugger_run(gb);
|
|
|
|
GB_cpu_run(gb);
|
2016-10-21 21:49:32 +00:00
|
|
|
if (gb->vblank_just_occured) {
|
|
|
|
GB_update_joyp(gb);
|
|
|
|
GB_rtc_run(gb);
|
|
|
|
GB_debugger_handle_async_commands(gb);
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
}
|
|
|
|
|
2017-04-24 21:19:10 +00:00
|
|
|
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 * FRAME_LENGTH * LCDC_PERIOD;
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
gb->screen = output;
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
gb->vblank_callback = callback;
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
gb->log_callback = callback;
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
2016-07-18 10:10:19 +00:00
|
|
|
if (gb->input_callback == default_input_callback) {
|
|
|
|
gb->async_input_callback = NULL;
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
gb->input_callback = callback;
|
|
|
|
}
|
|
|
|
|
2016-07-17 21:39:43 +00:00
|
|
|
void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback)
|
|
|
|
{
|
|
|
|
gb->async_input_callback = callback;
|
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
2017-04-17 17:16:17 +00:00
|
|
|
if (!gb->rgb_encode_callback && !gb->is_cgb) {
|
2017-04-19 20:26:39 +00:00
|
|
|
gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] =
|
2017-04-17 17:16:17 +00:00
|
|
|
callback(gb, 0xFF, 0xFF, 0xFF);
|
2017-04-19 20:26:39 +00:00
|
|
|
gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] =
|
2017-04-17 17:16:17 +00:00
|
|
|
callback(gb, 0xAA, 0xAA, 0xAA);
|
2017-04-19 20:26:39 +00:00
|
|
|
gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] =
|
2017-04-17 17:16:17 +00:00
|
|
|
callback(gb, 0x55, 0x55, 0x55);
|
2017-04-19 20:26:39 +00:00
|
|
|
gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] =
|
2017-04-17 17:16:17 +00:00
|
|
|
callback(gb, 0, 0, 0);
|
|
|
|
}
|
2016-03-30 20:07:55 +00:00
|
|
|
gb->rgb_encode_callback = callback;
|
|
|
|
}
|
|
|
|
|
2016-07-18 19:05:11 +00:00
|
|
|
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;
|
2016-07-20 22:03:13 +00:00
|
|
|
gb->cycles_since_input_ir_change = 0;
|
|
|
|
gb->ir_queue_length = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change)
|
|
|
|
{
|
|
|
|
if (gb->ir_queue_length == GB_MAX_IR_QUEUE) {
|
|
|
|
GB_log(gb, "IR Queue is full\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
gb->ir_queue[gb->ir_queue_length++] = (GB_ir_queue_item_t){state, cycles_after_previous_change};
|
2016-07-18 19:05:11 +00:00
|
|
|
}
|
|
|
|
|
2016-10-22 12:37:03 +00:00
|
|
|
void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback)
|
|
|
|
{
|
|
|
|
gb->rumble_callback = callback;
|
|
|
|
}
|
|
|
|
|
2016-11-11 23:58:53 +00:00
|
|
|
void GB_set_serial_transfer_start_callback(GB_gameboy_t *gb, GB_serial_transfer_start_callback_t callback)
|
|
|
|
{
|
|
|
|
gb->serial_transfer_start_callback = callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GB_set_serial_transfer_end_callback(GB_gameboy_t *gb, GB_serial_transfer_end_callback_t callback)
|
|
|
|
{
|
|
|
|
gb->serial_transfer_end_callback = callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t GB_serial_get_data(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];
|
|
|
|
}
|
|
|
|
void GB_serial_set_data(GB_gameboy_t *gb, uint8_t 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] = data;
|
2016-11-12 22:42:05 +00:00
|
|
|
gb->io_registers[GB_IO_IF] |= 8;
|
2016-11-11 23:58:53 +00:00
|
|
|
}
|
|
|
|
|
2016-06-18 17:29:11 +00:00
|
|
|
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate)
|
2016-03-30 20:07:55 +00:00
|
|
|
{
|
|
|
|
if (gb->audio_buffer) {
|
|
|
|
free(gb->audio_buffer);
|
|
|
|
}
|
|
|
|
gb->buffer_size = sample_rate / 25; // 40ms delay
|
2016-09-13 14:33:48 +00:00
|
|
|
gb->audio_buffer = malloc(gb->buffer_size * sizeof(*gb->audio_buffer));
|
2016-03-30 20:07:55 +00:00
|
|
|
gb->sample_rate = sample_rate;
|
|
|
|
gb->audio_position = 0;
|
|
|
|
}
|
2017-01-13 19:27:37 +00:00
|
|
|
|
|
|
|
void GB_disconnect_serial(GB_gameboy_t *gb)
|
|
|
|
{
|
|
|
|
gb->serial_transfer_start_callback = NULL;
|
|
|
|
gb->serial_transfer_end_callback = NULL;
|
|
|
|
|
|
|
|
/* Reset any internally-emulated device. Currently, only the printer. */
|
|
|
|
memset(&gb->printer, 0, sizeof(gb->printer));
|
2017-02-24 13:14:00 +00:00
|
|
|
}
|
2017-04-17 17:16:17 +00:00
|
|
|
|
|
|
|
bool GB_is_inited(GB_gameboy_t *gb)
|
|
|
|
{
|
|
|
|
return gb->magic == 'SAME';
|
|
|
|
}
|
|
|
|
|
2017-04-19 20:26:39 +00:00
|
|
|
bool GB_is_cgb(GB_gameboy_t *gb)
|
|
|
|
{
|
|
|
|
return gb->is_cgb;
|
|
|
|
}
|
|
|
|
|
2017-04-19 18:55:58 +00:00
|
|
|
void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip)
|
2017-04-17 17:16:17 +00:00
|
|
|
{
|
|
|
|
gb->turbo = on;
|
2017-04-19 18:55:58 +00:00
|
|
|
gb->turbo_dont_skip = no_frame_skip;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled)
|
|
|
|
{
|
|
|
|
gb->disable_rendering = disabled;
|
2017-04-17 17:16:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GB_reset(GB_gameboy_t *gb)
|
|
|
|
{
|
|
|
|
bool cgb = gb->is_cgb;
|
|
|
|
memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved));
|
|
|
|
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] = 0xF;
|
|
|
|
gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF;
|
|
|
|
|
|
|
|
if (cgb) {
|
|
|
|
gb->ram_size = 0x2000 * 8;
|
|
|
|
memset(gb->ram, 0, gb->ram_size);
|
|
|
|
gb->vram_size = 0x2000 * 2;
|
|
|
|
memset(gb->vram, 0, gb->vram_size);
|
|
|
|
|
|
|
|
gb->is_cgb = true;
|
|
|
|
gb->cgb_mode = true;
|
|
|
|
|
|
|
|
gb->io_registers[GB_IO_SC] = 0x7C;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
gb->ram_size = 0x2000;
|
|
|
|
memset(gb->ram, 0, gb->ram_size);
|
|
|
|
gb->vram_size = 0x2000;
|
|
|
|
memset(gb->vram, 0, gb->vram_size);
|
|
|
|
|
|
|
|
if (gb->rgb_encode_callback) {
|
2017-04-19 20:26:39 +00:00
|
|
|
gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] =
|
2017-04-17 17:16:17 +00:00
|
|
|
gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
|
2017-04-19 20:26:39 +00:00
|
|
|
gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] =
|
2017-04-17 17:16:17 +00:00
|
|
|
gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA);
|
2017-04-19 20:26:39 +00:00
|
|
|
gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] =
|
2017-04-17 17:16:17 +00:00
|
|
|
gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55);
|
2017-04-19 20:26:39 +00:00
|
|
|
gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] =
|
2017-04-17 17:16:17 +00:00
|
|
|
gb->rgb_encode_callback(gb, 0, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
gb->io_registers[GB_IO_SC] = 0x7E;
|
|
|
|
}
|
|
|
|
gb->magic = (uintptr_t)'SAME';
|
|
|
|
}
|
|
|
|
|
|
|
|
void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb)
|
|
|
|
{
|
|
|
|
if (is_cgb) {
|
|
|
|
gb->ram = realloc(gb->ram, gb->ram_size = 0x2000 * 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);
|
|
|
|
}
|
|
|
|
gb->is_cgb = is_cgb;
|
|
|
|
GB_reset(gb);
|
|
|
|
|
|
|
|
}
|
2017-04-19 20:26:39 +00:00
|
|
|
|
|
|
|
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 */
|
|
|
|
if (!size) {
|
|
|
|
size = alloca(sizeof(size));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!bank) {
|
|
|
|
bank = alloca(sizeof(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? 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;
|
|
|
|
default:
|
|
|
|
*size = 0;
|
|
|
|
*bank = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|