sameboy: initial commit

This commit is contained in:
nattthebear 2017-07-16 12:23:41 -04:00
parent f60a4a07e7
commit 87f7183a27
29 changed files with 8835 additions and 0 deletions

55
waterbox/sameboy/Makefile Normal file
View File

@ -0,0 +1,55 @@
CC = x86_64-nt64-midipix-gcc
CPP = x86_64-nt64-midipix-g++
FLAGS:=-Wall -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=implicit-function-declaration \
-Wno-multichar \
-fomit-frame-pointer -fvisibility=hidden \
-O0 -g
CCFLAGS:=$(FLAGS) -Ilib \
-I../emulibc \
-std=gnu99 \
-DLSB_FIRST -D_GNU_SOURCE -DGB_INTERNAL
CPPFLAGS:=$(FLAGS) -DSPC_NO_COPY_STATE_FUNCS
TARGET = sameboy.wbx
LDFLAGS = -Wl,--dynamicbase,--export-all-symbols
ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
C_SRCS:=$(shell find $(ROOT_DIR) -type f -name '*.c')
CPP_SRCS:=$(shell find $(ROOT_DIR) -type f -name '*.cpp')
SRCS:=$(C_SRCS) $(CPP_SRCS)
OBJ_DIR:=$(ROOT_DIR)/obj
__OBJS:=$(SRCS:.c=.o)
_OBJS:=$(__OBJS:.cpp=.opp)
OBJS:=$(patsubst $(ROOT_DIR)%,$(OBJ_DIR)%,$(_OBJS))
$(OBJ_DIR)/%.o: %.c
@mkdir -p $(@D)
@$(CC) -c -o $@ $< $(CCFLAGS)
$(OBJ_DIR)/%.opp: %.cpp
@mkdir -p $(@D)
@$(CPP) -c -o $@ $< $(CPPFLAGS)
all: $(TARGET)
.PHONY: clean all
$(TARGET).in: $(OBJS)
@$(CPP) -o $@ $(LDFLAGS) $(FLAGS) $(OBJS) ../emulibc/libemuhost.so
$(TARGET): $(TARGET).in
# strip $< -o $@ -R /4 -R /14 -R /29 -R /41 -R /55 -R /67 -R /78 -R /89 -R /104
cp $< $@
clean:
rm -rf $(OBJ_DIR)
rm -f $(TARGET).in
rm -f $(TARGET)
#install:
# $(CP) $(TARGET) $(DEST_$(ARCH))

497
waterbox/sameboy/apu.c Normal file
View File

@ -0,0 +1,497 @@
#include <stdint.h>
#include <math.h>
#include <string.h>
#include "gb.h"
#undef max
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
#undef min
#define min(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; })
#define APU_FREQUENCY 0x80000
static int16_t generate_square(uint64_t phase, uint32_t wave_length, int16_t amplitude, uint8_t duty)
{
if (!wave_length) return 0;
if (phase % wave_length > wave_length * duty / 8) {
return amplitude;
}
return 0;
}
static int16_t generate_wave(uint64_t phase, uint32_t wave_length, int16_t amplitude, int8_t *wave, uint8_t shift)
{
if (!wave_length) wave_length = 1;
phase = phase % wave_length;
return ((wave[(int)(phase * 32 / wave_length)]) >> shift) * (int)amplitude / 0xF;
}
static int16_t generate_noise(int16_t amplitude, uint16_t lfsr)
{
if (lfsr & 1) {
return amplitude;
}
return 0;
}
static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit)
{
bool xor = (lfsr & 1) ^ ((lfsr & 2) >> 1);
lfsr >>= 1;
if (xor) {
lfsr |= 0x4000;
}
if (uses_7_bit) {
lfsr &= ~0x40;
if (xor) {
lfsr |= 0x40;
}
}
return lfsr;
}
/* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with
these tests in mind. */
static void GB_apu_run_internal(GB_gameboy_t *gb)
{
while (!__sync_bool_compare_and_swap(&gb->apu_lock, false, true));
uint32_t steps = gb->apu.apu_cycles / (CPU_FREQUENCY/APU_FREQUENCY);
if (!steps) goto exit;
gb->apu.apu_cycles %= (CPU_FREQUENCY/APU_FREQUENCY);
for (uint8_t i = 0; i < 4; i++) {
/* Phase */
gb->apu.wave_channels[i].phase += steps;
while (gb->apu.wave_channels[i].wave_length && gb->apu.wave_channels[i].phase >= gb->apu.wave_channels[i].wave_length) {
if (i == 3) {
gb->apu.lfsr = step_lfsr(gb->apu.lfsr, gb->apu.lfsr_7_bit);
}
gb->apu.wave_channels[i].phase -= gb->apu.wave_channels[i].wave_length;
}
/* Stop on Length */
if (gb->apu.wave_channels[i].stop_on_length) {
if (gb->apu.wave_channels[i].sound_length > 0) {
gb->apu.wave_channels[i].sound_length -= steps;
}
if (gb->apu.wave_channels[i].sound_length <= 0) {
gb->apu.wave_channels[i].amplitude = 0;
gb->apu.wave_channels[i].is_playing = false;
gb->apu.wave_channels[i].sound_length = i == 2? APU_FREQUENCY : APU_FREQUENCY / 4;
}
}
}
gb->apu.envelope_step_timer += steps;
while (gb->apu.envelope_step_timer >= APU_FREQUENCY / 64) {
gb->apu.envelope_step_timer -= APU_FREQUENCY / 64;
for (uint8_t i = 0; i < 4; i++) {
if (gb->apu.wave_channels[i].envelope_steps && !--gb->apu.wave_channels[i].cur_envelope_steps) {
gb->apu.wave_channels[i].amplitude = min(max(gb->apu.wave_channels[i].amplitude + gb->apu.wave_channels[i].envelope_direction * CH_STEP, 0), MAX_CH_AMP);
gb->apu.wave_channels[i].cur_envelope_steps = gb->apu.wave_channels[i].envelope_steps;
}
}
}
gb->apu.sweep_step_timer += steps;
while (gb->apu.sweep_step_timer >= APU_FREQUENCY / 128) {
gb->apu.sweep_step_timer -= APU_FREQUENCY / 128;
if (gb->apu.wave_channels[0].sweep_steps && !--gb->apu.wave_channels[0].cur_sweep_steps) {
// Convert back to GB format
uint16_t temp = 2048 - gb->apu.wave_channels[0].wave_length / (APU_FREQUENCY / 131072);
// Apply sweep
temp = temp + gb->apu.wave_channels[0].sweep_direction *
(temp / (1 << gb->apu.wave_channels[0].sweep_shift));
if (temp > 2047) {
temp = 0;
}
// Back to frequency
gb->apu.wave_channels[0].wave_length = (2048 - temp) * (APU_FREQUENCY / 131072);
gb->apu.wave_channels[0].cur_sweep_steps = gb->apu.wave_channels[0].sweep_steps;
}
}
exit:
gb->apu_lock = false;
}
void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples)
{
GB_apu_run_internal(gb);
samples->left = samples->right = 0;
if (!gb->apu.global_enable) {
return;
}
gb->io_registers[GB_IO_PCM_12] = 0;
gb->io_registers[GB_IO_PCM_34] = 0;
{
int16_t sample = generate_square(gb->apu.wave_channels[0].phase,
gb->apu.wave_channels[0].wave_length,
gb->apu.wave_channels[0].amplitude,
gb->apu.wave_channels[0].duty);
if (gb->apu.wave_channels[0].left_on ) samples->left += sample;
if (gb->apu.wave_channels[0].right_on) samples->right += sample;
gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP;
}
{
int16_t sample = generate_square(gb->apu.wave_channels[1].phase,
gb->apu.wave_channels[1].wave_length,
gb->apu.wave_channels[1].amplitude,
gb->apu.wave_channels[1].duty);
if (gb->apu.wave_channels[1].left_on ) samples->left += sample;
if (gb->apu.wave_channels[1].right_on) samples->right += sample;
gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4;
}
if (gb->apu.wave_channels[2].is_playing)
{
int16_t sample = generate_wave(gb->apu.wave_channels[2].phase,
gb->apu.wave_channels[2].wave_length,
MAX_CH_AMP,
gb->apu.wave_form,
gb->apu.wave_shift);
if (gb->apu.wave_channels[2].left_on ) samples->left += sample;
if (gb->apu.wave_channels[2].right_on) samples->right += sample;
gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP;
}
{
int16_t sample = generate_noise(gb->apu.wave_channels[3].amplitude,
gb->apu.lfsr);
if (gb->apu.wave_channels[3].left_on ) samples->left += sample;
if (gb->apu.wave_channels[3].right_on) samples->right += sample;
gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4;
}
samples->left = (int) samples->left * gb->apu.left_volume / 7;
samples->right = (int) samples->right * gb->apu.right_volume / 7;
}
void GB_apu_run(GB_gameboy_t *gb)
{
if (gb->sample_rate == 0) {
if (gb->apu.apu_cycles > 0xFF00) {
GB_sample_t dummy;
GB_apu_get_samples_and_update_pcm_regs(gb, &dummy);
}
return;
}
while (gb->audio_copy_in_progress);
double ticks_per_sample = (double) CPU_FREQUENCY / gb->sample_rate;
if (gb->audio_quality == 0) {
GB_sample_t sample;
GB_apu_get_samples_and_update_pcm_regs(gb, &sample);
gb->current_supersample.left += sample.left;
gb->current_supersample.right += sample.right;
gb->n_subsamples++;
}
else if (gb->audio_quality != 1) {
double ticks_per_subsample = ticks_per_sample / gb->audio_quality;
if (ticks_per_subsample < 1) {
ticks_per_subsample = 1;
}
if (gb->apu_subsample_cycles > ticks_per_subsample) {
gb->apu_subsample_cycles -= ticks_per_subsample;
}
GB_sample_t sample;
GB_apu_get_samples_and_update_pcm_regs(gb, &sample);
gb->current_supersample.left += sample.left;
gb->current_supersample.right += sample.right;
gb->n_subsamples++;
}
if (gb->apu_sample_cycles > ticks_per_sample) {
gb->apu_sample_cycles -= ticks_per_sample;
if (gb->audio_position == gb->buffer_size) {
/*
if (!gb->turbo) {
GB_log(gb, "Audio overflow\n");
}
*/
}
else {
if (gb->audio_quality == 1) {
GB_apu_get_samples_and_update_pcm_regs(gb, &gb->audio_buffer[gb->audio_position++]);
}
else {
gb->audio_buffer[gb->audio_position].left = round(gb->current_supersample.left / gb->n_subsamples);
gb->audio_buffer[gb->audio_position].right = round(gb->current_supersample.right / gb->n_subsamples);
gb->n_subsamples = 0;
gb->current_supersample = (GB_double_sample_t){0, };
gb->audio_position++;
}
}
}
}
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count)
{
gb->audio_copy_in_progress = true;
if (!gb->audio_stream_started) {
// Intentionally fail the first copy to sync the stream with the Gameboy.
gb->audio_stream_started = true;
gb->audio_position = 0;
}
if (count > gb->audio_position) {
// GB_log(gb, "Audio underflow: %d\n", count - gb->audio_position);
if (gb->audio_position != 0) {
for (unsigned i = 0; i < count - gb->audio_position; i++) {
dest[gb->audio_position + i] = gb->audio_buffer[gb->audio_position - 1];
}
}
else {
memset(dest + gb->audio_position, 0, (count - gb->audio_position) * sizeof(*gb->audio_buffer));
}
count = gb->audio_position;
}
memcpy(dest, gb->audio_buffer, count * sizeof(*gb->audio_buffer));
memmove(gb->audio_buffer, gb->audio_buffer + count, (gb->audio_position - count) * sizeof(*gb->audio_buffer));
gb->audio_position -= count;
gb->audio_copy_in_progress = false;
}
void GB_apu_init(GB_gameboy_t *gb)
{
memset(&gb->apu, 0, sizeof(gb->apu));
gb->apu.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 4;
gb->apu.lfsr = 0x7FFF;
gb->apu.left_volume = 7;
gb->apu.right_volume = 7;
for (int i = 0; i < 4; i++) {
gb->apu.wave_channels[i].left_on = gb->apu.wave_channels[i].right_on = 1;
}
}
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
{
GB_apu_run_internal(gb);
if (reg == GB_IO_NR52) {
uint8_t value = 0;
for (int i = 0; i < 4; i++) {
value >>= 1;
if (gb->apu.wave_channels[i].is_playing) {
value |= 0x8;
}
}
if (gb->apu.global_enable) {
value |= 0x80;
}
value |= 0x70;
return value;
}
static const char read_mask[GB_IO_WAV_END - GB_IO_NR10 + 1] = {
/* NRX0 NRX1 NRX2 NRX3 NRX4 */
0x80, 0x3F, 0x00, 0xFF, 0xBF, // NR1X
0xFF, 0x3F, 0x00, 0xFF, 0xBF, // NR2X
0x7F, 0xFF, 0x9F, 0xFF, 0xBF, // NR3X
0xFF, 0xFF, 0x00, 0x00, 0xBF, // NR4X
0x00, 0x00, 0x70, 0xFF, 0xFF, // NR5X
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Unused
// Wave RAM
0, /* ... */
};
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.wave_channels[2].is_playing) {
if (gb->apu.wave_channels[2].wave_length == 0) {
return gb->apu.wave_form[0];
}
gb->apu.wave_channels[2].phase %= gb->apu.wave_channels[2].wave_length;
return gb->apu.wave_form[(int)(gb->apu.wave_channels[2].phase * 32 / gb->apu.wave_channels[2].wave_length)];
}
return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10];
}
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
{
GB_apu_run_internal(gb);
static const uint8_t duties[] = {1, 2, 4, 6}; /* Values are in 1/8 */
uint8_t channel = 0;
if (!gb->apu.global_enable && reg != GB_IO_NR52) {
return;
}
gb->io_registers[reg] = value;
switch (reg) {
case GB_IO_NR10:
case GB_IO_NR11:
case GB_IO_NR12:
case GB_IO_NR13:
case GB_IO_NR14:
channel = 0;
break;
case GB_IO_NR21:
case GB_IO_NR22:
case GB_IO_NR23:
case GB_IO_NR24:
channel = 1;
break;
case GB_IO_NR33:
case GB_IO_NR34:
channel = 2;
break;
case GB_IO_NR41:
case GB_IO_NR42:
channel = 3;
default:
break;
}
switch (reg) {
case GB_IO_NR10:
gb->apu.wave_channels[channel].sweep_direction = value & 8? -1 : 1;
gb->apu.wave_channels[channel].cur_sweep_steps =
gb->apu.wave_channels[channel].sweep_steps = (value & 0x70) >> 4;
gb->apu.wave_channels[channel].sweep_shift = value & 7;
break;
case GB_IO_NR11:
case GB_IO_NR21:
case GB_IO_NR41:
gb->apu.wave_channels[channel].duty = duties[value >> 6];
gb->apu.wave_channels[channel].sound_length = (64 - (value & 0x3F)) * (APU_FREQUENCY / 256);
if (gb->apu.wave_channels[channel].sound_length == 0) {
gb->apu.wave_channels[channel].is_playing = false;
}
break;
case GB_IO_NR12:
case GB_IO_NR22:
case GB_IO_NR42:
gb->apu.wave_channels[channel].start_amplitude =
gb->apu.wave_channels[channel].amplitude = CH_STEP * (value >> 4);
if (value >> 4 == 0) {
gb->apu.wave_channels[channel].is_playing = false;
}
gb->apu.wave_channels[channel].envelope_direction = value & 8? 1 : -1;
gb->apu.wave_channels[channel].cur_envelope_steps =
gb->apu.wave_channels[channel].envelope_steps = value & 7;
break;
case GB_IO_NR13:
case GB_IO_NR23:
case GB_IO_NR33:
gb->apu.wave_channels[channel].NRX3_X4_temp = (gb->apu.wave_channels[channel].NRX3_X4_temp & 0xFF00) | value;
gb->apu.wave_channels[channel].wave_length = (2048 - gb->apu.wave_channels[channel].NRX3_X4_temp) * (APU_FREQUENCY / 131072);
if (channel == 2) {
gb->apu.wave_channels[channel].wave_length *= 2;
}
break;
case GB_IO_NR14:
case GB_IO_NR24:
case GB_IO_NR34:
gb->apu.wave_channels[channel].stop_on_length = value & 0x40;
if ((value & 0x80) && (channel != 2 || gb->apu.wave_enable)) {
gb->apu.wave_channels[channel].is_playing = true;
gb->apu.wave_channels[channel].phase = 0;
gb->apu.wave_channels[channel].amplitude = gb->apu.wave_channels[channel].start_amplitude;
gb->apu.wave_channels[channel].cur_envelope_steps = gb->apu.wave_channels[channel].envelope_steps;
}
gb->apu.wave_channels[channel].NRX3_X4_temp = (gb->apu.wave_channels[channel].NRX3_X4_temp & 0xFF) | ((value & 0x7) << 8);
gb->apu.wave_channels[channel].wave_length = (2048 - gb->apu.wave_channels[channel].NRX3_X4_temp) * (APU_FREQUENCY / 131072);
if (channel == 2) {
gb->apu.wave_channels[channel].wave_length *= 2;
}
break;
case GB_IO_NR30:
gb->apu.wave_enable = value & 0x80;
gb->apu.wave_channels[2].is_playing &= gb->apu.wave_enable;
break;
case GB_IO_NR31:
gb->apu.wave_channels[2].sound_length = (256 - value) * (APU_FREQUENCY / 256);
if (gb->apu.wave_channels[2].sound_length == 0) {
gb->apu.wave_channels[2].is_playing = false;
}
break;
case GB_IO_NR32:
gb->apu.wave_shift = ((value >> 5) + 3) & 3;
if (gb->apu.wave_shift == 3) {
gb->apu.wave_shift = 4;
}
break;
case GB_IO_NR43:
{
double r = value & 0x7;
if (r == 0) r = 0.5;
uint8_t s = value >> 4;
gb->apu.wave_channels[3].wave_length = r * (1 << s) * (APU_FREQUENCY / 262144) ;
gb->apu.lfsr_7_bit = value & 0x8;
break;
}
case GB_IO_NR44:
gb->apu.wave_channels[3].stop_on_length = value & 0x40;
if (value & 0x80) {
gb->apu.wave_channels[3].is_playing = true;
gb->apu.lfsr = 0x7FFF;
gb->apu.wave_channels[3].amplitude = gb->apu.wave_channels[3].start_amplitude;
gb->apu.wave_channels[3].cur_envelope_steps = gb->apu.wave_channels[3].envelope_steps;
}
break;
case GB_IO_NR50:
gb->apu.left_volume = (value & 7);
gb->apu.right_volume = ((value >> 4) & 7);
break;
case GB_IO_NR51:
for (int i = 0; i < 4; i++) {
gb->apu.wave_channels[i].left_on = value & 1;
gb->apu.wave_channels[i].right_on = value & 0x10;
value >>= 1;
}
break;
case GB_IO_NR52:
if ((value & 0x80) && !gb->apu.global_enable) {
GB_apu_init(gb);
gb->apu.global_enable = true;
}
else if (!(value & 0x80) && gb->apu.global_enable) {
memset(&gb->apu, 0, sizeof(gb->apu));
memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10);
}
break;
default:
if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) {
gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4;
gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF;
}
break;
}
}
void GB_set_audio_quality(GB_gameboy_t *gb, unsigned quality)
{
gb->audio_quality = quality;
}
unsigned GB_apu_get_current_buffer_length(GB_gameboy_t *gb)
{
return gb->audio_position;
}

