From 87f7183a2712cfe343530d96c6d57bec45f342bb Mon Sep 17 00:00:00 2001 From: nattthebear Date: Sun, 16 Jul 2017 12:23:41 -0400 Subject: [PATCH 01/13] sameboy: initial commit --- waterbox/sameboy/Makefile | 55 + waterbox/sameboy/apu.c | 497 +++++++ waterbox/sameboy/apu.h | 78 ++ waterbox/sameboy/camera.c | 149 +++ waterbox/sameboy/camera.h | 29 + waterbox/sameboy/debugger.c | 1850 +++++++++++++++++++++++++++ waterbox/sameboy/debugger.h | 32 + waterbox/sameboy/display.c | 800 ++++++++++++ waterbox/sameboy/display.h | 40 + waterbox/sameboy/gb.c | 563 ++++++++ waterbox/sameboy/gb.h | 570 +++++++++ waterbox/sameboy/gb_struct_def.h | 5 + waterbox/sameboy/joypad.c | 63 + waterbox/sameboy/joypad.h | 22 + waterbox/sameboy/mbc.c | 154 +++ waterbox/sameboy/mbc.h | 31 + waterbox/sameboy/memory.c | 726 +++++++++++ waterbox/sameboy/memory.h | 12 + waterbox/sameboy/printer.c | 201 +++ waterbox/sameboy/printer.h | 59 + waterbox/sameboy/save_state.c | 304 +++++ waterbox/sameboy/save_state.h | 24 + waterbox/sameboy/symbol_hash.c | 106 ++ waterbox/sameboy/symbol_hash.h | 37 + waterbox/sameboy/timing.c | 228 ++++ waterbox/sameboy/timing.h | 21 + waterbox/sameboy/z80_cpu.c | 1381 ++++++++++++++++++++ waterbox/sameboy/z80_cpu.h | 10 + waterbox/sameboy/z80_disassembler.c | 788 ++++++++++++ 29 files changed, 8835 insertions(+) create mode 100644 waterbox/sameboy/Makefile create mode 100644 waterbox/sameboy/apu.c create mode 100644 waterbox/sameboy/apu.h create mode 100644 waterbox/sameboy/camera.c create mode 100644 waterbox/sameboy/camera.h create mode 100644 waterbox/sameboy/debugger.c create mode 100644 waterbox/sameboy/debugger.h create mode 100644 waterbox/sameboy/display.c create mode 100644 waterbox/sameboy/display.h create mode 100644 waterbox/sameboy/gb.c create mode 100644 waterbox/sameboy/gb.h create mode 100644 waterbox/sameboy/gb_struct_def.h create mode 100644 waterbox/sameboy/joypad.c create mode 100644 waterbox/sameboy/joypad.h create mode 100644 waterbox/sameboy/mbc.c create mode 100644 waterbox/sameboy/mbc.h create mode 100644 waterbox/sameboy/memory.c create mode 100644 waterbox/sameboy/memory.h create mode 100644 waterbox/sameboy/printer.c create mode 100644 waterbox/sameboy/printer.h create mode 100644 waterbox/sameboy/save_state.c create mode 100644 waterbox/sameboy/save_state.h create mode 100644 waterbox/sameboy/symbol_hash.c create mode 100644 waterbox/sameboy/symbol_hash.h create mode 100644 waterbox/sameboy/timing.c create mode 100644 waterbox/sameboy/timing.h create mode 100644 waterbox/sameboy/z80_cpu.c create mode 100644 waterbox/sameboy/z80_cpu.h create mode 100644 waterbox/sameboy/z80_disassembler.c diff --git a/waterbox/sameboy/Makefile b/waterbox/sameboy/Makefile new file mode 100644 index 0000000000..0fdbe63b73 --- /dev/null +++ b/waterbox/sameboy/Makefile @@ -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)) diff --git a/waterbox/sameboy/apu.c b/waterbox/sameboy/apu.c new file mode 100644 index 0000000000..e0e0b89f28 --- /dev/null +++ b/waterbox/sameboy/apu.c @@ -0,0 +1,497 @@ +#include +#include +#include +#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; +} diff --git a/waterbox/sameboy/apu.h b/waterbox/sameboy/apu.h new file mode 100644 index 0000000000..ffc530e693 --- /dev/null +++ b/waterbox/sameboy/apu.h @@ -0,0 +1,78 @@ +#ifndef apu_h +#define apu_h +#include +#include +#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 */ diff --git a/waterbox/sameboy/camera.c b/waterbox/sameboy/camera.c new file mode 100644 index 0000000000..9b34998af3 --- /dev/null +++ b/waterbox/sameboy/camera.c @@ -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; +} diff --git a/waterbox/sameboy/camera.h b/waterbox/sameboy/camera.h new file mode 100644 index 0000000000..21c69b68e6 --- /dev/null +++ b/waterbox/sameboy/camera.h @@ -0,0 +1,29 @@ +#ifndef camera_h +#define camera_h +#include +#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 diff --git a/waterbox/sameboy/debugger.c b/waterbox/sameboy/debugger.c new file mode 100644 index 0000000000..7fc7b2a9a6 --- /dev/null +++ b/waterbox/sameboy/debugger.c @@ -0,0 +1,1850 @@ +#include +#include +#include +#include "gb.h" + +typedef struct { + bool has_bank; + uint16_t bank:9; + uint16_t value; +} value_t; + +typedef struct { + enum { + LVALUE_MEMORY, + LVALUE_REG16, + LVALUE_REG_H, + LVALUE_REG_L, + } kind; + union { + uint16_t *register_address; + value_t memory_address; + }; +} lvalue_t; + +#define VALUE_16(x) ((value_t){false, 0, (x)}) + +struct GB_breakpoint_s { + union { + struct { + uint16_t addr; + uint16_t bank; /* -1 = any bank*/ + }; + uint32_t key; /* For sorting and comparing */ + }; + char *condition; +}; + +#define BP_KEY(x) (((struct GB_breakpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) + +#define GB_WATCHPOINT_R (1) +#define GB_WATCHPOINT_W (2) + +struct GB_watchpoint_s { + union { + struct { + uint16_t addr; + uint16_t bank; /* -1 = any bank*/ + }; + uint32_t key; /* For sorting and comparing */ + }; + char *condition; + uint8_t flags; +}; + +#define WP_KEY(x) (((struct GB_watchpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) + +static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x4000) { + return gb->mbc_rom0_bank; + } + + if (addr < 0x8000) { + return gb->mbc_rom_bank; + } + + if (addr < 0xD000) { + return 0; + } + + if (addr < 0xE000) { + return gb->cgb_ram_bank; + } + + return 0; +} + +typedef struct { + uint16_t rom0_bank; + uint16_t rom_bank; + uint8_t mbc_ram_bank; + bool mbc_ram_enable; + uint8_t ram_bank; + uint8_t vram_bank; +} banking_state_t; + +static inline void save_banking_state(GB_gameboy_t *gb, banking_state_t *state) +{ + state->rom0_bank = gb->mbc_rom0_bank; + state->rom_bank = gb->mbc_rom_bank; + state->mbc_ram_bank = gb->mbc_ram_bank; + state->mbc_ram_enable = gb->mbc_ram_enable; + state->ram_bank = gb->cgb_ram_bank; + state->vram_bank = gb->cgb_vram_bank; +} + +static inline void restore_banking_state(GB_gameboy_t *gb, banking_state_t *state) +{ + + gb->mbc_rom0_bank = state->rom0_bank; + gb->mbc_rom_bank = state->rom_bank; + gb->mbc_ram_bank = state->mbc_ram_bank; + gb->mbc_ram_enable = state->mbc_ram_enable; + gb->cgb_ram_bank = state->ram_bank; + gb->cgb_vram_bank = state->vram_bank; +} + +static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank) +{ + gb->mbc_rom0_bank = bank; + gb->mbc_rom_bank = bank; + gb->mbc_ram_bank = bank; + gb->mbc_ram_enable = true; + if (gb->is_cgb) { + gb->cgb_ram_bank = bank & 7; + gb->cgb_vram_bank = bank & 1; + } +} + +static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name) +{ + static __thread char output[256]; + const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value); + + if (symbol && (value - symbol->addr > 0x1000 || symbol->addr == 0) ) { + symbol = NULL; + } + + /* Avoid overflow */ + if (symbol && strlen(symbol->name) > 240) { + symbol = NULL; + } + + if (!symbol) { + sprintf(output, "$%04x", value); + } + + else if (symbol->addr == value) { + if (prefer_name) { + sprintf(output, "%s ($%04x)", symbol->name, value); + } + else { + sprintf(output, "$%04x (%s)", value, symbol->name); + } + } + + else { + if (prefer_name) { + sprintf(output, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); + } + else { + sprintf(output, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); + } + } + return output; +} + +static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name) +{ + if (!value.has_bank) return value_to_string(gb, value.value, prefer_name); + + static __thread char output[256]; + const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value); + + if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) { + symbol = NULL; + } + + /* Avoid overflow */ + if (symbol && strlen(symbol->name) > 240) { + symbol = NULL; + } + + if (!symbol) { + sprintf(output, "$%02x:$%04x", value.bank, value.value); + } + + else if (symbol->addr == value.value) { + if (prefer_name) { + sprintf(output, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); + } + else { + sprintf(output, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); + } + } + + else { + if (prefer_name) { + sprintf(output, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); + } + else { + sprintf(output, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); + } + } + return output; +} + +static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) +{ + /* Not used until we add support for operators like += */ + switch (lvalue.kind) { + case LVALUE_MEMORY: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + restore_banking_state(gb, &state); + return r; + } + return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); + + case LVALUE_REG16: + return VALUE_16(*lvalue.register_address); + + case LVALUE_REG_L: + return VALUE_16(*lvalue.register_address & 0x00FF); + + case LVALUE_REG_H: + return VALUE_16(*lvalue.register_address >> 8); + } +} + +static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) +{ + switch (lvalue.kind) { + case LVALUE_MEMORY: + if (lvalue.memory_address.has_bank) { + banking_state_t state; + save_banking_state(gb, &state); + switch_banking_state(gb, lvalue.memory_address.bank); + GB_write_memory(gb, lvalue.memory_address.value, value); + restore_banking_state(gb, &state); + return; + } + GB_write_memory(gb, lvalue.memory_address.value, value); + return; + + case LVALUE_REG16: + *lvalue.register_address = value; + return; + + case LVALUE_REG_L: + *lvalue.register_address &= 0xFF00; + *lvalue.register_address |= value & 0xFF; + return; + + case LVALUE_REG_H: + *lvalue.register_address &= 0x00FF; + *lvalue.register_address |= value << 8; + return; + } +} + +/* 16 bit value 16 bit value = 16 bit value + 25 bit address 16 bit value = 25 bit address + 16 bit value 25 bit address = 25 bit address + 25 bit address 25 bit address = 16 bit value (since adding pointers, for examples, makes no sense) + + Boolean operators always return a 16-bit value + */ +#define FIX_BANK(x) ((value_t){a.has_bank ^ b.has_bank, a.has_bank? a.bank : b.bank, (x)}) + +static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);} +static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);} +static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);} +static value_t _div(value_t a, value_t b) { + if (b.value == 0) { + return FIX_BANK(0); + } + return FIX_BANK(a.value / b.value); +}; +static value_t mod(value_t a, value_t b) { + if (b.value == 0) { + return FIX_BANK(0); + } + return FIX_BANK(a.value % b.value); +}; +static value_t and(value_t a, value_t b) {return FIX_BANK(a.value & b.value);} +static value_t or(value_t a, value_t b) {return FIX_BANK(a.value | b.value);} +static value_t xor(value_t a, value_t b) {return FIX_BANK(a.value ^ b.value);} +static value_t shleft(value_t a, value_t b) {return FIX_BANK(a.value << b.value);} +static value_t shright(value_t a, value_t b) {return FIX_BANK(a.value >> b.value);} +static value_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b) +{ + write_lvalue(gb, a, b); + return read_lvalue(gb, a); +} + +static value_t bool_and(value_t a, value_t b) {return VALUE_16(a.value && b.value);} +static value_t bool_or(value_t a, value_t b) {return VALUE_16(a.value || b.value);} +static value_t equals(value_t a, value_t b) {return VALUE_16(a.value == b.value);} +static value_t different(value_t a, value_t b) {return VALUE_16(a.value != b.value);} +static value_t lower(value_t a, value_t b) {return VALUE_16(a.value < b.value);} +static value_t greater(value_t a, value_t b) {return VALUE_16(a.value > b.value);} +static value_t lower_equals(value_t a, value_t b) {return VALUE_16(a.value <= b.value);} +static value_t greater_equals(value_t a, value_t b) {return VALUE_16(a.value >= b.value);} +static value_t bank(value_t a, value_t b) {return (value_t) {true, a.value, b.value};} + + +static struct { + const char *string; + char priority; + value_t (*operator)(value_t, value_t); + value_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t); +} operators[] = +{ + // Yes. This is not C-like. But it makes much more sense. + // Deal with it. + {"+", 0, add}, + {"-", 0, sub}, + {"||", 0, bool_or}, + {"|", 0, or}, + {"*", 1, mul}, + {"/", 1, _div}, + {"%", 1, mod}, + {"&&", 1, bool_and}, + {"&", 1, and}, + {"^", 1, xor}, + {"<<", 2, shleft}, + {"<=", 3, lower_equals}, + {"<", 3, lower}, + {">>", 2, shright}, + {">=", 3, greater_equals}, + {">", 3, greater}, + {"==", 3, equals}, + {"=", -1, NULL, assign}, + {"!=", 3, different}, + {":", 4, bank}, +}; + +value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, + size_t length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value); + +static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, + size_t length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) +{ + *error = false; + // Strip whitespace + while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { + string++; + length--; + } + while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { + length--; + } + if (length == 0) + { + GB_log(gb, "Expected expression.\n"); + *error = true; + return (lvalue_t){0,}; + } + if (string[0] == '(' && string[length - 1] == ')') { + // Attempt to strip parentheses + signed int depth = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '(') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ')') depth--; + } + if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + } + else if (string[0] == '[' && string[length - 1] == ']') { + // Attempt to strip square parentheses (memory dereference) + signed int depth = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '[') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ']') depth--; + } + if (depth == 0) { + return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; + } + } + + // Registers + if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { + if (length == 1) { + switch (string[0]) { + case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]}; + case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]}; + } + } + else if (length == 2) { + switch (string[0]) { + case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]}; + case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]}; + case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]}; + case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]}; + case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]}; + case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; + } + } + GB_log(gb, "Unknown register: %.*s\n", (unsigned int) length, string); + *error = true; + return (lvalue_t){0,}; + } + + GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned int) length, string); + *error = true; + return (lvalue_t){0,}; +} + +#define ERROR ((value_t){0,}) +value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, + size_t length, bool *error, + uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) +{ + /* Disable watchpoints while evaulating expressions */ + uint16_t n_watchpoints = gb->n_watchpoints; + gb->n_watchpoints = 0; + + value_t ret = ERROR; + + *error = false; + // Strip whitespace + while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { + string++; + length--; + } + while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { + length--; + } + if (length == 0) + { + GB_log(gb, "Expected expression.\n"); + *error = true; + goto exit; + } + if (string[0] == '(' && string[length - 1] == ')') { + // Attempt to strip parentheses + signed int depth = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '(') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ')') depth--; + } + if (depth == 0) { + ret = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + goto exit; + } + } + else if (string[0] == '[' && string[length - 1] == ']') { + // Attempt to strip square parentheses (memory dereference) + signed int depth = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '[') depth++; + if (depth == 0) { + // First and last are not matching + depth = 1; + break; + } + if (string[i] == ']') depth--; + } + + if (depth == 0) { + value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); + banking_state_t state; + if (addr.bank) { + save_banking_state(gb, &state); + switch_banking_state(gb, addr.bank); + } + ret = VALUE_16(GB_read_memory(gb, addr.value)); + if (addr.bank) { + restore_banking_state(gb, &state); + } + goto exit; + } + + } + // Search for lowest priority operator + signed int depth = 0; + unsigned int operator_index = -1; + unsigned int operator_pos = 0; + for (int i = 0; i < length; i++) { + if (string[i] == '(') depth++; + else if (string[i] == ')') depth--; + else if (string[i] == '[') depth++; + else if (string[i] == ']') depth--; + else if (depth == 0) { + for (int j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) { + if (strlen(operators[j].string) > length - i) continue; // Operator too big. + // Priority higher than what we already have. + if (operator_index != -1 && operators[operator_index].priority < operators[j].priority) continue; + unsigned long operator_length = strlen(operators[j].string); + if (memcmp(string + i, operators[j].string, operator_length) == 0) { + // Found an operator! + operator_pos = i; + operator_index = j; + /* for supporting = vs ==, etc*/ + i += operator_length - 1; + break; + } + } + } + } + if (operator_index != -1) { + unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string)); + value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); + if (*error) goto exit; + if (operators[operator_index].lvalue_operator) { + lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); + if (*error) goto exit; + ret = operators[operator_index].lvalue_operator(gb, left, right.value); + goto exit; + } + value_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); + if (*error) goto exit; + ret = operators[operator_index].operator(left, right); + goto exit; + } + + // Not an expression - must be a register or a literal + + // Registers + if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { + if (length == 1) { + switch (string[0]) { + case 'a': ret = VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); goto exit; + case 'f': ret = VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); goto exit; + case 'b': ret = VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); goto exit; + case 'c': ret = VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); goto exit; + case 'd': ret = VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); goto exit; + case 'e': ret = VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); goto exit; + case 'h': ret = VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); goto exit; + case 'l': ret = VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); goto exit; + } + } + else if (length == 2) { + switch (string[0]) { + case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->registers[GB_REGISTER_AF]); goto exit;} + case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->registers[GB_REGISTER_BC]); goto exit;} + case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->registers[GB_REGISTER_DE]); goto exit;} + case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->registers[GB_REGISTER_HL]); goto exit;} + case 's': if (string[1] == 'p') {ret = VALUE_16(gb->registers[GB_REGISTER_SP]); goto exit;} + case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;} + } + } + else if (length == 3) { + if (watchpoint_address && memcmp(string, "old", 3) == 0) { + ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); + goto exit; + } + + if (watchpoint_new_value && memcmp(string, "new", 3) == 0) { + ret = VALUE_16(*watchpoint_new_value); + goto exit; + } + + /* $new is identical to $old in read conditions */ + if (watchpoint_address && memcmp(string, "new", 3) == 0) { + ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); + goto exit; + } + } + + char symbol_name[length + 1]; + memcpy(symbol_name, string, length); + symbol_name[length] = 0; + const GB_symbol_t *symbol = GB_reversed_map_find_symbol(&gb->reversed_symbol_map, symbol_name); + if (symbol) { + ret = (value_t){true, symbol->bank, symbol->addr}; + goto exit; + } + + GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned int) length, string); + *error = true; + goto exit; + } + + char *end; + int base = 10; + if (string[0] == '$') { + string++; + base = 16; + length--; + } + uint16_t literal = (uint16_t) (strtol(string, &end, base)); + if (end != string + length) { + GB_log(gb, "Failed to parse: %.*s\n", (unsigned int) length, string); + *error = true; + goto exit; + } + ret = VALUE_16(literal); +exit: + gb->n_watchpoints = n_watchpoints; + return ret; +} + +struct debugger_command_s; +typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command); + +typedef struct debugger_command_s { + const char *command; + uint8_t min_length; + debugger_command_imp_t *implementation; + const char *help_string; // Null if should not appear in help + const char *arguments_format; // For usage message + const char *modifiers_format; // For usage message +} debugger_command_t; + +static const char *lstrip(const char *str) +{ + while (*str == ' ' || *str == '\t') { + str++; + } + return str; +} + +#define STOPPED_ONLY \ +if (!gb->debug_stopped) { \ +GB_log(gb, "Program is running. \n"); \ +return false; \ +} + +#define NO_MODIFIERS \ +if (modifiers) { \ +print_usage(gb, command); \ +return true; \ +} + +static void print_usage(GB_gameboy_t *gb, const debugger_command_t *command) +{ + GB_log(gb, "Usage: %s", command->command); + + if (command->modifiers_format) { + GB_log(gb, "[/%s]", command->modifiers_format); + } + + if (command->arguments_format) { + GB_log(gb, " %s", command->arguments_format); + } + + GB_log(gb, "\n"); +} + +static bool cont(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + return false; +} + +static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + gb->debug_next_command = true; + gb->debug_call_depth = 0; + return false; +} + +static bool step(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + return false; +} + +static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + gb->debug_fin_command = true; + gb->debug_call_depth = 0; + return false; +} + +static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + gb->debug_stopped = false; + gb->stack_leak_detection = true; + gb->debug_call_depth = 0; + return false; +} + +static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + GB_log(gb, "AF = $%04x\n", gb->registers[GB_REGISTER_AF]); /* AF can't really be an address */ + GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); + GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); + GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); + GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); + GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); + return true; +} + +/* Find the index of the closest breakpoint equal or greater to addr */ +static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) +{ + if (!gb->breakpoints) { + return 0; + } + + uint32_t key = BP_KEY(addr); + + int min = 0; + int max = gb->n_breakpoints; + while (min < max) { + uint16_t pivot = (min + max) / 2; + if (gb->breakpoints[pivot].key == key) return pivot; + if (gb->breakpoints[pivot].key > key) { + max = pivot; + } + else { + min = pivot + 1; + } + } + return (uint16_t) min; +} + +static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments)) == 0) { + print_usage(gb, command); + return true; + } + + if (gb->n_breakpoints == (typeof(gb->n_breakpoints)) -1) { + GB_log(gb, "Too many breakpoints set\n"); + return true; + } + + char *condition = NULL; + if ((condition = strstr(arguments, " if "))) { + *condition = 0; + condition += strlen(" if "); + /* Verify condition is sane (Todo: This might have side effects!) */ + bool error; + debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, NULL, NULL); + if (error) return true; + + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint32_t key = BP_KEY(result); + + if (error) return true; + + uint16_t index = find_breakpoint(gb, result); + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { + GB_log(gb, "Breakpoint already set at %s\n", debugger_value_to_string(gb, result, true)); + if (!gb->breakpoints[index].condition && condition) { + GB_log(gb, "Added condition to breakpoint\n"); + gb->breakpoints[index].condition = strdup(condition); + } + else if (gb->breakpoints[index].condition && condition) { + GB_log(gb, "Replaced breakpoint condition\n"); + free(gb->breakpoints[index].condition); + gb->breakpoints[index].condition = strdup(condition); + } + else if (gb->breakpoints[index].condition && !condition) { + GB_log(gb, "Removed breakpoint condition\n"); + free(gb->breakpoints[index].condition); + gb->breakpoints[index].condition = NULL; + } + return true; + } + + gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0])); + memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); + gb->breakpoints[index].key = key; + + if (condition) { + gb->breakpoints[index].condition = strdup(condition); + } + else { + gb->breakpoints[index].condition = NULL; + } + gb->n_breakpoints++; + + GB_log(gb, "Breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments)) == 0) { + for (unsigned i = gb->n_breakpoints; i--;) { + if (gb->breakpoints[i].condition) { + free(gb->breakpoints[i].condition); + } + } + free(gb->breakpoints); + gb->breakpoints = NULL; + gb->n_breakpoints = 0; + return true; + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint32_t key = BP_KEY(result); + + if (error) return true; + + uint16_t index = find_breakpoint(gb, result); + if (index >= gb->n_breakpoints || gb->breakpoints[index].key != key) { + GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; + } + + if (gb->breakpoints[index].condition) { + free(gb->breakpoints[index].condition); + } + + memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); + gb->n_breakpoints--; + gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); + + GB_log(gb, "Breakpoint removed from %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +/* Find the index of the closest watchpoint equal or greater to addr */ +static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) +{ + if (!gb->watchpoints) { + return 0; + } + uint32_t key = WP_KEY(addr); + int min = 0; + int max = gb->n_watchpoints; + while (min < max) { + uint16_t pivot = (min + max) / 2; + if (gb->watchpoints[pivot].key == key) return pivot; + if (gb->watchpoints[pivot].key > key) { + max = pivot; + } + else { + min = pivot + 1; + } + } + return (uint16_t) min; +} + +static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { +print_usage: + print_usage(gb, command); + return true; + } + + if (gb->n_watchpoints == (typeof(gb->n_watchpoints)) -1) { + GB_log(gb, "Too many watchpoints set\n"); + return true; + } + + if (!modifiers) { + modifiers = "w"; + } + + uint8_t flags = 0; + while (*modifiers) { + switch (*modifiers) { + case 'r': + flags |= GB_WATCHPOINT_R; + break; + case 'w': + flags |= GB_WATCHPOINT_W; + break; + default: + goto print_usage; + } + modifiers++; + } + + if (!flags) { + goto print_usage; + } + + char *condition = NULL; + if ((condition = strstr(arguments, " if "))) { + *condition = 0; + condition += strlen(" if "); + /* Verify condition is sane (Todo: This might have side effects!) */ + bool error; + /* To make $new and $old legal */ + uint16_t dummy = 0; + uint8_t dummy2 = 0; + debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, &dummy, &dummy2); + if (error) return true; + + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint32_t key = WP_KEY(result); + + if (error) return true; + + uint16_t index = find_watchpoint(gb, result); + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + GB_log(gb, "Watchpoint already set at %s\n", debugger_value_to_string(gb, result, true)); + if (gb->watchpoints[index].flags != flags) { + GB_log(gb, "Modified watchpoint type\n"); + gb->watchpoints[index].flags = flags; + } + if (!gb->watchpoints[index].condition && condition) { + GB_log(gb, "Added condition to watchpoint\n"); + gb->watchpoints[index].condition = strdup(condition); + } + else if (gb->watchpoints[index].condition && condition) { + GB_log(gb, "Replaced watchpoint condition\n"); + free(gb->watchpoints[index].condition); + gb->watchpoints[index].condition = strdup(condition); + } + else if (gb->watchpoints[index].condition && !condition) { + GB_log(gb, "Removed watchpoint condition\n"); + free(gb->watchpoints[index].condition); + gb->watchpoints[index].condition = NULL; + } + return true; + } + + gb->watchpoints = realloc(gb->watchpoints, (gb->n_watchpoints + 1) * sizeof(gb->watchpoints[0])); + memmove(&gb->watchpoints[index + 1], &gb->watchpoints[index], (gb->n_watchpoints - index) * sizeof(gb->watchpoints[0])); + gb->watchpoints[index].key = key; + gb->watchpoints[index].flags = flags; + if (condition) { + gb->watchpoints[index].condition = strdup(condition); + } + else { + gb->watchpoints[index].condition = NULL; + } + gb->n_watchpoints++; + + GB_log(gb, "Watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments)) == 0) { + for (unsigned i = gb->n_watchpoints; i--;) { + if (gb->watchpoints[i].condition) { + free(gb->watchpoints[i].condition); + } + } + free(gb->watchpoints); + gb->watchpoints = NULL; + gb->n_watchpoints = 0; + return true; + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint32_t key = WP_KEY(result); + + if (error) return true; + + uint16_t index = find_watchpoint(gb, result); + if (index >= gb->n_watchpoints || gb->watchpoints[index].key != key) { + GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); + return true; + } + + if (gb->watchpoints[index].condition) { + free(gb->watchpoints[index].condition); + } + + memmove(&gb->watchpoints[index], &gb->watchpoints[index + 1], (gb->n_watchpoints - index - 1) * sizeof(gb->watchpoints[0])); + gb->n_watchpoints--; + gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0])); + + GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true)); + return true; +} + +static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (gb->n_breakpoints == 0) { + GB_log(gb, "No breakpoints set.\n"); + } + else { + GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); + for (uint16_t i = 0; i < gb->n_breakpoints; i++) { + value_t addr = (value_t){gb->breakpoints[i].bank != (uint16_t)-1, gb->breakpoints[i].bank, gb->breakpoints[i].addr}; + if (gb->breakpoints[i].condition) { + GB_log(gb, " %d. %s (Condition: %s)\n", i + 1, + debugger_value_to_string(gb, addr, addr.has_bank), + gb->breakpoints[i].condition); + } + else { + GB_log(gb, " %d. %s\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank)); + } + } + } + + if (gb->n_watchpoints == 0) { + GB_log(gb, "No watchpoints set.\n"); + } + else { + GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints); + for (uint16_t i = 0; i < gb->n_watchpoints; i++) { + value_t addr = (value_t){gb->watchpoints[i].bank != (uint16_t)-1, gb->watchpoints[i].bank, gb->watchpoints[i].addr}; + if (gb->watchpoints[i].condition) { + GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-', + gb->watchpoints[i].condition); + } + else { + GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb,addr, addr.has_bank), + (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', + (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); + } + } + } + + return true; +} + +static bool _should_break(GB_gameboy_t *gb, value_t addr) +{ + uint16_t index = find_breakpoint(gb, addr); + uint32_t key = BP_KEY(addr); + + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { + if (!gb->breakpoints[index].condition) { + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition, + (unsigned int)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return true; + } + return condition; + } + return false; +} + +static bool should_break(GB_gameboy_t *gb, uint16_t addr) +{ + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_should_break(gb, full_addr)) return true; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + return _should_break(gb, full_addr); +} + +static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + print_usage(gb, command); + return true; + } + + if (!modifiers || !modifiers[0]) { + modifiers = "a"; + } + else if (modifiers[1]) { + print_usage(gb, command); + return true; + } + + bool error; + value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + if (!error) { + switch (modifiers[0]) { + case 'a': + GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false)); + break; + case 'd': + GB_log(gb, "=%d\n", result.value); + break; + case 'x': + GB_log(gb, "=$%x\n", result.value); + break; + case 'o': + GB_log(gb, "=0%o\n", result.value); + break; + case 'b': + { + if (!result.value) { + GB_log(gb, "=%%0\n"); + break; + } + char binary[17]; + binary[16] = 0; + char *ptr = &binary[16]; + while (result.value) { + *(--ptr) = (result.value & 1)? '1' : '0'; + result.value >>= 1; + } + GB_log(gb, "=%%%s\n", ptr); + break; + } + default: + break; + } + } + return true; +} + +static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + print_usage(gb, command); + return true; + } + + bool error; + value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint16_t count = 32; + + if (modifiers) { + char *end; + count = (uint16_t) (strtol(modifiers, &end, 10)); + if (*end) { + print_usage(gb, command); + return true; + } + } + + if (!error) { + if (addr.has_bank) { + banking_state_t old_state; + save_banking_state(gb, &old_state); + switch_banking_state(gb, addr.bank); + + while (count) { + GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); + for (int i = 0; i < 16 && count; i++) { + GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + count--; + } + addr.value += 16; + GB_log(gb, "\n"); + } + + restore_banking_state(gb, &old_state); + } + else { + while (count) { + GB_log(gb, "%04x: ", addr.value); + for (int i = 0; i < 16 && count; i++) { + GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); + count--; + } + addr.value += 16; + GB_log(gb, "\n"); + } + } + } + return true; +} + +static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + if (strlen(lstrip(arguments)) == 0) { + arguments = "pc"; + } + + bool error; + value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); + uint16_t count = 5; + + if (modifiers) { + char *end; + count = (uint16_t) (strtol(modifiers, &end, 10)); + if (*end) { + print_usage(gb, command); + return true; + } + } + + if (!error) { + if (addr.has_bank) { + banking_state_t old_state; + save_banking_state(gb, &old_state); + switch_banking_state(gb, addr.bank); + + GB_cpu_disassemble(gb, addr.value, count); + + restore_banking_state(gb, &old_state); + } + else { + GB_cpu_disassemble(gb, addr.value, count); + } + } + return true; +} + +static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + const GB_cartridge_t *cartridge = gb->cartridge_type; + + if (cartridge->has_ram) { + GB_log(gb, "Cartrdige includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); + } + else { + GB_log(gb, "No cartridge RAM\n"); + } + + if (cartridge->mbc_type) { + static const char * const mapper_names[] = { + [GB_MBC1] = "MBC1", + [GB_MBC2] = "MBC2", + [GB_MBC3] = "MBC3", + [GB_MBC5] = "MBC5", + [GB_HUC1] = "HUC1", + [GB_HUC3] = "HUC3", + }; + GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); + GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); + if (cartridge->has_ram) { + GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); + GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); + } + if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) { + GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM"); + } + if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_MBC1M_WIRING) { + GB_log(gb, "MBC1 uses MBC1M wiring. \n"); + GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank); + GB_log(gb, "MBC1 multicart banking mode is %s\n", gb->mbc1.mode == 1 ? "enabled" : "disabled"); + } + + } + else { + GB_log(gb, "No MBC\n"); + } + + if (cartridge->has_rumble) { + GB_log(gb, "Cart contains a rumble pak\n"); + } + + if (cartridge->has_rtc) { + GB_log(gb, "Cart contains a real time clock\n"); + } + + return true; +} + +static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true)); + for (unsigned int i = gb->backtrace_size; i--;) { + GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true)); + } + + return true; +} + +static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + STOPPED_ONLY + + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + GB_log(gb, "Ticks: %lu. (Resetting)\n", gb->debugger_ticks); + gb->debugger_ticks = 0; + + return true; +} + + +static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + + if (!gb->is_cgb) { + GB_log(gb, "Not available on a DMG.\n"); + return true; + } + + GB_log(gb, "Background palettes: \n"); + for (unsigned i = 0; i < 32; i++) { + GB_log(gb, "%04x ", ((uint16_t *)&gb->background_palettes_data)[i]); + if (i % 4 == 3) { + GB_log(gb, "\n"); + } + } + + GB_log(gb, "Sprites palettes: \n"); + for (unsigned i = 0; i < 32; i++) { + GB_log(gb, "%04x ", ((uint16_t *)&gb->sprite_palettes_data)[i]); + if (i % 4 == 3) { + GB_log(gb, "\n"); + } + } + + return true; +} + +static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + if (strlen(lstrip(arguments))) { + print_usage(gb, command); + return true; + } + GB_log(gb, "LCDC:\n"); + GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled"); + GB_log(gb, " %s: %s\n", gb->is_cgb? (gb->cgb_mode? "Sprite priority flags" : "Background and Window") : "Background", + (gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled"); + GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled"); + GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8"); + GB_log(gb, " Background tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 8)? "$9C00" : "$9800"); + GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & 16)? "$8000" : "$8800"); + GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & 32)? "Enabled" : "Disabled"); + GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 64)? "$9C00" : "$9800"); + + GB_log(gb, "\nSTAT:\n"); + static const char *modes[] = {"Mode 0, H-Blank", "Mode 1, V-Blank", "Mode 2, OAM", "Mode 3, Rendering"}; + GB_log(gb, " Current mode: %s\n", modes[gb->io_registers[GB_IO_STAT] & 3]); + GB_log(gb, " LYC flag: %s\n", (gb->io_registers[GB_IO_STAT] & 4)? "On" : "Off"); + GB_log(gb, " H-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 8)? "Enabled" : "Disabled"); + GB_log(gb, " V-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 16)? "Enabled" : "Disabled"); + GB_log(gb, " OAM interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 32)? "Enabled" : "Disabled"); + GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled"); + + + GB_log(gb, "\nCycles since frame start: %d\n", gb->display_cycles); + GB_log(gb, "Current line: %d\n", gb->display_cycles / 456); + GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]); + GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); + GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]); + GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off"); + + return true; +} + +static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command); + +#define HELP_NEWLINE "\n " + +/* Commands without implementations are aliases of the previous non-alias commands */ +static const debugger_command_t commands[] = { + {"continue", 1, cont, "Continue running until next stop"}, + {"next", 1, next, "Run the next instruction, skipping over function calls"}, + {"step", 1, step, "Run the next instruction, stepping into function calls"}, + {"finish", 1, finish, "Run until the current function returns"}, + {"backtrace", 2, backtrace, "Display the current call stack"}, + {"bt", 2, }, /* Alias */ + {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"}, + {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was used"}, + {"registers", 1, registers, "Print values of processor registers and other important registers"}, + {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, + {"mbc", 3, }, /* Alias */ + {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, + {"palettes", 3, palettes, "Displays the current CGB palettes"}, + {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE + "Can also modify the condition of existing breakpoints.", + "[ if ]"}, + {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, + {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE + "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE + "Default watchpoint type is write-only.", + "[ if ]", "(r|w|rw)"}, + {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]"}, + {"list", 1, list, "List all set breakpoints and watchpoints"}, + {"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE + "Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE + "decimal (d), hexadecimal (x), octal (o) or binary (b).", + "", "format"}, + {"eval", 2, }, /* Alias */ + {"examine", 2, examine, "Examine values at address", "", "count"}, + {"x", 1, }, /* Alias */ + {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count"}, + + + {"help", 1, help, "List available commands or show help for the specified command", "[]"}, + {NULL,}, /* Null terminator */ +}; + +static const debugger_command_t *find_command(const char *string) +{ + size_t length = strlen(string); + for (const debugger_command_t *command = commands; command->command; command++) { + if (command->min_length > length) continue; + if (memcmp(command->command, string, length) == 0) { /* Is a substring? */ + /* Aliases */ + while (!command->implementation) { + command--; + } + return command; + } + } + + return NULL; +} + +static void print_command_shortcut(GB_gameboy_t *gb, const debugger_command_t *command) +{ + GB_attributed_log(gb, GB_LOG_BOLD | GB_LOG_UNDERLINE, "%.*s", command->min_length, command->command); + GB_attributed_log(gb, GB_LOG_BOLD , "%s", command->command + command->min_length); +} + +static void print_command_description(GB_gameboy_t *gb, const debugger_command_t *command) +{ + print_command_shortcut(gb, command); + GB_log(gb, ": "); + GB_log(gb, (const char *)&" %s\n" + strlen(command->command), command->help_string); +} + +static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *ignored) +{ + const debugger_command_t *command = find_command(arguments); + if (command) { + print_command_description(gb, command); + GB_log(gb, "\n"); + print_usage(gb, command); + + command++; + if (command->command && !command->implementation) { /* Command has aliases*/ + GB_log(gb, "\nAliases: "); + do { + print_command_shortcut(gb, command); + GB_log(gb, " "); + command++; + } while (command->command && !command->implementation); + GB_log(gb, "\n"); + } + return true; + } + for (command = commands; command->command; command++) { + if (command->help_string) { + print_command_description(gb, command); + } + } + return true; +} + +void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr) +{ + /* Called just after the CPU calls a function/enters an interrupt/etc... */ + + if (gb->stack_leak_detection) { + if (gb->debug_call_depth >= sizeof(gb->sp_for_call_depth) / sizeof(gb->sp_for_call_depth[0])) { + GB_log(gb, "Potential stack overflow detected (Functions nest too much). \n"); + gb->debug_stopped = true; + } + else { + gb->sp_for_call_depth[gb->debug_call_depth] = gb->registers[GB_REGISTER_SP]; + gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc; + } + } + + if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) { + + while (gb->backtrace_size) { + if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->registers[GB_REGISTER_SP]) { + gb->backtrace_size--; + } + else { + break; + } + } + + gb->backtrace_sps[gb->backtrace_size] = gb->registers[GB_REGISTER_SP]; + gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr); + gb->backtrace_returns[gb->backtrace_size].addr = call_addr; + gb->backtrace_size++; + } + + gb->debug_call_depth++; +} + +void GB_debugger_ret_hook(GB_gameboy_t *gb) +{ + /* Called just before the CPU runs ret/reti */ + + gb->debug_call_depth--; + + if (gb->stack_leak_detection) { + if (gb->debug_call_depth < 0) { + GB_log(gb, "Function finished without a stack leak.\n"); + gb->debug_stopped = true; + } + else { + if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { + GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true)); + GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->registers[GB_REGISTER_SP], + gb->sp_for_call_depth[gb->debug_call_depth]); + gb->debug_stopped = true; + } + } + } + + while (gb->backtrace_size) { + if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->registers[GB_REGISTER_SP]) { + gb->backtrace_size--; + } + else { + break; + } + } +} + +static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, uint8_t value) +{ + uint16_t index = find_watchpoint(gb, addr); + uint32_t key = WP_KEY(addr); + + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_W)) { + return false; + } + if (!gb->watchpoints[index].condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, + (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return false; + } + if (condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); + return true; + } + } + return false; +} + +void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (gb->debug_stopped) return; + + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_GB_debugger_test_write_watchpoint(gb, full_addr, value)) return; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + _GB_debugger_test_write_watchpoint(gb, full_addr, value); +} + +static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr) +{ + uint16_t index = find_watchpoint(gb, addr); + uint32_t key = WP_KEY(addr); + + if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { + if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) { + return false; + } + if (!gb->watchpoints[index].condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); + return true; + } + bool error; + bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, + (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value; + if (error) { + /* Should never happen */ + GB_log(gb, "An internal error has occured\n"); + return false; + } + if (condition) { + gb->debug_stopped = true; + GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); + return true; + } + } + return false; +} + +void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->debug_stopped) return; + + /* Try any-bank breakpoint */ + value_t full_addr = (VALUE_16(addr)); + if (_GB_debugger_test_read_watchpoint(gb, full_addr)) return; + + /* Try bank-specific breakpoint */ + full_addr.has_bank = true; + full_addr.bank = bank_for_addr(gb, addr); + _GB_debugger_test_read_watchpoint(gb, full_addr); +} + +/* Returns true if debugger waits for more commands */ +bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) +{ + if (!input[0]) { + return true; + } + + char *command_string = input; + char *arguments = strchr(input, ' '); + if (arguments) { + /* Actually "split" the string. */ + arguments[0] = 0; + arguments++; + } + else { + arguments = ""; + } + + char *modifiers = strchr(command_string, '/'); + if (modifiers) { + /* Actually "split" the string. */ + modifiers[0] = 0; + modifiers++; + } + + const debugger_command_t *command = find_command(command_string); + if (command) { + return command->implementation(gb, arguments, modifiers, command); + } + else { + GB_log(gb, "%s: no such command.\n", command_string); + return true; + } +} + +void GB_debugger_run(GB_gameboy_t *gb) +{ + if (gb->debug_disable) return; + + char *input = NULL; + if (gb->debug_next_command && gb->debug_call_depth <= 0) { + gb->debug_stopped = true; + } + if (gb->debug_fin_command && gb->debug_call_depth == -1) { + gb->debug_stopped = true; + } + if (gb->debug_stopped) { + GB_cpu_disassemble(gb, gb->pc, 5); + } +next_command: + if (input) { + free(input); + } + if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc)) { + gb->debug_stopped = true; + GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + } + if (gb->debug_stopped && !gb->debug_disable) { + gb->debug_next_command = false; + gb->debug_fin_command = false; + gb->stack_leak_detection = false; + input = gb->input_callback(gb); + + if (input == NULL) { + /* Debugging is no currently available, continue running */ + gb->debug_stopped = false; + return; + } + + if (GB_debugger_execute_command(gb, input)) { + goto next_command; + } + + free(input); + } +} + +void GB_debugger_handle_async_commands(GB_gameboy_t *gb) +{ + char *input = NULL; + + while (gb->async_input_callback && (input = gb->async_input_callback(gb))) { + GB_debugger_execute_command(gb, input); + free(input); + } +} + +void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "r"); + if (!f) return; + + char *line = NULL; + size_t size = 0; + size_t length = 0; + while ((length = getline(&line, &size, f)) != -1) { + for (unsigned i = 0; i < length; i++) { + if (line[i] == ';' || line[i] == '\n' || line[i] == '\r') { + line[i] = 0; + length = i; + break; + } + } + if (length == 0) continue; + + unsigned int bank, address; + char symbol[length]; + + if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) { + bank &= 0x1FF; + if (!gb->bank_symbols[bank]) { + gb->bank_symbols[bank] = GB_map_alloc(); + } + GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol); + if (allocated_symbol) { + GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol); + } + } + } + free(line); + fclose(f); +} + +const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) +{ + uint16_t bank = bank_for_addr(gb, addr); + + const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr); + if (symbol) return symbol; + if (bank != 0) return GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */ + + return NULL; +} + +const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr) +{ + const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, addr); + if (symbol && symbol->addr == addr) return symbol->name; + return NULL; +} + +/* The public version of debugger_evaluate */ +bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank) +{ + bool error = false; + value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL, NULL); + if (result) { + *result = value.value; + } + if (result_bank) { + *result_bank = value.has_bank? value.value : -1; + } + return error; +} + +void GB_debugger_break(GB_gameboy_t *gb) +{ + gb->debug_stopped = true; +} + +bool GB_debugger_is_stopped(GB_gameboy_t *gb) +{ + return gb->debug_stopped; +} + +void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled) +{ + gb->debug_disable = disabled; +} diff --git a/waterbox/sameboy/debugger.h b/waterbox/sameboy/debugger.h new file mode 100644 index 0000000000..5d6491972c --- /dev/null +++ b/waterbox/sameboy/debugger.h @@ -0,0 +1,32 @@ +#ifndef debugger_h +#define debugger_h +#include +#include +#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 */ diff --git a/waterbox/sameboy/display.c b/waterbox/sameboy/display.c new file mode 100644 index 0000000000..e624ac93b6 --- /dev/null +++ b/waterbox/sameboy/display.c @@ -0,0 +1,800 @@ +#include +#include +#include +#include +#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; +} diff --git a/waterbox/sameboy/display.h b/waterbox/sameboy/display.h new file mode 100644 index 0000000000..3967787237 --- /dev/null +++ b/waterbox/sameboy/display.h @@ -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 */ diff --git a/waterbox/sameboy/gb.c b/waterbox/sameboy/gb.c new file mode 100644 index 0000000000..56697a1e83 --- /dev/null +++ b/waterbox/sameboy/gb.c @@ -0,0 +1,563 @@ +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#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; + } +} diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h new file mode 100644 index 0000000000..82608821fd --- /dev/null +++ b/waterbox/sameboy/gb.h @@ -0,0 +1,570 @@ +#ifndef GB_h +#define GB_h +#include +#include +#include +#include + +#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 */ diff --git a/waterbox/sameboy/gb_struct_def.h b/waterbox/sameboy/gb_struct_def.h new file mode 100644 index 0000000000..0e0ebd12ee --- /dev/null +++ b/waterbox/sameboy/gb_struct_def.h @@ -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 diff --git a/waterbox/sameboy/joypad.c b/waterbox/sameboy/joypad.c new file mode 100644 index 0000000000..c5c4f08961 --- /dev/null +++ b/waterbox/sameboy/joypad.c @@ -0,0 +1,63 @@ +#include +#include "gb.h" +#include + +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; +} diff --git a/waterbox/sameboy/joypad.h b/waterbox/sameboy/joypad.h new file mode 100644 index 0000000000..def4b9ac82 --- /dev/null +++ b/waterbox/sameboy/joypad.h @@ -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 */ diff --git a/waterbox/sameboy/mbc.c b/waterbox/sameboy/mbc.c new file mode 100644 index 0000000000..d3791a180f --- /dev/null +++ b/waterbox/sameboy/mbc.c @@ -0,0 +1,154 @@ +#include +#include +#include +#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; + } +} diff --git a/waterbox/sameboy/mbc.h b/waterbox/sameboy/mbc.h new file mode 100644 index 0000000000..e7260f5643 --- /dev/null +++ b/waterbox/sameboy/mbc.h @@ -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 */ diff --git a/waterbox/sameboy/memory.c b/waterbox/sameboy/memory.c new file mode 100644 index 0000000000..c914e55965 --- /dev/null +++ b/waterbox/sameboy/memory.c @@ -0,0 +1,726 @@ +#include +#include +#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; + } + } +} diff --git a/waterbox/sameboy/memory.h b/waterbox/sameboy/memory.h new file mode 100644 index 0000000000..16b3b9220f --- /dev/null +++ b/waterbox/sameboy/memory.h @@ -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 */ diff --git a/waterbox/sameboy/printer.c b/waterbox/sameboy/printer.c new file mode 100644 index 0000000000..bc1d45ab47 --- /dev/null +++ b/waterbox/sameboy/printer.c @@ -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; +} \ No newline at end of file diff --git a/waterbox/sameboy/printer.h b/waterbox/sameboy/printer.h new file mode 100644 index 0000000000..e5d9036a08 --- /dev/null +++ b/waterbox/sameboy/printer.h @@ -0,0 +1,59 @@ +#ifndef printer_h +#define printer_h +#include +#include +#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 diff --git a/waterbox/sameboy/save_state.c b/waterbox/sameboy/save_state.c new file mode 100644 index 0000000000..cd579aa800 --- /dev/null +++ b/waterbox/sameboy/save_state.c @@ -0,0 +1,304 @@ +#include "gb.h" +#include +#include + +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 diff --git a/waterbox/sameboy/save_state.h b/waterbox/sameboy/save_state.h new file mode 100644 index 0000000000..546ac2d95a --- /dev/null +++ b/waterbox/sameboy/save_state.h @@ -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 + +#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 */ diff --git a/waterbox/sameboy/symbol_hash.c b/waterbox/sameboy/symbol_hash.c new file mode 100644 index 0000000000..709421c233 --- /dev/null +++ b/waterbox/sameboy/symbol_hash.c @@ -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; +} diff --git a/waterbox/sameboy/symbol_hash.h b/waterbox/sameboy/symbol_hash.h new file mode 100644 index 0000000000..239b0e313e --- /dev/null +++ b/waterbox/sameboy/symbol_hash.h @@ -0,0 +1,37 @@ +#ifndef symbol_hash_h +#define symbol_hash_h + +#include +#include + +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 */ diff --git a/waterbox/sameboy/timing.c b/waterbox/sameboy/timing.c new file mode 100644 index 0000000000..77348fe232 --- /dev/null +++ b/waterbox/sameboy/timing.c @@ -0,0 +1,228 @@ +#include "gb.h" +#ifdef _WIN32 +#define _WIN32_WINNT 0x0500 +#include +#else +#include +#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; + } + } + } + } + } + } +} diff --git a/waterbox/sameboy/timing.h b/waterbox/sameboy/timing.h new file mode 100644 index 0000000000..ed9e15a513 --- /dev/null +++ b/waterbox/sameboy/timing.h @@ -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 */ diff --git a/waterbox/sameboy/z80_cpu.c b/waterbox/sameboy/z80_cpu.c new file mode 100644 index 0000000000..0a1bde5dd7 --- /dev/null +++ b/waterbox/sameboy/z80_cpu.c @@ -0,0 +1,1381 @@ +#include +#include +#include "gb.h" + + +typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); + +static void ill(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_log(gb, "Illegal Opcode. Halting.\n"); + gb->interrupt_enable = 0; + gb->halted = true; +} + +static void nop(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); +} + +static void stop(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + if (gb->io_registers[GB_IO_KEY1] & 0x1) { + /* Make sure we don't leave display_cycles not divisble by 4 in single speed mode */ + if (gb->display_cycles % 4 == 2) { + GB_advance_cycles(gb, 4); + } + + /* Todo: the switch is not instant. We should emulate this. */ + gb->cgb_double_speed ^= true; + gb->io_registers[GB_IO_KEY1] = 0; + } + else { + gb->stopped = true; + } + gb->pc++; +} + +/* Operand naming conventions for functions: + r = 8-bit register + lr = low 8-bit register + hr = high 8-bit register + rr = 16-bit register + d8 = 8-bit imm + d16 = 16-bit imm + d.. = [..] + cc = condition code (z, nz, c, nc) + */ + +static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint16_t value; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + value |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); + gb->registers[register_id] = value; +} + +static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + GB_write_memory(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (opcode >> 4) + 1; + gb->registers[register_id]++; +} + +static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] += 0x100; + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} +static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] -= 0x100; + gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F00) == 0xF00) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] &= 0xFF; + gb->registers[register_id] |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); +} + +static void rlca(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; + + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x0100; + } +} + +static void rla(GB_gameboy_t *gb, uint8_t opcode) +{ + bool bit7 = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; + bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + if (carry) { + gb->registers[GB_REGISTER_AF] |= 0x0100; + } + if (bit7) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify order is correct */ + uint16_t addr; + GB_advance_cycles(gb, 4); + addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); + GB_write_memory(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); + GB_advance_cycles(gb, 4); +} + +static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t hl = gb->registers[GB_REGISTER_HL]; + uint16_t rr; + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (opcode >> 4) + 1; + rr = gb->registers[register_id]; + gb->registers[GB_REGISTER_HL] = hl + rr; + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG); + + /* The meaning of the Half Carry flag is really hard to track -_- */ + if (((hl & 0xFFF) + (rr & 0xFFF)) & 0x1000) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ( ((unsigned long) hl) + ((unsigned long) rr) & 0x10000) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = (opcode >> 4) + 1; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[register_id]) << 8; + GB_advance_cycles(gb, 4); +} + +static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (opcode >> 4) + 1; + gb->registers[register_id]--; +} + +static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint8_t value; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) + 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} +static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint8_t value; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) - 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F) == 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + gb->registers[register_id] &= 0xFF00; + gb->registers[register_id] |= GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); +} + +static void rrca(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry = (gb->registers[GB_REGISTER_AF] & 0x100) != 0; + + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x8000; + } +} + +static void rra(GB_gameboy_t *gb, uint8_t opcode) +{ + bool bit1 = (gb->registers[GB_REGISTER_AF] & 0x0100) != 0; + bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + if (carry) { + gb->registers[GB_REGISTER_AF] |= 0x8000; + } + if (bit1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify cycles are not 8 and 4 instead */ + GB_advance_cycles(gb, 4); + gb->pc += (int8_t) GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 8); +} + +static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 1: + return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 2: + return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + case 3: + return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + } + + return false; +} + +static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + GB_advance_cycles(gb, 4); + gb->pc += (int8_t)GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 8); + } + else { + GB_advance_cycles(gb, 8); + gb->pc += 1; + } +} + +static void daa(GB_gameboy_t *gb, uint8_t opcode) +{ + /* This function is UGLY and UNREADABLE! But it passes Blargg's daa test! */ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= ~GB_ZERO_FLAG; + if (gb->registers[GB_REGISTER_AF] & GB_SUBSTRACT_FLAG) { + if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { + gb->registers[GB_REGISTER_AF] &= ~GB_HALF_CARRY_FLAG; + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + gb->registers[GB_REGISTER_AF] += 0x9A00; + } + else { + gb->registers[GB_REGISTER_AF] += 0xFA00; + } + } + else if(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + gb->registers[GB_REGISTER_AF] += 0xA000; + } + } + else { + if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { + uint16_t number = gb->registers[GB_REGISTER_AF] >> 8; + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + number += 0x100; + } + gb->registers[GB_REGISTER_AF] = 0; + number += 0x06; + if (number >= 0xa0) { + number -= 0xa0; + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + gb->registers[GB_REGISTER_AF] |= number << 8; + } + else { + uint16_t number = gb->registers[GB_REGISTER_AF] >> 8; + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + number += 0x100; + } + if (number > 0x99) { + number += 0x60; + } + number = (number & 0x0F) + ((number & 0x0F) > 9 ? 6 : 0) + (number & 0xFF0); + gb->registers[GB_REGISTER_AF] = number << 8; + if (number & 0xFF00) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + } + } + if ((gb->registers[GB_REGISTER_AF] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cpl(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] ^= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG; +} + +static void scf(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); +} + +static void ccf(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] ^= GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); +} + +static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]++) << 8; + GB_advance_cycles(gb, 4); +} + +static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]--) << 8; + GB_advance_cycles(gb, 4); +} + +static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) + 1; + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); + + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + if ((value & 0x0F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) - 1; + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); + + gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + if ((value & 0x0F) == 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + uint8_t data = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], data); + GB_advance_cycles(gb, 4); +} + +uint8_t get_src_value(GB_gameboy_t *gb, 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) { + if (src_low) { + return gb->registers[GB_REGISTER_AF] >> 8; + } + uint8_t ret = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); + GB_advance_cycles(gb, 4); + return ret; + } + if (src_low) { + return gb->registers[src_register_id] & 0xFF; + } + return gb->registers[src_register_id] >> 8; +} + +static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) +{ + 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) { + if (src_low) { + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= value << 8; + } + else { + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); + } + } + else { + if (src_low) { + gb->registers[src_register_id] &= 0xFF00; + gb->registers[src_register_id] |= value; + } + else { + gb->registers[src_register_id] &= 0xFF; + gb->registers[src_register_id] |= value << 8; + } + } +} + +/* The LD r,r instruction is extremely common and extremely simple. Decoding this opcode at runtime is a significent + performance hit, so we generate functions for every ld x,y couple (including [hl]) at compile time using macros. */ + +/* Todo: It's probably wise to do the same to all opcodes. */ + +#define LD_X_Y(x, y) \ +static void ld_##x##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ + GB_advance_cycles(gb, 4); \ + gb->x = gb->y;\ +} + +#define LD_X_DHL(x) \ +static void ld_##x##_##dhl(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +GB_advance_cycles(gb, 4); \ +gb->x = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); \ +GB_advance_cycles(gb, 4);\ +} + +#define LD_DHL_Y(y) \ +static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +GB_advance_cycles(gb, 4); \ +GB_write_memory(gb, gb->registers[GB_REGISTER_HL], gb->y); \ +GB_advance_cycles(gb, 4);\ +} + +LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) +LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a) +LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a) +LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a) +LD_X_Y(h,b) LD_X_Y(h,c) LD_X_Y(h,d) LD_X_Y(h,e) LD_X_Y(h,l) LD_X_DHL(h) LD_X_Y(h,a) +LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL(l) LD_X_Y(l,a) +LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a) +LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a) + + +static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a + value) << 8; + if ((uint8_t)(a + value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8; + + if ((uint8_t)(a + value + carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG; + + if ((uint8_t) (a - value - carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a | value) << 8; + if ((a | value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void halt(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->halted = true; + /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ + if (!gb->ime && (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0) { + gb->halted = false; + gb->halt_bug = true; + } +} + +static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify timing */ + if (condition_code(gb, opcode)) { + GB_debugger_ret_hook(gb); + GB_advance_cycles(gb, 8); + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_advance_cycles(gb, 4); + gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_SP] += 2; + } + else { + GB_advance_cycles(gb, 8); + } +} + +static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = ((opcode >> 4) + 1) & 3; + gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_advance_cycles(gb, 4); + gb->registers[register_id] |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test. + gb->registers[GB_REGISTER_SP] += 2; +} + +static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + GB_advance_cycles(gb, 4); + uint16_t addr = GB_read_memory(gb, gb->pc); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 8); + gb->pc = addr; + + } + else { + GB_advance_cycles(gb, 12); + gb->pc += 2; + } +} + +static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + uint16_t addr = GB_read_memory(gb, gb->pc); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 8); + gb->pc = addr;} + +static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + if (condition_code(gb, opcode)) { + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_SP] -= 2; + uint16_t addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc++) << 8); + GB_advance_cycles(gb, 8); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_advance_cycles(gb, 4); + gb->pc = addr; + + GB_debugger_call_hook(gb, call_addr); + } + else { + GB_advance_cycles(gb, 12); + gb->pc += 2; + } +} + +static void push_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = ((opcode >> 4) + 1) & 3; + gb->registers[GB_REGISTER_SP] -= 2; + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->registers[register_id]) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); + GB_advance_cycles(gb, 4); +} + +static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a + value) << 8; + if ((uint8_t) (a + value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8; + + if (gb->registers[GB_REGISTER_AF] == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG; + + if ((uint8_t) (a - value - carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a | value) << 8; + if ((a | value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void rst(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_SP] -= 2; + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_advance_cycles(gb, 4); + gb->pc = opcode ^ 0xC7; + GB_debugger_call_hook(gb, call_addr); +} + +static void ret(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_debugger_ret_hook(gb); + GB_advance_cycles(gb, 4); + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_advance_cycles(gb, 4); + gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_SP] += 2; +} + +static void reti(GB_gameboy_t *gb, uint8_t opcode) +{ + ret(gb, opcode); + gb->ime = true; +} + +static void call_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_SP] -= 2; + uint16_t addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc++) << 8); + GB_advance_cycles(gb, 8); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_advance_cycles(gb, 4); + gb->pc = addr; + GB_debugger_call_hook(gb, call_addr); +} + +static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + uint8_t temp = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + uint8_t temp = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + temp) << 8; + GB_advance_cycles(gb, 4); +} + +static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + GB_write_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; + GB_advance_cycles(gb, 4); +} + +static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t offset; + uint16_t sp = gb->registers[GB_REGISTER_SP]; + GB_advance_cycles(gb, 4); + offset = (int8_t) GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 12); + gb->registers[GB_REGISTER_SP] += offset; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + + /* A new instruction, a new meaning for Half Carry! */ + if ((sp & 0xF) + (offset & 0xF) > 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((sp & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->pc = gb->registers[GB_REGISTER_HL]; +} + +static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr; + GB_advance_cycles(gb, 4); + addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); + GB_write_memory(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= GB_read_memory(gb, gb->pc++) << 8 ; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, addr) << 8; + GB_advance_cycles(gb, 4); +} + +static void di(GB_gameboy_t *gb, uint8_t opcode) +{ + /* DI is NOT delayed, not even on a CGB. Mooneye's di_timing-GS test fails on a CGB + for different reasons.*/ + GB_advance_cycles(gb, 4); + gb->ime = false; +} + +static void ei(GB_gameboy_t *gb, uint8_t opcode) +{ + /* ei is actually "disable interrupts for one instruction, then enable them". */ + GB_advance_cycles(gb, 4); + gb->ime = false; + gb->ime_toggle = true; +} + +static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t offset; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + offset = (int8_t) GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset; + + if ((gb->registers[GB_REGISTER_SP] & 0xF) + (offset & 0xF) > 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[GB_REGISTER_SP] & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_SP] = gb->registers[GB_REGISTER_HL]; +} + +static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value << 1) | carry); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (!(value << 1)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (value & 0x01) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rl_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + bool bit7; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bit7 = (value & 0x80) != 0; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value << 1) | carry; + set_src_value(gb, opcode, value); + if (bit7) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rr_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + bool bit1; + + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bit1 = (value & 0x1) != 0; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (bit1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void sla_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + bool carry; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value << 1)); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if ((value & 0x7F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void sra_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t bit7; + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + bit7 = value & 0x80; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + if (value & 1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + value = (value >> 1) | bit7; + set_src_value(gb, opcode, value); + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void srl_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value >> 1)); + if (value & 1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (!(value >> 1)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void swap_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value >> 4) | (value << 4)); + if (!value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void bit_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + uint8_t bit; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + bit = 1 << ((opcode >> 3) & 7); + if ((opcode & 0xC0) == 0x40) { /* Bit */ + gb->registers[GB_REGISTER_AF] &= 0xFF00 | GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + if (!(bit & value)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + } + else if ((opcode & 0xC0) == 0x80) { /* res */ + set_src_value(gb, opcode, value & ~bit) ; + } + else if ((opcode & 0xC0) == 0xC0) { /* set */ + set_src_value(gb, opcode, value | bit) ; + } +} + +static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + opcode = GB_read_memory(gb, gb->pc++); + switch (opcode >> 3) { + case 0: + rlc_r(gb, opcode); + break; + case 1: + rrc_r(gb, opcode); + break; + case 2: + rl_r(gb, opcode); + break; + case 3: + rr_r(gb, opcode); + break; + case 4: + sla_r(gb, opcode); + break; + case 5: + sra_r(gb, opcode); + break; + case 6: + swap_r(gb, opcode); + break; + case 7: + srl_r(gb, opcode); + break; + default: + bit_r(gb, opcode); + 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, + nop, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */ + ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a, + ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */ + ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a, + ld_h_b, ld_h_c, ld_h_d, ld_h_e, nop, ld_h_l, ld_h_dhl, ld_h_a, /* 6X */ + ld_l_b, ld_l_c, ld_l_d, ld_l_e, ld_l_h, nop, ld_l_dhl, ld_l_a, + ld_dhl_b, ld_dhl_c, ld_dhl_d, ld_dhl_e, ld_dhl_h, ld_dhl_l, halt, ld_dhl_a, /* 7X */ + ld_a_b, ld_a_c, ld_a_d, ld_a_e, ld_a_h, ld_a_l, ld_a_dhl, nop, + 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_run(GB_gameboy_t *gb) +{ + gb->vblank_just_occured = false; + bool interrupt = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; + + if (interrupt) { + gb->halted = false; + } + + if (gb->hdma_on) { + GB_advance_cycles(gb, 4); + return; + } + + bool effecitve_ime = gb->ime; + if (gb->ime_toggle) { + gb->ime = !gb->ime; + gb->ime_toggle = false; + } + + if (effecitve_ime && interrupt) { + uint8_t interrupt_bit = 0; + uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; + while (!(interrupt_queue & 1)) { + interrupt_queue >>= 1; + interrupt_bit++; + } + gb->io_registers[GB_IO_IF] &= ~(1 << interrupt_bit); + gb->ime = false; + nop(gb, 0); + /* Run pseudo instructions rst 40-60*/ + rst(gb, 0x87 + interrupt_bit * 8); + } + else if(!gb->halted && !gb->stopped) { + uint8_t opcode = GB_read_memory(gb, gb->pc++); + if (gb->halt_bug) { + gb->pc--; + gb->halt_bug = false; + } + opcodes[opcode](gb, opcode); + } + else { + GB_advance_cycles(gb, 4); + } +} diff --git a/waterbox/sameboy/z80_cpu.h b/waterbox/sameboy/z80_cpu.h new file mode 100644 index 0000000000..1434ed7b96 --- /dev/null +++ b/waterbox/sameboy/z80_cpu.h @@ -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 */ diff --git a/waterbox/sameboy/z80_disassembler.c b/waterbox/sameboy/z80_disassembler.c new file mode 100644 index 0000000000..08fb62f039 --- /dev/null +++ b/waterbox/sameboy/z80_disassembler.c @@ -0,0 +1,788 @@ +#include +#include +#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); + } +} From 34e68c589fd602333be43b94c06aa641052ab39d Mon Sep 17 00:00:00 2001 From: nattthebear Date: Sun, 16 Jul 2017 14:53:03 -0400 Subject: [PATCH 02/13] Sameboy progress --- BizHawk.Client.Common/RomLoader.cs | 3 +- .../BizHawk.Emulation.Cores.csproj | 4 + .../Consoles/Nintendo/Gameboy/LibSameboy.cs | 16 + .../Consoles/Nintendo/Gameboy/Sameboy.cs | 88 + .../Properties/Resources.Designer.cs | 20 + .../Properties/Resources.resx | 6 + .../Resources/cgb_boot.bin.gz | Bin 0 -> 1682 bytes .../Resources/dmg_boot.bin.gz | Bin 0 -> 215 bytes waterbox/emulibc/emulibc.h | 4 + waterbox/sameboy/.vscode/settings.json | 6 + waterbox/sameboy/Makefile | 2 +- waterbox/sameboy/bizhawk.cpp | 118 ++ waterbox/sameboy/debugger.c | 1850 ----------------- waterbox/sameboy/debugger.h | 32 - waterbox/sameboy/gb.c | 4 +- waterbox/sameboy/gb.h | 12 - waterbox/sameboy/memory.c | 6 - waterbox/sameboy/timing.c | 2 - waterbox/sameboy/z80_cpu.c | 6 - waterbox/sameboy/z80_disassembler.c | 788 ------- 20 files changed, 266 insertions(+), 2701 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs create mode 100644 BizHawk.Emulation.Cores/Resources/cgb_boot.bin.gz create mode 100644 BizHawk.Emulation.Cores/Resources/dmg_boot.bin.gz create mode 100644 waterbox/sameboy/.vscode/settings.json create mode 100644 waterbox/sameboy/bizhawk.cpp delete mode 100644 waterbox/sameboy/debugger.c delete mode 100644 waterbox/sameboy/debugger.h delete mode 100644 waterbox/sameboy/z80_disassembler.c diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 9a9e0338ab..4af0355a29 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -921,7 +921,8 @@ namespace BizHawk.Client.Common if (!Global.Config.GB_AsSGB) { //core = CoreInventory.Instance["GB", "Pizza Boy"]; - core = CoreInventory.Instance["GB", "Gambatte"]; + //core = CoreInventory.Instance["GB", "Gambatte"]; + core = CoreInventory.Instance["GB", "SameBoy"]; } else { diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 07fb3f1ede..f56e83a6ab 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -538,7 +538,9 @@ + + @@ -1309,6 +1311,8 @@ + + diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs new file mode 100644 index 0000000000..5c13e48d23 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs @@ -0,0 +1,16 @@ +using BizHawk.Common.BizInvoke; +using BizHawk.Emulation.Cores.Waterbox; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy +{ + public abstract class LibSameboy : LibWaterboxCore + { + [BizImport(CC)] + public abstract bool Init(bool cgb); + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs new file mode 100644 index 0000000000..453aaf65a0 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -0,0 +1,88 @@ +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Properties; +using BizHawk.Emulation.Cores.Waterbox; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy +{ + [Core("SameBoy", "LIJI32", true, false, "efc11783c7fb6da66e1dd084e41ba6a85c0bd17e", + "https://sameboy.github.io/", false)] + public class Sameboy : WaterboxCore, IGameboyCommon + { + /// + /// the nominal length of one frame + /// + private const int TICKSPERFRAME = 35112; + + /// + /// number of ticks per second (GB, CGB) + /// + private const int TICKSPERSECOND = 2097152; + + /// + /// number of ticks per second (SGB) + /// + private const int TICKSPERSECOND_SGB = 2147727; + + private LibSameboy _core; + private bool _cgb; + + [CoreConstructor("GB")] + public Sameboy(CoreComm comm, byte[] rom) + : base(comm, new Configuration + { + DefaultWidth = 160, + DefaultHeight = 144, + MaxWidth = 256, + MaxHeight = 224, + MaxSamples = 1024, + DefaultFpsNumerator = TICKSPERSECOND, + DefaultFpsDenominator = TICKSPERFRAME, + SystemId = "GB" + }) + { + _core = PreInit(new PeRunnerOptions + { + Filename = "sameboy.wbx", + SbrkHeapSizeKB = 128, + InvisibleHeapSizeKB = 16 * 1024, + SealedHeapSizeKB = 5 * 1024, + PlainHeapSizeKB = 4096, + MmapHeapSizeKB = 34 * 1024 + }); + + _cgb = (rom[0x143] & 0xc0) == 0xc0; + Console.WriteLine("Automaticly detected CGB to " + _cgb); + var bios = Util.DecompressGzipFile(new MemoryStream( + _cgb ? Resources.SameboyCgbBoot : Resources.SameboyDmgBoot)); + + _exe.AddReadonlyFile(rom, "game.rom"); + _exe.AddReadonlyFile(bios, "boot.rom"); + + if (!_core.Init(_cgb)) + { + throw new InvalidOperationException("Core rejected the rom!"); + } + + _exe.RemoveReadonlyFile("game.rom"); + _exe.RemoveReadonlyFile("boot.rom"); + + PostInit(); + } + + protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound) + { + return new LibSameboy.FrameInfo + { + }; + } + + public bool IsCGBMode() => _cgb; + } +} diff --git a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs index 59d6602caf..5a3bbcce9a 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs +++ b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs @@ -60,6 +60,26 @@ namespace BizHawk.Emulation.Cores.Properties { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] SameboyCgbBoot { + get { + object obj = ResourceManager.GetObject("SameboyCgbBoot", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] SameboyDmgBoot { + get { + object obj = ResourceManager.GetObject("SameboyDmgBoot", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/BizHawk.Emulation.Cores/Properties/Resources.resx b/BizHawk.Emulation.Cores/Properties/Resources.resx index 1378c9e6e9..bdb07b9a9f 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.resx +++ b/BizHawk.Emulation.Cores/Properties/Resources.resx @@ -118,6 +118,12 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\cgb_boot.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\dmg_boot.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\sgb-cart-present.spc.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/BizHawk.Emulation.Cores/Resources/cgb_boot.bin.gz b/BizHawk.Emulation.Cores/Resources/cgb_boot.bin.gz new file mode 100644 index 0000000000000000000000000000000000000000..ab0eb54bfdbeeb29f773031bd817112cd4bd807d GIT binary patch literal 1682 zcmV;D25tEtiwFqP+h$n;0ApujUt(`>bS`3PZUE(0_Y2cN6uvZVT1V5=8LKvz)KTl+ zE2i#^TU6W%?g@&kHW5Ju#l3NZI1m*@R9tZzN}MPT)N?P?I;wRgzSQjx{sVs8-Fxre zy^nj3BQI4gd&kp;x`;HhA5-Q$EF!YlbvqnLtX3RqP!sCsp?2 zPs(S~BIu?>P7Ad#D={z%PI*xSzc$x13PsxQ_{JAp>J~MP)k9LGpfmkA13@fv(p<f1s*MmhE)+Zu; zM`cM06?Dc93E;;sVY5iXFfw;B$^z2RyvZ;X&Gw8k-R!|I9!ERsXkpOImkjy_(gFSN zm{p|Kg&VKsISU)~>TW1wO7<99tmfuITgNY1QDbyrds&r=+Jq9@-TuR;*ksR54buRGCmLTuzcaF~%bni^ZZY4pt8;TdGJg2Inu8uWW&` zyihGzi7AdMmn(-*L8)kgl8u`+Z`7z$<7RD|wZWY#cJ0)t6K>V6QLDDCnl!_mFn&CL z{`__8*6F(5vSmxi^y<~C(P*?^zkcV>pVt~T#Drju<1rk3OfcbQahlc;<2o46$9UcH z`>a_U#xQt75YGdLZ~o5t>vpcwck0^ChO_XHt7yzsJaPHXmKy1R_mNpUIgB_= zc)}x|;LQXg(HWSep~Jyf!Q^o|oz7;n1p5%;Z1qUB^FX_H;H1Vo3#Z(+T6I+GW(e8p z%i_r8NTKoT5{_KlsdYI=+H_?q=clja-Y0>0$s98&Hyno~Vjg4hQc>{u=$S7;sRtW^4D4Oq%$SK2Crzp!pYZv`!2b_9$vFzz56E&*xgHf2CuRaFGA0AMCm0F+9wtXfSsC=Y99T3$1p4U%qENnzc)xhWhN1zb%x>+B5<|BUm!C;5+E_R5b?Y^5ocDFG8 zWQh85mQGrMs862o&}&gqI>M)?z7s}A{Ac(_PD3dYVqDHQ{|HkUXACpzHH-V8!@&Bn z36)YYZSr2y_G3eR6Q-4*k+T%afY1?UBX3GQamx2lZQ=N%dBv&j_6*9C8?*0)|aN_01N|Fc0ZEBGNB{`{0A3e5XaE2J literal 0 HcmV?d00001 diff --git a/BizHawk.Emulation.Cores/Resources/dmg_boot.bin.gz b/BizHawk.Emulation.Cores/Resources/dmg_boot.bin.gz new file mode 100644 index 0000000000000000000000000000000000000000..85f1e8276a20c915a780bf8d228032451b736ab4 GIT binary patch literal 215 zcmV;|04V<-iwFqP+h$n;0Ay`vUt(`>bS`3PZU8g<_g|5rLFsgk#&5fZ2Wk%l?LI#c zdZ21o{y@d<&jWWs7Dh#Z1}XQmO$Z{O;gjGR20jicVI^f=g`akkuLNc)>d)lk zvDM%(0t)EyOMJDP_`rcp_v}0dyXLb?8QAzitb-udG$2cH1LM<=ioT4D!VH2p7!Lnq zuw&t4IIZyYwBX0ng0>1@E0vU$luv$e`8Pq~*U7Wf8CZeT17W-N2O=kJoOUkVxzx#q RfokAE002P!A@=|Q005=QWzzrv literal 0 HcmV?d00001 diff --git a/waterbox/emulibc/emulibc.h b/waterbox/emulibc/emulibc.h index da0eccc7c8..29d0ff7baa 100644 --- a/waterbox/emulibc/emulibc.h +++ b/waterbox/emulibc/emulibc.h @@ -10,7 +10,11 @@ extern "C" { // mark an entry point or callback pointer #define ECL_ENTRY // mark a visible symbol +#ifdef __cplusplus +#define ECL_EXPORT extern "C" __attribute__((visibility("default"))) +#else #define ECL_EXPORT __attribute__((visibility("default"))) +#endif // allocate memory from the "sealed" pool. this memory can never be freed, // and can only be allocated or written to during the init phase. after that, the host diff --git a/waterbox/sameboy/.vscode/settings.json b/waterbox/sameboy/.vscode/settings.json new file mode 100644 index 0000000000..a5401e1c1f --- /dev/null +++ b/waterbox/sameboy/.vscode/settings.json @@ -0,0 +1,6 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "editor.tabSize": 4, + "editor.insertSpaces": false, + "editor.detectIndentation": false +} \ No newline at end of file diff --git a/waterbox/sameboy/Makefile b/waterbox/sameboy/Makefile index 0fdbe63b73..f3824c30ee 100644 --- a/waterbox/sameboy/Makefile +++ b/waterbox/sameboy/Makefile @@ -11,7 +11,7 @@ CCFLAGS:=$(FLAGS) -Ilib \ -std=gnu99 \ -DLSB_FIRST -D_GNU_SOURCE -DGB_INTERNAL -CPPFLAGS:=$(FLAGS) -DSPC_NO_COPY_STATE_FUNCS +CPPFLAGS:=$(FLAGS) -DSPC_NO_COPY_STATE_FUNCS -std=c++0x TARGET = sameboy.wbx diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp new file mode 100644 index 0000000000..81b31201d0 --- /dev/null +++ b/waterbox/sameboy/bizhawk.cpp @@ -0,0 +1,118 @@ +#include +#include "../emulibc/emulibc.h" +#include "../emulibc/waterboxcore.h" + +#define _Static_assert static_assert + +extern "C" { +#include "gb.h" +#include "joypad.h" +#include "apu.h" +} + +static GB_gameboy_t GB; + +static void VBlankCallback(GB_gameboy_t *gb) +{ + +} + +static void LogCallback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + fputs(string, stdout); +} + +static uint32_t RgbEncodeCallback(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return b | g << 8 | r << 16 | 0xff000000; +} + +static void InfraredCallback(GB_gameboy_t *gb, bool on, long cycles_since_last_update) +{ + +} + +static void RumbleCallback(GB_gameboy_t *gb, bool rumble_on) +{ + +} + +static void SerialStartCallback(GB_gameboy_t *gb, uint8_t byte_to_send) +{ + +} + +static uint8_t SerialEndCallback(GB_gameboy_t *gb) +{ + return 0; +} + +ECL_EXPORT bool Init(bool cgb) +{ + if (cgb) + GB_init_cgb(&GB); + else + GB_init(&GB); + if (GB_load_boot_rom(&GB, "boot.rom") != 0) + return false; + + if (GB_load_rom(&GB, "game.rom") != 0) + return false; + + GB_set_vblank_callback(&GB, VBlankCallback); + GB_set_log_callback(&GB, LogCallback); + GB_set_rgb_encode_callback(&GB, RgbEncodeCallback); + GB_set_infrared_callback(&GB, InfraredCallback); + GB_set_rumble_callback(&GB, RumbleCallback); + + return true; +} + +struct MyFrameInfo : public FrameInfo +{ + +}; + +ECL_EXPORT void FrameAdvance(MyFrameInfo &f) +{ + GB_set_pixels_output(&GB, f.VideoBuffer); + // void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed); + GB_run_frame(&GB); + f.Samples = 735; + f.Width = 160; + f.Height = 144; +} + +static void SetMemoryArea(MemoryArea *m, GB_direct_access_t access, const char* name, int32_t flags) +{ + size_t size; + m->Name = name; + m->Data = GB_get_direct_access(&GB, access, &size, nullptr); + m->Size = size; + m->Flags = flags; +} + +ECL_EXPORT void GetMemoryAreas(MemoryArea *m) +{ + // TODO: "System Bus" + SetMemoryArea(m + 0, GB_DIRECT_ACCESS_RAM, "WRAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_PRIMARY); + SetMemoryArea(m + 1, GB_DIRECT_ACCESS_ROM, "ROM", MEMORYAREA_FLAGS_WORDSIZE1); + SetMemoryArea(m + 2, GB_DIRECT_ACCESS_VRAM, "VRAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 3, GB_DIRECT_ACCESS_CART_RAM, "CartRAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 4, GB_DIRECT_ACCESS_OAM, "OAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 5, GB_DIRECT_ACCESS_HRAM, "HRAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 6, GB_DIRECT_ACCESS_IO, "IO", MEMORYAREA_FLAGS_WORDSIZE1); + SetMemoryArea(m + 7, GB_DIRECT_ACCESS_BOOTROM, "BOOTROM", MEMORYAREA_FLAGS_WORDSIZE1); + SetMemoryArea(m + 8, GB_DIRECT_ACCESS_BGP, "BGP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 8, GB_DIRECT_ACCESS_OBP, "OBP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); +} + +ECL_EXPORT void SetInputCallback(void (*callback)()) +{ + // TODO +} + +int main() +{ + return 0; +} diff --git a/waterbox/sameboy/debugger.c b/waterbox/sameboy/debugger.c deleted file mode 100644 index 7fc7b2a9a6..0000000000 --- a/waterbox/sameboy/debugger.c +++ /dev/null @@ -1,1850 +0,0 @@ -#include -#include -#include -#include "gb.h" - -typedef struct { - bool has_bank; - uint16_t bank:9; - uint16_t value; -} value_t; - -typedef struct { - enum { - LVALUE_MEMORY, - LVALUE_REG16, - LVALUE_REG_H, - LVALUE_REG_L, - } kind; - union { - uint16_t *register_address; - value_t memory_address; - }; -} lvalue_t; - -#define VALUE_16(x) ((value_t){false, 0, (x)}) - -struct GB_breakpoint_s { - union { - struct { - uint16_t addr; - uint16_t bank; /* -1 = any bank*/ - }; - uint32_t key; /* For sorting and comparing */ - }; - char *condition; -}; - -#define BP_KEY(x) (((struct GB_breakpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) - -#define GB_WATCHPOINT_R (1) -#define GB_WATCHPOINT_W (2) - -struct GB_watchpoint_s { - union { - struct { - uint16_t addr; - uint16_t bank; /* -1 = any bank*/ - }; - uint32_t key; /* For sorting and comparing */ - }; - char *condition; - uint8_t flags; -}; - -#define WP_KEY(x) (((struct GB_watchpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) - -static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr) -{ - if (addr < 0x4000) { - return gb->mbc_rom0_bank; - } - - if (addr < 0x8000) { - return gb->mbc_rom_bank; - } - - if (addr < 0xD000) { - return 0; - } - - if (addr < 0xE000) { - return gb->cgb_ram_bank; - } - - return 0; -} - -typedef struct { - uint16_t rom0_bank; - uint16_t rom_bank; - uint8_t mbc_ram_bank; - bool mbc_ram_enable; - uint8_t ram_bank; - uint8_t vram_bank; -} banking_state_t; - -static inline void save_banking_state(GB_gameboy_t *gb, banking_state_t *state) -{ - state->rom0_bank = gb->mbc_rom0_bank; - state->rom_bank = gb->mbc_rom_bank; - state->mbc_ram_bank = gb->mbc_ram_bank; - state->mbc_ram_enable = gb->mbc_ram_enable; - state->ram_bank = gb->cgb_ram_bank; - state->vram_bank = gb->cgb_vram_bank; -} - -static inline void restore_banking_state(GB_gameboy_t *gb, banking_state_t *state) -{ - - gb->mbc_rom0_bank = state->rom0_bank; - gb->mbc_rom_bank = state->rom_bank; - gb->mbc_ram_bank = state->mbc_ram_bank; - gb->mbc_ram_enable = state->mbc_ram_enable; - gb->cgb_ram_bank = state->ram_bank; - gb->cgb_vram_bank = state->vram_bank; -} - -static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank) -{ - gb->mbc_rom0_bank = bank; - gb->mbc_rom_bank = bank; - gb->mbc_ram_bank = bank; - gb->mbc_ram_enable = true; - if (gb->is_cgb) { - gb->cgb_ram_bank = bank & 7; - gb->cgb_vram_bank = bank & 1; - } -} - -static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name) -{ - static __thread char output[256]; - const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value); - - if (symbol && (value - symbol->addr > 0x1000 || symbol->addr == 0) ) { - symbol = NULL; - } - - /* Avoid overflow */ - if (symbol && strlen(symbol->name) > 240) { - symbol = NULL; - } - - if (!symbol) { - sprintf(output, "$%04x", value); - } - - else if (symbol->addr == value) { - if (prefer_name) { - sprintf(output, "%s ($%04x)", symbol->name, value); - } - else { - sprintf(output, "$%04x (%s)", value, symbol->name); - } - } - - else { - if (prefer_name) { - sprintf(output, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value); - } - else { - sprintf(output, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr); - } - } - return output; -} - -static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name) -{ - if (!value.has_bank) return value_to_string(gb, value.value, prefer_name); - - static __thread char output[256]; - const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value); - - if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) { - symbol = NULL; - } - - /* Avoid overflow */ - if (symbol && strlen(symbol->name) > 240) { - symbol = NULL; - } - - if (!symbol) { - sprintf(output, "$%02x:$%04x", value.bank, value.value); - } - - else if (symbol->addr == value.value) { - if (prefer_name) { - sprintf(output, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value); - } - else { - sprintf(output, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name); - } - } - - else { - if (prefer_name) { - sprintf(output, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value); - } - else { - sprintf(output, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr); - } - } - return output; -} - -static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue) -{ - /* Not used until we add support for operators like += */ - switch (lvalue.kind) { - case LVALUE_MEMORY: - if (lvalue.memory_address.has_bank) { - banking_state_t state; - save_banking_state(gb, &state); - switch_banking_state(gb, lvalue.memory_address.bank); - value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); - restore_banking_state(gb, &state); - return r; - } - return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value)); - - case LVALUE_REG16: - return VALUE_16(*lvalue.register_address); - - case LVALUE_REG_L: - return VALUE_16(*lvalue.register_address & 0x00FF); - - case LVALUE_REG_H: - return VALUE_16(*lvalue.register_address >> 8); - } -} - -static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value) -{ - switch (lvalue.kind) { - case LVALUE_MEMORY: - if (lvalue.memory_address.has_bank) { - banking_state_t state; - save_banking_state(gb, &state); - switch_banking_state(gb, lvalue.memory_address.bank); - GB_write_memory(gb, lvalue.memory_address.value, value); - restore_banking_state(gb, &state); - return; - } - GB_write_memory(gb, lvalue.memory_address.value, value); - return; - - case LVALUE_REG16: - *lvalue.register_address = value; - return; - - case LVALUE_REG_L: - *lvalue.register_address &= 0xFF00; - *lvalue.register_address |= value & 0xFF; - return; - - case LVALUE_REG_H: - *lvalue.register_address &= 0x00FF; - *lvalue.register_address |= value << 8; - return; - } -} - -/* 16 bit value 16 bit value = 16 bit value - 25 bit address 16 bit value = 25 bit address - 16 bit value 25 bit address = 25 bit address - 25 bit address 25 bit address = 16 bit value (since adding pointers, for examples, makes no sense) - - Boolean operators always return a 16-bit value - */ -#define FIX_BANK(x) ((value_t){a.has_bank ^ b.has_bank, a.has_bank? a.bank : b.bank, (x)}) - -static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);} -static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);} -static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);} -static value_t _div(value_t a, value_t b) { - if (b.value == 0) { - return FIX_BANK(0); - } - return FIX_BANK(a.value / b.value); -}; -static value_t mod(value_t a, value_t b) { - if (b.value == 0) { - return FIX_BANK(0); - } - return FIX_BANK(a.value % b.value); -}; -static value_t and(value_t a, value_t b) {return FIX_BANK(a.value & b.value);} -static value_t or(value_t a, value_t b) {return FIX_BANK(a.value | b.value);} -static value_t xor(value_t a, value_t b) {return FIX_BANK(a.value ^ b.value);} -static value_t shleft(value_t a, value_t b) {return FIX_BANK(a.value << b.value);} -static value_t shright(value_t a, value_t b) {return FIX_BANK(a.value >> b.value);} -static value_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b) -{ - write_lvalue(gb, a, b); - return read_lvalue(gb, a); -} - -static value_t bool_and(value_t a, value_t b) {return VALUE_16(a.value && b.value);} -static value_t bool_or(value_t a, value_t b) {return VALUE_16(a.value || b.value);} -static value_t equals(value_t a, value_t b) {return VALUE_16(a.value == b.value);} -static value_t different(value_t a, value_t b) {return VALUE_16(a.value != b.value);} -static value_t lower(value_t a, value_t b) {return VALUE_16(a.value < b.value);} -static value_t greater(value_t a, value_t b) {return VALUE_16(a.value > b.value);} -static value_t lower_equals(value_t a, value_t b) {return VALUE_16(a.value <= b.value);} -static value_t greater_equals(value_t a, value_t b) {return VALUE_16(a.value >= b.value);} -static value_t bank(value_t a, value_t b) {return (value_t) {true, a.value, b.value};} - - -static struct { - const char *string; - char priority; - value_t (*operator)(value_t, value_t); - value_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t); -} operators[] = -{ - // Yes. This is not C-like. But it makes much more sense. - // Deal with it. - {"+", 0, add}, - {"-", 0, sub}, - {"||", 0, bool_or}, - {"|", 0, or}, - {"*", 1, mul}, - {"/", 1, _div}, - {"%", 1, mod}, - {"&&", 1, bool_and}, - {"&", 1, and}, - {"^", 1, xor}, - {"<<", 2, shleft}, - {"<=", 3, lower_equals}, - {"<", 3, lower}, - {">>", 2, shright}, - {">=", 3, greater_equals}, - {">", 3, greater}, - {"==", 3, equals}, - {"=", -1, NULL, assign}, - {"!=", 3, different}, - {":", 4, bank}, -}; - -value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, - size_t length, bool *error, - uint16_t *watchpoint_address, uint8_t *watchpoint_new_value); - -static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string, - size_t length, bool *error, - uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) -{ - *error = false; - // Strip whitespace - while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { - string++; - length--; - } - while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { - length--; - } - if (length == 0) - { - GB_log(gb, "Expected expression.\n"); - *error = true; - return (lvalue_t){0,}; - } - if (string[0] == '(' && string[length - 1] == ')') { - // Attempt to strip parentheses - signed int depth = 0; - for (int i = 0; i < length; i++) { - if (string[i] == '(') depth++; - if (depth == 0) { - // First and last are not matching - depth = 1; - break; - } - if (string[i] == ')') depth--; - } - if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); - } - else if (string[0] == '[' && string[length - 1] == ']') { - // Attempt to strip square parentheses (memory dereference) - signed int depth = 0; - for (int i = 0; i < length; i++) { - if (string[i] == '[') depth++; - if (depth == 0) { - // First and last are not matching - depth = 1; - break; - } - if (string[i] == ']') depth--; - } - if (depth == 0) { - return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)}; - } - } - - // Registers - if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { - if (length == 1) { - switch (string[0]) { - case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]}; - case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]}; - case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]}; - case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]}; - case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]}; - case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]}; - case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]}; - case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]}; - } - } - else if (length == 2) { - switch (string[0]) { - case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]}; - case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]}; - case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]}; - case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]}; - case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]}; - case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc}; - } - } - GB_log(gb, "Unknown register: %.*s\n", (unsigned int) length, string); - *error = true; - return (lvalue_t){0,}; - } - - GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned int) length, string); - *error = true; - return (lvalue_t){0,}; -} - -#define ERROR ((value_t){0,}) -value_t debugger_evaluate(GB_gameboy_t *gb, const char *string, - size_t length, bool *error, - uint16_t *watchpoint_address, uint8_t *watchpoint_new_value) -{ - /* Disable watchpoints while evaulating expressions */ - uint16_t n_watchpoints = gb->n_watchpoints; - gb->n_watchpoints = 0; - - value_t ret = ERROR; - - *error = false; - // Strip whitespace - while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) { - string++; - length--; - } - while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) { - length--; - } - if (length == 0) - { - GB_log(gb, "Expected expression.\n"); - *error = true; - goto exit; - } - if (string[0] == '(' && string[length - 1] == ')') { - // Attempt to strip parentheses - signed int depth = 0; - for (int i = 0; i < length; i++) { - if (string[i] == '(') depth++; - if (depth == 0) { - // First and last are not matching - depth = 1; - break; - } - if (string[i] == ')') depth--; - } - if (depth == 0) { - ret = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); - goto exit; - } - } - else if (string[0] == '[' && string[length - 1] == ']') { - // Attempt to strip square parentheses (memory dereference) - signed int depth = 0; - for (int i = 0; i < length; i++) { - if (string[i] == '[') depth++; - if (depth == 0) { - // First and last are not matching - depth = 1; - break; - } - if (string[i] == ']') depth--; - } - - if (depth == 0) { - value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value); - banking_state_t state; - if (addr.bank) { - save_banking_state(gb, &state); - switch_banking_state(gb, addr.bank); - } - ret = VALUE_16(GB_read_memory(gb, addr.value)); - if (addr.bank) { - restore_banking_state(gb, &state); - } - goto exit; - } - - } - // Search for lowest priority operator - signed int depth = 0; - unsigned int operator_index = -1; - unsigned int operator_pos = 0; - for (int i = 0; i < length; i++) { - if (string[i] == '(') depth++; - else if (string[i] == ')') depth--; - else if (string[i] == '[') depth++; - else if (string[i] == ']') depth--; - else if (depth == 0) { - for (int j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) { - if (strlen(operators[j].string) > length - i) continue; // Operator too big. - // Priority higher than what we already have. - if (operator_index != -1 && operators[operator_index].priority < operators[j].priority) continue; - unsigned long operator_length = strlen(operators[j].string); - if (memcmp(string + i, operators[j].string, operator_length) == 0) { - // Found an operator! - operator_pos = i; - operator_index = j; - /* for supporting = vs ==, etc*/ - i += operator_length - 1; - break; - } - } - } - } - if (operator_index != -1) { - unsigned int right_start = (unsigned int)(operator_pos + strlen(operators[operator_index].string)); - value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value); - if (*error) goto exit; - if (operators[operator_index].lvalue_operator) { - lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); - if (*error) goto exit; - ret = operators[operator_index].lvalue_operator(gb, left, right.value); - goto exit; - } - value_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value); - if (*error) goto exit; - ret = operators[operator_index].operator(left, right); - goto exit; - } - - // Not an expression - must be a register or a literal - - // Registers - if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) { - if (length == 1) { - switch (string[0]) { - case 'a': ret = VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); goto exit; - case 'f': ret = VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); goto exit; - case 'b': ret = VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); goto exit; - case 'c': ret = VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); goto exit; - case 'd': ret = VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); goto exit; - case 'e': ret = VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); goto exit; - case 'h': ret = VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); goto exit; - case 'l': ret = VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); goto exit; - } - } - else if (length == 2) { - switch (string[0]) { - case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->registers[GB_REGISTER_AF]); goto exit;} - case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->registers[GB_REGISTER_BC]); goto exit;} - case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->registers[GB_REGISTER_DE]); goto exit;} - case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->registers[GB_REGISTER_HL]); goto exit;} - case 's': if (string[1] == 'p') {ret = VALUE_16(gb->registers[GB_REGISTER_SP]); goto exit;} - case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;} - } - } - else if (length == 3) { - if (watchpoint_address && memcmp(string, "old", 3) == 0) { - ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); - goto exit; - } - - if (watchpoint_new_value && memcmp(string, "new", 3) == 0) { - ret = VALUE_16(*watchpoint_new_value); - goto exit; - } - - /* $new is identical to $old in read conditions */ - if (watchpoint_address && memcmp(string, "new", 3) == 0) { - ret = VALUE_16(GB_read_memory(gb, *watchpoint_address)); - goto exit; - } - } - - char symbol_name[length + 1]; - memcpy(symbol_name, string, length); - symbol_name[length] = 0; - const GB_symbol_t *symbol = GB_reversed_map_find_symbol(&gb->reversed_symbol_map, symbol_name); - if (symbol) { - ret = (value_t){true, symbol->bank, symbol->addr}; - goto exit; - } - - GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned int) length, string); - *error = true; - goto exit; - } - - char *end; - int base = 10; - if (string[0] == '$') { - string++; - base = 16; - length--; - } - uint16_t literal = (uint16_t) (strtol(string, &end, base)); - if (end != string + length) { - GB_log(gb, "Failed to parse: %.*s\n", (unsigned int) length, string); - *error = true; - goto exit; - } - ret = VALUE_16(literal); -exit: - gb->n_watchpoints = n_watchpoints; - return ret; -} - -struct debugger_command_s; -typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command); - -typedef struct debugger_command_s { - const char *command; - uint8_t min_length; - debugger_command_imp_t *implementation; - const char *help_string; // Null if should not appear in help - const char *arguments_format; // For usage message - const char *modifiers_format; // For usage message -} debugger_command_t; - -static const char *lstrip(const char *str) -{ - while (*str == ' ' || *str == '\t') { - str++; - } - return str; -} - -#define STOPPED_ONLY \ -if (!gb->debug_stopped) { \ -GB_log(gb, "Program is running. \n"); \ -return false; \ -} - -#define NO_MODIFIERS \ -if (modifiers) { \ -print_usage(gb, command); \ -return true; \ -} - -static void print_usage(GB_gameboy_t *gb, const debugger_command_t *command) -{ - GB_log(gb, "Usage: %s", command->command); - - if (command->modifiers_format) { - GB_log(gb, "[/%s]", command->modifiers_format); - } - - if (command->arguments_format) { - GB_log(gb, " %s", command->arguments_format); - } - - GB_log(gb, "\n"); -} - -static bool cont(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - STOPPED_ONLY - - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - gb->debug_stopped = false; - return false; -} - -static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - STOPPED_ONLY - - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - gb->debug_stopped = false; - gb->debug_next_command = true; - gb->debug_call_depth = 0; - return false; -} - -static bool step(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - STOPPED_ONLY - - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - return false; -} - -static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - STOPPED_ONLY - - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - gb->debug_stopped = false; - gb->debug_fin_command = true; - gb->debug_call_depth = 0; - return false; -} - -static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - STOPPED_ONLY - - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - gb->debug_stopped = false; - gb->stack_leak_detection = true; - gb->debug_call_depth = 0; - return false; -} - -static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - GB_log(gb, "AF = $%04x\n", gb->registers[GB_REGISTER_AF]); /* AF can't really be an address */ - GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); - GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); - GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false)); - GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false)); - GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false)); - return true; -} - -/* Find the index of the closest breakpoint equal or greater to addr */ -static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) -{ - if (!gb->breakpoints) { - return 0; - } - - uint32_t key = BP_KEY(addr); - - int min = 0; - int max = gb->n_breakpoints; - while (min < max) { - uint16_t pivot = (min + max) / 2; - if (gb->breakpoints[pivot].key == key) return pivot; - if (gb->breakpoints[pivot].key > key) { - max = pivot; - } - else { - min = pivot + 1; - } - } - return (uint16_t) min; -} - -static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - if (strlen(lstrip(arguments)) == 0) { - print_usage(gb, command); - return true; - } - - if (gb->n_breakpoints == (typeof(gb->n_breakpoints)) -1) { - GB_log(gb, "Too many breakpoints set\n"); - return true; - } - - char *condition = NULL; - if ((condition = strstr(arguments, " if "))) { - *condition = 0; - condition += strlen(" if "); - /* Verify condition is sane (Todo: This might have side effects!) */ - bool error; - debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, NULL, NULL); - if (error) return true; - - } - - bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); - uint32_t key = BP_KEY(result); - - if (error) return true; - - uint16_t index = find_breakpoint(gb, result); - if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { - GB_log(gb, "Breakpoint already set at %s\n", debugger_value_to_string(gb, result, true)); - if (!gb->breakpoints[index].condition && condition) { - GB_log(gb, "Added condition to breakpoint\n"); - gb->breakpoints[index].condition = strdup(condition); - } - else if (gb->breakpoints[index].condition && condition) { - GB_log(gb, "Replaced breakpoint condition\n"); - free(gb->breakpoints[index].condition); - gb->breakpoints[index].condition = strdup(condition); - } - else if (gb->breakpoints[index].condition && !condition) { - GB_log(gb, "Removed breakpoint condition\n"); - free(gb->breakpoints[index].condition); - gb->breakpoints[index].condition = NULL; - } - return true; - } - - gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0])); - memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); - gb->breakpoints[index].key = key; - - if (condition) { - gb->breakpoints[index].condition = strdup(condition); - } - else { - gb->breakpoints[index].condition = NULL; - } - gb->n_breakpoints++; - - GB_log(gb, "Breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); - return true; -} - -static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - if (strlen(lstrip(arguments)) == 0) { - for (unsigned i = gb->n_breakpoints; i--;) { - if (gb->breakpoints[i].condition) { - free(gb->breakpoints[i].condition); - } - } - free(gb->breakpoints); - gb->breakpoints = NULL; - gb->n_breakpoints = 0; - return true; - } - - bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); - uint32_t key = BP_KEY(result); - - if (error) return true; - - uint16_t index = find_breakpoint(gb, result); - if (index >= gb->n_breakpoints || gb->breakpoints[index].key != key) { - GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); - return true; - } - - if (gb->breakpoints[index].condition) { - free(gb->breakpoints[index].condition); - } - - memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); - gb->n_breakpoints--; - gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); - - GB_log(gb, "Breakpoint removed from %s\n", debugger_value_to_string(gb, result, true)); - return true; -} - -/* Find the index of the closest watchpoint equal or greater to addr */ -static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr) -{ - if (!gb->watchpoints) { - return 0; - } - uint32_t key = WP_KEY(addr); - int min = 0; - int max = gb->n_watchpoints; - while (min < max) { - uint16_t pivot = (min + max) / 2; - if (gb->watchpoints[pivot].key == key) return pivot; - if (gb->watchpoints[pivot].key > key) { - max = pivot; - } - else { - min = pivot + 1; - } - } - return (uint16_t) min; -} - -static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - if (strlen(lstrip(arguments)) == 0) { -print_usage: - print_usage(gb, command); - return true; - } - - if (gb->n_watchpoints == (typeof(gb->n_watchpoints)) -1) { - GB_log(gb, "Too many watchpoints set\n"); - return true; - } - - if (!modifiers) { - modifiers = "w"; - } - - uint8_t flags = 0; - while (*modifiers) { - switch (*modifiers) { - case 'r': - flags |= GB_WATCHPOINT_R; - break; - case 'w': - flags |= GB_WATCHPOINT_W; - break; - default: - goto print_usage; - } - modifiers++; - } - - if (!flags) { - goto print_usage; - } - - char *condition = NULL; - if ((condition = strstr(arguments, " if "))) { - *condition = 0; - condition += strlen(" if "); - /* Verify condition is sane (Todo: This might have side effects!) */ - bool error; - /* To make $new and $old legal */ - uint16_t dummy = 0; - uint8_t dummy2 = 0; - debugger_evaluate(gb, condition, (unsigned int)strlen(condition), &error, &dummy, &dummy2); - if (error) return true; - - } - - bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); - uint32_t key = WP_KEY(result); - - if (error) return true; - - uint16_t index = find_watchpoint(gb, result); - if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { - GB_log(gb, "Watchpoint already set at %s\n", debugger_value_to_string(gb, result, true)); - if (gb->watchpoints[index].flags != flags) { - GB_log(gb, "Modified watchpoint type\n"); - gb->watchpoints[index].flags = flags; - } - if (!gb->watchpoints[index].condition && condition) { - GB_log(gb, "Added condition to watchpoint\n"); - gb->watchpoints[index].condition = strdup(condition); - } - else if (gb->watchpoints[index].condition && condition) { - GB_log(gb, "Replaced watchpoint condition\n"); - free(gb->watchpoints[index].condition); - gb->watchpoints[index].condition = strdup(condition); - } - else if (gb->watchpoints[index].condition && !condition) { - GB_log(gb, "Removed watchpoint condition\n"); - free(gb->watchpoints[index].condition); - gb->watchpoints[index].condition = NULL; - } - return true; - } - - gb->watchpoints = realloc(gb->watchpoints, (gb->n_watchpoints + 1) * sizeof(gb->watchpoints[0])); - memmove(&gb->watchpoints[index + 1], &gb->watchpoints[index], (gb->n_watchpoints - index) * sizeof(gb->watchpoints[0])); - gb->watchpoints[index].key = key; - gb->watchpoints[index].flags = flags; - if (condition) { - gb->watchpoints[index].condition = strdup(condition); - } - else { - gb->watchpoints[index].condition = NULL; - } - gb->n_watchpoints++; - - GB_log(gb, "Watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); - return true; -} - -static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - if (strlen(lstrip(arguments)) == 0) { - for (unsigned i = gb->n_watchpoints; i--;) { - if (gb->watchpoints[i].condition) { - free(gb->watchpoints[i].condition); - } - } - free(gb->watchpoints); - gb->watchpoints = NULL; - gb->n_watchpoints = 0; - return true; - } - - bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); - uint32_t key = WP_KEY(result); - - if (error) return true; - - uint16_t index = find_watchpoint(gb, result); - if (index >= gb->n_watchpoints || gb->watchpoints[index].key != key) { - GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true)); - return true; - } - - if (gb->watchpoints[index].condition) { - free(gb->watchpoints[index].condition); - } - - memmove(&gb->watchpoints[index], &gb->watchpoints[index + 1], (gb->n_watchpoints - index - 1) * sizeof(gb->watchpoints[0])); - gb->n_watchpoints--; - gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0])); - - GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true)); - return true; -} - -static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - if (gb->n_breakpoints == 0) { - GB_log(gb, "No breakpoints set.\n"); - } - else { - GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); - for (uint16_t i = 0; i < gb->n_breakpoints; i++) { - value_t addr = (value_t){gb->breakpoints[i].bank != (uint16_t)-1, gb->breakpoints[i].bank, gb->breakpoints[i].addr}; - if (gb->breakpoints[i].condition) { - GB_log(gb, " %d. %s (Condition: %s)\n", i + 1, - debugger_value_to_string(gb, addr, addr.has_bank), - gb->breakpoints[i].condition); - } - else { - GB_log(gb, " %d. %s\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank)); - } - } - } - - if (gb->n_watchpoints == 0) { - GB_log(gb, "No watchpoints set.\n"); - } - else { - GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints); - for (uint16_t i = 0; i < gb->n_watchpoints; i++) { - value_t addr = (value_t){gb->watchpoints[i].bank != (uint16_t)-1, gb->watchpoints[i].bank, gb->watchpoints[i].addr}; - if (gb->watchpoints[i].condition) { - GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), - (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', - (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-', - gb->watchpoints[i].condition); - } - else { - GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb,addr, addr.has_bank), - (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-', - (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-'); - } - } - } - - return true; -} - -static bool _should_break(GB_gameboy_t *gb, value_t addr) -{ - uint16_t index = find_breakpoint(gb, addr); - uint32_t key = BP_KEY(addr); - - if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { - if (!gb->breakpoints[index].condition) { - return true; - } - bool error; - bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition, - (unsigned int)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value; - if (error) { - /* Should never happen */ - GB_log(gb, "An internal error has occured\n"); - return true; - } - return condition; - } - return false; -} - -static bool should_break(GB_gameboy_t *gb, uint16_t addr) -{ - /* Try any-bank breakpoint */ - value_t full_addr = (VALUE_16(addr)); - if (_should_break(gb, full_addr)) return true; - - /* Try bank-specific breakpoint */ - full_addr.has_bank = true; - full_addr.bank = bank_for_addr(gb, addr); - return _should_break(gb, full_addr); -} - -static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - if (strlen(lstrip(arguments)) == 0) { - print_usage(gb, command); - return true; - } - - if (!modifiers || !modifiers[0]) { - modifiers = "a"; - } - else if (modifiers[1]) { - print_usage(gb, command); - return true; - } - - bool error; - value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); - if (!error) { - switch (modifiers[0]) { - case 'a': - GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false)); - break; - case 'd': - GB_log(gb, "=%d\n", result.value); - break; - case 'x': - GB_log(gb, "=$%x\n", result.value); - break; - case 'o': - GB_log(gb, "=0%o\n", result.value); - break; - case 'b': - { - if (!result.value) { - GB_log(gb, "=%%0\n"); - break; - } - char binary[17]; - binary[16] = 0; - char *ptr = &binary[16]; - while (result.value) { - *(--ptr) = (result.value & 1)? '1' : '0'; - result.value >>= 1; - } - GB_log(gb, "=%%%s\n", ptr); - break; - } - default: - break; - } - } - return true; -} - -static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - if (strlen(lstrip(arguments)) == 0) { - print_usage(gb, command); - return true; - } - - bool error; - value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); - uint16_t count = 32; - - if (modifiers) { - char *end; - count = (uint16_t) (strtol(modifiers, &end, 10)); - if (*end) { - print_usage(gb, command); - return true; - } - } - - if (!error) { - if (addr.has_bank) { - banking_state_t old_state; - save_banking_state(gb, &old_state); - switch_banking_state(gb, addr.bank); - - while (count) { - GB_log(gb, "%02x:%04x: ", addr.bank, addr.value); - for (int i = 0; i < 16 && count; i++) { - GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); - count--; - } - addr.value += 16; - GB_log(gb, "\n"); - } - - restore_banking_state(gb, &old_state); - } - else { - while (count) { - GB_log(gb, "%04x: ", addr.value); - for (int i = 0; i < 16 && count; i++) { - GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i)); - count--; - } - addr.value += 16; - GB_log(gb, "\n"); - } - } - } - return true; -} - -static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - if (strlen(lstrip(arguments)) == 0) { - arguments = "pc"; - } - - bool error; - value_t addr = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL); - uint16_t count = 5; - - if (modifiers) { - char *end; - count = (uint16_t) (strtol(modifiers, &end, 10)); - if (*end) { - print_usage(gb, command); - return true; - } - } - - if (!error) { - if (addr.has_bank) { - banking_state_t old_state; - save_banking_state(gb, &old_state); - switch_banking_state(gb, addr.bank); - - GB_cpu_disassemble(gb, addr.value, count); - - restore_banking_state(gb, &old_state); - } - else { - GB_cpu_disassemble(gb, addr.value, count); - } - } - return true; -} - -static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - const GB_cartridge_t *cartridge = gb->cartridge_type; - - if (cartridge->has_ram) { - GB_log(gb, "Cartrdige includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); - } - else { - GB_log(gb, "No cartridge RAM\n"); - } - - if (cartridge->mbc_type) { - static const char * const mapper_names[] = { - [GB_MBC1] = "MBC1", - [GB_MBC2] = "MBC2", - [GB_MBC3] = "MBC3", - [GB_MBC5] = "MBC5", - [GB_HUC1] = "HUC1", - [GB_HUC3] = "HUC3", - }; - GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); - GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank); - if (cartridge->has_ram) { - GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank); - GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); - } - if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) { - GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM"); - } - if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_MBC1M_WIRING) { - GB_log(gb, "MBC1 uses MBC1M wiring. \n"); - GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank); - GB_log(gb, "MBC1 multicart banking mode is %s\n", gb->mbc1.mode == 1 ? "enabled" : "disabled"); - } - - } - else { - GB_log(gb, "No MBC\n"); - } - - if (cartridge->has_rumble) { - GB_log(gb, "Cart contains a rumble pak\n"); - } - - if (cartridge->has_rtc) { - GB_log(gb, "Cart contains a real time clock\n"); - } - - return true; -} - -static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true)); - for (unsigned int i = gb->backtrace_size; i--;) { - GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true)); - } - - return true; -} - -static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - STOPPED_ONLY - - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - GB_log(gb, "Ticks: %lu. (Resetting)\n", gb->debugger_ticks); - gb->debugger_ticks = 0; - - return true; -} - - -static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - - if (!gb->is_cgb) { - GB_log(gb, "Not available on a DMG.\n"); - return true; - } - - GB_log(gb, "Background palettes: \n"); - for (unsigned i = 0; i < 32; i++) { - GB_log(gb, "%04x ", ((uint16_t *)&gb->background_palettes_data)[i]); - if (i % 4 == 3) { - GB_log(gb, "\n"); - } - } - - GB_log(gb, "Sprites palettes: \n"); - for (unsigned i = 0; i < 32; i++) { - GB_log(gb, "%04x ", ((uint16_t *)&gb->sprite_palettes_data)[i]); - if (i % 4 == 3) { - GB_log(gb, "\n"); - } - } - - return true; -} - -static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) -{ - NO_MODIFIERS - if (strlen(lstrip(arguments))) { - print_usage(gb, command); - return true; - } - GB_log(gb, "LCDC:\n"); - GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled"); - GB_log(gb, " %s: %s\n", gb->is_cgb? (gb->cgb_mode? "Sprite priority flags" : "Background and Window") : "Background", - (gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled"); - GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled"); - GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8"); - GB_log(gb, " Background tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 8)? "$9C00" : "$9800"); - GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & 16)? "$8000" : "$8800"); - GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & 32)? "Enabled" : "Disabled"); - GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 64)? "$9C00" : "$9800"); - - GB_log(gb, "\nSTAT:\n"); - static const char *modes[] = {"Mode 0, H-Blank", "Mode 1, V-Blank", "Mode 2, OAM", "Mode 3, Rendering"}; - GB_log(gb, " Current mode: %s\n", modes[gb->io_registers[GB_IO_STAT] & 3]); - GB_log(gb, " LYC flag: %s\n", (gb->io_registers[GB_IO_STAT] & 4)? "On" : "Off"); - GB_log(gb, " H-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 8)? "Enabled" : "Disabled"); - GB_log(gb, " V-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 16)? "Enabled" : "Disabled"); - GB_log(gb, " OAM interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 32)? "Enabled" : "Disabled"); - GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled"); - - - GB_log(gb, "\nCycles since frame start: %d\n", gb->display_cycles); - GB_log(gb, "Current line: %d\n", gb->display_cycles / 456); - GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]); - GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]); - GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]); - GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off"); - - return true; -} - -static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command); - -#define HELP_NEWLINE "\n " - -/* Commands without implementations are aliases of the previous non-alias commands */ -static const debugger_command_t commands[] = { - {"continue", 1, cont, "Continue running until next stop"}, - {"next", 1, next, "Run the next instruction, skipping over function calls"}, - {"step", 1, step, "Run the next instruction, stepping into function calls"}, - {"finish", 1, finish, "Run until the current function returns"}, - {"backtrace", 2, backtrace, "Display the current call stack"}, - {"bt", 2, }, /* Alias */ - {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"}, - {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was used"}, - {"registers", 1, registers, "Print values of processor registers and other important registers"}, - {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"}, - {"mbc", 3, }, /* Alias */ - {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, - {"palettes", 3, palettes, "Displays the current CGB palettes"}, - {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE - "Can also modify the condition of existing breakpoints.", - "[ if ]"}, - {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, - {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE - "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE - "Default watchpoint type is write-only.", - "[ if ]", "(r|w|rw)"}, - {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]"}, - {"list", 1, list, "List all set breakpoints and watchpoints"}, - {"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE - "Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE - "decimal (d), hexadecimal (x), octal (o) or binary (b).", - "", "format"}, - {"eval", 2, }, /* Alias */ - {"examine", 2, examine, "Examine values at address", "", "count"}, - {"x", 1, }, /* Alias */ - {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count"}, - - - {"help", 1, help, "List available commands or show help for the specified command", "[]"}, - {NULL,}, /* Null terminator */ -}; - -static const debugger_command_t *find_command(const char *string) -{ - size_t length = strlen(string); - for (const debugger_command_t *command = commands; command->command; command++) { - if (command->min_length > length) continue; - if (memcmp(command->command, string, length) == 0) { /* Is a substring? */ - /* Aliases */ - while (!command->implementation) { - command--; - } - return command; - } - } - - return NULL; -} - -static void print_command_shortcut(GB_gameboy_t *gb, const debugger_command_t *command) -{ - GB_attributed_log(gb, GB_LOG_BOLD | GB_LOG_UNDERLINE, "%.*s", command->min_length, command->command); - GB_attributed_log(gb, GB_LOG_BOLD , "%s", command->command + command->min_length); -} - -static void print_command_description(GB_gameboy_t *gb, const debugger_command_t *command) -{ - print_command_shortcut(gb, command); - GB_log(gb, ": "); - GB_log(gb, (const char *)&" %s\n" + strlen(command->command), command->help_string); -} - -static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *ignored) -{ - const debugger_command_t *command = find_command(arguments); - if (command) { - print_command_description(gb, command); - GB_log(gb, "\n"); - print_usage(gb, command); - - command++; - if (command->command && !command->implementation) { /* Command has aliases*/ - GB_log(gb, "\nAliases: "); - do { - print_command_shortcut(gb, command); - GB_log(gb, " "); - command++; - } while (command->command && !command->implementation); - GB_log(gb, "\n"); - } - return true; - } - for (command = commands; command->command; command++) { - if (command->help_string) { - print_command_description(gb, command); - } - } - return true; -} - -void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr) -{ - /* Called just after the CPU calls a function/enters an interrupt/etc... */ - - if (gb->stack_leak_detection) { - if (gb->debug_call_depth >= sizeof(gb->sp_for_call_depth) / sizeof(gb->sp_for_call_depth[0])) { - GB_log(gb, "Potential stack overflow detected (Functions nest too much). \n"); - gb->debug_stopped = true; - } - else { - gb->sp_for_call_depth[gb->debug_call_depth] = gb->registers[GB_REGISTER_SP]; - gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc; - } - } - - if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) { - - while (gb->backtrace_size) { - if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->registers[GB_REGISTER_SP]) { - gb->backtrace_size--; - } - else { - break; - } - } - - gb->backtrace_sps[gb->backtrace_size] = gb->registers[GB_REGISTER_SP]; - gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr); - gb->backtrace_returns[gb->backtrace_size].addr = call_addr; - gb->backtrace_size++; - } - - gb->debug_call_depth++; -} - -void GB_debugger_ret_hook(GB_gameboy_t *gb) -{ - /* Called just before the CPU runs ret/reti */ - - gb->debug_call_depth--; - - if (gb->stack_leak_detection) { - if (gb->debug_call_depth < 0) { - GB_log(gb, "Function finished without a stack leak.\n"); - gb->debug_stopped = true; - } - else { - if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) { - GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true)); - GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->registers[GB_REGISTER_SP], - gb->sp_for_call_depth[gb->debug_call_depth]); - gb->debug_stopped = true; - } - } - } - - while (gb->backtrace_size) { - if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->registers[GB_REGISTER_SP]) { - gb->backtrace_size--; - } - else { - break; - } - } -} - -static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, uint8_t value) -{ - uint16_t index = find_watchpoint(gb, addr); - uint32_t key = WP_KEY(addr); - - if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { - if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_W)) { - return false; - } - if (!gb->watchpoints[index].condition) { - gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); - return true; - } - bool error; - bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, - (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value; - if (error) { - /* Should never happen */ - GB_log(gb, "An internal error has occured\n"); - return false; - } - if (condition) { - gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value); - return true; - } - } - return false; -} - -void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value) -{ - if (gb->debug_stopped) return; - - /* Try any-bank breakpoint */ - value_t full_addr = (VALUE_16(addr)); - if (_GB_debugger_test_write_watchpoint(gb, full_addr, value)) return; - - /* Try bank-specific breakpoint */ - full_addr.has_bank = true; - full_addr.bank = bank_for_addr(gb, addr); - _GB_debugger_test_write_watchpoint(gb, full_addr, value); -} - -static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr) -{ - uint16_t index = find_watchpoint(gb, addr); - uint32_t key = WP_KEY(addr); - - if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) { - if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) { - return false; - } - if (!gb->watchpoints[index].condition) { - gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); - return true; - } - bool error; - bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition, - (unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value; - if (error) { - /* Should never happen */ - GB_log(gb, "An internal error has occured\n"); - return false; - } - if (condition) { - gb->debug_stopped = true; - GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true)); - return true; - } - } - return false; -} - -void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr) -{ - if (gb->debug_stopped) return; - - /* Try any-bank breakpoint */ - value_t full_addr = (VALUE_16(addr)); - if (_GB_debugger_test_read_watchpoint(gb, full_addr)) return; - - /* Try bank-specific breakpoint */ - full_addr.has_bank = true; - full_addr.bank = bank_for_addr(gb, addr); - _GB_debugger_test_read_watchpoint(gb, full_addr); -} - -/* Returns true if debugger waits for more commands */ -bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) -{ - if (!input[0]) { - return true; - } - - char *command_string = input; - char *arguments = strchr(input, ' '); - if (arguments) { - /* Actually "split" the string. */ - arguments[0] = 0; - arguments++; - } - else { - arguments = ""; - } - - char *modifiers = strchr(command_string, '/'); - if (modifiers) { - /* Actually "split" the string. */ - modifiers[0] = 0; - modifiers++; - } - - const debugger_command_t *command = find_command(command_string); - if (command) { - return command->implementation(gb, arguments, modifiers, command); - } - else { - GB_log(gb, "%s: no such command.\n", command_string); - return true; - } -} - -void GB_debugger_run(GB_gameboy_t *gb) -{ - if (gb->debug_disable) return; - - char *input = NULL; - if (gb->debug_next_command && gb->debug_call_depth <= 0) { - gb->debug_stopped = true; - } - if (gb->debug_fin_command && gb->debug_call_depth == -1) { - gb->debug_stopped = true; - } - if (gb->debug_stopped) { - GB_cpu_disassemble(gb, gb->pc, 5); - } -next_command: - if (input) { - free(input); - } - if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc)) { - gb->debug_stopped = true; - GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); - GB_cpu_disassemble(gb, gb->pc, 5); - } - if (gb->debug_stopped && !gb->debug_disable) { - gb->debug_next_command = false; - gb->debug_fin_command = false; - gb->stack_leak_detection = false; - input = gb->input_callback(gb); - - if (input == NULL) { - /* Debugging is no currently available, continue running */ - gb->debug_stopped = false; - return; - } - - if (GB_debugger_execute_command(gb, input)) { - goto next_command; - } - - free(input); - } -} - -void GB_debugger_handle_async_commands(GB_gameboy_t *gb) -{ - char *input = NULL; - - while (gb->async_input_callback && (input = gb->async_input_callback(gb))) { - GB_debugger_execute_command(gb, input); - free(input); - } -} - -void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path) -{ - FILE *f = fopen(path, "r"); - if (!f) return; - - char *line = NULL; - size_t size = 0; - size_t length = 0; - while ((length = getline(&line, &size, f)) != -1) { - for (unsigned i = 0; i < length; i++) { - if (line[i] == ';' || line[i] == '\n' || line[i] == '\r') { - line[i] = 0; - length = i; - break; - } - } - if (length == 0) continue; - - unsigned int bank, address; - char symbol[length]; - - if (sscanf(line, "%02x:%04x %s", &bank, &address, symbol) == 3) { - bank &= 0x1FF; - if (!gb->bank_symbols[bank]) { - gb->bank_symbols[bank] = GB_map_alloc(); - } - GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol); - if (allocated_symbol) { - GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol); - } - } - } - free(line); - fclose(f); -} - -const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr) -{ - uint16_t bank = bank_for_addr(gb, addr); - - const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr); - if (symbol) return symbol; - if (bank != 0) return GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */ - - return NULL; -} - -const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr) -{ - const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, addr); - if (symbol && symbol->addr == addr) return symbol->name; - return NULL; -} - -/* The public version of debugger_evaluate */ -bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank) -{ - bool error = false; - value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL, NULL); - if (result) { - *result = value.value; - } - if (result_bank) { - *result_bank = value.has_bank? value.value : -1; - } - return error; -} - -void GB_debugger_break(GB_gameboy_t *gb) -{ - gb->debug_stopped = true; -} - -bool GB_debugger_is_stopped(GB_gameboy_t *gb) -{ - return gb->debug_stopped; -} - -void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled) -{ - gb->debug_disable = disabled; -} diff --git a/waterbox/sameboy/debugger.h b/waterbox/sameboy/debugger.h deleted file mode 100644 index 5d6491972c..0000000000 --- a/waterbox/sameboy/debugger.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef debugger_h -#define debugger_h -#include -#include -#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 */ diff --git a/waterbox/sameboy/gb.c b/waterbox/sameboy/gb.c index 56697a1e83..d464734c4e 100644 --- a/waterbox/sameboy/gb.c +++ b/waterbox/sameboy/gb.c @@ -67,7 +67,7 @@ static char *default_input_callback(GB_gameboy_t *gb) static char *default_async_input_callback(GB_gameboy_t *gb) { -#ifndef _WIN32 +#if 0 fd_set set; FD_ZERO(&set); FD_SET(STDIN_FILENO, &set); @@ -260,12 +260,10 @@ exit: 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); } } diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h index 82608821fd..4d7588b644 100644 --- a/waterbox/sameboy/gb.h +++ b/waterbox/sameboy/gb.h @@ -10,7 +10,6 @@ #include "apu.h" #include "camera.h" -#include "debugger.h" #include "display.h" #include "joypad.h" #include "mbc.h" @@ -179,9 +178,6 @@ typedef struct { 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. @@ -445,10 +441,6 @@ struct GB_gameboy_internal_s { 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; @@ -467,10 +459,6 @@ struct GB_gameboy_internal_s { 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; diff --git a/waterbox/sameboy/memory.c b/waterbox/sameboy/memory.c index c914e55965..f694d210a4 100644 --- a/waterbox/sameboy/memory.c +++ b/waterbox/sameboy/memory.c @@ -268,9 +268,6 @@ static GB_read_function_t * const read_map[] = 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; } @@ -677,9 +674,6 @@ static GB_write_function_t * const write_map[] = 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; diff --git a/waterbox/sameboy/timing.c b/waterbox/sameboy/timing.c index 77348fe232..d53c85a014 100644 --- a/waterbox/sameboy/timing.c +++ b/waterbox/sameboy/timing.c @@ -125,8 +125,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) } } - gb->debugger_ticks += cycles; - if (gb->cgb_double_speed) { cycles >>=1; } diff --git a/waterbox/sameboy/z80_cpu.c b/waterbox/sameboy/z80_cpu.c index 0a1bde5dd7..19adc82f47 100644 --- a/waterbox/sameboy/z80_cpu.c +++ b/waterbox/sameboy/z80_cpu.c @@ -701,7 +701,6 @@ static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify timing */ if (condition_code(gb, opcode)) { - GB_debugger_ret_hook(gb); GB_advance_cycles(gb, 8); gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); GB_advance_cycles(gb, 4); @@ -768,8 +767,6 @@ static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_advance_cycles(gb, 4); gb->pc = addr; - - GB_debugger_call_hook(gb, call_addr); } else { GB_advance_cycles(gb, 12); @@ -938,12 +935,10 @@ static void rst(GB_gameboy_t *gb, uint8_t opcode) GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_advance_cycles(gb, 4); gb->pc = opcode ^ 0xC7; - GB_debugger_call_hook(gb, call_addr); } static void ret(GB_gameboy_t *gb, uint8_t opcode) { - GB_debugger_ret_hook(gb); GB_advance_cycles(gb, 4); gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); GB_advance_cycles(gb, 4); @@ -972,7 +967,6 @@ static void call_a16(GB_gameboy_t *gb, uint8_t opcode) GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); GB_advance_cycles(gb, 4); gb->pc = addr; - GB_debugger_call_hook(gb, call_addr); } static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) diff --git a/waterbox/sameboy/z80_disassembler.c b/waterbox/sameboy/z80_disassembler.c deleted file mode 100644 index 08fb62f039..0000000000 --- a/waterbox/sameboy/z80_disassembler.c +++ /dev/null @@ -1,788 +0,0 @@ -#include -#include -#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); - } -} From f7bb894753caacaf994eaf51c2b61e71019bcce2 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Sun, 16 Jul 2017 20:36:58 -0400 Subject: [PATCH 03/13] sameboy: it's alive! --- .../Consoles/Nintendo/Gameboy/Sameboy.cs | 4 +- waterbox/sameboy/Makefile | 5 +- waterbox/sameboy/bizhawk.cpp | 14 +- waterbox/sameboy/camera.c | 2 +- waterbox/sameboy/display.c | 7 - waterbox/sameboy/gb.c | 40 +-- waterbox/sameboy/gb.h | 42 +-- waterbox/sameboy/joypad.c | 7 - waterbox/sameboy/memory.c | 4 - waterbox/sameboy/save_state.c | 304 ------------------ waterbox/sameboy/save_state.h | 24 -- waterbox/sameboy/symbol_hash.c | 106 ------ waterbox/sameboy/symbol_hash.h | 37 --- waterbox/sameboy/timing.c | 65 +--- waterbox/sameboy/timing.h | 3 - waterbox/sameboy/z80_cpu.c | 4 +- 16 files changed, 31 insertions(+), 637 deletions(-) delete mode 100644 waterbox/sameboy/save_state.c delete mode 100644 waterbox/sameboy/save_state.h delete mode 100644 waterbox/sameboy/symbol_hash.c delete mode 100644 waterbox/sameboy/symbol_hash.h diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index 453aaf65a0..8d93b7521f 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -59,8 +59,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy _cgb = (rom[0x143] & 0xc0) == 0xc0; Console.WriteLine("Automaticly detected CGB to " + _cgb); - var bios = Util.DecompressGzipFile(new MemoryStream( - _cgb ? Resources.SameboyCgbBoot : Resources.SameboyDmgBoot)); + var bios = Util.DecompressGzipFile(new MemoryStream(_cgb ? Resources.SameboyCgbBoot : Resources.SameboyDmgBoot)); + // var bios = comm.CoreFileProvider.GetFirmware(_cgb ? "GBC" : "GB", "World", true); _exe.AddReadonlyFile(rom, "game.rom"); _exe.AddReadonlyFile(bios, "boot.rom"); diff --git a/waterbox/sameboy/Makefile b/waterbox/sameboy/Makefile index f3824c30ee..640a98f0d7 100644 --- a/waterbox/sameboy/Makefile +++ b/waterbox/sameboy/Makefile @@ -6,12 +6,11 @@ FLAGS:=-Wall -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=imp -fomit-frame-pointer -fvisibility=hidden \ -O0 -g -CCFLAGS:=$(FLAGS) -Ilib \ - -I../emulibc \ +CCFLAGS:=$(FLAGS) \ -std=gnu99 \ -DLSB_FIRST -D_GNU_SOURCE -DGB_INTERNAL -CPPFLAGS:=$(FLAGS) -DSPC_NO_COPY_STATE_FUNCS -std=c++0x +CPPFLAGS:=$(FLAGS) -DSPC_NO_COPY_STATE_FUNCS -std=c++0x -D_GNU_SOURCE -DGB_INTERNAL TARGET = sameboy.wbx diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp index 81b31201d0..15f462da84 100644 --- a/waterbox/sameboy/bizhawk.cpp +++ b/waterbox/sameboy/bizhawk.cpp @@ -65,6 +65,9 @@ ECL_EXPORT bool Init(bool cgb) GB_set_infrared_callback(&GB, InfraredCallback); GB_set_rumble_callback(&GB, RumbleCallback); + GB_set_sample_rate(&GB, 44100); + GB_set_audio_quality(&GB, 1); + return true; } @@ -76,9 +79,12 @@ struct MyFrameInfo : public FrameInfo ECL_EXPORT void FrameAdvance(MyFrameInfo &f) { GB_set_pixels_output(&GB, f.VideoBuffer); - // void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed); - GB_run_frame(&GB); - f.Samples = 735; + for (int i = 0; i < (int)GB_KEY_MAX; i++) + GB_set_key_state(&GB, (GB_key_t)i, false); + + f.Cycles = GB_run_cycles(&GB, 35112); + f.Samples = GB_apu_get_current_buffer_length(&GB); + GB_apu_copy_buffer(&GB, (GB_sample_t*)f.SoundBuffer, f.Samples); f.Width = 160; f.Height = 144; } @@ -104,7 +110,7 @@ ECL_EXPORT void GetMemoryAreas(MemoryArea *m) SetMemoryArea(m + 6, GB_DIRECT_ACCESS_IO, "IO", MEMORYAREA_FLAGS_WORDSIZE1); SetMemoryArea(m + 7, GB_DIRECT_ACCESS_BOOTROM, "BOOTROM", MEMORYAREA_FLAGS_WORDSIZE1); SetMemoryArea(m + 8, GB_DIRECT_ACCESS_BGP, "BGP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); - SetMemoryArea(m + 8, GB_DIRECT_ACCESS_OBP, "OBP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 9, GB_DIRECT_ACCESS_OBP, "OBP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); } ECL_EXPORT void SetInputCallback(void (*callback)()) diff --git a/waterbox/sameboy/camera.c b/waterbox/sameboy/camera.c index 9b34998af3..6ce2c0d89e 100644 --- a/waterbox/sameboy/camera.c +++ b/waterbox/sameboy/camera.c @@ -125,7 +125,7 @@ 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(); + noise_seed = 42; // 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; diff --git a/waterbox/sameboy/display.c b/waterbox/sameboy/display.c index e624ac93b6..d2ee911619 100644 --- a/waterbox/sameboy/display.c +++ b/waterbox/sameboy/display.c @@ -197,12 +197,6 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) 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); @@ -212,7 +206,6 @@ static void display_vblank(GB_gameboy_t *gb) } gb->vblank_callback(gb); - GB_timing_sync(gb); gb->vblank_just_occured = true; } diff --git a/waterbox/sameboy/gb.c b/waterbox/sameboy/gb.c index d464734c4e..475d48f881 100644 --- a/waterbox/sameboy/gb.c +++ b/waterbox/sameboy/gb.c @@ -133,18 +133,6 @@ void GB_free(GB_gameboy_t *gb) 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)); } @@ -267,24 +255,20 @@ void GB_run(GB_gameboy_t *gb) } } -uint64_t GB_run_frame(GB_gameboy_t *gb) +uint64_t GB_run_cycles(GB_gameboy_t *gb, uint32_t cycles) { - /* 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) { + uint64_t start = gb->cycles_since_epoch; + uint64_t target = start + cycles; + while (gb->cycles_since_epoch < target) { GB_run(gb); if (gb->vblank_just_occured) { + // TODO: fix these up + GB_update_joyp(gb); + GB_rtc_run(gb); break; } } - gb->turbo = old_turbo; - gb->turbo_dont_skip = old_dont_skip; - return gb->cycles_since_last_sync * FRAME_LENGTH * LCDC_PERIOD; + return gb->cycles_since_epoch - start; } void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) @@ -416,12 +400,6 @@ 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; @@ -441,7 +419,7 @@ 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)); + // memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved)); gb->version = GB_STRUCT_VERSION; gb->mbc_rom_bank = 1; diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h index 4d7588b644..a80b89e5ec 100644 --- a/waterbox/sameboy/gb.h +++ b/waterbox/sameboy/gb.h @@ -4,9 +4,9 @@ #include #include #include +#include #include "gb_struct_def.h" -#include "save_state.h" #include "apu.h" #include "camera.h" @@ -17,7 +17,6 @@ #include "printer.h" #include "timing.h" #include "z80_cpu.h" -#include "symbol_hash.h" #define GB_STRUCT_VERSION 11 @@ -193,7 +192,6 @@ 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. */ @@ -201,9 +199,7 @@ struct GB_gameboy_internal_s { /* 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 { @@ -249,10 +245,8 @@ struct GB_gameboy_internal_s { /* 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; @@ -264,10 +258,8 @@ struct GB_gameboy_internal_s { 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; @@ -311,32 +303,23 @@ struct GB_gameboy_internal_s { 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; @@ -349,10 +332,8 @@ struct GB_gameboy_internal_s { } 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]; @@ -379,11 +360,9 @@ struct GB_gameboy_internal_s { 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; @@ -404,8 +383,7 @@ struct GB_gameboy_internal_s { bool keys[GB_KEY_MAX]; /* Timing */ - uint64_t last_sync; - uint64_t cycles_since_last_sync; + uint64_t cycles_since_epoch; /* Audio */ unsigned buffer_size; @@ -459,21 +437,11 @@ struct GB_gameboy_internal_s { uint16_t addr; } backtrace_returns[0x200]; - /* 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 @@ -497,8 +465,7 @@ 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); +uint64_t GB_run_cycles(GB_gameboy_t *gb, uint32_t cycles); typedef enum { GB_DIRECT_ACCESS_ROM, @@ -526,7 +493,6 @@ 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); diff --git a/waterbox/sameboy/joypad.c b/waterbox/sameboy/joypad.c index c5c4f08961..21e6d666b0 100644 --- a/waterbox/sameboy/joypad.c +++ b/waterbox/sameboy/joypad.c @@ -22,13 +22,6 @@ void GB_update_joyp(GB_gameboy_t *gb) 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: diff --git a/waterbox/sameboy/memory.c b/waterbox/sameboy/memory.c index f694d210a4..3e34895c97 100644 --- a/waterbox/sameboy/memory.c +++ b/waterbox/sameboy/memory.c @@ -456,10 +456,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) 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; diff --git a/waterbox/sameboy/save_state.c b/waterbox/sameboy/save_state.c deleted file mode 100644 index cd579aa800..0000000000 --- a/waterbox/sameboy/save_state.c +++ /dev/null @@ -1,304 +0,0 @@ -#include "gb.h" -#include -#include - -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 diff --git a/waterbox/sameboy/save_state.h b/waterbox/sameboy/save_state.h deleted file mode 100644 index 546ac2d95a..0000000000 --- a/waterbox/sameboy/save_state.h +++ /dev/null @@ -1,24 +0,0 @@ -/* Macros to make the GB_gameboy_t struct more future compatible when state saving */ -#ifndef save_state_h -#define save_state_h -#include - -#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 */ diff --git a/waterbox/sameboy/symbol_hash.c b/waterbox/sameboy/symbol_hash.c deleted file mode 100644 index 709421c233..0000000000 --- a/waterbox/sameboy/symbol_hash.c +++ /dev/null @@ -1,106 +0,0 @@ -#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; -} diff --git a/waterbox/sameboy/symbol_hash.h b/waterbox/sameboy/symbol_hash.h deleted file mode 100644 index 239b0e313e..0000000000 --- a/waterbox/sameboy/symbol_hash.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef symbol_hash_h -#define symbol_hash_h - -#include -#include - -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 */ diff --git a/waterbox/sameboy/timing.c b/waterbox/sameboy/timing.c index d53c85a014..6e6c81e752 100644 --- a/waterbox/sameboy/timing.c +++ b/waterbox/sameboy/timing.c @@ -6,69 +6,6 @@ #include #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; @@ -136,7 +73,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t 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->cycles_since_epoch += cycles >> 1; GB_dma_run(gb); GB_hdma_run(gb); GB_apu_run(gb); diff --git a/waterbox/sameboy/timing.h b/waterbox/sameboy/timing.h index ed9e15a513..bd3b2f2732 100644 --- a/waterbox/sameboy/timing.h +++ b/waterbox/sameboy/timing.h @@ -7,9 +7,6 @@ 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, diff --git a/waterbox/sameboy/z80_cpu.c b/waterbox/sameboy/z80_cpu.c index 19adc82f47..9558765263 100644 --- a/waterbox/sameboy/z80_cpu.c +++ b/waterbox/sameboy/z80_cpu.c @@ -283,7 +283,7 @@ static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) { /* Todo: Verify cycles are not 8 and 4 instead */ GB_advance_cycles(gb, 4); - gb->pc += (int8_t) GB_read_memory(gb, gb->pc++); + gb->pc += (int8_t)GB_read_memory(gb, gb->pc) + 1; GB_advance_cycles(gb, 8); } @@ -307,7 +307,7 @@ static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) { if (condition_code(gb, opcode)) { GB_advance_cycles(gb, 4); - gb->pc += (int8_t)GB_read_memory(gb, gb->pc++); + gb->pc += (int8_t)GB_read_memory(gb, gb->pc) + 1; GB_advance_cycles(gb, 8); } else { From 721a6c047086572f2f4e4dfd165d4497050e8163 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Tue, 18 Jul 2017 17:46:21 -0400 Subject: [PATCH 04/13] sameboy: brip bruffer --- waterbox/sameboy/apu.c | 105 +------- waterbox/sameboy/apu.h | 13 - waterbox/sameboy/bizhawk.cpp | 91 ++++--- waterbox/sameboy/blip_buf/blip_buf.c | 344 +++++++++++++++++++++++++++ waterbox/sameboy/blip_buf/blip_buf.h | 72 ++++++ waterbox/sameboy/gb.c | 28 +-- waterbox/sameboy/gb.h | 19 +- waterbox/sameboy/timing.c | 2 - 8 files changed, 493 insertions(+), 181 deletions(-) create mode 100644 waterbox/sameboy/blip_buf/blip_buf.c create mode 100644 waterbox/sameboy/blip_buf/blip_buf.h diff --git a/waterbox/sameboy/apu.c b/waterbox/sameboy/apu.c index e0e0b89f28..10b5bf9bea 100644 --- a/waterbox/sameboy/apu.c +++ b/waterbox/sameboy/apu.c @@ -62,9 +62,9 @@ static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) 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; + if (!steps) + return; gb->apu.apu_cycles %= (CPU_FREQUENCY/APU_FREQUENCY); for (uint8_t i = 0; i < 4; i++) { @@ -118,13 +118,9 @@ static void GB_apu_run_internal(GB_gameboy_t *gb) // 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) @@ -185,90 +181,9 @@ void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *sampl 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; + GB_sample_t sample; + GB_apu_get_samples_and_update_pcm_regs(gb, &sample); + gb->sample_callback(gb, sample, gb->cycles_since_epoch); } void GB_apu_init(GB_gameboy_t *gb) @@ -485,13 +400,3 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) 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; -} diff --git a/waterbox/sameboy/apu.h b/waterbox/sameboy/apu.h index ffc530e693..a8eab55fd6 100644 --- a/waterbox/sameboy/apu.h +++ b/waterbox/sameboy/apu.h @@ -14,12 +14,6 @@ typedef struct 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 @@ -60,13 +54,6 @@ typedef struct 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); diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp index 15f462da84..187c668a5b 100644 --- a/waterbox/sameboy/bizhawk.cpp +++ b/waterbox/sameboy/bizhawk.cpp @@ -1,6 +1,7 @@ #include #include "../emulibc/emulibc.h" #include "../emulibc/waterboxcore.h" +#include "blip_buf/blip_buf.h" #define _Static_assert static_assert @@ -14,7 +15,6 @@ static GB_gameboy_t GB; static void VBlankCallback(GB_gameboy_t *gb) { - } static void LogCallback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) @@ -24,22 +24,19 @@ static void LogCallback(GB_gameboy_t *gb, const char *string, GB_log_attributes static uint32_t RgbEncodeCallback(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) { - return b | g << 8 | r << 16 | 0xff000000; + return b | g << 8 | r << 16 | 0xff000000; } static void InfraredCallback(GB_gameboy_t *gb, bool on, long cycles_since_last_update) { - } static void RumbleCallback(GB_gameboy_t *gb, bool rumble_on) { - } static void SerialStartCallback(GB_gameboy_t *gb, uint8_t byte_to_send) { - } static uint8_t SerialEndCallback(GB_gameboy_t *gb) @@ -47,55 +44,83 @@ static uint8_t SerialEndCallback(GB_gameboy_t *gb) return 0; } +static blip_t *leftblip; +static blip_t *rightblip; +const int SOUND_RATE_GB = 2097152; +const int SOUND_RATE_SGB = 2147727; +static uint64_t sound_start_clock; +static GB_sample_t sample_gb; + +static void SampleCallback(GB_gameboy_t *gb, GB_sample_t sample, uint64_t clock) +{ + int l = sample.left - sample_gb.left; + int r = sample.right - sample_gb.right; + if (l) + blip_add_delta(leftblip, clock - sound_start_clock, l); + if (r) + blip_add_delta(rightblip, clock - sound_start_clock, r); + sample_gb = sample; +} + ECL_EXPORT bool Init(bool cgb) { - if (cgb) - GB_init_cgb(&GB); - else - GB_init(&GB); - if (GB_load_boot_rom(&GB, "boot.rom") != 0) - return false; - - if (GB_load_rom(&GB, "game.rom") != 0) - return false; + if (cgb) + GB_init_cgb(&GB); + else + GB_init(&GB); + if (GB_load_boot_rom(&GB, "boot.rom") != 0) + return false; - GB_set_vblank_callback(&GB, VBlankCallback); - GB_set_log_callback(&GB, LogCallback); - GB_set_rgb_encode_callback(&GB, RgbEncodeCallback); - GB_set_infrared_callback(&GB, InfraredCallback); - GB_set_rumble_callback(&GB, RumbleCallback); + if (GB_load_rom(&GB, "game.rom") != 0) + return false; - GB_set_sample_rate(&GB, 44100); - GB_set_audio_quality(&GB, 1); + GB_set_vblank_callback(&GB, VBlankCallback); + GB_set_log_callback(&GB, LogCallback); + GB_set_rgb_encode_callback(&GB, RgbEncodeCallback); + GB_set_infrared_callback(&GB, InfraredCallback); + GB_set_rumble_callback(&GB, RumbleCallback); + GB_set_sample_callback(&GB, SampleCallback); - return true; + leftblip = blip_new(1024); + rightblip = blip_new(1024); + blip_set_rates(leftblip, SOUND_RATE_GB, 44100); + blip_set_rates(rightblip, SOUND_RATE_GB, 44100); + + return true; } struct MyFrameInfo : public FrameInfo { - }; +static int FrameOverflow; + ECL_EXPORT void FrameAdvance(MyFrameInfo &f) { - GB_set_pixels_output(&GB, f.VideoBuffer); + GB_set_pixels_output(&GB, f.VideoBuffer); for (int i = 0; i < (int)GB_KEY_MAX; i++) GB_set_key_state(&GB, (GB_key_t)i, false); + + sound_start_clock = GB_epoch(&GB); - f.Cycles = GB_run_cycles(&GB, 35112); - f.Samples = GB_apu_get_current_buffer_length(&GB); - GB_apu_copy_buffer(&GB, (GB_sample_t*)f.SoundBuffer, f.Samples); + uint32_t target = 35112 - FrameOverflow; + f.Cycles = GB_run_cycles(&GB, target); + FrameOverflow = f.Cycles - target; + blip_end_frame(leftblip, f.Cycles); + blip_end_frame(rightblip, f.Cycles); + f.Samples = blip_read_samples(leftblip, f.SoundBuffer, 2048, 1); + blip_read_samples(rightblip, f.SoundBuffer + 1, 2048, 1); f.Width = 160; f.Height = 144; } -static void SetMemoryArea(MemoryArea *m, GB_direct_access_t access, const char* name, int32_t flags) +static void SetMemoryArea(MemoryArea *m, GB_direct_access_t access, const char *name, int32_t flags) { - size_t size; - m->Name = name; - m->Data = GB_get_direct_access(&GB, access, &size, nullptr); - m->Size = size; - m->Flags = flags; + size_t size; + m->Name = name; + m->Data = GB_get_direct_access(&GB, access, &size, nullptr); + m->Size = size; + m->Flags = flags; } ECL_EXPORT void GetMemoryAreas(MemoryArea *m) diff --git a/waterbox/sameboy/blip_buf/blip_buf.c b/waterbox/sameboy/blip_buf/blip_buf.c new file mode 100644 index 0000000000..1bd3377e92 --- /dev/null +++ b/waterbox/sameboy/blip_buf/blip_buf.c @@ -0,0 +1,344 @@ +/* blip_buf 1.1.0. http://www.slack.net/~ant/ */ + +#include "blip_buf.h" + +#include +#include +#include +#include + +/* Library Copyright (C) 2003-2009 Shay Green. This library is free software; +you can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +library is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#if defined (BLARGG_TEST) && BLARGG_TEST + #include "blargg_test.h" +#endif + +/* Equivalent to ULONG_MAX >= 0xFFFFFFFF00000000. +Avoids constants that don't fit in 32 bits. */ +#if ULONG_MAX/0xFFFFFFFF > 0xFFFFFFFF + typedef unsigned long fixed_t; + enum { pre_shift = 32 }; + +#elif defined(ULLONG_MAX) + typedef unsigned long long fixed_t; + enum { pre_shift = 32 }; + +#else + typedef unsigned fixed_t; + enum { pre_shift = 0 }; + +#endif + +enum { time_bits = pre_shift + 20 }; + +static fixed_t const time_unit = (fixed_t) 1 << time_bits; + +enum { bass_shift = 9 }; /* affects high-pass filter breakpoint frequency */ +enum { end_frame_extra = 2 }; /* allows deltas slightly after frame length */ + +enum { half_width = 8 }; +enum { buf_extra = half_width*2 + end_frame_extra }; +enum { phase_bits = 5 }; +enum { phase_count = 1 << phase_bits }; +enum { delta_bits = 15 }; +enum { delta_unit = 1 << delta_bits }; +enum { frac_bits = time_bits - pre_shift }; + +/* We could eliminate avail and encode whole samples in offset, but that would +limit the total buffered samples to blip_max_frame. That could only be +increased by decreasing time_bits, which would reduce resample ratio accuracy. +*/ + +/** Sample buffer that resamples to output rate and accumulates samples +until they're read out */ +struct blip_t +{ + fixed_t factor; + fixed_t offset; + int avail; + int size; + int integrator; +}; + +typedef int buf_t; + +/* probably not totally portable */ +#define SAMPLES( buf ) ((buf_t*) ((buf) + 1)) + +/* Arithmetic (sign-preserving) right shift */ +#define ARITH_SHIFT( n, shift ) \ + ((n) >> (shift)) + +enum { max_sample = +32767 }; +enum { min_sample = -32768 }; + +#define CLAMP( n ) \ + {\ + if ( (short) n != n )\ + n = ARITH_SHIFT( n, 16 ) ^ max_sample;\ + } + +static void check_assumptions( void ) +{ + int n; + + #if INT_MAX < 0x7FFFFFFF || UINT_MAX < 0xFFFFFFFF + #error "int must be at least 32 bits" + #endif + + assert( (-3 >> 1) == -2 ); /* right shift must preserve sign */ + + n = max_sample * 2; + CLAMP( n ); + assert( n == max_sample ); + + n = min_sample * 2; + CLAMP( n ); + assert( n == min_sample ); + + assert( blip_max_ratio <= time_unit ); + assert( blip_max_frame <= (fixed_t) -1 >> time_bits ); +} + +blip_t* blip_new( int size ) +{ + blip_t* m; + assert( size >= 0 ); + + m = (blip_t*) malloc( sizeof *m + (size + buf_extra) * sizeof (buf_t) ); + if ( m ) + { + m->factor = time_unit / blip_max_ratio; + m->size = size; + blip_clear( m ); + check_assumptions(); + } + return m; +} + +void blip_delete( blip_t* m ) +{ + if ( m != NULL ) + { + /* Clear fields in case user tries to use after freeing */ + memset( m, 0, sizeof *m ); + free( m ); + } +} + +void blip_set_rates( blip_t* m, double clock_rate, double sample_rate ) +{ + double factor = time_unit * sample_rate / clock_rate; + m->factor = (fixed_t) factor; + + /* Fails if clock_rate exceeds maximum, relative to sample_rate */ + assert( 0 <= factor - m->factor && factor - m->factor < 1 ); + + /* Avoid requiring math.h. Equivalent to + m->factor = (int) ceil( factor ) */ + if ( m->factor < factor ) + m->factor++; + + /* At this point, factor is most likely rounded up, but could still + have been rounded down in the floating-point calculation. */ +} + +void blip_clear( blip_t* m ) +{ + /* We could set offset to 0, factor/2, or factor-1. 0 is suitable if + factor is rounded up. factor-1 is suitable if factor is rounded down. + Since we don't know rounding direction, factor/2 accommodates either, + with the slight loss of showing an error in half the time. Since for + a 64-bit factor this is years, the halving isn't a problem. */ + + m->offset = m->factor / 2; + m->avail = 0; + m->integrator = 0; + memset( SAMPLES( m ), 0, (m->size + buf_extra) * sizeof (buf_t) ); +} + +int blip_clocks_needed( const blip_t* m, int samples ) +{ + fixed_t needed; + + /* Fails if buffer can't hold that many more samples */ + assert( samples >= 0 && m->avail + samples <= m->size ); + + needed = (fixed_t) samples * time_unit; + if ( needed < m->offset ) + return 0; + + return (needed - m->offset + m->factor - 1) / m->factor; +} + +void blip_end_frame( blip_t* m, unsigned t ) +{ + fixed_t off = t * m->factor + m->offset; + m->avail += off >> time_bits; + m->offset = off & (time_unit - 1); + + /* Fails if buffer size was exceeded */ + assert( m->avail <= m->size ); +} + +int blip_samples_avail( const blip_t* m ) +{ + return m->avail; +} + +static void remove_samples( blip_t* m, int count ) +{ + buf_t* buf = SAMPLES( m ); + int remain = m->avail + buf_extra - count; + m->avail -= count; + + memmove( &buf [0], &buf [count], remain * sizeof buf [0] ); + memset( &buf [remain], 0, count * sizeof buf [0] ); +} + +int blip_read_samples( blip_t* m, short out [], int count, int stereo ) +{ + assert( count >= 0 ); + + if ( count > m->avail ) + count = m->avail; + + if ( count ) + { + int const step = stereo ? 2 : 1; + buf_t const* in = SAMPLES( m ); + buf_t const* end = in + count; + int sum = m->integrator; + do + { + /* Eliminate fraction */ + int s = ARITH_SHIFT( sum, delta_bits ); + + sum += *in++; + + CLAMP( s ); + + *out = s; + out += step; + + /* High-pass filter */ + sum -= s << (delta_bits - bass_shift); + } + while ( in != end ); + m->integrator = sum; + + remove_samples( m, count ); + } + + return count; +} + +/* Things that didn't help performance on x86: + __attribute__((aligned(128))) + #define short int + restrict +*/ + +/* Sinc_Generator( 0.9, 0.55, 4.5 ) */ +static short const bl_step [phase_count + 1] [half_width] = +{ +{ 43, -115, 350, -488, 1136, -914, 5861,21022}, +{ 44, -118, 348, -473, 1076, -799, 5274,21001}, +{ 45, -121, 344, -454, 1011, -677, 4706,20936}, +{ 46, -122, 336, -431, 942, -549, 4156,20829}, +{ 47, -123, 327, -404, 868, -418, 3629,20679}, +{ 47, -122, 316, -375, 792, -285, 3124,20488}, +{ 47, -120, 303, -344, 714, -151, 2644,20256}, +{ 46, -117, 289, -310, 634, -17, 2188,19985}, +{ 46, -114, 273, -275, 553, 117, 1758,19675}, +{ 44, -108, 255, -237, 471, 247, 1356,19327}, +{ 43, -103, 237, -199, 390, 373, 981,18944}, +{ 42, -98, 218, -160, 310, 495, 633,18527}, +{ 40, -91, 198, -121, 231, 611, 314,18078}, +{ 38, -84, 178, -81, 153, 722, 22,17599}, +{ 36, -76, 157, -43, 80, 824, -241,17092}, +{ 34, -68, 135, -3, 8, 919, -476,16558}, +{ 32, -61, 115, 34, -60, 1006, -683,16001}, +{ 29, -52, 94, 70, -123, 1083, -862,15422}, +{ 27, -44, 73, 106, -184, 1152,-1015,14824}, +{ 25, -36, 53, 139, -239, 1211,-1142,14210}, +{ 22, -27, 34, 170, -290, 1261,-1244,13582}, +{ 20, -20, 16, 199, -335, 1301,-1322,12942}, +{ 18, -12, -3, 226, -375, 1331,-1376,12293}, +{ 15, -4, -19, 250, -410, 1351,-1408,11638}, +{ 13, 3, -35, 272, -439, 1361,-1419,10979}, +{ 11, 9, -49, 292, -464, 1362,-1410,10319}, +{ 9, 16, -63, 309, -483, 1354,-1383, 9660}, +{ 7, 22, -75, 322, -496, 1337,-1339, 9005}, +{ 6, 26, -85, 333, -504, 1312,-1280, 8355}, +{ 4, 31, -94, 341, -507, 1278,-1205, 7713}, +{ 3, 35, -102, 347, -506, 1238,-1119, 7082}, +{ 1, 40, -110, 350, -499, 1190,-1021, 6464}, +{ 0, 43, -115, 350, -488, 1136, -914, 5861} +}; + +/* Shifting by pre_shift allows calculation using unsigned int rather than +possibly-wider fixed_t. On 32-bit platforms, this is likely more efficient. +And by having pre_shift 32, a 32-bit platform can easily do the shift by +simply ignoring the low half. */ + +void blip_add_delta( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int const phase_shift = frac_bits - phase_bits; + int phase = fixed >> phase_shift & (phase_count - 1); + short const* in = bl_step [phase]; + short const* rev = bl_step [phase_count - phase]; + + int interp = fixed >> (phase_shift - delta_bits) & (delta_unit - 1); + int delta2 = (delta * interp) >> delta_bits; + delta -= delta2; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [0] += in[0]*delta + in[half_width+0]*delta2; + out [1] += in[1]*delta + in[half_width+1]*delta2; + out [2] += in[2]*delta + in[half_width+2]*delta2; + out [3] += in[3]*delta + in[half_width+3]*delta2; + out [4] += in[4]*delta + in[half_width+4]*delta2; + out [5] += in[5]*delta + in[half_width+5]*delta2; + out [6] += in[6]*delta + in[half_width+6]*delta2; + out [7] += in[7]*delta + in[half_width+7]*delta2; + + in = rev; + out [ 8] += in[7]*delta + in[7-half_width]*delta2; + out [ 9] += in[6]*delta + in[6-half_width]*delta2; + out [10] += in[5]*delta + in[5-half_width]*delta2; + out [11] += in[4]*delta + in[4-half_width]*delta2; + out [12] += in[3]*delta + in[3-half_width]*delta2; + out [13] += in[2]*delta + in[2-half_width]*delta2; + out [14] += in[1]*delta + in[1-half_width]*delta2; + out [15] += in[0]*delta + in[0-half_width]*delta2; +} + +void blip_add_delta_fast( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int interp = fixed >> (frac_bits - delta_bits) & (delta_unit - 1); + int delta2 = delta * interp; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [7] += delta * delta_unit - delta2; + out [8] += delta2; +} diff --git a/waterbox/sameboy/blip_buf/blip_buf.h b/waterbox/sameboy/blip_buf/blip_buf.h new file mode 100644 index 0000000000..e9a5d4cc3b --- /dev/null +++ b/waterbox/sameboy/blip_buf/blip_buf.h @@ -0,0 +1,72 @@ +/** \file +Sample buffer that resamples from input clock rate to output sample rate */ + +/* blip_buf 1.1.0 */ +#ifndef BLIP_BUF_H +#define BLIP_BUF_H + +#ifdef __cplusplus + extern "C" { +#endif + +/** First parameter of most functions is blip_t*, or const blip_t* if nothing +is changed. */ +typedef struct blip_t blip_t; + +/** Creates new buffer that can hold at most sample_count samples. Sets rates +so that there are blip_max_ratio clocks per sample. Returns pointer to new +buffer, or NULL if insufficient memory. */ +blip_t* blip_new( int sample_count ); + +/** Sets approximate input clock rate and output sample rate. For every +clock_rate input clocks, approximately sample_rate samples are generated. */ +void blip_set_rates( blip_t*, double clock_rate, double sample_rate ); + +enum { /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, +clock_rate must not be greater than sample_rate*blip_max_ratio. */ +blip_max_ratio = 1 << 20 }; + +/** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ +void blip_clear( blip_t* ); + +/** Adds positive/negative delta into buffer at specified clock time. */ +void blip_add_delta( blip_t*, unsigned int clock_time, int delta ); + +/** Same as blip_add_delta(), but uses faster, lower-quality synthesis. */ +void blip_add_delta_fast( blip_t*, unsigned int clock_time, int delta ); + +/** Length of time frame, in clocks, needed to make sample_count additional +samples available. */ +int blip_clocks_needed( const blip_t*, int sample_count ); + +enum { /** Maximum number of samples that can be generated from one time frame. */ +blip_max_frame = 4000 }; + +/** Makes input clocks before clock_duration available for reading as output +samples. Also begins new time frame at clock_duration, so that clock time 0 in +the new time frame specifies the same clock as clock_duration in the old time +frame specified. Deltas can have been added slightly past clock_duration (up to +however many clocks there are in two output samples). */ +void blip_end_frame( blip_t*, unsigned int clock_duration ); + +/** Number of buffered samples available for reading. */ +int blip_samples_avail( const blip_t* ); + +/** Reads and removes at most 'count' samples and writes them to 'out'. If +'stereo' is true, writes output to every other element of 'out', allowing easy +interleaving of two buffers into a stereo sample stream. Outputs 16-bit signed +samples. Returns number of samples actually read. */ +int blip_read_samples( blip_t*, short out [], int count, int stereo ); + +/** Frees buffer. No effect if NULL is passed. */ +void blip_delete( blip_t* ); + + +/* Deprecated */ +typedef blip_t blip_buffer_t; + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/waterbox/sameboy/gb.c b/waterbox/sameboy/gb.c index 475d48f881..986a5623c6 100644 --- a/waterbox/sameboy/gb.c +++ b/waterbox/sameboy/gb.c @@ -92,7 +92,6 @@ void GB_init(GB_gameboy_t *gb) 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); } @@ -107,7 +106,6 @@ void GB_init_cgb(GB_gameboy_t *gb) 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); } @@ -127,9 +125,6 @@ void GB_free(GB_gameboy_t *gb) if (gb->rom) { free(gb->rom); } - if (gb->audio_buffer) { - free(gb->audio_buffer); - } if (gb->breakpoints) { free(gb->breakpoints); } @@ -262,15 +257,18 @@ uint64_t GB_run_cycles(GB_gameboy_t *gb, uint32_t cycles) while (gb->cycles_since_epoch < target) { GB_run(gb); if (gb->vblank_just_occured) { - // TODO: fix these up GB_update_joyp(gb); GB_rtc_run(gb); - break; } } return gb->cycles_since_epoch - start; } +uint64_t GB_epoch(GB_gameboy_t *gb) +{ + return gb->cycles_since_epoch; +} + void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) { gb->screen = output; @@ -340,6 +338,11 @@ void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback) gb->rumble_callback = callback; } +void GB_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback) +{ + gb->sample_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; @@ -370,17 +373,6 @@ void GB_serial_set_data(GB_gameboy_t *gb, uint8_t 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; diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h index a80b89e5ec..5eb644d20a 100644 --- a/waterbox/sameboy/gb.h +++ b/waterbox/sameboy/gb.h @@ -171,6 +171,7 @@ typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_si 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 void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t sample, uint64_t clock); typedef struct { bool state; @@ -379,26 +380,11 @@ struct GB_gameboy_internal_s { /* I/O */ uint32_t *screen; - GB_sample_t *audio_buffer; bool keys[GB_KEY_MAX]; /* Timing */ uint64_t cycles_since_epoch; - /* 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; @@ -412,6 +398,7 @@ struct GB_gameboy_internal_s { 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; + GB_sample_callback_t sample_callback; /* IR */ long cycles_since_ir_change; @@ -466,6 +453,7 @@ 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); uint64_t GB_run_cycles(GB_gameboy_t *gb, uint32_t cycles); +uint64_t GB_epoch(GB_gameboy_t *gb); typedef enum { GB_DIRECT_ACCESS_ROM, @@ -510,6 +498,7 @@ 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); +void GB_set_sample_callback(GB_gameboy_t *gb, GB_sample_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); diff --git a/waterbox/sameboy/timing.c b/waterbox/sameboy/timing.c index 6e6c81e752..e67992c167 100644 --- a/waterbox/sameboy/timing.c +++ b/waterbox/sameboy/timing.c @@ -68,8 +68,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) // 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; From 1f891581798f016a402a8347875b15d0ff199ab1 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Tue, 18 Jul 2017 18:19:51 -0400 Subject: [PATCH 05/13] sameboy: input --- .../Consoles/Nintendo/Gameboy/LibSameboy.cs | 21 +++++++ .../Consoles/Nintendo/Gameboy/Sameboy.cs | 56 +++++++++++++++++++ waterbox/sameboy/bizhawk.cpp | 5 +- waterbox/sameboy/gb.c | 6 +- waterbox/sameboy/gb.h | 2 +- waterbox/sameboy/joypad.c | 20 ++----- waterbox/sameboy/joypad.h | 14 +---- 7 files changed, 90 insertions(+), 34 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs index 5c13e48d23..c9ba9cc16b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs @@ -3,6 +3,7 @@ using BizHawk.Emulation.Cores.Waterbox; using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -10,6 +11,26 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { public abstract class LibSameboy : LibWaterboxCore { + [Flags] + public enum Buttons : uint + { + A = 0x01, + B = 0x02, + SELECT = 0x04, + START = 0x08, + RIGHT = 0x10, + LEFT = 0x20, + UP = 0x40, + DOWN = 0x80 + } + + [StructLayout(LayoutKind.Sequential)] + public new class FrameInfo : LibWaterboxCore.FrameInfo + { + public long Time; + public Buttons Keys; + } + [BizImport(CC)] public abstract bool Init(bool cgb); } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index 8d93b7521f..a052576634 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -32,6 +32,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy private LibSameboy _core; private bool _cgb; + private bool _sgb; [CoreConstructor("GB")] public Sameboy(CoreComm comm, byte[] rom) @@ -76,10 +77,65 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy PostInit(); } + #region Controller + + private static readonly ControllerDefinition _gbDefinition; + private static readonly ControllerDefinition _sgbDefinition; + public override ControllerDefinition ControllerDefinition => _sgb ? _sgbDefinition : _gbDefinition; + + private static ControllerDefinition CreateControllerDefinition(int p) + { + var ret = new ControllerDefinition { Name = "Gameboy Controller" }; + for (int i = 0; i < p; i++) + { + ret.BoolButtons.AddRange( + new[] { "Up", "Down", "Left", "Right", "A", "B", "Select", "Start" } + .Select(s => $"P{i + 1} {s}")); + } + return ret; + } + + static Sameboy() + { + _gbDefinition = CreateControllerDefinition(1); + _sgbDefinition = CreateControllerDefinition(4); + } + + private LibSameboy.Buttons GetButtons(IController c) + { + LibSameboy.Buttons b = 0; + for (int i = _sgb ? 4 : 1; i > 0; i--) + { + if (c.IsPressed($"P{i} Up")) + b |= LibSameboy.Buttons.UP; + if (c.IsPressed($"P{i} Down")) + b |= LibSameboy.Buttons.DOWN; + if (c.IsPressed($"P{i} Left")) + b |= LibSameboy.Buttons.LEFT; + if (c.IsPressed($"P{i} Right")) + b |= LibSameboy.Buttons.RIGHT; + if (c.IsPressed($"P{i} A")) + b |= LibSameboy.Buttons.A; + if (c.IsPressed($"P{i} B")) + b |= LibSameboy.Buttons.B; + if (c.IsPressed($"P{i} Select")) + b |= LibSameboy.Buttons.SELECT; + if (c.IsPressed($"P{i} Start")) + b |= LibSameboy.Buttons.START; + if (i != 1) + b = (LibSameboy.Buttons)((uint)b << 8); + } + return b; + } + + #endregion + protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound) { return new LibSameboy.FrameInfo { + Time = 0, + Keys = GetButtons(controller) }; } diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp index 187c668a5b..c704aaf13a 100644 --- a/waterbox/sameboy/bizhawk.cpp +++ b/waterbox/sameboy/bizhawk.cpp @@ -91,6 +91,8 @@ ECL_EXPORT bool Init(bool cgb) struct MyFrameInfo : public FrameInfo { + int64_t Time; + uint32_t Keys; }; static int FrameOverflow; @@ -98,8 +100,7 @@ static int FrameOverflow; ECL_EXPORT void FrameAdvance(MyFrameInfo &f) { GB_set_pixels_output(&GB, f.VideoBuffer); - for (int i = 0; i < (int)GB_KEY_MAX; i++) - GB_set_key_state(&GB, (GB_key_t)i, false); + GB_set_key_state(&GB, f.Keys & 0xff); sound_start_clock = GB_epoch(&GB); diff --git a/waterbox/sameboy/gb.c b/waterbox/sameboy/gb.c index 986a5623c6..0f6b298b07 100644 --- a/waterbox/sameboy/gb.c +++ b/waterbox/sameboy/gb.c @@ -252,14 +252,12 @@ void GB_run(GB_gameboy_t *gb) uint64_t GB_run_cycles(GB_gameboy_t *gb, uint32_t cycles) { + GB_update_joyp(gb); + GB_rtc_run(gb); uint64_t start = gb->cycles_since_epoch; uint64_t target = start + cycles; while (gb->cycles_since_epoch < target) { GB_run(gb); - if (gb->vblank_just_occured) { - GB_update_joyp(gb); - GB_rtc_run(gb); - } } return gb->cycles_since_epoch - start; } diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h index 5eb644d20a..d1fb323d3e 100644 --- a/waterbox/sameboy/gb.h +++ b/waterbox/sameboy/gb.h @@ -380,7 +380,7 @@ struct GB_gameboy_internal_s { /* I/O */ uint32_t *screen; - bool keys[GB_KEY_MAX]; + int keys; /* Timing */ uint64_t cycles_since_epoch; diff --git a/waterbox/sameboy/joypad.c b/waterbox/sameboy/joypad.c index 21e6d666b0..0cad7ab084 100644 --- a/waterbox/sameboy/joypad.c +++ b/waterbox/sameboy/joypad.c @@ -9,7 +9,7 @@ void GB_update_joyp(GB_gameboy_t *gb) /* 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; + key_selection = gb->io_registers[GB_IO_JOYP] >> 4 & 3; gb->io_registers[GB_IO_JOYP] &= 0xF0; switch (key_selection) { case 3: @@ -19,24 +19,17 @@ void GB_update_joyp(GB_gameboy_t *gb) case 2: /* Direction keys */ - 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 >> 4 & 0xf; break; case 1: /* Other keys */ - for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i + 4]) << i; - } + gb->io_registers[GB_IO_JOYP] |= ~gb->keys & 0xf; 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; - } + gb->io_registers[GB_IO_JOYP] |= ~(gb->keys >> 4 & gb->keys) & 0xf; break; default: @@ -49,8 +42,7 @@ void GB_update_joyp(GB_gameboy_t *gb) 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) +void GB_set_key_state(GB_gameboy_t *gb, int keys) { - assert(index >= 0 && index < GB_KEY_MAX); - gb->keys[index] = pressed; + gb->keys = keys; } diff --git a/waterbox/sameboy/joypad.h b/waterbox/sameboy/joypad.h index def4b9ac82..f7ffdfdec4 100644 --- a/waterbox/sameboy/joypad.h +++ b/waterbox/sameboy/joypad.h @@ -2,19 +2,7 @@ #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); +void GB_set_key_state(GB_gameboy_t *gb, int keys); #ifdef GB_INTERNAL void GB_update_joyp(GB_gameboy_t *gb); From bfd7337a32911477033ff49f22b1e9e7b230e51c Mon Sep 17 00:00:00 2001 From: nattthebear Date: Tue, 18 Jul 2017 19:57:53 -0400 Subject: [PATCH 06/13] sameboy: SGB support. Mostly accomplished through the power of copy+paste, since we plan on dropping pizza boy --- BizHawk.Client.Common/RomLoader.cs | 2 +- .../Consoles/Nintendo/Gameboy/LibSameboy.cs | 2 +- .../Consoles/Nintendo/Gameboy/Sameboy.cs | 29 +- waterbox/sameboy/bizhawk.cpp | 75 +- waterbox/sameboy/gb.c | 14 + waterbox/sameboy/gb.h | 2 + waterbox/sameboy/memory.c | 12 + waterbox/sameboy/sgb.c | 1001 ++++++++++++++ waterbox/sameboy/sgb.h | 36 + waterbox/sameboy/snes_spc/SNES_SPC.cpp | 564 ++++++++ waterbox/sameboy/snes_spc/SNES_SPC.h | 284 ++++ waterbox/sameboy/snes_spc/SNES_SPC_misc.cpp | 380 +++++ waterbox/sameboy/snes_spc/SNES_SPC_state.cpp | 129 ++ waterbox/sameboy/snes_spc/SPC_CPU.h | 1220 +++++++++++++++++ waterbox/sameboy/snes_spc/SPC_DSP.cpp | 1018 ++++++++++++++ waterbox/sameboy/snes_spc/SPC_DSP.h | 304 ++++ waterbox/sameboy/snes_spc/SPC_Filter.cpp | 68 + waterbox/sameboy/snes_spc/SPC_Filter.h | 47 + waterbox/sameboy/snes_spc/blargg_common.h | 186 +++ waterbox/sameboy/snes_spc/blargg_config.h | 24 + waterbox/sameboy/snes_spc/blargg_endian.h | 185 +++ waterbox/sameboy/snes_spc/blargg_source.h | 100 ++ waterbox/sameboy/snes_spc/changes.txt | 107 ++ waterbox/sameboy/snes_spc/dsp.cpp | 48 + waterbox/sameboy/snes_spc/dsp.h | 83 ++ waterbox/sameboy/snes_spc/license.txt | 504 +++++++ waterbox/sameboy/snes_spc/readme.txt | 86 ++ waterbox/sameboy/snes_spc/snes_spc.txt | 318 +++++ waterbox/sameboy/snes_spc/spc.cpp | 74 + waterbox/sameboy/snes_spc/spc.h | 149 ++ 30 files changed, 7030 insertions(+), 21 deletions(-) create mode 100644 waterbox/sameboy/sgb.c create mode 100644 waterbox/sameboy/sgb.h create mode 100644 waterbox/sameboy/snes_spc/SNES_SPC.cpp create mode 100644 waterbox/sameboy/snes_spc/SNES_SPC.h create mode 100644 waterbox/sameboy/snes_spc/SNES_SPC_misc.cpp create mode 100644 waterbox/sameboy/snes_spc/SNES_SPC_state.cpp create mode 100644 waterbox/sameboy/snes_spc/SPC_CPU.h create mode 100644 waterbox/sameboy/snes_spc/SPC_DSP.cpp create mode 100644 waterbox/sameboy/snes_spc/SPC_DSP.h create mode 100644 waterbox/sameboy/snes_spc/SPC_Filter.cpp create mode 100644 waterbox/sameboy/snes_spc/SPC_Filter.h create mode 100644 waterbox/sameboy/snes_spc/blargg_common.h create mode 100644 waterbox/sameboy/snes_spc/blargg_config.h create mode 100644 waterbox/sameboy/snes_spc/blargg_endian.h create mode 100644 waterbox/sameboy/snes_spc/blargg_source.h create mode 100644 waterbox/sameboy/snes_spc/changes.txt create mode 100644 waterbox/sameboy/snes_spc/dsp.cpp create mode 100644 waterbox/sameboy/snes_spc/dsp.h create mode 100644 waterbox/sameboy/snes_spc/license.txt create mode 100644 waterbox/sameboy/snes_spc/readme.txt create mode 100644 waterbox/sameboy/snes_spc/snes_spc.txt create mode 100644 waterbox/sameboy/snes_spc/spc.cpp create mode 100644 waterbox/sameboy/snes_spc/spc.h diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 4af0355a29..810c8a303b 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -935,7 +935,7 @@ namespace BizHawk.Client.Common } else { - core = CoreInventory.Instance["SGB", "Pizza Boy"]; + core = CoreInventory.Instance["SGB", "SameBoy"]; } } break; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs index c9ba9cc16b..4801cd7d45 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs @@ -32,6 +32,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy } [BizImport(CC)] - public abstract bool Init(bool cgb); + public abstract bool Init(bool cgb, byte[] spc, int spclen); } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index a052576634..a82a4e2e21 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -34,18 +34,27 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy private bool _cgb; private bool _sgb; + [CoreConstructor("SGB")] + public Sameboy(byte[] rom, CoreComm comm) + : this(rom, comm, true) + { } + [CoreConstructor("GB")] public Sameboy(CoreComm comm, byte[] rom) + : this(rom, comm, false) + { } + + public Sameboy(byte[] rom, CoreComm comm, bool sgb) : base(comm, new Configuration { - DefaultWidth = 160, - DefaultHeight = 144, - MaxWidth = 256, - MaxHeight = 224, + DefaultWidth = sgb ? 256 : 160, + DefaultHeight = sgb ? 224 : 144, + MaxWidth = sgb ? 256 : 160, + MaxHeight = sgb ? 224 : 144, MaxSamples = 1024, - DefaultFpsNumerator = TICKSPERSECOND, + DefaultFpsNumerator = sgb ? TICKSPERSECOND_SGB : TICKSPERSECOND, DefaultFpsDenominator = TICKSPERFRAME, - SystemId = "GB" + SystemId = sgb ? "SGB" : "GB" }) { _core = PreInit(new PeRunnerOptions @@ -58,15 +67,19 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy MmapHeapSizeKB = 34 * 1024 }); - _cgb = (rom[0x143] & 0xc0) == 0xc0; + _cgb = (rom[0x143] & 0xc0) == 0xc0 && !sgb; + _sgb = sgb; Console.WriteLine("Automaticly detected CGB to " + _cgb); var bios = Util.DecompressGzipFile(new MemoryStream(_cgb ? Resources.SameboyCgbBoot : Resources.SameboyDmgBoot)); // var bios = comm.CoreFileProvider.GetFirmware(_cgb ? "GBC" : "GB", "World", true); + var spc = sgb + ? Util.DecompressGzipFile(new MemoryStream(Resources.SgbCartPresent_SPC)) + : null; _exe.AddReadonlyFile(rom, "game.rom"); _exe.AddReadonlyFile(bios, "boot.rom"); - if (!_core.Init(_cgb)) + if (!_core.Init(_cgb, spc, spc?.Length ?? 0)) { throw new InvalidOperationException("Core rejected the rom!"); } diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp index c704aaf13a..dbcfdcb0e9 100644 --- a/waterbox/sameboy/bizhawk.cpp +++ b/waterbox/sameboy/bizhawk.cpp @@ -9,12 +9,25 @@ extern "C" { #include "gb.h" #include "joypad.h" #include "apu.h" +#include "sgb.h" } static GB_gameboy_t GB; +static uint32_t GBPixels[160 * 144]; +static uint32_t* CurrentFramebuffer; +static bool sgb; static void VBlankCallback(GB_gameboy_t *gb) { + if (sgb) + { + sgb_take_frame(GBPixels); + sgb_render_frame(CurrentFramebuffer); + } + else + { + memcpy(CurrentFramebuffer, GBPixels, sizeof(GBPixels)); + } } static void LogCallback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) @@ -50,6 +63,7 @@ const int SOUND_RATE_GB = 2097152; const int SOUND_RATE_SGB = 2147727; static uint64_t sound_start_clock; static GB_sample_t sample_gb; +static GB_sample_t sample_sgb; static void SampleCallback(GB_gameboy_t *gb, GB_sample_t sample, uint64_t clock) { @@ -61,19 +75,42 @@ static void SampleCallback(GB_gameboy_t *gb, GB_sample_t sample, uint64_t clock) blip_add_delta(rightblip, clock - sound_start_clock, r); sample_gb = sample; } - -ECL_EXPORT bool Init(bool cgb) +static void SgbSampleCallback(int16_t sl, int16_t sr, uint64_t clock) { - if (cgb) + int l = sl - sample_sgb.left; + int r = sr - sample_sgb.right; + if (l) + blip_add_delta(leftblip, clock - sound_start_clock, l); + if (r) + blip_add_delta(rightblip, clock - sound_start_clock, r); + sample_sgb.left = sl; + sample_sgb.right = sr; +} + +ECL_EXPORT bool Init(bool cgb, const uint8_t* spc, int spclen) +{ + if (spc) + { + GB_init_sgb(&GB); + if (!sgb_init(spc, spclen)) + return false; + sgb = true; + } + else if (cgb) + { GB_init_cgb(&GB); + } else + { GB_init(&GB); + } + if (GB_load_boot_rom(&GB, "boot.rom") != 0) return false; - if (GB_load_rom(&GB, "game.rom") != 0) return false; + GB_set_pixels_output(&GB, GBPixels); GB_set_vblank_callback(&GB, VBlankCallback); GB_set_log_callback(&GB, LogCallback); GB_set_rgb_encode_callback(&GB, RgbEncodeCallback); @@ -83,8 +120,8 @@ ECL_EXPORT bool Init(bool cgb) leftblip = blip_new(1024); rightblip = blip_new(1024); - blip_set_rates(leftblip, SOUND_RATE_GB, 44100); - blip_set_rates(rightblip, SOUND_RATE_GB, 44100); + blip_set_rates(leftblip, sgb ? SOUND_RATE_SGB : SOUND_RATE_GB, 44100); + blip_set_rates(rightblip, sgb ? SOUND_RATE_SGB : SOUND_RATE_GB, 44100); return true; } @@ -99,20 +136,36 @@ static int FrameOverflow; ECL_EXPORT void FrameAdvance(MyFrameInfo &f) { - GB_set_pixels_output(&GB, f.VideoBuffer); - GB_set_key_state(&GB, f.Keys & 0xff); - + if (sgb) + { + sgb_set_controller_data((uint8_t*)&f.Keys); + } + else + { + GB_set_key_state(&GB, f.Keys & 0xff); + } sound_start_clock = GB_epoch(&GB); + CurrentFramebuffer = f.VideoBuffer; uint32_t target = 35112 - FrameOverflow; f.Cycles = GB_run_cycles(&GB, target); FrameOverflow = f.Cycles - target; + if (sgb) + { + f.Width = 256; + f.Height = 224; + sgb_render_audio(GB_epoch(&GB), SgbSampleCallback); + } + else + { + f.Width = 160; + f.Height = 144; + } blip_end_frame(leftblip, f.Cycles); blip_end_frame(rightblip, f.Cycles); f.Samples = blip_read_samples(leftblip, f.SoundBuffer, 2048, 1); blip_read_samples(rightblip, f.SoundBuffer + 1, 2048, 1); - f.Width = 160; - f.Height = 144; + CurrentFramebuffer = NULL; } static void SetMemoryArea(MemoryArea *m, GB_direct_access_t access, const char *name, int32_t flags) diff --git a/waterbox/sameboy/gb.c b/waterbox/sameboy/gb.c index 0f6b298b07..814a78811b 100644 --- a/waterbox/sameboy/gb.c +++ b/waterbox/sameboy/gb.c @@ -96,6 +96,20 @@ void GB_init(GB_gameboy_t *gb) GB_reset(gb); } +void GB_init_sgb(GB_gameboy_t *gb) +{ + memset(gb, 0, sizeof(*gb)); + gb->ram = malloc(gb->ram_size = 0x2000); + gb->vram = malloc(gb->vram_size = 0x2000); + gb->is_sgb = 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_reset(gb); +} + void GB_init_cgb(GB_gameboy_t *gb) { memset(gb, 0, sizeof(*gb)); diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h index d1fb323d3e..e67a89244d 100644 --- a/waterbox/sameboy/gb.h +++ b/waterbox/sameboy/gb.h @@ -234,6 +234,7 @@ struct GB_gameboy_internal_s { uint8_t cgb_ram_bank; /* CPU and General Hardware Flags*/ + bool is_sgb; bool cgb_mode; bool is_cgb; bool cgb_double_speed; @@ -445,6 +446,7 @@ __attribute__((__format__ (__printf__, fmtarg, firstvararg))) #endif void GB_init(GB_gameboy_t *gb); +void GB_init_sgb(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); diff --git a/waterbox/sameboy/memory.c b/waterbox/sameboy/memory.c index 3e34895c97..7ae5368d70 100644 --- a/waterbox/sameboy/memory.c +++ b/waterbox/sameboy/memory.c @@ -1,6 +1,7 @@ #include #include #include "gb.h" +#include "sgb.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); @@ -134,6 +135,11 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFF80) { + if (addr == 0xff00 && gb->is_sgb) + { + return sgb_read_ff00(gb->cycles_since_epoch); + } + switch (addr & 0xFF) { case GB_IO_IF: return gb->io_registers[GB_IO_IF] | 0xE0; @@ -406,6 +412,12 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } if (addr < 0xFF80) { + if (addr == 0xff00 && gb->is_sgb) + { + sgb_write_ff00(value, gb->cycles_since_epoch); + return; + } + /* Hardware registers */ switch (addr & 0xFF) { diff --git a/waterbox/sameboy/sgb.c b/waterbox/sameboy/sgb.c new file mode 100644 index 0000000000..896cc68c73 --- /dev/null +++ b/waterbox/sameboy/sgb.c @@ -0,0 +1,1001 @@ +#include "sgb.h" +#include +#include +#include +#include "snes_spc/spc.h" + +#define utils_log printf + +const uint8_t iplrom[64] = { + /*ffc0*/ 0xcd, 0xef, //mov x,#$ef + /*ffc2*/ 0xbd, //mov sp,x + /*ffc3*/ 0xe8, 0x00, //mov a,#$00 + /*ffc5*/ 0xc6, //mov (x),a + /*ffc6*/ 0x1d, //dec x + /*ffc7*/ 0xd0, 0xfc, //bne $ffc5 + /*ffc9*/ 0x8f, 0xaa, 0xf4, //mov $f4,#$aa + /*ffcc*/ 0x8f, 0xbb, 0xf5, //mov $f5,#$bb + /*ffcf*/ 0x78, 0xcc, 0xf4, //cmp $f4,#$cc + /*ffd2*/ 0xd0, 0xfb, //bne $ffcf + /*ffd4*/ 0x2f, 0x19, //bra $ffef + /*ffd6*/ 0xeb, 0xf4, //mov y,$f4 + /*ffd8*/ 0xd0, 0xfc, //bne $ffd6 + /*ffda*/ 0x7e, 0xf4, //cmp y,$f4 + /*ffdc*/ 0xd0, 0x0b, //bne $ffe9 + /*ffde*/ 0xe4, 0xf5, //mov a,$f5 + /*ffe0*/ 0xcb, 0xf4, //mov $f4,y + /*ffe2*/ 0xd7, 0x00, //mov ($00)+y,a + /*ffe4*/ 0xfc, //inc y + /*ffe5*/ 0xd0, 0xf3, //bne $ffda + /*ffe7*/ 0xab, 0x01, //inc $01 + /*ffe9*/ 0x10, 0xef, //bpl $ffda + /*ffeb*/ 0x7e, 0xf4, //cmp y,$f4 + /*ffed*/ 0x10, 0xeb, //bpl $ffda + /*ffef*/ 0xba, 0xf6, //movw ya,$f6 + /*fff1*/ 0xda, 0x00, //movw $00,ya + /*fff3*/ 0xba, 0xf4, //movw ya,$f4 + /*fff5*/ 0xc4, 0xf4, //mov $f4,a + /*fff7*/ 0xdd, //mov a,y + /*fff8*/ 0x5d, //mov x,a + /*fff9*/ 0xd0, 0xdb, //bne $ffd6 + /*fffb*/ 0x1f, 0x00, 0x00, //jmp ($0000+x) + /*fffe*/ 0xc0, 0xff //reset vector location ($ffc0) +}; + +// the "reference clock" is tied to the GB cpu. 35112 of these should equal one GB LCD frame. +// it is always increasing and never resets/rebases + +const int refclocks_per_spc_sample = 67; // ~32055hz + +typedef struct +{ + // writes to FF00 + uint64_t last_write_time; // last write time relative to reference clock + uint8_t last_write_value; + + // recv packets + uint8_t read_index; // 0-127, index of the next bit read. if 255, not currently reading + uint8_t packet[16]; // a packet in the process of being formed + + uint8_t command[16 * 7]; // a command in the process of being formed + uint8_t expected_packets; // total number of packets expected for a command + uint8_t next_packet; // index of the next packet to be read + + // joypad reading + uint8_t joypad_index; // index of currently reading joypad + uint8_t num_joypads; // number of currently selected joypads (MLT_REQ) + uint8_t joypad_data[4]; // data for each joypad + uint8_t joypad_has_been_read; // state for advancing joypad_index. extermely weird; logic lifted from VBA and probably wrong + + // palettes + uint32_t palette[8][16]; + uint32_t auxpalette[512][4]; + + // border + uint8_t tiles[256][64]; // tiles stored in packed form + uint16_t tilemap[32 * 32]; + + // frame data + uint8_t frame[160 * 144]; // the most recent obtained full frame + uint32_t frozenframe[256 * 224]; // the most recent saved full frame (MASK_EN) + uint8_t attr[20 * 18]; // current attr map for the GB screen + uint8_t auxattr[45][20 * 18]; // 45 attr files + + // MASK_EN + uint8_t active_mask; // true if mask is currently being used + + // audio + SNES_SPC *spc; + uint64_t frame_start; // when the current audio frame started relative to reference clock + uint32_t clock_remainder; // number of reference clocks not sent to the SPC last frame + uint8_t sound_control[4]; // TODO... + + // transfers + uint32_t waiting_transfer; +#define TRN_NONE 0 +#define TRN_SOUND 1 +#define TRN_PAL 2 +#define TRN_CHR_LOW 3 +#define TRN_CHR_HI 4 +#define TRN_PCT 5 +#define TRN_ATTR 6 + int32_t transfer_countdown; // number of frames until transfer. not entirely accurate +} sgb_t; + +static sgb_t sgb; + +static uint32_t makecol(uint16_t c) +{ + return c >> 7 & 0xf8 | c >> 12 & 0x07 | c << 6 & 0xf800 | c << 1 & 0x0700 | c << 19 & 0xf80000 | c << 14 & 0x070000 | 0xff000000; +} + +static void cmd_trn(uint32_t which) +{ + if ((sgb.command[0] & 7) == 1) + { + if (sgb.waiting_transfer == TRN_NONE) + { + sgb.waiting_transfer = which; + sgb.transfer_countdown = 4; + } + else + { + utils_log("SGB: TRN already queued!\n"); + } + } + else + { + utils_log("SGB: cmd_trn bad length\n"); + } +} + +static void cmd_pal(int a, int b) +{ + if ((sgb.command[0] & 7) == 1) + { + uint32_t c[7]; + for (int i = 0; i < 7; i++) + c[i] = makecol(sgb.command[i * 2 + 1] | sgb.command[i * 2 + 2] << 8); + sgb.palette[0][0] = c[0]; + sgb.palette[1][0] = c[0]; + sgb.palette[2][0] = c[0]; + sgb.palette[3][0] = c[0]; + sgb.palette[a][1] = c[1]; + sgb.palette[a][2] = c[2]; + sgb.palette[a][3] = c[3]; + sgb.palette[b][1] = c[4]; + sgb.palette[b][2] = c[5]; + sgb.palette[b][3] = c[6]; + } + else + { + utils_log("SGB: cmd_pal bad length\n"); + } +} + +static void cmd_pal_set(void) +{ + if ((sgb.command[0] & 7) == 1) + { + int p0 = sgb.command[1] | sgb.command[2] << 8 & 0x100; + for (int i = 0; i < 4; i++) + { + int p = sgb.command[i * 2 + 1] | sgb.command[i * 2 + 2] << 8 & 0x100; + sgb.palette[i][0] = sgb.auxpalette[p0][0]; + sgb.palette[i][1] = sgb.auxpalette[p][1]; + sgb.palette[i][2] = sgb.auxpalette[p][2]; + sgb.palette[i][3] = sgb.auxpalette[p][3]; + } + if (sgb.command[9] & 0x80) // change attribute + { + int attr = sgb.command[9] & 0x3f; + if (attr >= 45) + attr = 44; + memcpy(sgb.attr, sgb.auxattr[attr], sizeof(sgb.attr)); + } + if (sgb.command[9] & 0x40) // cancel mask + { + sgb.active_mask = 0; + } + } + else + { + utils_log("SGB: cmd_pal bad length\n"); + } +} + +static void cmd_attr_blk() +{ + int nset = sgb.command[1]; + if (nset <= 0 || nset >= 19) + { + utils_log("SGB: cmd_attr_blk bad nset\n"); + return; + } + int npacket = (nset * 6 + 16) / 16; + if ((sgb.command[0] & 7) != npacket) + { + utils_log("SGB: cmd_attr_blk bad length\n"); + return; + } + for (int i = 0; i < nset; i++) + { + int ctrl = sgb.command[i * 6 + 2] & 7; + int pals = sgb.command[i * 6 + 3]; + int x1 = sgb.command[i * 6 + 4]; + int y1 = sgb.command[i * 6 + 5]; + int x2 = sgb.command[i * 6 + 6]; + int y2 = sgb.command[i * 6 + 7]; + int inside = ctrl & 1; + int line = ctrl & 2; + int outside = ctrl & 4; + int insidepal = pals & 3; + int linepal = pals >> 2 & 3; + int outsidepal = pals >> 4 & 3; + if (ctrl == 1) + { + ctrl = 3; + linepal = insidepal; + } + else if (ctrl == 4) + { + ctrl = 6; + linepal = outsidepal; + } + uint8_t *dst = sgb.attr; + for (int y = 0; y < 18; y++) + { + for (int x = 0; x < 20; x++) + { + if (outside && (x < x1 || x > x2 || y < y1 || y > y2)) + *dst = outsidepal; + else if (inside && x > x1 && x < x2 && y > y1 && y < y2) + *dst = insidepal; + else if (line) + *dst = linepal; + dst++; + } + } + } +} + +static void cmd_attr_lin() +{ + int nset = sgb.command[1]; + if (nset <= 0 || nset >= 111) + { + utils_log("SGB: cmd_attr_lin bad nset\n"); + return; + } + int npacket = (nset + 17) / 16; + if ((sgb.command[0] & 7) != npacket) + { + utils_log("SGB: cmd_attr_lin bad length\n"); + return; + } + for (int i = 0; i < nset; i++) + { + uint8_t v = sgb.command[i + 2]; + int line = v & 31; + int a = v >> 5 & 3; + if (v & 0x80) // horizontal + { + if (line > 17) + line = 17; + memset(sgb.attr + line * 20, a, 20); + } + else // vertical + { + if (line > 19) + line = 19; + uint8_t *dst = sgb.attr + line; + for (int i = 0; i < 18; i++, dst += 20) + dst[0] = a; + } + } +} + +static void cmd_attr_div() +{ + if ((sgb.command[0] & 7) == 1) + { + uint8_t v = sgb.command[1]; + + int c = v & 3; + int a = v >> 2 & 3; + int b = v >> 4 & 3; + + int pos = sgb.command[2]; + uint8_t *dst = sgb.attr; + if (v & 0x40) // horizontal + { + if (pos > 17) + pos = 17; + int i; + for (i = 0; i < pos; i++, dst += 20) + memset(dst, a, 20); + memset(dst, b, 20); + i++, dst += 20; + for (; i < 18; i++, dst += 20) + memset(dst, c, 20); + } + else // vertical + { + if (pos > 19) + pos = 19; + for (int j = 0; j < 18; j++) + { + int i; + for (i = 0; i < pos; i++) + *dst++ = a; + *dst++ = b; + i++; + for (; i < 20; i++) + *dst++ = c; + } + } + } + else + { + utils_log("SGB: cmd_attr_div bad length\n"); + } +} + +static void cmd_attr_chr() +{ + int x = sgb.command[1]; + int y = sgb.command[2]; + int n = sgb.command[3] | sgb.command[4] << 8; + if (n > 360) + { + utils_log("SGB: cmd_attr_chr bad n\n"); + return; + } + int npacket = (n + 87) / 64; + if ((sgb.command[0] & 7) != npacket) + { + utils_log("SGB: cmd_attr_chr bad length\n"); + return; + } + uint8_t *dst = sgb.attr; + if (x > 19) + x = 19; + if (y > 17) + y = 17; + int vertical = sgb.command[5]; + for (int i = 0; i < 360; i++) + { + uint8_t v = i / 4 + 6; + int a = v >> (2 * (3 - (i & 3))) & 3; + dst[y * 20 + x] = a; + if (vertical) + { + y++; + if (y == 18) + { + y = 0; + x++; + if (x == 20) + return; + } + } + else + { + x++; + if (x == 20) + { + x = 0; + y++; + if (y == 18) + return; + } + } + } +} + +static void cmd_attr_set() +{ + if ((sgb.command[0] & 7) == 1) + { + int attr = sgb.command[1] & 0x3f; + if (attr >= 45) + attr = 44; + memcpy(sgb.attr, sgb.auxattr[attr], sizeof(sgb.attr)); + if (sgb.command[1] & 0x40) + { + sgb.active_mask = 0; + } + } + else + { + utils_log("SGB: cmd_attr_set bad length\n"); + } +} + +static void cmd_mlt_req(void) +{ + if ((sgb.command[0] & 7) == 1) + { + switch (sgb.command[1] & 3) + { + case 0: + case 2: + sgb.num_joypads = 1; + sgb.joypad_index = 0; + break; + case 1: + sgb.num_joypads = 2; + sgb.joypad_index = 1; + break; + case 3: + sgb.num_joypads = 4; + sgb.joypad_index = 1; + break; + } + utils_log("SGB: %u joypads\n", sgb.num_joypads); + } + else + { + utils_log("SGB: cmd_mlt_req bad length\n"); + } +} + +static void cmd_mask(void) +{ + if ((sgb.command[0] & 7) == 1) + { + switch (sgb.command[1] & 3) + { + case 0: + sgb.active_mask = 0; + break; + case 1: + sgb.active_mask = 1; + break; + case 2: + case 3: + sgb.active_mask = 1; + for (int i = 0; i < 256 * 224; i++) + sgb.frozenframe[i] = sgb.palette[0][0]; + break; + } + } + else + { + utils_log("SGB: cmd_mask bad length\n"); + } +} + +static void cmd_sound(void) +{ + if ((sgb.command[0] & 7) == 1) + { + sgb.sound_control[1] = sgb.command[1]; + sgb.sound_control[2] = sgb.command[2]; + sgb.sound_control[3] = sgb.command[3]; + sgb.sound_control[0] = sgb.command[4]; + } + else + { + utils_log("SGB: cmd_sound bad length\n"); + } +} + +static void do_command(void) +{ + const int command = sgb.command[0] >> 3; + switch (command) + { + default: + utils_log("SGB: Unknown or unimplemented command %02xh\n", command); + break; + + case 0x00: // PAL01 + utils_log("SGB: PAL01\n"); + cmd_pal(0, 1); + break; + case 0x01: // PAL23 + utils_log("SGB: PAL23\n"); + cmd_pal(2, 3); + break; + case 0x02: // PAL03 + utils_log("SGB: PAL03\n"); + cmd_pal(0, 3); + break; + case 0x03: // PAL12 + utils_log("SGB: PAL12\n"); + cmd_pal(1, 2); + break; + case 0x0a: // PAL_SET + utils_log("SGB: PAL_SET\n"); + cmd_pal_set(); + break; + + case 0x04: // ATTR_BLK + utils_log("SGB: ATTR_BLK\n"); + cmd_attr_blk(); + break; + case 0x05: // ATTR_LIN + utils_log("SGB: ATTR_LIN\n"); + cmd_attr_lin(); + break; + case 0x06: // ATTR_DIV + utils_log("SGB: ATTR_DIV\n"); + cmd_attr_div(); + break; + case 0x07: // ATTR_CHR + utils_log("SGB: ATTR_CHR\n"); + cmd_attr_chr(); + break; + case 0x16: // ATTR_SET + utils_log("SGB: ATTR_SET\n"); + cmd_attr_set(); + break; + + case 0x17: // MASK_EN + utils_log("SGB: MASK_EN\n"); + cmd_mask(); + break; + + // unknown functions + case 0x0c: // ATRC_EN + utils_log("SGB: ATRC_EN??\n"); + break; + case 0x0d: // TEST_EN + utils_log("SGB: TEST_EN??\n"); + break; + case 0x0e: // ICON_EN + utils_log("SGB: ICON_EN??\n"); + break; + case 0x18: // OBJ_TRN + // no game used this + utils_log("SGB: OBJ_TRN??\n"); + break; + + // unimplementable functions + case 0x0f: // DATA_SND + // TODO: Is it possible for this (and DATA_TRN) to write data to + // memory areas used for the attribute file, etc? + // If so, do games do this? + utils_log("SGB: DATA_SND!! %02x:%02x%02x [%02x]\n", sgb.command[3], sgb.command[2], sgb.command[1], sgb.command[4]); + break; + case 0x10: // DATA_TRN + utils_log("SGB: DATA_TRN!!\n"); + break; + case 0x12: // JUMP + utils_log("SGB: JUMP!!\n"); + break; + + // joypad + case 0x11: // MLT_REQ + utils_log("SGB: MLT_REQ\n"); + cmd_mlt_req(); + break; + + // sound + case 0x08: // SOUND + utils_log("SGB: SOUND %02x %02x %02x %02x\n", sgb.command[1], sgb.command[2], sgb.command[3], sgb.command[4]); + cmd_sound(); + break; + + // all vram transfers + case 0x09: // SOU_TRN + utils_log("SGB: SOU_TRN\n"); + cmd_trn(TRN_SOUND); + break; + case 0x0b: // PAL_TRN + utils_log("SGB: PAL_TRN\n"); + cmd_trn(TRN_PAL); + break; + case 0x13: // CHR_TRN + utils_log("SGB: CHR_TRN\n"); + cmd_trn(sgb.command[1] & 1 ? TRN_CHR_HI : TRN_CHR_LOW); + break; + case 0x14: // PCT_TRN + utils_log("SGB: PCT_TRN\n"); + cmd_trn(TRN_PCT); + break; + case 0x15: // ATTR_TRN + utils_log("SGB: ATTR_TRN\n"); + cmd_trn(TRN_ATTR); + break; + } +} + +static void do_packet(void) +{ + memcpy(sgb.command + sgb.next_packet * 16, sgb.packet, sizeof(sgb.packet)); + sgb.next_packet++; + + if (sgb.expected_packets == 0) // not in the middle of a command + sgb.expected_packets = sgb.command[0] & 7; + + if (sgb.expected_packets == 0) // huh? + { + utils_log("SGB: zero packet command\n"); + sgb.expected_packets = 0; + sgb.next_packet = 0; + } + else if (sgb.next_packet == sgb.expected_packets) + { + do_command(); + sgb.expected_packets = 0; + sgb.next_packet = 0; + } +} + +int sgb_init(const uint8_t *spc, int length) +{ + memset(&sgb, 0, sizeof(sgb)); + sgb.read_index = 255; + sgb.num_joypads = 1; + sgb.palette[0][0] = 0xffffffff; + sgb.palette[0][1] = 0xffaaaaaa; + sgb.palette[0][2] = 0xff555555; + sgb.palette[0][3] = 0xff000000; + + sgb.spc = spc_new(); + spc_init_rom(sgb.spc, iplrom); + spc_reset(sgb.spc); + if (spc_load_spc(sgb.spc, spc, length) != NULL) + { + utils_log("SGB: Failed to load SPC\n"); + return 0; + } + + return 1; +} + +void sgb_write_ff00(uint8_t val, uint64_t time) +{ + val &= 0x30; + + //utils_log("ZZ: %02x, %llu", val, time); + const int p14_fell = (val & 0x10) < (sgb.last_write_value & 0x10); + const int p15_fell = (val & 0x20) < (sgb.last_write_value & 0x20); + const int p14_rose = (val & 0x10) > (sgb.last_write_value & 0x10); + const int p15_rose = (val & 0x20) > (sgb.last_write_value & 0x20); + + if (val == 0) // reset command processing + { + sgb.read_index = 0; + memset(sgb.packet, 0, sizeof(sgb.packet)); + } + else if (sgb.read_index != 255) // currently reading a packet + { + if (p14_fell || p15_fell) + { + if (sgb.read_index == 128) // end of packet + { + if (p14_fell) + do_packet(); + else + utils_log("SGB: Stop bit not present\n"); + sgb.read_index = 255; + } + else + { + if (p15_fell) + { + int byte = sgb.read_index >> 3; + int bit = sgb.read_index & 7; + sgb.packet[byte] |= 1 << bit; + } + sgb.read_index++; + } + } + } + else // joypad processing + { + if (val == 0x10) + sgb.joypad_has_been_read |= 2; // reading P15 + if (val == 0x20) + sgb.joypad_has_been_read |= 1; // reading P14 + if (val == 0x30 && (p14_rose || p15_rose)) + { + if (sgb.joypad_has_been_read == 7) + { + sgb.joypad_has_been_read = 0; + sgb.joypad_index++; + sgb.joypad_index &= sgb.num_joypads - 1; + //utils_log("SGB: joypad index to %u", sgb.joypad_index); + } + else + { + sgb.joypad_has_been_read &= 3; // the other line must be asserted and a read must happen before joypad_index inc?? + } + } + } + + sgb.last_write_value = val; + sgb.last_write_time = time; +} + +uint8_t sgb_read_ff00(uint64_t time) +{ + uint8_t ret = sgb.last_write_value & 0xf0 | 0xc0; + const int p14 = !(ret & 0x10); + const int p15 = !(ret & 0x20); + const int ji = sgb.joypad_index; + + // TODO: is this "reset" correct? + sgb.joypad_has_been_read |= 4; // read occured + sgb.read_index = 255; + sgb.next_packet = 0; + sgb.expected_packets = 0; + + if (!p14 && !p15) + { + //utils_log("SGB: SCAN%u", ji); + // scan id + return ret | (15 - ji); + } + else + { + // get data + const uint8_t j = sgb.joypad_data[ji]; + if (p14) + ret |= j >> 4; + if (p15) + ret |= j & 0x0f; + //utils_log("SGB: READ%u %02x", ji, ret ^ 0x0f); + return ret ^ 0x0f; + } +} + +// for each of 4 joypads: +// 7......0 +// DULRSsBA +void sgb_set_controller_data(const uint8_t *buttons) +{ + memcpy(sgb.joypad_data, buttons, sizeof(sgb.joypad_data)); +} + +static void trn_sound(const uint8_t* data) +{ + int len = data[0] | data[1] << 8; + int addr = data[2] | data[3] << 8; + utils_log("TRN_SOUND %04x %04x\n", addr, len); + uint8_t* dst = spc_get_ram(sgb.spc); + + if (len > 0xffc) + { + utils_log("TRN_SOUND src overflow\n"); + return; + } + if (len + addr >= 0x10000) + { + utils_log("TRN_SOUND dst overflow\n"); + return; + } + memcpy(dst + addr, data + 4, len); +} + +static void trn_pal(const uint8_t *data) +{ + const uint16_t *src = (const uint16_t *)data; + uint32_t *dst = sgb.auxpalette[0]; + for (int i = 0; i < 2048; i++) + dst[i] = makecol(src[i]); +} + +static void trn_attr(const uint8_t *data) +{ + uint8_t *dst = sgb.auxattr[0]; + for (int n = 0; n < 45 * 90; n++) + { + uint8_t s = *data++; + *dst++ = s >> 6 & 3; + *dst++ = s >> 4 & 3; + *dst++ = s >> 2 & 3; + *dst++ = s >> 0 & 3; + } +} + +static void trn_pct(const uint8_t *data) +{ + memcpy(sgb.tilemap, data, sizeof(sgb.tilemap)); + const uint16_t *palettes = (const uint16_t *)(data + sizeof(sgb.tilemap)); + uint32_t *dst = sgb.palette[4]; + for (int i = 0; i < 64; i++) + dst[i] = makecol(palettes[i]); +} + +static void trn_chr(const uint8_t *data, int bank) +{ + uint8_t *dst = sgb.tiles[128 * bank]; + for (int n = 0; n < 128; n++) + { + for (int y = 0; y < 8; y++) + { + int a = data[0]; + int b = data[1] << 1; + int c = data[16] << 2; + int d = data[17] << 3; + for (int x = 7; x >= 0; x--) + { + dst[x] = a & 1 | b & 2 | c & 4 | d & 8; + a >>= 1; + b >>= 1; + c >>= 1; + d >>= 1; + } + dst += 8; + data += 2; + } + data += 16; + } +} + +static void do_vram_transfer(void) +{ + uint8_t vram[4096]; + for (int tilenum = 0; tilenum < 256; tilenum++) + { + const int ty = tilenum / 20; + const int tx = tilenum % 20; + const uint8_t *src = sgb.frame + ty * 8 * 160 + tx * 8; + uint8_t *dst = vram + 16 * tilenum; + for (int j = 0; j < 8; j++) + { + uint32_t a = 0, b = 0; + a |= (src[7] & 1) << 0; + a |= (src[6] & 1) << 1; + a |= (src[5] & 1) << 2; + a |= (src[4] & 1) << 3; + a |= (src[3] & 1) << 4; + a |= (src[2] & 1) << 5; + a |= (src[1] & 1) << 6; + a |= (src[0] & 1) << 7; + + b |= (src[7] & 2) >> 1; + b |= (src[6] & 2) << 0; + b |= (src[5] & 2) << 1; + b |= (src[4] & 2) << 2; + b |= (src[3] & 2) << 3; + b |= (src[2] & 2) << 4; + b |= (src[1] & 2) << 5; + b |= (src[0] & 2) << 6; + *dst++ = a; + *dst++ = b; + src += 160; + } + } + + switch (sgb.waiting_transfer) + { + case TRN_SOUND: + trn_sound(vram); + break; + case TRN_PAL: + trn_pal(vram); + break; + case TRN_CHR_LOW: + trn_chr(vram, 0); + break; + case TRN_CHR_HI: + trn_chr(vram, 1); + break; + case TRN_PCT: + trn_pct(vram); + break; + case TRN_ATTR: + trn_attr(vram); + break; + } +} + +static void sgb_render_frame_gb(uint32_t *vbuff) +{ + const uint8_t *attr = sgb.attr; + const uint8_t *src = sgb.frame; + uint32_t *dst = vbuff + ((224 - 144) / 2 * 256 + (256 - 160) / 2); + + for (int j = 0; j < 144; j++) + { + const uint8_t *attr_line = attr + j / 8 * 20; + for (int i = 0; i < 160; i++) + { + const int attr_index = i / 8; + *dst++ = sgb.palette[attr_line[attr_index]][*src++]; + } + dst += 256 - 160; + } +} + +static void draw_tile(uint16_t entry, uint32_t *dest) +{ + const uint8_t *tile = sgb.tiles[entry & 0xff]; + const uint32_t *palette = sgb.palette[entry >> 10 & 7]; + int hflip = entry & 0x4000; + int vflip = entry & 0x8000; + int hinc, vinc; + if (hflip) + { + hinc = -1; + dest += 7; + } + else + { + hinc = 1; + } + if (vflip) + { + vinc = -256; + dest += 7 * 256; + } + else + { + vinc = 256; + } + vinc -= 8 * hinc; + for (int y = 0; y < 8; y++, dest += vinc) + { + for (int x = 0; x < 8; x++, dest += hinc) + { + int c = *tile++; + if (c) + *dest = palette[c]; + } + } +} + +static void sgb_render_border(uint32_t *vbuff) +{ + const uint16_t *tilemap = sgb.tilemap; + for (int n = 0; n < 32 * 28; n++) + { + draw_tile(*tilemap++, vbuff); + vbuff += 8; + if ((n & 31) == 31) + vbuff += 256 * 7; + } +} + +// 160x144 32bpp pixel data +// assumed to contain exact pixel values 00, 55, aa, ff +void sgb_take_frame(uint32_t *vbuff) +{ + for (int i = 0; i < 160 * 144; i++) + { + sgb.frame[i] = 3 - (vbuff[i] >> 6 & 3); // 0, 1, 2, or 3 for each pixel + } + if (sgb.waiting_transfer != TRN_NONE) + { + if (!--sgb.transfer_countdown) + { + do_vram_transfer(); + sgb.waiting_transfer = TRN_NONE; + } + } + if (!sgb.active_mask) + { + // render the frame now + for (int i = 0; i < 256 * 224; i++) + sgb.frozenframe[i] = sgb.palette[0][0]; + sgb_render_frame_gb(sgb.frozenframe); + sgb_render_border(sgb.frozenframe); + } +} + +void sgb_render_frame(uint32_t *vbuff) +{ + memcpy(vbuff, sgb.frozenframe, sizeof(sgb.frozenframe)); +} + +void sgb_render_audio(uint64_t time, void (*callback)(int16_t l, int16_t r, uint64_t time)) +{ + int16_t sound_buffer[4096]; + uint32_t diff = time - sgb.frame_start + sgb.clock_remainder; + //utils_log("%ul", diff); + + uint32_t samples = diff / refclocks_per_spc_sample; + uint32_t new_remainder = diff % refclocks_per_spc_sample; + + spc_set_output(sgb.spc, sound_buffer, sizeof(sound_buffer) / sizeof(sound_buffer[0])); + int p; + for (p = 0; p < 4; p++) + { + if (spc_read_port(sgb.spc, 0, p) != sgb.sound_control[p]) + break; + } + if (p == 4) // recived + { + sgb.sound_control[0] = 0; + sgb.sound_control[1] = 0; + sgb.sound_control[2] = 0; + } + for (p = 0; p < 4; p++) + { + spc_write_port(sgb.spc, 0, p, sgb.sound_control[p]); + } + + spc_end_frame(sgb.spc, samples * 32); + + uint64_t t = sgb.frame_start + refclocks_per_spc_sample - sgb.clock_remainder; + for (int i = 0; i < samples; i++, t += refclocks_per_spc_sample) + callback(sound_buffer[i * 2], sound_buffer[i * 2] + 1, t); + + sgb.frame_start = time; + sgb.clock_remainder = new_remainder; +} diff --git a/waterbox/sameboy/sgb.h b/waterbox/sameboy/sgb.h new file mode 100644 index 0000000000..35e76dcad9 --- /dev/null +++ b/waterbox/sameboy/sgb.h @@ -0,0 +1,36 @@ +#pragma once +#include + +// whenever a time is asked for, it is relative to a clock that ticks 35112 times +// per nominal frame on the GB lcd, starts at 0 when emulation begins, and never resets/rebases + +// write to MMIO ff00. only bits 4 and 5 are used +void sgb_write_ff00(uint8_t val, uint64_t time); + +// read from MMIO ff00. supplies data for all 8 bits +uint8_t sgb_read_ff00(uint64_t time); + +// set controller data to be used by subsequent controller reads +// buttons[0] = controller 1, buttons[3] = controller 4 +// 7......0 +// DULRSsBA +void sgb_set_controller_data(const uint8_t* buttons); + +// initialize the SGB module. pass an SPC file that results from the real S-CPU initialization, +// and the length of that file +int sgb_init(const uint8_t* spc, int length); + +// call whenever the gameboy has finished producing a video frame +// data is 32bpp 160x144 screen data. for each pixel: +//31 7 0 +// xxxxxxxx xxxxxxxx xxxxxxxx DDxxxxxx -- DD = 0, 1, 2, or 3. x = don't care +void sgb_take_frame(uint32_t* vbuff); + +// copy the finished video frame to an output buffer. pixel format is 32bpp xrgb +// can be called at any time, including right after sgb_take_frame +void sgb_render_frame(uint32_t* vbuff); + +// call to finish a frame's worth of audio. should be called once every 35112 time units (some jitter is OK) +// callback will be called with L and R sample values for various time points +// between the last time sgb_render_audio was called and now +void sgb_render_audio(uint64_t time, void(*callback)(int16_t l, int16_t r, uint64_t time)); diff --git a/waterbox/sameboy/snes_spc/SNES_SPC.cpp b/waterbox/sameboy/snes_spc/SNES_SPC.cpp new file mode 100644 index 0000000000..fb3b147ae8 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SNES_SPC.cpp @@ -0,0 +1,564 @@ +// Core SPC emulation: CPU, timers, SMP registers, memory + +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SNES_SPC.h" + +#include + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#define RAM (m.ram.ram) +#define REGS (m.smp_regs [0]) +#define REGS_IN (m.smp_regs [1]) + +// (n ? n : 256) +#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1) + +// Note: SPC_MORE_ACCURACY exists mainly so I can run my validation tests, which +// do crazy echo buffer accesses. +#ifndef SPC_MORE_ACCURACY + #define SPC_MORE_ACCURACY 0 +#endif + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + + +//// Timers + +#if SPC_DISABLE_TEMPO + #define TIMER_DIV( t, n ) ((n) >> t->prescaler) + #define TIMER_MUL( t, n ) ((n) << t->prescaler) +#else + #define TIMER_DIV( t, n ) ((n) / t->prescaler) + #define TIMER_MUL( t, n ) ((n) * t->prescaler) +#endif + +SNES_SPC::Timer* SNES_SPC::run_timer_( Timer* t, rel_time_t time ) +{ + int elapsed = TIMER_DIV( t, time - t->next_time ) + 1; + t->next_time += TIMER_MUL( t, elapsed ); + + if ( t->enabled ) + { + int remain = IF_0_THEN_256( t->period - t->divider ); + int divider = t->divider + elapsed; + int over = elapsed - remain; + if ( over >= 0 ) + { + int n = over / t->period; + t->counter = (t->counter + 1 + n) & 0x0F; + divider = over - n * t->period; + } + t->divider = (uint8_t) divider; + } + return t; +} + +inline SNES_SPC::Timer* SNES_SPC::run_timer( Timer* t, rel_time_t time ) +{ + if ( time >= t->next_time ) + t = run_timer_( t, time ); + return t; +} + + +//// ROM + +void SNES_SPC::enable_rom( int enable ) +{ + if ( m.rom_enabled != enable ) + { + m.rom_enabled = enable; + if ( enable ) + memcpy( m.hi_ram, &RAM [rom_addr], sizeof m.hi_ram ); + memcpy( &RAM [rom_addr], (enable ? m.rom : m.hi_ram), rom_size ); + // TODO: ROM can still get overwritten when DSP writes to echo buffer + } +} + + +//// DSP + +#if SPC_LESS_ACCURATE + int const max_reg_time = 29; + + signed char const SNES_SPC::reg_times_ [256] = + { + -1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22, + 2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23, + 5, 6, 3, 4, -1, 3, 4, 4, 10, 9, 14, 14, 26, -5, 14, 23, + 8, 9, 6, 7, 2, 6, 7, 7, 13, 12, 14, 14, 27, -4, 14, 24, + 11, 12, 9, 10, 5, 9, 10, 10, 16, 15, 14, 14, -2, -4, 14, 24, + 14, 15, 12, 13, 8, 12, 13, 13, 19, 18, 14, 14, -2,-36, 14, 24, + 17, 18, 15, 16, 11, 15, 16, 16, 22, 21, 14, 14, 28, -3, 14, 25, + 20, 21, 18, 19, 14, 18, 19, 19, 25, 24, 14, 14, 14, 29, 14, 25, + + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + }; + + #define RUN_DSP( time, offset ) \ + int count = (time) - (offset) - m.dsp_time;\ + if ( count >= 0 )\ + {\ + int clock_count = (count & ~(clocks_per_sample - 1)) + clocks_per_sample;\ + m.dsp_time += clock_count;\ + dsp.run( clock_count );\ + } +#else + #define RUN_DSP( time, offset ) \ + {\ + int count = (time) - m.dsp_time;\ + if ( !SPC_MORE_ACCURACY || count )\ + {\ + assert( count > 0 );\ + m.dsp_time = (time);\ + dsp.run( count );\ + }\ + } +#endif + +int SNES_SPC::dsp_read( rel_time_t time ) +{ + RUN_DSP( time, reg_times [REGS [r_dspaddr] & 0x7F] ); + + int result = dsp.read( REGS [r_dspaddr] & 0x7F ); + + #ifdef SPC_DSP_READ_HOOK + SPC_DSP_READ_HOOK( spc_time + time, (REGS [r_dspaddr] & 0x7F), result ); + #endif + + return result; +} + +inline void SNES_SPC::dsp_write( int data, rel_time_t time ) +{ + RUN_DSP( time, reg_times [REGS [r_dspaddr]] ) + #if SPC_LESS_ACCURATE + else if ( m.dsp_time == skipping_time ) + { + int r = REGS [r_dspaddr]; + if ( r == SPC_DSP::r_kon ) + m.skipped_kon |= data & ~dsp.read( SPC_DSP::r_koff ); + + if ( r == SPC_DSP::r_koff ) + { + m.skipped_koff |= data; + m.skipped_kon &= ~data; + } + } + #endif + + #ifdef SPC_DSP_WRITE_HOOK + SPC_DSP_WRITE_HOOK( m.spc_time + time, REGS [r_dspaddr], (uint8_t) data ); + #endif + + if ( REGS [r_dspaddr] <= 0x7F ) + dsp.write( REGS [r_dspaddr], data ); + else if ( !SPC_MORE_ACCURACY ) + dprintf( "SPC wrote to DSP register > $7F\n" ); +} + + +//// Memory access extras + +#if SPC_MORE_ACCURACY + #define MEM_ACCESS( time, addr ) \ + {\ + if ( time >= m.dsp_time )\ + {\ + RUN_DSP( time, max_reg_time );\ + }\ + } +#elif !defined (NDEBUG) + // Debug-only check for read/write within echo buffer, since this might result in + // inaccurate emulation due to the DSP not being caught up to the present. + + bool SNES_SPC::check_echo_access( int addr ) + { + if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) ) + { + int start = 0x100 * dsp.read( SPC_DSP::r_esa ); + int size = 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F); + int end = start + (size ? size : 4); + if ( start <= addr && addr < end ) + { + if ( !m.echo_accessed ) + { + m.echo_accessed = 1; + return true; + } + } + } + return false; + } + + #define MEM_ACCESS( time, addr ) check( !check_echo_access( (uint16_t) addr ) ); +#else + #define MEM_ACCESS( time, addr ) +#endif + + +//// CPU write + +#if SPC_MORE_ACCURACY +static unsigned char const glitch_probs [3] [256] = +{ + 0xC3,0x92,0x5B,0x1C,0xD1,0x92,0x5B,0x1C,0xDB,0x9C,0x72,0x18,0xCD,0x5C,0x38,0x0B, + 0xE1,0x9C,0x74,0x17,0xCF,0x75,0x45,0x0C,0xCF,0x6E,0x4A,0x0D,0xA3,0x3A,0x1D,0x08, + 0xDB,0xA0,0x82,0x19,0xD9,0x73,0x3C,0x0E,0xCB,0x76,0x52,0x0B,0xA5,0x46,0x1D,0x09, + 0xDA,0x74,0x55,0x0F,0xA2,0x3F,0x21,0x05,0x9A,0x40,0x20,0x07,0x63,0x1E,0x10,0x01, + 0xDF,0xA9,0x85,0x1D,0xD3,0x84,0x4B,0x0E,0xCF,0x6F,0x49,0x0F,0xB3,0x48,0x1E,0x05, + 0xD8,0x77,0x52,0x12,0xB7,0x49,0x23,0x06,0xAA,0x45,0x28,0x07,0x7D,0x28,0x0F,0x07, + 0xCC,0x7B,0x4A,0x0E,0xB2,0x4F,0x24,0x07,0xAD,0x43,0x2C,0x06,0x86,0x29,0x11,0x07, + 0xAE,0x48,0x1F,0x0A,0x76,0x21,0x19,0x05,0x76,0x21,0x14,0x05,0x44,0x11,0x0B,0x01, + 0xE7,0xAD,0x96,0x23,0xDC,0x86,0x59,0x0E,0xDC,0x7C,0x5F,0x15,0xBB,0x53,0x2E,0x09, + 0xD6,0x7C,0x4A,0x16,0xBB,0x4A,0x25,0x08,0xB3,0x4F,0x28,0x0B,0x8E,0x23,0x15,0x08, + 0xCF,0x7F,0x57,0x11,0xB5,0x4A,0x23,0x0A,0xAA,0x42,0x28,0x05,0x7D,0x22,0x12,0x03, + 0xA6,0x49,0x28,0x09,0x82,0x2B,0x0D,0x04,0x7A,0x20,0x0F,0x04,0x3D,0x0F,0x09,0x03, + 0xD1,0x7C,0x4C,0x0F,0xAF,0x4E,0x21,0x09,0xA8,0x46,0x2A,0x07,0x85,0x1F,0x0E,0x07, + 0xA6,0x3F,0x26,0x07,0x7C,0x24,0x14,0x07,0x78,0x22,0x16,0x04,0x46,0x12,0x0A,0x02, + 0xA6,0x41,0x2C,0x0A,0x7E,0x28,0x11,0x05,0x73,0x1B,0x14,0x05,0x3D,0x11,0x0A,0x02, + 0x70,0x22,0x17,0x05,0x48,0x13,0x08,0x03,0x3C,0x07,0x0D,0x07,0x26,0x07,0x06,0x01, + + 0xE0,0x9F,0xDA,0x7C,0x4F,0x18,0x28,0x0D,0xE9,0x9F,0xDA,0x7C,0x4F,0x18,0x1F,0x07, + 0xE6,0x97,0xD8,0x72,0x64,0x13,0x26,0x09,0xDC,0x67,0xA9,0x38,0x21,0x07,0x15,0x06, + 0xE9,0x91,0xD2,0x6B,0x63,0x14,0x2B,0x0E,0xD6,0x61,0xB7,0x41,0x2B,0x0E,0x10,0x09, + 0xCF,0x59,0xB0,0x2F,0x35,0x08,0x0F,0x07,0xB6,0x30,0x7A,0x21,0x17,0x07,0x09,0x03, + 0xE7,0xA3,0xE5,0x6B,0x65,0x1F,0x34,0x09,0xD8,0x6B,0xBE,0x45,0x27,0x07,0x10,0x07, + 0xDA,0x54,0xB1,0x39,0x2E,0x0E,0x17,0x08,0xA9,0x3C,0x86,0x22,0x16,0x06,0x07,0x03, + 0xD4,0x51,0xBC,0x3D,0x38,0x0A,0x13,0x06,0xB2,0x37,0x79,0x1C,0x17,0x05,0x0E,0x06, + 0xA7,0x31,0x74,0x1C,0x11,0x06,0x0C,0x02,0x6D,0x1A,0x38,0x10,0x0B,0x05,0x06,0x03, + 0xEB,0x9A,0xE1,0x7A,0x6F,0x13,0x34,0x0E,0xE6,0x75,0xC5,0x45,0x3E,0x0B,0x1A,0x05, + 0xD8,0x63,0xC1,0x40,0x3C,0x1B,0x19,0x06,0xB3,0x42,0x83,0x29,0x18,0x0A,0x08,0x04, + 0xD4,0x58,0xBA,0x43,0x3F,0x0A,0x1F,0x09,0xB1,0x33,0x8A,0x1F,0x1F,0x06,0x0D,0x05, + 0xAF,0x3C,0x7A,0x1F,0x16,0x08,0x0A,0x01,0x72,0x1B,0x52,0x0D,0x0B,0x09,0x06,0x01, + 0xCF,0x63,0xB7,0x47,0x40,0x10,0x14,0x06,0xC0,0x41,0x96,0x20,0x1C,0x09,0x10,0x05, + 0xA6,0x35,0x82,0x1A,0x20,0x0C,0x0E,0x04,0x80,0x1F,0x53,0x0F,0x0B,0x02,0x06,0x01, + 0xA6,0x31,0x81,0x1B,0x1D,0x01,0x08,0x08,0x7B,0x20,0x4D,0x19,0x0E,0x05,0x07,0x03, + 0x6B,0x17,0x49,0x07,0x0E,0x03,0x0A,0x05,0x37,0x0B,0x1F,0x06,0x04,0x02,0x07,0x01, + + 0xF0,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x47,0x1E,0x6E,0x1B,0x32,0x0A, + 0xF0,0xD6,0xEA,0xA4,0xED,0xC4,0xDE,0x82,0x98,0x1F,0x50,0x13,0x52,0x15,0x2A,0x0A, + 0xF1,0xD1,0xEB,0xA2,0xEB,0xB7,0xD8,0x69,0xA2,0x1F,0x5B,0x18,0x55,0x18,0x2C,0x0A, + 0xED,0xB5,0xDE,0x7E,0xE6,0x85,0xD3,0x59,0x59,0x0F,0x2C,0x09,0x24,0x07,0x15,0x09, + 0xF1,0xD6,0xEA,0xA0,0xEC,0xBB,0xDA,0x77,0xA9,0x23,0x58,0x14,0x5D,0x12,0x2F,0x09, + 0xF1,0xC1,0xE3,0x86,0xE4,0x87,0xD2,0x4E,0x68,0x15,0x26,0x0B,0x27,0x09,0x15,0x02, + 0xEE,0xA6,0xE0,0x5C,0xE0,0x77,0xC3,0x41,0x67,0x1B,0x3C,0x07,0x2A,0x06,0x19,0x07, + 0xE4,0x75,0xC6,0x43,0xCC,0x50,0x95,0x23,0x35,0x09,0x14,0x04,0x15,0x05,0x0B,0x04, + 0xEE,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x56,0x14,0x5A,0x12,0x26,0x0A, + 0xEE,0xBB,0xE7,0x7E,0xE9,0x8D,0xCB,0x49,0x67,0x11,0x34,0x07,0x2B,0x0B,0x14,0x07, + 0xED,0xA7,0xE5,0x76,0xE3,0x7E,0xC4,0x4B,0x77,0x14,0x34,0x08,0x27,0x07,0x14,0x04, + 0xE7,0x8B,0xD2,0x4C,0xCA,0x56,0x9E,0x31,0x36,0x0C,0x11,0x07,0x14,0x04,0x0A,0x02, + 0xF0,0x9B,0xEA,0x6F,0xE5,0x81,0xC4,0x43,0x74,0x10,0x30,0x0B,0x2D,0x08,0x1B,0x06, + 0xE6,0x83,0xCA,0x48,0xD9,0x56,0xA7,0x23,0x3B,0x09,0x12,0x09,0x15,0x07,0x0A,0x03, + 0xE5,0x5F,0xCB,0x3C,0xCF,0x48,0x91,0x22,0x31,0x0A,0x17,0x08,0x15,0x04,0x0D,0x02, + 0xD1,0x43,0x91,0x20,0xA9,0x2D,0x54,0x12,0x17,0x07,0x09,0x02,0x0C,0x04,0x05,0x03, +}; +#endif + +// divided into multiple functions to keep rarely-used functionality separate +// so often-used functionality can be optimized better by compiler + +// If write isn't preceded by read, data has this added to it +int const no_read_before_write = 0x2000; + +void SNES_SPC::cpu_write_smp_reg_( int data, rel_time_t time, int addr ) +{ + switch ( addr ) + { + case r_t0target: + case r_t1target: + case r_t2target: { + Timer* t = &m.timers [addr - r_t0target]; + int period = IF_0_THEN_256( data ); + if ( t->period != period ) + { + t = run_timer( t, time ); + #if SPC_MORE_ACCURACY + // Insane behavior when target is written just after counter is + // clocked and counter matches new period and new period isn't 1, 2, 4, or 8 + if ( t->divider == (period & 0xFF) && + t->next_time == time + TIMER_MUL( t, 1 ) && + ((period - 1) | ~0x0F) & period ) + { + //dprintf( "SPC pathological timer target write\n" ); + + // If the period is 3, 5, or 9, there's a probability this behavior won't occur, + // based on the previous period + int prob = 0xFF; + int old_period = t->period & 0xFF; + if ( period == 3 ) prob = glitch_probs [0] [old_period]; + if ( period == 5 ) prob = glitch_probs [1] [old_period]; + if ( period == 9 ) prob = glitch_probs [2] [old_period]; + + // The glitch suppresses incrementing of one of the counter bits, based on + // the lowest set bit in the new period + int b = 1; + while ( !(period & b) ) + b <<= 1; + + if ( (rand() >> 4 & 0xFF) <= prob ) + t->divider = (t->divider - b) & 0xFF; + } + #endif + t->period = period; + } + break; + } + + case r_t0out: + case r_t1out: + case r_t2out: + if ( !SPC_MORE_ACCURACY ) + dprintf( "SPC wrote to counter %d\n", (int) addr - r_t0out ); + + if ( data < no_read_before_write / 2 ) + run_timer( &m.timers [addr - r_t0out], time - 1 )->counter = 0; + break; + + // Registers that act like RAM + case 0x8: + case 0x9: + REGS_IN [addr] = (uint8_t) data; + break; + + case r_test: + if ( (uint8_t) data != 0x0A ) + dprintf( "SPC wrote to test register\n" ); + break; + + case r_control: + // port clears + if ( data & 0x10 ) + { + REGS_IN [r_cpuio0] = 0; + REGS_IN [r_cpuio1] = 0; + } + if ( data & 0x20 ) + { + REGS_IN [r_cpuio2] = 0; + REGS_IN [r_cpuio3] = 0; + } + + // timers + { + for ( int i = 0; i < timer_count; i++ ) + { + Timer* t = &m.timers [i]; + int enabled = data >> i & 1; + if ( t->enabled != enabled ) + { + t = run_timer( t, time ); + t->enabled = enabled; + if ( enabled ) + { + t->divider = 0; + t->counter = 0; + } + } + } + } + enable_rom( data & 0x80 ); + break; + } +} + +void SNES_SPC::cpu_write_smp_reg( int data, rel_time_t time, int addr ) +{ + if ( addr == r_dspdata ) // 99% + dsp_write( data, time ); + else + cpu_write_smp_reg_( data, time, addr ); +} + +void SNES_SPC::cpu_write_high( int data, int i, rel_time_t time ) +{ + if ( i < rom_size ) + { + m.hi_ram [i] = (uint8_t) data; + if ( m.rom_enabled ) + RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM + } + else + { + assert( RAM [i + rom_addr] == (uint8_t) data ); + RAM [i + rom_addr] = cpu_pad_fill; // restore overwritten padding + cpu_write( data, i + rom_addr - 0x10000, time ); + } +} + +int const bits_in_int = CHAR_BIT * sizeof (int); + +void SNES_SPC::cpu_write( int data, int addr, rel_time_t time ) +{ + MEM_ACCESS( time, addr ) + + // RAM + RAM [addr] = (uint8_t) data; + int reg = addr - 0xF0; + if ( reg >= 0 ) // 64% + { + // $F0-$FF + if ( reg < reg_count ) // 87% + { + REGS [reg] = (uint8_t) data; + + // Ports + #ifdef SPC_PORT_WRITE_HOOK + if ( (unsigned) (reg - r_cpuio0) < port_count ) + SPC_PORT_WRITE_HOOK( m.spc_time + time, (reg - r_cpuio0), + (uint8_t) data, ®S [r_cpuio0] ); + #endif + + // Registers other than $F2 and $F4-$F7 + //if ( reg != 2 && reg != 4 && reg != 5 && reg != 6 && reg != 7 ) + // TODO: this is a bit on the fragile side + if ( ((~0x2F00 << (bits_in_int - 16)) << reg) < 0 ) // 36% + cpu_write_smp_reg( data, time, reg ); + } + // High mem/address wrap-around + else + { + reg -= rom_addr - 0xF0; + if ( reg >= 0 ) // 1% in IPL ROM area or address wrapped around + cpu_write_high( data, reg, time ); + } + } +} + + +//// CPU read + +inline int SNES_SPC::cpu_read_smp_reg( int reg, rel_time_t time ) +{ + int result = REGS_IN [reg]; + reg -= r_dspaddr; + // DSP addr and data + if ( (unsigned) reg <= 1 ) // 4% 0xF2 and 0xF3 + { + result = REGS [r_dspaddr]; + if ( (unsigned) reg == 1 ) + result = dsp_read( time ); // 0xF3 + } + return result; +} + +int SNES_SPC::cpu_read( int addr, rel_time_t time ) +{ + MEM_ACCESS( time, addr ) + + // RAM + int result = RAM [addr]; + int reg = addr - 0xF0; + if ( reg >= 0 ) // 40% + { + reg -= 0x10; + if ( (unsigned) reg >= 0xFF00 ) // 21% + { + reg += 0x10 - r_t0out; + + // Timers + if ( (unsigned) reg < timer_count ) // 90% + { + Timer* t = &m.timers [reg]; + if ( time >= t->next_time ) + t = run_timer_( t, time ); + result = t->counter; + t->counter = 0; + } + // Other registers + else if ( reg < 0 ) // 10% + { + result = cpu_read_smp_reg( reg + r_t0out, time ); + } + else // 1% + { + assert( reg + (r_t0out + 0xF0 - 0x10000) < 0x100 ); + result = cpu_read( reg + (r_t0out + 0xF0 - 0x10000), time ); + } + } + } + + return result; +} + + +//// Run + +// Prefix and suffix for CPU emulator function +#define SPC_CPU_RUN_FUNC \ +BOOST::uint8_t* SNES_SPC::run_until_( time_t end_time )\ +{\ + rel_time_t rel_time = m.spc_time - end_time;\ + assert( rel_time <= 0 );\ + m.spc_time = end_time;\ + m.dsp_time += rel_time;\ + m.timers [0].next_time += rel_time;\ + m.timers [1].next_time += rel_time;\ + m.timers [2].next_time += rel_time; + +#define SPC_CPU_RUN_FUNC_END \ + m.spc_time += rel_time;\ + m.dsp_time -= rel_time;\ + m.timers [0].next_time -= rel_time;\ + m.timers [1].next_time -= rel_time;\ + m.timers [2].next_time -= rel_time;\ + assert( m.spc_time <= end_time );\ + return ®S [r_cpuio0];\ +} + +int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks + +void SNES_SPC::end_frame( time_t end_time ) +{ + // Catch CPU up to as close to end as possible. If final instruction + // would exceed end, does NOT execute it and leaves m.spc_time < end. + if ( end_time > m.spc_time ) + run_until_( end_time ); + + m.spc_time -= end_time; + m.extra_clocks += end_time; + + // Greatest number of clocks early that emulation can stop early due to + // not being able to execute current instruction without going over + // allowed time. + assert( -cpu_lag_max <= m.spc_time && m.spc_time <= 0 ); + + // Catch timers up to CPU + for ( int i = 0; i < timer_count; i++ ) + run_timer( &m.timers [i], 0 ); + + // Catch DSP up to CPU + if ( m.dsp_time < 0 ) + { + RUN_DSP( 0, max_reg_time ); + } + + // Save any extra samples beyond what should be generated + if ( m.buf_begin ) + save_extra(); +} + +// Inclusion here allows static memory access functions and better optimization +#include "SPC_CPU.h" diff --git a/waterbox/sameboy/snes_spc/SNES_SPC.h b/waterbox/sameboy/snes_spc/SNES_SPC.h new file mode 100644 index 0000000000..fb1ad18a45 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SNES_SPC.h @@ -0,0 +1,284 @@ +// SNES SPC-700 APU emulator + +// snes_spc 0.9.0 +#ifndef SNES_SPC_H +#define SNES_SPC_H + +#include "SPC_DSP.h" +#include "blargg_endian.h" +#include + +struct SNES_SPC { +public: + typedef BOOST::uint8_t uint8_t; + + // Must be called once before using + blargg_err_t init(); + + // Sample pairs generated per second + enum { sample_rate = 32000 }; + +// Emulator use + + // Sets IPL ROM data. Library does not include ROM data. Most SPC music files + // don't need ROM, but a full emulator must provide this. + enum { rom_size = 0x40 }; + void init_rom( uint8_t const rom [rom_size] ); + + // Sets destination for output samples + typedef short sample_t; + void set_output( sample_t* out, int out_size ); + + // Number of samples written to output since last set + int sample_count() const; + + // Resets SPC to power-on state. This resets your output buffer, so you must + // call set_output() after this. + void reset(); + + // Emulates pressing reset switch on SNES. This resets your output buffer, so + // you must call set_output() after this. + void soft_reset(); + + // 1024000 SPC clocks per second, sample pair every 32 clocks + typedef int time_t; + enum { clock_rate = 1024000 }; + enum { clocks_per_sample = 32 }; + + // Emulated port read/write at specified time + enum { port_count = 4 }; + int read_port ( time_t, int port ); + void write_port( time_t, int port, int data ); + + // Runs SPC to end_time and starts a new time frame at 0 + void end_frame( time_t end_time ); + + uint8_t* get_ram(); + +// Sound control + + // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). + // Reduces emulation accuracy. + enum { voice_count = 8 }; + void mute_voices( int mask ); + + // If true, prevents channels and global volumes from being phase-negated. + // Only supported by fast DSP. + void disable_surround( bool disable = true ); + + // Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc. + enum { tempo_unit = 0x100 }; + void set_tempo( int ); + +// SPC music files + + // Loads SPC data into emulator + enum { spc_min_file_size = 0x10180 }; + enum { spc_file_size = 0x10200 }; + blargg_err_t load_spc( void const* in, long size ); + + // Clears echo region. Useful after loading an SPC as many have garbage in echo. + void clear_echo(); + + // Plays for count samples and write samples to out. Discards samples if out + // is NULL. Count must be a multiple of 2 since output is stereo. + blargg_err_t play( int count, sample_t* out ); + + // Skips count samples. Several times faster than play() when using fast DSP. + blargg_err_t skip( int count ); + +// State save/load (only available with accurate DSP) + +#if !SPC_NO_COPY_STATE_FUNCS + // Saves/loads state + enum { state_size = 67 * 1024L }; // maximum space needed when saving + typedef SPC_DSP::copy_func_t copy_func_t; + void copy_state( unsigned char** io, copy_func_t ); + + // Writes minimal header to spc_out + static void init_header( void* spc_out ); + + // Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out. + // Does not set up SPC header; use init_header() for that. + void save_spc( void* spc_out ); + + // Returns true if new key-on events occurred since last check. Useful for + // trimming silence while saving an SPC. + bool check_kon(); +#endif + +public: + BLARGG_DISABLE_NOTHROW + + typedef BOOST::uint16_t uint16_t; + + // Time relative to m_spc_time. Speeds up code a bit by eliminating need to + // constantly add m_spc_time to time from CPU. CPU uses time that ends at + // 0 to eliminate reloading end time every instruction. It pays off. + typedef int rel_time_t; + + struct Timer + { + rel_time_t next_time; // time of next event + int prescaler; + int period; + int divider; + int enabled; + int counter; + }; + enum { reg_count = 0x10 }; + enum { timer_count = 3 }; + enum { extra_size = SPC_DSP::extra_size }; + + enum { signature_size = 35 }; + +private: + SPC_DSP dsp; + + #if SPC_LESS_ACCURATE + static signed char const reg_times_ [256]; + signed char reg_times [256]; + #endif + + struct state_t + { + Timer timers [timer_count]; + + uint8_t smp_regs [2] [reg_count]; + + struct + { + int pc; + int a; + int x; + int y; + int psw; + int sp; + } cpu_regs; + + rel_time_t dsp_time; + time_t spc_time; + bool echo_accessed; + + int tempo; + int skipped_kon; + int skipped_koff; + const char* cpu_error; + + int extra_clocks; + sample_t* buf_begin; + sample_t const* buf_end; + sample_t* extra_pos; + sample_t extra_buf [extra_size]; + + int rom_enabled; + uint8_t rom [rom_size]; + uint8_t hi_ram [rom_size]; + + unsigned char cycle_table [256]; + + struct + { + // padding to neutralize address overflow + union { + uint8_t padding1 [0x100]; + uint16_t align; // makes compiler align data for 16-bit access + } padding1 [1]; + uint8_t ram [0x10000]; + uint8_t padding2 [0x100]; + } ram; + }; + state_t m; + + enum { rom_addr = 0xFFC0 }; + + enum { skipping_time = 127 }; + + // Value that padding should be filled with + enum { cpu_pad_fill = 0xFF }; + + enum { + r_test = 0x0, r_control = 0x1, + r_dspaddr = 0x2, r_dspdata = 0x3, + r_cpuio0 = 0x4, r_cpuio1 = 0x5, + r_cpuio2 = 0x6, r_cpuio3 = 0x7, + r_f8 = 0x8, r_f9 = 0x9, + r_t0target = 0xA, r_t1target = 0xB, r_t2target = 0xC, + r_t0out = 0xD, r_t1out = 0xE, r_t2out = 0xF + }; + + void timers_loaded(); + void enable_rom( int enable ); + void reset_buf(); + void save_extra(); + void load_regs( uint8_t const in [reg_count] ); + void ram_loaded(); + void regs_loaded(); + void reset_time_regs(); + void reset_common( int timer_counter_init ); + + Timer* run_timer_ ( Timer* t, rel_time_t ); + Timer* run_timer ( Timer* t, rel_time_t ); + int dsp_read ( rel_time_t ); + void dsp_write ( int data, rel_time_t ); + void cpu_write_smp_reg_( int data, rel_time_t, int addr ); + void cpu_write_smp_reg ( int data, rel_time_t, int addr ); + void cpu_write_high ( int data, int i, rel_time_t ); + void cpu_write ( int data, int addr, rel_time_t ); + int cpu_read_smp_reg ( int i, rel_time_t ); + int cpu_read ( int addr, rel_time_t ); + unsigned CPU_mem_bit ( uint8_t const* pc, rel_time_t ); + + bool check_echo_access ( int addr ); + uint8_t* run_until_( time_t end_time ); + + struct spc_file_t + { + char signature [signature_size]; + uint8_t has_id666; + uint8_t version; + uint8_t pcl, pch; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t psw; + uint8_t sp; + char text [212]; + uint8_t ram [0x10000]; + uint8_t dsp [128]; + uint8_t unused [0x40]; + uint8_t ipl_rom [0x40]; + }; + + static char const signature [signature_size + 1]; + + void save_regs( uint8_t out [reg_count] ); +}; + +#include + +inline uint8_t* SNES_SPC::get_ram() { return m.ram.ram; } + +inline int SNES_SPC::sample_count() const { return (m.extra_clocks >> 5) * 2; } + +inline int SNES_SPC::read_port( time_t t, int port ) +{ + assert( (unsigned) port < port_count ); + return run_until_( t ) [port]; +} + +inline void SNES_SPC::write_port( time_t t, int port, int data ) +{ + assert( (unsigned) port < port_count ); + run_until_( t ) [0x10 + port] = data; +} + +inline void SNES_SPC::mute_voices( int mask ) { dsp.mute_voices( mask ); } + +inline void SNES_SPC::disable_surround( bool disable ) { dsp.disable_surround( disable ); } + +#if !SPC_NO_COPY_STATE_FUNCS +inline bool SNES_SPC::check_kon() { return dsp.check_kon(); } +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/SNES_SPC_misc.cpp b/waterbox/sameboy/snes_spc/SNES_SPC_misc.cpp new file mode 100644 index 0000000000..87288ab8ee --- /dev/null +++ b/waterbox/sameboy/snes_spc/SNES_SPC_misc.cpp @@ -0,0 +1,380 @@ +// SPC emulation support: init, sample buffering, reset, SPC loading + +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SNES_SPC.h" + +#include + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#define RAM (m.ram.ram) +#define REGS (m.smp_regs [0]) +#define REGS_IN (m.smp_regs [1]) + +// (n ? n : 256) +#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1) + + +//// Init + +blargg_err_t SNES_SPC::init() +{ + memset( &m, 0, sizeof m ); + dsp.init( RAM ); + + m.tempo = tempo_unit; + + // Most SPC music doesn't need ROM, and almost all the rest only rely + // on these two bytes + m.rom [0x3E] = 0xFF; + m.rom [0x3F] = 0xC0; + + static unsigned char const cycle_table [128] = + {// 01 23 45 67 89 AB CD EF + 0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x68, // 0 + 0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x46, // 1 + 0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x74, // 2 + 0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x38, // 3 + 0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x66, // 4 + 0x48,0x47,0x45,0x56,0x55,0x45,0x22,0x43, // 5 + 0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x75, // 6 + 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x36, // 7 + 0x28,0x47,0x34,0x36,0x26,0x54,0x52,0x45, // 8 + 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0xC5, // 9 + 0x38,0x47,0x34,0x36,0x26,0x44,0x52,0x44, // A + 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x34, // B + 0x38,0x47,0x45,0x47,0x25,0x64,0x52,0x49, // C + 0x48,0x47,0x56,0x67,0x45,0x55,0x22,0x83, // D + 0x28,0x47,0x34,0x36,0x24,0x53,0x43,0x40, // E + 0x48,0x47,0x45,0x56,0x34,0x54,0x22,0x60, // F + }; + + // unpack cycle table + for ( int i = 0; i < 128; i++ ) + { + int n = cycle_table [i]; + m.cycle_table [i * 2 + 0] = n >> 4; + m.cycle_table [i * 2 + 1] = n & 0x0F; + } + + #if SPC_LESS_ACCURATE + memcpy( reg_times, reg_times_, sizeof reg_times ); + #endif + + reset(); + return 0; +} + +void SNES_SPC::init_rom( uint8_t const in [rom_size] ) +{ + memcpy( m.rom, in, sizeof m.rom ); +} + +void SNES_SPC::set_tempo( int t ) +{ + m.tempo = t; + int const timer2_shift = 4; // 64 kHz + int const other_shift = 3; // 8 kHz + + #if SPC_DISABLE_TEMPO + m.timers [2].prescaler = timer2_shift; + m.timers [1].prescaler = timer2_shift + other_shift; + m.timers [0].prescaler = timer2_shift + other_shift; + #else + if ( !t ) + t = 1; + int const timer2_rate = 1 << timer2_shift; + int rate = (timer2_rate * tempo_unit + (t >> 1)) / t; + if ( rate < timer2_rate / 4 ) + rate = timer2_rate / 4; // max 4x tempo + m.timers [2].prescaler = rate; + m.timers [1].prescaler = rate << other_shift; + m.timers [0].prescaler = rate << other_shift; + #endif +} + +// Timer registers have been loaded. Applies these to the timers. Does not +// reset timer prescalers or dividers. +void SNES_SPC::timers_loaded() +{ + int i; + for ( i = 0; i < timer_count; i++ ) + { + Timer* t = &m.timers [i]; + t->period = IF_0_THEN_256( REGS [r_t0target + i] ); + t->enabled = REGS [r_control] >> i & 1; + t->counter = REGS_IN [r_t0out + i] & 0x0F; + } + + set_tempo( m.tempo ); +} + +// Loads registers from unified 16-byte format +void SNES_SPC::load_regs( uint8_t const in [reg_count] ) +{ + memcpy( REGS, in, reg_count ); + memcpy( REGS_IN, REGS, reg_count ); + + // These always read back as 0 + REGS_IN [r_test ] = 0; + REGS_IN [r_control ] = 0; + REGS_IN [r_t0target] = 0; + REGS_IN [r_t1target] = 0; + REGS_IN [r_t2target] = 0; +} + +// RAM was just loaded from SPC, with $F0-$FF containing SMP registers +// and timer counts. Copies these to proper registers. +void SNES_SPC::ram_loaded() +{ + m.rom_enabled = 0; + load_regs( &RAM [0xF0] ); + + // Put STOP instruction around memory to catch PC underflow/overflow + memset( m.ram.padding1, cpu_pad_fill, sizeof m.ram.padding1 ); + memset( m.ram.padding2, cpu_pad_fill, sizeof m.ram.padding2 ); +} + +// Registers were just loaded. Applies these new values. +void SNES_SPC::regs_loaded() +{ + enable_rom( REGS [r_control] & 0x80 ); + timers_loaded(); +} + +void SNES_SPC::reset_time_regs() +{ + m.cpu_error = 0; + m.echo_accessed = 0; + m.spc_time = 0; + m.dsp_time = 0; + #if SPC_LESS_ACCURATE + m.dsp_time = clocks_per_sample + 1; + #endif + + for ( int i = 0; i < timer_count; i++ ) + { + Timer* t = &m.timers [i]; + t->next_time = 1; + t->divider = 0; + } + + regs_loaded(); + + m.extra_clocks = 0; + reset_buf(); +} + +void SNES_SPC::reset_common( int timer_counter_init ) +{ + int i; + for ( i = 0; i < timer_count; i++ ) + REGS_IN [r_t0out + i] = timer_counter_init; + + // Run IPL ROM + memset( &m.cpu_regs, 0, sizeof m.cpu_regs ); + m.cpu_regs.pc = rom_addr; + + REGS [r_test ] = 0x0A; + REGS [r_control] = 0xB0; // ROM enabled, clear ports + for ( i = 0; i < port_count; i++ ) + REGS_IN [r_cpuio0 + i] = 0; + + reset_time_regs(); +} + +void SNES_SPC::soft_reset() +{ + reset_common( 0 ); + dsp.soft_reset(); +} + +void SNES_SPC::reset() +{ + memset( RAM, 0xFF, 0x10000 ); + ram_loaded(); + reset_common( 0x0F ); + dsp.reset(); +} + +char const SNES_SPC::signature [signature_size + 1] = + "SNES-SPC700 Sound File Data v0.30\x1A\x1A"; + +blargg_err_t SNES_SPC::load_spc( void const* data, long size ) +{ + spc_file_t const* const spc = (spc_file_t const*) data; + + // be sure compiler didn't insert any padding into fle_t + assert( sizeof (spc_file_t) == spc_min_file_size + 0x80 ); + + // Check signature and file size + if ( size < signature_size || memcmp( spc, signature, 27 ) ) + return "Not an SPC file"; + + if ( size < spc_min_file_size ) + return "Corrupt SPC file"; + + // CPU registers + m.cpu_regs.pc = spc->pch * 0x100 + spc->pcl; + m.cpu_regs.a = spc->a; + m.cpu_regs.x = spc->x; + m.cpu_regs.y = spc->y; + m.cpu_regs.psw = spc->psw; + m.cpu_regs.sp = spc->sp; + + // RAM and registers + memcpy( RAM, spc->ram, 0x10000 ); + ram_loaded(); + + // DSP registers + dsp.load( spc->dsp ); + + reset_time_regs(); + + return 0; +} + +void SNES_SPC::clear_echo() +{ + if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) ) + { + int addr = 0x100 * dsp.read( SPC_DSP::r_esa ); + int end = addr + 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F); + if ( end > 0x10000 ) + end = 0x10000; + memset( &RAM [addr], 0xFF, end - addr ); + } +} + + +//// Sample output + +void SNES_SPC::reset_buf() +{ + // Start with half extra buffer of silence + sample_t* out = m.extra_buf; + while ( out < &m.extra_buf [extra_size / 2] ) + *out++ = 0; + + m.extra_pos = out; + m.buf_begin = 0; + + dsp.set_output( 0, 0 ); +} + +void SNES_SPC::set_output( sample_t* out, int size ) +{ + require( (size & 1) == 0 ); // size must be even + + m.extra_clocks &= clocks_per_sample - 1; + if ( out ) + { + sample_t const* out_end = out + size; + m.buf_begin = out; + m.buf_end = out_end; + + // Copy extra to output + sample_t const* in = m.extra_buf; + while ( in < m.extra_pos && out < out_end ) + *out++ = *in++; + + // Handle output being full already + if ( out >= out_end ) + { + // Have DSP write to remaining extra space + out = dsp.extra(); + out_end = &dsp.extra() [extra_size]; + + // Copy any remaining extra samples as if DSP wrote them + while ( in < m.extra_pos ) + *out++ = *in++; + assert( out <= out_end ); + } + + dsp.set_output( out, out_end - out ); + } + else + { + reset_buf(); + } +} + +void SNES_SPC::save_extra() +{ + // Get end pointers + sample_t const* main_end = m.buf_end; // end of data written to buf + sample_t const* dsp_end = dsp.out_pos(); // end of data written to dsp.extra() + if ( m.buf_begin <= dsp_end && dsp_end <= main_end ) + { + main_end = dsp_end; + dsp_end = dsp.extra(); // nothing in DSP's extra + } + + // Copy any extra samples at these ends into extra_buf + sample_t* out = m.extra_buf; + sample_t const* in; + for ( in = m.buf_begin + sample_count(); in < main_end; in++ ) + *out++ = *in; + for ( in = dsp.extra(); in < dsp_end ; in++ ) + *out++ = *in; + + m.extra_pos = out; + assert( out <= &m.extra_buf [extra_size] ); +} + +blargg_err_t SNES_SPC::play( int count, sample_t* out ) +{ + require( (count & 1) == 0 ); // must be even + if ( count ) + { + set_output( out, count ); + end_frame( count * (clocks_per_sample / 2) ); + } + + const char* err = m.cpu_error; + m.cpu_error = 0; + return err; +} + +blargg_err_t SNES_SPC::skip( int count ) +{ + #if SPC_LESS_ACCURATE + if ( count > 2 * sample_rate * 2 ) + { + set_output( 0, 0 ); + + // Skip a multiple of 4 samples + time_t end = count; + count = (count & 3) + 1 * sample_rate * 2; + end = (end - count) * (clocks_per_sample / 2); + + m.skipped_kon = 0; + m.skipped_koff = 0; + + // Preserve DSP and timer synchronization + // TODO: verify that this really preserves it + int old_dsp_time = m.dsp_time + m.spc_time; + m.dsp_time = end - m.spc_time + skipping_time; + end_frame( end ); + m.dsp_time = m.dsp_time - skipping_time + old_dsp_time; + + dsp.write( SPC_DSP::r_koff, m.skipped_koff & ~m.skipped_kon ); + dsp.write( SPC_DSP::r_kon , m.skipped_kon ); + clear_echo(); + } + #endif + + return play( count, 0 ); +} diff --git a/waterbox/sameboy/snes_spc/SNES_SPC_state.cpp b/waterbox/sameboy/snes_spc/SNES_SPC_state.cpp new file mode 100644 index 0000000000..a8052b6587 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SNES_SPC_state.cpp @@ -0,0 +1,129 @@ +// SPC emulation state save/load: copy_state(), save_spc() +// Separate file to avoid linking in unless needed + +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SNES_SPC.h" + +#if !SPC_NO_COPY_STATE_FUNCS + +#include + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#define RAM (m.ram.ram) +#define REGS (m.smp_regs [0]) +#define REGS_IN (m.smp_regs [1]) + +void SNES_SPC::save_regs( uint8_t out [reg_count] ) +{ + // Use current timer counter values + for ( int i = 0; i < timer_count; i++ ) + out [r_t0out + i] = m.timers [i].counter; + + // Last written values + memcpy( out, REGS, r_t0out ); +} + +void SNES_SPC::init_header( void* spc_out ) +{ + spc_file_t* const spc = (spc_file_t*) spc_out; + + spc->has_id666 = 26; // has none + spc->version = 30; + memcpy( spc, signature, sizeof spc->signature ); + memset( spc->text, 0, sizeof spc->text ); +} + +void SNES_SPC::save_spc( void* spc_out ) +{ + spc_file_t* const spc = (spc_file_t*) spc_out; + + // CPU + spc->pcl = (uint8_t) (m.cpu_regs.pc >> 0); + spc->pch = (uint8_t) (m.cpu_regs.pc >> 8); + spc->a = m.cpu_regs.a; + spc->x = m.cpu_regs.x; + spc->y = m.cpu_regs.y; + spc->psw = m.cpu_regs.psw; + spc->sp = m.cpu_regs.sp; + + // RAM, ROM + memcpy( spc->ram, RAM, sizeof spc->ram ); + if ( m.rom_enabled ) + memcpy( spc->ram + rom_addr, m.hi_ram, sizeof m.hi_ram ); + memset( spc->unused, 0, sizeof spc->unused ); + memcpy( spc->ipl_rom, m.rom, sizeof spc->ipl_rom ); + + // SMP registers + save_regs( &spc->ram [0xF0] ); + int i; + for ( i = 0; i < port_count; i++ ) + spc->ram [0xF0 + r_cpuio0 + i] = REGS_IN [r_cpuio0 + i]; + + // DSP registers + for ( i = 0; i < SPC_DSP::register_count; i++ ) + spc->dsp [i] = dsp.read( i ); +} + +void SNES_SPC::copy_state( unsigned char** io, copy_func_t copy ) +{ + SPC_State_Copier copier( io, copy ); + + // Make state data more readable by putting 64K RAM, 16 SMP registers, + // then DSP (with its 128 registers) first + + // RAM + enable_rom( 0 ); // will get re-enabled if necessary in regs_loaded() below + copier.copy( RAM, 0x10000 ); + + { + // SMP registers + uint8_t out_ports [port_count]; + uint8_t regs [reg_count]; + memcpy( out_ports, ®S [r_cpuio0], sizeof out_ports ); + save_regs( regs ); + copier.copy( regs, sizeof regs ); + copier.copy( out_ports, sizeof out_ports ); + load_regs( regs ); + regs_loaded(); + memcpy( ®S [r_cpuio0], out_ports, sizeof out_ports ); + } + + // CPU registers + SPC_COPY( uint16_t, m.cpu_regs.pc ); + SPC_COPY( uint8_t, m.cpu_regs.a ); + SPC_COPY( uint8_t, m.cpu_regs.x ); + SPC_COPY( uint8_t, m.cpu_regs.y ); + SPC_COPY( uint8_t, m.cpu_regs.psw ); + SPC_COPY( uint8_t, m.cpu_regs.sp ); + copier.extra(); + + SPC_COPY( int16_t, m.spc_time ); + SPC_COPY( int16_t, m.dsp_time ); + + // DSP + dsp.copy_state( io, copy ); + + // Timers + for ( int i = 0; i < timer_count; i++ ) + { + Timer* t = &m.timers [i]; + SPC_COPY( int16_t, t->next_time ); + SPC_COPY( uint8_t, t->divider ); + copier.extra(); + } + copier.extra(); +} +#endif diff --git a/waterbox/sameboy/snes_spc/SPC_CPU.h b/waterbox/sameboy/snes_spc/SPC_CPU.h new file mode 100644 index 0000000000..664fc4886e --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_CPU.h @@ -0,0 +1,1220 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +//// Memory access + +#if SPC_MORE_ACCURACY + #define SUSPICIOUS_OPCODE( name ) ((void) 0) +#else + #define SUSPICIOUS_OPCODE( name ) dprintf( "SPC: suspicious opcode: " name "\n" ) +#endif + +#define CPU_READ( time, offset, addr )\ + cpu_read( addr, time + offset ) + +#define CPU_WRITE( time, offset, addr, data )\ + cpu_write( data, addr, time + offset ) + +#if SPC_MORE_ACCURACY + #define CPU_READ_TIMER( time, offset, addr, out )\ + { out = CPU_READ( time, offset, addr ); } + +#else + // timers are by far the most common thing read from dp + #define CPU_READ_TIMER( time, offset, addr_, out )\ + {\ + rel_time_t adj_time = time + offset;\ + int dp_addr = addr_;\ + int ti = dp_addr - (r_t0out + 0xF0);\ + if ( (unsigned) ti < timer_count )\ + {\ + Timer* t = &m.timers [ti];\ + if ( adj_time >= t->next_time )\ + t = run_timer_( t, adj_time );\ + out = t->counter;\ + t->counter = 0;\ + }\ + else\ + {\ + out = ram [dp_addr];\ + int i = dp_addr - 0xF0;\ + if ( (unsigned) i < 0x10 )\ + out = cpu_read_smp_reg( i, adj_time );\ + }\ + } +#endif + +#define TIME_ADJ( n ) (n) + +#define READ_TIMER( time, addr, out ) CPU_READ_TIMER( rel_time, TIME_ADJ(time), (addr), out ) +#define READ( time, addr ) CPU_READ ( rel_time, TIME_ADJ(time), (addr) ) +#define WRITE( time, addr, data ) CPU_WRITE( rel_time, TIME_ADJ(time), (addr), (data) ) + +#define DP_ADDR( addr ) (dp + (addr)) + +#define READ_DP_TIMER( time, addr, out ) CPU_READ_TIMER( rel_time, TIME_ADJ(time), DP_ADDR( addr ), out ) +#define READ_DP( time, addr ) READ ( time, DP_ADDR( addr ) ) +#define WRITE_DP( time, addr, data ) WRITE( time, DP_ADDR( addr ), data ) + +#define READ_PROG16( addr ) GET_LE16( ram + (addr) ) + +#define SET_PC( n ) (pc = ram + (n)) +#define GET_PC() (pc - ram) +#define READ_PC( pc ) (*(pc)) +#define READ_PC16( pc ) GET_LE16( pc ) + +// TODO: remove non-wrapping versions? +#define SPC_NO_SP_WRAPAROUND 0 + +#define SET_SP( v ) (sp = ram + 0x101 + (v)) +#define GET_SP() (sp - 0x101 - ram) + +#if SPC_NO_SP_WRAPAROUND +#define PUSH16( v ) (sp -= 2, SET_LE16( sp, v )) +#define PUSH( v ) (void) (*--sp = (uint8_t) (v)) +#define POP( out ) (void) ((out) = *sp++) + +#else +#define PUSH16( data )\ +{\ + int addr = (sp -= 2) - ram;\ + if ( addr > 0x100 )\ + {\ + SET_LE16( sp, data );\ + }\ + else\ + {\ + ram [(uint8_t) addr + 0x100] = (uint8_t) data;\ + sp [1] = (uint8_t) (data >> 8);\ + sp += 0x100;\ + }\ +} + +#define PUSH( data )\ +{\ + *--sp = (uint8_t) (data);\ + if ( sp - ram == 0x100 )\ + sp += 0x100;\ +} + +#define POP( out )\ +{\ + out = *sp++;\ + if ( sp - ram == 0x201 )\ + {\ + out = sp [-0x101];\ + sp -= 0x100;\ + }\ +} + +#endif + +#define MEM_BIT( rel ) CPU_mem_bit( pc, rel_time + rel ) + +unsigned SNES_SPC::CPU_mem_bit( uint8_t const* pc, rel_time_t rel_time ) +{ + unsigned addr = READ_PC16( pc ); + unsigned t = READ( 0, addr & 0x1FFF ) >> (addr >> 13); + return t << 8 & 0x100; +} + +//// Status flag handling + +// Hex value in name to clarify code and bit shifting. +// Flag stored in indicated variable during emulation +int const n80 = 0x80; // nz +int const v40 = 0x40; // psw +int const p20 = 0x20; // dp +int const b10 = 0x10; // psw +int const h08 = 0x08; // psw +int const i04 = 0x04; // psw +int const z02 = 0x02; // nz +int const c01 = 0x01; // c + +int const nz_neg_mask = 0x880; // either bit set indicates N flag set + +#define GET_PSW( out )\ +{\ + out = psw & ~(n80 | p20 | z02 | c01);\ + out |= c >> 8 & c01;\ + out |= dp >> 3 & p20;\ + out |= ((nz >> 4) | nz) & n80;\ + if ( !(uint8_t) nz ) out |= z02;\ +} + +#define SET_PSW( in )\ +{\ + psw = in;\ + c = in << 8;\ + dp = in << 3 & 0x100;\ + nz = (in << 4 & 0x800) | (~in & z02);\ +} + +SPC_CPU_RUN_FUNC +{ + uint8_t* const ram = RAM; + int a = m.cpu_regs.a; + int x = m.cpu_regs.x; + int y = m.cpu_regs.y; + uint8_t const* pc; + uint8_t* sp; + int psw; + int c; + int nz; + int dp; + + SET_PC( m.cpu_regs.pc ); + SET_SP( m.cpu_regs.sp ); + SET_PSW( m.cpu_regs.psw ); + + goto loop; + + + // Main loop + +cbranch_taken_loop: + pc += *(BOOST::int8_t const*) pc; +inc_pc_loop: + pc++; +loop: +{ + unsigned opcode; + unsigned data; + + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + opcode = *pc; + if ( (rel_time += m.cycle_table [opcode]) > 0 ) + goto out_of_time; + + #ifdef SPC_CPU_OPCODE_HOOK + SPC_CPU_OPCODE_HOOK( GET_PC(), opcode ); + #endif + /* + //SUB_CASE_COUNTER( 1 ); + #define PROFILE_TIMER_LOOP( op, addr, len )\ + if ( opcode == op )\ + {\ + int cond = (unsigned) ((addr) - 0xFD) < 3 &&\ + pc [len] == 0xF0 && pc [len+1] == 0xFE - len;\ + SUB_CASE_COUNTER( op && cond );\ + } + + PROFILE_TIMER_LOOP( 0xEC, GET_LE16( pc + 1 ), 3 ); + PROFILE_TIMER_LOOP( 0xEB, pc [1], 2 ); + PROFILE_TIMER_LOOP( 0xE4, pc [1], 2 ); + */ + + // TODO: if PC is at end of memory, this will get wrong operand (very obscure) + data = *++pc; + switch ( opcode ) + { + +// Common instructions + +#define BRANCH( cond )\ +{\ + pc++;\ + pc += (BOOST::int8_t) data;\ + if ( cond )\ + goto loop;\ + pc -= (BOOST::int8_t) data;\ + rel_time -= 2;\ + goto loop;\ +} + + case 0xF0: // BEQ + BRANCH( !(uint8_t) nz ) // 89% taken + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ) + + case 0x3F:{// CALL + int old_addr = GET_PC() + 2; + SET_PC( READ_PC16( pc ) ); + PUSH16( old_addr ); + goto loop; + } + + case 0x6F:// RET + #if SPC_NO_SP_WRAPAROUND + { + SET_PC( GET_LE16( sp ) ); + sp += 2; + } + #else + { + int addr = sp - ram; + SET_PC( GET_LE16( sp ) ); + sp += 2; + if ( addr < 0x1FF ) + goto loop; + + SET_PC( sp [-0x101] * 0x100 + ram [(uint8_t) addr + 0x100] ); + sp -= 0x100; + } + #endif + goto loop; + + case 0xE4: // MOV a,dp + ++pc; + // 80% from timer + READ_DP_TIMER( 0, data, a = nz ); + goto loop; + + case 0xFA:{// MOV dp,dp + int temp; + READ_DP_TIMER( -2, data, temp ); + data = temp + no_read_before_write ; + } + // fall through + case 0x8F:{// MOV dp,#imm + int temp = READ_PC( pc + 1 ); + pc += 2; + + #if !SPC_MORE_ACCURACY + { + int i = dp + temp; + ram [i] = (uint8_t) data; + i -= 0xF0; + if ( (unsigned) i < 0x10 ) // 76% + { + REGS [i] = (uint8_t) data; + + // Registers other than $F2 and $F4-$F7 + //if ( i != 2 && i != 4 && i != 5 && i != 6 && i != 7 ) + if ( ((~0x2F00 << (bits_in_int - 16)) << i) < 0 ) // 12% + cpu_write_smp_reg( data, rel_time, i ); + } + } + #else + WRITE_DP( 0, temp, data ); + #endif + goto loop; + } + + case 0xC4: // MOV dp,a + ++pc; + #if !SPC_MORE_ACCURACY + { + int i = dp + data; + ram [i] = (uint8_t) a; + i -= 0xF0; + if ( (unsigned) i < 0x10 ) // 39% + { + unsigned sel = i - 2; + REGS [i] = (uint8_t) a; + + if ( sel == 1 ) // 51% $F3 + dsp_write( a, rel_time ); + else if ( sel > 1 ) // 1% not $F2 or $F3 + cpu_write_smp_reg_( a, rel_time, i ); + } + } + #else + WRITE_DP( 0, data, a ); + #endif + goto loop; + +#define CASE( n ) case n: + +// Define common address modes based on opcode for immediate mode. Execution +// ends with data set to the address of the operand. +#define ADDR_MODES_( op )\ + CASE( op - 0x02 ) /* (X) */\ + data = x + dp;\ + pc--;\ + goto end_##op;\ + CASE( op + 0x0F ) /* (dp)+Y */\ + data = READ_PROG16( data + dp ) + y;\ + goto end_##op;\ + CASE( op - 0x01 ) /* (dp+X) */\ + data = READ_PROG16( ((uint8_t) (data + x)) + dp );\ + goto end_##op;\ + CASE( op + 0x0E ) /* abs+Y */\ + data += y;\ + goto abs_##op;\ + CASE( op + 0x0D ) /* abs+X */\ + data += x;\ + CASE( op - 0x03 ) /* abs */\ + abs_##op:\ + data += 0x100 * READ_PC( ++pc );\ + goto end_##op;\ + CASE( op + 0x0C ) /* dp+X */\ + data = (uint8_t) (data + x); + +#define ADDR_MODES_NO_DP( op )\ + ADDR_MODES_( op )\ + data += dp;\ + end_##op: + +#define ADDR_MODES( op )\ + ADDR_MODES_( op )\ + CASE( op - 0x04 ) /* dp */\ + data += dp;\ + end_##op: + +// 1. 8-bit Data Transmission Commands. Group I + + ADDR_MODES_NO_DP( 0xE8 ) // MOV A,addr + a = nz = READ( 0, data ); + goto inc_pc_loop; + + case 0xBF:{// MOV A,(X)+ + int temp = x + dp; + x = (uint8_t) (x + 1); + a = nz = READ( -1, temp ); + goto loop; + } + + case 0xE8: // MOV A,imm + a = data; + nz = data; + goto inc_pc_loop; + + case 0xF9: // MOV X,dp+Y + data = (uint8_t) (data + y); + case 0xF8: // MOV X,dp + READ_DP_TIMER( 0, data, x = nz ); + goto inc_pc_loop; + + case 0xE9: // MOV X,abs + data = READ_PC16( pc ); + ++pc; + data = READ( 0, data ); + case 0xCD: // MOV X,imm + x = data; + nz = data; + goto inc_pc_loop; + + case 0xFB: // MOV Y,dp+X + data = (uint8_t) (data + x); + case 0xEB: // MOV Y,dp + // 70% from timer + pc++; + READ_DP_TIMER( 0, data, y = nz ); + goto loop; + + case 0xEC:{// MOV Y,abs + int temp = READ_PC16( pc ); + pc += 2; + READ_TIMER( 0, temp, y = nz ); + //y = nz = READ( 0, temp ); + goto loop; + } + + case 0x8D: // MOV Y,imm + y = data; + nz = data; + goto inc_pc_loop; + +// 2. 8-BIT DATA TRANSMISSION COMMANDS, GROUP 2 + + ADDR_MODES_NO_DP( 0xC8 ) // MOV addr,A + WRITE( 0, data, a ); + goto inc_pc_loop; + + { + int temp; + case 0xCC: // MOV abs,Y + temp = y; + goto mov_abs_temp; + case 0xC9: // MOV abs,X + temp = x; + mov_abs_temp: + WRITE( 0, READ_PC16( pc ), temp ); + pc += 2; + goto loop; + } + + case 0xD9: // MOV dp+Y,X + data = (uint8_t) (data + y); + case 0xD8: // MOV dp,X + WRITE( 0, data + dp, x ); + goto inc_pc_loop; + + case 0xDB: // MOV dp+X,Y + data = (uint8_t) (data + x); + case 0xCB: // MOV dp,Y + WRITE( 0, data + dp, y ); + goto inc_pc_loop; + +// 3. 8-BIT DATA TRANSMISSIN COMMANDS, GROUP 3. + + case 0x7D: // MOV A,X + a = x; + nz = x; + goto loop; + + case 0xDD: // MOV A,Y + a = y; + nz = y; + goto loop; + + case 0x5D: // MOV X,A + x = a; + nz = a; + goto loop; + + case 0xFD: // MOV Y,A + y = a; + nz = a; + goto loop; + + case 0x9D: // MOV X,SP + x = nz = GET_SP(); + goto loop; + + case 0xBD: // MOV SP,X + SET_SP( x ); + goto loop; + + //case 0xC6: // MOV (X),A (handled by MOV addr,A in group 2) + + case 0xAF: // MOV (X)+,A + WRITE_DP( 0, x, a + no_read_before_write ); + x++; + goto loop; + +// 5. 8-BIT LOGIC OPERATION COMMANDS + +#define LOGICAL_OP( op, func )\ + ADDR_MODES( op ) /* addr */\ + data = READ( 0, data );\ + case op: /* imm */\ + nz = a func##= data;\ + goto inc_pc_loop;\ + { unsigned addr;\ + case op + 0x11: /* X,Y */\ + data = READ_DP( -2, y );\ + addr = x + dp;\ + goto addr_##op;\ + case op + 0x01: /* dp,dp */\ + data = READ_DP( -3, data );\ + case op + 0x10:{/*dp,imm*/\ + uint8_t const* addr2 = pc + 1;\ + pc += 2;\ + addr = READ_PC( addr2 ) + dp;\ + }\ + addr_##op:\ + nz = data func READ( -1, addr );\ + WRITE( 0, addr, nz );\ + goto loop;\ + } + + LOGICAL_OP( 0x28, & ); // AND + + LOGICAL_OP( 0x08, | ); // OR + + LOGICAL_OP( 0x48, ^ ); // EOR + +// 4. 8-BIT ARITHMETIC OPERATION COMMANDS + + ADDR_MODES( 0x68 ) // CMP addr + data = READ( 0, data ); + case 0x68: // CMP imm + nz = a - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x79: // CMP (X),(Y) + data = READ_DP( -2, y ); + nz = READ_DP( -1, x ) - data; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x69: // CMP dp,dp + data = READ_DP( -3, data ); + case 0x78: // CMP dp,imm + nz = READ_DP( -1, READ_PC( ++pc ) ) - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x3E: // CMP X,dp + data += dp; + goto cmp_x_addr; + case 0x1E: // CMP X,abs + data = READ_PC16( pc ); + pc++; + cmp_x_addr: + data = READ( 0, data ); + case 0xC8: // CMP X,imm + nz = x - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x7E: // CMP Y,dp + data += dp; + goto cmp_y_addr; + case 0x5E: // CMP Y,abs + data = READ_PC16( pc ); + pc++; + cmp_y_addr: + data = READ( 0, data ); + case 0xAD: // CMP Y,imm + nz = y - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + { + int addr; + case 0xB9: // SBC (x),(y) + case 0x99: // ADC (x),(y) + pc--; // compensate for inc later + data = READ_DP( -2, y ); + addr = x + dp; + goto adc_addr; + case 0xA9: // SBC dp,dp + case 0x89: // ADC dp,dp + data = READ_DP( -3, data ); + case 0xB8: // SBC dp,imm + case 0x98: // ADC dp,imm + addr = READ_PC( ++pc ) + dp; + adc_addr: + nz = READ( -1, addr ); + goto adc_data; + +// catch ADC and SBC together, then decode later based on operand +#undef CASE +#define CASE( n ) case n: case (n) + 0x20: + ADDR_MODES( 0x88 ) // ADC/SBC addr + data = READ( 0, data ); + case 0xA8: // SBC imm + case 0x88: // ADC imm + addr = -1; // A + nz = a; + adc_data: { + int flags; + if ( opcode >= 0xA0 ) // SBC + data ^= 0xFF; + + flags = data ^ nz; + nz += data + (c >> 8 & 1); + flags ^= nz; + + psw = (psw & ~(v40 | h08)) | + (flags >> 1 & h08) | + ((flags + 0x80) >> 2 & v40); + c = nz; + if ( addr < 0 ) + { + a = (uint8_t) nz; + goto inc_pc_loop; + } + WRITE( 0, addr, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + } + + } + +// 6. ADDITION & SUBTRACTION COMMANDS + +#define INC_DEC_REG( reg, op )\ + nz = reg op;\ + reg = (uint8_t) nz;\ + goto loop; + + case 0xBC: INC_DEC_REG( a, + 1 ) // INC A + case 0x3D: INC_DEC_REG( x, + 1 ) // INC X + case 0xFC: INC_DEC_REG( y, + 1 ) // INC Y + + case 0x9C: INC_DEC_REG( a, - 1 ) // DEC A + case 0x1D: INC_DEC_REG( x, - 1 ) // DEC X + case 0xDC: INC_DEC_REG( y, - 1 ) // DEC Y + + case 0x9B: // DEC dp+X + case 0xBB: // INC dp+X + data = (uint8_t) (data + x); + case 0x8B: // DEC dp + case 0xAB: // INC dp + data += dp; + goto inc_abs; + case 0x8C: // DEC abs + case 0xAC: // INC abs + data = READ_PC16( pc ); + pc++; + inc_abs: + nz = (opcode >> 4 & 2) - 1; + nz += READ( -1, data ); + WRITE( 0, data, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + +// 7. SHIFT, ROTATION COMMANDS + + case 0x5C: // LSR A + c = 0; + case 0x7C:{// ROR A + nz = (c >> 1 & 0x80) | (a >> 1); + c = a << 8; + a = nz; + goto loop; + } + + case 0x1C: // ASL A + c = 0; + case 0x3C:{// ROL A + int temp = c >> 8 & 1; + c = a << 1; + nz = c | temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x0B: // ASL dp + c = 0; + data += dp; + goto rol_mem; + case 0x1B: // ASL dp+X + c = 0; + case 0x3B: // ROL dp+X + data = (uint8_t) (data + x); + case 0x2B: // ROL dp + data += dp; + goto rol_mem; + case 0x0C: // ASL abs + c = 0; + case 0x2C: // ROL abs + data = READ_PC16( pc ); + pc++; + rol_mem: + nz = c >> 8 & 1; + nz |= (c = READ( -1, data ) << 1); + WRITE( 0, data, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + + case 0x4B: // LSR dp + c = 0; + data += dp; + goto ror_mem; + case 0x5B: // LSR dp+X + c = 0; + case 0x7B: // ROR dp+X + data = (uint8_t) (data + x); + case 0x6B: // ROR dp + data += dp; + goto ror_mem; + case 0x4C: // LSR abs + c = 0; + case 0x6C: // ROR abs + data = READ_PC16( pc ); + pc++; + ror_mem: { + int temp = READ( -1, data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + WRITE( 0, data, nz ); + goto inc_pc_loop; + } + + case 0x9F: // XCN + nz = a = (a >> 4) | (uint8_t) (a << 4); + goto loop; + +// 8. 16-BIT TRANSMISION COMMANDS + + case 0xBA: // MOVW YA,dp + a = READ_DP( -2, data ); + nz = (a & 0x7F) | (a >> 1); + y = READ_DP( 0, (uint8_t) (data + 1) ); + nz |= y; + goto inc_pc_loop; + + case 0xDA: // MOVW dp,YA + WRITE_DP( -1, data, a ); + WRITE_DP( 0, (uint8_t) (data + 1), y + no_read_before_write ); + goto inc_pc_loop; + +// 9. 16-BIT OPERATION COMMANDS + + case 0x3A: // INCW dp + case 0x1A:{// DECW dp + int temp; + // low byte + data += dp; + temp = READ( -3, data ); + temp += (opcode >> 4 & 2) - 1; // +1 for INCW, -1 for DECW + nz = ((temp >> 1) | temp) & 0x7F; + WRITE( -2, data, /*(uint8_t)*/ temp ); + + // high byte + data = (uint8_t) (data + 1) + dp; + temp = (uint8_t) ((temp >> 8) + READ( -1, data )); + nz |= temp; + WRITE( 0, data, temp ); + + goto inc_pc_loop; + } + + case 0x7A: // ADDW YA,dp + case 0x9A:{// SUBW YA,dp + int lo = READ_DP( -2, data ); + int hi = READ_DP( 0, (uint8_t) (data + 1) ); + int result; + int flags; + + if ( opcode == 0x9A ) // SUBW + { + lo = (lo ^ 0xFF) + 1; + hi ^= 0xFF; + } + + lo += a; + result = y + hi + (lo >> 8); + flags = hi ^ y ^ result; + + psw = (psw & ~(v40 | h08)) | + (flags >> 1 & h08) | + ((flags + 0x80) >> 2 & v40); + c = result; + a = (uint8_t) lo; + result = (uint8_t) result; + y = result; + nz = (((lo >> 1) | lo) & 0x7F) | result; + + goto inc_pc_loop; + } + + case 0x5A: { // CMPW YA,dp + int temp = a - READ_DP( -1, data ); + nz = ((temp >> 1) | temp) & 0x7F; + temp = y + (temp >> 8); + temp -= READ_DP( 0, (uint8_t) (data + 1) ); + nz |= temp; + c = ~temp; + nz &= 0xFF; + goto inc_pc_loop; + } + +// 10. MULTIPLICATION & DIVISON COMMANDS + + case 0xCF: { // MUL YA + unsigned temp = y * a; + a = (uint8_t) temp; + nz = ((temp >> 1) | temp) & 0x7F; + y = temp >> 8; + nz |= y; + goto loop; + } + + case 0x9E: // DIV YA,X + { + unsigned ya = y * 0x100 + a; + + psw &= ~(h08 | v40); + + if ( y >= x ) + psw |= v40; + + if ( (y & 15) >= (x & 15) ) + psw |= h08; + + if ( y < x * 2 ) + { + a = ya / x; + y = ya - a * x; + } + else + { + a = 255 - (ya - x * 0x200) / (256 - x); + y = x + (ya - x * 0x200) % (256 - x); + } + + nz = (uint8_t) a; + a = (uint8_t) a; + + goto loop; + } + +// 11. DECIMAL COMPENSATION COMMANDS + + case 0xDF: // DAA + SUSPICIOUS_OPCODE( "DAA" ); + if ( a > 0x99 || c & 0x100 ) + { + a += 0x60; + c = 0x100; + } + + if ( (a & 0x0F) > 9 || psw & h08 ) + a += 0x06; + + nz = a; + a = (uint8_t) a; + goto loop; + + case 0xBE: // DAS + SUSPICIOUS_OPCODE( "DAS" ); + if ( a > 0x99 || !(c & 0x100) ) + { + a -= 0x60; + c = 0; + } + + if ( (a & 0x0F) > 9 || !(psw & h08) ) + a -= 0x06; + + nz = a; + a = (uint8_t) a; + goto loop; + +// 12. BRANCHING COMMANDS + + case 0x2F: // BRA rel + pc += (BOOST::int8_t) data; + goto inc_pc_loop; + + case 0x30: // BMI + BRANCH( (nz & nz_neg_mask) ) + + case 0x10: // BPL + BRANCH( !(nz & nz_neg_mask) ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x70: // BVS + BRANCH( psw & v40 ) + + case 0x50: // BVC + BRANCH( !(psw & v40) ) + + #define CBRANCH( cond )\ + {\ + pc++;\ + if ( cond )\ + goto cbranch_taken_loop;\ + rel_time -= 2;\ + goto inc_pc_loop;\ + } + + case 0x03: // BBS dp.bit,rel + case 0x23: + case 0x43: + case 0x63: + case 0x83: + case 0xA3: + case 0xC3: + case 0xE3: + CBRANCH( READ_DP( -4, data ) >> (opcode >> 5) & 1 ) + + case 0x13: // BBC dp.bit,rel + case 0x33: + case 0x53: + case 0x73: + case 0x93: + case 0xB3: + case 0xD3: + case 0xF3: + CBRANCH( !(READ_DP( -4, data ) >> (opcode >> 5) & 1) ) + + case 0xDE: // CBNE dp+X,rel + data = (uint8_t) (data + x); + // fall through + case 0x2E:{// CBNE dp,rel + int temp; + // 61% from timer + READ_DP_TIMER( -4, data, temp ); + CBRANCH( temp != a ) + } + + case 0x6E: { // DBNZ dp,rel + unsigned temp = READ_DP( -4, data ) - 1; + WRITE_DP( -3, (uint8_t) data, /*(uint8_t)*/ temp + no_read_before_write ); + CBRANCH( temp ) + } + + case 0xFE: // DBNZ Y,rel + y = (uint8_t) (y - 1); + BRANCH( y ) + + case 0x1F: // JMP [abs+X] + SET_PC( READ_PC16( pc ) + x ); + // fall through + case 0x5F: // JMP abs + SET_PC( READ_PC16( pc ) ); + goto loop; + +// 13. SUB-ROUTINE CALL RETURN COMMANDS + + case 0x0F:{// BRK + int temp; + int ret_addr = GET_PC(); + SUSPICIOUS_OPCODE( "BRK" ); + SET_PC( READ_PROG16( 0xFFDE ) ); // vector address verified + PUSH16( ret_addr ); + GET_PSW( temp ); + psw = (psw | b10) & ~i04; + PUSH( temp ); + goto loop; + } + + case 0x4F:{// PCALL offset + int ret_addr = GET_PC() + 1; + SET_PC( 0xFF00 | data ); + PUSH16( ret_addr ); + goto loop; + } + + case 0x01: // TCALL n + case 0x11: + case 0x21: + case 0x31: + case 0x41: + case 0x51: + case 0x61: + case 0x71: + case 0x81: + case 0x91: + case 0xA1: + case 0xB1: + case 0xC1: + case 0xD1: + case 0xE1: + case 0xF1: { + int ret_addr = GET_PC(); + SET_PC( READ_PROG16( 0xFFDE - (opcode >> 3) ) ); + PUSH16( ret_addr ); + goto loop; + } + +// 14. STACK OPERATION COMMANDS + + { + int temp; + case 0x7F: // RET1 + temp = *sp; + SET_PC( GET_LE16( sp + 1 ) ); + sp += 3; + goto set_psw; + case 0x8E: // POP PSW + POP( temp ); + set_psw: + SET_PSW( temp ); + goto loop; + } + + case 0x0D: { // PUSH PSW + int temp; + GET_PSW( temp ); + PUSH( temp ); + goto loop; + } + + case 0x2D: // PUSH A + PUSH( a ); + goto loop; + + case 0x4D: // PUSH X + PUSH( x ); + goto loop; + + case 0x6D: // PUSH Y + PUSH( y ); + goto loop; + + case 0xAE: // POP A + POP( a ); + goto loop; + + case 0xCE: // POP X + POP( x ); + goto loop; + + case 0xEE: // POP Y + POP( y ); + goto loop; + +// 15. BIT OPERATION COMMANDS + + case 0x02: // SET1 + case 0x22: + case 0x42: + case 0x62: + case 0x82: + case 0xA2: + case 0xC2: + case 0xE2: + case 0x12: // CLR1 + case 0x32: + case 0x52: + case 0x72: + case 0x92: + case 0xB2: + case 0xD2: + case 0xF2: { + int bit = 1 << (opcode >> 5); + int mask = ~bit; + if ( opcode & 0x10 ) + bit = 0; + data += dp; + WRITE( 0, data, (READ( -1, data ) & mask) | bit ); + goto inc_pc_loop; + } + + case 0x0E: // TSET1 abs + case 0x4E: // TCLR1 abs + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -2, data ); + nz = (uint8_t) (a - temp); + temp &= ~a; + if ( opcode == 0x0E ) + temp |= a; + WRITE( 0, data, temp ); + } + goto loop; + + case 0x4A: // AND1 C,mem.bit + c &= MEM_BIT( 0 ); + pc += 2; + goto loop; + + case 0x6A: // AND1 C,/mem.bit + c &= ~MEM_BIT( 0 ); + pc += 2; + goto loop; + + case 0x0A: // OR1 C,mem.bit + c |= MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0x2A: // OR1 C,/mem.bit + c |= ~MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0x8A: // EOR1 C,mem.bit + c ^= MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0xEA: // NOT1 mem.bit + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -1, data & 0x1FFF ); + temp ^= 1 << (data >> 13); + WRITE( 0, data & 0x1FFF, temp ); + } + goto loop; + + case 0xCA: // MOV1 mem.bit,C + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -2, data & 0x1FFF ); + unsigned bit = data >> 13; + temp = (temp & ~(1 << bit)) | ((c >> 8 & 1) << bit); + WRITE( 0, data & 0x1FFF, temp + no_read_before_write ); + } + goto loop; + + case 0xAA: // MOV1 C,mem.bit + c = MEM_BIT( 0 ); + pc += 2; + goto loop; + +// 16. PROGRAM PSW FLAG OPERATION COMMANDS + + case 0x60: // CLRC + c = 0; + goto loop; + + case 0x80: // SETC + c = ~0; + goto loop; + + case 0xED: // NOTC + c ^= 0x100; + goto loop; + + case 0xE0: // CLRV + psw &= ~(v40 | h08); + goto loop; + + case 0x20: // CLRP + dp = 0; + goto loop; + + case 0x40: // SETP + dp = 0x100; + goto loop; + + case 0xA0: // EI + SUSPICIOUS_OPCODE( "EI" ); + psw |= i04; + goto loop; + + case 0xC0: // DI + SUSPICIOUS_OPCODE( "DI" ); + psw &= ~i04; + goto loop; + +// 17. OTHER COMMANDS + + case 0x00: // NOP + goto loop; + + case 0xFF:{// STOP + // handle PC wrap-around + unsigned addr = GET_PC() - 1; + if ( addr >= 0x10000 ) + { + addr &= 0xFFFF; + SET_PC( addr ); + dprintf( "SPC: PC wrapped around\n" ); + goto loop; + } + } + // fall through + case 0xEF: // SLEEP + SUSPICIOUS_OPCODE( "STOP/SLEEP" ); + --pc; + rel_time = 0; + m.cpu_error = "SPC emulation error"; + goto stop; + } // switch + + assert( 0 ); // catch any unhandled instructions +} +out_of_time: + rel_time -= m.cycle_table [*pc]; // undo partial execution of opcode +stop: + + // Uncache registers + if ( GET_PC() >= 0x10000 ) + dprintf( "SPC: PC wrapped around\n" ); + m.cpu_regs.pc = (uint16_t) GET_PC(); + m.cpu_regs.sp = ( uint8_t) GET_SP(); + m.cpu_regs.a = ( uint8_t) a; + m.cpu_regs.x = ( uint8_t) x; + m.cpu_regs.y = ( uint8_t) y; + { + int temp; + GET_PSW( temp ); + m.cpu_regs.psw = (uint8_t) temp; + } +} +SPC_CPU_RUN_FUNC_END diff --git a/waterbox/sameboy/snes_spc/SPC_DSP.cpp b/waterbox/sameboy/snes_spc/SPC_DSP.cpp new file mode 100644 index 0000000000..dd180506fc --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_DSP.cpp @@ -0,0 +1,1018 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SPC_DSP.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +#if INT_MAX < 0x7FFFFFFF + #error "Requires that int type have at least 32 bits" +#endif + +// TODO: add to blargg_endian.h +#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) +#define GET_LE16A( addr ) GET_LE16( addr ) +#define SET_LE16A( addr, data ) SET_LE16( addr, data ) + +static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = +{ + 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, + 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, + 0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A, + 0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF, + 0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67, + 0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF, + 0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F, + 0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF +}; + +// if ( io < -32768 ) io = -32768; +// if ( io > 32767 ) io = 32767; +#define CLAMP16( io )\ +{\ + if ( (int16_t) io != io )\ + io = (io >> 31) ^ 0x7FFF;\ +} + +// Access global DSP register +#define REG(n) m.regs [r_##n] + +// Access voice DSP register +#define VREG(r,n) r [v_##n] + +#define WRITE_SAMPLES( l, r, out ) \ +{\ + out [0] = l;\ + out [1] = r;\ + out += 2;\ + if ( out >= m.out_end )\ + {\ + check( out == m.out_end );\ + check( m.out_end != &m.extra [extra_size] || \ + (m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\ + out = m.extra;\ + m.out_end = &m.extra [extra_size];\ + }\ +}\ + +void SPC_DSP::set_output( sample_t* out, int size ) +{ + require( (size & 1) == 0 ); // must be even + if ( !out ) + { + out = m.extra; + size = extra_size; + } + m.out_begin = out; + m.out = out; + m.out_end = out + size; +} + +// Volume registers and efb are signed! Easy to forget int8_t cast. +// Prefixes are to avoid accidental use of locals with same names. + +// Gaussian interpolation + +static short const gauss [512] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, + 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, + 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77, + 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102, + 104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132, + 134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168, + 171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210, + 212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257, + 260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311, + 314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370, + 374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434, + 439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504, + 508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577, + 582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654, + 659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732, + 737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811, + 816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889, + 894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965, + 969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036, +1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102, +1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160, +1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210, +1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251, +1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280, +1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298, +1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, +}; + +inline int SPC_DSP::interpolate( voice_t const* v ) +{ + // Make pointers into gaussian based on fractional position between samples + int offset = v->interp_pos >> 4 & 0xFF; + short const* fwd = gauss + 255 - offset; + short const* rev = gauss + offset; // mirror left half of gaussian + + int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; + int out; + out = (fwd [ 0] * in [0]) >> 11; + out += (fwd [256] * in [1]) >> 11; + out += (rev [256] * in [2]) >> 11; + out = (int16_t) out; + out += (rev [ 0] * in [3]) >> 11; + + CLAMP16( out ); + out &= ~1; + return out; +} + + +//// Counters + +int const simple_counter_range = 2048 * 5 * 3; // 30720 + +static unsigned const counter_rates [32] = +{ + simple_counter_range + 1, // never fires + 2048, 1536, + 1280, 1024, 768, + 640, 512, 384, + 320, 256, 192, + 160, 128, 96, + 80, 64, 48, + 40, 32, 24, + 20, 16, 12, + 10, 8, 6, + 5, 4, 3, + 2, + 1 +}; + +static unsigned const counter_offsets [32] = +{ + 1, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 0, + 0 +}; + +inline void SPC_DSP::init_counter() +{ + m.counter = 0; +} + +inline void SPC_DSP::run_counters() +{ + if ( --m.counter < 0 ) + m.counter = simple_counter_range - 1; +} + +inline unsigned SPC_DSP::read_counter( int rate ) +{ + return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate]; +} + + +//// Envelope + +inline void SPC_DSP::run_envelope( voice_t* const v ) +{ + int env = v->env; + if ( v->env_mode == env_release ) // 60% + { + if ( (env -= 0x8) < 0 ) + env = 0; + v->env = env; + } + else + { + int rate; + int env_data = VREG(v->regs,adsr1); + if ( m.t_adsr0 & 0x80 ) // 99% ADSR + { + if ( v->env_mode >= env_decay ) // 99% + { + env--; + env -= env >> 8; + rate = env_data & 0x1F; + if ( v->env_mode == env_decay ) // 1% + rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10; + } + else // env_attack + { + rate = (m.t_adsr0 & 0x0F) * 2 + 1; + env += rate < 31 ? 0x20 : 0x400; + } + } + else // GAIN + { + int mode; + env_data = VREG(v->regs,gain); + mode = env_data >> 5; + if ( mode < 4 ) // direct + { + env = env_data * 0x10; + rate = 31; + } + else + { + rate = env_data & 0x1F; + if ( mode == 4 ) // 4: linear decrease + { + env -= 0x20; + } + else if ( mode < 6 ) // 5: exponential decrease + { + env--; + env -= env >> 8; + } + else // 6,7: linear increase + { + env += 0x20; + if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) + env += 0x8 - 0x20; // 7: two-slope linear increase + } + } + } + + // Sustain level + if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) + v->env_mode = env_sustain; + + v->hidden_env = env; + + // unsigned cast because linear decrease going negative also triggers this + if ( (unsigned) env > 0x7FF ) + { + env = (env < 0 ? 0 : 0x7FF); + if ( v->env_mode == env_attack ) + v->env_mode = env_decay; + } + + if ( !read_counter( rate ) ) + v->env = env; // nothing else is controlled by the counter + } +} + + +//// BRR Decoding + +inline void SPC_DSP::decode_brr( voice_t* v ) +{ + // Arrange the four input nybbles in 0xABCD order for easy decoding + int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; + + int const header = m.t_brr_header; + + // Write to next four samples in circular buffer + int* pos = &v->buf [v->buf_pos]; + int* end; + if ( (v->buf_pos += 4) >= brr_buf_size ) + v->buf_pos = 0; + + // Decode four samples + for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) + { + // Extract nybble and sign-extend + int s = (int16_t) nybbles >> 12; + + // Shift sample based on header + int const shift = header >> 4; + s = (s << shift) >> 1; + if ( shift >= 0xD ) // handle invalid range + s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0) + + // Apply IIR filter (8 is the most commonly used) + int const filter = header & 0x0C; + int const p1 = pos [brr_buf_size - 1]; + int const p2 = pos [brr_buf_size - 2] >> 1; + if ( filter >= 8 ) + { + s += p1; + s -= p2; + if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875 + { + s += p2 >> 4; + s += (p1 * -3) >> 6; + } + else // s += p1 * 0.8984375 - p2 * 0.40625 + { + s += (p1 * -13) >> 7; + s += (p2 * 3) >> 4; + } + } + else if ( filter ) // s += p1 * 0.46875 + { + s += p1 >> 1; + s += (-p1) >> 5; + } + + // Adjust and write sample + CLAMP16( s ); + s = (int16_t) (s * 2); + pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around + } +} + + +//// Misc + +#define MISC_CLOCK( n ) inline void SPC_DSP::misc_##n() + +MISC_CLOCK( 27 ) +{ + m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON +} +MISC_CLOCK( 28 ) +{ + m.t_non = REG(non); + m.t_eon = REG(eon); + m.t_dir = REG(dir); +} +MISC_CLOCK( 29 ) +{ + if ( (m.every_other_sample ^= 1) != 0 ) + m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read +} +MISC_CLOCK( 30 ) +{ + if ( m.every_other_sample ) + { + m.kon = m.new_kon; + m.t_koff = REG(koff) | m.mute_mask; + } + + run_counters(); + + // Noise + if ( !read_counter( REG(flg) & 0x1F ) ) + { + int feedback = (m.noise << 13) ^ (m.noise << 14); + m.noise = (feedback & 0x4000) ^ (m.noise >> 1); + } +} + + +//// Voices + +#define VOICE_CLOCK( n ) void SPC_DSP::voice_##n( voice_t* const v ) + +inline VOICE_CLOCK( V1 ) +{ + m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4; + m.t_srcn = VREG(v->regs,srcn); +} +inline VOICE_CLOCK( V2 ) +{ + // Read sample pointer (ignored if not needed) + uint8_t const* entry = &m.ram [m.t_dir_addr]; + if ( !v->kon_delay ) + entry += 2; + m.t_brr_next_addr = GET_LE16A( entry ); + + m.t_adsr0 = VREG(v->regs,adsr0); + + // Read pitch, spread over two clocks + m.t_pitch = VREG(v->regs,pitchl); +} +inline VOICE_CLOCK( V3a ) +{ + m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8; +} +inline VOICE_CLOCK( V3b ) +{ + // Read BRR header and byte + m.t_brr_byte = m.ram [(v->brr_addr + v->brr_offset) & 0xFFFF]; + m.t_brr_header = m.ram [v->brr_addr]; // brr_addr doesn't need masking +} +VOICE_CLOCK( V3c ) +{ + // Pitch modulation using previous voice's output + if ( m.t_pmon & v->vbit ) + m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10; + + if ( v->kon_delay ) + { + // Get ready to start BRR decoding on next sample + if ( v->kon_delay == 5 ) + { + v->brr_addr = m.t_brr_next_addr; + v->brr_offset = 1; + v->buf_pos = 0; + m.t_brr_header = 0; // header is ignored on this sample + m.kon_check = true; + } + + // Envelope is never run during KON + v->env = 0; + v->hidden_env = 0; + + // Disable BRR decoding until last three samples + v->interp_pos = 0; + if ( --v->kon_delay & 3 ) + v->interp_pos = 0x4000; + + // Pitch is never added during KON + m.t_pitch = 0; + } + + // Gaussian interpolation + { + int output = interpolate( v ); + + // Noise + if ( m.t_non & v->vbit ) + output = (int16_t) (m.noise * 2); + + // Apply envelope + m.t_output = (output * v->env) >> 11 & ~1; + v->t_envx_out = (uint8_t) (v->env >> 4); + } + + // Immediate silence due to end of sample or soft reset + if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) + { + v->env_mode = env_release; + v->env = 0; + } + + if ( m.every_other_sample ) + { + // KOFF + if ( m.t_koff & v->vbit ) + v->env_mode = env_release; + + // KON + if ( m.kon & v->vbit ) + { + v->kon_delay = 5; + v->env_mode = env_attack; + } + } + + // Run envelope for next sample + if ( !v->kon_delay ) + run_envelope( v ); +} +inline void SPC_DSP::voice_output( voice_t const* v, int ch ) +{ + // Apply left/right volume + int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7; + + // Add to output total + m.t_main_out [ch] += amp; + CLAMP16( m.t_main_out [ch] ); + + // Optionally add to echo total + if ( m.t_eon & v->vbit ) + { + m.t_echo_out [ch] += amp; + CLAMP16( m.t_echo_out [ch] ); + } +} +VOICE_CLOCK( V4 ) +{ + // Decode BRR + m.t_looped = 0; + if ( v->interp_pos >= 0x4000 ) + { + decode_brr( v ); + + if ( (v->brr_offset += 2) >= brr_block_size ) + { + // Start decoding next BRR block + assert( v->brr_offset == brr_block_size ); + v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; + if ( m.t_brr_header & 1 ) + { + v->brr_addr = m.t_brr_next_addr; + m.t_looped = v->vbit; + } + v->brr_offset = 1; + } + } + + // Apply pitch + v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; + + // Keep from getting too far ahead (when using pitch modulation) + if ( v->interp_pos > 0x7FFF ) + v->interp_pos = 0x7FFF; + + // Output left + voice_output( v, 0 ); +} +inline VOICE_CLOCK( V5 ) +{ + // Output right + voice_output( v, 1 ); + + // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier + int endx_buf = REG(endx) | m.t_looped; + + // Clear bit in ENDX if KON just began + if ( v->kon_delay == 5 ) + endx_buf &= ~v->vbit; + m.endx_buf = (uint8_t) endx_buf; +} +inline VOICE_CLOCK( V6 ) +{ + (void) v; // avoid compiler warning about unused v + m.outx_buf = (uint8_t) (m.t_output >> 8); +} +inline VOICE_CLOCK( V7 ) +{ + // Update ENDX + REG(endx) = m.endx_buf; + + m.envx_buf = v->t_envx_out; +} +inline VOICE_CLOCK( V8 ) +{ + // Update OUTX + VREG(v->regs,outx) = m.outx_buf; +} +inline VOICE_CLOCK( V9 ) +{ + // Update ENVX + VREG(v->regs,envx) = m.envx_buf; +} + +// Most voices do all these in one clock, so make a handy composite +inline VOICE_CLOCK( V3 ) +{ + voice_V3a( v ); + voice_V3b( v ); + voice_V3c( v ); +} + +// Common combinations of voice steps on different voices. This greatly reduces +// code size and allows everything to be inlined in these functions. +VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); } +VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); } +VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); } + + +//// Echo + +// Current echo buffer pointer for left/right channel +#define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2]) + +// Sample in echo history buffer, where 0 is the oldest +#define ECHO_FIR( i ) (m.echo_hist_pos [i]) + +// Calculate FIR point for left/right channel +#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6) + +#define ECHO_CLOCK( n ) inline void SPC_DSP::echo_##n() + +inline void SPC_DSP::echo_read( int ch ) +{ + int s = GET_LE16SA( ECHO_PTR( ch ) ); + // second copy simplifies wrap-around handling + ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; +} + +ECHO_CLOCK( 22 ) +{ + // History + if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) + m.echo_hist_pos = m.echo_hist; + + m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; + echo_read( 0 ); + + // FIR (using l and r temporaries below helps compiler optimize) + int l = CALC_FIR( 0, 0 ); + int r = CALC_FIR( 0, 1 ); + + m.t_echo_in [0] = l; + m.t_echo_in [1] = r; +} +ECHO_CLOCK( 23 ) +{ + int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); + int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; + + echo_read( 1 ); +} +ECHO_CLOCK( 24 ) +{ + int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); + int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; +} +ECHO_CLOCK( 25 ) +{ + int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); + int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); + + l = (int16_t) l; + r = (int16_t) r; + + l += (int16_t) CALC_FIR( 7, 0 ); + r += (int16_t) CALC_FIR( 7, 1 ); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_in [0] = l & ~1; + m.t_echo_in [1] = r & ~1; +} +inline int SPC_DSP::echo_output( int ch ) +{ + int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) + + (int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7); + CLAMP16( out ); + return out; +} +ECHO_CLOCK( 26 ) +{ + // Left output volumes + // (save sample for next clock so we can output both together) + m.t_main_out [0] = echo_output( 0 ); + + // Echo feedback + int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); + int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_out [0] = l & ~1; + m.t_echo_out [1] = r & ~1; +} +ECHO_CLOCK( 27 ) +{ + // Output + int l = m.t_main_out [0]; + int r = echo_output( 1 ); + m.t_main_out [0] = 0; + m.t_main_out [1] = 0; + + // TODO: global muting isn't this simple (turns DAC on and off + // or something, causing small ~37-sample pulse when first muted) + if ( REG(flg) & 0x40 ) + { + l = 0; + r = 0; + } + + // Output sample to DAC + #ifdef SPC_DSP_OUT_HOOK + SPC_DSP_OUT_HOOK( l, r ); + #else + sample_t* out = m.out; + WRITE_SAMPLES( l, r, out ); + m.out = out; + #endif +} +ECHO_CLOCK( 28 ) +{ + m.t_echo_enabled = REG(flg); +} +inline void SPC_DSP::echo_write( int ch ) +{ + if ( !(m.t_echo_enabled & 0x20) ) + SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] ); + m.t_echo_out [ch] = 0; +} +ECHO_CLOCK( 29 ) +{ + m.t_esa = REG(esa); + + if ( !m.echo_offset ) + m.echo_length = (REG(edl) & 0x0F) * 0x800; + + m.echo_offset += 4; + if ( m.echo_offset >= m.echo_length ) + m.echo_offset = 0; + + // Write left echo + echo_write( 0 ); + + m.t_echo_enabled = REG(flg); +} +ECHO_CLOCK( 30 ) +{ + // Write right echo + echo_write( 1 ); +} + + +//// Timing + +// Execute clock for a particular voice +#define V( clock, voice ) voice_##clock( &m.voices [voice] ); + +/* The most common sequence of clocks uses composite operations +for efficiency. For example, the following are equivalent to the +individual steps on the right: + +V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5) +V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4) +V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */ + +// Voice 0 1 2 3 4 5 6 7 +#define GEN_DSP_TIMING \ +PHASE( 0) V(V5,0)V(V2,1)\ +PHASE( 1) V(V6,0)V(V3,1)\ +PHASE( 2) V(V7_V4_V1,0)\ +PHASE( 3) V(V8_V5_V2,0)\ +PHASE( 4) V(V9_V6_V3,0)\ +PHASE( 5) V(V7_V4_V1,1)\ +PHASE( 6) V(V8_V5_V2,1)\ +PHASE( 7) V(V9_V6_V3,1)\ +PHASE( 8) V(V7_V4_V1,2)\ +PHASE( 9) V(V8_V5_V2,2)\ +PHASE(10) V(V9_V6_V3,2)\ +PHASE(11) V(V7_V4_V1,3)\ +PHASE(12) V(V8_V5_V2,3)\ +PHASE(13) V(V9_V6_V3,3)\ +PHASE(14) V(V7_V4_V1,4)\ +PHASE(15) V(V8_V5_V2,4)\ +PHASE(16) V(V9_V6_V3,4)\ +PHASE(17) V(V1,0) V(V7,5)V(V4,6)\ +PHASE(18) V(V8_V5_V2,5)\ +PHASE(19) V(V9_V6_V3,5)\ +PHASE(20) V(V1,1) V(V7,6)V(V4,7)\ +PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\ +PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\ +PHASE(23) V(V7,7) echo_23();\ +PHASE(24) V(V8,7) echo_24();\ +PHASE(25) V(V3b,0) V(V9,7) echo_25();\ +PHASE(26) echo_26();\ +PHASE(27) misc_27(); echo_27();\ +PHASE(28) misc_28(); echo_28();\ +PHASE(29) misc_29(); echo_29();\ +PHASE(30) misc_30();V(V3c,0) echo_30();\ +PHASE(31) V(V4,0) V(V1,2)\ + +#if !SPC_DSP_CUSTOM_RUN + +void SPC_DSP::run( int clocks_remain ) +{ + require( clocks_remain > 0 ); + + int const phase = m.phase; + m.phase = (phase + clocks_remain) & 31; + switch ( phase ) + { + loop: + + #define PHASE( n ) if ( n && !--clocks_remain ) break; case n: + GEN_DSP_TIMING + #undef PHASE + + if ( --clocks_remain ) + goto loop; + } +} + +#endif + + +//// Setup + +void SPC_DSP::init( void* ram_64k ) +{ + m.ram = (uint8_t*) ram_64k; + mute_voices( 0 ); + disable_surround( false ); + set_output( 0, 0 ); + reset(); + + #ifndef NDEBUG + // be sure this sign-extends + assert( (int16_t) 0x8000 == -0x8000 ); + + // be sure right shift preserves sign + assert( (-1 >> 1) == -1 ); + + // check clamp macro + int i; + i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF ); + i = -0x8001; CLAMP16( i ); assert( i == -0x8000 ); + + blargg_verify_byte_order(); + #endif +} + +void SPC_DSP::soft_reset_common() +{ + require( m.ram ); // init() must have been called already + + m.noise = 0x4000; + m.echo_hist_pos = m.echo_hist; + m.every_other_sample = 1; + m.echo_offset = 0; + m.phase = 0; + + init_counter(); +} + +void SPC_DSP::soft_reset() +{ + REG(flg) = 0xE0; + soft_reset_common(); +} + +void SPC_DSP::load( uint8_t const regs [register_count] ) +{ + memcpy( m.regs, regs, sizeof m.regs ); + memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); + + // Internal state + for ( int i = voice_count; --i >= 0; ) + { + voice_t* v = &m.voices [i]; + v->brr_offset = 1; + v->vbit = 1 << i; + v->regs = &m.regs [i * 0x10]; + } + m.new_kon = REG(kon); + m.t_dir = REG(dir); + m.t_esa = REG(esa); + + soft_reset_common(); +} + +void SPC_DSP::reset() { load( initial_regs ); } + + +//// State save/load + +#if !SPC_NO_COPY_STATE_FUNCS + +void SPC_State_Copier::copy( void* state, size_t size ) +{ + func( buf, state, size ); +} + +int SPC_State_Copier::copy_int( int state, int size ) +{ + BOOST::uint8_t s [2]; + SET_LE16( s, state ); + func( buf, &s, size ); + return GET_LE16( s ); +} + +void SPC_State_Copier::skip( int count ) +{ + if ( count > 0 ) + { + char temp [64]; + memset( temp, 0, sizeof temp ); + do + { + int n = sizeof temp; + if ( n > count ) + n = count; + count -= n; + func( buf, temp, n ); + } + while ( count ); + } +} + +void SPC_State_Copier::extra() +{ + int n = 0; + SPC_State_Copier& copier = *this; + SPC_COPY( uint8_t, n ); + skip( n ); +} + +void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) +{ + SPC_State_Copier copier( io, copy ); + + // DSP registers + copier.copy( m.regs, register_count ); + + // Internal state + + // Voices + int i; + for ( i = 0; i < voice_count; i++ ) + { + voice_t* v = &m.voices [i]; + + // BRR buffer + int i; + for ( i = 0; i < brr_buf_size; i++ ) + { + int s = v->buf [i]; + SPC_COPY( int16_t, s ); + v->buf [i] = v->buf [i + brr_buf_size] = s; + } + + SPC_COPY( uint16_t, v->interp_pos ); + SPC_COPY( uint16_t, v->brr_addr ); + SPC_COPY( uint16_t, v->env ); + SPC_COPY( int16_t, v->hidden_env ); + SPC_COPY( uint8_t, v->buf_pos ); + SPC_COPY( uint8_t, v->brr_offset ); + SPC_COPY( uint8_t, v->kon_delay ); + { + int m = v->env_mode; + SPC_COPY( uint8_t, m ); + v->env_mode = (enum env_mode_t) m; + } + SPC_COPY( uint8_t, v->t_envx_out ); + + copier.extra(); + } + + // Echo history + for ( i = 0; i < echo_hist_size; i++ ) + { + int j; + for ( j = 0; j < 2; j++ ) + { + int s = m.echo_hist_pos [i] [j]; + SPC_COPY( int16_t, s ); + m.echo_hist [i] [j] = s; // write back at offset 0 + } + } + m.echo_hist_pos = m.echo_hist; + memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] ); + + // Misc + SPC_COPY( uint8_t, m.every_other_sample ); + SPC_COPY( uint8_t, m.kon ); + + SPC_COPY( uint16_t, m.noise ); + SPC_COPY( uint16_t, m.counter ); + SPC_COPY( uint16_t, m.echo_offset ); + SPC_COPY( uint16_t, m.echo_length ); + SPC_COPY( uint8_t, m.phase ); + + SPC_COPY( uint8_t, m.new_kon ); + SPC_COPY( uint8_t, m.endx_buf ); + SPC_COPY( uint8_t, m.envx_buf ); + SPC_COPY( uint8_t, m.outx_buf ); + + SPC_COPY( uint8_t, m.t_pmon ); + SPC_COPY( uint8_t, m.t_non ); + SPC_COPY( uint8_t, m.t_eon ); + SPC_COPY( uint8_t, m.t_dir ); + SPC_COPY( uint8_t, m.t_koff ); + + SPC_COPY( uint16_t, m.t_brr_next_addr ); + SPC_COPY( uint8_t, m.t_adsr0 ); + SPC_COPY( uint8_t, m.t_brr_header ); + SPC_COPY( uint8_t, m.t_brr_byte ); + SPC_COPY( uint8_t, m.t_srcn ); + SPC_COPY( uint8_t, m.t_esa ); + SPC_COPY( uint8_t, m.t_echo_enabled ); + + SPC_COPY( int16_t, m.t_main_out [0] ); + SPC_COPY( int16_t, m.t_main_out [1] ); + SPC_COPY( int16_t, m.t_echo_out [0] ); + SPC_COPY( int16_t, m.t_echo_out [1] ); + SPC_COPY( int16_t, m.t_echo_in [0] ); + SPC_COPY( int16_t, m.t_echo_in [1] ); + + SPC_COPY( uint16_t, m.t_dir_addr ); + SPC_COPY( uint16_t, m.t_pitch ); + SPC_COPY( int16_t, m.t_output ); + SPC_COPY( uint16_t, m.t_echo_ptr ); + SPC_COPY( uint8_t, m.t_looped ); + + copier.extra(); +} +#endif diff --git a/waterbox/sameboy/snes_spc/SPC_DSP.h b/waterbox/sameboy/snes_spc/SPC_DSP.h new file mode 100644 index 0000000000..4522ace915 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_DSP.h @@ -0,0 +1,304 @@ +// Highly accurate SNES SPC-700 DSP emulator + +// snes_spc 0.9.0 +#ifndef SPC_DSP_H +#define SPC_DSP_H + +#include "blargg_common.h" + +extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); } + +class SPC_DSP { +public: + typedef BOOST::uint8_t uint8_t; + +// Setup + + // Initializes DSP and has it use the 64K RAM provided + void init( void* ram_64k ); + + // Sets destination for output samples. If out is NULL or out_size is 0, + // doesn't generate any. + typedef short sample_t; + void set_output( sample_t* out, int out_size ); + + // Number of samples written to output since it was last set, always + // a multiple of 2. Undefined if more samples were generated than + // output buffer could hold. + int sample_count() const; + +// Emulation + + // Resets DSP to power-on state + void reset(); + + // Emulates pressing reset switch on SNES + void soft_reset(); + + // Reads/writes DSP registers. For accuracy, you must first call run() + // to catch the DSP up to present. + int read ( int addr ) const; + void write( int addr, int data ); + + // Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks + // a pair of samples is be generated. + void run( int clock_count ); + +// Sound control + + // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). + // Reduces emulation accuracy. + enum { voice_count = 8 }; + void mute_voices( int mask ); + +// State + + // Resets DSP and uses supplied values to initialize registers + enum { register_count = 128 }; + void load( uint8_t const regs [register_count] ); + + // Saves/loads exact emulator state + enum { state_size = 640 }; // maximum space needed when saving + typedef dsp_copy_func_t copy_func_t; + void copy_state( unsigned char** io, copy_func_t ); + + // Returns non-zero if new key-on events occurred since last call + bool check_kon(); + +// DSP register addresses + + // Global registers + enum { + r_mvoll = 0x0C, r_mvolr = 0x1C, + r_evoll = 0x2C, r_evolr = 0x3C, + r_kon = 0x4C, r_koff = 0x5C, + r_flg = 0x6C, r_endx = 0x7C, + r_efb = 0x0D, r_pmon = 0x2D, + r_non = 0x3D, r_eon = 0x4D, + r_dir = 0x5D, r_esa = 0x6D, + r_edl = 0x7D, + r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F + }; + + // Voice registers + enum { + v_voll = 0x00, v_volr = 0x01, + v_pitchl = 0x02, v_pitchh = 0x03, + v_srcn = 0x04, v_adsr0 = 0x05, + v_adsr1 = 0x06, v_gain = 0x07, + v_envx = 0x08, v_outx = 0x09 + }; + +public: + enum { extra_size = 16 }; + sample_t* extra() { return m.extra; } + sample_t const* out_pos() const { return m.out; } + void disable_surround( bool ) { } // not supported +public: + BLARGG_DISABLE_NOTHROW + + typedef BOOST::int8_t int8_t; + typedef BOOST::int16_t int16_t; + + enum { echo_hist_size = 8 }; + + enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; + enum { brr_buf_size = 12 }; + struct voice_t + { + int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling) + int buf_pos; // place in buffer where next samples will be decoded + int interp_pos; // relative fractional position in sample (0x1000 = 1.0) + int brr_addr; // address of current BRR block + int brr_offset; // current decoding offset in BRR block + uint8_t* regs; // pointer to voice's DSP registers + int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc. + int kon_delay; // KON delay/current setup phase + env_mode_t env_mode; + int env; // current envelope level + int hidden_env; // used by GAIN mode 7, very obscure quirk + uint8_t t_envx_out; + }; +private: + enum { brr_block_size = 9 }; + + struct state_t + { + uint8_t regs [register_count]; + + // Echo history keeps most recent 8 samples (twice the size to simplify wrap handling) + int echo_hist [echo_hist_size * 2] [2]; + int (*echo_hist_pos) [2]; // &echo_hist [0 to 7] + + int every_other_sample; // toggles every sample + int kon; // KON value when last checked + int noise; + int counter; + int echo_offset; // offset from ESA in echo buffer + int echo_length; // number of bytes that echo_offset will stop at + int phase; // next clock cycle to run (0-31) + bool kon_check; // set when a new KON occurs + + // Hidden registers also written to when main register is written to + int new_kon; + uint8_t endx_buf; + uint8_t envx_buf; + uint8_t outx_buf; + + // Temporary state between clocks + + // read once per sample + int t_pmon; + int t_non; + int t_eon; + int t_dir; + int t_koff; + + // read a few clocks ahead then used + int t_brr_next_addr; + int t_adsr0; + int t_brr_header; + int t_brr_byte; + int t_srcn; + int t_esa; + int t_echo_enabled; + + // internal state that is recalculated every sample + int t_dir_addr; + int t_pitch; + int t_output; + int t_looped; + int t_echo_ptr; + + // left/right sums + int t_main_out [2]; + int t_echo_out [2]; + int t_echo_in [2]; + + voice_t voices [voice_count]; + + // non-emulation state + uint8_t* ram; // 64K shared RAM between DSP and SMP + int mute_mask; + sample_t* out; + sample_t* out_end; + sample_t* out_begin; + sample_t extra [extra_size]; + }; + state_t m; + + void init_counter(); + void run_counters(); + unsigned read_counter( int rate ); + + int interpolate( voice_t const* v ); + void run_envelope( voice_t* const v ); + void decode_brr( voice_t* v ); + + void misc_27(); + void misc_28(); + void misc_29(); + void misc_30(); + + void voice_output( voice_t const* v, int ch ); + void voice_V1( voice_t* const ); + void voice_V2( voice_t* const ); + void voice_V3( voice_t* const ); + void voice_V3a( voice_t* const ); + void voice_V3b( voice_t* const ); + void voice_V3c( voice_t* const ); + void voice_V4( voice_t* const ); + void voice_V5( voice_t* const ); + void voice_V6( voice_t* const ); + void voice_V7( voice_t* const ); + void voice_V8( voice_t* const ); + void voice_V9( voice_t* const ); + void voice_V7_V4_V1( voice_t* const ); + void voice_V8_V5_V2( voice_t* const ); + void voice_V9_V6_V3( voice_t* const ); + + void echo_read( int ch ); + int echo_output( int ch ); + void echo_write( int ch ); + void echo_22(); + void echo_23(); + void echo_24(); + void echo_25(); + void echo_26(); + void echo_27(); + void echo_28(); + void echo_29(); + void echo_30(); + + void soft_reset_common(); +}; + +#include + +inline int SPC_DSP::sample_count() const { return m.out - m.out_begin; } + +inline int SPC_DSP::read( int addr ) const +{ + assert( (unsigned) addr < register_count ); + return m.regs [addr]; +} + +inline void SPC_DSP::write( int addr, int data ) +{ + assert( (unsigned) addr < register_count ); + + m.regs [addr] = (uint8_t) data; + switch ( addr & 0x0F ) + { + case v_envx: + m.envx_buf = (uint8_t) data; + break; + + case v_outx: + m.outx_buf = (uint8_t) data; + break; + + case 0x0C: + if ( addr == r_kon ) + m.new_kon = (uint8_t) data; + + if ( addr == r_endx ) // always cleared, regardless of data written + { + m.endx_buf = 0; + m.regs [r_endx] = 0; + } + break; + } +} + +inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; } + +inline bool SPC_DSP::check_kon() +{ + bool old = m.kon_check; + m.kon_check = 0; + return old; +} + +#if !SPC_NO_COPY_STATE_FUNCS + +class SPC_State_Copier { + SPC_DSP::copy_func_t func; + unsigned char** buf; +public: + SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; } + void copy( void* state, size_t size ); + int copy_int( int state, int size ); + void skip( int count ); + void extra(); +}; + +#define SPC_COPY( type, state )\ +{\ + state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\ + assert( (BOOST::type) state == state );\ +} + +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/SPC_Filter.cpp b/waterbox/sameboy/snes_spc/SPC_Filter.cpp new file mode 100644 index 0000000000..b3d5770822 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_Filter.cpp @@ -0,0 +1,68 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SPC_Filter.h" + +#include + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void SPC_Filter::clear() { memset( ch, 0, sizeof ch ); } + +SPC_Filter::SPC_Filter() +{ + gain = gain_unit; + bass = bass_norm; + clear(); +} + +void SPC_Filter::run( short* io, int count ) +{ + require( (count & 1) == 0 ); // must be even + + int const gain = this->gain; + int const bass = this->bass; + chan_t* c = &ch [2]; + do + { + // cache in registers + int sum = (--c)->sum; + int pp1 = c->pp1; + int p1 = c->p1; + + for ( int i = 0; i < count; i += 2 ) + { + // Low-pass filter (two point FIR with coeffs 0.25, 0.75) + int f = io [i] + p1; + p1 = io [i] * 3; + + // High-pass filter ("leaky integrator") + int delta = f - pp1; + pp1 = f; + int s = sum >> (gain_bits + 2); + sum += (delta * gain) - (sum >> bass); + + // Clamp to 16 bits + if ( (short) s != s ) + s = (s >> 31) ^ 0x7FFF; + + io [i] = (short) s; + } + + c->p1 = p1; + c->pp1 = pp1; + c->sum = sum; + ++io; + } + while ( c != ch ); +} diff --git a/waterbox/sameboy/snes_spc/SPC_Filter.h b/waterbox/sameboy/snes_spc/SPC_Filter.h new file mode 100644 index 0000000000..d5c83cb8f1 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_Filter.h @@ -0,0 +1,47 @@ +// Simple low-pass and high-pass filter to better match sound output of a SNES + +// snes_spc 0.9.0 +#ifndef SPC_FILTER_H +#define SPC_FILTER_H + +#include "blargg_common.h" + +struct SPC_Filter { +public: + + // Filters count samples of stereo sound in place. Count must be a multiple of 2. + typedef short sample_t; + void run( sample_t* io, int count ); + +// Optional features + + // Clears filter to silence + void clear(); + + // Sets gain (volume), where gain_unit is normal. Gains greater than gain_unit + // are fine, since output is clamped to 16-bit sample range. + enum { gain_unit = 0x100 }; + void set_gain( int gain ); + + // Sets amount of bass (logarithmic scale) + enum { bass_none = 0 }; + enum { bass_norm = 8 }; // normal amount + enum { bass_max = 31 }; + void set_bass( int bass ); + +public: + SPC_Filter(); + BLARGG_DISABLE_NOTHROW +private: + enum { gain_bits = 8 }; + int gain; + int bass; + struct chan_t { int p1, pp1, sum; }; + chan_t ch [2]; +}; + +inline void SPC_Filter::set_gain( int g ) { gain = g; } + +inline void SPC_Filter::set_bass( int b ) { bass = b; } + +#endif diff --git a/waterbox/sameboy/snes_spc/blargg_common.h b/waterbox/sameboy/snes_spc/blargg_common.h new file mode 100644 index 0000000000..75edff3914 --- /dev/null +++ b/waterbox/sameboy/snes_spc/blargg_common.h @@ -0,0 +1,186 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +// snes_spc 0.9.0 +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include +#include + +#undef BLARGG_COMMON_H +// allow blargg_config.h to #include blargg_common.h +#include "blargg_config.h" +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// BLARGG_RESTRICT: equivalent to restrict, where supported +#if defined (__GNUC__) || _MSC_VER >= 1100 + #define BLARGG_RESTRICT __restrict +#else + #define BLARGG_RESTRICT +#endif + +// STATIC_CAST(T,expr): Used in place of static_cast (expr) +#ifndef STATIC_CAST + #define STATIC_CAST(T,expr) ((T) (expr)) +#endif + +// blargg_err_t (0 on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; +#endif + +// blargg_vector - very lightweight vector of POD types (no constructor/destructor) +template +class blargg_vector { + T* begin_; + size_t size_; +public: + blargg_vector() : begin_( 0 ), size_( 0 ) { } + ~blargg_vector() { free( begin_ ); } + size_t size() const { return size_; } + T* begin() const { return begin_; } + T* end() const { return begin_ + size_; } + blargg_err_t resize( size_t n ) + { + // TODO: blargg_common.cpp to hold this as an outline function, ugh + void* p = realloc( begin_, n * sizeof (T) ); + if ( p ) + begin_ = (T*) p; + else if ( n > size_ ) // realloc failure only a problem if expanding + return "Out of memory"; + size_ = n; + return 0; + } + void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); } + T& operator [] ( size_t n ) const + { + assert( n <= size_ ); // <= to allow past-the-end value + return begin_ [n]; + } +}; + +#ifndef BLARGG_DISABLE_NOTHROW + // throw spec mandatory in ISO C++ if operator new can return NULL + #if __cplusplus >= 199711 || defined (__GNUC__) + #define BLARGG_THROWS( spec ) throw spec + #else + #define BLARGG_THROWS( spec ) + #endif + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\ + void operator delete ( void* p ) { free( p ); } + #define BLARGG_NEW new +#else + #include + #define BLARGG_NEW new (std::nothrow) +#endif + +// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant) +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF)) + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] ) + #else + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) + #endif +#endif + +// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1, +// compiler is assumed to support bool. If undefined, availability is determined. +#ifndef BLARGG_COMPILER_HAS_BOOL + #if defined (__MWERKS__) + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (__GNUC__) + // supports bool + #elif __cplusplus < 199711 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif +#endif +#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + // If you get errors here, modify your blargg_config.h file + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough + +#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF + typedef long blargg_long; +#else + typedef int blargg_long; +#endif + +#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF + typedef unsigned long blargg_ulong; +#else + typedef unsigned blargg_ulong; +#endif + +// BOOST::int8_t etc. + +// HAVE_STDINT_H: If defined, use for int8_t etc. +#if defined (HAVE_STDINT_H) + #include + #define BOOST + +// HAVE_INTTYPES_H: If defined, use for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include + #define BOOST + +#else + struct BOOST + { + #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F + typedef signed char int8_t; + typedef unsigned char uint8_t; + #else + // No suitable 8-bit type available + typedef struct see_blargg_common_h int8_t; + typedef struct see_blargg_common_h uint8_t; + #endif + + #if USHRT_MAX == 0xFFFF + typedef short int16_t; + typedef unsigned short uint16_t; + #else + // No suitable 16-bit type available + typedef struct see_blargg_common_h int16_t; + typedef struct see_blargg_common_h uint16_t; + #endif + + #if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; + #elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; + #else + // No suitable 32-bit type available + typedef struct see_blargg_common_h int32_t; + typedef struct see_blargg_common_h uint32_t; + #endif + }; +#endif + +#endif +#endif diff --git a/waterbox/sameboy/snes_spc/blargg_config.h b/waterbox/sameboy/snes_spc/blargg_config.h new file mode 100644 index 0000000000..9dc69db836 --- /dev/null +++ b/waterbox/sameboy/snes_spc/blargg_config.h @@ -0,0 +1,24 @@ +// snes_spc 0.9.0 user configuration file. Don't replace when updating library. + +// snes_spc 0.9.0 +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to disable debugging checks +//#define NDEBUG 1 + +// Uncomment to enable platform-specific (and possibly non-portable) optimizations +//#define BLARGG_NONPORTABLE 1 + +// Uncomment if automatic byte-order determination doesn't work +//#define BLARGG_BIG_ENDIAN 1 + +// Uncomment if you get errors in the bool section of blargg_common.h +//#define BLARGG_COMPILER_HAS_BOOL 1 + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/blargg_endian.h b/waterbox/sameboy/snes_spc/blargg_endian.h new file mode 100644 index 0000000000..f2daca6416 --- /dev/null +++ b/waterbox/sameboy/snes_spc/blargg_endian.h @@ -0,0 +1,185 @@ +// CPU Byte Order Utilities + +// snes_spc 0.9.0 +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + #define BLARGG_CPU_RISC 1 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one may be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else + +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif + +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__sparc__) || BLARGG_CPU_POWERPC || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#elif !defined (__mips__) + // No endian specified; assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +inline void blargg_verify_byte_order() +{ + #ifndef NDEBUG + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif + #endif +} + +inline unsigned get_le16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; +} + +inline unsigned get_be16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 8 | + (unsigned) ((unsigned char const*) p) [1]; +} + +inline blargg_ulong get_le32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [3] << 24 | + (blargg_ulong) ((unsigned char const*) p) [2] << 16 | + (blargg_ulong) ((unsigned char const*) p) [1] << 8 | + (blargg_ulong) ((unsigned char const*) p) [0]; +} + +inline blargg_ulong get_be32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [0] << 24 | + (blargg_ulong) ((unsigned char const*) p) [1] << 16 | + (blargg_ulong) ((unsigned char const*) p) [2] << 8 | + (blargg_ulong) ((unsigned char const*) p) [3]; +} + +inline void set_le16( void* p, unsigned n ) +{ + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} + +inline void set_be16( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} + +inline void set_le32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [0] = (unsigned char) n; + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); +} + +inline void set_be32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [3] = (unsigned char) n; + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); +} + +#if BLARGG_NONPORTABLE + // Optimized implementation if byte order is known + #if BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif BLARGG_BIG_ENDIAN + #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + + #if BLARGG_CPU_POWERPC + // PowerPC has special byte-reversed instructions + #if defined (__MWERKS__) + #define GET_LE16( addr ) (__lhbrx( addr, 0 )) + #define GET_LE32( addr ) (__lwbrx( addr, 0 )) + #define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 )) + #define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 )) + #elif defined (__GNUC__) + #define GET_LE16( addr ) ({unsigned ppc_lhbrx_; asm( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr), "0" (ppc_lhbrx_) ); ppc_lhbrx_;}) + #define GET_LE32( addr ) ({unsigned ppc_lwbrx_; asm( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr), "0" (ppc_lwbrx_) ); ppc_lwbrx_;}) + #define SET_LE16( addr, in ) ({asm( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #define SET_LE32( addr, in ) ({asm( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #endif + #endif + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) +#endif + +#ifndef GET_LE32 + #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) +#endif + +#ifndef GET_BE32 + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); } +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); } +inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } +inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } +inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } +inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } + +#endif diff --git a/waterbox/sameboy/snes_spc/blargg_source.h b/waterbox/sameboy/snes_spc/blargg_source.h new file mode 100644 index 0000000000..5e45c4fb42 --- /dev/null +++ b/waterbox/sameboy/snes_spc/blargg_source.h @@ -0,0 +1,100 @@ +/* Included at the beginning of library source files, after all other #include lines. +Sets up helpful macros and services used in my source code. They don't need +module an annoying module prefix on their names since they are defined after +all other #include lines. */ + +// snes_spc 0.9.0 +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#undef require +#define require( expr ) assert( expr ) + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments). +// void dprintf( const char* format, ... ); +static inline void blargg_dprintf_( const char*, ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#define check( expr ) ((void) 0) + +// If expr yields error string, return it from current function, otherwise continue. +#undef RETURN_ERR +#define RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is 0, return out of memory error string. +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) + +// Avoid any macros which evaluate their arguments multiple times +#undef min +#undef max + +#define DEF_MIN_MAX( type ) \ + static inline type min( type x, type y ) { if ( x < y ) return x; return y; }\ + static inline type max( type x, type y ) { if ( y < x ) return x; return y; } + +DEF_MIN_MAX( int ) +DEF_MIN_MAX( unsigned ) +DEF_MIN_MAX( long ) +DEF_MIN_MAX( unsigned long ) +DEF_MIN_MAX( float ) +DEF_MIN_MAX( double ) + +#undef DEF_MIN_MAX + +/* +// using const references generates crappy code, and I am currenly only using these +// for built-in types, so they take arguments by value + +// TODO: remove +inline int min( int x, int y ) +template +inline T min( T x, T y ) +{ + if ( x < y ) + return x; + return y; +} + +template +inline T max( T x, T y ) +{ + if ( x < y ) + return y; + return x; +} +*/ + +// TODO: good idea? bad idea? +#undef byte +#define byte byte_ +typedef unsigned char byte; + +// deprecated +#define BLARGG_CHECK_ALLOC CHECK_ALLOC +#define BLARGG_RETURN_ERR RETURN_ERR + +// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/changes.txt b/waterbox/sameboy/snes_spc/changes.txt new file mode 100644 index 0000000000..33661832b4 --- /dev/null +++ b/waterbox/sameboy/snes_spc/changes.txt @@ -0,0 +1,107 @@ +snes_spc Change Log +------------------- + +snes_spc 0.9.0 +-------------- +- Improved documentation + +- SPC: Added spc_skip() function for quickly seeking in an SPC music +file. Runs 3-4x faster than normal playback using the fast DSP (or about +43-60X real-time on my 400 MHz Mac). + +- SPC: Added spc_set_tempo() to change tempo of SPC music playback. + +- SPC: Sample generation is now corrected to generate exactly one pair +of samples every 32 clocks without exception. Before it could generate a +few samples more or less depending on how far ahead or behind DSP was at +the moment. + +- SPC: Changed spc_reset() and spc_soft_reset() to also reset output +buffer (see spc.h). + +- SPC: Fixed minor timer counting bug. + +- SPC: Stack pointer wrap-around is now emulated (and without any +noticeable performance hit). + +- SPC: Runs about 5% faster due to various optimizations. + +- SPC: Found way to make fast DSP register accesses cycle-accurate in +most cases, without reducing performance. Allows fast DSP to pass most +of my validation tests. + +- DSP: Added surround disable support to fast DSP again. + +- DSP: Improved voice un-muting to take effect immediately on fast DSP. + +- DSP: Noise shift register now starts at 0x4000 instead of 0x4001 as it +incorrectly did before. + +- Converted library to C++ code internally. A C interface is still +included in spc.h and dsp.h. Note that these are different than the +previous interface, so your code will require minor changes: + + Old SPC code New SPC code + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + #include "spc/spc.h" #include "snes_spc/spc.h" + + snes_spc_t* spc; SNES_SPC* spc; + spc = malloc( sizeof (snes_spc_t) ); spc = spc_new(); + spc_init( spc ); + + spc_end_frame( time ); spc_end_frame( spc, time ); + /* etc. */ + + /* done using SPC */ spc_delete( spc ); + + + Old DSP code New DSP code + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + #include "spc/spc_dsp.h" #include "snes_spc/dsp.h" + + spc_dsp_init( ram ); SPC_DSP* dsp; + dsp = spc_dsp_new(); + spc_dsp_init( dsp, ram ); + + spc_dsp_run( count ); spc_dsp_run( dsp, count ); + /* etc. */ + + /* done using DSP */ spc_dsp_delete( dsp ); + + +snes_spc 0.8.0 +-------------- +- Added several demos + +- Added high-pass/low-pass filter to better match SNES sound + +- Added save state functionality for SPC and accurate DSP (but not fast +DSP) + +- Added emulation of reset switch on NES (soft reset) + +- Made source more compatible with pre-C99 compilers by eliminating +mid-block declarations + +- SPC: Many S-SMP accuracy improvements, mostly in memory access times + +- SPC: S-SMP speed improvements + +- SPC: Added SPC load/save functions and KON checking to help trim +silence from beginning + +- SPC: Changed spc_init() to have you allocate most of the memory used +by the library so you have more control over it + +- DSP: New highly accurate DSP and faster version derived from same code + +- DSP: Changed prefix from dsp_ to spc_dsp_. Your DSP code will require +changes. + +- DSP: Removed surround disable and gain. Gain can now be done with the +dsp_filter module, and surround disable will probably only be +implemented in the fast DSP at some point. + +- DSP: Changed interface to work in clocks rather than samples, +necessary for the new accurate DSP. Sample output is now done with +separate functions. Your DSP code will require changes. diff --git a/waterbox/sameboy/snes_spc/dsp.cpp b/waterbox/sameboy/snes_spc/dsp.cpp new file mode 100644 index 0000000000..99d0cf5c5e --- /dev/null +++ b/waterbox/sameboy/snes_spc/dsp.cpp @@ -0,0 +1,48 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "dsp.h" + +#include "SPC_DSP.h" + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +SPC_DSP* spc_dsp_new( void ) +{ + // be sure constants match + assert( spc_dsp_voice_count == (int) SPC_DSP::voice_count ); + assert( spc_dsp_register_count == (int) SPC_DSP::register_count ); + #if !SPC_NO_COPY_STATE_FUNCS + assert( spc_dsp_state_size == (int) SPC_DSP::state_size ); + #endif + + return new SPC_DSP; +} + +void spc_dsp_delete ( SPC_DSP* s ) { delete s; } +void spc_dsp_init ( SPC_DSP* s, void* ram_64k ) { s->init( ram_64k ); } +void spc_dsp_set_output ( SPC_DSP* s, spc_dsp_sample_t* p, int n ) { s->set_output( p, n ); } +int spc_dsp_sample_count( SPC_DSP const* s ) { return s->sample_count(); } +void spc_dsp_reset ( SPC_DSP* s ) { s->reset(); } +void spc_dsp_soft_reset ( SPC_DSP* s ) { s->soft_reset(); } +int spc_dsp_read ( SPC_DSP const* s, int addr ) { return s->read( addr ); } +void spc_dsp_write ( SPC_DSP* s, int addr, int data ) { s->write( addr, data ); } +void spc_dsp_run ( SPC_DSP* s, int clock_count ) { s->run( clock_count ); } +void spc_dsp_mute_voices ( SPC_DSP* s, int mask ) { s->mute_voices( mask ); } +void spc_dsp_disable_surround( SPC_DSP* s, int disable ) { s->disable_surround( disable ); } +void spc_dsp_load ( SPC_DSP* s, unsigned char const regs [spc_dsp_register_count] ) { s->load( regs ); } + +#if !SPC_NO_COPY_STATE_FUNCS +void spc_dsp_copy_state ( SPC_DSP* s, unsigned char** p, spc_dsp_copy_func_t f ) { s->copy_state( p, f ); } +int spc_dsp_check_kon ( SPC_DSP* s ) { return s->check_kon(); } +#endif diff --git a/waterbox/sameboy/snes_spc/dsp.h b/waterbox/sameboy/snes_spc/dsp.h new file mode 100644 index 0000000000..59867d92f9 --- /dev/null +++ b/waterbox/sameboy/snes_spc/dsp.h @@ -0,0 +1,83 @@ +/* SNES SPC-700 DSP emulator C interface (also usable from C++) */ + +/* snes_spc 0.9.0 */ +#ifndef DSP_H +#define DSP_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +typedef struct SPC_DSP SPC_DSP; + +/* Creates new DSP emulator. NULL if out of memory. */ +SPC_DSP* spc_dsp_new( void ); + +/* Frees DSP emulator */ +void spc_dsp_delete( SPC_DSP* ); + +/* Initializes DSP and has it use the 64K RAM provided */ +void spc_dsp_init( SPC_DSP*, void* ram_64k ); + +/* Sets destination for output samples. If out is NULL or out_size is 0, +doesn't generate any. */ +typedef short spc_dsp_sample_t; +void spc_dsp_set_output( SPC_DSP*, spc_dsp_sample_t* out, int out_size ); + +/* Number of samples written to output since it was last set, always +a multiple of 2. Undefined if more samples were generated than +output buffer could hold. */ +int spc_dsp_sample_count( SPC_DSP const* ); + + +/**** Emulation *****/ + +/* Resets DSP to power-on state */ +void spc_dsp_reset( SPC_DSP* ); + +/* Emulates pressing reset switch on SNES */ +void spc_dsp_soft_reset( SPC_DSP* ); + +/* Reads/writes DSP registers. For accuracy, you must first call spc_dsp_run() */ +/* to catch the DSP up to present. */ +int spc_dsp_read ( SPC_DSP const*, int addr ); +void spc_dsp_write( SPC_DSP*, int addr, int data ); + +/* Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks */ +/* a pair of samples is be generated. */ +void spc_dsp_run( SPC_DSP*, int clock_count ); + + +/**** Sound control *****/ + +/* Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */ +enum { spc_dsp_voice_count = 8 }; +void spc_dsp_mute_voices( SPC_DSP*, int mask ); + +/* If true, prevents channels and global volumes from being phase-negated. +Only supported by fast DSP; has no effect on accurate DSP. */ +void spc_dsp_disable_surround( SPC_DSP*, int disable ); + + +/**** State save/load *****/ + +/* Resets DSP and uses supplied values to initialize registers */ +enum { spc_dsp_register_count = 128 }; +void spc_dsp_load( SPC_DSP*, unsigned char const regs [spc_dsp_register_count] ); + +/* Saves/loads exact emulator state (accurate DSP only) */ +enum { spc_dsp_state_size = 640 }; /* maximum space needed when saving */ +typedef void (*spc_dsp_copy_func_t)( unsigned char** io, void* state, size_t ); +void spc_dsp_copy_state( SPC_DSP*, unsigned char** io, spc_dsp_copy_func_t ); + +/* Returns non-zero if new key-on events occurred since last call (accurate DSP only) */ +int spc_dsp_check_kon( SPC_DSP* ); + + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/license.txt b/waterbox/sameboy/snes_spc/license.txt new file mode 100644 index 0000000000..5faba9d48c --- /dev/null +++ b/waterbox/sameboy/snes_spc/license.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/waterbox/sameboy/snes_spc/readme.txt b/waterbox/sameboy/snes_spc/readme.txt new file mode 100644 index 0000000000..979913f0ac --- /dev/null +++ b/waterbox/sameboy/snes_spc/readme.txt @@ -0,0 +1,86 @@ +snes_spc 0.9.0: SNES SPC-700 APU Emulator +----------------------------------------- +This library includes a full SPC emulator and an S-DSP emulator that can +be used on its own. Two S-DSP emulators are available: a highly accurate +one for use in a SNES emulator, and a 3x faster one for use in an SPC +music player or a resource-limited SNES emulator. + +* Can be used from C and C++ code +* Full SPC-700 APU emulator with cycle accuracy in most cases +* Loads, plays, and saves SPC music files +* Can save and load exact full emulator state +* DSP voice muting, surround sound disable, and song tempo adjustment +* Uses 7% CPU average on 400 MHz Mac to play an SPC using fast DSP + +The accurate DSP emulator is based on past research by others and +hundreds of hours of recent research by me. It passes over a hundred +strenuous timing and behavior validation tests that were also run on the +SNES. As far as I know, it's the first DSP emulator with cycle accuracy, +properly emulating every DSP register and memory access at the exact SPC +cycle it occurs at, whereas previous DSP emulators emulated these only +to the nearest sample (which occurs every 32 clocks). + +Author : Shay Green +Website: http://www.slack.net/~ant/ +Forum : http://groups.google.com/group/blargg-sound-libs +License: GNU Lesser General Public License (LGPL) + + +Getting Started +--------------- +Build a program consisting of demo/play_spc.c, demo/demo_util.c, +demo/wave_writer.c, and all source files in snes_spc/. Put an SPC music +file in the same directory and name it "test.spc". Running the program +should generate the recording "out.wav". + +Read snes_spc.txt for more information. Post to the discussion forum for +assistance. + + +Files +----- +snes_spc.txt Documentation +changes.txt Change log +license.txt GNU LGPL license + +demo/ + play_spc.c Records SPC file to wave sound file + benchmark.c Finds how fast emulator runs on your computer + trim_spc.c Trims silence off beginning of an SPC file + save_state.c Saves/loads exact emulator state to/from file + comm.c Communicates with SPC how SNES would + demo_util.h General utility functions used by demos + demo_util.c + wave_writer.h WAVE sound file writer used for demo output + wave_writer.c + +fast_dsp/ Optional standalone fast DSP emulator + SPC_DSP.h To use with full SPC emulator, move into + SPC_DSP.cpp snes_spc/ and replace original files + +snes_spc/ Library sources + blargg_config.h Configuration (modify as necessary) + + spc.h C interface to SPC emulator and sound filter + spc.cpp + + SPC_Filter.h Optional filter to make sound more authentic + SPC_Filter.cpp + + SNES_SPC.h Full SPC emulator + SNES_SPC.cpp + SNES_SPC_misc.cpp + SNES_SPC_state.cpp + SPC_CPU.h + + dsp.h C interface to DSP emulator + dsp.cpp + + SPC_DSP.h Standalone accurate DSP emulator + SPC_DSP.cpp + blargg_common.h + blargg_endian.h + blargg_source.h + +-- +Shay Green diff --git a/waterbox/sameboy/snes_spc/snes_spc.txt b/waterbox/sameboy/snes_spc/snes_spc.txt new file mode 100644 index 0000000000..d37b34347f --- /dev/null +++ b/waterbox/sameboy/snes_spc/snes_spc.txt @@ -0,0 +1,318 @@ +snes_spc 0.9.0: SNES SPC-700 APU Emulator +----------------------------------------- +Author : Shay Green +Website: http://www.slack.net/~ant/ +Forum : http://groups.google.com/group/blargg-sound-libs +License: GNU Lesser General Public License (LGPL) + + +Contents +-------- +* C and C++ Interfaces +* Overview +* Full SPC Emulation +* DSP Emulation +* SPC Music Playback +* State Copying +* Library Compilation +* Error handling +* Solving Problems +* Accurate S-DSP Limitations +* Fast S-DSP Limitations +* S-SMP Limitations +* To Do +* Thanks + + +C and C++ Interfaces +-------------------- +The library includes a C interface in spc.h and dsp.h, which can be used +from C and C++. This C interface is referred to in the following +documentation. If you're building this as a shared library (rather than +linking statically), you should use the C interface since it will change +less in future versions. + +The native C++ interface is in the header files SNES_SPC.h, SPC_DSP.h, +and SPC_Filter.h, and the two interfaces can be freely mixed in C++ +code. Conversion between the two interfaces generally follows a pattern: + + C interface C++ interface + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + SNES_SPC* spc; SNES_SPC* spc; + + spc = spc_new(); spc = new SNES_SPC; + + spc_play( spc, count, buf ); spc->play( count, buf ); + + spc_sample_rate SNES_SPC::sample_rate + + spc_delete( spc ); delete spc; + + +Overview +-------- +There are three main roles for this library: +* Full SPC emulation in a SNES emulator +* DSP emulation in a SNES emulator (where you emulate the SMP CPU) +* SPC playback in an SPC music file player + +Each of these uses are described separately below. + + +Full SPC Emulation +------------------ +See spc.h for full function reference (SNES_SPC.h if using C++). + +* Create SPC emulator with spc_new() and check for NULL. + +* Call spc_init_rom() with a pointer to the 64-byte IPL ROM dump (not +included with library). + +* When your emulated SNES is powered on, call spc_reset(). When the +reset switch is pressed, call spc_soft_reset(). + +* Call spc_set_output() with your output buffer, then do emulation. + +* When the SNES CPU accesses APU ports, call spc_read_port() and +spc_write_port(). + +* When your emulator's timebase is going back to 0, call +spc_end_frame(), usually at the end of a video frame or scanline. + +* Periodically play samples from your buffer. Use spc_sample_count() to +find out how many samples have been written, then spc_set_output() after +you've made more space in your buffer. + +* Save/load full emulator state with spc_copy_state(). + +* You can save as an SPC music file with spc_save_spc(). + +* When done, use spc_delete() to free memory. + + +DSP Emulation +------------- +See dsp.h for full function reference (SPC_DSP.h if using C++). + +* Create DSP emulator with spc_dsp_new() and check for NULL. + +* Let the DSP know where your 64K RAM is with spc_dsp_init(). + +* When your emulated SNES is powered on, call spc_dsp_reset(). When the +reset switch is pressed, call spc_dsp_soft_reset(). + +* Call spc_dsp_set_output() with your output buffer, then do emulation. + +* Use spc_dsp_run() to run DSP for specified number of clocks (1024000 +per second). Every 32 clocks a pair of samples is added to your output +buffer. + +* Use spc_dsp_read() and spc_dsp_write() to handle DSP reads/writes from +the S-SMP. Before calling these always catch the DSP up to present time +with spc_dsp_run(). + +* Periodically play samples from your buffer. Use spc_dsp_sample_count() +to find out how many samples have been written, then +spc_dsp_set_output() after you've made more space in your buffer. + +* Use spc_dsp_copy_state() to save/load full DSP state. + +* When done, use spc_delete() to free memory. + + +SPC Music Playback +------------------ +See spc.h for full function reference (SNES_SPC.h if using C++). + +* Create SPC emulator with spc_new() and check for NULL. + +* Load SPC with spc_load_spc() and check for error. + +* Optionally cear echo buffer with spc_clear_echo(). Many SPCs have +garbage in echo buffer, which causes noise at the beginning. + +* Generate samples as needed with spc_play(). + +* When done, use spc_delete() to free memory. + +* For a more complete game music playback library, use Game_Music_Emu + + +State Copying +------------- +The SPC and DSP modules include state save/load functions. They take a +pointer to a pointer to a buffer to store state, and a copy function. +This copy function can either copy data to the buffer or from it, +allowing state save and restore with the same library function. The +internal save state format allows for future expansion without making +previous save states unusable. + +The SPC save state format puts the most important parts first to make it +easier to manually examine. It's organized as follows: + +Offset Size Data +- - - - - - - - - - - - - - - - - - + 0 $10000 SPC RAM +$10000 $10 SMP $F0-$FF registers +$10010 4 SMP $F4-$F8 output registers +$10014 2 PC +$10016 1 A +$10017 1 X +$10018 1 Y +$10019 1 PSW +$1001A 1 SP +$1001B 5 internal +$10020 $80 DSP registers +$100A0 ... internal + + +Library Compilation +------------------- +While this library is in C++, it has been written to easily link in a C +program *without* needing the standard C++ library. It doesn't use +exception handling or run-time type information (RTTI), so you can +disable these in your C++ compiler to increase efficiency. + +If you're building a shared library (DLL), I recommend only exporting +the C interfaces in spc.h and dsp.h, as the C++ interfaces expose +implementation details that will break link compatibility across +versions. + +If you're using C and compiling with GCC, I recommend the following +command-line options when compiling the library source, otherwise GCC +will insert calls to the standard C++ library and require that it be +linked in: + + -fno-rtti -fno-exceptions + +For maximum optimization, see the NDEBUG and BLARGG_NONPORTABLE options +in blargg_config. If using GCC, you can enable these by adding the +following command-line options when you invoke gcc. If you encounter +problems, try without -DBLARGG_NONPORTABLE; if that works, contact me so +I can figure out why BLARGG_NONPORTABLE was causing it to fail. + + -O3 -DNDEBUG -DBLARGG_NONPORTABLE -fno-rtti -fno-exceptions + + + +Error handling +-------------- +Functions which can fail have a return type of spc_err_t (blargg_err_t +in the C++ interfaces), which is a pointer to an error string (const +char*). If a function is successful it returns NULL. Errors that you can +easily avoid are checked with debug assertions; spc_err_t return values +are only used for genuine run-time errors that can't be easily predicted +in advance (out of memory, I/O errors, incompatible file data). Your +code should check all error values. + +To improve usability for C programmers, C++ programmers unfamiliar with +exceptions, and compatibility with older C++ compilers, the library does +*not* throw any C++ exceptions and uses malloc() instead of the standard +operator new. This means that you *must* check for NULL when creating a +library object with the new operator. + + +Solving Problems +---------------- +If you're having problems, try the following: + +* If you're getting garbled sound, try this simple siren generator in +place of your call to play(). This will quickly tell whether the problem +is in the library or in your code. + + static void play_siren( long count, short* out ) + { + static double a, a2; + while ( count-- ) + *out++ = 0x2000 * sin( a += .1 + .05*sin( a2+=.00005 ) ); + } + +* Enable debugging support in your environment. This enables assertions +and other run-time checks. + +* Turn the compiler's optimizer is off. Sometimes an optimizer generates +bad code. + +* If multiple threads are being used, ensure that only one at a time is +accessing a given set of objects from the library. This library is not +in general thread-safe, though independent objects can be used in +separate threads. + +* If all else fails, see if the demos work. + + +Accurate S-DSP Limitations +-------------------------- +* Power-up and soft reset behavior might have slight inaccuracies. + +* Muting (FLG bit 6) behavior when toggling bit very rapidly is not +emulated properly. + +* No other known inaccuracies. Has passed 100+ strenuous tests. + + +Fast S-DSP Limitations +---------------------- +* Uses faster sample calculations except in cases where exact value is +actually important (BRR decoding, and gaussian interpolation combined +with pitch modulation). + +* Stops decoding BRR data when a voice's envelope has released to +silence. + +* Emulates 32 clocks at a time, so DSP register and memory accesses are +all done in a bunch rather than spread out. Even though, some clever +code makes register accesses separated by 40 or so clocks occur with +cycle-accurate timing. + + +S-SMP Limitations +----------------- +* Opcode fetches and indirect pointers are always read directly from +memory, even for the $F0-$FF region, and the DSP is not caught up for +these fetches. + +* Attempts to perversely execute data in registers or an area being +modified by echo will not be emulated properly. + +* Has not been thoroughly tested. + +* Test register ($F0) is not implemented. + +* Echo buffer can overwrite IPL ROM area, and does not correctly update +extra RAM there. + + +To Do +----- +* I'd like feedback on the interface and any ways to improve it. In +particular, the differing features between the accurate and fast DSP +emulators might make it harder to cleanly switch between them without +modifying source code. + +* Finish thorough tests on SMP memory access times. + +* Finish thorough tests on SMP instruction behavior (flags, registers). + +* Finish thorough tests on SMP timers. + +* Finish power-up and reset behavior testing. + +* Come up with best starting conditions to play an SPC and implement in +hardware SNES SPC player for verification. + + +Thanks +------ +Thanks to Anti-Resonance's SPC2ROM and help getting SPCs playing on my +SNES in the first place, then Brad Martin's openspc and Chris Moeller's +openspc++ C++ adaptation, giving me a good SPC emulator to start with +several years ago. Thanks to Richard Bannister, Mahendra Tallur, Shazz, +nenolod, theHobbit, Johan Samuelsson, nes6502, and Micket for helping +test my Game_Music_Emu library. Thanks to hcs for help in converting to +C for the Rockbox port. Thanks to byuu (bsnes author) and pagefault and +Nach (zsnes team) for testing and using my new rewritten DSP in their +emulators. Thanks to anomie for his good SNES documentation and +discussions with me to keep it up to date with my latest findings. +-- +Shay Green diff --git a/waterbox/sameboy/snes_spc/spc.cpp b/waterbox/sameboy/snes_spc/spc.cpp new file mode 100644 index 0000000000..4e5a77ef15 --- /dev/null +++ b/waterbox/sameboy/snes_spc/spc.cpp @@ -0,0 +1,74 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "spc.h" + +#include "SNES_SPC.h" +#include "SPC_Filter.h" + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +SNES_SPC* spc_new( void ) +{ + // be sure constants match + assert( spc_sample_rate == (int) SNES_SPC::sample_rate ); + assert( spc_rom_size == (int) SNES_SPC::rom_size ); + assert( spc_clock_rate == (int) SNES_SPC::clock_rate ); + assert( spc_clocks_per_sample == (int) SNES_SPC::clocks_per_sample ); + assert( spc_port_count == (int) SNES_SPC::port_count ); + assert( spc_voice_count == (int) SNES_SPC::voice_count ); + assert( spc_tempo_unit == (int) SNES_SPC::tempo_unit ); + assert( spc_file_size == (int) SNES_SPC::spc_file_size ); + #if !SPC_NO_COPY_STATE_FUNCS + assert( spc_state_size == (int) SNES_SPC::state_size ); + #endif + + SNES_SPC* s = new SNES_SPC; + if ( s && s->init() ) + { + delete s; + s = 0; + } + return s; +} + +void spc_delete ( SNES_SPC* s ) { delete s; } +void spc_init_rom ( SNES_SPC* s, unsigned char const r [64] ) { s->init_rom( r ); } +void spc_set_output ( SNES_SPC* s, spc_sample_t* p, int n ) { s->set_output( p, n ); } +int spc_sample_count ( SNES_SPC const* s ) { return s->sample_count(); } +void spc_reset ( SNES_SPC* s ) { s->reset(); } +void spc_soft_reset ( SNES_SPC* s ) { s->soft_reset(); } +int spc_read_port ( SNES_SPC* s, spc_time_t t, int p ) { return s->read_port( t, p ); } +void spc_write_port ( SNES_SPC* s, spc_time_t t, int p, int d ) { s->write_port( t, p, d ); } +void spc_end_frame ( SNES_SPC* s, spc_time_t t ) { s->end_frame( t ); } +void spc_mute_voices ( SNES_SPC* s, int mask ) { s->mute_voices( mask ); } +void spc_disable_surround( SNES_SPC* s, int disable ) { s->disable_surround( disable ); } +void spc_set_tempo ( SNES_SPC* s, int tempo ) { s->set_tempo( tempo ); } +uint8_t* spc_get_ram(SNES_SPC* s) { return s->get_ram(); } +spc_err_t spc_load_spc ( SNES_SPC* s, void const* p, long n ) { return s->load_spc( p, n ); } +void spc_clear_echo ( SNES_SPC* s ) { s->clear_echo(); } +spc_err_t spc_play ( SNES_SPC* s, int count, short* out ) { return s->play( count, out ); } +spc_err_t spc_skip ( SNES_SPC* s, int count ) { return s->skip( count ); } +#if !SPC_NO_COPY_STATE_FUNCS +void spc_copy_state ( SNES_SPC* s, unsigned char** p, spc_copy_func_t f ) { s->copy_state( p, f ); } +void spc_init_header ( void* spc_out ) { SNES_SPC::init_header( spc_out ); } +void spc_save_spc ( SNES_SPC* s, void* spc_out ) { s->save_spc( spc_out ); } +int spc_check_kon ( SNES_SPC* s ) { return s->check_kon(); } +#endif + +SPC_Filter* spc_filter_new( void ) { return new SPC_Filter; } +void spc_filter_delete( SPC_Filter* f ) { delete f; } +void spc_filter_run( SPC_Filter* f, spc_sample_t* p, int s ) { f->run( p, s ); } +void spc_filter_clear( SPC_Filter* f ) { f->clear(); } +void spc_filter_set_gain( SPC_Filter* f, int gain ) { f->set_gain( gain ); } +void spc_filter_set_bass( SPC_Filter* f, int bass ) { f->set_bass( bass ); } diff --git a/waterbox/sameboy/snes_spc/spc.h b/waterbox/sameboy/snes_spc/spc.h new file mode 100644 index 0000000000..cf23f3c945 --- /dev/null +++ b/waterbox/sameboy/snes_spc/spc.h @@ -0,0 +1,149 @@ +/* SNES SPC-700 APU emulator C interface (also usable from C++) */ + +/* snes_spc 0.9.0 */ +#ifndef SPC_H +#define SPC_H + +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* Error string return. NULL if success, otherwise error message. */ +typedef const char* spc_err_t; + +typedef struct SNES_SPC SNES_SPC; + +/* Creates new SPC emulator. NULL if out of memory. */ +SNES_SPC* spc_new( void ); + +/* Frees SPC emulator */ +void spc_delete( SNES_SPC* ); + +/* Sample pairs generated per second */ +enum { spc_sample_rate = 32000 }; + + +/**** Emulator use ****/ + +/* Sets IPL ROM data. Library does not include ROM data. Most SPC music files +don't need ROM, but a full emulator must provide this. */ +enum { spc_rom_size = 0x40 }; +void spc_init_rom( SNES_SPC*, unsigned char const rom [spc_rom_size] ); + +/* Sets destination for output samples */ +typedef short spc_sample_t; +void spc_set_output( SNES_SPC*, spc_sample_t* out, int out_size ); + +/* Number of samples written to output since last set */ +int spc_sample_count( SNES_SPC const* ); + +/* Resets SPC to power-on state. This resets your output buffer, so you must +call spc_set_output() after this. */ +void spc_reset( SNES_SPC* ); + +/* Emulates pressing reset switch on SNES. This resets your output buffer, so +you must call spc_set_output() after this. */ +void spc_soft_reset( SNES_SPC* ); + +/* 1024000 SPC clocks per second, sample pair every 32 clocks */ +typedef int spc_time_t; +enum { spc_clock_rate = 1024000 }; +enum { spc_clocks_per_sample = 32 }; + +/* Reads/writes port at specified time */ +enum { spc_port_count = 4 }; +int spc_read_port ( SNES_SPC*, spc_time_t, int port ); +void spc_write_port( SNES_SPC*, spc_time_t, int port, int data ); + +/* Runs SPC to end_time and starts a new time frame at 0 */ +void spc_end_frame( SNES_SPC*, spc_time_t end_time ); + +uint8_t* spc_get_ram(SNES_SPC*); + +/**** Sound control ****/ + +/*Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */ +enum { spc_voice_count = 8 }; +void spc_mute_voices( SNES_SPC*, int mask ); + +/* If true, prevents channels and global volumes from being phase-negated. +Only supported by fast DSP; has no effect on accurate DSP. */ +void spc_disable_surround( SNES_SPC*, int disable ); + +/* Sets tempo, where spc_tempo_unit = normal, spc_tempo_unit / 2 = half speed, etc. */ +enum { spc_tempo_unit = 0x100 }; +void spc_set_tempo( SNES_SPC*, int ); + + +/**** SPC music playback *****/ + +/* Loads SPC data into emulator. Returns NULL on success, otherwise error string. */ +spc_err_t spc_load_spc( SNES_SPC*, void const* spc_in, long size ); + +/* Clears echo region. Useful after loading an SPC as many have garbage in echo. */ +void spc_clear_echo( SNES_SPC* ); + +/* Plays for count samples and write samples to out. Discards samples if out +is NULL. Count must be a multiple of 2 since output is stereo. */ +spc_err_t spc_play( SNES_SPC*, int count, short* out ); + +/* Skips count samples. Several times faster than spc_play(). */ +spc_err_t spc_skip( SNES_SPC*, int count ); + + +/**** State save/load (only available with accurate DSP) ****/ + +/* Saves/loads exact emulator state */ +enum { spc_state_size = 67 * 1024L }; /* maximum space needed when saving */ +typedef void (*spc_copy_func_t)( unsigned char** io, void* state, size_t ); +void spc_copy_state( SNES_SPC*, unsigned char** io, spc_copy_func_t ); + +/* Writes minimal SPC file header to spc_out */ +void spc_init_header( void* spc_out ); + +/* Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out. +Does not set up SPC header; use spc_init_header() for that. */ +enum { spc_file_size = 0x10200 }; /* spc_out must have this many bytes allocated */ +void spc_save_spc( SNES_SPC*, void* spc_out ); + +/* Returns non-zero if new key-on events occurred since last check. Useful for +trimming silence while saving an SPC. */ +int spc_check_kon( SNES_SPC* ); + + +/**** SPC_Filter ****/ + +typedef struct SPC_Filter SPC_Filter; + +/* Creates new filter. NULL if out of memory. */ +SPC_Filter* spc_filter_new( void ); + +/* Frees filter */ +void spc_filter_delete( SPC_Filter* ); + +/* Filters count samples of stereo sound in place. Count must be a multiple of 2. */ +void spc_filter_run( SPC_Filter*, spc_sample_t* io, int count ); + +/* Clears filter to silence */ +void spc_filter_clear( SPC_Filter* ); + +/* Sets gain (volume), where spc_filter_gain_unit is normal. Gains greater than +spc_filter_gain_unit are fine, since output is clamped to 16-bit sample range. */ +enum { spc_filter_gain_unit = 0x100 }; +void spc_filter_set_gain( SPC_Filter*, int gain ); + +/* Sets amount of bass (logarithmic scale) */ +enum { spc_filter_bass_none = 0 }; +enum { spc_filter_bass_norm = 8 }; /* normal amount */ +enum { spc_filter_bass_max = 31 }; +void spc_filter_set_bass( SPC_Filter*, int bass ); + + +#ifdef __cplusplus + } +#endif + +#endif From 09672a6bf9ecbf3af5d3376183ec94b0d1617ff1 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Tue, 18 Jul 2017 20:45:06 -0400 Subject: [PATCH 07/13] SGB: fix bugs in attr chr. Fixes some graphical glitches in Daffy Duck Marvin Missions --- waterbox/sameboy/sgb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/waterbox/sameboy/sgb.c b/waterbox/sameboy/sgb.c index 896cc68c73..42c0ba7b9f 100644 --- a/waterbox/sameboy/sgb.c +++ b/waterbox/sameboy/sgb.c @@ -332,7 +332,7 @@ static void cmd_attr_chr() return; } int npacket = (n + 87) / 64; - if ((sgb.command[0] & 7) != npacket) + if ((sgb.command[0] & 7) < npacket) { utils_log("SGB: cmd_attr_chr bad length\n"); return; @@ -343,9 +343,9 @@ static void cmd_attr_chr() if (y > 17) y = 17; int vertical = sgb.command[5]; - for (int i = 0; i < 360; i++) + for (int i = 0; i < n; i++) { - uint8_t v = i / 4 + 6; + uint8_t v = sgb.command[i / 4 + 6]; int a = v >> (2 * (3 - (i & 3))) & 3; dst[y * 20 + x] = a; if (vertical) From 7d2ee60adee0da0b454f9d570008ee9cfb8a29c5 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Wed, 19 Jul 2017 17:08:48 -0400 Subject: [PATCH 08/13] sameboy: lag flag / inputcallback --- waterbox/sameboy/bizhawk.cpp | 12 ++++++- waterbox/sameboy/gb.c | 68 ++++++------------------------------ waterbox/sameboy/gb.h | 8 +++-- waterbox/sameboy/memory.c | 3 ++ 4 files changed, 30 insertions(+), 61 deletions(-) diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp index dbcfdcb0e9..531e4f4eeb 100644 --- a/waterbox/sameboy/bizhawk.cpp +++ b/waterbox/sameboy/bizhawk.cpp @@ -57,6 +57,13 @@ static uint8_t SerialEndCallback(GB_gameboy_t *gb) return 0; } +static void (*FrontendInputCallback)(); + +static void InputCallback(GB_gameboy_t *gb) +{ + FrontendInputCallback(); +} + static blip_t *leftblip; static blip_t *rightblip; const int SOUND_RATE_GB = 2097152; @@ -146,6 +153,7 @@ ECL_EXPORT void FrameAdvance(MyFrameInfo &f) } sound_start_clock = GB_epoch(&GB); CurrentFramebuffer = f.VideoBuffer; + GB_set_lagged(&GB, true); uint32_t target = 35112 - FrameOverflow; f.Cycles = GB_run_cycles(&GB, target); @@ -166,6 +174,7 @@ ECL_EXPORT void FrameAdvance(MyFrameInfo &f) f.Samples = blip_read_samples(leftblip, f.SoundBuffer, 2048, 1); blip_read_samples(rightblip, f.SoundBuffer + 1, 2048, 1); CurrentFramebuffer = NULL; + f.Lagged = GB_get_lagged(&GB); } static void SetMemoryArea(MemoryArea *m, GB_direct_access_t access, const char *name, int32_t flags) @@ -194,7 +203,8 @@ ECL_EXPORT void GetMemoryAreas(MemoryArea *m) ECL_EXPORT void SetInputCallback(void (*callback)()) { - // TODO + FrontendInputCallback = callback; + GB_set_input_callback(&GB, callback ? InputCallback : nullptr); } int main() diff --git a/waterbox/sameboy/gb.c b/waterbox/sameboy/gb.c index 814a78811b..e8e90d55bd 100644 --- a/waterbox/sameboy/gb.c +++ b/waterbox/sameboy/gb.c @@ -43,54 +43,11 @@ void GB_log(GB_gameboy_t *gb, const char *fmt, ...) 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) -{ -#if 0 - 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_reset(gb); @@ -102,9 +59,6 @@ void GB_init_sgb(GB_gameboy_t *gb) gb->ram = malloc(gb->ram_size = 0x2000); gb->vram = malloc(gb->vram_size = 0x2000); gb->is_sgb = 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_reset(gb); @@ -116,9 +70,6 @@ void GB_init_cgb(GB_gameboy_t *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_reset(gb); @@ -298,17 +249,9 @@ 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) { - 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) { @@ -541,3 +484,14 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * return NULL; } } + +void GB_set_lagged(GB_gameboy_t *gb, bool lagged) +{ + gb->lagged = lagged; +} + +bool GB_get_lagged(GB_gameboy_t *gb) +{ + return gb->lagged; +} + diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h index e67a89244d..9b5f4613dd 100644 --- a/waterbox/sameboy/gb.h +++ b/waterbox/sameboy/gb.h @@ -165,7 +165,7 @@ typedef enum { 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 void (*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); @@ -382,6 +382,7 @@ struct GB_gameboy_internal_s { /* I/O */ uint32_t *screen; int keys; + bool lagged; /* Timing */ uint64_t cycles_since_epoch; @@ -390,7 +391,6 @@ struct GB_gameboy_internal_s { 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; @@ -496,7 +496,6 @@ void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_pre 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); @@ -512,4 +511,7 @@ void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data); void GB_disconnect_serial(GB_gameboy_t *gb); +void GB_set_lagged(GB_gameboy_t *gb, bool lagged); +bool GB_get_lagged(GB_gameboy_t *gb); + #endif /* GB_h */ diff --git a/waterbox/sameboy/memory.c b/waterbox/sameboy/memory.c index 7ae5368d70..f53aac608d 100644 --- a/waterbox/sameboy/memory.c +++ b/waterbox/sameboy/memory.c @@ -137,6 +137,9 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) if (addr < 0xFF80) { if (addr == 0xff00 && gb->is_sgb) { + if (gb->input_callback) + gb->input_callback(gb); + gb->lagged = false; return sgb_read_ff00(gb->cycles_since_epoch); } From cf8013af4a642fd0c9ef834bac336b27695f022b Mon Sep 17 00:00:00 2001 From: nattthebear Date: Wed, 19 Jul 2017 18:48:11 -0400 Subject: [PATCH 09/13] sameboy: Add more features for the merciless slave driver --- BizHawk.Client.Common/RomLoader.cs | 4 +- BizHawk.Client.EmuHawk/MainForm.cs | 5 + BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs | 493 ++++++++++-------- .../Consoles/Nintendo/Gameboy/Gambatte.cs | 20 +- .../Nintendo/Gameboy/IGameboyCommon.cs | 47 +- .../Consoles/Nintendo/Gameboy/LibSameboy.cs | 9 + .../Consoles/Nintendo/Gameboy/Pizza.cs | 2 +- .../Consoles/Nintendo/Gameboy/Sameboy.cs | 51 ++ waterbox/sameboy/bizhawk.cpp | 18 + waterbox/sameboy/display.c | 7 +- waterbox/sameboy/gb.h | 6 +- 11 files changed, 409 insertions(+), 253 deletions(-) diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 810c8a303b..d054ab86f6 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -921,8 +921,8 @@ namespace BizHawk.Client.Common if (!Global.Config.GB_AsSGB) { //core = CoreInventory.Instance["GB", "Pizza Boy"]; - //core = CoreInventory.Instance["GB", "Gambatte"]; - core = CoreInventory.Instance["GB", "SameBoy"]; + core = CoreInventory.Instance["GB", "Gambatte"]; + //core = CoreInventory.Instance["GB", "SameBoy"]; } else { diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 2274b6ce5e..611c693e20 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -33,6 +33,7 @@ using BizHawk.Emulation.Common.Base_Implementations; using BizHawk.Emulation.Cores.Nintendo.SNES9X; using BizHawk.Emulation.Cores.Consoles.SNK; using BizHawk.Emulation.Cores.Consoles.Sega.PicoDrive; +using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; namespace BizHawk.Client.EmuHawk { @@ -1744,6 +1745,10 @@ namespace BizHawk.Client.EmuHawk { sNESToolStripMenuItem.Visible = true; } + else if (Emulator is Sameboy) + { + GBSubMenu.Visible = true; + } break; case "Coleco": ColecoSubMenu.Visible = true; diff --git a/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs b/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs index 6151c05677..8dc50567ef 100644 --- a/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs +++ b/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs @@ -9,13 +9,15 @@ using BizHawk.Emulation.Cores.Nintendo.Gameboy; using BizHawk.Client.EmuHawk.WinFormExtensions; using System.Collections.Generic; using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; +using BizHawk.Common; namespace BizHawk.Client.EmuHawk { public partial class GBGPUView : Form, IToolFormAutoConfig { [RequiredService] - public Gameboy Gb { get; private set; } + public IGameboyCommon Gb { get; private set; } // TODO: freeze semantics are a bit weird: details for a mouseover or freeze are taken from the current // state, not the state at the last callback (and so can be quite different when update is set to manual). @@ -32,11 +34,8 @@ namespace BizHawk.Client.EmuHawk // g' = 8.25g // b' = 8.25b - // gambatte doesn't modify these memory locations unless you reconstruct, so we can store - private IntPtr _vram; - private IntPtr _bgpal; - private IntPtr _sppal; - private IntPtr _oam; + + private GPUMemoryAreas _memory; private bool _cgb; // set once at start private int _lcdc; // set at each callback @@ -89,13 +88,9 @@ namespace BizHawk.Client.EmuHawk _cgb = Gb.IsCGBMode(); _lcdc = 0; - // TODO: can this be a required Emulator Service, and let the tool manage the logic of closing? - if (!Gb.GetGPUMemoryAreas(out _vram, out _bgpal, out _sppal, out _oam)) - { - if (Visible) - Close(); - } - tilespal = _bgpal; + _memory = Gb.GetGPU(); + + tilespal = _memory.Bgpal; if (_cgb) label4.Enabled = true; @@ -357,112 +352,120 @@ namespace BizHawk.Client.EmuHawk #endregion - void ScanlineCallback(int lcdc) + void ScanlineCallback(byte lcdc) { - _lcdc = lcdc; - // set alpha on all pixels - unsafe + using (_memory.EnterExit()) { - int* p = (int*)_bgpal; - for (int i = 0; i < 32; i++) - p[i] |= unchecked((int)0xff000000); - p = (int*)_sppal; - for (int i = 0; i < 32; i++) - p[i] |= unchecked((int)0xff000000); - int c = Spriteback.ToArgb(); - for (int i = 0; i < 32; i += 4) - p[i] = c; - } + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - // bg maps - if (!_cgb) - { - DrawBGDMG( - bmpViewBG.BMP, - _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), - _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), - !lcdc.Bit(4), - _bgpal); + _lcdc = lcdc; + // set alpha on all pixels + // TODO: RE: Spriteback, you can't muck with Sameboy in this way due to how SGB reads stuff...? + /*unsafe + { + int* p = (int*)_bgpal; + for (int i = 0; i < 32; i++) + p[i] |= unchecked((int)0xff000000); + p = (int*)_sppal; + for (int i = 0; i < 32; i++) + p[i] |= unchecked((int)0xff000000); + int c = Spriteback.ToArgb(); + for (int i = 0; i < 32; i += 4) + p[i] = c; + }*/ - DrawBGDMG( - bmpViewWin.BMP, - _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), - _vram + 0x1000, // force win to second tile bank??? - true, - _bgpal); - } - else - { - DrawBGCGB( - bmpViewBG.BMP, - _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), - _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), - !lcdc.Bit(4), - _bgpal); + // bg maps + if (!_cgb) + { + DrawBGDMG( + bmpViewBG.BMP, + _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), + _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), + !lcdc.Bit(4), + _bgpal); - DrawBGCGB( - bmpViewWin.BMP, - _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), - _vram + 0x1000, // force win to second tile bank??? - true, - _bgpal); - } - bmpViewBG.Refresh(); - bmpViewWin.Refresh(); + DrawBGDMG( + bmpViewWin.BMP, + _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), + _vram + 0x1000, // force win to second tile bank??? + true, + _bgpal); + } + else + { + DrawBGCGB( + bmpViewBG.BMP, + _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), + _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), + !lcdc.Bit(4), + _bgpal); - // tile display - // TODO: user selects palette to use, instead of fixed palette 0 - // or possibly "smart" where, if a tile is in use, it's drawn with one of the palettes actually being used with it? - DrawTiles(bmpViewTiles1.BMP, _vram, tilespal); - bmpViewTiles1.Refresh(); - if (_cgb) - { - DrawTiles(bmpViewTiles2.BMP, _vram + 0x2000, tilespal); - bmpViewTiles2.Refresh(); - } + DrawBGCGB( + bmpViewWin.BMP, + _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), + _vram + 0x1000, // force win to second tile bank??? + true, + _bgpal); + } + bmpViewBG.Refresh(); + bmpViewWin.Refresh(); - // palettes - if (_cgb) - { - bmpViewBGPal.ChangeBitmapSize(8, 4); - if (bmpViewBGPal.Width != 128) - bmpViewBGPal.Width = 128; - bmpViewSPPal.ChangeBitmapSize(8, 4); - if (bmpViewSPPal.Width != 128) - bmpViewSPPal.Width = 128; - DrawPal(bmpViewBGPal.BMP, _bgpal, 8); - DrawPal(bmpViewSPPal.BMP, _sppal, 8); - } - else - { - bmpViewBGPal.ChangeBitmapSize(1, 4); - if (bmpViewBGPal.Width != 16) - bmpViewBGPal.Width = 16; - bmpViewSPPal.ChangeBitmapSize(2, 4); - if (bmpViewSPPal.Width != 32) - bmpViewSPPal.Width = 32; - DrawPal(bmpViewBGPal.BMP, _bgpal, 1); - DrawPal(bmpViewSPPal.BMP, _sppal, 2); - } - bmpViewBGPal.Refresh(); - bmpViewSPPal.Refresh(); + // tile display + // TODO: user selects palette to use, instead of fixed palette 0 + // or possibly "smart" where, if a tile is in use, it's drawn with one of the palettes actually being used with it? + DrawTiles(bmpViewTiles1.BMP, _vram, tilespal); + bmpViewTiles1.Refresh(); + if (_cgb) + { + DrawTiles(bmpViewTiles2.BMP, _vram + 0x2000, tilespal); + bmpViewTiles2.Refresh(); + } - // oam - if (lcdc.Bit(2)) // 8x16 - { - bmpViewOAM.ChangeBitmapSize(320, 16); - if (bmpViewOAM.Height != 16) - bmpViewOAM.Height = 16; - } - else - { - bmpViewOAM.ChangeBitmapSize(320, 8); - if (bmpViewOAM.Height != 8) - bmpViewOAM.Height = 8; - } - DrawOam(bmpViewOAM.BMP, _oam, _vram, _sppal, lcdc.Bit(2), _cgb); - bmpViewOAM.Refresh(); + // palettes + if (_cgb) + { + bmpViewBGPal.ChangeBitmapSize(8, 4); + if (bmpViewBGPal.Width != 128) + bmpViewBGPal.Width = 128; + bmpViewSPPal.ChangeBitmapSize(8, 4); + if (bmpViewSPPal.Width != 128) + bmpViewSPPal.Width = 128; + DrawPal(bmpViewBGPal.BMP, _bgpal, 8); + DrawPal(bmpViewSPPal.BMP, _sppal, 8); + } + else + { + bmpViewBGPal.ChangeBitmapSize(1, 4); + if (bmpViewBGPal.Width != 16) + bmpViewBGPal.Width = 16; + bmpViewSPPal.ChangeBitmapSize(2, 4); + if (bmpViewSPPal.Width != 32) + bmpViewSPPal.Width = 32; + DrawPal(bmpViewBGPal.BMP, _bgpal, 1); + DrawPal(bmpViewSPPal.BMP, _sppal, 2); + } + bmpViewBGPal.Refresh(); + bmpViewSPPal.Refresh(); + // oam + if (lcdc.Bit(2)) // 8x16 + { + bmpViewOAM.ChangeBitmapSize(320, 16); + if (bmpViewOAM.Height != 16) + bmpViewOAM.Height = 16; + } + else + { + bmpViewOAM.ChangeBitmapSize(320, 8); + if (bmpViewOAM.Height != 8) + bmpViewOAM.Height = 8; + } + DrawOam(bmpViewOAM.BMP, _oam, _vram, _sppal, lcdc.Bit(2), _cgb); + bmpViewOAM.Refresh(); + } // try to run the current mouseover, to refresh if the mouse is being held over a pane while the emulator runs // this doesn't really work well; the update rate seems to be throttled MouseEventArgs e = new MouseEventArgs(MouseButtons.None, 0, Cursor.Position.X, Cursor.Position.Y, 0); @@ -514,7 +517,7 @@ namespace BizHawk.Client.EmuHawk private void buttonRefresh_Click(object sender, EventArgs e) { - if (cbscanline == -2 && Gb != null) + if (cbscanline == -2) Gb.SetScanlineCallback(ScanlineCallback, -2); } @@ -614,145 +617,177 @@ namespace BizHawk.Client.EmuHawk private unsafe void PaletteMouseover(int x, int y, bool sprite) { - bmpViewDetails.ChangeBitmapSize(8, 10); - if (bmpViewDetails.Height != 80) - bmpViewDetails.Height = 80; - var sb = new StringBuilder(); - x /= 16; - y /= 16; - int* pal = (int*)(sprite ? _sppal : _bgpal) + x * 4; - int color = pal[y]; - - sb.AppendLine(string.Format("Palette {0}", x)); - sb.AppendLine(string.Format("Color {0}", y)); - sb.AppendLine(string.Format("(R,G,B) = ({0},{1},{2})", color >> 16 & 255, color >> 8 & 255, color & 255)); - - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 10), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - int* dest = (int*)lockdata.Scan0; - int pitch = lockdata.Stride / sizeof(int); - - for (int py = 0; py < 10; py++) + using (_memory.EnterExit()) { - for (int px = 0; px < 8; px++) + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; + + bmpViewDetails.ChangeBitmapSize(8, 10); + if (bmpViewDetails.Height != 80) + bmpViewDetails.Height = 80; + var sb = new StringBuilder(); + x /= 16; + y /= 16; + int* pal = (int*)(sprite ? _sppal : _bgpal) + x * 4; + int color = pal[y]; + + sb.AppendLine(string.Format("Palette {0}", x)); + sb.AppendLine(string.Format("Color {0}", y)); + sb.AppendLine(string.Format("(R,G,B) = ({0},{1},{2})", color >> 16 & 255, color >> 8 & 255, color & 255)); + + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 10), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + int* dest = (int*)lockdata.Scan0; + int pitch = lockdata.Stride / sizeof(int); + + for (int py = 0; py < 10; py++) { - if (py < 8) - *dest++ = color; - else - *dest++ = pal[px / 2]; + for (int px = 0; px < 8; px++) + { + if (py < 8) + *dest++ = color; + else + *dest++ = pal[px / 2]; + } + dest -= 8; + dest += pitch; } - dest -= 8; - dest += pitch; + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); } - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); } unsafe void TileMouseover(int x, int y, bool secondbank) { - // todo: draw with a specific palette - bmpViewDetails.ChangeBitmapSize(8, 8); - if (bmpViewDetails.Height != 64) - bmpViewDetails.Height = 64; - var sb = new StringBuilder(); - x /= 8; - y /= 8; - int tileindex = y * 16 + x; - int tileoffs = tileindex * 16; - if (_cgb) - sb.AppendLine(string.Format("Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, secondbank ? 1 : 0)); - else - sb.AppendLine(string.Format("Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); + using (_memory.EnterExit()) + { + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - DrawTile((byte*)_vram + tileoffs + (secondbank ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)tilespal); - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); + // todo: draw with a specific palette + bmpViewDetails.ChangeBitmapSize(8, 8); + if (bmpViewDetails.Height != 64) + bmpViewDetails.Height = 64; + var sb = new StringBuilder(); + x /= 8; + y /= 8; + int tileindex = y * 16 + x; + int tileoffs = tileindex * 16; + if (_cgb) + sb.AppendLine(string.Format("Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, secondbank ? 1 : 0)); + else + sb.AppendLine(string.Format("Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); + + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + DrawTile((byte*)_vram + tileoffs + (secondbank ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)tilespal); + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); + } } unsafe void TilemapMouseover(int x, int y, bool win) { - bmpViewDetails.ChangeBitmapSize(8, 8); - if (bmpViewDetails.Height != 64) - bmpViewDetails.Height = 64; - var sb = new StringBuilder(); - bool secondmap = win ? _lcdc.Bit(6) : _lcdc.Bit(3); - int mapoffs = secondmap ? 0x1c00 : 0x1800; - x /= 8; - y /= 8; - mapoffs += y * 32 + x; - byte* mapbase = (byte*)_vram + mapoffs; - int tileindex = mapbase[0]; - if (win || !_lcdc.Bit(4)) // 0x9000 base - if (tileindex < 128) - tileindex += 256; // compute all if from 0x8000 base - int tileoffs = tileindex * 16; - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - if (!_cgb) + using (_memory.EnterExit()) { - sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); - sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); - DrawTile((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal); - } - else - { - int tileext = mapbase[8192]; + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); - sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, tileext.Bit(3) ? 1 : 0)); - sb.AppendLine(string.Format(" Palette {0}", tileext & 7)); - sb.AppendLine(string.Format(" Flags {0}{1}{2}", tileext.Bit(5) ? 'H' : ' ', tileext.Bit(6) ? 'V' : ' ', tileext.Bit(7) ? 'P' : ' ')); - DrawTileHv((byte*)_vram + tileoffs + (tileext.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal + 4 * (tileext & 7), tileext.Bit(5), tileext.Bit(6)); + bmpViewDetails.ChangeBitmapSize(8, 8); + if (bmpViewDetails.Height != 64) + bmpViewDetails.Height = 64; + var sb = new StringBuilder(); + bool secondmap = win ? _lcdc.Bit(6) : _lcdc.Bit(3); + int mapoffs = secondmap ? 0x1c00 : 0x1800; + x /= 8; + y /= 8; + mapoffs += y * 32 + x; + byte* mapbase = (byte*)_vram + mapoffs; + int tileindex = mapbase[0]; + if (win || !_lcdc.Bit(4)) // 0x9000 base + if (tileindex < 128) + tileindex += 256; // compute all if from 0x8000 base + int tileoffs = tileindex * 16; + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + if (!_cgb) + { + sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); + sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); + DrawTile((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal); + } + else + { + int tileext = mapbase[8192]; + + sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); + sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, tileext.Bit(3) ? 1 : 0)); + sb.AppendLine(string.Format(" Palette {0}", tileext & 7)); + sb.AppendLine(string.Format(" Flags {0}{1}{2}", tileext.Bit(5) ? 'H' : ' ', tileext.Bit(6) ? 'V' : ' ', tileext.Bit(7) ? 'P' : ' ')); + DrawTileHv((byte*)_vram + tileoffs + (tileext.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal + 4 * (tileext & 7), tileext.Bit(5), tileext.Bit(6)); + } + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); } - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); } unsafe void SpriteMouseover(int x, int y) { - bool tall = _lcdc.Bit(2); - x /= 8; - y /= 8; - bmpViewDetails.ChangeBitmapSize(8, tall ? 16 : 8); - if (bmpViewDetails.Height != bmpViewDetails.BMP.Height * 8) - bmpViewDetails.Height = bmpViewDetails.BMP.Height * 8; - var sb = new StringBuilder(); + using (_memory.EnterExit()) + { + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - byte* oament = (byte*)_oam + 4 * x; - int sy = oament[0]; - int sx = oament[1]; - int tilenum = oament[2]; - int flags = oament[3]; - bool hflip = flags.Bit(5); - bool vflip = flags.Bit(6); - if (tall) - tilenum = vflip ? tilenum | 1 : tilenum & ~1; - int tileoffs = tilenum * 16; - sb.AppendLine(string.Format("Sprite #{0} @{1:x4}", x, 4 * x + 0xfe00)); - sb.AppendLine(string.Format(" (x,y) = ({0},{1})", sx, sy)); - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, tall ? 16 : 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - if (_cgb) - { - sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000, flags.Bit(3) ? 1 : 0)); - sb.AppendLine(string.Format(" Palette {0}", flags & 7)); - DrawTileHv((byte*)_vram + tileoffs + (flags.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + bool tall = _lcdc.Bit(2); + x /= 8; + y /= 8; + bmpViewDetails.ChangeBitmapSize(8, tall ? 16 : 8); + if (bmpViewDetails.Height != bmpViewDetails.BMP.Height * 8) + bmpViewDetails.Height = bmpViewDetails.BMP.Height * 8; + var sb = new StringBuilder(); + + byte* oament = (byte*)_oam + 4 * x; + int sy = oament[0]; + int sx = oament[1]; + int tilenum = oament[2]; + int flags = oament[3]; + bool hflip = flags.Bit(5); + bool vflip = flags.Bit(6); if (tall) - DrawTileHv((byte*)_vram + (tileoffs ^ 16) + (flags.Bit(3) ? 8192 : 0), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + tilenum = vflip ? tilenum | 1 : tilenum & ~1; + int tileoffs = tilenum * 16; + sb.AppendLine(string.Format("Sprite #{0} @{1:x4}", x, 4 * x + 0xfe00)); + sb.AppendLine(string.Format(" (x,y) = ({0},{1})", sx, sy)); + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, tall ? 16 : 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + if (_cgb) + { + sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000, flags.Bit(3) ? 1 : 0)); + sb.AppendLine(string.Format(" Palette {0}", flags & 7)); + DrawTileHv((byte*)_vram + tileoffs + (flags.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + if (tall) + DrawTileHv((byte*)_vram + (tileoffs ^ 16) + (flags.Bit(3) ? 8192 : 0), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + } + else + { + sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000)); + sb.AppendLine(string.Format(" Palette {0}", flags.Bit(4) ? 1 : 0)); + DrawTileHv((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + (flags.Bit(4) ? 4 : 0), hflip, vflip); + if (tall) + DrawTileHv((byte*)_vram + (tileoffs ^ 16), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags.Bit(4) ? 4 : 0), hflip, vflip); + } + sb.AppendLine(string.Format(" Flags {0}{1}{2}", hflip ? 'H' : ' ', vflip ? 'V' : ' ', flags.Bit(7) ? 'P' : ' ')); + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); } - else - { - sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000)); - sb.AppendLine(string.Format(" Palette {0}", flags.Bit(4) ? 1 : 0)); - DrawTileHv((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + (flags.Bit(4) ? 4 : 0), hflip, vflip); - if (tall) - DrawTileHv((byte*)_vram + (tileoffs ^ 16), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags.Bit(4) ? 4 : 0), hflip, vflip); - } - sb.AppendLine(string.Format(" Flags {0}{1}{2}", hflip ? 'H' : ' ', vflip ? 'V' : ' ', flags.Bit(7) ? 'P' : ' ')); - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); } private void bmpViewBG_MouseEnter(object sender, EventArgs e) @@ -882,9 +917,9 @@ namespace BizHawk.Client.EmuHawk else if (e.Button == MouseButtons.Left) { if (sender == bmpViewBGPal) - tilespal = _bgpal + e.X / 16 * 16; + tilespal = _memory.Bgpal + e.X / 16 * 16; else if (sender == bmpViewSPPal) - tilespal = _sppal + e.X / 16 * 16; + tilespal = _memory.Sppal + e.X / 16 * 16; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 9e04730e4a..864cdd856d 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -439,7 +439,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy #region ppudebug - public bool GetGPUMemoryAreas(out IntPtr vram, out IntPtr bgpal, out IntPtr sppal, out IntPtr oam) + public GPUMemoryAreas GetGPU() { IntPtr _vram = IntPtr.Zero; IntPtr _bgpal = IntPtr.Zero; @@ -451,23 +451,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy || !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.sppal, ref _sppal, ref unused) || !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.oam, ref _oam, ref unused)) { - vram = IntPtr.Zero; - bgpal = IntPtr.Zero; - sppal = IntPtr.Zero; - oam = IntPtr.Zero; - return false; + throw new InvalidOperationException("Unexpected error in gambatte_getmemoryarea"); } - vram = _vram; - bgpal = _bgpal; - sppal = _sppal; - oam = _oam; - return true; - } + return new GPUMemoryAreas(_vram, _oam, _sppal, _bgpal); - /// - /// - /// current value of register $ff40 (LCDC) - public delegate void ScanlineCallback(int lcdc); + } /// /// set up callback diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs index fa236b8f47..b874919ad7 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs @@ -1,4 +1,6 @@ -using System; +using BizHawk.Common; +using BizHawk.Emulation.Common; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,8 +8,49 @@ using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { - public interface IGameboyCommon + /// + /// + /// current value of register $ff40 (LCDC) + public delegate void ScanlineCallback(byte lcdc); + + public interface IGameboyCommon : ISpecializedEmulatorService { bool IsCGBMode(); + GPUMemoryAreas GetGPU(); + + /// + /// set up callback + /// + /// scanline. -1 = end of frame, -2 = RIGHT NOW + void SetScanlineCallback(ScanlineCallback callback, int line); + } + + public class GPUMemoryAreas : IMonitor + { + public IntPtr Vram { get; } + public IntPtr Oam { get; } + public IntPtr Sppal { get; } + public IntPtr Bgpal { get; } + + private readonly IMonitor _monitor; + + public GPUMemoryAreas(IntPtr vram, IntPtr oam, IntPtr sppal, IntPtr bgpal, IMonitor monitor = null) + { + Vram = vram; + Oam = oam; + Sppal = sppal; + Bgpal = bgpal; + _monitor = monitor; + } + + public void Enter() + { + _monitor?.Enter(); + } + + public void Exit() + { + _monitor?.Exit(); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs index 4801cd7d45..aba870b558 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs @@ -33,5 +33,14 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy [BizImport(CC)] public abstract bool Init(bool cgb, byte[] spc, int spclen); + + [BizImport(CC)] + public abstract void GetGpuMemory(IntPtr[] ptrs); + + [BizImport(CC)] + public abstract void SetScanlineCallback(ScanlineCallback callback, int ly); + + [BizImport(CC)] + public abstract byte GetIoReg(byte port); } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs index 52b966647b..90fecb3b99 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs @@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { [Core("Pizza Boy", "Davide Berra", true, true, "c7bc6ee376028b3766de8d7a02e60ab794841f45", "https://github.com/davideberra/emu-pizza/", false)] - public class Pizza : WaterboxCore, IGameboyCommon + public class Pizza : WaterboxCore { private LibPizza _pizza; private readonly bool _sgb; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index a82a4e2e21..aaa1a1a006 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -88,6 +88,10 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy _exe.RemoveReadonlyFile("boot.rom"); PostInit(); + + var scratch = new IntPtr[4]; + _core.GetGpuMemory(scratch); + _gpuMemory = new GPUMemoryAreas(scratch[0], scratch[1], scratch[3], scratch[2], _exe); } #region Controller @@ -152,6 +156,53 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy }; } + protected override void FrameAdvancePost() + { + if (_scanlineCallback != null && _scanlineCallbackLine == -1) + _scanlineCallback(_core.GetIoReg(0x40)); + } + + protected override void LoadStateBinaryInternal(BinaryReader reader) + { + UpdateCoreScanlineCallback(false); + } + public bool IsCGBMode() => _cgb; + + private GPUMemoryAreas _gpuMemory; + + public GPUMemoryAreas GetGPU() => _gpuMemory; + private ScanlineCallback _scanlineCallback; + private int _scanlineCallbackLine; + + public void SetScanlineCallback(ScanlineCallback callback, int line) + { + _scanlineCallback = callback; + _scanlineCallbackLine = line; + UpdateCoreScanlineCallback(true); + } + + private void UpdateCoreScanlineCallback(bool now) + { + if (_scanlineCallback == null) + { + _core.SetScanlineCallback(null, -1); + } + else + { + if (_scanlineCallbackLine >= 0 && _scanlineCallbackLine <= 153) + { + _core.SetScanlineCallback(_scanlineCallback, _scanlineCallbackLine); + } + else + { + _core.SetScanlineCallback(null, -1); + if (_scanlineCallbackLine == -2 && now) + { + _scanlineCallback(_core.GetIoReg(0x40)); + } + } + } + } } } diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp index 531e4f4eeb..15db12b9d6 100644 --- a/waterbox/sameboy/bizhawk.cpp +++ b/waterbox/sameboy/bizhawk.cpp @@ -207,6 +207,24 @@ ECL_EXPORT void SetInputCallback(void (*callback)()) GB_set_input_callback(&GB, callback ? InputCallback : nullptr); } +ECL_EXPORT void GetGpuMemory(void **p) +{ + p[0] = GB_get_direct_access(&GB, GB_DIRECT_ACCESS_VRAM, nullptr, nullptr); + p[1] = GB_get_direct_access(&GB, GB_DIRECT_ACCESS_OAM, nullptr, nullptr); + p[2] = GB.background_palettes_rgb; + p[3] = GB.sprite_palettes_rgb; +} + +ECL_EXPORT void SetScanlineCallback(void (*callback)(uint8_t), int ly) +{ + GB.scanline_callback = callback; + GB.scanline_callback_ly = ly; +} +ECL_EXPORT uint8_t GetIoReg(uint8_t port) +{ + return GB.io_registers[port]; +} + int main() { return 0; diff --git a/waterbox/sameboy/display.c b/waterbox/sameboy/display.c index d2ee911619..28dd5f07ec 100644 --- a/waterbox/sameboy/display.c +++ b/waterbox/sameboy/display.c @@ -296,7 +296,10 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) } bool should_compare_ly = true; uint8_t ly_for_comparison = gb->io_registers[GB_IO_LY] = gb->display_cycles / LINE_LENGTH; - + + if (gb->scanline_callback && gb->display_cycles % LINE_LENGTH == 0 && gb->scanline_callback_ly == ly_for_comparison) { + gb->scanline_callback(gb->io_registers[GB_IO_LCDC]); + } /* Handle cycle completion. STAT's initial value depends on model and mode */ if (gb->display_cycles == LCDC_PERIOD) { @@ -380,7 +383,7 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) /* 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 || diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h index 9b5f4613dd..91d29effe4 100644 --- a/waterbox/sameboy/gb.h +++ b/waterbox/sameboy/gb.h @@ -172,6 +172,7 @@ 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 void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t sample, uint64_t clock); +typedef void (*GB_scanline_callback_t)(uint8_t lcdc); typedef struct { bool state; @@ -400,7 +401,10 @@ struct GB_gameboy_internal_s { GB_serial_transfer_start_callback_t serial_transfer_start_callback; GB_serial_transfer_end_callback_t serial_transfer_end_callback; GB_sample_callback_t sample_callback; - + + GB_scanline_callback_t scanline_callback; + int scanline_callback_ly; + /* IR */ long cycles_since_ir_change; long cycles_since_input_ir_change; From 57ca86710b8796b54547f37920d978c4e16daf68 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Wed, 19 Jul 2017 19:49:23 -0400 Subject: [PATCH 10/13] sameboy: SAVERAMS --- .../Consoles/Nintendo/Gameboy/LibSameboy.cs | 9 ++ .../Consoles/Nintendo/Gameboy/Sameboy.cs | 22 +++- BizHawk.Emulation.Cores/Waterbox/PeRunner.cs | 114 ++++++++++++++++-- waterbox/sameboy/bizhawk.cpp | 25 +++- 4 files changed, 157 insertions(+), 13 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs index aba870b558..405635404c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs @@ -42,5 +42,14 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy [BizImport(CC)] public abstract byte GetIoReg(byte port); + + [BizImport(CC)] + public abstract void PutSaveRam(); + + [BizImport(CC)] + public abstract void GetSaveRam(); + + [BizImport(CC)] + public abstract bool HasSaveRam(); } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index aaa1a1a006..928ab1bd62 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { [Core("SameBoy", "LIJI32", true, false, "efc11783c7fb6da66e1dd084e41ba6a85c0bd17e", "https://sameboy.github.io/", false)] - public class Sameboy : WaterboxCore, IGameboyCommon + public class Sameboy : WaterboxCore, IGameboyCommon, ISaveRam { /// /// the nominal length of one frame @@ -147,6 +147,26 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy #endregion + #region ISaveram + + public new bool SaveRamModified => _core.HasSaveRam(); + + public new byte[] CloneSaveRam() + { + _exe.AddTransientFile(null, "save.ram"); + _core.GetSaveRam(); + return _exe.RemoveTransientFile("save.ram"); + } + + public new void StoreSaveRam(byte[] data) + { + _exe.AddReadonlyFile(data, "save.ram"); + _core.PutSaveRam(); + _exe.RemoveReadonlyFile("save.ram"); + } + + #endregion + protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound) { return new LibSameboy.FrameInfo diff --git a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs index 1db3950b0a..080b91e4b3 100644 --- a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs +++ b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs @@ -308,6 +308,68 @@ namespace BizHawk.Emulation.Cores.Waterbox } } + private class TransientFile : IFileObject + { + private bool _inUse = false; + public string Name { get; } + public Stream Stream { get; } + public bool Close() + { + if (_inUse) + { + _inUse = false; + return true; + } + else + { + return false; + } + } + + public bool Open(FileAccess access) + { + if (_inUse) + { + return false; + } + else + { + // TODO: if access != RW, the resultant handle lets you do those all anyway + _inUse = true; + Stream.Position = 0; + return true; + } + } + + public void LoadStateBinary(BinaryReader br) + { + throw new InvalidOperationException("Internal savestate error!"); + } + + public void SaveStateBinary(BinaryWriter bw) + { + throw new InvalidOperationException("Transient files cannot be savestated!"); + } + + public TransientFile(byte[] data, string name) + { + Stream = new MemoryStream(); + Name = name; + if (data != null) + { + Stream.Write(data, 0, data.Length); + Stream.Position = 0; + } + } + + public byte[] GetContents() + { + if (_inUse) + throw new InvalidOperationException(); + return ((MemoryStream)Stream).ToArray(); + } + } + private readonly List _openFiles = new List(); private readonly Dictionary _availableFiles = new Dictionary(); @@ -697,6 +759,21 @@ namespace BizHawk.Emulation.Cores.Waterbox } } + + private T RemoveFileInternal(string name) + where T : IFileObject + { + IFileObject o; + if (!_availableFiles.TryGetValue(name, out o)) + throw new InvalidOperationException("File was never registered!"); + if (o.GetType() != typeof(T)) + throw new InvalidOperationException("Object was not a the right kind of file"); + if (_openFiles.Contains(o)) + throw new InvalidOperationException("Core never closed the file!"); + _availableFiles.Remove(name); + return (T)o; + } + public void AddReadonlyFile(byte[] data, string name) { _availableFiles.Add(name, new ReadonlyFirmware(data, name)); @@ -704,15 +781,16 @@ namespace BizHawk.Emulation.Cores.Waterbox public void RemoveReadonlyFile(string name) { - IFileObject o; - if (!_availableFiles.TryGetValue(name, out o)) - throw new InvalidOperationException("Firmware was never registered!"); - var f = o as ReadonlyFirmware; - if (f == null) - throw new InvalidOperationException("Object was not a firmware!"); - if (_openFiles.Contains(o)) - throw new InvalidOperationException("Core never closed the firmware!"); - _availableFiles.Remove(name); + RemoveFileInternal(name); + } + + public void AddTransientFile(byte[] data, string name) + { + _availableFiles.Add(name, new TransientFile(data, name)); + } + public byte[] RemoveTransientFile(string name) + { + return RemoveFileInternal(name).GetContents(); } } @@ -1090,6 +1168,24 @@ namespace BizHawk.Emulation.Cores.Waterbox _syscalls.RemoveReadonlyFile(name); } + /// + /// Add a transient file that will appear to the waterbox core's libc. The file will be readable + /// and writable. Any attempt to save state while the file is loaded will fail. + /// + public void AddTransientFile(byte[] data, string name) + { + _syscalls.AddTransientFile(data, name); // don't need to clone data, as it's used at init only + } + + /// + /// Remove a file previously added by AddTransientFile + /// + /// The state of the file when it was removed + public byte[] RemoveTransientFile(string name) + { + return _syscalls.RemoveTransientFile(name); + } + public void SaveStateBinary(BinaryWriter bw) { bw.Write(_createstamp); diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp index 15db12b9d6..f37c60a0eb 100644 --- a/waterbox/sameboy/bizhawk.cpp +++ b/waterbox/sameboy/bizhawk.cpp @@ -15,7 +15,7 @@ extern "C" { static GB_gameboy_t GB; static uint32_t GBPixels[160 * 144]; -static uint32_t* CurrentFramebuffer; +static uint32_t *CurrentFramebuffer; static bool sgb; static void VBlankCallback(GB_gameboy_t *gb) { @@ -94,7 +94,7 @@ static void SgbSampleCallback(int16_t sl, int16_t sr, uint64_t clock) sample_sgb.right = sr; } -ECL_EXPORT bool Init(bool cgb, const uint8_t* spc, int spclen) +ECL_EXPORT bool Init(bool cgb, const uint8_t *spc, int spclen) { if (spc) { @@ -145,7 +145,7 @@ ECL_EXPORT void FrameAdvance(MyFrameInfo &f) { if (sgb) { - sgb_set_controller_data((uint8_t*)&f.Keys); + sgb_set_controller_data((uint8_t *)&f.Keys); } else { @@ -225,6 +225,25 @@ ECL_EXPORT uint8_t GetIoReg(uint8_t port) return GB.io_registers[port]; } +ECL_EXPORT void PutSaveRam() +{ + GB_load_battery(&GB, "save.ram"); +} + +ECL_EXPORT void GetSaveRam() +{ + GB_save_battery(&GB, "save.ram"); +} + +ECL_EXPORT bool HasSaveRam() +{ + if (!GB.cartridge_type->has_battery) + return false; // Nothing to save. + if (GB.mbc_ram_size == 0 && !GB.cartridge_type->has_rtc) + return false; /* Claims to have battery, but has no RAM or RTC */ + return true; +} + int main() { return 0; From a640c53e52eeec0b2f2542142ae4409457d4bf93 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Wed, 19 Jul 2017 20:18:13 -0400 Subject: [PATCH 11/13] Sameboy: RTC --- BizHawk.Client.EmuHawk/MainForm.Events.cs | 10 ++- .../Consoles/Nintendo/Gameboy/Sameboy.cs | 88 +++++++++++++++++-- waterbox/sameboy/bizhawk.cpp | 1 + waterbox/sameboy/gb.h | 2 + waterbox/sameboy/timing.c | 2 +- 5 files changed, 92 insertions(+), 11 deletions(-) diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 290b2429bf..21c72d1312 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -25,6 +25,7 @@ using BizHawk.Client.EmuHawk.ToolExtensions; using BizHawk.Emulation.Cores.Computers.AppleII; using BizHawk.Client.ApiHawk; using BizHawk.Emulation.Cores.Computers.Commodore64; +using BizHawk.Emulation.Cores.Nintendo.Gameboy; namespace BizHawk.Client.EmuHawk { @@ -2002,7 +2003,14 @@ namespace BizHawk.Client.EmuHawk private void GBCoreSettingsMenuItem_Click(object sender, EventArgs e) { - GBPrefs.DoGBPrefsDialog(this); + if (Global.Emulator is Gameboy) + { + GBPrefs.DoGBPrefsDialog(this); + } + else // sameboy + { + GenericCoreConfig.DoDialog(this, "Gameboy Settings"); + } } private void LoadGbInSgbMenuItem_Click(object sender, EventArgs e) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index 928ab1bd62..1b7231b02d 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -4,6 +4,7 @@ using BizHawk.Emulation.Cores.Properties; using BizHawk.Emulation.Cores.Waterbox; using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Text; @@ -13,7 +14,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { [Core("SameBoy", "LIJI32", true, false, "efc11783c7fb6da66e1dd084e41ba6a85c0bd17e", "https://sameboy.github.io/", false)] - public class Sameboy : WaterboxCore, IGameboyCommon, ISaveRam + public class Sameboy : WaterboxCore, + IGameboyCommon, ISaveRam, + ISettable { /// /// the nominal length of one frame @@ -35,16 +38,16 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy private bool _sgb; [CoreConstructor("SGB")] - public Sameboy(byte[] rom, CoreComm comm) - : this(rom, comm, true) + public Sameboy(byte[] rom, CoreComm comm, SyncSettings syncSettings, bool deterministic) + : this(rom, comm, true, syncSettings, deterministic) { } [CoreConstructor("GB")] - public Sameboy(CoreComm comm, byte[] rom) - : this(rom, comm, false) + public Sameboy(CoreComm comm, byte[] rom, SyncSettings syncSettings, bool deterministic) + : this(rom, comm, false, syncSettings, deterministic) { } - public Sameboy(byte[] rom, CoreComm comm, bool sgb) + public Sameboy(byte[] rom, CoreComm comm, bool sgb, SyncSettings syncSettings, bool deterministic) : base(comm, new Configuration { DefaultWidth = sgb ? 256 : 160, @@ -70,8 +73,12 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy _cgb = (rom[0x143] & 0xc0) == 0xc0 && !sgb; _sgb = sgb; Console.WriteLine("Automaticly detected CGB to " + _cgb); - var bios = Util.DecompressGzipFile(new MemoryStream(_cgb ? Resources.SameboyCgbBoot : Resources.SameboyDmgBoot)); - // var bios = comm.CoreFileProvider.GetFirmware(_cgb ? "GBC" : "GB", "World", true); + _syncSettings = syncSettings ?? new SyncSettings(); + + var bios = _syncSettings.UseRealBIOS && !sgb + ? comm.CoreFileProvider.GetFirmware(_cgb ? "GBC" : "GB", "World", true) + : Util.DecompressGzipFile(new MemoryStream(_cgb ? Resources.SameboyCgbBoot : Resources.SameboyDmgBoot)); + var spc = sgb ? Util.DecompressGzipFile(new MemoryStream(Resources.SgbCartPresent_SPC)) : null; @@ -92,6 +99,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy var scratch = new IntPtr[4]; _core.GetGpuMemory(scratch); _gpuMemory = new GPUMemoryAreas(scratch[0], scratch[1], scratch[3], scratch[2], _exe); + + DeterministicEmulation = deterministic || !_syncSettings.UseRealTime; + InitializeRtc(_syncSettings.InitialTime); } #region Controller @@ -167,11 +177,71 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy #endregion + #region ISettable + + private SyncSettings _syncSettings; + + public class SyncSettings + { + [DisplayName("Initial Time")] + [Description("Initial time of emulation. Only relevant when UseRealTime is false.")] + [DefaultValue(typeof(DateTime), "2010-01-01")] + public DateTime InitialTime { get; set; } + + [DisplayName("Use RealTime")] + [Description("If true, RTC clock will be based off of real time instead of emulated time. Ignored (set to false) when recording a movie.")] + [DefaultValue(false)] + public bool UseRealTime { get; set; } + + [Description("If true, real BIOS files will be used. Ignored in SGB mode.")] + [DefaultValue(false)] + public bool UseRealBIOS { get; set; } + + public SyncSettings Clone() + { + return (SyncSettings)MemberwiseClone(); + } + + public static bool NeedsReboot(SyncSettings x, SyncSettings y) + { + return !DeepEquality.DeepEquals(x, y); + } + + public SyncSettings() + { + SettingsUtil.SetDefaultValues(this); + } + } + + public object GetSettings() + { + return null; + } + + public SyncSettings GetSyncSettings() + { + return _syncSettings.Clone(); + } + + public bool PutSettings(object o) + { + return false; + } + + public bool PutSyncSettings(SyncSettings o) + { + var ret = SyncSettings.NeedsReboot(_syncSettings, o); + _syncSettings = o; + return ret; + } + + #endregion + protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound) { return new LibSameboy.FrameInfo { - Time = 0, + Time = GetRtcTime(!DeterministicEmulation), Keys = GetButtons(controller) }; } diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp index f37c60a0eb..28c6cda11e 100644 --- a/waterbox/sameboy/bizhawk.cpp +++ b/waterbox/sameboy/bizhawk.cpp @@ -154,6 +154,7 @@ ECL_EXPORT void FrameAdvance(MyFrameInfo &f) sound_start_clock = GB_epoch(&GB); CurrentFramebuffer = f.VideoBuffer; GB_set_lagged(&GB, true); + GB.frontend_rtc_time = f.Time; uint32_t target = 35112 - FrameOverflow; f.Cycles = GB_run_cycles(&GB, target); diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h index 91d29effe4..098a3d2ea3 100644 --- a/waterbox/sameboy/gb.h +++ b/waterbox/sameboy/gb.h @@ -434,6 +434,8 @@ struct GB_gameboy_internal_s { 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 + + int64_t frontend_rtc_time; }; #ifndef GB_INTERNAL diff --git a/waterbox/sameboy/timing.c b/waterbox/sameboy/timing.c index e67992c167..bb2501b31a 100644 --- a/waterbox/sameboy/timing.c +++ b/waterbox/sameboy/timing.c @@ -133,7 +133,7 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) void GB_rtc_run(GB_gameboy_t *gb) { if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ - time_t current_time = time(NULL); + time_t current_time = gb->frontend_rtc_time; while (gb->last_rtc_second < current_time) { gb->last_rtc_second++; if (++gb->rtc_real.seconds == 60) From 0d6bdc200b00413abe41fef0294aca1cb95f7ccf Mon Sep 17 00:00:00 2001 From: nattthebear Date: Wed, 19 Jul 2017 20:38:19 -0400 Subject: [PATCH 12/13] sameboy: a bit of savestate optimization --- .../Consoles/Nintendo/Gameboy/Sameboy.cs | 10 +++---- waterbox/sameboy/gb.c | 28 ++----------------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs index 1b7231b02d..c9d5269710 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -63,11 +63,11 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy _core = PreInit(new PeRunnerOptions { Filename = "sameboy.wbx", - SbrkHeapSizeKB = 128, - InvisibleHeapSizeKB = 16 * 1024, - SealedHeapSizeKB = 5 * 1024, - PlainHeapSizeKB = 4096, - MmapHeapSizeKB = 34 * 1024 + SbrkHeapSizeKB = 192, + InvisibleHeapSizeKB = 4, + SealedHeapSizeKB = 9 * 1024, + PlainHeapSizeKB = 4, + MmapHeapSizeKB = 1024 }); _cgb = (rom[0x143] & 0xc0) == 0xc0 && !sgb; diff --git a/waterbox/sameboy/gb.c b/waterbox/sameboy/gb.c index e8e90d55bd..ce14dc647c 100644 --- a/waterbox/sameboy/gb.c +++ b/waterbox/sameboy/gb.c @@ -5,11 +5,8 @@ #include #include #include -#ifndef _WIN32 -#include -#include -#endif #include "gb.h" +#include "../emulibc/emulibc.h" void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { @@ -75,27 +72,6 @@ void GB_init_cgb(GB_gameboy_t *gb) 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->breakpoints) { - free(gb->breakpoints); - } - memset(gb, 0, sizeof(*gb)); -} - int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) { FILE *f = fopen(path, "rb"); @@ -127,7 +103,7 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) if (gb->rom) { free(gb->rom); } - gb->rom = malloc(gb->rom_size); + gb->rom = alloc_sealed(gb->rom_size); memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ fread(gb->rom, gb->rom_size, 1, f); fclose(f); From 491dc9c1fbc9683d253f1a781e903cd5592fee90 Mon Sep 17 00:00:00 2001 From: nattthebear Date: Wed, 19 Jul 2017 20:43:44 -0400 Subject: [PATCH 13/13] sameboy: wbx file --- output/dll/sameboy.wbx.gz | Bin 0 -> 58573 bytes waterbox/sameboy/Makefile | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 output/dll/sameboy.wbx.gz diff --git a/output/dll/sameboy.wbx.gz b/output/dll/sameboy.wbx.gz new file mode 100644 index 0000000000000000000000000000000000000000..c810c4beead9c4e0539a4e65ccab05dafb5b6f32 GIT binary patch literal 58573 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 literal 0 HcmV?d00001 diff --git a/waterbox/sameboy/Makefile b/waterbox/sameboy/Makefile index 640a98f0d7..e694a321e1 100644 --- a/waterbox/sameboy/Makefile +++ b/waterbox/sameboy/Makefile @@ -3,8 +3,8 @@ 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 + -fomit-frame-pointer \ + -O3 -flto CCFLAGS:=$(FLAGS) \ -std=gnu99 \ @@ -42,8 +42,8 @@ $(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 $< $@ + 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)