78
waterbox/sameboy/apu.h Normal file
View File

@ -0,0 +1,78 @@
#ifndef apu_h
#define apu_h
#include <stdbool.h>
#include <stdint.h>
#include "gb_struct_def.h"
/* Divides nicely and never overflows with 4 channels */
#define MAX_CH_AMP 0x1E00
#define CH_STEP (0x1E00/0xF)
typedef struct
{
int16_t left;
int16_t right;
} GB_sample_t;
typedef struct
{
double left;
double right;
} GB_double_sample_t;
/* Not all used on all channels */
/* All lengths are in APU ticks */
typedef struct
{
uint32_t phase;
uint32_t wave_length;
int32_t sound_length;
bool stop_on_length;
uint8_t duty;
int16_t amplitude;
int16_t start_amplitude;
uint8_t envelope_steps;
uint8_t cur_envelope_steps;
int8_t envelope_direction;
uint8_t sweep_steps;
uint8_t cur_sweep_steps;
int8_t sweep_direction;
uint8_t sweep_shift;
bool is_playing;
uint16_t NRX3_X4_temp;
bool left_on;
bool right_on;
} GB_apu_channel_t;
typedef struct
{
uint16_t apu_cycles;
bool global_enable;
uint32_t envelope_step_timer;
uint32_t sweep_step_timer;
int8_t wave_form[32];
uint8_t wave_shift;
bool wave_enable;
uint16_t lfsr;
bool lfsr_7_bit;
uint8_t left_volume;
uint8_t right_volume;
GB_apu_channel_t wave_channels[4];
} GB_apu_t;
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate);
/* Quality is the number of subsamples per sampling, for the sake of resampling.
1 means on resampling at all, 0 is maximum quality. Default is 4. */
void GB_set_audio_quality(GB_gameboy_t *gb, unsigned quality);
void GB_apu_copy_buffer(GB_gameboy_t *gb, GB_sample_t *dest, unsigned int count);
unsigned GB_apu_get_current_buffer_length(GB_gameboy_t *gb);
#ifdef GB_INTERNAL
void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);
uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg);
void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples);
void GB_apu_init(GB_gameboy_t *gb);
void GB_apu_run(GB_gameboy_t *gb);
#endif
#endif /* apu_h */

149
waterbox/sameboy/camera.c Normal file
View File

@ -0,0 +1,149 @@
#include "gb.h"
static int noise_seed = 0;
/* This is not a complete emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported.
We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */
static uint8_t generate_noise(uint8_t x, uint8_t y)
{
int value = (x + y * 128 + noise_seed);
uint8_t *data = (uint8_t *) &value;
unsigned hash = 0;
while ((int *) data != &value + 1) {
hash ^= (*data << 8);
if (hash & 0x8000) {
hash ^= 0x8a00;
hash ^= *data;
}
data++;
hash <<= 1;
}
return (hash >> 8);
}
static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y)
{
if (x >= 128) {
x = 0;
}
if (y >= 112) {
y = 0;
}
long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (generate_noise(x, y));
static const double gain_values[] =
{0.8809390, 0.9149149, 0.9457498, 0.9739758,
1.0000000, 1.0241412, 1.0466537, 1.0677433,
1.0875793, 1.1240310, 1.1568911, 1.1868043,
1.2142561, 1.2396208, 1.2743837, 1.3157323,
1.3525190, 1.3856512, 1.4157897, 1.4434309,
1.4689574, 1.4926697, 1.5148087, 1.5355703,
1.5551159, 1.5735801, 1.5910762, 1.6077008,
1.6235366, 1.6386550, 1.6531183, 1.6669808};
/* Multiply color by gain value */
color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x1F];
/* Color is multiplied by the exposure register to simulate exposure. */
color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000;
return color;
}
uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr)
{
if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) {
/* Forbid reading the image while the camera is busy. */
return 0xFF;
}
uint8_t tile_x = addr / 0x10 % 0x10;
uint8_t tile_y = addr / 0x10 / 0x10;
uint8_t y = ((addr >> 1) & 0x7) + tile_y * 8;
uint8_t bit = addr & 1;
uint8_t ret = 0;
for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) {
long color = get_processed_color(gb, x, y);
static const double edge_enhancement_ratios[] = {0.5, 0.75, 1, 1.25, 2, 3, 4, 5};
double edge_enhancement_ratio = edge_enhancement_ratios[(gb->camera_registers[GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE] >> 4) & 0x7];
if ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0xE0) == 0xE0) {
color += (color * 4) * edge_enhancement_ratio;
color -= get_processed_color(gb, x - 1, y) * edge_enhancement_ratio;
color -= get_processed_color(gb, x + 1, y) * edge_enhancement_ratio;
color -= get_processed_color(gb, x, y - 1) * edge_enhancement_ratio;
color -= get_processed_color(gb, x, y + 1) * edge_enhancement_ratio;
}
/* The camera's registers are used as a threshold pattern, which defines the dithering */
uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START;
if (color < gb->camera_registers[pattern_base]) {
color = 3;
}
else if (color < gb->camera_registers[pattern_base + 1]) {
color = 2;
}
else if (color < gb->camera_registers[pattern_base + 2]) {
color = 1;
}
else {
color = 0;
}
ret <<= 1;
ret |= (color >> bit) & 1;
}
return ret;
}
void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback)
{
gb->camera_get_pixel_callback = callback;
}
void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback)
{
gb->camera_update_request_callback = callback;
}
void GB_camera_updated(GB_gameboy_t *gb)
{
gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] &= ~1;
}
void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
addr &= 0x7F;
if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) {
value &= 0x7;
noise_seed = rand();
if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && gb->camera_update_request_callback) {
/* If no callback is set, ignore the write as if the camera is instantly done */
gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] |= 1;
gb->camera_update_request_callback(gb);
}
}
else {
if (addr >= 0x36) {
GB_log(gb, "Wrote invalid camera register %02x: %2x\n", addr, value);
return;
}
gb->camera_registers[addr] = value;
}
}
uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr)
{
if ((addr & 0x7F) == 0) {
return gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS];
}
return 0;
}

29
waterbox/sameboy/camera.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef camera_h
#define camera_h
#include <stdint.h>
#include "gb_struct_def.h"
typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y);
typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb);
enum {
GB_CAMERA_SHOOT_AND_1D_FLAGS = 0,
GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS = 1,
GB_CAMERA_EXPOSURE_HIGH = 2,
GB_CAMERA_EXPOSURE_LOW = 3,
GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE = 4,
GB_CAMERA_DITHERING_PATTERN_START = 6,
GB_CAMERA_DITHERING_PATTERN_END = 0x35,
};
uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr);
void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback);
void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback);
void GB_camera_updated(GB_gameboy_t *gb);
void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr);
#endif

1850
waterbox/sameboy/debugger.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
#ifndef debugger_h
#define debugger_h
#include <stdbool.h>
#include <stdint.h>
#include "gb_struct_def.h"
#include "symbol_hash.h"
#ifdef GB_INTERNAL
void GB_debugger_run(GB_gameboy_t *gb);
void GB_debugger_handle_async_commands(GB_gameboy_t *gb);
void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr);
void GB_debugger_ret_hook(GB_gameboy_t *gb);
void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr);
const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr);
#endif
#ifdef GB_INTERNAL
bool /* Returns true if debugger waits for more commands. Not relevant for non-GB_INTERNAL */
#else
void
#endif
GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */
void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path);
const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);
bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank); /* result_bank is -1 if unused. */
void GB_debugger_break(GB_gameboy_t *gb);
bool GB_debugger_is_stopped(GB_gameboy_t *gb);
void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled);
#endif /* debugger_h */

800
waterbox/sameboy/display.c Normal file
View File

@ -0,0 +1,800 @@
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "gb.h"
/*
Each line is 456 cycles, approximately:
Mode 2 - 80 cycles / OAM Transfer
Mode 3 - 172 cycles / Rendering
Mode 0 - 204 cycles / HBlank
Mode 1 is VBlank
Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt
*/
#define MODE2_LENGTH (80)
#define MODE3_LENGTH (172)
#define MODE0_LENGTH (204)
#define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456
#define LINES (144)
#define WIDTH (160)
#define VIRTUAL_LINES (LCDC_PERIOD / LINE_LENGTH) // = 154
typedef struct __attribute__((packed)) {
uint8_t y;
uint8_t x;
uint8_t tile;
uint8_t flags;
} GB_sprite_t;
static bool window_enabled(GB_gameboy_t *gb)
{
if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) {
if (!gb->cgb_mode && gb->is_cgb) {
return false;
}
}
return (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] < 167;
}
static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y)
{
/*
Bit 7 - LCD Display Enable (0=Off, 1=On)
Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
Bit 5 - Window Display Enable (0=Off, 1=On)
Bit 4 - BG & Window Tile Data Select (0=8800-97FF, 1=8000-8FFF)
Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
Bit 2 - OBJ (Sprite) Size (0=8x8, 1=8x16)
Bit 1 - OBJ (Sprite) Display Enable (0=Off, 1=On)
Bit 0 - BG Display (for CGB see below) (0=Off, 1=On)
*/
uint16_t map = 0x1800;
uint8_t tile = 0;
uint8_t attributes = 0;
uint8_t sprite_palette = 0;
uint16_t tile_address = 0;
uint8_t background_pixel = 0, sprite_pixel = 0;
GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam;
uint8_t sprites_in_line = 0;
bool lcd_8_16_mode = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
bool sprites_enabled = (gb->io_registers[GB_IO_LCDC] & 2) != 0;
uint8_t lowest_sprite_x = 0xFF;
bool use_obp1 = false, priority = false;
bool in_window = false;
bool bg_enabled = true;
bool bg_behind = false;
if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) {
if (gb->cgb_mode) {
bg_behind = true;
}
else {
bg_enabled = false;
}
}
if (window_enabled(gb) && y >= gb->io_registers[GB_IO_WY] && x + 7 >= gb->io_registers[GB_IO_WX] && gb->current_window_line != 0xFF) {
in_window = true;
}
if (sprites_enabled) {
// Loop all sprites
for (uint8_t i = 40; i--; sprite++) {
int sprite_y = sprite->y - 16;
int sprite_x = sprite->x - 8;
// Is sprite in our line?
if (sprite_y <= y && sprite_y + (lcd_8_16_mode? 16:8) > y) {
uint8_t tile_x, tile_y, current_sprite_pixel;
uint16_t line_address;
// Limit to 10 sprites in one scan line.
if (++sprites_in_line == 11) break;
// Does not overlap our pixel.
if (sprite_x > x || sprite_x + 8 <= x) continue;
tile_x = x - sprite_x;
tile_y = y - sprite_y;
if (sprite->flags & 0x20) tile_x = 7 - tile_x;
if (sprite->flags & 0x40) tile_y = (lcd_8_16_mode? 15:7) - tile_y;
line_address = (lcd_8_16_mode? sprite->tile & 0xFE : sprite->tile) * 0x10 + tile_y * 2;
if (gb->cgb_mode && (sprite->flags & 0x8)) {
line_address += 0x2000;
}
current_sprite_pixel = (((gb->vram[line_address ] >> ((~tile_x)&7)) & 1 ) |
((gb->vram[line_address + 1] >> ((~tile_x)&7)) & 1) << 1 );
/* From Pandocs:
When sprites with different x coordinate values overlap, the one with the smaller x coordinate
(closer to the left) will have priority and appear above any others. This applies in Non CGB Mode
only. When sprites with the same x coordinate values overlap, they have priority according to table
ordering. (i.e. $FE00 - highest, $FE04 - next highest, etc.) In CGB Mode priorities are always
assigned like this.
*/
if (current_sprite_pixel != 0) {
if (!gb->cgb_mode && sprite->x >= lowest_sprite_x) {
break;
}
sprite_pixel = current_sprite_pixel;
lowest_sprite_x = sprite->x;
use_obp1 = (sprite->flags & 0x10) != 0;
sprite_palette = sprite->flags & 7;
priority = (sprite->flags & 0x80) != 0;
if (gb->cgb_mode) {
break;
}
}
}
}
}
if (in_window) {
x -= gb->io_registers[GB_IO_WX] - 7; // Todo: This value is probably latched
y = gb->current_window_line;
}
else {
x += gb->effective_scx;
y += gb->io_registers[GB_IO_SCY];
}
if (gb->io_registers[GB_IO_LCDC] & 0x08 && !in_window) {
map = 0x1C00;
}
else if (gb->io_registers[GB_IO_LCDC] & 0x40 && in_window) {
map = 0x1C00;
}
tile = gb->vram[map + x/8 + y/8 * 32];
if (gb->cgb_mode) {
attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000];
}
if (attributes & 0x80) {
priority = !bg_behind && bg_enabled;
}
if (!priority && sprite_pixel) {
if (!gb->cgb_mode) {
sprite_pixel = (gb->io_registers[use_obp1? GB_IO_OBP1:GB_IO_OBP0] >> (sprite_pixel << 1)) & 3;
sprite_palette = use_obp1;
}
return gb->sprite_palettes_rgb[sprite_palette * 4 + sprite_pixel];
}
if (bg_enabled) {
if (gb->io_registers[GB_IO_LCDC] & 0x10) {
tile_address = tile * 0x10;
}
else {
tile_address = (int8_t) tile * 0x10 + 0x1000;
}
if (attributes & 0x8) {
tile_address += 0x2000;
}
if (attributes & 0x20) {
x = ~x;
}
if (attributes & 0x40) {
y = ~y;
}
background_pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) |
((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1 );
}
if (priority && sprite_pixel && !background_pixel) {
if (!gb->cgb_mode) {
sprite_pixel = (gb->io_registers[use_obp1? GB_IO_OBP1:GB_IO_OBP0] >> (sprite_pixel << 1)) & 3;
sprite_palette = use_obp1;
}
return gb->sprite_palettes_rgb[sprite_palette * 4 + sprite_pixel];
}
if (!gb->cgb_mode) {
background_pixel = ((gb->io_registers[GB_IO_BGP] >> (background_pixel << 1)) & 3);
}
return gb->background_palettes_rgb[(attributes & 7) * 4 + background_pixel];
}
static void display_vblank(GB_gameboy_t *gb)
{
if (gb->turbo) {
if (GB_timing_sync_turbo(gb)) {
return;
}
}
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
/* LCD is off, set screen to white */
uint32_t white = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
for (unsigned i = 0; i < WIDTH * LINES; i++) {
gb ->screen[i] = white;
}
}
gb->vblank_callback(gb);
GB_timing_sync(gb);
gb->vblank_just_occured = true;
}
static inline uint8_t scale_channel(uint8_t x)
{
x &= 0x1f;
return (x << 3) | (x >> 2);
}
void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index)
{
uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data;
uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8);
// No need to &, scale channel does that.
uint8_t r = scale_channel(color);
uint8_t g = scale_channel(color >> 5);
uint8_t b = scale_channel(color >> 10);
assert (gb->rgb_encode_callback);
(background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = gb->rgb_encode_callback(gb, r, g, b);
}
/*
STAT interrupt is implemented based on this finding:
http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531
General timing is based on GiiBiiAdvance's documents:
https://github.com/AntonioND/giibiiadvance
*/
static void update_display_state(GB_gameboy_t *gb, uint8_t cycles)
{
uint8_t previous_stat_interrupt_line = gb->stat_interrupt_line;
gb->stat_interrupt_line = false;
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
/* LCD is disabled, state is constant */
/* When the LCD is off, LY is 0 and STAT mode is 0.
Todo: Verify the LY=LYC flag should be on. */
gb->io_registers[GB_IO_LY] = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 4;
if (gb->hdma_on_hblank) {
gb->hdma_on_hblank = false;
gb->hdma_on = false;
/* Todo: is this correct? */
gb->hdma_steps_left = 0xff;
}
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
/* Keep sending vblanks to user even if the screen is off */
gb->display_cycles += cycles;
if (gb->display_cycles >= LCDC_PERIOD) {
/* VBlank! */
gb->display_cycles -= LCDC_PERIOD;
display_vblank(gb);
}
/* Reset window rendering state */
gb->current_window_line = 0xFF;
return;
}
uint8_t atomic_increase = gb->cgb_double_speed? 2 : 4;
uint8_t stat_delay = gb->cgb_double_speed? 2 : (gb->cgb_mode? 0 : 4);
/* Todo: This is correct for DMG. Is it correct for the 3 CGB modes (DMG/single/double)?*/
uint8_t scx_delay = ((gb->effective_scx & 7) + atomic_increase - 1) & ~(atomic_increase - 1);
/* Todo: These are correct for DMG, DMG-mode CGB, and single speed CGB. Is is correct for double speed CGB? */
uint8_t oam_blocking_rush = gb->cgb_double_speed? 2 : 4;
uint8_t vram_blocking_rush = gb->is_cgb? 0 : 4;
for (; cycles; cycles -= atomic_increase) {
gb->display_cycles += atomic_increase;
/* The very first line is 2 (4 from the CPU's perseptive) clocks shorter when the LCD turns on.
Todo: Verify on the 3 CGB modes, especially double speed mode. */
if (gb->first_scanline && gb->display_cycles >= LINE_LENGTH - atomic_increase) {
gb->first_scanline = false;
gb->display_cycles += atomic_increase;
}
bool should_compare_ly = true;
uint8_t ly_for_comparison = gb->io_registers[GB_IO_LY] = gb->display_cycles / LINE_LENGTH;
/* Handle cycle completion. STAT's initial value depends on model and mode */
if (gb->display_cycles == LCDC_PERIOD) {
/* VBlank! */
gb->display_cycles = 0;
gb->io_registers[GB_IO_STAT] &= ~3;
if (gb->is_cgb) {
if (stat_delay) {
gb->io_registers[GB_IO_STAT] |= 1;
}
else {
gb->io_registers[GB_IO_STAT] |= 2;
}
}
ly_for_comparison = gb->io_registers[GB_IO_LY] = 0;
/* Todo: verify timing */
gb->oam_read_blocked = true;
gb->vram_read_blocked = false;
gb->oam_write_blocked = true;
gb->vram_write_blocked = false;
/* Reset window rendering state */
gb->current_window_line = 0xFF;
}
/* Entered VBlank state, update STAT and IF */
else if (gb->display_cycles == LINES * LINE_LENGTH + stat_delay) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 1;
gb->io_registers[GB_IO_IF] |= 1;
/* Entering VBlank state triggers the OAM interrupt. In CGB, it happens 4 cycles earlier */
if (gb->io_registers[GB_IO_STAT] & 0x20 && !gb->is_cgb) {
gb->stat_interrupt_line = true;
}
if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) {
if (!gb->is_cgb) {
display_vblank(gb);
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
}
else {
gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED;
}
}
else {
gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
display_vblank(gb);
}
}
/* Handle line 0 right after turning the LCD on */
else if (gb->first_scanline) {
/* OAM and VRAM blocking is not rushed in the very first scanline */
if (gb->display_cycles == atomic_increase) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
}
else if (gb->display_cycles == MODE2_LENGTH) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 3;
gb->oam_read_blocked = true;
gb->vram_read_blocked = true;
gb->oam_write_blocked = true;
gb->vram_write_blocked = true;
}
else if (gb->display_cycles == MODE2_LENGTH + MODE3_LENGTH) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
}
}
/* Handle STAT changes for lines 0-143 */
else if (gb->display_cycles < LINES * LINE_LENGTH) {
unsigned position_in_line = gb->display_cycles % LINE_LENGTH;
/* Handle OAM and VRAM blocking */
/* Todo: verify CGB timing for write blocking */
if (position_in_line == stat_delay - oam_blocking_rush ||
// In case stat_delay is 0
(position_in_line == LINE_LENGTH + stat_delay - oam_blocking_rush && gb->io_registers[GB_IO_LY] != 143)) {
gb->oam_read_blocked = true;
gb->oam_write_blocked = gb->is_cgb;
}
else if (position_in_line == MODE2_LENGTH + stat_delay - vram_blocking_rush) {
gb->vram_read_blocked = true;
gb->vram_write_blocked = gb->is_cgb;
}
if (position_in_line == stat_delay) {
gb->oam_write_blocked = true;
}
else if (!gb->is_cgb && position_in_line == MODE2_LENGTH + stat_delay - oam_blocking_rush) {
gb->oam_write_blocked = false;
}
else if (position_in_line == MODE2_LENGTH + stat_delay) {
gb->vram_write_blocked = true;
gb->oam_write_blocked = true;
}
/* Handle everything else */
if (position_in_line == stat_delay) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 2;
if (window_enabled(gb) && gb->display_cycles / LINE_LENGTH >= gb->io_registers[GB_IO_WY]) {
gb->current_window_line++;
}
}
else if (position_in_line == 0 && gb->display_cycles != 0) {
should_compare_ly = gb->is_cgb;
ly_for_comparison--;
}
else if (position_in_line == MODE2_LENGTH + stat_delay) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->io_registers[GB_IO_STAT] |= 3;
gb->effective_scx = gb->io_registers[GB_IO_SCX];
gb->previous_lcdc_x = - (gb->effective_scx & 0x7);
/* Todo: This works on both 007 - The World Is Not Enough and Donkey Kong 94, but should be verified better */
if (window_enabled(gb) && gb->display_cycles / LINE_LENGTH == gb->io_registers[GB_IO_WY] && gb->current_window_line == 0xFF) {
gb->current_window_line = 0;
}
}
else if (position_in_line == MODE2_LENGTH + MODE3_LENGTH + stat_delay + scx_delay) {
gb->io_registers[GB_IO_STAT] &= ~3;
gb->oam_read_blocked = false;
gb->vram_read_blocked = false;
gb->oam_write_blocked = false;
gb->vram_write_blocked = false;
if (gb->hdma_on_hblank) {
gb->hdma_on = true;
gb->hdma_cycles = 0;
}
}
}
/* Line 153 is special */
else if (gb->display_cycles >= (VIRTUAL_LINES - 1) * LINE_LENGTH) {
/* DMG */
if (!gb->is_cgb) {
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
case 0:
should_compare_ly = false;
break;
case 4:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = VIRTUAL_LINES - 1;
break;
case 8:
gb->io_registers[GB_IO_LY] = 0;
should_compare_ly = false;
break;
default:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = 0;
}
}
/* CGB in DMG mode */
else if (!gb->cgb_mode) {
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
case 0:
ly_for_comparison = VIRTUAL_LINES - 2;
break;
case 4:
break;
case 8:
gb->io_registers[GB_IO_LY] = 0;
break;
default:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = 0;
}
}
/* Single speed CGB */
else if (!gb->cgb_double_speed) {
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
case 0:
break;
case 4:
gb->io_registers[GB_IO_LY] = 0;
break;
default:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = 0;
}
}
/* Double speed CGB */
else {
switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) {
case 0:
ly_for_comparison = VIRTUAL_LINES - 2;
break;
case 2:
case 4:
break;
case 6:
case 8:
gb->io_registers[GB_IO_LY] = 0;
break;
default:
gb->io_registers[GB_IO_LY] = 0;
ly_for_comparison = 0;
}
}
}
/* Lines 144 - 152 */
else {
if (stat_delay && gb->display_cycles % LINE_LENGTH == 0) {
should_compare_ly = gb->is_cgb;
ly_for_comparison--;
}
}
/* Set LY=LYC bit */
if (should_compare_ly && (ly_for_comparison == gb->io_registers[GB_IO_LYC])) {
gb->io_registers[GB_IO_STAT] |= 4;
}
else {
gb->io_registers[GB_IO_STAT] &= ~4;
}
if (!gb->stat_interrupt_line) {
switch (gb->io_registers[GB_IO_STAT] & 3) {
case 0: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 8; break;
case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break;
case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break;
}
/* Use requested a LY=LYC interrupt and the LY=LYC bit is on */
if ((gb->io_registers[GB_IO_STAT] & 0x44) == 0x44) {
gb->stat_interrupt_line = true;
}
}
}
/* On the CGB, the last cycle of line 144 triggers an OAM interrupt
Todo: Verify timing for CGB in CGB mode and double speed CGB */
if (gb->is_cgb &&
gb->display_cycles == LINES * LINE_LENGTH + stat_delay - atomic_increase &&
(gb->io_registers[GB_IO_STAT] & 0x20)) {
gb->stat_interrupt_line = true;
}
if (gb->stat_interrupt_line && !previous_stat_interrupt_line) {
gb->io_registers[GB_IO_IF] |= 2;
}
/* The value of LY is glitched in the last cycle of every line in CGB mode CGB in single speed
This is based on GiiBiiAdvance's docs */
if (gb->cgb_mode && !gb->cgb_double_speed &&
gb->display_cycles % LINE_LENGTH == LINE_LENGTH - 4) {
uint8_t glitch_pattern[] = {0, 0, 2, 0, 4, 4, 6, 0, 8};
if ((gb->io_registers[GB_IO_LY] & 0xF) == 0xF) {
gb->io_registers[GB_IO_LY] = glitch_pattern[gb->io_registers[GB_IO_LY] >> 4] << 4;
}
else {
gb->io_registers[GB_IO_LY] = glitch_pattern[gb->io_registers[GB_IO_LY] & 7] | (gb->io_registers[GB_IO_LY] & 0xF8);
}
}
}
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
{
update_display_state(gb, cycles);
if (gb->disable_rendering) {
return;
}
/*
Display controller bug: For some reason, the OAM STAT interrupt is called, as expected, for LY = 0..143.
However, it is also called from LY = 144.
See http://forums.nesdev.com/viewtopic.php?f=20&t=13727
*/
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
/* LCD is disabled, do nothing */
return;
}
if (gb->display_cycles >= LINE_LENGTH * 144) { /* VBlank */
return;
}
uint8_t effective_ly = gb->display_cycles / LINE_LENGTH;
if (gb->display_cycles % LINE_LENGTH < MODE2_LENGTH) { /* Mode 2 */
return;
}
/* Render */
/* Todo: it appears that the actual rendering starts 4 cycles after mode 3 starts. Is this correct? */
int16_t current_lcdc_x = gb->display_cycles % LINE_LENGTH - MODE2_LENGTH - (gb->effective_scx & 0x7) - 4;
for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) {
if (gb->previous_lcdc_x >= WIDTH) {
continue;
}
if (gb->previous_lcdc_x < 0) {
continue;
}
gb->screen[effective_ly * WIDTH + gb->previous_lcdc_x] =
get_pixel(gb, gb->previous_lcdc_x, effective_ly);
}
}
void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index)
{
uint32_t none_palette[4];
uint32_t *palette = NULL;
switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) {
default:
case GB_PALETTE_NONE:
none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA);
none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55);
none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 );
palette = none_palette;
break;
case GB_PALETTE_BACKGROUND:
palette = gb->background_palettes_rgb + (4 * (palette_index & 7));
break;
case GB_PALETTE_OAM:
palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7));
break;
}
for (unsigned y = 0; y < 192; y++) {
for (unsigned x = 0; x < 256; x++) {
if (x >= 128 && !gb->is_cgb) {
*(dest++) = gb->background_palettes_rgb[0];
continue;
}
uint16_t tile = (x % 128) / 8 + y / 8 * 16;
uint16_t tile_address = tile * 0x10 + (x >= 128? 0x2000 : 0);
uint8_t pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) |
((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1);
if (!gb->cgb_mode) {
if (palette_type == GB_PALETTE_BACKGROUND) {
pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
}
else if (!gb->cgb_mode) {
if (palette_type == GB_PALETTE_OAM) {
pixel = ((gb->io_registers[palette_index == 0? GB_IO_OBP0 : GB_IO_OBP1] >> (pixel << 1)) & 3);
}
}
}
*(dest++) = palette[pixel];
}
}
}
void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type)
{
uint32_t none_palette[4];
uint32_t *palette = NULL;
uint16_t map = 0x1800;
switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) {
case GB_PALETTE_NONE:
none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA);
none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55);
none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 );
palette = none_palette;
break;
case GB_PALETTE_BACKGROUND:
palette = gb->background_palettes_rgb + (4 * (palette_index & 7));
break;
case GB_PALETTE_OAM:
palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7));
break;
case GB_PALETTE_AUTO:
break;
}
if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) {
map = 0x1c00;
}
if (tileset_type == GB_TILESET_AUTO) {
tileset_type = (gb->io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000;
}
for (unsigned y = 0; y < 256; y++) {
for (unsigned x = 0; x < 256; x++) {
uint8_t tile = gb->vram[map + x/8 + y/8 * 32];
uint16_t tile_address;
uint8_t attributes = 0;
if (tileset_type == GB_TILESET_8800) {
tile_address = tile * 0x10;
}
else {
tile_address = (int8_t) tile * 0x10 + 0x1000;
}
if (gb->cgb_mode) {
attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000];
}
if (attributes & 0x8) {
tile_address += 0x2000;
}
uint8_t pixel = (((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 ] >> (((attributes & 0x20)? x : ~x)&7)) & 1 ) |
((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 + 1] >> (((attributes & 0x20)? x : ~x)&7)) & 1) << 1);
if (!gb->cgb_mode && (palette_type == GB_PALETTE_BACKGROUND || palette_type == GB_PALETTE_AUTO)) {
pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
}
if (palette) {
*(dest++) = palette[pixel];
}
else {
*(dest++) = gb->background_palettes_rgb[(attributes & 7) * 4 + pixel];
}
}
}
}
uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height)
{
uint8_t count = 0;
*sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8;
uint8_t oam_to_dest_index[40] = {0,};
for (unsigned y = 0; y < LINES; y++) {
GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam;
uint8_t sprites_in_line = 0;
for (uint8_t i = 0; i < 40; i++, sprite++) {
int sprite_y = sprite->y - 16;
bool obscured = false;
// Is sprite not in this line?
if (sprite_y > y || sprite_y + *sprite_height <= y) continue;
if (++sprites_in_line == 11) obscured = true;
GB_oam_info_t *info = NULL;
if (!oam_to_dest_index[i]) {
info = dest + count;
oam_to_dest_index[i] = ++count;
info->x = sprite->x;
info->y = sprite->y;
info->tile = *sprite_height == 16? sprite->tile & 0xFE : sprite->tile;
info->flags = sprite->flags;
info->obscured_by_line_limit = false;
info->oam_addr = 0xFE00 + i * sizeof(*sprite);
}
else {
info = dest + oam_to_dest_index[i] - 1;
}
info->obscured_by_line_limit |= obscured;
}
}
for (unsigned i = 0; i < count; i++) {
uint16_t vram_address = dest[i].tile * 0x10;
uint8_t flags = dest[i].flags;
uint8_t palette = gb->cgb_mode? (flags & 7) : ((flags & 0x10)? 1 : 0);
if (gb->is_cgb && (flags & 0x8)) {
vram_address += 0x2000;
}
for (unsigned y = 0; y < *sprite_height; y++) {
for (unsigned x = 0; x < 8; x++) {
uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) |
((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 );
if (!gb->cgb_mode) {
color = (gb->io_registers[palette? GB_IO_OBP1:GB_IO_OBP0] >> (color << 1)) & 3;
}
dest[i].image[((flags & 0x20)?7-x:x) + ((flags & 0x40)?*sprite_height - 1 -y:y) * 8] = gb->sprite_palettes_rgb[palette * 4 + color];
}
vram_address += 2;
}
}
return count;
}

View File

@ -0,0 +1,40 @@
#ifndef display_h
#define display_h
#include "gb.h"
#ifdef GB_INTERNAL
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles);
void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index);
#endif
typedef enum {
GB_PALETTE_NONE,
GB_PALETTE_BACKGROUND,
GB_PALETTE_OAM,
GB_PALETTE_AUTO,
} GB_palette_type_t;
typedef enum {
GB_MAP_AUTO,
GB_MAP_9800,
GB_MAP_9C00,
} GB_map_type_t;
typedef enum {
GB_TILESET_AUTO,
GB_TILESET_8800,
GB_TILESET_8000,
} GB_tileset_type_t;
typedef struct {
uint32_t image[128];
uint8_t x, y, tile, flags;
uint16_t oam_addr;
bool obscured_by_line_limit;
} GB_oam_info_t;
void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index);
void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type);
uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height);
#endif /* display_h */

563
waterbox/sameboy/gb.c Normal file
View File

@ -0,0 +1,563 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/select.h>
#endif
#include "gb.h"
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);
}
static char *default_input_callback(GB_gameboy_t *gb)
{
char *expression = NULL;
size_t size = 0;
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;
}
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;
}
void GB_init(GB_gameboy_t *gb)
{
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;
gb->async_input_callback = default_async_input_callback;
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
gb->audio_quality = 4;
GB_reset(gb);
}
void GB_init_cgb(GB_gameboy_t *gb)
{
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;
gb->async_input_callback = default_async_input_callback;
gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
gb->audio_quality = 4;
GB_reset(gb);
}
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->audio_buffer) {
free(gb->audio_buffer);
}
if (gb->breakpoints) {
free(gb->breakpoints);
}
for (int i = 0x200; i--;) {
if (gb->bank_symbols[i]) {
GB_map_free(gb->bank_symbols[i]);
}
}
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;
}
}
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;
}
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++;
}
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, gb->rom_size, 1, f);
fclose(f);
GB_configure_cart(gb);
return 0;
}
int GB_save_battery(GB_gameboy_t *gb, const char *path)
{
if (!gb->cartridge_type->has_battery) 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->has_rtc) {
if (fwrite(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) {
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 */
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 (fread(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) {
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);
gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */
exit:
fclose(f);
return;
}
void GB_run(GB_gameboy_t *gb)
{
GB_debugger_run(gb);
GB_cpu_run(gb);
if (gb->vblank_just_occured) {
GB_update_joyp(gb);
GB_rtc_run(gb);
GB_debugger_handle_async_commands(gb);
}
}
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;
}
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)
{
if (gb->input_callback == default_input_callback) {
gb->async_input_callback = NULL;
}
gb->input_callback = callback;
}
void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback)
{
gb->async_input_callback = callback;
}
void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback)
{
if (!gb->rgb_encode_callback && !gb->is_cgb) {
gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] =
callback(gb, 0xFF, 0xFF, 0xFF);
gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] =
callback(gb, 0xAA, 0xAA, 0xAA);
gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] =
callback(gb, 0x55, 0x55, 0x55);
gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] =
callback(gb, 0, 0, 0);
}
gb->rgb_encode_callback = callback;
}
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;
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};
}
void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback)
{
gb->rumble_callback = callback;
}
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;
gb->io_registers[GB_IO_IF] |= 8;
}
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate)
{
if (gb->audio_buffer) {
free(gb->audio_buffer);
}
gb->buffer_size = sample_rate / 25; // 40ms delay
gb->audio_buffer = malloc(gb->buffer_size * sizeof(*gb->audio_buffer));
gb->sample_rate = sample_rate;
gb->audio_position = 0;
}
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));
}
bool GB_is_inited(GB_gameboy_t *gb)
{
return gb->magic == 'SAME';
}
bool GB_is_cgb(GB_gameboy_t *gb)
{
return gb->is_cgb;
}
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;
}
void GB_reset(GB_gameboy_t *gb)
{
uint32_t mbc_ram_size = gb->mbc_ram_size;
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->mbc_ram_size = mbc_ram_size;
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_OBP0] = gb->io_registers[GB_IO_OBP1] = 0x00;
}
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) {
gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] =
gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] =
gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA);
gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] =
gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55);
gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] =
gb->rgb_encode_callback(gb, 0, 0, 0);
}
gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF;
}
/* The serial interrupt always occur on the 0xF8th cycle of every 0x100 cycle since boot. */
gb->serial_cycles = 0x100 - 0xF8;
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);
}
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? 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;
}
}

570
waterbox/sameboy/gb.h Normal file
View File

@ -0,0 +1,570 @@
#ifndef GB_h
#define GB_h
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include "gb_struct_def.h"
#include "save_state.h"
#include "apu.h"
#include "camera.h"
#include "debugger.h"
#include "display.h"
#include "joypad.h"
#include "mbc.h"
#include "memory.h"
#include "printer.h"
#include "timing.h"
#include "z80_cpu.h"
#include "symbol_hash.h"
#define GB_STRUCT_VERSION 11
enum {
GB_REGISTER_AF,
GB_REGISTER_BC,
GB_REGISTER_DE,
GB_REGISTER_HL,
GB_REGISTER_SP,
GB_REGISTERS_16_BIT /* Count */
};
/* Todo: Actually use these! */
enum {
GB_CARRY_FLAG = 16,
GB_HALF_CARRY_FLAG = 32,
GB_SUBSTRACT_FLAG = 64,
GB_ZERO_FLAG = 128,
};
#define GB_MAX_IR_QUEUE 256
enum {
/* Joypad and Serial */
GB_IO_JOYP = 0x00, // Joypad (R/W)
GB_IO_SB = 0x01, // Serial transfer data (R/W)
GB_IO_SC = 0x02, // Serial Transfer Control (R/W)
/* Missing */
/* Timers */
GB_IO_DIV = 0x04, // Divider Register (R/W)
GB_IO_TIMA = 0x05, // Timer counter (R/W)
GB_IO_TMA = 0x06, // Timer Modulo (R/W)
GB_IO_TAC = 0x07, // Timer Control (R/W)
/* Missing */
GB_IO_IF = 0x0f, // Interrupt Flag (R/W)
/* Sound */
GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W)
GB_IO_NR11 = 0x11, // Channel 1 Sound length/Wave pattern duty (R/W)
GB_IO_NR12 = 0x12, // Channel 1 Volume Envelope (R/W)
GB_IO_NR13 = 0x13, // Channel 1 Frequency lo (Write Only)
GB_IO_NR14 = 0x14, // Channel 1 Frequency hi (R/W)
GB_IO_NR21 = 0x16, // Channel 2 Sound Length/Wave Pattern Duty (R/W)
GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W)
GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W)
GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W)
GB_IO_NR30 = 0x1a, // Channel 3 Sound on/off (R/W)
GB_IO_NR31 = 0x1b, // Channel 3 Sound Length
GB_IO_NR32 = 0x1c, // Channel 3 Select output level (R/W)
GB_IO_NR33 = 0x1d, // Channel 3 Frequency's lower data (W)
GB_IO_NR34 = 0x1e, // Channel 3 Frequency's higher data (R/W)
/* Missing */
GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W)
GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W)
GB_IO_NR43 = 0x22, // Channel 4 Polynomial Counter (R/W)
GB_IO_NR44 = 0x23, // Channel 4 Counter/consecutive, Inital (R/W)
GB_IO_NR50 = 0x24, // Channel control / ON-OFF / Volume (R/W)
GB_IO_NR51 = 0x25, // Selection of Sound output terminal (R/W)
GB_IO_NR52 = 0x26, // Sound on/off
/* Missing */
GB_IO_WAV_START = 0x30, // Wave pattern start
GB_IO_WAV_END = 0x3f, // Wave pattern end
/* Graphics */
GB_IO_LCDC = 0x40, // LCD Control (R/W)
GB_IO_STAT = 0x41, // LCDC Status (R/W)
GB_IO_SCY = 0x42, // Scroll Y (R/W)
GB_IO_SCX = 0x43, // Scroll X (R/W)
GB_IO_LY = 0x44, // LCDC Y-Coordinate (R)
GB_IO_LYC = 0x45, // LY Compare (R/W)
GB_IO_DMA = 0x46, // DMA Transfer and Start Address (W)
GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only
GB_IO_OBP0 = 0x48, // Object Palette 0 Data (R/W) - Non CGB Mode Only
GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only
GB_IO_WY = 0x4a, // Window Y Position (R/W)
GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W)
// Has some undocumented compatibility flags written at boot.
// Unfortunately it is not readable or writable after boot has finished, so research of this
// register is quite limited. The value written to this register, however, can be controlled
// in some cases.
GB_IO_DMG_EMULATION = 0x4c,
/* General CGB features */
GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch
/* Missing */
GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank
GB_IO_BIOS = 0x50, // Write to disable the BIOS mapping
/* CGB DMA */
GB_IO_HDMA1 = 0x51, // CGB Mode Only - New DMA Source, High
GB_IO_HDMA2 = 0x52, // CGB Mode Only - New DMA Source, Low
GB_IO_HDMA3 = 0x53, // CGB Mode Only - New DMA Destination, High
GB_IO_HDMA4 = 0x54, // CGB Mode Only - New DMA Destination, Low
GB_IO_HDMA5 = 0x55, // CGB Mode Only - New DMA Length/Mode/Start
/* IR */
GB_IO_RP = 0x56, // CGB Mode Only - Infrared Communications Port
/* Missing */
/* CGB Paletts */
GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index
GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data
GB_IO_OBPI = 0x6a, // CGB Mode Only - Sprite Palette Index
GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite Palette Data
// 1 is written for DMG ROMs on a CGB. Does not appear to have an effect.
GB_IO_DMG_EMULATION_INDICATION = 0x6c, // (FEh) Bit 0 (Read/Write)
/* Missing */
GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank
GB_IO_UNKNOWN2 = 0x72, // (00h) - Bit 0-7 (Read/Write)
GB_IO_UNKNOWN3 = 0x73, // (00h) - Bit 0-7 (Read/Write)
GB_IO_UNKNOWN4 = 0x74, // (00h) - Bit 0-7 (Read/Write) - CGB Mode Only
GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write)
GB_IO_PCM_12 = 0x76, // Channels 1 and 2 amplitudes
GB_IO_PCM_34 = 0x77, // Channels 3 and 4 amplitudes
GB_IO_UNKNOWN8 = 0x7F, // Unknown, write only
};
typedef enum {
GB_LOG_BOLD = 1,
GB_LOG_DASHED_UNDERLINE = 2,
GB_LOG_UNDERLINE = 4,
GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE
} GB_log_attributes;
#ifdef GB_INTERNAL
#define LCDC_PERIOD 70224
#define CPU_FREQUENCY 0x400000
#define DIV_CYCLES (0x100)
#define INTERNAL_DIV_CYCLES (0x40000)
#define FRAME_LENGTH 16742706 // in nanoseconds
#endif
typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb);
typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes);
typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb);
typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b);
typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update);
typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on);
typedef void (*GB_serial_transfer_start_callback_t)(GB_gameboy_t *gb, uint8_t byte_to_send);
typedef uint8_t (*GB_serial_transfer_end_callback_t)(GB_gameboy_t *gb);
typedef struct {
bool state;
long delay;
} GB_ir_queue_item_t;
struct GB_breakpoint_s;
struct GB_watchpoint_s;
/* When state saving, each section is dumped independently of other sections.
This allows adding data to the end of the section without worrying about future compatibility.
Some other changes might be "safe" as well.
This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64
bit platforms. */
/* We make sure bool is 1 for cross-platform save state compatibility. */
/* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */
_Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1");
#ifdef GB_INTERNAL
struct GB_gameboy_s {
#else
struct GB_gameboy_internal_s {
#endif
GB_SECTION(header,
/* The magic makes sure a state file is:
- Indeed a SameBoy state file.
- Has the same endianess has the current platform. */
volatile uint32_t magic;
/* The version field makes sure we don't load save state files with a completely different structure.
This happens when struct fields are removed/resized in an backward incompatible manner. */
uint32_t version;
);
GB_SECTION(core_state,
/* Registers */
uint16_t pc;
union {
uint16_t registers[GB_REGISTERS_16_BIT];
struct {
uint16_t af,
bc,
de,
hl,
sp;
};
struct {
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
uint8_t a, f,
b, c,
d, e,
h, l;
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
uint8_t f, a,
c, b,
e, d,
l, h;
#else
#error Unable to detect endianess
#endif
};
};
uint8_t ime;
uint8_t interrupt_enable;
uint8_t cgb_ram_bank;
/* CPU and General Hardware Flags*/
bool cgb_mode;
bool is_cgb;
bool cgb_double_speed;
bool halted;
bool stopped;
bool boot_rom_finished;
bool ime_toggle; /* ei (and di in CGB) have delayed effects.*/
bool halt_bug;
/* Misc state */
bool infrared_input;
GB_printer_t printer;
);
/* DMA and HDMA */
GB_SECTION(dma,
bool hdma_on;
bool hdma_on_hblank;
uint8_t hdma_steps_left;
uint16_t hdma_cycles;
uint16_t hdma_current_src, hdma_current_dest;
uint8_t dma_steps_left;
uint8_t dma_current_dest;
uint16_t dma_current_src;
int16_t dma_cycles;
bool is_dma_restarting;
);
/* MBC */
GB_SECTION(mbc,
uint16_t mbc_rom_bank;
uint8_t mbc_ram_bank;
uint32_t mbc_ram_size;
bool mbc_ram_enable;
union {
struct {
uint8_t bank_low:5;
uint8_t bank_high:2;
uint8_t padding:1; // Save state compatibility with 0.9
uint8_t mode:1;
} mbc1;
struct {
uint8_t rom_bank:4;
} mbc2;
struct {
uint8_t rom_bank:7;
uint8_t padding:1;
uint8_t ram_bank:4;
} mbc3;
struct {
uint8_t rom_bank_low;
uint8_t rom_bank_high:1;
uint8_t ram_bank:4;
} mbc5;
struct {
uint8_t bank_low:6;
uint8_t bank_high:3;
uint8_t mode:1;
} huc1;
struct {
uint8_t rom_bank;
uint8_t ram_bank;
} huc3;
};
uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */
bool camera_registers_mapped;
uint8_t camera_registers[0x36];
bool rumble_state;
);
/* HRAM and HW Registers */
GB_SECTION(hram,
uint8_t hram[0xFFFF - 0xFF80];
uint8_t io_registers[0x80];
);
/* Timing */
GB_SECTION(timing,
uint32_t display_cycles;
uint32_t div_cycles;
uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
GB_PADDING(uint16_t, serial_cycles);
uint16_t serial_cycles; /* This field changed its meaning in v0.10 */
uint16_t serial_length;
);
/* APU */
GB_SECTION(apu,
GB_apu_t apu;
);
/* RTC */
GB_SECTION(rtc,
union {
struct {
uint8_t seconds;
uint8_t minutes;
uint8_t hours;
uint8_t days;
uint8_t high;
};
uint8_t data[5];
} rtc_real, rtc_latched;
time_t last_rtc_second;
bool rtc_latch;
);
/* Video Display */
GB_SECTION(video,
uint32_t vram_size; // Different between CGB and DMG
uint8_t cgb_vram_bank;
uint8_t oam[0xA0];
uint8_t background_palettes_data[0x40];
uint8_t sprite_palettes_data[0x40];
uint32_t background_palettes_rgb[0x20];
uint32_t sprite_palettes_rgb[0x20];
int16_t previous_lcdc_x;
bool stat_interrupt_line;
uint8_t effective_scx;
uint8_t current_window_line;
/* The LCDC will skip the first frame it renders after turning it on.
On the CGB, a frame is not skipped if the previous frame was skipped as well.
See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */
enum {
GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state,
// on a CGB, the previous frame is repeated (which might be
// blank if the LCD was off for more than a few cycles)
GB_FRAMESKIP_FIRST_FRAME_SKIPPED, // This state is 'skipped' when emulating a DMG
GB_FRAMESKIP_SECOND_FRAME_RENDERED,
} frame_skip_state;
bool first_scanline; // The very first scan line after turning the LCD behaves differently.
bool oam_read_blocked;
bool vram_read_blocked;
bool oam_write_blocked;
bool vram_write_blocked;
);
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
/* This data is reserved on reset and must come last in the struct */
GB_SECTION(unsaved,
/* ROM */
uint8_t *rom;
uint32_t rom_size;
const GB_cartridge_t *cartridge_type;
enum {
GB_STANDARD_MBC1_WIRING,
GB_MBC1M_WIRING,
} mbc1_wiring;
/* Various RAMs */
uint8_t *ram;
uint8_t *vram;
uint8_t *mbc_ram;
/* I/O */
uint32_t *screen;
GB_sample_t *audio_buffer;
bool keys[GB_KEY_MAX];
/* Timing */
uint64_t last_sync;
uint64_t cycles_since_last_sync;
/* Audio */
unsigned buffer_size;
unsigned sample_rate;
unsigned audio_position;
bool audio_stream_started; /* detects first copy request to minimize lag */
volatile bool audio_copy_in_progress;
volatile bool apu_lock;
double apu_sample_cycles;
double apu_subsample_cycles;
GB_double_sample_t current_supersample;
unsigned n_subsamples;
unsigned audio_quality;
/* Callbacks */
void *user_data;
GB_log_callback_t log_callback;
GB_input_callback_t input_callback;
GB_input_callback_t async_input_callback;
GB_rgb_encode_callback_t rgb_encode_callback;
GB_vblank_callback_t vblank_callback;
GB_infrared_callback_t infrared_callback;
GB_camera_get_pixel_callback_t camera_get_pixel_callback;
GB_camera_update_request_callback_t camera_update_request_callback;
GB_rumble_callback_t rumble_callback;
GB_serial_transfer_start_callback_t serial_transfer_start_callback;
GB_serial_transfer_end_callback_t serial_transfer_end_callback;
/* IR */
long cycles_since_ir_change;
long cycles_since_input_ir_change;
GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE];
size_t ir_queue_length;
/*** Debugger ***/
volatile bool debug_stopped, debug_disable;
bool debug_fin_command, debug_next_command;
/* Breakpoints */
uint16_t n_breakpoints;
struct GB_breakpoint_s *breakpoints;
/* SLD (Todo: merge with backtrace) */
bool stack_leak_detection;
int debug_call_depth;
uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */
uint16_t addr_for_call_depth[0x200];
/* Backtrace */
unsigned int backtrace_size;
uint16_t backtrace_sps[0x200];
struct {
uint16_t bank;
uint16_t addr;
} backtrace_returns[0x200];
/* Watchpoints */
uint16_t n_watchpoints;
struct GB_watchpoint_s *watchpoints;
/* Symbol tables */
GB_symbol_map_t *bank_symbols[0x200];
GB_reversed_symbol_map_t reversed_symbol_map;
/* Ticks command */
unsigned long debugger_ticks;
/* Misc */
bool turbo;
bool turbo_dont_skip;
bool disable_rendering;
uint32_t ram_size; // Different between CGB and DMG
uint8_t boot_rom[0x900];
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
);
};
#ifndef GB_INTERNAL
struct GB_gameboy_s {
char __internal[sizeof(struct GB_gameboy_internal_s)];
};
#endif
#ifndef __printflike
/* Missing from Linux headers. */
#define __printflike(fmtarg, firstvararg) \
__attribute__((__format__ (__printf__, fmtarg, firstvararg)))
#endif
void GB_init(GB_gameboy_t *gb);
void GB_init_cgb(GB_gameboy_t *gb);
bool GB_is_inited(GB_gameboy_t *gb);
bool GB_is_cgb(GB_gameboy_t *gb);
void GB_free(GB_gameboy_t *gb);
void GB_reset(GB_gameboy_t *gb);
void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb);
void GB_run(GB_gameboy_t *gb);
/* Returns the time passed since the last frame, in nanoseconds */
uint64_t GB_run_frame(GB_gameboy_t *gb);
typedef enum {
GB_DIRECT_ACCESS_ROM,
GB_DIRECT_ACCESS_RAM,
GB_DIRECT_ACCESS_CART_RAM,
GB_DIRECT_ACCESS_VRAM,
GB_DIRECT_ACCESS_HRAM,
GB_DIRECT_ACCESS_IO, /* Warning: Some registers can only be read/written correctly via GB_memory_read/write. */
GB_DIRECT_ACCESS_BOOTROM,
GB_DIRECT_ACCESS_OAM,
GB_DIRECT_ACCESS_BGP,
GB_DIRECT_ACCESS_OBP,
} GB_direct_access_t;
/* Returns a mutable pointer to various hardware memories. If that memory is banked, the current bank
is returned at *bank, even if only a portion of the memory is banked. */
void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank);
void *GB_get_user_data(GB_gameboy_t *gb);
void GB_set_user_data(GB_gameboy_t *gb, void *data);
int GB_load_boot_rom(GB_gameboy_t *gb, const char *path);
int GB_load_rom(GB_gameboy_t *gb, const char *path);
int GB_save_battery(GB_gameboy_t *gb, const char *path);
void GB_load_battery(GB_gameboy_t *gb, const char *path);
void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip);
void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled);
void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3);
void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) __printflike(3, 4);
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output);
void GB_set_infrared_input(GB_gameboy_t *gb, bool state);
void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change);
void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback);
void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback);
void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback);
void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback);
void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback);
void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback);
void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback);
/* These APIs are used when using internal clock */
void GB_set_serial_transfer_start_callback(GB_gameboy_t *gb, GB_serial_transfer_start_callback_t callback);
void GB_set_serial_transfer_end_callback(GB_gameboy_t *gb, GB_serial_transfer_end_callback_t callback);
/* These APIs are used when using external clock */
uint8_t GB_serial_get_data(GB_gameboy_t *gb);
void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data);
void GB_disconnect_serial(GB_gameboy_t *gb);
#endif /* GB_h */

View File

@ -0,0 +1,5 @@
#ifndef gb_struct_def_h
#define gb_struct_def_h
struct GB_gameboy_s;
typedef struct GB_gameboy_s GB_gameboy_t;
#endif

63
waterbox/sameboy/joypad.c Normal file
View File

@ -0,0 +1,63 @@
#include <stdio.h>
#include "gb.h"
#include <assert.h>
void GB_update_joyp(GB_gameboy_t *gb)
{
uint8_t key_selection = 0;
uint8_t previous_state = 0;
/* Todo: add delay to key selection */
previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3;
gb->io_registers[GB_IO_JOYP] &= 0xF0;
switch (key_selection) {
case 3:
/* Nothing is wired, all up */
gb->io_registers[GB_IO_JOYP] |= 0x0F;
break;
case 2:
/* Direction keys */
for (uint8_t i = 0; i < 4; i++) {
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i]) << i;
}
/* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */
if (!(gb->io_registers[GB_IO_JOYP] & 1)) {
gb->io_registers[GB_IO_JOYP] |= 2;
}
if (!(gb->io_registers[GB_IO_JOYP] & 4)) {
gb->io_registers[GB_IO_JOYP] |= 8;
}
break;
case 1:
/* Other keys */
for (uint8_t i = 0; i < 4; i++) {
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i + 4]) << i;
}
break;
case 0:
/* Todo: verifiy this is correct */
for (uint8_t i = 0; i < 4; i++) {
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i]) << i;
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i + 4]) << i;
}
break;
default:
break;
}
if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
/* Todo: disable when emulating CGB */
gb->io_registers[GB_IO_IF] |= 0x10;
}
gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support
}
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed)
{
assert(index >= 0 && index < GB_KEY_MAX);
gb->keys[index] = pressed;
}

22
waterbox/sameboy/joypad.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef joypad_h
#define joypad_h
#include "gb_struct_def.h"
typedef enum {
GB_KEY_RIGHT,
GB_KEY_LEFT,
GB_KEY_UP,
GB_KEY_DOWN,
GB_KEY_A,
GB_KEY_B,
GB_KEY_SELECT,
GB_KEY_START,
GB_KEY_MAX
} GB_key_t;
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed);
#ifdef GB_INTERNAL
void GB_update_joyp(GB_gameboy_t *gb);
#endif
#endif /* joypad_h */

154
waterbox/sameboy/mbc.c Normal file
View File

@ -0,0 +1,154 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "gb.h"
const GB_cartridge_t GB_cart_defs[256] = {
// From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type
/* MBC SUBTYPE RAM BAT. RTC RUMB. */
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 00h ROM ONLY
{ GB_MBC1 , GB_STANDARD_MBC, false, false, false, false}, // 01h MBC1
{ GB_MBC1 , GB_STANDARD_MBC, true , false, false, false}, // 02h MBC1+RAM
{ GB_MBC1 , GB_STANDARD_MBC, true , true , false, false}, // 03h MBC1+RAM+BATTERY
[5] =
{ GB_MBC2 , GB_STANDARD_MBC, true , false, false, false}, // 05h MBC2
{ GB_MBC2 , GB_STANDARD_MBC, true , true , false, false}, // 06h MBC2+BATTERY
[8] =
{ GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h ROM+RAM
{ GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY
[0xB] =
/* Todo: Not supported yet */
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh MMM01
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch MMM01+RAM
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY
[0xF] =
{ GB_MBC3 , GB_STANDARD_MBC, false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY
{ GB_MBC3 , GB_STANDARD_MBC, true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY
{ GB_MBC3 , GB_STANDARD_MBC, false, false, false, false}, // 11h MBC3
{ GB_MBC3 , GB_STANDARD_MBC, true , false, false, false}, // 12h MBC3+RAM
{ GB_MBC3 , GB_STANDARD_MBC, true , true , false, false}, // 13h MBC3+RAM+BATTERY
[0x19] =
{ GB_MBC5 , GB_STANDARD_MBC, false, false, false, false}, // 19h MBC5
{ GB_MBC5 , GB_STANDARD_MBC, true , false, false, false}, // 1Ah MBC5+RAM
{ GB_MBC5 , GB_STANDARD_MBC, true , true , false, false}, // 1Bh MBC5+RAM+BATTERY
{ GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE
{ GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM
{ GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY
[0xFC] =
{ GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA
{ GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported)
{ GB_HUC3 , GB_STANDARD_MBC, true , true , false, false}, // FEh HuC3 (Todo: Mapper support only)
{ GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY (Todo: No IR bindings)
};
void GB_update_mbc_mappings(GB_gameboy_t *gb)
{
switch (gb->cartridge_type->mbc_type) {
case GB_NO_MBC: return;
case GB_MBC1:
switch (gb->mbc1_wiring) {
case GB_STANDARD_MBC1_WIRING:
gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5);
if (gb->mbc1.mode == 0) {
gb->mbc_ram_bank = 0;
gb->mbc_rom0_bank = 0;
}
else {
gb->mbc_ram_bank = gb->mbc1.bank_high;
gb->mbc_rom0_bank = gb->mbc1.bank_high << 5;
}
if ((gb->mbc_rom_bank & 0x1F) == 0) {
gb->mbc_rom_bank++;
}
break;
case GB_MBC1M_WIRING:
gb->mbc_rom_bank = (gb->mbc1.bank_low & 0xF) | (gb->mbc1.bank_high << 4);
if (gb->mbc1.mode == 0) {
gb->mbc_ram_bank = 0;
gb->mbc_rom0_bank = 0;
}
else {
gb->mbc_rom0_bank = gb->mbc1.bank_high << 4;
gb->mbc_ram_bank = 0;
}
if ((gb->mbc1.bank_low & 0x1F) == 0) {
gb->mbc_rom_bank++;
}
break;
}
break;
case GB_MBC2:
gb->mbc_rom_bank = gb->mbc2.rom_bank;
if ((gb->mbc_rom_bank & 0xF) == 0) {
gb->mbc_rom_bank = 1;
}
break;
case GB_MBC3:
gb->mbc_rom_bank = gb->mbc3.rom_bank;
gb->mbc_ram_bank = gb->mbc3.ram_bank;
if (gb->mbc_rom_bank == 0) {
gb->mbc_rom_bank = 1;
}
break;
case GB_MBC5:
gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8);
gb->mbc_ram_bank = gb->mbc5.ram_bank;
break;
case GB_HUC1:
if (gb->huc1.mode == 0) {
gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6);
gb->mbc_ram_bank = 0;
}
else {
gb->mbc_rom_bank = gb->huc1.bank_low;
gb->mbc_ram_bank = gb->huc1.bank_high;
}
break;
case GB_HUC3:
gb->mbc_rom_bank = gb->huc3.rom_bank;
gb->mbc_ram_bank = gb->huc3.ram_bank;
break;
}
}
void GB_configure_cart(GB_gameboy_t *gb)
{
gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]];
if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) {
GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n");
gb->cartridge_type = &GB_cart_defs[0x11];
}
else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) {
GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]);
}
if (gb->cartridge_type->has_ram) {
if (gb->cartridge_type->mbc_type == GB_MBC2) {
gb->mbc_ram_size = 0x200;
}
else {
static const int ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
}
gb->mbc_ram = malloc(gb->mbc_ram_size);
/* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */
memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size);
}
/* MBC1 has at least 3 types of wiring (We currently support two (Standard and 4bit-MBC1M) of these).
See http://forums.nesdev.com/viewtopic.php?f=20&t=14099 */
/* Attempt to "guess" wiring */
if (gb->cartridge_type->mbc_type == GB_MBC1) {
if (gb->rom_size >= 0x44000 && memcmp(gb->rom + 0x104, gb->rom + 0x40104, 0x30) == 0) {
gb->mbc1_wiring = GB_MBC1M_WIRING;
}
}
/* Set MBC5's bank to 1 correctly */
if (gb->cartridge_type->mbc_type == GB_MBC5) {
gb->mbc5.rom_bank_low = 1;
}
}

31
waterbox/sameboy/mbc.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef MBC_h
#define MBC_h
#include "gb_struct_def.h"
typedef struct {
enum {
GB_NO_MBC,
GB_MBC1,
GB_MBC2,
GB_MBC3,
GB_MBC5,
GB_HUC1, /* Todo: HUC1 features are not emulated. Should be unified with the CGB IR sensor API. */
GB_HUC3,
} mbc_type;
enum {
GB_STANDARD_MBC,
GB_CAMERA,
} mbc_subtype;
bool has_ram;
bool has_battery;
bool has_rtc;
bool has_rumble;
} GB_cartridge_t;
#ifdef GB_INTERNAL
extern const GB_cartridge_t GB_cart_defs[256];
void GB_update_mbc_mappings(GB_gameboy_t *gb);
void GB_configure_cart(GB_gameboy_t *gb);
#endif
#endif /* MBC_h */

726
waterbox/sameboy/memory.c Normal file
View File

@ -0,0 +1,726 @@
#include <stdio.h>
#include <stdbool.h>
#include "gb.h"
typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr);
typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
typedef enum {
GB_BUS_MAIN, /* In DMG: Cart and RAM. In CGB: Cart only */
GB_BUS_RAM, /* In CGB only. */
GB_BUS_VRAM,
GB_BUS_INTERNAL, /* Anything in highram. Might not be the most correct name. */
} GB_bus_t;
static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr)
{
if (addr < 0x8000) {
return GB_BUS_MAIN;
}
if (addr < 0xA000) {
return GB_BUS_VRAM;
}
if (addr < 0xC000) {
return GB_BUS_MAIN;
}
if (addr < 0xFE00) {
return gb->is_cgb? GB_BUS_RAM : GB_BUS_MAIN;
}
return GB_BUS_INTERNAL;
}
static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr)
{
if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting)) return false;
return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src);
}
static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr)
{
if (addr < 0x100 && !gb->boot_rom_finished) {
return gb->boot_rom[addr];
}
if (addr >= 0x200 && addr < 0x900 && gb->is_cgb && !gb->boot_rom_finished) {
return gb->boot_rom[addr];
}
if (!gb->rom_size) {
return 0xFF;
}
unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000;
return gb->rom[effective_address & (gb->rom_size - 1)];
}
static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr)
{
unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000;
return gb->rom[effective_address & (gb->rom_size - 1)];
}
static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr)
{
if (gb->vram_read_blocked) {
return 0xFF;
}
return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000];
}
static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
{
if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) &&
gb->cartridge_type->mbc_subtype != GB_CAMERA &&
gb->cartridge_type->mbc_type != GB_HUC1) return 0xFF;
if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
/* RTC read */
gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */
return gb->rtc_latched.data[gb->mbc_ram_bank - 8];
}
if (gb->camera_registers_mapped) {
return GB_camera_read_register(gb, addr);
}
if (!gb->mbc_ram) {
return 0xFF;
}
if (gb->cartridge_type->mbc_subtype == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xa100 && addr < 0xaf00) {
return GB_camera_read_image(gb, addr - 0xa100);
}
uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)];
if (gb->cartridge_type->mbc_type == GB_MBC2) {
ret |= 0xF0;
}
return ret;
}
static uint8_t read_ram(GB_gameboy_t *gb, uint16_t addr)
{
return gb->ram[addr & 0x0FFF];
}
static uint8_t read_banked_ram(GB_gameboy_t *gb, uint16_t addr)
{
return gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000];
}
static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
{
if (addr < 0xFE00) {
return gb->ram[addr & 0x0FFF];
}
if (addr < 0xFEA0) {
if (gb->oam_read_blocked || (gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
return 0xFF;
}
return gb->oam[addr & 0xFF];
}
if (addr < 0xFF00) {
/* Unusable. CGB results are verified, but DMG results were tested on a SGB2 */
if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { /* Seems to be disabled in Modes 2 and 3 */
return 0xFF;
}
if (gb->is_cgb) {
return (addr & 0xF0) | ((addr >> 4) & 0xF);
}
return 0;
}
if (addr < 0xFF80) {
switch (addr & 0xFF) {
case GB_IO_IF:
return gb->io_registers[GB_IO_IF] | 0xE0;
case GB_IO_TAC:
return gb->io_registers[GB_IO_TAC] | 0xF8;
case GB_IO_STAT:
return gb->io_registers[GB_IO_STAT] | 0x80;
case GB_IO_DMG_EMULATION_INDICATION:
if (!gb->cgb_mode) {
return 0xFF;
}
return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE;
case GB_IO_PCM_12:
case GB_IO_PCM_34:
{
if (!gb->is_cgb) return 0xFF;
GB_sample_t dummy;
GB_apu_get_samples_and_update_pcm_regs(gb, &dummy);
}
/* Fall through */
case GB_IO_JOYP:
case GB_IO_TMA:
case GB_IO_LCDC:
case GB_IO_SCY:
case GB_IO_SCX:
case GB_IO_LY:
case GB_IO_LYC:
case GB_IO_BGP:
case GB_IO_OBP0:
case GB_IO_OBP1:
case GB_IO_WY:
case GB_IO_WX:
case GB_IO_SC:
case GB_IO_SB:
return gb->io_registers[addr & 0xFF];
case GB_IO_TIMA:
if (gb->tima_reload_state == GB_TIMA_RELOADING) {
return 0;
}
return gb->io_registers[GB_IO_TIMA];
case GB_IO_DIV:
return gb->div_cycles >> 8;
case GB_IO_HDMA5:
if (!gb->cgb_mode) return 0xFF;
return ((gb->hdma_on || gb->hdma_on_hblank)? 0 : 0x80) | ((gb->hdma_steps_left - 1) & 0x7F);
case GB_IO_SVBK:
if (!gb->cgb_mode) {
return 0xFF;
}
return gb->cgb_ram_bank | ~0x7;
case GB_IO_VBK:
if (!gb->is_cgb) {
return 0xFF;
}
return gb->cgb_vram_bank | ~0x1;
/* Todo: It seems that a CGB in DMG mode can access BGPI and OBPI, but not BGPD and OBPD? */
case GB_IO_BGPI:
case GB_IO_OBPI:
if (!gb->is_cgb) {
return 0xFF;
}
return gb->io_registers[addr & 0xFF] | 0x40;
case GB_IO_BGPD:
case GB_IO_OBPD:
{
if (!gb->cgb_mode && gb->boot_rom_finished) {
return 0xFF;
}
uint8_t index_reg = (addr & 0xFF) - 1;
return ((addr & 0xFF) == GB_IO_BGPD?
gb->background_palettes_data :
gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F];
}
case GB_IO_KEY1:
if (!gb->cgb_mode) {
return 0xFF;
}
return (gb->io_registers[GB_IO_KEY1] & 0x7F) | (gb->cgb_double_speed? 0xFE : 0x7E);
case GB_IO_RP: {
if (!gb->cgb_mode) return 0xFF;
/* You will read your own IR LED if it's on. */
bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1);
uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C;
if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && read_value) {
ret |= 2;
}
return ret;
}
case GB_IO_DMA:
/* Todo: is this documented? */
return gb->is_cgb? 0x00 : 0xFF;
case GB_IO_UNKNOWN2:
case GB_IO_UNKNOWN3:
return gb->is_cgb? gb->io_registers[addr & 0xFF] : 0xFF;
case GB_IO_UNKNOWN4:
return gb->cgb_mode? gb->io_registers[addr & 0xFF] : 0xFF;
case GB_IO_UNKNOWN5:
return gb->is_cgb? gb->io_registers[addr & 0xFF] | 0x8F : 0xFF;
default:
if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) {
return GB_apu_read(gb, addr & 0xFF);
}
return 0xFF;
}
/* Hardware registers */
return 0;
}
if (addr == 0xFFFF) {
/* Interrupt Mask */
return gb->interrupt_enable;
}
/* HRAM */
return gb->hram[addr - 0xFF80];
}
static GB_read_function_t * const read_map[] =
{
read_rom, read_rom, read_rom, read_rom, /* 0XXX, 1XXX, 2XXX, 3XXX */
read_mbc_rom, read_mbc_rom, read_mbc_rom, read_mbc_rom, /* 4XXX, 5XXX, 6XXX, 7XXX */
read_vram, read_vram, /* 8XXX, 9XXX */
read_mbc_ram, read_mbc_ram, /* AXXX, BXXX */
read_ram, read_banked_ram, /* CXXX, DXXX */
read_high_memory, read_high_memory, /* EXXX FXXX */
};
uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
{
if (gb->n_watchpoints) {
GB_debugger_test_read_watchpoint(gb, addr);
}
if (is_addr_in_dma_use(gb, addr)) {
addr = gb->dma_current_src;
}
return read_map[addr >> 12](gb, addr);
}
static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
switch (gb->cartridge_type->mbc_type) {
case GB_NO_MBC: return;
case GB_MBC1:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: case 0x3000: gb->mbc1.bank_low = value; break;
case 0x4000: case 0x5000: gb->mbc1.bank_high = value; break;
case 0x6000: case 0x7000: gb->mbc1.mode = value; break;
}
break;
case GB_MBC2:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break;
}
break;
case GB_MBC3:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break;
case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; break;
case 0x6000: case 0x7000:
if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct */
memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real));
}
gb->rtc_latch = value & 1;
break;
}
break;
case GB_MBC5:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: gb->mbc5.rom_bank_low = value; break;
case 0x3000: gb->mbc5.rom_bank_high = value; break;
case 0x4000: case 0x5000:
if (gb->cartridge_type->has_rumble) {
if (!!(value & 8) != gb->rumble_state) {
gb->rumble_state = !gb->rumble_state;
if (gb->rumble_callback) {
gb->rumble_callback(gb, gb->rumble_state);
}
}
value &= 7;
}
gb->mbc5.ram_bank = value;
gb->camera_registers_mapped = (value & 0x10) && gb->cartridge_type->mbc_subtype == GB_CAMERA;
break;
}
break;
case GB_HUC1:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: case 0x3000: gb->huc1.bank_low = value; break;
case 0x4000: case 0x5000: gb->huc1.bank_high = value; break;
case 0x6000: case 0x7000: gb->huc1.mode = value; break;
}
break;
case GB_HUC3:
switch (addr & 0xF000) {
case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
case 0x2000: case 0x3000: gb->huc3.rom_bank = value; break;
case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break;
}
break;
}
GB_update_mbc_mappings(gb);
}
static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
if (gb->vram_write_blocked) {
//GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr);
return;
}
gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value;
}
static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
if (gb->camera_registers_mapped) {
GB_camera_write_register(gb, addr, value);
return;
}
if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return;
if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
/* RTC read */
gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value; /* Todo: does it really write both? */
}
if (!gb->mbc_ram) {
return;
}
gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value;
}
static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
gb->ram[addr & 0x0FFF] = value;
}
static void write_banked_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value;
}
static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
if (addr < 0xFE00) {
GB_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr);
gb->ram[addr & 0x0FFF] = value;
return;
}
if (addr < 0xFEA0) {
if (gb->oam_write_blocked|| (gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
return;
}
gb->oam[addr & 0xFF] = value;
return;
}
if (addr < 0xFF00) {
GB_log(gb, "Wrote %02x to %04x (Unused)\n", value, addr);
return;
}
if (addr < 0xFF80) {
/* Hardware registers */
switch (addr & 0xFF) {
case GB_IO_SCX:
case GB_IO_IF:
case GB_IO_SCY:
case GB_IO_LYC:
case GB_IO_BGP:
case GB_IO_OBP0:
case GB_IO_OBP1:
case GB_IO_WY:
case GB_IO_WX:
case GB_IO_SB:
case GB_IO_DMG_EMULATION_INDICATION:
case GB_IO_UNKNOWN2:
case GB_IO_UNKNOWN3:
case GB_IO_UNKNOWN4:
case GB_IO_UNKNOWN5:
gb->io_registers[addr & 0xFF] = value;
return;
case GB_IO_TIMA:
if (gb->tima_reload_state != GB_TIMA_RELOADED) {
gb->io_registers[GB_IO_TIMA] = value;
}
return;
case GB_IO_TMA:
gb->io_registers[GB_IO_TMA] = value;
if (gb->tima_reload_state != GB_TIMA_RUNNING) {
gb->io_registers[GB_IO_TIMA] = value;
}
return;
case GB_IO_TAC:
GB_emulate_timer_glitch(gb, gb->io_registers[GB_IO_TAC], value);
gb->io_registers[GB_IO_TAC] = value;
return;
case GB_IO_LCDC:
if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) {
/* It appears that there's a slight delay after enabling the screen? */
/* Todo: verify this. */
gb->display_cycles = 0;
gb->first_scanline = true;
if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) {
gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON;
}
}
else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) {
/* Sync after turning off LCD */
GB_timing_sync(gb);
}
gb->io_registers[GB_IO_LCDC] = value;
return;
case GB_IO_STAT:
/* A DMG bug: http://www.devrs.com/gb/files/faqs.html#GBBugs */
if (!gb->is_cgb && !gb->stat_interrupt_line &&
(gb->io_registers[GB_IO_STAT] & 0x3) < 2 && (gb->io_registers[GB_IO_LCDC] & 0x80)) {
gb->io_registers[GB_IO_IF] |= 2;
}
/* Delete previous R/W bits */
gb->io_registers[GB_IO_STAT] &= 7;
/* Set them by value */
gb->io_registers[GB_IO_STAT] |= value & ~7;
/* Set unused bit to 1 */
gb->io_registers[GB_IO_STAT] |= 0x80;
return;
case GB_IO_DIV:
GB_set_internal_div_counter(gb, 0);
return;
case GB_IO_JOYP:
gb->io_registers[GB_IO_JOYP] &= 0x0F;
gb->io_registers[GB_IO_JOYP] |= value & 0xF0;
GB_update_joyp(gb);
return;
case GB_IO_BIOS:
gb->boot_rom_finished = true;
return;
case GB_IO_DMG_EMULATION:
if (gb->is_cgb && !gb->boot_rom_finished) {
gb->cgb_mode = value != 4; /* The real "contents" of this register aren't quite known yet. */
}
return;
case GB_IO_DMA:
if (value <= 0xE0) {
if (gb->dma_steps_left) {
/* This is not correct emulation, since we're not really delaying the second DMA.
One write that should have happened in the first DMA will not happen. However,
since that byte will be overwritten by the second DMA before it can actually be
read, it doesn't actually matter. */
gb->is_dma_restarting = true;
}
gb->dma_cycles = -7;
gb->dma_current_dest = 0;
gb->dma_current_src = value << 8;
gb->dma_steps_left = 0xa0;
}
/* else { what? } */
return;
case GB_IO_SVBK:
if (!gb->cgb_mode) {
return;
}
gb->cgb_ram_bank = value & 0x7;
if (!gb->cgb_ram_bank) {
gb->cgb_ram_bank++;
}
return;
case GB_IO_VBK:
if (!gb->cgb_mode) {
return;
}
gb->cgb_vram_bank = value & 0x1;
return;
case GB_IO_BGPI:
case GB_IO_OBPI:
if (!gb->is_cgb) {
return;
}
gb->io_registers[addr & 0xFF] = value;
return;
case GB_IO_BGPD:
case GB_IO_OBPD:
if (!gb->cgb_mode && gb->boot_rom_finished) {
/* Todo: Due to the behavior of a broken Game & Watch Gallery 2 ROM on a real CGB. A proper test ROM
is required. */
return;
}
uint8_t index_reg = (addr & 0xFF) - 1;
((addr & 0xFF) == GB_IO_BGPD?
gb->background_palettes_data :
gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F] = value;
GB_palette_changed(gb, (addr & 0xFF) == GB_IO_BGPD, gb->io_registers[index_reg] & 0x3F);
if (gb->io_registers[index_reg] & 0x80) {
gb->io_registers[index_reg]++;
gb->io_registers[index_reg] |= 0x80;
}
return;
case GB_IO_KEY1:
if (!gb->is_cgb) {
return;
}
gb->io_registers[GB_IO_KEY1] = value;
return;
case GB_IO_HDMA1:
if (gb->cgb_mode) {
gb->hdma_current_src &= 0xF0;
gb->hdma_current_src |= value << 8;
}
return;
case GB_IO_HDMA2:
if (gb->cgb_mode) {
gb->hdma_current_src &= 0xFF00;
gb->hdma_current_src |= value & 0xF0;
}
return;
case GB_IO_HDMA3:
if (gb->cgb_mode) {
gb->hdma_current_dest &= 0xF0;
gb->hdma_current_dest |= value << 8;
}
return;
case GB_IO_HDMA4:
if (gb->cgb_mode) {
gb->hdma_current_dest &= 0x1F00;
gb->hdma_current_dest |= value & 0xF0;
}
return;
case GB_IO_HDMA5:
if (!gb->cgb_mode) return;
if ((value & 0x80) == 0 && gb->hdma_on_hblank) {
gb->hdma_on_hblank = false;
return;
}
gb->hdma_on = (value & 0x80) == 0;
gb->hdma_on_hblank = (value & 0x80) != 0;
if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0) {
gb->hdma_on = true;
gb->hdma_cycles = 0;
}
gb->io_registers[GB_IO_HDMA5] = value;
gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1;
/* Todo: Verify this. Gambatte's DMA tests require this. */
if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) {
gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4;
}
gb->hdma_cycles = 0;
return;
/* Todo: what happens when starting a transfer during a transfer?
What happens when starting a transfer during external clock?
*/
case GB_IO_SC:
if (!gb->cgb_mode) {
value |= 2;
}
gb->io_registers[GB_IO_SC] = value | (~0x83);
if ((value & 0x80) && (value & 0x1) ) {
gb->serial_length = gb->cgb_mode && (value & 2)? 128 : 4096;
/* Todo: This is probably incorrect for CGB's faster clock mode. */
gb->serial_cycles &= 0xFF;
if (gb->serial_transfer_start_callback) {
gb->serial_transfer_start_callback(gb, gb->io_registers[GB_IO_SB]);
}
}
else {
gb->serial_length = 0;
}
return;
case GB_IO_RP: {
if (!gb->is_cgb) {
return;
}
if ((value & 1) != (gb->io_registers[GB_IO_RP] & 1)) {
if (gb->infrared_callback) {
gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change);
gb->cycles_since_ir_change = 0;
}
}
gb->io_registers[GB_IO_RP] = value;
return;
}
default:
if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) {
GB_apu_write(gb, addr & 0xFF, value);
return;
}
GB_log(gb, "Wrote %02x to %04x (HW Register)\n", value, addr);
return;
}
}
if (addr == 0xFFFF) {
/* Interrupt mask */
gb->interrupt_enable = value;
return;
}
/* HRAM */
gb->hram[addr - 0xFF80] = value;
}
static GB_write_function_t * const write_map[] =
{
write_mbc, write_mbc, write_mbc, write_mbc, /* 0XXX, 1XXX, 2XXX, 3XXX */
write_mbc, write_mbc, write_mbc, write_mbc, /* 4XXX, 5XXX, 6XXX, 7XXX */
write_vram, write_vram, /* 8XXX, 9XXX */
write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */
write_ram, write_banked_ram, /* CXXX, DXXX */
write_high_memory, write_high_memory, /* EXXX FXXX */
};
void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
if (gb->n_watchpoints) {
GB_debugger_test_write_watchpoint(gb, addr, value);
}
if (is_addr_in_dma_use(gb, addr)) {
/* Todo: What should happen? Will this affect DMA? Will data be written? What and where? */
return;
}
write_map[addr >> 12](gb, addr, value);
}
void GB_dma_run(GB_gameboy_t *gb)
{
while (gb->dma_cycles >= 4 && gb->dma_steps_left) {
/* Todo: measure this value */
gb->dma_cycles -= 4;
gb->dma_steps_left--;
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src);
/* dma_current_src must be the correct value during GB_read_memory */
gb->dma_current_src++;
if (!gb->dma_steps_left) {
gb->is_dma_restarting = false;
}
}
}
void GB_hdma_run(GB_gameboy_t *gb)
{
if (!gb->hdma_on) return;
while (gb->hdma_cycles >= 8) {
gb->hdma_cycles -= 8;
for (uint8_t i = 0; i < 0x10; i++) {
GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++)));
}
if(--gb->hdma_steps_left == 0){
gb->hdma_on = false;
gb->hdma_on_hblank = false;
gb->io_registers[GB_IO_HDMA5] &= 0x7F;
break;
}
if (gb->hdma_on_hblank) {
gb->hdma_on = false;
break;
}
}
}

12
waterbox/sameboy/memory.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef memory_h
#define memory_h
#include "gb.h"
uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr);
void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
#ifdef GB_INTERNAL
void GB_dma_run(GB_gameboy_t *gb);
void GB_hdma_run(GB_gameboy_t *gb);
#endif
#endif /* memory_h */

201
waterbox/sameboy/printer.c Normal file
View File

@ -0,0 +1,201 @@
#include "gb.h"
/* TODO: Emulation is VERY basic and assumes the ROM correctly uses the printer's interface.
Incorrect usage is not correctly emulated, as it's not well documented, nor do I
have my own GB Printer to figure it out myself.
It also does not currently emulate communication timeout, which means that a bug
might prevent the printer operation until the GameBoy is restarted.
Also, field mask values are assumed. */
static void handle_command(GB_gameboy_t *gb)
{
switch (gb->printer.command_id) {
case GB_PRINTER_INIT_COMMAND:
gb->printer.status = 0;
gb->printer.image_offset = 0;
break;
case GB_PRINTER_START_COMMAND:
if (gb->printer.command_length == 4) {
gb->printer.status = 6; /* Printing */
uint32_t image[gb->printer.image_offset];
uint8_t palette = gb->printer.command_data[2];
uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xff, 0xff, 0xff),
gb->rgb_encode_callback(gb, 0xaa, 0xaa, 0xaa),
gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55),
gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)};
for (unsigned i = 0; i < gb->printer.image_offset; i++) {
image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3];
}
if (gb->printer.callback) {
gb->printer.callback(gb, image, gb->printer.image_offset / 160,
gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7,
gb->printer.command_data[3] & 0x7F);
}
gb->printer.image_offset = 0;
}
break;
case GB_PRINTER_DATA_COMMAND:
if (gb->printer.command_length == GB_PRINTER_DATA_SIZE) {
gb->printer.image_offset %= sizeof(gb->printer.image);
gb->printer.status = 8; /* Received 0x280 bytes */
uint8_t *byte = gb->printer.command_data;
for (unsigned row = 2; row--; ) {
for (unsigned tile_x = 0; tile_x < 160 / 8; tile_x++) {
for (unsigned y = 0; y < 8; y++, byte += 2) {
for (unsigned x_pixel = 0; x_pixel < 8; x_pixel++) {
gb->printer.image[gb->printer.image_offset + tile_x * 8 + x_pixel + y * 160] =
((*byte) >> 7) | (((*(byte + 1)) >> 7) << 1);
(*byte) <<= 1;
(*(byte + 1)) <<= 1;
}
}
}
gb->printer.image_offset += 8 * 160;
}
}
case GB_PRINTER_NOP_COMMAND:
default:
break;
}
}
static void serial_start(GB_gameboy_t *gb, uint8_t byte_received)
{
gb->printer.byte_to_send = 0;
switch (gb->printer.command_state) {
case GB_PRINTER_COMMAND_MAGIC1:
if (byte_received != 0x88) {
return;
}
gb->printer.status &= ~1;
gb->printer.command_length = 0;
gb->printer.checksum = 0;
break;
case GB_PRINTER_COMMAND_MAGIC2:
if (byte_received != 0x33) {
if (byte_received != 0x88) {
gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
}
return;
}
break;
case GB_PRINTER_COMMAND_ID:
gb->printer.command_id = byte_received & 0xF;
break;
case GB_PRINTER_COMMAND_COMPRESSION:
gb->printer.compression = byte_received & 1;
break;
case GB_PRINTER_COMMAND_LENGTH_LOW:
gb->printer.length_left = byte_received;
break;
case GB_PRINTER_COMMAND_LENGTH_HIGH:
gb->printer.length_left |= (byte_received & 3) << 8;
break;
case GB_PRINTER_COMMAND_DATA:
if (gb->printer.command_length != GB_PRINTER_MAX_COMMAND_LENGTH) {
if (gb->printer.compression) {
if (!gb->printer.compression_run_lenth) {
gb->printer.compression_run_is_compressed = byte_received & 0x80;
gb->printer.compression_run_lenth = (byte_received & 0x7F) + 1 + gb->printer.compression_run_is_compressed;
}
else if (gb->printer.compression_run_is_compressed) {
while (gb->printer.compression_run_lenth) {
gb->printer.command_data[gb->printer.command_length++] = byte_received;
gb->printer.compression_run_lenth--;
if (gb->printer.command_length == GB_PRINTER_MAX_COMMAND_LENGTH) {
gb->printer.compression_run_lenth = 0;
}
}
}
else {
gb->printer.command_data[gb->printer.command_length++] = byte_received;
gb->printer.compression_run_lenth--;
}
}
else {
gb->printer.command_data[gb->printer.command_length++] = byte_received;
}
}
gb->printer.length_left--;
break;
case GB_PRINTER_COMMAND_CHECKSUM_LOW:
gb->printer.checksum ^= byte_received;
break;
case GB_PRINTER_COMMAND_CHECKSUM_HIGH:
gb->printer.checksum ^= byte_received << 8;
if (gb->printer.checksum) {
gb->printer.status |= 1; /* Checksum error*/
gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
return;
}
break;
case GB_PRINTER_COMMAND_ACTIVE:
gb->printer.byte_to_send = 0x81;
break;
case GB_PRINTER_COMMAND_STATUS:
if ((gb->printer.command_id & 0xF) == GB_PRINTER_INIT_COMMAND) {
/* Games expect INIT commands to return 0? */
gb->printer.byte_to_send = 0;
}
else {
gb->printer.byte_to_send = gb->printer.status;
}
/* Printing is done instantly, but let the game recieve a 6 (Printing) status at least once, for compatibility */
if (gb->printer.status == 6) {
gb->printer.status = 4; /* Done */
}
gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
handle_command(gb);
return;
}
if (gb->printer.command_state >= GB_PRINTER_COMMAND_ID && gb->printer.command_state < GB_PRINTER_COMMAND_CHECKSUM_LOW) {
gb->printer.checksum += byte_received;
}
if (gb->printer.command_state != GB_PRINTER_COMMAND_DATA) {
gb->printer.command_state++;
}
if (gb->printer.command_state == GB_PRINTER_COMMAND_DATA) {
if (gb->printer.length_left == 0) {
gb->printer.command_state++;
}
}
}
static uint8_t serial_end(GB_gameboy_t *gb)
{
return gb->printer.byte_to_send;
}
void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback)
{
memset(&gb->printer, 0, sizeof(gb->printer));
GB_set_serial_transfer_start_callback(gb, serial_start);
GB_set_serial_transfer_end_callback(gb, serial_end);
gb->printer.callback = callback;
}

View File

@ -0,0 +1,59 @@
#ifndef printer_h
#define printer_h
#include <stdint.h>
#include <stdbool.h>
#include "gb_struct_def.h"
#define GB_PRINTER_MAX_COMMAND_LENGTH 0x280
#define GB_PRINTER_DATA_SIZE 0x280
typedef void (*GB_print_image_callback_t)(GB_gameboy_t *gb,
uint32_t *image,
uint8_t height,
uint8_t top_margin,
uint8_t bottom_margin,
uint8_t exposure);
typedef struct
{
/* Communication state machine */
enum {
GB_PRINTER_COMMAND_MAGIC1,
GB_PRINTER_COMMAND_MAGIC2,
GB_PRINTER_COMMAND_ID,
GB_PRINTER_COMMAND_COMPRESSION,
GB_PRINTER_COMMAND_LENGTH_LOW,
GB_PRINTER_COMMAND_LENGTH_HIGH,
GB_PRINTER_COMMAND_DATA,
GB_PRINTER_COMMAND_CHECKSUM_LOW,
GB_PRINTER_COMMAND_CHECKSUM_HIGH,
GB_PRINTER_COMMAND_ACTIVE,
GB_PRINTER_COMMAND_STATUS,
} command_state : 8;
enum {
GB_PRINTER_INIT_COMMAND = 1,
GB_PRINTER_START_COMMAND = 2,
GB_PRINTER_DATA_COMMAND = 4,
GB_PRINTER_NOP_COMMAND = 0xF,
} command_id : 8;
bool compression;
uint16_t length_left;
uint8_t command_data[GB_PRINTER_MAX_COMMAND_LENGTH];
uint16_t command_length;
uint16_t checksum;
uint8_t status;
uint8_t byte_to_send;
uint8_t image[160 * 200];
uint16_t image_offset;
GB_print_image_callback_t callback;
uint8_t compression_run_lenth;
bool compression_run_is_compressed;
} GB_printer_t;
void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback);
#endif

View File

@ -0,0 +1,304 @@
#include "gb.h"
#include <stdio.h>
#include <errno.h>
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))
/* Todo: we need a sane and protable save state format. */
int GB_save_state(GB_gameboy_t *gb, const char *path)
{
FILE *f = fopen(path, "wb");
if (!f) {
GB_log(gb, "Could not open save state: %s.\n", strerror(errno));
return errno;
}
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;
if (!DUMP_SECTION(gb, f, dma )) goto error;
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;
if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
goto error;
}
if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
goto error;
}
if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
goto error;
}
errno = 0;
error:
fclose(f);
return errno;
}
#undef DUMP_SECTION
size_t GB_get_save_state_size(GB_gameboy_t *gb)
{
return GB_SECTION_SIZE(header)
+ GB_SECTION_SIZE(core_state) + sizeof(uint32_t)
+ GB_SECTION_SIZE(dma ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(mbc ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(hram ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(timing ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(apu ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(rtc ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(video ) + sizeof(uint32_t)
+ gb->mbc_ram_size
+ gb->ram_size
+ gb->vram_size;
}
/* A write-line function for memory copying */
static void buffer_write(const void *src, size_t size, uint8_t **dest)
{
memcpy(*dest, src, size);
*dest += size;
}
static void buffer_dump_section(uint8_t **buffer, const void *src, uint32_t size)
{
buffer_write(&size, sizeof(size), buffer);
buffer_write(src, size, buffer);
}
#define DUMP_SECTION(gb, buffer, section) buffer_dump_section(&buffer, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
{
buffer_write(GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header), &buffer);
DUMP_SECTION(gb, buffer, core_state);
DUMP_SECTION(gb, buffer, dma );
DUMP_SECTION(gb, buffer, mbc );
DUMP_SECTION(gb, buffer, hram );
DUMP_SECTION(gb, buffer, timing );
DUMP_SECTION(gb, buffer, apu );
DUMP_SECTION(gb, buffer, rtc );
DUMP_SECTION(gb, buffer, video );
buffer_write(gb->mbc_ram, gb->mbc_ram_size, &buffer);
buffer_write(gb->ram, gb->ram_size, &buffer);
buffer_write(gb->vram, gb->vram_size, &buffer);
}
/* 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;
}
#undef DUMP_SECTION
static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
{
if (gb->magic != save->magic) {
GB_log(gb, "File is not a save state, or is from an incompatible operating system.\n");
return false;
}
if (gb->version != save->version) {
GB_log(gb, "Save state is for a different version of SameBoy.\n");
return false;
}
if (gb->mbc_ram_size < save->mbc_ram_size) {
GB_log(gb, "Save state has non-matching MBC RAM size.\n");
return false;
}
if (gb->ram_size != save->ram_size) {
GB_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n");
return false;
}
if (gb->vram_size != save->vram_size) {
GB_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n");
return false;
}
return true;
}
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
int GB_load_state(GB_gameboy_t *gb, const char *path)
{
GB_gameboy_t save;
/* Every unread value should be kept the same. */
memcpy(&save, gb, sizeof(save));
FILE *f = fopen(path, "rb");
if (!f) {
GB_log(gb, "Could not open save state: %s.\n", strerror(errno));
return errno;
}
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;
if (!READ_SECTION(&save, f, dma )) goto error;
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;
if (!verify_state_compatibility(gb, &save)) {
errno = -1;
goto error;
}
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
if (fread(gb->mbc_ram, 1, save.mbc_ram_size, f) != save.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;
}
memcpy(gb, &save, sizeof(save));
errno = 0;
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
gb->rumble_callback(gb, gb->rumble_state);
}
error:
fclose(f);
return errno;
}
#undef READ_SECTION
/* An read-like function for buffer-copying */
static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, size_t *buffer_length)
{
if (length > *buffer_length) {
length = *buffer_length;
}
memcpy(dest, *buffer, length);
*buffer += length;
*buffer_length -= length;
return length;
}
static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size)
{
uint32_t saved_size = 0;
if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) {
return false;
}
if (saved_size <= size) {
if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) {
return false;
}
}
else {
if (buffer_read(dest, size, buffer, buffer_length) != size) {
return false;
}
*buffer += saved_size - size;
*buffer_length -= saved_size - size;
}
return true;
}
#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length)
{
GB_gameboy_t save;
/* Every unread value should be kept the same. */
memcpy(&save, gb, sizeof(save));
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
if (!READ_SECTION(&save, buffer, length, core_state)) return -1;
if (!READ_SECTION(&save, buffer, length, dma )) return -1;
if (!READ_SECTION(&save, buffer, length, mbc )) return -1;
if (!READ_SECTION(&save, buffer, length, hram )) return -1;
if (!READ_SECTION(&save, buffer, length, timing )) return -1;
if (!READ_SECTION(&save, buffer, length, apu )) return -1;
if (!READ_SECTION(&save, buffer, length, rtc )) return -1;
if (!READ_SECTION(&save, buffer, length, video )) return -1;
if (!verify_state_compatibility(gb, &save)) {
return -1;
}
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
if (buffer_read(gb->mbc_ram, save.mbc_ram_size, &buffer, &length) != save.mbc_ram_size) {
return -1;
}
if (buffer_read(gb->ram, gb->ram_size, &buffer, &length) != gb->ram_size) {
return -1;
}
if (buffer_read(gb->vram,gb->vram_size, &buffer, &length) != gb->vram_size) {
return -1;
}
memcpy(gb, &save, sizeof(save));
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
gb->rumble_callback(gb, gb->rumble_state);
}
return 0;
}
#undef READ_SECTION

View File

@ -0,0 +1,24 @@
/* Macros to make the GB_gameboy_t struct more future compatible when state saving */
#ifndef save_state_h
#define save_state_h
#include <stddef.h>
#define GB_PADDING(type, old_usage) type old_usage##__do_not_use
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end
#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start))
#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start))
#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start))
#define GB_aligned_double __attribute__ ((aligned (8))) double
/* Public calls related to save states */
int GB_save_state(GB_gameboy_t *gb, const char *path);
size_t GB_get_save_state_size(GB_gameboy_t *gb);
/* Assumes buffer is big enough to contain the save state. Use with GB_get_save_state_size(). */
void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer);
int GB_load_state(GB_gameboy_t *gb, const char *path);
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length);
#endif /* save_state_h */

View File

@ -0,0 +1,106 @@
#include "gb.h"
static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr)
{
if (!map->symbols) {
return 0;
}
ssize_t min = 0;
ssize_t max = map->n_symbols;
while (min < max) {
size_t pivot = (min + max) / 2;
if (map->symbols[pivot].addr == addr) return pivot;
if (map->symbols[pivot].addr > addr) {
max = pivot;
}
else {
min = pivot + 1;
}
}
return (size_t) min;
}
GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name)
{
size_t index = GB_map_find_symbol_index(map, addr);
if (index < map->n_symbols && map->symbols[index].addr == addr) return NULL;
map->symbols = realloc(map->symbols, (map->n_symbols + 1) * sizeof(map->symbols[0]));
memmove(&map->symbols[index + 1], &map->symbols[index], (map->n_symbols - index) * sizeof(map->symbols[0]));
map->symbols[index].addr = addr;
map->symbols[index].name = strdup(name);
map->n_symbols++;
return &map->symbols[index];
}
const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr)
{
if (!map) return NULL;
size_t index = GB_map_find_symbol_index(map, addr);
if (index < map->n_symbols && map->symbols[index].addr != addr) {
index--;
}
if (index < map->n_symbols) {
return &map->symbols[index];
}
return NULL;
}
GB_symbol_map_t *GB_map_alloc(void)
{
GB_symbol_map_t *map = malloc(sizeof(*map));
memset(map, 0, sizeof(*map));
return map;
}
void GB_map_free(GB_symbol_map_t *map)
{
for (unsigned i = 0; i < map->n_symbols; i++) {
free(map->symbols[i].name);
}
if (map->symbols) {
free(map->symbols);
}
free(map);
}
static int hash_name(const char *name)
{
int r = 0;
while (*name) {
r <<= 1;
if (r & 0x400) {
r ^= 0x401;
}
r += (unsigned char)*(name++);
}
return r & 0x3FF;
}
void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *bank_symbol)
{
int hash = hash_name(bank_symbol->name);
GB_symbol_t *symbol = malloc(sizeof(*symbol));
symbol->name = bank_symbol->name;
symbol->addr = bank_symbol->addr;
symbol->bank = bank;
symbol->next = map->buckets[hash];
map->buckets[hash] = symbol;
}
const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name)
{
int hash = hash_name(name);
GB_symbol_t *symbol = map->buckets[hash];
while (symbol) {
if (strcmp(symbol->name, name) == 0) return symbol;
symbol = symbol->next;
}
return NULL;
}

View File

@ -0,0 +1,37 @@
#ifndef symbol_hash_h
#define symbol_hash_h
#include <stdlib.h>
#include <string.h>
typedef struct {
char *name;
uint16_t addr;
} GB_bank_symbol_t;
typedef struct GB_symbol_s {
struct GB_symbol_s *next;
const char *name;
uint16_t bank;
uint16_t addr;
} GB_symbol_t;
typedef struct {
GB_bank_symbol_t *symbols;
size_t n_symbols;
} GB_symbol_map_t;
typedef struct {
GB_symbol_t *buckets[0x400];
} GB_reversed_symbol_map_t;
#ifdef GB_INTERNAL
void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *symbol);
const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name);
GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name);
const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr);
GB_symbol_map_t *GB_map_alloc(void);
void GB_map_free(GB_symbol_map_t *map);
#endif
#endif /* symbol_hash_h */

228
waterbox/sameboy/timing.c Normal file
View File

@ -0,0 +1,228 @@
#include "gb.h"
#ifdef _WIN32
#define _WIN32_WINNT 0x0500
#include <Windows.h>
#else
#include <sys/time.h>
#endif
static int64_t get_nanoseconds(void)
{
#ifndef _WIN32
struct timeval now;
gettimeofday(&now, NULL);
return (now.tv_usec) * 1000 + now.tv_sec * 1000000000L;
#else
FILETIME time;
GetSystemTimeAsFileTime(&time);
return (((int64_t)time.dwHighDateTime << 32) | time.dwLowDateTime) * 100L;
#endif
}
static void nsleep(uint64_t nanoseconds)
{
#ifndef _WIN32
struct timespec sleep = {0, nanoseconds};
nanosleep(&sleep, NULL);
#else
HANDLE timer;
LARGE_INTEGER time;
timer = CreateWaitableTimer(NULL, true, NULL);
time.QuadPart = -(nanoseconds / 100L);
SetWaitableTimer(timer, &time, 0, NULL, NULL, false);
WaitForSingleObject(timer, INFINITE);
CloseHandle(timer);
#endif
}
bool GB_timing_sync_turbo(GB_gameboy_t *gb)
{
if (!gb->turbo_dont_skip) {
int64_t nanoseconds = get_nanoseconds();
if (nanoseconds <= gb->last_sync + FRAME_LENGTH) {
return true;
}
gb->last_sync = nanoseconds;
}
return false;
}
void GB_timing_sync(GB_gameboy_t *gb)
{
if (gb->turbo) {
gb->cycles_since_last_sync = 0;
return;
}
/* Prevent syncing if not enough time has passed.*/
if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return;
uint64_t target_nanoseconds = gb->cycles_since_last_sync * FRAME_LENGTH / LCDC_PERIOD;
int64_t nanoseconds = get_nanoseconds();
if (labs((signed long)(nanoseconds - gb->last_sync)) < target_nanoseconds ) {
nsleep(target_nanoseconds + gb->last_sync - nanoseconds);
gb->last_sync += target_nanoseconds;
}
else {
gb->last_sync = nanoseconds;
}
gb->cycles_since_last_sync = 0;
}
static void GB_ir_run(GB_gameboy_t *gb)
{
if (gb->ir_queue_length == 0) return;
if (gb->cycles_since_input_ir_change >= gb->ir_queue[0].delay) {
gb->cycles_since_input_ir_change -= gb->ir_queue[0].delay;
gb->infrared_input = gb->ir_queue[0].state;
gb->ir_queue_length--;
memmove(&gb->ir_queue[0], &gb->ir_queue[1], sizeof(gb->ir_queue[0]) * (gb->ir_queue_length));
}
}
static void advance_tima_state_machine(GB_gameboy_t *gb)
{
if (gb->tima_reload_state == GB_TIMA_RELOADED) {
gb->tima_reload_state = GB_TIMA_RUNNING;
}
else if (gb->tima_reload_state == GB_TIMA_RELOADING) {
gb->tima_reload_state = GB_TIMA_RELOADED;
}
}
void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
{
// Affected by speed boost
gb->dma_cycles += cycles;
advance_tima_state_machine(gb);
for (int i = 0; i < cycles; i += 4) {
GB_set_internal_div_counter(gb, gb->div_cycles + 4);
}
if (cycles > 4) {
advance_tima_state_machine(gb);
if (cycles > 8) {
advance_tima_state_machine(gb);
}
}
uint16_t previous_serial_cycles = gb->serial_cycles;
gb->serial_cycles += cycles;
if (gb->serial_length) {
if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) {
gb->serial_length = 0;
gb->io_registers[GB_IO_SC] &= ~0x80;
/* TODO: Does SB "update" bit by bit? */
if (gb->serial_transfer_end_callback) {
gb->io_registers[GB_IO_SB] = gb->serial_transfer_end_callback(gb);
}
else {
gb->io_registers[GB_IO_SB] = 0xFF;
}
gb->io_registers[GB_IO_IF] |= 8;
}
}
gb->debugger_ticks += cycles;
if (gb->cgb_double_speed) {
cycles >>=1;
}
// Not affected by speed boost
gb->hdma_cycles += cycles;
gb->apu_sample_cycles += cycles;
gb->apu_subsample_cycles += cycles;
gb->apu.apu_cycles += cycles;
gb->cycles_since_ir_change += cycles;
gb->cycles_since_input_ir_change += cycles;
gb->cycles_since_last_sync += cycles;
GB_dma_run(gb);
GB_hdma_run(gb);
GB_apu_run(gb);
GB_display_run(gb, cycles);
GB_ir_run(gb);
}
/* Standard Timers */
static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256};
static void increase_tima(GB_gameboy_t *gb)
{
gb->io_registers[GB_IO_TIMA]++;
if (gb->io_registers[GB_IO_TIMA] == 0) {
gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA];
gb->io_registers[GB_IO_IF] |= 4;
gb->tima_reload_state = GB_TIMA_RELOADING;
}
}
static bool counter_overflow_check(uint32_t old, uint32_t new, uint32_t max)
{
return (old & (max >> 1)) && !(new & (max >> 1));
}
void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value)
{
/* TIMA increases when a specific high-bit becomes a low-bit. */
value &= INTERNAL_DIV_CYCLES - 1;
if ((gb->io_registers[GB_IO_TAC] & 4) &&
counter_overflow_check(gb->div_cycles, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) {
increase_tima(gb);
}
gb->div_cycles = value;
}
/*
This glitch is based on the expected results of mooneye-gb rapid_toggle test.
This glitch happens because how TIMA is increased, see GB_set_internal_div_counter.
According to GiiBiiAdvance, GBC's behavior is different, but this was not tested or implemented.
*/
void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac)
{
/* Glitch only happens when old_tac is enabled. */
if (!(old_tac & 4)) return;
unsigned int old_clocks = GB_TAC_RATIOS[old_tac & 3];
unsigned int new_clocks = GB_TAC_RATIOS[new_tac & 3];
/* The bit used for overflow testing must have been 1 */
if (gb->div_cycles & (old_clocks >> 1)) {
/* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */
if (!(new_tac & 4) || gb->div_cycles & (new_clocks >> 1)) {
increase_tima(gb);
}
}
}
void GB_rtc_run(GB_gameboy_t *gb)
{
if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */
time_t current_time = time(NULL);
while (gb->last_rtc_second < current_time) {
gb->last_rtc_second++;
if (++gb->rtc_real.seconds == 60)
{
gb->rtc_real.seconds = 0;
if (++gb->rtc_real.minutes == 60)
{
gb->rtc_real.minutes = 0;
if (++gb->rtc_real.hours == 24)
{
gb->rtc_real.hours = 0;
if (++gb->rtc_real.days == 0)
{
if (gb->rtc_real.high & 1) /* Bit 8 of days*/
{
gb->rtc_real.high |= 0x80; /* Overflow bit */
}
gb->rtc_real.high ^= 1;
}
}
}
}
}
}
}

21
waterbox/sameboy/timing.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef timing_h
#define timing_h
#include "gb.h"
#ifdef GB_INTERNAL
void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles);
void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value);
void GB_rtc_run(GB_gameboy_t *gb);
void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac);
bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */
void GB_timing_sync(GB_gameboy_t *gb);
enum {
GB_TIMA_RUNNING = 0,
GB_TIMA_RELOADING = 1,
GB_TIMA_RELOADED = 2
};
#endif
#endif /* timing_h */

1381
waterbox/sameboy/z80_cpu.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
#ifndef z80_cpu_h
#define z80_cpu_h
#include "gb.h"
void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count);
#ifdef GB_INTERNAL
void GB_cpu_run(GB_gameboy_t *gb);
#endif
#endif /* z80_cpu_h */

View File

@ -0,0 +1,788 @@
#include <stdio.h>
#include <stdbool.h>
#include "gb.h"
typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc);
static void ill(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, ".BYTE $%02x\n", opcode);
(*pc)++;
}
static void nop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "NOP\n");
(*pc)++;
}
static void stop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint8_t next = GB_read_memory(gb, (*pc)++);
if (next) {
GB_log(gb, "CORRUPTED STOP (%02x)\n", next);
}
else {
GB_log(gb, "STOP\n");
}
}
static char *register_names[] = {"af", "bc", "de", "hl", "sp"};
static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
uint16_t value;
register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
value = GB_read_memory(gb, (*pc)++);
value |= GB_read_memory(gb, (*pc)++) << 8;
const char *symbol = GB_debugger_name_for_address(gb, value);
if (symbol) {
GB_log(gb, "LD %s, %s ; =$%04x\n", register_names[register_id], symbol, value);
}
else {
GB_log(gb, "LD %s, $%04x\n", register_names[register_id], value);
}
}
static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
GB_log(gb, "LD [%s], a\n", register_names[register_id]);
}
static void inc_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
GB_log(gb, "INC %s\n", register_names[register_id]);
}
static void inc_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
(*pc)++;
register_id = ((opcode >> 4) + 1) & 0x03;
GB_log(gb, "INC %c\n", register_names[register_id][0]);
}
static void dec_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
(*pc)++;
register_id = ((opcode >> 4) + 1) & 0x03;
GB_log(gb, "DEC %c\n", register_names[register_id][0]);
}
static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
(*pc)++;
register_id = ((opcode >> 4) + 1) & 0x03;
GB_log(gb, "LD %c, $%02x\n", register_names[register_id][0], GB_read_memory(gb, (*pc)++));
}
static void rlca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "RLCA\n");
}
static void rla(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "RLA\n");
}
static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc){
uint16_t addr;
(*pc)++;
addr = GB_read_memory(gb, (*pc)++);
addr |= GB_read_memory(gb, (*pc)++) << 8;
const char *symbol = GB_debugger_name_for_address(gb, addr);
if (symbol) {
GB_log(gb, "LD [%s], sp ; =$%04x\n", symbol, addr);
}
else {
GB_log(gb, "LD [$%04x], sp\n", addr);
}
}
static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
(*pc)++;
register_id = (opcode >> 4) + 1;
GB_log(gb, "ADD hl, %s\n", register_names[register_id]);
}
static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
GB_log(gb, "LD a, [%s]\n", register_names[register_id]);
}
static void dec_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
GB_log(gb, "DEC %s\n", register_names[register_id]);
}
static void inc_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
GB_log(gb, "INC %c\n", register_names[register_id][1]);
}
static void dec_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
GB_log(gb, "DEC %c\n", register_names[register_id][1]);
}
static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
GB_log(gb, "LD %c, $%02x\n", register_names[register_id][1], GB_read_memory(gb, (*pc)++));
}
static void rrca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "RRCA\n");
(*pc)++;
}
static void rra(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "RRA\n");
(*pc)++;
}
static void jr_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1;
const char *symbol = GB_debugger_name_for_address(gb, addr);
if (symbol) {
GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR %s ; =$%04x\n", symbol, addr);
}
else {
GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR $%04x\n", addr);
}
(*pc)++;
}
static const char *condition_code(uint8_t opcode)
{
switch ((opcode >> 3) & 0x3) {
case 0:
return "nz";
case 1:
return "z";
case 2:
return "nc";
case 3:
return "c";
}
return NULL;
}
static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1;
const char *symbol = GB_debugger_name_for_address(gb, addr);
if (symbol) {
GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr);
}
else {
GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, $%04x\n", condition_code(opcode), addr);
}
(*pc)++;
}
static void daa(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "DAA\n");
(*pc)++;
}
static void cpl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "CPL\n");
(*pc)++;
}
static void scf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "SCF\n");
(*pc)++;
}
static void ccf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "CCF\n");
(*pc)++;
}
static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "LD [hli], a\n");
(*pc)++;
}
static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "LD [hld], a\n");
(*pc)++;
}
static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "LD a, [hli]\n");
(*pc)++;
}
static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "LD a, [hld]\n");
(*pc)++;
}
static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "INC [hl]\n");
(*pc)++;
}
static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
GB_log(gb, "DEC [hl]\n");
(*pc)++;
}
static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "LD [hl], $%02x\n", GB_read_memory(gb, (*pc)++));
}
static const char *get_src_name(uint8_t opcode)
{
uint8_t src_register_id;
uint8_t src_low;
src_register_id = ((opcode >> 1) + 1) & 3;
src_low = (opcode & 1);
if (src_register_id == GB_REGISTER_AF) {
return src_low? "a": "[hl]";
}
if (src_low) {
return register_names[src_register_id] + 1;
}
static const char *high_register_names[] = {"a", "b", "d", "h"};
return high_register_names[src_register_id];
}
static const char *get_dst_name(uint8_t opcode)
{
uint8_t dst_register_id;
uint8_t dst_low;
dst_register_id = ((opcode >> 4) + 1) & 3;
dst_low = opcode & 8;
if (dst_register_id == GB_REGISTER_AF) {
return dst_low? "a": "[hl]";
}
if (dst_low) {
return register_names[dst_register_id] + 1;
}
static const char *high_register_names[] = {"a", "b", "d", "h"};
return high_register_names[dst_register_id];
}
static void ld_r_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "LD %s, %s\n", get_dst_name(opcode), get_src_name(opcode));
}
static void add_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "ADD %s\n", get_src_name(opcode));
}
static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "ADC %s\n", get_src_name(opcode));
}
static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "SUB %s\n", get_src_name(opcode));
}
static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "SBC %s\n", get_src_name(opcode));
}
static void and_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "AND %s\n", get_src_name(opcode));
}
static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "XOR %s\n", get_src_name(opcode));
}
static void or_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "OR %s\n", get_src_name(opcode));
}
static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "CP %s\n", get_src_name(opcode));
}
static void halt(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "HALT\n");
}
static void ret_cc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "RET %s\n", condition_code(opcode));
}
static void pop_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
register_id = ((GB_read_memory(gb, (*pc)++) >> 4) + 1) & 3;
GB_log(gb, "POP %s\n", register_names[register_id]);
}
static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
const char *symbol = GB_debugger_name_for_address(gb, addr);
if (symbol) {
GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr);
}
else {
GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, $%04x\n", condition_code(opcode), addr);
}
(*pc) += 2;
}
static void jp_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
const char *symbol = GB_debugger_name_for_address(gb, addr);
if (symbol) {
GB_log(gb, "JP %s ; =$%04x\n", symbol, addr);
}
else {
GB_log(gb, "JP $%04x\n", addr);
}
(*pc) += 2;
}
static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
const char *symbol = GB_debugger_name_for_address(gb, addr);
if (symbol) {
GB_log(gb, "CALL %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr);
}
else {
GB_log(gb, "CALL %s, $%04x\n", condition_code(opcode), addr);
}
(*pc) += 2;
}
static void push_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t register_id;
register_id = ((GB_read_memory(gb, (*pc)++) >> 4) + 1) & 3;
GB_log(gb, "PUSH %s\n", register_names[register_id]);
}
static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "ADD $%02x\n", GB_read_memory(gb, (*pc)++));
}
static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "ADC $%02x\n", GB_read_memory(gb, (*pc)++));
}
static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "SUB $%02x\n", GB_read_memory(gb, (*pc)++));
}
static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "SBC $%02x\n", GB_read_memory(gb, (*pc)++));
}
static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "AND $%02x\n", GB_read_memory(gb, (*pc)++));
}
static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "XOR $%02x\n", GB_read_memory(gb, (*pc)++));
}
static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "OR $%02x\n", GB_read_memory(gb, (*pc)++));
}
static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "CP $%02x\n", GB_read_memory(gb, (*pc)++));
}
static void rst(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "RST $%02x\n", opcode ^ 0xC7);
}
static void ret(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_attributed_log(gb, GB_LOG_UNDERLINE, "RET\n");
}
static void reti(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_attributed_log(gb, GB_LOG_UNDERLINE, "RETI\n");
}
static void call_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr);
if (symbol) {
GB_log(gb, "CALL %s ; =$%04x\n", symbol, addr);
}
else {
GB_log(gb, "CALL $%04x\n", addr);
}
(*pc) += 2;
}
static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint8_t addr = GB_read_memory(gb, (*pc)++);
const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr);
if (symbol) {
GB_log(gb, "LDH [%s & $FF], a ; =$%02x\n", symbol, addr);
}
else {
GB_log(gb, "LDH [$%02x], a\n", addr);
}
}
static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint8_t addr = GB_read_memory(gb, (*pc)++);
const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr);
if (symbol) {
GB_log(gb, "LDH a, [%s & $FF] ; =$%02x\n", symbol, addr);
}
else {
GB_log(gb, "LDH a, [$%02x]\n", addr);
}
}
static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "LDH [c], a\n");
}
static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "LDH a, [c]\n");
}
static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
int8_t temp = GB_read_memory(gb, (*pc)++);
GB_log(gb, "ADD SP, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp);
}
static void jp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "JP hl\n");
}
static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
const char *symbol = GB_debugger_name_for_address(gb, addr);
if (symbol) {
GB_log(gb, "LD [%s], a ; =$%04x\n", symbol, addr);
}
else {
GB_log(gb, "LD [$%04x], a\n", addr);
}
(*pc) += 2;
}
static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
const char *symbol = GB_debugger_name_for_address(gb, addr);
if (symbol) {
GB_log(gb, "LD a, [%s] ; =$%04x\n", symbol, addr);
}
else {
GB_log(gb, "LD a, [$%04x]\n", addr);
}
(*pc) += 2;
}
static void di(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "DI\n");
}
static void ei(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "EI\n");
}
static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
int8_t temp = GB_read_memory(gb, (*pc)++);
GB_log(gb, "LD hl, sp, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp);
}
static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "LD sp, hl\n");
}
static void rlc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "RLC %s\n", get_src_name(opcode));
}
static void rrc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "RRC %s\n", get_src_name(opcode));
}
static void rl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "RL %s\n", get_src_name(opcode));
}
static void rr_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "RR %s\n", get_src_name(opcode));
}
static void sla_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "SLA %s\n", get_src_name(opcode));
}
static void sra_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "SRA %s\n", get_src_name(opcode));
}
static void srl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "SRL %s\n", get_src_name(opcode));
}
static void swap_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
(*pc)++;
GB_log(gb, "RLC %s\n", get_src_name(opcode));
}
static void bit_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
uint8_t bit;
(*pc)++;
bit = ((opcode >> 3) & 7);
if ((opcode & 0xC0) == 0x40) { /* Bit */
GB_log(gb, "BIT %s, %d\n", get_src_name(opcode), bit);
}
else if ((opcode & 0xC0) == 0x80) { /* res */
GB_log(gb, "RES %s, %d\n", get_src_name(opcode), bit);
}
else if ((opcode & 0xC0) == 0xC0) { /* set */
GB_log(gb, "SET %s, %d\n", get_src_name(opcode), bit);
}
}
static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
{
opcode = GB_read_memory(gb, ++*pc);
switch (opcode >> 3) {
case 0:
rlc_r(gb, opcode, pc);
break;
case 1:
rrc_r(gb, opcode, pc);
break;
case 2:
rl_r(gb, opcode, pc);
break;
case 3:
rr_r(gb, opcode, pc);
break;
case 4:
sla_r(gb, opcode, pc);
break;
case 5:
sra_r(gb, opcode, pc);
break;
case 6:
swap_r(gb, opcode, pc);
break;
case 7:
srl_r(gb, opcode, pc);
break;
default:
bit_r(gb, opcode, pc);
break;
}
}
static GB_opcode_t *opcodes[256] = {
/* X0 X1 X2 X3 X4 X5 X6 X7 */
/* X8 X9 Xa Xb Xc Xd Xe Xf */
nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */
ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca,
stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */
jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra,
jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */
jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl,
jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */
jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf,
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 4X */
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 5X */
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 6X */
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, halt, ld_r_r, /* 7X */
ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */
adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r,
sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */
sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r,
and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */
xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r,
or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */
cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r,
ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */
ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst,
ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */
ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst,
ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */
add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst,
ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */
ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst,
};
void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count)
{
const GB_bank_symbol_t *function_symbol = GB_debugger_find_symbol(gb, pc);
if (function_symbol && pc - function_symbol->addr > 0x1000) {
function_symbol = NULL;
}
if (function_symbol && pc != function_symbol->addr) {
GB_log(gb, "%s:\n", function_symbol->name);
}
uint16_t current_function = function_symbol? function_symbol->addr : 0;
while (count--) {
function_symbol = GB_debugger_find_symbol(gb, pc);
if (function_symbol && function_symbol->addr == pc) {
if (current_function != function_symbol->addr) {
GB_log(gb, "\n");
}
GB_log(gb, "%s:\n", function_symbol->name);
}
if (function_symbol) {
GB_log(gb, "%s%04x <+%03x>: ", pc == gb->pc? " ->": " ", pc, pc - function_symbol->addr);
}
else {
GB_log(gb, "%s%04x: ", pc == gb->pc? " ->": " ", pc);
}
uint8_t opcode = GB_read_memory(gb, pc);
opcodes[opcode](gb, opcode, &pc);
}
}