flybrain/services/flysim/vendor/binjgb/src/emulator.c
acamilo 660c3cf00d
Some checks failed
ci / node 22 (test + typecheck) (push) Has been cancelled
ci / rust stable (cargo test --workspace --release) (push) Has been cancelled
ci / infra/tests/lint.sh (push) Has been cancelled
ci / playwright apps/stage (allowed to fail) (push) Has been cancelled
flybrain v0.4.0: public tree (history retained privately)
2026-09-21 15:09:46 +00:00

5353 lines
171 KiB
C

/*
* Copyright (C) 2016 Ben Smith
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#if RGBDS_LIVE
#include <emscripten.h>
#endif
#include "emulator.h"
#define MAX_CART_INFOS (MAXIMUM_ROM_SIZE / MINIMUM_ROM_SIZE)
#define VIDEO_RAM_SIZE KILOBYTES(16)
#define WORK_RAM_SIZE KILOBYTES(32)
#define EXT_RAM_MAX_SIZE KILOBYTES(128)
#define WAVE_RAM_SIZE 16
#define HIGH_RAM_SIZE 127
#define OBJ_PER_LINE_COUNT 10
/* Addresses are relative to IO_START_ADDR (0xff00). */
#define FOREACH_IO_REG(V) \
V(JOYP, 0x00) /* Joypad */ \
V(SB, 0x01) /* Serial transfer data */ \
V(SC, 0x02) /* Serial transfer control */ \
V(DIV, 0x04) /* Divider */ \
V(TIMA, 0x05) /* Timer counter */ \
V(TMA, 0x06) /* Timer modulo */ \
V(TAC, 0x07) /* Timer control */ \
V(IF, 0x0f) /* Interrupt request */ \
V(LCDC, 0x40) /* LCD control */ \
V(STAT, 0x41) /* LCD status */ \
V(SCY, 0x42) /* Screen Y */ \
V(SCX, 0x43) /* Screen X */ \
V(LY, 0x44) /* Y Line */ \
V(LYC, 0x45) /* Y Line compare */ \
V(DMA, 0x46) /* DMA transfer to OAM */ \
V(BGP, 0x47) /* BG palette */ \
V(OBP0, 0x48) /* OBJ palette 0 */ \
V(OBP1, 0x49) /* OBJ palette 1 */ \
V(WY, 0x4a) /* Window Y */ \
V(WX, 0x4b) /* Window X */ \
V(KEY1, 0x4d) /* Prepare speed switch X */ \
V(VBK, 0x4f) /* VRAM bank */ \
V(HDMA1, 0x51) /* HDMA 1 */ \
V(HDMA2, 0x52) /* HDMA 2 */ \
V(HDMA3, 0x53) /* HDMA 3 */ \
V(HDMA4, 0x54) /* HDMA 4 */ \
V(HDMA5, 0x55) /* HDMA 5 */ \
V(RP, 0x56) /* Infrared communications port */ \
V(BCPS, 0x68) /* Background palette index */ \
V(BCPD, 0x69) /* Background palette data */ \
V(OCPS, 0x6a) /* Obj palette index */ \
V(OCPD, 0x6b) /* Obj palette data */ \
V(SVBK, 0x70) /* WRAM bank */ \
V(IE, 0xff) /* Interrupt enable */
/* Addresses are relative to APU_START_ADDR (0xff10). */
#define FOREACH_APU_REG(V) \
V(NR10, 0x0) /* Channel 1 sweep */ \
V(NR11, 0x1) /* Channel 1 sound length/wave pattern */ \
V(NR12, 0x2) /* Channel 1 volume envelope */ \
V(NR13, 0x3) /* Channel 1 frequency lo */ \
V(NR14, 0x4) /* Channel 1 frequency hi */ \
V(NR21, 0x6) /* Channel 2 sound length/wave pattern */ \
V(NR22, 0x7) /* Channel 2 volume envelope */ \
V(NR23, 0x8) /* Channel 2 frequency lo */ \
V(NR24, 0x9) /* Channel 2 frequency hi */ \
V(NR30, 0xa) /* Channel 3 DAC enabled */ \
V(NR31, 0xb) /* Channel 3 sound length */ \
V(NR32, 0xc) /* Channel 3 select output level */ \
V(NR33, 0xd) /* Channel 3 frequency lo */ \
V(NR34, 0xe) /* Channel 3 frequency hi */ \
V(NR41, 0x10) /* Channel 4 sound length */ \
V(NR42, 0x11) /* Channel 4 volume envelope */ \
V(NR43, 0x12) /* Channel 4 polynomial counter */ \
V(NR44, 0x13) /* Channel 4 counter/consecutive; trigger */ \
V(NR50, 0x14) /* Sound volume */ \
V(NR51, 0x15) /* Sound output select */ \
V(NR52, 0x16) /* Sound enabled */
#define FOREACH_BOOL(V) \
V(FALSE, 0) \
V(TRUE, 1)
#define FOREACH_CGB_FLAG(V) \
V(CGB_FLAG_NONE, 0) \
V(CGB_FLAG_SUPPORTED, 0x80) \
V(CGB_FLAG_REQUIRED, 0xC0)
#define FOREACH_SGB_FLAG(V) \
V(SGB_FLAG_NONE, 0) \
V(SGB_FLAG_SUPPORTED, 3)
#define FOREACH_CART_TYPE(V) \
V(CART_TYPE_ROM_ONLY, 0x0, NO_MBC, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC1, 0x1, MBC1, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC1_RAM, 0x2, MBC1, WITH_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC1_RAM_BATTERY, 0x3, MBC1, WITH_RAM, WITH_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC2, 0x5, MBC2, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC2_BATTERY, 0x6, MBC2, NO_RAM, WITH_BATTERY, NO_TIMER) \
V(CART_TYPE_ROM_RAM, 0x8, NO_MBC, WITH_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_ROM_RAM_BATTERY, 0x9, NO_MBC, WITH_RAM, WITH_BATTERY, NO_TIMER) \
V(CART_TYPE_MMM01, 0xb, MMM01, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MMM01_RAM, 0xc, MMM01, WITH_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MMM01_RAM_BATTERY, 0xd, MMM01, WITH_RAM, WITH_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC3_TIMER_BATTERY, 0xf, MBC3, NO_RAM, WITH_BATTERY, WITH_TIMER) \
V(CART_TYPE_MBC3_TIMER_RAM_BATTERY, 0x10, MBC3, WITH_RAM, WITH_BATTERY, \
WITH_TIMER) \
V(CART_TYPE_MBC3, 0x11, MBC3, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC3_RAM, 0x12, MBC3, WITH_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC3_RAM_BATTERY, 0x13, MBC3, WITH_RAM, WITH_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC5, 0x19, MBC5, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC5_RAM, 0x1a, MBC5, WITH_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC5_RAM_BATTERY, 0x1b, MBC5, WITH_RAM, WITH_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC5_RUMBLE, 0x1c, MBC5, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC5_RUMBLE_RAM, 0x1d, MBC5, WITH_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_MBC5_RUMBLE_RAM_BATTERY, 0x1e, MBC5, WITH_RAM, WITH_BATTERY, \
NO_TIMER) \
V(CART_TYPE_POCKET_CAMERA, 0xfc, NO_MBC, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_BANDAI_TAMA5, 0xfd, TAMA5, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_HUC3, 0xfe, HUC3, NO_RAM, NO_BATTERY, NO_TIMER) \
V(CART_TYPE_HUC1_RAM_BATTERY, 0xff, HUC1, WITH_RAM, WITH_BATTERY, NO_TIMER)
#define FOREACH_ROM_SIZE(V) \
V(ROM_SIZE_32K, 0, 2) \
V(ROM_SIZE_64K, 1, 4) \
V(ROM_SIZE_128K, 2, 8) \
V(ROM_SIZE_256K, 3, 16) \
V(ROM_SIZE_512K, 4, 32) \
V(ROM_SIZE_1M, 5, 64) \
V(ROM_SIZE_2M, 6, 128) \
V(ROM_SIZE_4M, 7, 256) \
V(ROM_SIZE_8M, 8, 512)
#define FOREACH_EXT_RAM_SIZE(V) \
V(EXT_RAM_SIZE_NONE, 0, 0) \
V(EXT_RAM_SIZE_2K, 1, KILOBYTES(2)) \
V(EXT_RAM_SIZE_8K, 2, KILOBYTES(8)) \
V(EXT_RAM_SIZE_32K, 3, KILOBYTES(32)) \
V(EXT_RAM_SIZE_128K, 4, KILOBYTES(128)) \
V(EXT_RAM_SIZE_64K, 5, KILOBYTES(64))
#define FOREACH_PPU_MODE(V) \
V(PPU_MODE_HBLANK, 0) \
V(PPU_MODE_VBLANK, 1) \
V(PPU_MODE_MODE2, 2) \
V(PPU_MODE_MODE3, 3)
#define FOREACH_PPU_STATE(V) \
V(PPU_STATE_HBLANK, 0) \
V(PPU_STATE_HBLANK_PLUS_4, 1) \
V(PPU_STATE_VBLANK, 2) \
V(PPU_STATE_VBLANK_PLUS_4, 3) \
V(PPU_STATE_VBLANK_LY_0, 4) \
V(PPU_STATE_VBLANK_LY_0_PLUS_4, 5) \
V(PPU_STATE_VBLANK_LINE_Y_0, 6) \
V(PPU_STATE_LCD_ON_MODE2, 7) \
V(PPU_STATE_MODE2, 8) \
V(PPU_STATE_MODE3_EARLY_TRIGGER, 9) \
V(PPU_STATE_MODE3, 10) \
V(PPU_STATE_MODE3_COMMON, 11)
#define DEFINE_ENUM(name, code, ...) name = code,
#define DEFINE_IO_REG_ENUM(name, code, ...) IO_##name##_ADDR = code,
#define DEFINE_APU_REG_ENUM(name, code, ...) APU_##name##_ADDR = code,
#define DEFINE_STRING(name, code, ...) [code] = #name,
static inline const char* get_enum_string(const char** strings,
size_t string_count, size_t value) {
const char* result = value < string_count ? strings[value] : "unknown";
return result ? result : "unknown";
}
#define DEFINE_NAMED_ENUM(NAME, Name, name, foreach, enum_def) \
typedef enum { foreach (enum_def) NAME##_COUNT } Name; \
static inline Bool is_##name##_valid(Name value) { \
return value < NAME##_COUNT; \
} \
static inline const char* get_##name##_string(Name value) { \
static const char* s_strings[] = {foreach (DEFINE_STRING)}; \
return get_enum_string(s_strings, ARRAY_SIZE(s_strings), value); \
}
DEFINE_NAMED_ENUM(CGB_FLAG, CgbFlag, cgb_flag, FOREACH_CGB_FLAG, DEFINE_ENUM)
DEFINE_NAMED_ENUM(SGB_FLAG, SgbFlag, sgb_flag, FOREACH_SGB_FLAG, DEFINE_ENUM)
DEFINE_NAMED_ENUM(CART_TYPE, CartType, cart_type, FOREACH_CART_TYPE,
DEFINE_ENUM)
DEFINE_NAMED_ENUM(ROM_SIZE, RomSize, rom_size, FOREACH_ROM_SIZE, DEFINE_ENUM)
DEFINE_NAMED_ENUM(EXT_RAM_SIZE, ExtRamSize, ext_ram_size, FOREACH_EXT_RAM_SIZE,
DEFINE_ENUM)
DEFINE_NAMED_ENUM(IO_REG, IOReg, io_reg, FOREACH_IO_REG, DEFINE_IO_REG_ENUM)
DEFINE_NAMED_ENUM(APU_REG, APUReg, apu_reg, FOREACH_APU_REG,
DEFINE_APU_REG_ENUM)
DEFINE_NAMED_ENUM(PPU_MODE, PPUMode, ppu_mode, FOREACH_PPU_MODE, DEFINE_ENUM)
DEFINE_NAMED_ENUM(PPU_STATE, PPUState, ppu_state, FOREACH_PPU_STATE,
DEFINE_ENUM)
typedef enum {
MBC_TYPE_NO_MBC,
MBC_TYPE_MBC1,
MBC_TYPE_MBC2,
MBC_TYPE_MBC3,
MBC_TYPE_MBC5,
MBC_TYPE_MMM01,
MBC_TYPE_TAMA5,
MBC_TYPE_HUC3,
MBC_TYPE_HUC1,
} MbcType;
typedef enum {
EXT_RAM_TYPE_NO_RAM,
EXT_RAM_TYPE_WITH_RAM,
} ExtRamType;
typedef enum {
BATTERY_TYPE_NO_BATTERY,
BATTERY_TYPE_WITH_BATTERY,
} BatteryType;
typedef enum {
TIMER_TYPE_NO_TIMER,
TIMER_TYPE_WITH_TIMER,
} TimerType;
typedef struct {
MbcType mbc_type;
ExtRamType ext_ram_type;
BatteryType battery_type;
TimerType timer_type;
} CartTypeInfo;
typedef enum {
MEMORY_MAP_ROM0,
MEMORY_MAP_ROM1,
MEMORY_MAP_VRAM,
MEMORY_MAP_EXT_RAM,
MEMORY_MAP_WORK_RAM0,
MEMORY_MAP_WORK_RAM1,
MEMORY_MAP_OAM,
MEMORY_MAP_UNUSED,
MEMORY_MAP_IO,
MEMORY_MAP_APU,
MEMORY_MAP_WAVE_RAM,
MEMORY_MAP_HIGH_RAM,
} MemoryMapType;
typedef enum {
BANK_MODE_ROM = 0,
BANK_MODE_RAM = 1,
} BankMode;
typedef enum {
JOYPAD_SELECT_BOTH = 0,
JOYPAD_SELECT_BUTTONS = 1,
JOYPAD_SELECT_DPAD = 2,
JOYPAD_SELECT_NONE = 3,
JOYPAD_SGB_BOTH_LOW = 0,
JOYPAD_SGB_P15_LOW = 1,
JOYPAD_SGB_P14_LOW = 2,
JOYPAD_SGB_BOTH_HIGH = 3,
} JoypadSelect;
typedef enum {
TIMA_STATE_NORMAL,
TIMA_STATE_OVERFLOW,
TIMA_STATE_RESET,
} TimaState;
typedef enum {
SERIAL_CLOCK_EXTERNAL = 0,
SERIAL_CLOCK_INTERNAL = 1,
} SerialClock;
typedef enum {
DATA_READ_DISABLE = 0,
DATA_READ_ENABLE = 3,
} DataReadEnable;
enum {
SOUND1,
SOUND2,
SOUND3,
SOUND4,
VIN,
SOUND_COUNT,
};
typedef enum {
SWEEP_DIRECTION_ADDITION = 0,
SWEEP_DIRECTION_SUBTRACTION = 1,
} SweepDirection;
typedef enum {
ENVELOPE_ATTENUATE = 0,
ENVELOPE_AMPLIFY = 1,
} EnvelopeDirection;
typedef enum {
WAVE_DUTY_12_5 = 0,
WAVE_DUTY_25 = 1,
WAVE_DUTY_50 = 2,
WAVE_DUTY_75 = 3,
WAVE_DUTY_COUNT,
} WaveDuty;
typedef enum {
WAVE_VOLUME_MUTE = 0,
WAVE_VOLUME_100 = 1,
WAVE_VOLUME_50 = 2,
WAVE_VOLUME_25 = 3,
WAVE_VOLUME_COUNT,
} WaveVolume;
typedef enum {
LFSR_WIDTH_15 = 0, /* 15-bit LFSR */
LFSR_WIDTH_7 = 1, /* 7-bit LFSR */
} LfsrWidth;
typedef enum {
DMA_INACTIVE = 0,
DMA_TRIGGERED = 1,
DMA_ACTIVE = 2,
} DmaState;
typedef enum {
HDMA_TRANSFER_MODE_GDMA = 0,
HDMA_TRANSFER_MODE_HDMA = 1,
} HdmaTransferMode;
typedef enum {
SPEED_NORMAL = 0,
SPEED_DOUBLE = 1,
} Speed;
typedef enum {
SGB_MASK_CANCEL = 0,
SGB_MASK_FREEZE = 1,
SGB_MASK_BLACK = 2,
SGB_MASK_COLOR0 = 3,
} SgbMask;
typedef enum {
SGB_STATE_IDLE,
SGB_STATE_WAIT_BIT,
SGB_STATE_READ_BIT,
SGB_STATE_STOP_BIT,
SGB_STATE_STOP_WAIT,
} SgbState;
typedef struct {
u8 data[EXT_RAM_MAX_SIZE];
size_t size;
BatteryType battery_type;
} ExtRam;
typedef struct {
size_t offset; /* Offset of cart in FileData. */
u8* data; /* == FileData.data + offset */
size_t size;
CgbFlag cgb_flag;
SgbFlag sgb_flag;
CartType cart_type;
RomSize rom_size;
ExtRamSize ext_ram_size;
} CartInfo;
typedef struct {
u8 byte_2000_3fff;
u8 byte_4000_5fff;
BankMode bank_mode;
} Mbc1, Huc1, Mmm01;
typedef struct {
u8 sec, min, hour;
u16 day;
Bool day_carry;
Ticks latch_ticks;
u8 rtc_reg;
Bool rtc_halt;
Bool latched;
} Mbc3;
typedef struct {
u8 byte_2000_2fff;
u8 byte_3000_3fff;
} Mbc5;
typedef struct {
u8 (*read_ext_ram)(Emulator*, MaskedAddress);
void (*write_rom)(Emulator*, MaskedAddress, u8);
void (*write_ext_ram)(Emulator*, MaskedAddress, u8);
} MemoryMap;
typedef struct {
u32 rom_base[2];
u32 ext_ram_base;
Bool ext_ram_enabled;
union {
Mbc1 mbc1;
Mmm01 mmm01;
Mbc3 mbc3;
Huc1 huc1;
Mbc5 mbc5;
};
} MemoryMapState;
typedef struct {
MemoryMapType type;
MaskedAddress addr;
} MemoryTypeAddressPair;
typedef struct {
JoypadButtons buttons;
JoypadSelect joypad_select;
u8 last_p10_p13;
Ticks last_callback; /* The last time joypad callback was called. */
} Joypad;
typedef struct {
u8 chr_ram[8192];
u8 pal_ram[4096];
u8 attr_ram[4050];
u8 attr_map[90];
PaletteRGBA screen_pal[4];
RGBA border_pal[4][16];
SgbMask mask;
SgbState state;
u8 data[16 * 7];
u8 bits_read;
u8 current_packet;
u8 packet_count;
u8 current_player;
u8 player_mask;
Bool player_incremented;
} SGB;
typedef enum {
CPU_STATE_NORMAL = 0,
CPU_STATE_STOP = 1,
CPU_STATE_ENABLE_IME = 2,
CPU_STATE_HALT_BUG = 3,
CPU_STATE_HALT = 4,
CPU_STATE_HALT_DI = 5,
} CpuState;
typedef struct {
Bool ime; /* Interrupt Master Enable */
u8 ie; /* Interrupt Enable */
u8 if_; /* Interrupt Request, delayed by 1 tick for some IRQs. */
u8 new_if; /* The new value of IF, updated in 1 tick. */
CpuState state;
} Interrupt;
typedef struct {
Ticks sync_ticks; /* Current synchronization ticks. */
Ticks next_intr_ticks; /* Tick when the next timer intr will occur. */
TimerClock clock_select; /* Select the rate of TIMA */
TimaState tima_state; /* Used to implement TIMA overflow delay. */
u16 div_counter; /* Internal clock counter, upper 8 bits are DIV. */
u8 tima; /* Incremented at rate defined by clock_select */
u8 tma; /* When TIMA overflows, it is set to this value */
Bool on;
} Timer;
typedef struct {
Ticks sync_ticks; /* Current synchronization ticks. */
Ticks tick_count; /* 0..SERIAL_TICKS */
Ticks next_intr_ticks; /* Tick when the next intr will occur. */
SerialClock clock;
Bool transferring;
u8 sb; /* Serial transfer data. */
u8 transferred_bits;
} Serial;
typedef struct {
Bool write;
Bool read;
DataReadEnable enabled;
} Infrared;
typedef struct {
u8 period;
SweepDirection direction;
u8 shift;
u16 frequency;
u8 timer; /* 0..period */
Bool enabled;
Bool calculated_subtract;
} Sweep;
typedef struct {
u8 initial_volume;
EnvelopeDirection direction;
u8 period;
u8 volume; /* 0..15 */
u32 timer; /* 0..period */
Bool automatic; /* TRUE when MAX/MIN has not yet been reached. */
u8 zombie_step; /* HACK: support zombie volume decrease */
} Envelope;
/* Channel 1 and 2 */
typedef struct {
WaveDuty duty;
u8 sample; /* Last sample generated, 0..1 */
u32 period; /* Calculated from the frequency. */
u8 position; /* Position in the duty tick, 0..7 */
u32 ticks; /* 0..period */
} SquareWave;
/* Channel 3 */
typedef struct {
WaveVolume volume;
u8 volume_shift;
u8 ram[WAVE_RAM_SIZE];
Ticks sample_time; /* Time (in ticks) the sample was read. */
u8 sample_data; /* Last sample generated, 0..1 */
u32 period; /* Calculated from the frequency. */
u8 position; /* 0..31 */
u32 ticks; /* 0..period */
Bool playing; /* TRUE if the channel has been triggered but the DAC not
disabled. */
} Wave;
/* Channel 4 */
typedef struct {
u8 clock_shift;
LfsrWidth lfsr_width;
u8 divisor; /* 0..NOISE_DIVISOR_COUNT */
u8 sample; /* Last sample generated, 0..1 */
u16 lfsr; /* Linear feedback shift register, 15- or 7-bit. */
u32 period; /* Calculated from the clock_shift and divisor. */
u32 ticks; /* 0..period */
} Noise;
typedef struct {
SquareWave square_wave; /* Channel 1, 2 */
Envelope envelope; /* Channel 1, 2, 4 */
u16 frequency; /* Channel 1, 2, 3 */
u16 length; /* All channels */
Bool length_enabled; /* All channels */
Bool dac_enabled;
Bool status; /* Status bit for NR52 */
u32 accumulator; /* Accumulates samples for resampling. */
} Channel;
typedef struct {
u8 so_volume[SOUND_OUTPUT_COUNT];
Bool so_output[SOUND_COUNT][SOUND_OUTPUT_COUNT];
Bool enabled;
Sweep sweep;
Wave wave;
Noise noise;
Channel channel[APU_CHANNEL_COUNT];
u8 frame; /* 0..FRAME_SEQUENCER_COUNT */
Ticks sync_ticks; /* Raw tick counter */
Bool initialized;
} Apu;
typedef struct {
Bool display;
TileMapSelect window_tile_map_select;
Bool window_display;
TileDataSelect bg_tile_data_select;
TileMapSelect bg_tile_map_select;
ObjSize obj_size;
Bool obj_display;
Bool bg_display;
} Lcdc;
typedef struct {
Bool irq;
Bool trigger;
} StatInterrupt;
typedef struct {
StatInterrupt y_compare;
StatInterrupt mode2;
StatInterrupt vblank;
StatInterrupt hblank;
Bool ly_eq_lyc; /* TRUE if ly=lyc, delayed by 1 tick. */
PPUMode mode; /* The current PPU mode. */
Bool if_; /* Internal interrupt flag for STAT interrupts. */
PPUMode trigger_mode; /* This mode is used for checking STAT IRQ triggers. */
Bool new_ly_eq_lyc; /* The new value for ly_eq_lyc, updated in 1 tick. */
} Stat;
typedef struct {
PaletteRGBA palettes[8];
u8 data[64];
u8 index;
Bool auto_increment;
} ColorPalettes;
typedef struct {
Ticks sync_ticks; /* Current synchronization tick. */
Ticks next_intr_ticks; /* Tick when the next intr will occur. */
Lcdc lcdc; /* LCD control */
Stat stat; /* LCD status */
u8 scy; /* Screen Y */
u8 scx; /* Screen X */
u8 ly; /* Line Y */
u8 lyc; /* Line Y Compare */
u8 wy; /* Window Y */
u8 wx; /* Window X */
Palette pal[PALETTE_TYPE_COUNT]; /* BGP, OBP0, OBP1 Palettes */
ColorPalettes bgcp; /* BG Color Palettes */
ColorPalettes obcp; /* OBJ Color Palettes */
PPUState state;
Ticks mode3_render_ticks; /* Ticks at last mode3 synchronization. */
Ticks line_start_ticks; /* Ticks at the start of this line_y. */
u32 state_ticks;
u32 frame; /* The currently rendering frame. */
u8 last_ly; /* LY from the previous tick. */
u8 render_x; /* Currently rendering X coordinate. */
u8 line_y; /* The currently rendering line. Can be different than LY. */
u8 win_y; /* The window Y is only incremented when rendered. */
Obj line_obj[OBJ_PER_LINE_COUNT]; /* Cached from OAM during mode2. */
u8 line_obj_count; /* Number of sprites to draw on this line. */
Bool rendering_window; /* TRUE when this line is rendering the window. */
u8 display_delay_frames; /* Wait this many frames before displaying. */
} Ppu;
typedef struct {
Ticks sync_ticks; /* Current synchronization tick. */
Ticks tick_count; /* 0..DMA_TICKS */
DmaState state; /* Used to implement DMA delay. */
Address source; /* Source address; dest is calculated from this. */
} Dma;
typedef struct {
Speed speed;
Bool switching;
} CpuSpeed;
typedef struct {
u8 data[VIDEO_RAM_SIZE];
Address offset;
u8 bank;
} Vram;
typedef struct {
u8 data[WORK_RAM_SIZE];
Address offset;
u8 bank;
} Wram;
typedef struct {
DmaState state;
Address source;
Address dest;
HdmaTransferMode mode;
u8 blocks;
u8 block_bytes;
} Hdma;
typedef struct {
u32 header; /* Set to SAVE_STATE_HEADER; makes it easier to save state. */
u32 random_seed;
u8 cart_info_index;
MemoryMapState memory_map_state;
Registers reg;
Vram vram;
ExtRam ext_ram;
Wram wram;
Interrupt interrupt;
Obj oam[OBJ_COUNT];
Joypad joyp;
SGB sgb;
Serial serial;
Infrared infrared;
Timer timer;
Apu apu;
Ppu ppu;
Dma dma;
Hdma hdma;
CpuSpeed cpu_speed;
u8 hram[HIGH_RAM_SIZE];
Ticks ticks;
Ticks cpu_tick;
Ticks next_intr_ticks; /* For Timer, Serial, or PPU interrupts. */
Bool is_cgb;
Bool is_sgb;
Bool ext_ram_updated;
EmulatorEvent event;
} EmulatorState;
const size_t s_emulator_state_size = sizeof(EmulatorState);
#ifdef RGBDS_LIVE
#ifndef BREAKPOINTS_MAX_BANKS_NUMBER
#define BREAKPOINTS_MAX_BANKS_NUMBER 1
#endif
typedef uint32_t breakpoints_type;
#define MEMORY_SIZE (64 * 1024)
#define BREAKPOINTS_BIT_SIZE (sizeof(breakpoints_type) * 8)
#define BREAKPOINTS_SIZE ((BREAKPOINTS_MAX_BANKS_NUMBER * MEMORY_SIZE) / BREAKPOINTS_BIT_SIZE)
#define BREAKPOINTS_SHIFT (__builtin_ctz(BREAKPOINTS_BIT_SIZE))
#define BREAKPOINTS_MASK (BREAKPOINTS_BIT_SIZE - 1)
#define BREAKPOINTS_BANK_SHIFT (16 - BREAKPOINTS_SHIFT)
#endif
struct Emulator {
EmulatorConfig config;
FileData file_data;
CartInfo cart_infos[MAX_CART_INFOS];
u32 cart_info_count;
CartInfo* cart_info; /* Cached for convenience. */
MemoryMap memory_map;
EmulatorState state;
FrameBuffer frame_buffer;
SgbFrameBuffer sgb_frame_buffer;
AudioBuffer audio_buffer;
JoypadCallbackInfo joypad_info;
/* color_to_rgba stores mappings from 4 DMG colors to RGBA colors. pal is a
* cached copy of the current DMG palette (e.g. could be all COLOR_WHITE). */
PaletteRGBA color_to_rgba[PALETTE_TYPE_COUNT];
PaletteRGBA pal[PALETTE_TYPE_COUNT];
PaletteRGBA sgb_pal[4];
CgbColorCurve cgb_color_curve;
ApuLog apu_log;
#ifdef RGBDS_LIVE
breakpoints_type breakpoint[BREAKPOINTS_SIZE] __attribute__((aligned(8)));
#endif
};
/* Abbreviations of commonly accessed values. */
#define APU (e->state.apu)
#define CHANNEL1 CHANNEL(1)
#define CHANNEL2 CHANNEL(2)
#define CHANNEL3 CHANNEL(3)
#define CHANNEL4 CHANNEL(4)
#define CHANNEL(i) (APU.channel[APU_CHANNEL##i])
#define CPU_SPEED (e->state.cpu_speed)
#define TICKS (e->state.ticks)
#define DMA (e->state.dma)
#define EXT_RAM (e->state.ext_ram)
#define HRAM (e->state.hram)
#define HDMA (e->state.hdma)
#define INFRARED (e->state.infrared)
#define INTR (e->state.interrupt)
#define IS_CGB (e->state.is_cgb)
#define IS_SGB (e->state.is_sgb)
#define JOYP (e->state.joyp)
#define SGB (e->state.sgb)
#define LCDC (PPU.lcdc)
#define MMAP_STATE (e->state.memory_map_state)
#define NOISE (APU.noise)
#define OAM (e->state.oam)
#define PPU (e->state.ppu)
#define REG (e->state.reg)
#define SERIAL (e->state.serial)
#define STAT (PPU.stat)
#define SWEEP (APU.sweep)
#define TIMER (e->state.timer)
#define VRAM (e->state.vram)
#define WAVE (APU.wave)
#define WRAM (e->state.wram)
#define DIV_CEIL(numer, denom) (((numer) + (denom) - 1) / (denom))
#define VALUE_WRAPPED(X, MAX) \
(UNLIKELY((X) >= (MAX) ? ((X) -= (MAX), TRUE) : FALSE))
#define SAVE_STATE_VERSION (2)
#define SAVE_STATE_HEADER (u32)(0x6b57a7e0 + SAVE_STATE_VERSION)
#ifndef HOOK0
#define HOOK0(name)
#endif
#ifndef HOOK
#define HOOK(name, ...)
#endif
#ifndef HOOK0_FALSE
#define HOOK0_FALSE(name) FALSE
#endif
/* ROM header stuff */
#define LOGO_START_ADDR 0x104
#define LOGO_END_ADDR 0x133
#define TITLE_START_ADDR 0x134
#define TITLE_MAX_LENGTH 0x10
#define CGB_FLAG_ADDR 0x143
#define SGB_FLAG_ADDR 0x146
#define CART_TYPE_ADDR 0x147
#define ROM_SIZE_ADDR 0x148
#define EXT_RAM_SIZE_ADDR 0x149
#define HEADER_CHECKSUM_ADDR 0x14d
#define GLOBAL_CHECKSUM_START_ADDR 0x14e
#define HEADER_CHECKSUM_RANGE_START 0x134
#define HEADER_CHECKSUM_RANGE_END 0x14c
/* Memory map */
#define ADDR_MASK_4K 0x0fff
#define ADDR_MASK_8K 0x1fff
#define ADDR_MASK_16K 0x3fff
#define MBC_RAM_ENABLED_MASK 0xf
#define MBC_RAM_ENABLED_VALUE 0xa
#define MBC1_ROM_BANK_LO_SELECT_MASK 0x1f
#define MBC1_BANK_HI_SELECT_MASK 0x3
#define MBC1_BANK_HI_SHIFT 5
#define MBC1M_ROM_BANK_LO_SELECT_MASK 0xf
#define MBC1M_BANK_HI_SHIFT 4
/* MBC2 has built-in RAM, 512 4-bit values. It's not external, but it maps to
* the same address space. */
#define MBC2_RAM_SIZE 0x200
#define MBC2_RAM_ADDR_MASK 0x1ff
#define MBC2_RAM_VALUE_MASK 0xf
#define MBC2_ADDR_SELECT_BIT_MASK 0x100
#define MBC2_ROM_BANK_SELECT_MASK 0xf
#define MBC3_ROM_BANK_SELECT_MASK 0x7f
#define MBC3_RAM_BANK_SELECT_MASK 0x7
#define MBC5_RAM_BANK_SELECT_MASK 0xf
#define HUC1_ROM_BANK_LO_SELECT_MASK 0x3f
#define HUC1_BANK_HI_SELECT_MASK 0x3
#define HUC1_BANK_HI_SHIFT 6
#define OAM_START_ADDR 0xfe00
#define OAM_END_ADDR 0xfe9f
#define IO_START_ADDR 0xff00
#define APU_START_ADDR 0xff10
#define WAVE_RAM_START_ADDR 0xff30
#define HIGH_RAM_START_ADDR 0xff80
#define IE_ADDR 0xffff
#define OAM_TRANSFER_SIZE (OAM_END_ADDR - OAM_START_ADDR + 1)
#define CART_INFO_SHIFT 15
#define ROM_BANK_SHIFT 14
#define EXT_RAM_BANK_SHIFT 13
/* Tick counts */
#define CPU_TICK 4
#define CPU_2X_TICK 2
#define APU_TICKS 2
#define PPU_ENABLE_DISPLAY_DELAY_FRAMES 4
#define PPU_MODE2_TICKS 80
#define PPU_MODE3_MIN_TICKS 172
#define DMA_TICKS 648
#define DMA_DELAY_TICKS 8
#define SERIAL_TICKS (CPU_TICKS_PER_SECOND / 8192)
#define JOYP_INTERRUPT_WAIT_TICKS 10000 /* Arbitrary. */
/* Video */
#define TILE_WIDTH 8
#define TILE_HEIGHT 8
#define TILE_ROW_BYTES 2
#define TILE_MAP_WIDTH 32
#define WINDOW_MAX_X 166
#define WINDOW_X_OFFSET 7
/* Audio */
#define NRX1_MAX_LENGTH 64
#define NR31_MAX_LENGTH 256
#define SWEEP_MAX_PERIOD 8
#define SOUND_MAX_FREQUENCY 2047
#define WAVE_SAMPLE_COUNT 32
#define NOISE_MAX_CLOCK_SHIFT 13
#define NOISE_DIVISOR_COUNT 8
#define ENVELOPE_MAX_PERIOD 8
#define ENVELOPE_MAX_VOLUME 15
#define DUTY_CYCLE_COUNT 8
#define SOUND_OUTPUT_MAX_VOLUME 7
/* Additional samples so the AudioBuffer doesn't overflow. This could happen
* because the audio buffer is updated at the granularity of an instruction, so
* the most extra frames that could be added is equal to the Apu tick count
* of the slowest instruction. */
#define AUDIO_BUFFER_EXTRA_FRAMES 256
#define WAVE_TRIGGER_CORRUPTION_OFFSET_TICKS APU_TICKS
#define WAVE_TRIGGER_DELAY_TICKS (3 * APU_TICKS)
#define FRAME_SEQUENCER_COUNT 8
#define FRAME_SEQUENCER_TICKS 8192 /* 512Hz */
#define FRAME_SEQUENCER_UPDATE_ENVELOPE_FRAME 7
#define INVALID_READ_BYTE 0xff
#define GET_LO(HI, LO) (LO)
#define GET_BITMASK(HI, LO) ((1 << ((HI) - (LO) + 1)) - 1)
#define UNPACK(X, BITS) (((X) >> BITS(GET_LO)) & BITS(GET_BITMASK))
#define PACK(X, BITS) (((X) & BITS(GET_BITMASK)) << BITS(GET_LO))
#define BITS(X, HI, LO) X(HI, LO)
#define BIT(X, B) X(B, B)
#define CPU_FLAG_Z(X) BIT(X, 7)
#define CPU_FLAG_N(X) BIT(X, 6)
#define CPU_FLAG_H(X) BIT(X, 5)
#define CPU_FLAG_C(X) BIT(X, 4)
#define JOYP_UNUSED 0xc0
#define JOYP_RESULT_MASK 0x0f
#define JOYP_JOYPAD_SELECT(X) BITS(X, 5, 4)
#define JOYP_DPAD_DOWN(X) BIT(X, 3)
#define JOYP_DPAD_UP(X) BIT(X, 2)
#define JOYP_DPAD_LEFT(X) BIT(X, 1)
#define JOYP_DPAD_RIGHT(X) BIT(X, 0)
#define JOYP_BUTTON_START(X) BIT(X, 3)
#define JOYP_BUTTON_SELECT(X) BIT(X, 2)
#define JOYP_BUTTON_B(X) BIT(X, 1)
#define JOYP_BUTTON_A(X) BIT(X, 0)
#define SC_UNUSED 0x7e
#define SC_TRANSFER_START(X) BIT(X, 7)
#define SC_SHIFT_CLOCK(X) BIT(X, 0)
#define TAC_UNUSED 0xf8
#define TAC_TIMER_ON(X) BIT(X, 2)
#define TAC_CLOCK_SELECT(X) BITS(X, 1, 0)
#define IF_UNUSED 0xe0
#define IF_ALL 0x1f
#define IF_JOYPAD 0x10
#define IF_SERIAL 0x08
#define IF_TIMER 0x04
#define IF_STAT 0x02
#define IF_VBLANK 0x01
#define LCDC_DISPLAY(X) BIT(X, 7)
#define LCDC_WINDOW_TILE_MAP_SELECT(X) BIT(X, 6)
#define LCDC_WINDOW_DISPLAY(X) BIT(X, 5)
#define LCDC_BG_TILE_DATA_SELECT(X) BIT(X, 4)
#define LCDC_BG_TILE_MAP_SELECT(X) BIT(X, 3)
#define LCDC_OBJ_SIZE(X) BIT(X, 2)
#define LCDC_OBJ_DISPLAY(X) BIT(X, 1)
#define LCDC_BG_DISPLAY(X) BIT(X, 0)
#define STAT_UNUSED 0x80
#define STAT_YCOMPARE_INTR(X) BIT(X, 6)
#define STAT_MODE2_INTR(X) BIT(X, 5)
#define STAT_VBLANK_INTR(X) BIT(X, 4)
#define STAT_HBLANK_INTR(X) BIT(X, 3)
#define STAT_YCOMPARE(X) BIT(X, 2)
#define STAT_MODE(X) BITS(X, 1, 0)
#define PALETTE_COLOR3(X) BITS(X, 7, 6)
#define PALETTE_COLOR2(X) BITS(X, 5, 4)
#define PALETTE_COLOR1(X) BITS(X, 3, 2)
#define PALETTE_COLOR0(X) BITS(X, 1, 0)
#define NR10_UNUSED 0x80
#define NR10_SWEEP_PERIOD(X) BITS(X, 6, 4)
#define NR10_SWEEP_DIRECTION(X) BIT(X, 3)
#define NR10_SWEEP_SHIFT(X) BITS(X, 2, 0)
#define NRX1_UNUSED 0x3f
#define NRX1_WAVE_DUTY(X) BITS(X, 7, 6)
#define NRX1_LENGTH(X) BITS(X, 5, 0)
#define NRX2_INITIAL_VOLUME(X) BITS(X, 7, 4)
#define NRX2_DAC_ENABLED(X) BITS(X, 7, 3)
#define NRX2_ENVELOPE_DIRECTION(X) BIT(X, 3)
#define NRX2_ENVELOPE_PERIOD(X) BITS(X, 2, 0)
#define NRX4_UNUSED 0xbf
#define NRX4_INITIAL(X) BIT(X, 7)
#define NRX4_LENGTH_ENABLED(X) BIT(X, 6)
#define NRX4_FREQUENCY_HI(X) BITS(X, 2, 0)
#define NR30_UNUSED 0x7f
#define NR30_DAC_ENABLED(X) BIT(X, 7)
#define NR32_UNUSED 0x9f
#define NR32_SELECT_WAVE_VOLUME(X) BITS(X, 6, 5)
#define NR43_CLOCK_SHIFT(X) BITS(X, 7, 4)
#define NR43_LFSR_WIDTH(X) BIT(X, 3)
#define NR43_DIVISOR(X) BITS(X, 2, 0)
#define NR50_VIN_SO2(X) BIT(X, 7)
#define NR50_SO2_VOLUME(X) BITS(X, 6, 4)
#define NR50_VIN_SO1(X) BIT(X, 3)
#define NR50_SO1_VOLUME(X) BITS(X, 2, 0)
#define NR51_SOUND4_SO2(X) BIT(X, 7)
#define NR51_SOUND3_SO2(X) BIT(X, 6)
#define NR51_SOUND2_SO2(X) BIT(X, 5)
#define NR51_SOUND1_SO2(X) BIT(X, 4)
#define NR51_SOUND4_SO1(X) BIT(X, 3)
#define NR51_SOUND3_SO1(X) BIT(X, 2)
#define NR51_SOUND2_SO1(X) BIT(X, 1)
#define NR51_SOUND1_SO1(X) BIT(X, 0)
#define NR52_UNUSED 0x70
#define NR52_ALL_SOUND_ENABLED(X) BIT(X, 7)
#define NR52_SOUND4_ON(X) BIT(X, 3)
#define NR52_SOUND3_ON(X) BIT(X, 2)
#define NR52_SOUND2_ON(X) BIT(X, 1)
#define NR52_SOUND1_ON(X) BIT(X, 0)
#define KEY1_UNUSED 0x7e
#define KEY1_CURRENT_SPEED(X) BIT(X, 7)
#define KEY1_PREPARE_SPEED_SWITCH(X) BIT(X, 0)
#define RP_UNUSED 0x3c
#define RP_DATA_READ_ENABLE(X) BITS(X, 7, 6)
#define RP_READ_DATA(X) BIT(X, 1)
#define RP_WRITE_DATA(X) BIT(X, 0)
#define VBK_UNUSED 0xfe
#define VBK_VRAM_BANK(X) BIT(X, 0)
#define HDMA5_TRANSFER_MODE(X) BIT(X, 7)
#define HDMA5_BLOCKS(X) BITS(X, 6, 0)
#define XCPS_UNUSED 0x40
#define XCPS_AUTO_INCREMENT(X) BIT(X, 7)
#define XCPS_INDEX(X) BITS(X, 5, 0)
#define XCPD_BLUE_INTENSITY(X) BITS(X, 14, 10)
#define XCPD_GREEN_INTENSITY(X) BITS(X, 9, 5)
#define XCPD_RED_INTENSITY(X) BITS(X, 4, 0)
#define SVBK_UNUSED 0xf8
#define SVBK_WRAM_BANK(X) BITS(X, 2, 0)
#define OBJ_PRIORITY(X) BIT(X, 7)
#define OBJ_YFLIP(X) BIT(X, 6)
#define OBJ_XFLIP(X) BIT(X, 5)
#define OBJ_PALETTE(X) BIT(X, 4)
#define OBJ_BANK(X) BIT(X, 3)
#define OBJ_CGB_PALETTE(X) BITS(X, 2, 0)
#define MBC3_RTC_DAY_CARRY(X) BIT(X, 7)
#define MBC3_RTC_HALT(X) BIT(X, 6)
#define MBC3_RTC_DAY_HI(X) BIT(X, 0)
static u32 s_rom_bank_count[] = {
#define V(name, code, bank_count) [code] = bank_count,
FOREACH_ROM_SIZE(V)
#undef V
};
#define ROM_BANK_COUNT(e) s_rom_bank_count[(e)->cart_info->rom_size]
#define ROM_BANK_MASK(e) (ROM_BANK_COUNT(e) - 1)
static u32 s_ext_ram_byte_size[] = {
#define V(name, code, byte_size) [code] = byte_size,
FOREACH_EXT_RAM_SIZE(V)
#undef V
};
#define EXT_RAM_BYTE_SIZE(e) s_ext_ram_byte_size[(e)->cart_info->ext_ram_size]
#define EXT_RAM_BYTE_SIZE_MASK(e) (EXT_RAM_BYTE_SIZE(e) - 1)
static CartTypeInfo s_cart_type_info[] = {
#define V(name, code, mbc, ram, battery, timer) \
[code] = {MBC_TYPE_##mbc, EXT_RAM_TYPE_##ram, BATTERY_TYPE_##battery, \
TIMER_TYPE_##timer},
FOREACH_CART_TYPE(V)
#undef V
};
/* TIMA is incremented when the given bit of DIV_counter changes from 1 to 0. */
static const u16 s_tima_mask[] = {1 << 9, 1 << 3, 1 << 5, 1 << 7};
static u8 s_wave_volume_shift[WAVE_VOLUME_COUNT] = {4, 0, 1, 2};
static u8 s_obj_size_to_height[] = {[OBJ_SIZE_8X8] = 8, [OBJ_SIZE_8X16] = 16};
static Result init_memory_map(Emulator*);
static void apu_synchronize(Emulator*);
static void dma_synchronize(Emulator*);
static void intr_synchronize(Emulator*);
static void ppu_synchronize(Emulator*);
static void ppu_mode3_synchronize(Emulator*);
static void serial_synchronize(Emulator*);
static void timer_synchronize(Emulator*);
static void calculate_next_ppu_intr(Emulator*);
static void calculate_next_serial_intr(Emulator*);
static MemoryTypeAddressPair make_pair(MemoryMapType type, Address addr) {
MemoryTypeAddressPair result;
result.type = type;
result.addr = addr;
return result;
}
static MemoryTypeAddressPair map_address(Address addr) {
switch (addr >> 12) {
case 0x0: case 0x1: case 0x2: case 0x3:
return make_pair(MEMORY_MAP_ROM0, addr & ADDR_MASK_16K);
case 0x4: case 0x5: case 0x6: case 0x7:
return make_pair(MEMORY_MAP_ROM1, addr & ADDR_MASK_16K);
case 0x8: case 0x9:
return make_pair(MEMORY_MAP_VRAM, addr & ADDR_MASK_8K);
case 0xA: case 0xB:
return make_pair(MEMORY_MAP_EXT_RAM, addr & ADDR_MASK_8K);
case 0xC: case 0xE: /* mirror of 0xc000..0xcfff */
return make_pair(MEMORY_MAP_WORK_RAM0, addr & ADDR_MASK_4K);
case 0xD:
return make_pair(MEMORY_MAP_WORK_RAM1, addr & ADDR_MASK_4K);
default: case 0xF:
switch ((addr >> 8) & 0xf) {
default: /* 0xf000 - 0xfdff: mirror of 0xd000-0xddff */
return make_pair(MEMORY_MAP_WORK_RAM1, addr & ADDR_MASK_4K);
case 0xe:
if (addr <= OAM_END_ADDR) { /* 0xfe00 - 0xfe9f */
return make_pair(MEMORY_MAP_OAM, addr - OAM_START_ADDR);
} else { /* 0xfea0 - 0xfeff */
return make_pair(MEMORY_MAP_UNUSED, addr);
}
break;
case 0xf:
switch ((addr >> 4) & 0xf) {
case 0: case 4: case 5: case 6: case 7:
/* 0xff00 - 0xff0f, 0xff40 - 0xff7f */
return make_pair(MEMORY_MAP_IO, addr - IO_START_ADDR);
case 1: case 2: /* 0xff10 - 0xff2f */
return make_pair(MEMORY_MAP_APU, addr - APU_START_ADDR);
case 3: /* 0xff30 - 0xff3f */
return make_pair(MEMORY_MAP_WAVE_RAM, addr - WAVE_RAM_START_ADDR);
case 0xf:
if (addr == IE_ADDR) {
return make_pair(MEMORY_MAP_IO, addr - IO_START_ADDR);
}
/* fallthrough */
default: /* 0xff80 - 0xfffe */
return make_pair(MEMORY_MAP_HIGH_RAM, addr - HIGH_RAM_START_ADDR);
}
}
}
}
static MemoryTypeAddressPair map_hdma_source_address(Address addr) {
switch (addr >> 12) {
case 0x0: case 0x1: case 0x2: case 0x3:
return make_pair(MEMORY_MAP_ROM0, addr & ADDR_MASK_16K);
case 0x4: case 0x5: case 0x6: case 0x7:
return make_pair(MEMORY_MAP_ROM1, addr & ADDR_MASK_16K);
case 0x8: case 0x9:
return make_pair(MEMORY_MAP_VRAM, addr & ADDR_MASK_8K);
default: case 0xA: case 0xB: case 0xE: case 0xF:
return make_pair(MEMORY_MAP_EXT_RAM, addr & ADDR_MASK_8K);
case 0xC:
return make_pair(MEMORY_MAP_WORK_RAM0, addr & ADDR_MASK_4K);
case 0xD:
return make_pair(MEMORY_MAP_WORK_RAM1, addr & ADDR_MASK_4K);
}
}
static void set_cart_info(Emulator* e, u8 index) {
e->state.cart_info_index = index;
e->cart_info = &e->cart_infos[index];
if (!(e->cart_info->data && SUCCESS(init_memory_map(e)))) {
UNREACHABLE("Unable to switch cart (%d).\n", index);
}
}
static Result get_cart_info(FileData* file_data, size_t offset,
CartInfo* cart_info, Bool require_logo_checksum,
size_t* max_file_size) {
/* Simple checksum on logo data so we don't have to include it here. :) */
u8* data = file_data->data + offset;
size_t i;
u32 logo_checksum = 0;
for (i = LOGO_START_ADDR; i <= LOGO_END_ADDR; ++i) {
logo_checksum = (logo_checksum << 1) ^ data[i];
}
#if RGBDS_LIVE
if (offset == 0) { require_logo_checksum = FALSE; }
#endif
CHECK(!require_logo_checksum || logo_checksum == 0xe06c8834);
cart_info->offset = offset;
cart_info->data = data;
cart_info->rom_size = data[ROM_SIZE_ADDR];
/* HACK(binji): The mooneye-gb multicart test doesn't set any of the header
* bits, even though multicart games all seem to. Just force the values in
* reasonable defaults in that case. */
if (!is_rom_size_valid(cart_info->rom_size)) {
cart_info->rom_size = ROM_SIZE_32K;
cart_info->cgb_flag = CGB_FLAG_NONE;
cart_info->sgb_flag = SGB_FLAG_NONE;
cart_info->cart_type = CART_TYPE_MBC1;
cart_info->ext_ram_size = EXT_RAM_SIZE_NONE;
} else {
CHECK_MSG(is_rom_size_valid(cart_info->rom_size),
"Invalid ROM size code: %u\n", cart_info->rom_size);
cart_info->cgb_flag = data[CGB_FLAG_ADDR];
cart_info->sgb_flag = data[SGB_FLAG_ADDR];
cart_info->cart_type = data[CART_TYPE_ADDR];
CHECK_MSG(is_cart_type_valid(cart_info->cart_type),
"Invalid cart type: %u\n", cart_info->cart_type);
cart_info->ext_ram_size = data[EXT_RAM_SIZE_ADDR];
CHECK_MSG(is_ext_ram_size_valid(cart_info->ext_ram_size),
"Invalid ext ram size: %u\n", cart_info->ext_ram_size);
}
u32 rom_byte_size = s_rom_bank_count[cart_info->rom_size] << ROM_BANK_SHIFT;
*max_file_size = MAX(*max_file_size, offset + rom_byte_size);
cart_info->size = *max_file_size;
return OK;
ON_ERROR_RETURN;
}
static Result get_cart_infos(Emulator* e) {
size_t file_size = e->file_data.size;
size_t max_file_size = file_size;
u32 i;
for (i = 0; i < MAX_CART_INFOS; ++i) {
size_t offset = i << CART_INFO_SHIFT;
if (offset + MINIMUM_ROM_SIZE > e->file_data.size) break;
if (SUCCESS(get_cart_info(&e->file_data, offset, &e->cart_infos[i], TRUE,
&max_file_size))) {
if (s_cart_type_info[e->cart_infos[i].cart_type].mbc_type ==
MBC_TYPE_MMM01) {
/* MMM01 has the cart header at the end. */
goto done;
}
e->cart_info_count++;
}
}
// Maybe the logo checksum failed; try again without it required.
if (e->cart_info_count == 0 &&
SUCCESS(get_cart_info(&e->file_data, 0, &e->cart_infos[0], FALSE,
&max_file_size))) {
e->cart_info_count++;
}
CHECK_MSG(e->cart_info_count != 0, "Invalid ROM.\n");
i = 0;
done:
if (max_file_size > file_size) {
file_data_resize(&e->file_data, max_file_size);
// Fix cart_info data pointers.
for (u32 j = 0; j < e->cart_info_count; ++j) {
e->cart_infos[j].data = e->file_data.data + e->cart_infos[j].offset;
}
}
set_cart_info(e, i);
return OK;
ON_ERROR_RETURN;
}
static void dummy_write(Emulator* e, MaskedAddress addr, u8 value) {}
static u8 dummy_read(Emulator* e, MaskedAddress addr) {
return INVALID_READ_BYTE;
}
static void set_rom_bank(Emulator* e, int index, u16 bank) {
u32 new_base = (bank & ROM_BANK_MASK(e)) << ROM_BANK_SHIFT;
u32* base = &MMAP_STATE.rom_base[index];
if (new_base != *base) {
HOOK(set_rom_bank_ihi, index, bank, new_base);
}
*base = new_base;
}
static void set_ext_ram_bank(Emulator* e, u8 bank) {
u32 new_base = (bank << EXT_RAM_BANK_SHIFT) & EXT_RAM_BYTE_SIZE_MASK(e);
u32* base = &MMAP_STATE.ext_ram_base;
if (new_base != *base) {
HOOK(set_ext_ram_bank_bi, bank, new_base);
}
*base = new_base;
}
static u8 gb_read_ext_ram(Emulator* e, MaskedAddress addr) {
if (MMAP_STATE.ext_ram_enabled) {
assert(addr <= ADDR_MASK_8K);
return EXT_RAM.data[MMAP_STATE.ext_ram_base | addr];
} else {
HOOK(read_ram_disabled_a, addr);
return INVALID_READ_BYTE;
}
}
static void gb_write_ext_ram(Emulator* e, MaskedAddress addr, u8 value) {
if (MMAP_STATE.ext_ram_enabled) {
assert(addr <= ADDR_MASK_8K);
EXT_RAM.data[MMAP_STATE.ext_ram_base | addr] = value;
e->state.ext_ram_updated = TRUE;
} else {
HOOK(write_ram_disabled_ab, addr, value);
}
}
static void mbc1_write_rom_shared(Emulator* e, u16 bank_lo_mask,
int bank_hi_shift, MaskedAddress addr,
u8 value) {
Mbc1* mbc1 = &MMAP_STATE.mbc1;
switch (addr >> 13) {
case 0: /* 0000-1fff */
MMAP_STATE.ext_ram_enabled =
(value & MBC_RAM_ENABLED_MASK) == MBC_RAM_ENABLED_VALUE;
break;
case 1: /* 2000-3fff */
mbc1->byte_2000_3fff = value & MBC1_ROM_BANK_LO_SELECT_MASK;
break;
case 2: /* 4000-5fff */
mbc1->byte_4000_5fff = value & MBC1_BANK_HI_SELECT_MASK;
break;
case 3: /* 6000-7fff */
mbc1->bank_mode = (BankMode)(value & 1);
break;
}
u16 hi_bank = mbc1->byte_4000_5fff << bank_hi_shift;
u16 rom1_bank = mbc1->byte_2000_3fff;
if (rom1_bank == 0) {
rom1_bank++;
}
rom1_bank = (rom1_bank & bank_lo_mask) | hi_bank;
u16 rom0_bank = 0;
u8 ext_ram_bank = 0;
if (mbc1->bank_mode == BANK_MODE_RAM) {
rom0_bank |= hi_bank;
ext_ram_bank = mbc1->byte_4000_5fff;
}
set_rom_bank(e, 0, rom0_bank);
set_rom_bank(e, 1, rom1_bank);
set_ext_ram_bank(e, ext_ram_bank);
}
static void mbc1_write_rom(Emulator* e, MaskedAddress addr, u8 value) {
mbc1_write_rom_shared(e, MBC1_ROM_BANK_LO_SELECT_MASK, MBC1_BANK_HI_SHIFT,
addr, value);
}
static void mbc1m_write_rom(Emulator* e, MaskedAddress addr, u8 value) {
mbc1_write_rom_shared(e, MBC1M_ROM_BANK_LO_SELECT_MASK, MBC1M_BANK_HI_SHIFT,
addr, value);
}
static void mbc2_write_rom(Emulator* e, MaskedAddress addr, u8 value) {
if (addr < 0x4000) {
if ((addr & MBC2_ADDR_SELECT_BIT_MASK) != 0) {
u16 rom1_bank = value & MBC2_ROM_BANK_SELECT_MASK & ROM_BANK_MASK(e);
if (rom1_bank == 0) {
rom1_bank++;
}
set_rom_bank(e, 1, rom1_bank);
} else {
MMAP_STATE.ext_ram_enabled =
(value & MBC_RAM_ENABLED_MASK) == MBC_RAM_ENABLED_VALUE;
}
}
}
static u8 mbc2_read_ram(Emulator* e, MaskedAddress addr) {
if (MMAP_STATE.ext_ram_enabled) {
return EXT_RAM.data[addr & MBC2_RAM_ADDR_MASK];
} else {
HOOK(read_ram_disabled_a, addr);
return INVALID_READ_BYTE;
}
}
static void mbc2_write_ram(Emulator* e, MaskedAddress addr, u8 value) {
if (MMAP_STATE.ext_ram_enabled) {
EXT_RAM.data[addr & MBC2_RAM_ADDR_MASK] = value & MBC2_RAM_VALUE_MASK;
} else {
HOOK(write_ram_disabled_ab, addr, value);
}
}
static void mbc3_write_rom(Emulator* e, MaskedAddress addr, u8 value) {
switch (addr >> 13) {
case 0: /* 0000-1fff */
MMAP_STATE.ext_ram_enabled =
(value & MBC_RAM_ENABLED_MASK) == MBC_RAM_ENABLED_VALUE;
break;
case 1: { /* 2000-3fff */
u16 rom1_bank = value & MBC3_ROM_BANK_SELECT_MASK & ROM_BANK_MASK(e);
if (rom1_bank == 0) {
rom1_bank++;
}
set_rom_bank(e, 1, rom1_bank);
break;
}
case 2: /* 4000-5fff */
MMAP_STATE.mbc3.rtc_reg = value;
if (value < 8) {
set_ext_ram_bank(e, value & MBC3_RAM_BANK_SELECT_MASK);
}
break;
case 3: { /* 6000-7fff */
Mbc3* mbc3 = &MMAP_STATE.mbc3;
Bool was_latched = mbc3->latched;
Bool latched = value == 1;
if (!was_latched && latched && !mbc3->rtc_halt) {
// Update the clock by how much time has passed since it was last
// latched.
Ticks delta = TICKS - mbc3->latch_ticks;
// RTC ticks every second, so don't update unless at least a second
// has passed.
if (delta >= CPU_TICKS_PER_SECOND) {
u32 ms, sec, min, hour, day;
emulator_ticks_to_time(delta, &day, &hour, &min, &sec, &ms);
Bool secovf = FALSE;
if (mbc3->sec >= 60) {
mbc3->sec += sec;
if (mbc3->sec >= 64) {
mbc3->sec -= 64;
if (mbc3->sec >= 60) { mbc3->sec -= 60; ++min; secovf = TRUE; }
}
} else {
mbc3->sec += sec;
if (mbc3->sec >= 60) { mbc3->sec -= 60; ++min; secovf = TRUE; }
}
Bool minovf = FALSE;
if (min > 0 || secovf) {
if (mbc3->min >= 60) {
mbc3->min += min;
if (mbc3->min >= 64) {
mbc3->min -= 64;
if (mbc3->min >= 60) { mbc3->min -= 60; ++hour; minovf = TRUE; }
}
} else {
mbc3->min += min;
if (mbc3->min >= 60) { mbc3->min -= 60; ++hour; minovf = TRUE; }
}
}
Bool hourovf = FALSE;
if (hour > 0 || minovf) {
if (mbc3->hour >= 24) {
mbc3->hour += hour;
if (mbc3->hour >= 32) {
mbc3->hour -= 32;
if (mbc3->hour >= 24) { mbc3->hour -= 24; ++day; hourovf = TRUE; }
}
} else {
mbc3->hour += hour;
if (mbc3->hour >= 24) { mbc3->hour -= 24; ++day; hourovf = TRUE; }
}
}
if (day > 0 || hourovf) {
mbc3->day += day;
if (mbc3->day >= 512) {
mbc3->day_carry = TRUE;
}
}
mbc3->latch_ticks = TICKS;
}
}
mbc3->latched = latched;
break;
}
default:
break;
}
}
static u8 mbc3_read_ext_ram(Emulator* e, MaskedAddress addr) {
if (!MMAP_STATE.ext_ram_enabled) {
return INVALID_READ_BYTE;
}
Mbc3* mbc3 = &MMAP_STATE.mbc3;
if (mbc3->rtc_reg <= 3) {
return gb_read_ext_ram(e, addr);
}
if (!mbc3->latched) {
return INVALID_READ_BYTE;
}
u8 result = INVALID_READ_BYTE;
switch (mbc3->rtc_reg) {
case 8: result = mbc3->sec; break;
case 9: result = mbc3->min; break;
case 10: result = mbc3->hour; break;
case 11: result = mbc3->day; break;
case 12:
result = PACK(mbc3->day_carry, MBC3_RTC_DAY_CARRY) |
PACK(mbc3->rtc_halt, MBC3_RTC_HALT) |
PACK((mbc3->day >> 8) & 1, MBC3_RTC_DAY_HI);
break;
}
return result;
}
static void mbc3_write_ext_ram(Emulator* e, MaskedAddress addr, u8 value) {
if (!MMAP_STATE.ext_ram_enabled) {
return;
}
Mbc3* mbc3 = &MMAP_STATE.mbc3;
if (mbc3->rtc_reg <= 3) {
gb_write_ext_ram(e, addr, value);
return;
}
if (!mbc3->latched) {
return;
}
switch (mbc3->rtc_reg) {
case 8:
mbc3->sec = value & 63;
/* Reset the tick timer. Note that if the RTC timer is halted then
* latch_ticks is a previously stored delta, not an absolute tick timer.
* Once the timer is restarted then latch_ticks is an absolute timer
* again. */
mbc3->latch_ticks = mbc3->rtc_halt ? 0 : TICKS;
break;
case 9: mbc3->min = value & 63; break;
case 10: mbc3->hour = value & 31; break;
case 11: mbc3->day = (mbc3->day & 0x100) | value; break;
case 12: {
mbc3->day = (UNPACK(value, MBC3_RTC_DAY_HI) << 8) | (mbc3->day & 0xff);
mbc3->day_carry = UNPACK(value, MBC3_RTC_DAY_CARRY);
Bool old_rtc_halt = mbc3->rtc_halt;
mbc3->rtc_halt = UNPACK(value, MBC3_RTC_HALT);
if (mbc3->rtc_halt != old_rtc_halt) {
// Update the tick timer; if the clock is halted, then store the
// previous delta before the clock was stopped. If the clock is
// restarted, then subtract that delta from the current tick timer to
// "add" in the delta that is not yet accounted for in the RTC
// registers.
mbc3->latch_ticks = TICKS - mbc3->latch_ticks;
}
break;
}
default:
break;
}
}
static void mbc5_write_rom(Emulator* e, MaskedAddress addr, u8 value) {
switch (addr >> 12) {
case 0: case 1: /* 0000-1fff */
MMAP_STATE.ext_ram_enabled =
(value & MBC_RAM_ENABLED_MASK) == MBC_RAM_ENABLED_VALUE;
break;
case 2: /* 2000-2fff */
MMAP_STATE.mbc5.byte_2000_2fff = value;
break;
case 3: /* 3000-3fff */
MMAP_STATE.mbc5.byte_3000_3fff = value;
break;
case 4: case 5: /* 4000-5fff */
set_ext_ram_bank(e, value & MBC5_RAM_BANK_SELECT_MASK);
break;
default:
break;
}
set_rom_bank(e, 1,
((MMAP_STATE.mbc5.byte_3000_3fff & 1) << 8) |
MMAP_STATE.mbc5.byte_2000_2fff);
}
static void huc1_write_rom(Emulator* e, MaskedAddress addr, u8 value) {
Huc1* huc1 = &MMAP_STATE.huc1;
switch (addr >> 13) {
case 0: /* 0000-1fff */
MMAP_STATE.ext_ram_enabled =
(value & MBC_RAM_ENABLED_MASK) == MBC_RAM_ENABLED_VALUE;
break;
case 1: /* 2000-3fff */
huc1->byte_2000_3fff = value;
break;
case 2: /* 4000-5fff */
huc1->byte_4000_5fff = value;
break;
case 3: /* 6000-7fff */
huc1->bank_mode = (BankMode)(value & 1);
break;
}
u16 rom1_bank = huc1->byte_2000_3fff & HUC1_ROM_BANK_LO_SELECT_MASK;
if (rom1_bank == 0) {
rom1_bank++;
}
u8 ext_ram_bank;
if (huc1->bank_mode == BANK_MODE_ROM) {
rom1_bank |= (huc1->byte_4000_5fff & HUC1_BANK_HI_SELECT_MASK)
<< HUC1_BANK_HI_SHIFT;
ext_ram_bank = 0;
} else {
ext_ram_bank = huc1->byte_4000_5fff & HUC1_BANK_HI_SELECT_MASK;
}
set_rom_bank(e, 1, rom1_bank);
set_ext_ram_bank(e, ext_ram_bank);
}
static void mmm01_write_rom(Emulator* e, MaskedAddress addr, u8 value) {
Mmm01* mmm01 = &MMAP_STATE.mmm01;
switch (addr >> 13) {
case 0: { /* 0000-1fff */
/* ROM size should be power-of-two. */
assert((e->cart_info->size & (e->cart_info->size - 1)) == 0);
u32 rom_offset =
(mmm01->byte_2000_3fff << ROM_BANK_SHIFT) & (e->cart_info->size - 1);
set_cart_info(e, rom_offset >> CART_INFO_SHIFT);
break;
}
case 1: /* 2000-3fff */
mmm01->byte_2000_3fff = value;
break;
}
}
static Result init_memory_map(Emulator* e) {
CartTypeInfo* cart_type_info = &s_cart_type_info[e->cart_info->cart_type];
MemoryMap* memory_map = &e->memory_map;
switch (cart_type_info->ext_ram_type) {
case EXT_RAM_TYPE_WITH_RAM:
assert(is_ext_ram_size_valid(e->cart_info->ext_ram_size));
memory_map->read_ext_ram = gb_read_ext_ram;
memory_map->write_ext_ram = gb_write_ext_ram;
EXT_RAM.size = EXT_RAM_BYTE_SIZE(e);
break;
default:
case EXT_RAM_TYPE_NO_RAM:
memory_map->read_ext_ram = dummy_read;
memory_map->write_ext_ram = dummy_write;
EXT_RAM.size = 0;
break;
}
switch (cart_type_info->mbc_type) {
case MBC_TYPE_NO_MBC:
memory_map->write_rom = dummy_write;
break;
case MBC_TYPE_MBC1: {
Bool is_mbc1m = e->cart_info_count > 1;
memory_map->write_rom = is_mbc1m ? mbc1m_write_rom : mbc1_write_rom;
break;
}
case MBC_TYPE_MBC2:
memory_map->write_rom = mbc2_write_rom;
memory_map->read_ext_ram = mbc2_read_ram;
memory_map->write_ext_ram = mbc2_write_ram;
EXT_RAM.size = MBC2_RAM_SIZE;
break;
case MBC_TYPE_MMM01:
memory_map->write_rom = mmm01_write_rom;
break;
case MBC_TYPE_MBC3: {
memory_map->write_rom = mbc3_write_rom;
if (cart_type_info->timer_type == TIMER_TYPE_WITH_TIMER) {
memory_map->read_ext_ram = mbc3_read_ext_ram;
memory_map->write_ext_ram = mbc3_write_ext_ram;
}
break;
}
case MBC_TYPE_MBC5:
memory_map->write_rom = mbc5_write_rom;
MMAP_STATE.mbc5.byte_2000_2fff = 1;
break;
case MBC_TYPE_HUC1:
memory_map->write_rom = huc1_write_rom;
break;
default:
PRINT_ERROR("memory map for %s not implemented.\n",
get_cart_type_string(e->cart_info->cart_type));
return ERROR;
}
EXT_RAM.battery_type = cart_type_info->battery_type;
return OK;
}
static Bool is_almost_mode3(Emulator* e) {
return PPU.state_ticks == CPU_TICK && STAT.mode == PPU_MODE_MODE2;
}
static Bool is_using_vram(Emulator* e, Bool write) {
if (write) {
return STAT.mode == PPU_MODE_MODE3;
} else {
return STAT.mode == PPU_MODE_MODE3 || is_almost_mode3(e);
}
}
static Bool is_using_oam(Emulator* e, Bool write) {
if (write) {
return (STAT.mode == PPU_MODE_MODE2 && !is_almost_mode3(e)) ||
STAT.mode == PPU_MODE_MODE3;
} else {
return STAT.mode2.trigger || STAT.mode == PPU_MODE_MODE2 ||
STAT.mode == PPU_MODE_MODE3;
}
}
static u8 read_vram(Emulator* e, MaskedAddress addr) {
ppu_synchronize(e);
if (is_using_vram(e, FALSE)) {
HOOK(read_vram_in_use_a, addr);
return INVALID_READ_BYTE;
} else {
assert(addr <= ADDR_MASK_8K);
return VRAM.data[VRAM.offset + addr];
}
}
static u8 read_oam(Emulator* e, MaskedAddress addr) {
ppu_synchronize(e);
if (is_using_oam(e, FALSE)) {
HOOK(read_oam_in_use_a, addr);
return INVALID_READ_BYTE;
}
u8 obj_index = addr >> 2;
Obj* obj = &OAM[obj_index];
switch (addr & 3) {
case 0: return obj->y + OBJ_Y_OFFSET;
case 1: return obj->x + OBJ_X_OFFSET;
case 2: return obj->tile;
case 3: return obj->byte3;
}
UNREACHABLE("invalid OAM address: 0x%04x\n", addr);
}
static u8 read_joyp_p10_p13(Emulator* e) {
if (JOYP.joypad_select == JOYPAD_SELECT_NONE) {
return ~(SGB.current_player & 3);
}
if (SGB.current_player != 0) { return ~0; } // Ignore other controllers.
u8 result = 0;
if (JOYP.joypad_select == JOYPAD_SELECT_BUTTONS ||
JOYP.joypad_select == JOYPAD_SELECT_BOTH) {
result |= PACK(JOYP.buttons.start, JOYP_BUTTON_START) |
PACK(JOYP.buttons.select, JOYP_BUTTON_SELECT) |
PACK(JOYP.buttons.B, JOYP_BUTTON_B) |
PACK(JOYP.buttons.A, JOYP_BUTTON_A);
}
Bool left = JOYP.buttons.left;
Bool right = JOYP.buttons.right;
Bool up = JOYP.buttons.up;
Bool down = JOYP.buttons.down;
if (!e->config.allow_simulataneous_dpad_opposites) {
if (left && right) {
left = FALSE;
} else if (up && down) {
up = FALSE;
}
}
if (JOYP.joypad_select == JOYPAD_SELECT_DPAD ||
JOYP.joypad_select == JOYPAD_SELECT_BOTH) {
result |= PACK(down, JOYP_DPAD_DOWN) | PACK(up, JOYP_DPAD_UP) |
PACK(left, JOYP_DPAD_LEFT) | PACK(right, JOYP_DPAD_RIGHT);
}
/* The bits are low when the buttons are pressed. */
return ~result;
}
static void call_joyp_callback(Emulator* e, Bool wait) {
if (e->joypad_info.callback &&
(!wait || TICKS - JOYP.last_callback >= JOYP_INTERRUPT_WAIT_TICKS)) {
e->joypad_info.callback(&JOYP.buttons, e->joypad_info.user_data);
JOYP.last_callback = TICKS;
}
}
static u8 read_io(Emulator* e, MaskedAddress addr) {
switch (addr) {
case IO_JOYP_ADDR:
call_joyp_callback(e, FALSE);
return JOYP_UNUSED | PACK(JOYP.joypad_select, JOYP_JOYPAD_SELECT) |
(read_joyp_p10_p13(e) & JOYP_RESULT_MASK);
case IO_SB_ADDR:
serial_synchronize(e);
return SERIAL.sb;
case IO_SC_ADDR:
serial_synchronize(e);
return SC_UNUSED | PACK(SERIAL.transferring, SC_TRANSFER_START) |
PACK(SERIAL.clock, SC_SHIFT_CLOCK);
case IO_DIV_ADDR:
timer_synchronize(e);
return TIMER.div_counter >> 8;
case IO_TIMA_ADDR:
timer_synchronize(e);
return TIMER.tima;
case IO_TMA_ADDR:
timer_synchronize(e);
return TIMER.tma;
case IO_TAC_ADDR:
return TAC_UNUSED | PACK(TIMER.on, TAC_TIMER_ON) |
PACK(TIMER.clock_select, TAC_CLOCK_SELECT);
case IO_IF_ADDR:
intr_synchronize(e);
return IF_UNUSED | INTR.if_;
case IO_LCDC_ADDR:
return PACK(LCDC.display, LCDC_DISPLAY) |
PACK(LCDC.window_tile_map_select,
LCDC_WINDOW_TILE_MAP_SELECT) |
PACK(LCDC.window_display, LCDC_WINDOW_DISPLAY) |
PACK(LCDC.bg_tile_data_select, LCDC_BG_TILE_DATA_SELECT) |
PACK(LCDC.bg_tile_map_select, LCDC_BG_TILE_MAP_SELECT) |
PACK(LCDC.obj_size, LCDC_OBJ_SIZE) |
PACK(LCDC.obj_display, LCDC_OBJ_DISPLAY) |
PACK(LCDC.bg_display, LCDC_BG_DISPLAY);
case IO_STAT_ADDR:
ppu_synchronize(e);
return STAT_UNUSED | PACK(STAT.y_compare.irq, STAT_YCOMPARE_INTR) |
PACK(STAT.mode2.irq, STAT_MODE2_INTR) |
PACK(STAT.vblank.irq, STAT_VBLANK_INTR) |
PACK(STAT.hblank.irq, STAT_HBLANK_INTR) |
PACK(STAT.ly_eq_lyc, STAT_YCOMPARE) |
PACK(STAT.mode, STAT_MODE);
case IO_SCY_ADDR:
return PPU.scy;
case IO_SCX_ADDR:
return PPU.scx;
case IO_LY_ADDR:
ppu_synchronize(e);
return PPU.ly;
case IO_LYC_ADDR:
return PPU.lyc;
case IO_DMA_ADDR:
return INVALID_READ_BYTE; /* Write only. */
case IO_BGP_ADDR:
case IO_OBP0_ADDR:
case IO_OBP1_ADDR: {
Palette* pal = &PPU.pal[addr - IO_BGP_ADDR];
return PACK(pal->color[3], PALETTE_COLOR3) |
PACK(pal->color[2], PALETTE_COLOR2) |
PACK(pal->color[1], PALETTE_COLOR1) |
PACK(pal->color[0], PALETTE_COLOR0);
}
case IO_WY_ADDR:
return PPU.wy;
case IO_WX_ADDR:
return PPU.wx;
case IO_KEY1_ADDR:
return IS_CGB ? (KEY1_UNUSED | PACK(CPU_SPEED.speed, KEY1_CURRENT_SPEED) |
PACK(CPU_SPEED.switching, KEY1_PREPARE_SPEED_SWITCH))
: INVALID_READ_BYTE;
case IO_VBK_ADDR:
return IS_CGB ? (VBK_UNUSED | PACK(VRAM.bank, VBK_VRAM_BANK))
: INVALID_READ_BYTE;
case IO_HDMA5_ADDR:
return IS_CGB ? HDMA.blocks : INVALID_READ_BYTE;
case IO_RP_ADDR:
return IS_CGB ? (RP_UNUSED | PACK(INFRARED.enabled, RP_DATA_READ_ENABLE) |
PACK(INFRARED.read, RP_READ_DATA) |
PACK(INFRARED.write, RP_WRITE_DATA))
: INVALID_READ_BYTE;
case IO_BCPS_ADDR:
case IO_OCPS_ADDR:
if (IS_CGB) {
ColorPalettes* cp = addr == IO_BCPS_ADDR ? &PPU.bgcp : &PPU.obcp;
return XCPS_UNUSED | PACK(cp->index, XCPS_INDEX) |
PACK(cp->auto_increment, XCPS_AUTO_INCREMENT);
} else {
return INVALID_READ_BYTE;
}
case IO_BCPD_ADDR:
case IO_OCPD_ADDR:
if (IS_CGB) {
ColorPalettes* cp = addr == IO_BCPD_ADDR ? &PPU.bgcp : &PPU.obcp;
return cp->data[cp->index];
} else {
return INVALID_READ_BYTE;
}
case IO_SVBK_ADDR:
return IS_CGB ? (SVBK_UNUSED | PACK(WRAM.bank, SVBK_WRAM_BANK))
: INVALID_READ_BYTE;
case IO_IE_ADDR:
return INTR.ie;
default:
HOOK(read_io_ignored_as, addr, get_io_reg_string(addr));
return INVALID_READ_BYTE;
}
}
static u8 read_nrx1_reg(Channel* channel) {
return PACK(channel->square_wave.duty, NRX1_WAVE_DUTY);
}
static u8 read_nrx2_reg(Channel* channel) {
return PACK(channel->envelope.initial_volume, NRX2_INITIAL_VOLUME) |
PACK(channel->envelope.direction, NRX2_ENVELOPE_DIRECTION) |
PACK(channel->envelope.period, NRX2_ENVELOPE_PERIOD);
}
static u8 read_nrx4_reg(Channel* channel) {
return PACK(channel->length_enabled, NRX4_LENGTH_ENABLED);
}
static u8 read_apu(Emulator* e, MaskedAddress addr) {
apu_synchronize(e);
switch (addr) {
case APU_NR10_ADDR:
return NR10_UNUSED | PACK(SWEEP.period, NR10_SWEEP_PERIOD) |
PACK(SWEEP.direction, NR10_SWEEP_DIRECTION) |
PACK(SWEEP.shift, NR10_SWEEP_SHIFT);
case APU_NR11_ADDR:
return NRX1_UNUSED | read_nrx1_reg(&CHANNEL1);
case APU_NR12_ADDR:
return read_nrx2_reg(&CHANNEL1);
case APU_NR14_ADDR:
return NRX4_UNUSED | read_nrx4_reg(&CHANNEL1);
case APU_NR21_ADDR:
return NRX1_UNUSED | read_nrx1_reg(&CHANNEL2);
case APU_NR22_ADDR:
return read_nrx2_reg(&CHANNEL2);
case APU_NR24_ADDR:
return NRX4_UNUSED | read_nrx4_reg(&CHANNEL2);
case APU_NR30_ADDR:
return NR30_UNUSED |
PACK(CHANNEL3.dac_enabled, NR30_DAC_ENABLED);
case APU_NR32_ADDR:
return NR32_UNUSED | PACK(WAVE.volume, NR32_SELECT_WAVE_VOLUME);
case APU_NR34_ADDR:
return NRX4_UNUSED | read_nrx4_reg(&CHANNEL3);
case APU_NR42_ADDR:
return read_nrx2_reg(&CHANNEL4);
case APU_NR43_ADDR:
return PACK(NOISE.clock_shift, NR43_CLOCK_SHIFT) |
PACK(NOISE.lfsr_width, NR43_LFSR_WIDTH) |
PACK(NOISE.divisor, NR43_DIVISOR);
case APU_NR44_ADDR:
return NRX4_UNUSED | read_nrx4_reg(&CHANNEL4);
case APU_NR50_ADDR:
return PACK(APU.so_output[VIN][1], NR50_VIN_SO2) |
PACK(APU.so_volume[1], NR50_SO2_VOLUME) |
PACK(APU.so_output[VIN][0], NR50_VIN_SO1) |
PACK(APU.so_volume[0], NR50_SO1_VOLUME);
case APU_NR51_ADDR:
return PACK(APU.so_output[SOUND4][1], NR51_SOUND4_SO2) |
PACK(APU.so_output[SOUND3][1], NR51_SOUND3_SO2) |
PACK(APU.so_output[SOUND2][1], NR51_SOUND2_SO2) |
PACK(APU.so_output[SOUND1][1], NR51_SOUND1_SO2) |
PACK(APU.so_output[SOUND4][0], NR51_SOUND4_SO1) |
PACK(APU.so_output[SOUND3][0], NR51_SOUND3_SO1) |
PACK(APU.so_output[SOUND2][0], NR51_SOUND2_SO1) |
PACK(APU.so_output[SOUND1][0], NR51_SOUND1_SO1);
case APU_NR52_ADDR:
return NR52_UNUSED | PACK(APU.enabled, NR52_ALL_SOUND_ENABLED) |
PACK(CHANNEL4.status, NR52_SOUND4_ON) |
PACK(CHANNEL3.status, NR52_SOUND3_ON) |
PACK(CHANNEL2.status, NR52_SOUND2_ON) |
PACK(CHANNEL1.status, NR52_SOUND1_ON);
default:
return INVALID_READ_BYTE;
}
}
static u8 read_wave_ram(Emulator* e, MaskedAddress addr) {
apu_synchronize(e);
if (CHANNEL3.status) {
/* If the wave channel is playing, the byte is read from the sample
* position. On DMG, this is only allowed if the read occurs exactly when
* it is being accessed by the Wave channel. */
u8 result;
if (IS_CGB || TICKS == WAVE.sample_time) {
result = WAVE.ram[WAVE.position >> 1];
HOOK(read_wave_ram_while_playing_ab, addr, result);
} else {
result = INVALID_READ_BYTE;
HOOK(read_wave_ram_while_playing_invalid_a, addr);
}
return result;
} else {
return WAVE.ram[addr];
}
}
static Bool is_dma_access_ok(Emulator* e, Address addr) {
/* TODO: need to figure out bus conflicts during DMA for non-OAM accesses. */
return DMA.state != DMA_ACTIVE || (addr & 0xff00) != 0xfe00;
}
static u8 read_u8_pair(Emulator* e, MemoryTypeAddressPair pair, Bool raw) {
switch (pair.type) {
/* Take advantage of the fact that MEMORY_MAP_ROM9 is 0, and ROM1 is 1 when
* indexing into rom_base. */
case MEMORY_MAP_ROM0:
case MEMORY_MAP_ROM1: {
u32 rom_addr = MMAP_STATE.rom_base[pair.type] | pair.addr;
assert(rom_addr < e->cart_info->size);
u8 value = e->cart_info->data[rom_addr];
if (!raw) {
HOOK(read_rom_ib, rom_addr, value);
}
return value;
}
case MEMORY_MAP_VRAM:
return read_vram(e, pair.addr);
case MEMORY_MAP_EXT_RAM:
return e->memory_map.read_ext_ram(e, pair.addr);
case MEMORY_MAP_WORK_RAM0:
return WRAM.data[pair.addr];
case MEMORY_MAP_WORK_RAM1:
return WRAM.data[WRAM.offset + pair.addr];
case MEMORY_MAP_OAM:
return read_oam(e, pair.addr);
case MEMORY_MAP_UNUSED:
return INVALID_READ_BYTE;
case MEMORY_MAP_IO: {
u8 value = read_io(e, pair.addr);
HOOK(read_io_asb, pair.addr, get_io_reg_string(pair.addr), value);
return value;
}
case MEMORY_MAP_APU:
return read_apu(e, pair.addr);
case MEMORY_MAP_WAVE_RAM:
return read_wave_ram(e, pair.addr);
case MEMORY_MAP_HIGH_RAM:
return HRAM[pair.addr];
default:
UNREACHABLE("invalid address: %u 0x%04x.\n", pair.type, pair.addr);
}
}
static u8 read_u8_raw(Emulator* e, Address addr) {
return read_u8_pair(e, map_address(addr), TRUE);
}
static u8 read_u8(Emulator* e, Address addr) {
dma_synchronize(e);
if (UNLIKELY(!is_dma_access_ok(e, addr))) {
HOOK(read_during_dma_a, addr);
return INVALID_READ_BYTE;
}
if (LIKELY(addr < 0x8000)) {
u32 bank = addr >> ROM_BANK_SHIFT;
u32 rom_addr = MMAP_STATE.rom_base[bank] | (addr & ADDR_MASK_16K);
u8 value = e->cart_info->data[rom_addr];
HOOK(read_rom_ib, rom_addr, value);
return value;
} else {
return read_u8_pair(e, map_address(addr), FALSE);
}
}
static void write_vram(Emulator* e, MaskedAddress addr, u8 value) {
ppu_synchronize(e);
if (UNLIKELY(is_using_vram(e, TRUE))) {
HOOK(write_vram_in_use_ab, addr, value);
return;
}
assert(addr <= ADDR_MASK_8K);
VRAM.data[VRAM.offset + addr] = value;
}
static void write_oam_no_mode_check(Emulator* e, MaskedAddress addr, u8 value) {
Obj* obj = &OAM[addr >> 2];
switch (addr & 3) {
case 0: obj->y = value - OBJ_Y_OFFSET; break;
case 1: obj->x = value - OBJ_X_OFFSET; break;
case 2: obj->tile = value; break;
case 3:
obj->byte3 = value;
obj->priority = UNPACK(value, OBJ_PRIORITY);
obj->yflip = UNPACK(value, OBJ_YFLIP);
obj->xflip = UNPACK(value, OBJ_XFLIP);
obj->palette = UNPACK(value, OBJ_PALETTE);
obj->bank = UNPACK(value, OBJ_BANK);
obj->cgb_palette = UNPACK(value, OBJ_CGB_PALETTE);
break;
}
}
static void write_oam(Emulator* e, MaskedAddress addr, u8 value) {
ppu_synchronize(e);
if (UNLIKELY(is_using_oam(e, TRUE))) {
HOOK(write_oam_in_use_ab, addr, value);
return;
}
write_oam_no_mode_check(e, addr, value);
}
static void calculate_next_intr(Emulator* e) {
e->state.next_intr_ticks = MIN(
MIN(SERIAL.next_intr_ticks, TIMER.next_intr_ticks), PPU.next_intr_ticks);
}
static Bool is_div_falling_edge(Emulator* e, u16 old_div_counter,
u16 div_counter) {
u16 falling_edge = ((old_div_counter ^ div_counter) & ~div_counter);
return falling_edge & s_tima_mask[TIMER.clock_select];
}
static void increment_tima(Emulator*);
static void timer_synchronize(Emulator* e) {
if (TICKS > TIMER.sync_ticks) {
Ticks delta_ticks = TICKS - TIMER.sync_ticks;
TIMER.sync_ticks = TICKS;
if (TIMER.on) {
Ticks cpu_tick = e->state.cpu_tick;
for (; delta_ticks > 0; delta_ticks -= cpu_tick) {
if (TIMER.tima_state == TIMA_STATE_OVERFLOW) {
INTR.if_ |= (INTR.new_if & IF_TIMER);
TIMER.tima = TIMER.tma;
TIMER.tima_state = TIMA_STATE_RESET;
} else if (TIMER.tima_state == TIMA_STATE_RESET) {
TIMER.tima_state = TIMA_STATE_NORMAL;
}
u16 old_div_counter = TIMER.div_counter;
TIMER.div_counter += CPU_TICK;
if (is_div_falling_edge(e, old_div_counter, TIMER.div_counter)) {
increment_tima(e);
}
}
} else {
TIMER.div_counter += delta_ticks;
}
}
}
static void calculate_next_timer_intr(Emulator* e) {
if (TIMER.on) {
Ticks ticks = TIMER.sync_ticks;
Ticks cpu_tick = e->state.cpu_tick;
u16 div_counter = TIMER.div_counter;
u8 tima = TIMER.tima;
if (TIMER.tima_state == TIMA_STATE_OVERFLOW) {
tima = TIMER.tma;
div_counter += CPU_TICK;
ticks += cpu_tick;
}
while (1) {
u16 old_div_counter = div_counter;
div_counter += CPU_TICK;
if (is_div_falling_edge(e, old_div_counter, div_counter) && ++tima == 0) {
break;
}
ticks += cpu_tick;
}
TIMER.next_intr_ticks = ticks;
} else {
TIMER.next_intr_ticks = INVALID_TICKS;
}
calculate_next_intr(e);
}
static void do_timer_interrupt(Emulator* e) {
Ticks cpu_tick = e->state.cpu_tick;
HOOK(trigger_timer_i, TICKS + cpu_tick);
TIMER.tima_state = TIMA_STATE_OVERFLOW;
TIMER.div_counter += TICKS + CPU_TICK - TIMER.sync_ticks;
TIMER.sync_ticks = TICKS + cpu_tick;
TIMER.tima = 0;
INTR.new_if |= IF_TIMER;
calculate_next_timer_intr(e);
}
static void increment_tima(Emulator* e) {
if (++TIMER.tima == 0) {
do_timer_interrupt(e);
}
}
static void clear_div(Emulator* e) {
if (TIMER.on && is_div_falling_edge(e, TIMER.div_counter, 0)) {
increment_tima(e);
}
TIMER.div_counter = 0;
}
/* Trigger is only TRUE on the tick where it transitioned to the new state;
* "check" is TRUE as long as at continues to be in that state. This is
* necessary because the internal STAT IF flag is set when "triggered", and
* cleared only when the "check" returns FALSE for all STAT IF bits. HBLANK and
* VBLANK don't have a special trigger, so "trigger" and "check" are equal for
* those modes. */
#define TRIGGER_MODE_IS(X) (STAT.trigger_mode == PPU_MODE_##X)
#define TRIGGER_HBLANK (TRIGGER_MODE_IS(HBLANK) && STAT.hblank.irq)
#define TRIGGER_VBLANK (TRIGGER_MODE_IS(VBLANK) && STAT.vblank.irq)
#define TRIGGER_MODE2 (STAT.mode2.trigger && STAT.mode2.irq)
#define CHECK_MODE2 (TRIGGER_MODE_IS(MODE2) && STAT.mode2.irq)
#define TRIGGER_Y_COMPARE (STAT.y_compare.trigger && STAT.y_compare.irq)
#define CHECK_Y_COMPARE (STAT.new_ly_eq_lyc && STAT.y_compare.irq)
#define SHOULD_TRIGGER_STAT \
(TRIGGER_HBLANK || TRIGGER_VBLANK || TRIGGER_MODE2 || TRIGGER_Y_COMPARE)
static void check_stat(Emulator* e) {
if (!STAT.if_ && SHOULD_TRIGGER_STAT) {
HOOK(trigger_stat_ii, PPU.ly, TICKS + CPU_TICK);
INTR.new_if |= IF_STAT;
if (!(TRIGGER_VBLANK || TRIGGER_Y_COMPARE)) {
INTR.if_ |= IF_STAT;
}
STAT.if_ = TRUE;
} else if (!(TRIGGER_HBLANK || TRIGGER_VBLANK || CHECK_MODE2 ||
CHECK_Y_COMPARE)) {
STAT.if_ = FALSE;
}
}
static void check_ly_eq_lyc(Emulator* e, Bool write) {
if (PPU.ly == PPU.lyc ||
(write && PPU.last_ly == SCREEN_HEIGHT_WITH_VBLANK - 1 &&
PPU.last_ly == PPU.lyc)) {
HOOK(trigger_y_compare_ii, PPU.ly, TICKS + CPU_TICK);
STAT.y_compare.trigger = TRUE;
STAT.new_ly_eq_lyc = TRUE;
} else {
STAT.y_compare.trigger = FALSE;
STAT.ly_eq_lyc = STAT.new_ly_eq_lyc = FALSE;
if (write) {
/* If stat was triggered this frame due to Y compare, cancel it.
* There's probably a nicer way to do this. */
if ((INTR.new_if ^ INTR.if_) & INTR.new_if & IF_STAT) {
if (!SHOULD_TRIGGER_STAT) {
INTR.new_if &= ~IF_STAT;
}
}
}
}
}
static void check_joyp_intr(Emulator* e) {
call_joyp_callback(e, TRUE);
u8 p10_p13 = read_joyp_p10_p13(e);
/* joyp interrupt only triggers on p10-p13 going from high to low (i.e. not
* pressed to pressed). */
if ((p10_p13 ^ JOYP.last_p10_p13) & ~p10_p13) {
INTR.new_if |= IF_JOYPAD;
}
JOYP.last_p10_p13 = p10_p13;
}
static void update_bw_palette_rgba(Emulator* e, PaletteType type) {
for (int i = 0; i < 4; ++i) {
e->pal[type].color[i] =
e->color_to_rgba[type].color[PPU.pal[type].color[i]];
}
if (type == PALETTE_TYPE_BGP) {
for (int pal = 0; pal < 4; ++pal) {
for (int i = 0; i < 4; ++i) {
e->sgb_pal[pal].color[i] =
SGB.screen_pal[pal].color[PPU.pal[PALETTE_TYPE_BGP].color[i]];
}
}
}
}
static RGBA unpack_cgb_color(Emulator* e, u16 color) {
u8 r = UNPACK(color, XCPD_RED_INTENSITY);
u8 g = UNPACK(color, XCPD_GREEN_INTENSITY);
u8 b = UNPACK(color, XCPD_BLUE_INTENSITY);
switch (e->cgb_color_curve) {
default:
case CGB_COLOR_CURVE_NONE:
return MAKE_RGBA(r << 3, g << 3, b << 3, 255);
case CGB_COLOR_CURVE_SAMEBOY_EMULATE_HARDWARE: {
// Using Sameboy's color curves, see
// https://github.com/LIJI32/SameBoy/blob/345e51647f2a7ce1ea39f21497f5a6dc75a587c8/Core/display.c#L239
static const u8 curve[] = {
0, 6, 12, 20, 28, 36, 45, 56, 66, 76, 88,
100, 113, 125, 137, 149, 161, 172, 182, 192, 202, 210,
218, 225, 232, 238, 243, 247, 250, 252, 254, 255,
};
r = curve[r];
g = curve[g];
b = curve[b];
g = (g * 3 + b) / 4;
return MAKE_RGBA(r, g, b, 255);
}
case CGB_COLOR_CURVE_GAMBATTE:
// Using gambatte's color curves, according to Gameboy Online, see
// https://github.com/taisel/GameBoy-Online/blob/47f9f638a8a9445aaa75050f634e437baa34aae0/js/GameBoyCore.js#L6453
return MAKE_RGBA((r * 13 + g * 2 + b) >> 1, (g * 3 + b) << 1,
(r * 3 + g * 2 + b * 11) >> 1, 255);
}
}
static RGBA unpack_cgb_color8(Emulator* e, u8 lo, u8 hi) {
return unpack_cgb_color(e, (hi << 8) | lo);
}
static void set_sgb_palette(Emulator* e, int pal, u8 lo0, u8 hi0, u8 lo1,
u8 hi1, u8 lo2, u8 hi2, u8 lo3, u8 hi3) {
for (int i = 0; i < 4; ++i) {
SGB.screen_pal[i].color[0] = unpack_cgb_color8(e, lo0, hi0);
}
SGB.screen_pal[pal].color[1] = unpack_cgb_color8(e, lo1, hi1);
SGB.screen_pal[pal].color[2] = unpack_cgb_color8(e, lo2, hi2);
SGB.screen_pal[pal].color[3] = unpack_cgb_color8(e, lo3, hi3);
if (pal == 0) {
emulator_set_bw_palette(e, PALETTE_TYPE_OBP0, &SGB.screen_pal[0]);
emulator_set_bw_palette(e, PALETTE_TYPE_OBP1, &SGB.screen_pal[0]);
}
update_bw_palette_rgba(e, PALETTE_TYPE_BGP);
}
static void unpack_sgb_palette_ram(Emulator* e, int pal, u8 idx_lo, u8 idx_hi) {
u16 idx = (idx_hi << 8) | idx_lo;
u8* data = SGB.pal_ram + 8 * (idx & 0x1ff);
set_sgb_palette(e, pal, data[0], data[1], data[2], data[3], data[4], data[5],
data[6], data[7]);
}
static void clear_frame_buffer(Emulator* e, RGBA color) {
for (size_t i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; ++i) {
e->frame_buffer[i] = color;
}
}
static void update_sgb_mask(Emulator* e) {
RGBA color = RGBA_BLACK;
Bool should_clear = TRUE;
switch (SGB.mask) {
case SGB_MASK_CANCEL: should_clear = FALSE; break;
case SGB_MASK_FREEZE: should_clear = FALSE; break;
case SGB_MASK_BLACK: color = RGBA_BLACK; break;
case SGB_MASK_COLOR0: color = SGB.screen_pal[0].color[0]; break;
}
if (should_clear) {
clear_frame_buffer(e, color);
}
}
static void set_sgb_attr(Emulator* e, u8 byte) {
u8 file = byte & 0x3f;
if (file < 0x2D) {
memcpy(SGB.attr_map, SGB.attr_ram + file * 90, sizeof(SGB.attr_map));
}
if (byte & 0x40) {
SGB.mask = SGB_MASK_CANCEL;
update_sgb_mask(e);
}
}
static void set_sgb_attr_block(Emulator* e, int x0, int y0, int x1, int y1,
u8 pal) {
for (int y = y0; y <= y1; ++y) {
for (int x = x0; x <= x1; ++x) {
int index = y * 20 + x;
u8 *byte = &SGB.attr_map[index >> 2];
u8 mask = ~(0xc0 >> (2 * (x & 3)));
*byte = (*byte & mask) | (pal << (2 * (3 - (x & 3))));
}
}
}
static u8 reverse_bits_u8(u8 x) {
x = ((x << 4) & 0xf0) | ((x >> 4) & 0x0f);
x = ((x << 2) & 0xcc) | ((x >> 2) & 0x33);
x = ((x << 1) & 0xaa) | ((x >> 1) & 0x55);
return x;
}
static u16 map_select_to_address(TileMapSelect map_select) {
return map_select == TILE_MAP_9800_9BFF ? 0x1800 : 0x1c00;
}
static void do_sgb(Emulator* e) {
if (!IS_SGB) { return; }
Bool do_command = FALSE;
switch (SGB.state) {
case SGB_STATE_IDLE:
if (JOYP.joypad_select == JOYPAD_SGB_BOTH_LOW) {
SGB.bits_read = 0;
if (++SGB.current_packet >= SGB.packet_count) {
SGB.current_packet = 0;
SGB.packet_count = 0;
ZERO_MEMORY(SGB.data);
}
SGB.state = SGB_STATE_WAIT_BIT;
}
break;
case SGB_STATE_WAIT_BIT:
if (JOYP.joypad_select == JOYPAD_SGB_BOTH_HIGH) {
SGB.state =
SGB.bits_read >= 128 ? SGB_STATE_STOP_BIT : SGB_STATE_READ_BIT;
} else {
SGB.state = SGB_STATE_IDLE;
}
break;
case SGB_STATE_READ_BIT:
if (JOYP.joypad_select == JOYPAD_SGB_P15_LOW) {
int curbyte = (SGB.current_packet << 4) | (SGB.bits_read >> 3);
u8 curbit = SGB.bits_read & 7;
SGB.data[curbyte] |= 1 << curbit;
SGB.bits_read++;
SGB.state = SGB_STATE_WAIT_BIT;
} else if (JOYP.joypad_select == JOYPAD_SGB_P14_LOW) {
SGB.bits_read++;
SGB.state = SGB_STATE_WAIT_BIT;
}
break;
case SGB_STATE_STOP_BIT:
if (JOYP.joypad_select == JOYPAD_SGB_P14_LOW) {
SGB.state = SGB_STATE_STOP_WAIT;
} else {
SGB.state = SGB_STATE_IDLE;
}
break;
case SGB_STATE_STOP_WAIT:
if (JOYP.joypad_select == JOYPAD_SGB_BOTH_HIGH) {
do_command = TRUE;
SGB.state = SGB_STATE_IDLE;
}
break;
}
if ((JOYP.joypad_select == JOYPAD_SGB_BOTH_LOW ||
JOYP.joypad_select == JOYPAD_SGB_P15_LOW) &&
!SGB.player_incremented) {
SGB.player_incremented = TRUE;
} else if (JOYP.joypad_select == JOYPAD_SGB_BOTH_HIGH) {
if (SGB.player_incremented) {
SGB.current_player = (SGB.current_player + 1) & SGB.player_mask;
}
SGB.player_incremented = FALSE;
}
if (do_command) {
if (SGB.current_packet == 0) {
SGB.packet_count = SGB.data[0] & 7;
}
if (SGB.current_packet == SGB.packet_count - 1) {
// Assume we can just read the data directly from VRAM. Cheat by reading
// the upper-left tile and assuming that the rest of the data is in
// order.
int code = SGB.data[0] >> 3;
u8* xfer_src = NULL;
if (code == 0x0b || code == 0x13 || code == 0x14 || code == 0x15) {
u16 map_base = map_select_to_address(LCDC.bg_tile_map_select);
u16 tile_index = VRAM.data[map_base];
if (LCDC.bg_tile_data_select == TILE_DATA_8800_97FF) {
// Copy the data into the temporary buffer so it can be used
// contiguously.
static u8 s_temp_xfer_buffer[4096];
u16 start_offset = (256 + (s8)tile_index) * 16;
u16 len = 0x1800 - start_offset;
memcpy(s_temp_xfer_buffer, VRAM.data + start_offset, len);
memcpy(s_temp_xfer_buffer + len, VRAM.data + 0x800, 0x1000 - len);
xfer_src = s_temp_xfer_buffer;
} else {
xfer_src = VRAM.data + tile_index * 16;
}
}
switch (code) {
case 0x00: // PAL01
case 0x01: // PAL23
case 0x02: // PAL03
case 0x03: { // PAL12
static struct {
int pal0, pal1;
} s_pals[] = {{0, 1}, {2, 3}, {0, 3}, {1, 2}};
set_sgb_palette(e, s_pals[code].pal0, SGB.data[1], SGB.data[2],
SGB.data[3], SGB.data[4], SGB.data[5], SGB.data[6],
SGB.data[7], SGB.data[8]);
set_sgb_palette(e, s_pals[code].pal1, SGB.data[1], SGB.data[2],
SGB.data[9], SGB.data[10], SGB.data[11], SGB.data[12],
SGB.data[13], SGB.data[14]);
break;
}
case 0x04: { // ATTR_BLK
int datasets = MIN(SGB.data[1], (SGB.packet_count * 16 - 2) / 6);
for (int i = 0; i < datasets; ++i) {
u8 info = SGB.data[2 + i * 6];
u8 pal = SGB.data[3 + i * 6];
u8 palin = pal & 3, palon = (pal >> 2) & 3, palout = (pal >> 4) & 3;
u8 l = SGB.data[4 + i * 6], t = SGB.data[5 + i * 6],
r = SGB.data[6 + i * 6], b = SGB.data[7 + i * 6];
Bool inside = info & 1;
Bool border = info & 2;
Bool outside = info & 4;
if (inside && !border && !outside) {
border = TRUE;
palon = palin;
} else if (outside && !border && !inside) {
border = TRUE;
palon = palout;
}
Bool has_inner = (r - l) >= 2 && (b - t) >= 2;
if (inside && has_inner) { // colors inside region
set_sgb_attr_block(e, l + 1, t + 1, r - 1, b - 1, palin);
}
if (border) { // colors on region border
set_sgb_attr_block(e, l, t, r, t, palon); // top
set_sgb_attr_block(e, l, t, l, b, palon); // left
set_sgb_attr_block(e, l, b, r, b, palon); // bottom
set_sgb_attr_block(e, r, t, r, b, palon); // right
}
if (outside) { // colors outside region
set_sgb_attr_block(e, 0, 0, 19, t - 1, palout); // top
set_sgb_attr_block(e, 0, t, l - 1, b, palout); // left
set_sgb_attr_block(e, 0, b + 1, 19, 17, palout); // bottom
set_sgb_attr_block(e, r + 1, t, 19, b, palout); // right
}
}
break;
}
case 0x05: { // ATTR_LIN
int datasets = MIN(SGB.data[1], SGB.packet_count * 16 - 2);
for (int i = 0; i < datasets; ++i) {
u8 info = SGB.data[2 + i];
u8 line = info & 0x1f;
u8 pal = (info >> 5) & 3;
if (info & 0x80) { // horizontal
set_sgb_attr_block(e, 0, line, 19, line, pal);
} else { // vertical
set_sgb_attr_block(e, line, 0, line, 17, pal);
}
}
break;
}
case 0x06: { // ATTR_DIV
u8 pal = SGB.data[1];
u8 pallo = pal & 3, palon = (pal >> 2) & 3, palhi = (pal >> 4) & 3;
u8 line = SGB.data[2];
if (pal & 0x40) { // above/below
set_sgb_attr_block(e, 0, 0, 19, line - 1, palhi); // top
set_sgb_attr_block(e, 0, line, 19, line, palon); // on
set_sgb_attr_block(e, 0, line + 1, 19, 17, pallo); // bottom
} else { // left/right
set_sgb_attr_block(e, 0, 0, line - 1, 17, palhi); // left
set_sgb_attr_block(e, line, 0, line, 17, palon); // on
set_sgb_attr_block(e, line + 1, 0, 19, 17, pallo); // right
}
break;
}
case 0x07: { // ATTR_CHR
u8 x = SGB.data[1], y = SGB.data[2];
u8 dx = 0, dy = 0;
if (SGB.data[5] == 0) { dx = 1; } else { dy = 1; }
int datasets = MIN(MIN((SGB.data[4] << 8) | SGB.data[3],
(SGB.packet_count * 16 - 6) * 4),
360);
for (int i = 0; i < datasets; i += 4) {
u8 byte = SGB.data[6 + (i >> 2)];
for (int j = 0; j < MIN(datasets, 4); ++j) {
set_sgb_attr_block(e, x, y, x, y, byte >> ((3 - j) * 2));
x += dx;
y += dy;
if (x >= 20) { x = 0; y++; }
if (y >= 18) { y = 0; x++; if (x >= 20) { x = 0; } }
}
}
break;
}
case 0x0a: // PAL_SET
unpack_sgb_palette_ram(e, 3, SGB.data[7], SGB.data[8]);
unpack_sgb_palette_ram(e, 2, SGB.data[5], SGB.data[6]);
unpack_sgb_palette_ram(e, 1, SGB.data[3], SGB.data[4]);
unpack_sgb_palette_ram(e, 0, SGB.data[1], SGB.data[2]);
if (SGB.data[9] & 0x80) { // Use attr file
set_sgb_attr(e, SGB.data[9] & 0x7f);
}
break;
case 0x0b: // PAL_TRN
memcpy(SGB.pal_ram, xfer_src, sizeof(SGB.pal_ram));
break;
case 0x11: // MLT_REQ
SGB.player_mask = SGB.data[1] & 3;
break;
case 0x13: // CHR_TRN
memcpy(SGB.chr_ram + ((SGB.data[1] & 1) << 12), xfer_src, 4096);
break;
case 0x14: // PCT_TRN
for (int pal = 0; pal < 4; ++pal) {
SGB.border_pal[pal][0] = 0;
for (int col = 1; col < 16; ++col) {
int idx = 0x800 + (pal * 16 + col) * 2;
u8 lo = xfer_src[idx], hi = xfer_src[idx + 1];
SGB.border_pal[pal][col] = unpack_cgb_color8(e, lo, hi);
}
}
RGBA* dst = e->sgb_frame_buffer;
for (int col = 0; col < 28; ++col) {
for (int row = 0; row < 32; ++row) {
int idx = (col * 32 + row) * 2;
u8 tile = xfer_src[idx];
u8 info = xfer_src[idx + 1];
u8 pal = (info >> 2) & 3;
u8* src = SGB.chr_ram + tile * 32;
int dsrc = 2;
if (info & 0x80) {
dsrc = -2;
src += 14;
}
for (int y = 0; y < 8; ++y, src += dsrc) {
u8 p0 = src[0], p1 = src[1], p2 = src[16], p3 = src[17];
if (!(info & 0x40)) {
p0 = reverse_bits_u8(p0);
p1 = reverse_bits_u8(p1);
p2 = reverse_bits_u8(p2);
p3 = reverse_bits_u8(p3);
}
for (int x = 0; x < 8; ++x) {
int palidx = ((p3 & 1) << 3) | ((p2 & 1) << 2) |
((p1 & 1) << 1) | (p0 & 1);
dst[(col * 8 + y) * SGB_SCREEN_WIDTH + (row * 8 + x)] =
SGB.border_pal[pal][palidx];
p0 >>= 1;
p1 >>= 1;
p2 >>= 1;
p3 >>= 1;
}
}
}
}
// Update the mask in case we overwrote the center area.
update_sgb_mask(e);
break;
case 0x15: // ATTR_TRN
memcpy(SGB.attr_ram, xfer_src, sizeof(SGB.attr_ram));
break;
case 0x16: // ATTR_SET
set_sgb_attr(e, SGB.data[1]);
break;
case 0x17: // MASK_EN
if (SGB.data[1] <= 3) {
SGB.mask = (SgbMask)(SGB.data[1]);
update_sgb_mask(e);
}
break;
case 0x1e: case 0x1f:
return; // Invalid
}
}
}
}
static void write_io(Emulator* e, MaskedAddress addr, u8 value) {
HOOK(write_io_asb, addr, get_io_reg_string(addr), value);
switch (addr) {
case IO_JOYP_ADDR:
JOYP.joypad_select = UNPACK(value, JOYP_JOYPAD_SELECT);
do_sgb(e);
check_joyp_intr(e);
break;
case IO_SB_ADDR:
serial_synchronize(e);
SERIAL.sb = value;
#if RGBDS_LIVE
EM_ASM({emulator.serialCallback($0);}, value);
#endif
break;
case IO_SC_ADDR:
serial_synchronize(e);
SERIAL.transferring = UNPACK(value, SC_TRANSFER_START);
SERIAL.clock = UNPACK(value, SC_SHIFT_CLOCK);
if (SERIAL.transferring) {
SERIAL.tick_count = 0;
SERIAL.transferred_bits = 0;
}
calculate_next_serial_intr(e);
break;
case IO_DIV_ADDR:
timer_synchronize(e);
clear_div(e);
calculate_next_timer_intr(e);
break;
case IO_TIMA_ADDR:
timer_synchronize(e);
if (TIMER.on) {
if (UNLIKELY(TIMER.tima_state == TIMA_STATE_OVERFLOW)) {
/* Cancel the overflow and interrupt if written on the same tick. */
TIMER.tima_state = TIMA_STATE_NORMAL;
INTR.new_if &= ~IF_TIMER;
TIMER.tima = value;
} else if (TIMER.tima_state != TIMA_STATE_RESET) {
/* Only update tima if it wasn't reset this tick. */
TIMER.tima = value;
}
calculate_next_timer_intr(e);
} else {
TIMER.tima = value;
}
break;
case IO_TMA_ADDR:
timer_synchronize(e);
TIMER.tma = value;
if (UNLIKELY(TIMER.on && TIMER.tima_state == TIMA_STATE_RESET)) {
TIMER.tima = value;
}
calculate_next_timer_intr(e);
break;
case IO_TAC_ADDR: {
timer_synchronize(e);
Bool old_timer_on = TIMER.on;
u16 old_tima_mask = s_tima_mask[TIMER.clock_select];
TIMER.clock_select = UNPACK(value, TAC_CLOCK_SELECT);
TIMER.on = UNPACK(value, TAC_TIMER_ON);
/* tima is incremented when a specific bit of div_counter transitions
* from 1 to 0. This can happen as a result of writing to DIV, or in this
* case modifying which bit we're looking at. */
Bool tima_tick = FALSE;
if (!old_timer_on) {
u16 tima_mask = s_tima_mask[TIMER.clock_select];
if (TIMER.on) {
tima_tick = (TIMER.div_counter & old_tima_mask) != 0;
} else {
tima_tick = (TIMER.div_counter & old_tima_mask) != 0 &&
(TIMER.div_counter & tima_mask) == 0;
}
if (tima_tick) {
increment_tima(e);
}
}
calculate_next_timer_intr(e);
break;
}
case IO_IF_ADDR:
intr_synchronize(e);
INTR.new_if = INTR.if_ = value & IF_ALL;
break;
case IO_LCDC_ADDR: {
ppu_synchronize(e);
ppu_mode3_synchronize(e);
Bool was_enabled = LCDC.display;
LCDC.display = UNPACK(value, LCDC_DISPLAY);
LCDC.window_tile_map_select = UNPACK(value, LCDC_WINDOW_TILE_MAP_SELECT);
LCDC.window_display = UNPACK(value, LCDC_WINDOW_DISPLAY);
LCDC.bg_tile_data_select = UNPACK(value, LCDC_BG_TILE_DATA_SELECT);
LCDC.bg_tile_map_select = UNPACK(value, LCDC_BG_TILE_MAP_SELECT);
LCDC.obj_size = UNPACK(value, LCDC_OBJ_SIZE);
LCDC.obj_display = UNPACK(value, LCDC_OBJ_DISPLAY);
LCDC.bg_display = UNPACK(value, LCDC_BG_DISPLAY);
if (was_enabled ^ LCDC.display) {
STAT.mode = PPU_MODE_HBLANK;
PPU.ly = PPU.line_y = 0;
if (LCDC.display) {
check_ly_eq_lyc(e, FALSE);
HOOK0(enable_display_v);
PPU.state = PPU_STATE_LCD_ON_MODE2;
PPU.state_ticks = PPU_MODE2_TICKS;
PPU.line_start_ticks =
ALIGN_UP(TICKS - CPU_TICK - CPU_TICK, CPU_TICK);
PPU.display_delay_frames = PPU_ENABLE_DISPLAY_DELAY_FRAMES;
STAT.trigger_mode = PPU_MODE_MODE2;
} else {
HOOK0(disable_display_v);
/* Clear the framebuffer. */
if (IS_SGB) {
update_sgb_mask(e);
} else {
clear_frame_buffer(e, RGBA_WHITE);
}
e->state.event |= EMULATOR_EVENT_NEW_FRAME;
}
calculate_next_ppu_intr(e);
}
break;
}
case IO_STAT_ADDR: {
ppu_synchronize(e);
Bool new_vblank_irq = UNPACK(value, STAT_VBLANK_INTR);
Bool new_hblank_irq = UNPACK(value, STAT_HBLANK_INTR);
if (LCDC.display) {
Bool hblank = TRIGGER_MODE_IS(HBLANK) && !STAT.hblank.irq;
Bool vblank = TRIGGER_MODE_IS(VBLANK) && !STAT.vblank.irq;
Bool y_compare = STAT.new_ly_eq_lyc && !STAT.y_compare.irq;
if (IS_CGB) {
/* CGB only triggers on STAT write if the value being written
* actually sets that IRQ */
hblank = hblank && new_hblank_irq;
vblank = vblank && new_vblank_irq;
}
if (!STAT.if_ && (hblank || vblank || y_compare)) {
HOOK(trigger_stat_from_write_cccii, y_compare ? 'Y' : '.',
vblank ? 'V' : '.', hblank ? 'H' : '.', PPU.ly,
TICKS + CPU_TICK);
INTR.new_if |= IF_STAT;
INTR.if_ |= IF_STAT;
STAT.if_ = TRUE;
}
}
STAT.y_compare.irq = UNPACK(value, STAT_YCOMPARE_INTR);
STAT.mode2.irq = UNPACK(value, STAT_MODE2_INTR);
STAT.vblank.irq = new_vblank_irq;
STAT.hblank.irq = new_hblank_irq;
calculate_next_ppu_intr(e);
break;
}
case IO_SCY_ADDR:
ppu_mode3_synchronize(e);
PPU.scy = value;
break;
case IO_SCX_ADDR:
ppu_synchronize(e);
ppu_mode3_synchronize(e);
PPU.scx = value;
break;
case IO_LY_ADDR:
break;
case IO_LYC_ADDR:
ppu_synchronize(e);
PPU.lyc = value;
if (LCDC.display) {
check_ly_eq_lyc(e, TRUE);
check_stat(e);
}
calculate_next_ppu_intr(e);
break;
case IO_DMA_ADDR:
/* DMA can be restarted. */
dma_synchronize(e);
DMA.sync_ticks = TICKS;
DMA.tick_count = 0;
DMA.state = (DMA.state != DMA_INACTIVE ? DMA.state : DMA_TRIGGERED);
DMA.source = value << 8;
break;
case IO_BGP_ADDR:
case IO_OBP0_ADDR:
case IO_OBP1_ADDR: {
PaletteType type = addr - IO_BGP_ADDR;
Palette* pal = &PPU.pal[type];
ppu_mode3_synchronize(e);
pal->color[3] = UNPACK(value, PALETTE_COLOR3);
pal->color[2] = UNPACK(value, PALETTE_COLOR2);
pal->color[1] = UNPACK(value, PALETTE_COLOR1);
pal->color[0] = UNPACK(value, PALETTE_COLOR0);
update_bw_palette_rgba(e, type);
break;
}
case IO_WY_ADDR:
ppu_synchronize(e);
ppu_mode3_synchronize(e);
PPU.wy = value;
break;
case IO_WX_ADDR:
ppu_mode3_synchronize(e);
PPU.wx = value;
break;
case IO_KEY1_ADDR:
if (IS_CGB) {
CPU_SPEED.switching = UNPACK(value, KEY1_PREPARE_SPEED_SWITCH);
}
break;
case IO_VBK_ADDR:
if (IS_CGB) {
VRAM.bank = UNPACK(value, VBK_VRAM_BANK);
VRAM.offset = VRAM.bank << 13;
}
break;
case IO_HDMA1_ADDR:
if (IS_CGB) {
HDMA.source = (HDMA.source & 0x00ff) | (value << 8);
}
break;
case IO_HDMA2_ADDR:
if (IS_CGB) {
HDMA.source = (HDMA.source & 0xff00) | (value & 0xf0);
}
break;
case IO_HDMA3_ADDR:
if (IS_CGB) {
HDMA.dest = (HDMA.dest & 0x00ff) | (value << 8);
}
break;
case IO_HDMA4_ADDR:
if (IS_CGB) {
HDMA.dest = (HDMA.dest & 0xff00) | (value & 0xf0);
}
break;
case IO_HDMA5_ADDR:
if (IS_CGB) {
HdmaTransferMode new_mode = UNPACK(value, HDMA5_TRANSFER_MODE);
u8 new_blocks = UNPACK(value, HDMA5_BLOCKS);
if (HDMA.mode == HDMA_TRANSFER_MODE_HDMA &&
(HDMA.blocks & 0x80) == 0) { /* HDMA Active */
if (new_mode == HDMA_TRANSFER_MODE_GDMA) {
/* Stop HDMA copy. */
HDMA.blocks |= 0x80 | new_blocks;
} else {
HDMA.blocks = new_blocks;
HDMA.mode = new_mode;
}
} else {
HDMA.mode = new_mode;
HDMA.blocks = new_blocks;
}
if (HDMA.mode == HDMA_TRANSFER_MODE_GDMA) {
HDMA.state = DMA_ACTIVE;
}
}
break;
case IO_RP_ADDR:
if (IS_CGB) {
INFRARED.write = UNPACK(value, RP_WRITE_DATA);
INFRARED.enabled = UNPACK(value, RP_DATA_READ_ENABLE);
}
break;
case IO_BCPS_ADDR:
case IO_OCPS_ADDR:
if (IS_CGB) {
ppu_mode3_synchronize(e);
ColorPalettes* cp = addr == IO_BCPS_ADDR ? &PPU.bgcp : &PPU.obcp;
cp->index = UNPACK(value, XCPS_INDEX);
cp->auto_increment = UNPACK(value, XCPS_AUTO_INCREMENT);
}
break;
case IO_BCPD_ADDR:
case IO_OCPD_ADDR:
if (IS_CGB) {
ppu_mode3_synchronize(e);
ColorPalettes* cp = addr == IO_BCPD_ADDR ? &PPU.bgcp : &PPU.obcp;
cp->data[cp->index] = value;
u8 palette_index = (cp->index >> 3) & 7;
u8 color_index = (cp->index >> 1) & 3;
u16 color16 = (cp->data[cp->index | 1] << 8) | cp->data[cp->index & ~1];
RGBA color = unpack_cgb_color(e, color16);
cp->palettes[palette_index].color[color_index] = color;
if (cp->auto_increment) {
cp->index = (cp->index + 1) & 0x3f;
}
}
break;
case IO_SVBK_ADDR:
if (IS_CGB) {
WRAM.bank = UNPACK(value, SVBK_WRAM_BANK);
WRAM.offset = WRAM.bank == 0 ? 0x1000 : (WRAM.bank << 12);
}
break;
case IO_IE_ADDR:
INTR.ie = value;
break;
default:
HOOK(write_io_ignored_as, addr, get_io_reg_string(addr), value);
break;
}
}
static void write_nrx1_reg(Emulator* e, Channel* channel, Address addr,
u8 value) {
if (APU.enabled) {
channel->square_wave.duty = UNPACK(value, NRX1_WAVE_DUTY);
}
channel->length = NRX1_MAX_LENGTH - UNPACK(value, NRX1_LENGTH);
HOOK(write_nrx1_abi, addr, value, channel->length);
}
static void write_nrx2_reg(Emulator* e, Channel* channel, Address addr,
u8 value) {
channel->envelope.initial_volume = UNPACK(value, NRX2_INITIAL_VOLUME);
channel->dac_enabled = UNPACK(value, NRX2_DAC_ENABLED) != 0;
if (!channel->dac_enabled) {
channel->status = FALSE;
HOOK(write_nrx2_disable_dac_ab, addr, value);
}
if (channel->status) {
if (UNLIKELY(channel->envelope.period == 0 &&
channel->envelope.automatic)) {
u8 new_volume = (channel->envelope.volume + 1) & ENVELOPE_MAX_VOLUME;
HOOK(write_nrx2_zombie_mode_abii, addr, value, channel->envelope.volume,
new_volume);
channel->envelope.volume = new_volume;
// Super ugly hack to support decreasing volume in zombie mode.
channel->envelope.zombie_step = value == 9;
if (value == 9) {
HOOK(write_nrx2_zombie_mode_hack_abi, addr, value,
channel->envelope.zombie_step);
}
} else if (UNLIKELY(channel->envelope.zombie_step > 0)) {
if (channel->envelope.zombie_step == 1 && value == 0x11) {
channel->envelope.zombie_step++;
HOOK(write_nrx2_zombie_mode_hack_abi, addr, value,
channel->envelope.zombie_step);
} else if (channel->envelope.zombie_step == 2 && value == 0x18) {
channel->envelope.zombie_step++;
u8 new_volume = (channel->envelope.volume + ENVELOPE_MAX_VOLUME - 1) &
ENVELOPE_MAX_VOLUME;
HOOK(write_nrx2_zombie_mode_abii, addr, value, channel->envelope.volume,
new_volume);
channel->envelope.volume = new_volume;
} else {
channel->envelope.zombie_step = 0;
}
}
}
channel->envelope.direction = UNPACK(value, NRX2_ENVELOPE_DIRECTION);
channel->envelope.period = UNPACK(value, NRX2_ENVELOPE_PERIOD);
HOOK(write_nrx2_initial_volume_abi, addr, value,
channel->envelope.initial_volume);
}
static void write_nrx3_reg(Emulator* e, Channel* channel, u8 value) {
channel->frequency = (channel->frequency & ~0xff) | value;
}
/* Returns TRUE if this channel was triggered. */
static Bool write_nrx4_reg(Emulator* e, Channel* channel, Address addr,
u8 value, u16 max_length) {
Bool trigger = UNPACK(value, NRX4_INITIAL);
Bool was_length_enabled = channel->length_enabled;
channel->length_enabled = UNPACK(value, NRX4_LENGTH_ENABLED);
channel->frequency &= 0xff;
channel->frequency |= UNPACK(value, NRX4_FREQUENCY_HI) << 8;
/* Extra length clocking occurs on NRX4 writes if the next APU frame isn't a
* length counter frame. This only occurs on transition from disabled to
* enabled. */
Bool next_frame_is_length = (APU.frame & 1) == 1;
if (UNLIKELY(!was_length_enabled && channel->length_enabled &&
!next_frame_is_length && channel->length > 0)) {
channel->length--;
HOOK(write_nrx4_extra_length_clock_abi, addr, value, channel->length);
if (!trigger && channel->length == 0) {
HOOK(write_nrx4_disable_channel_ab, addr, value);
channel->status = FALSE;
}
}
if (trigger) {
if (channel->length == 0) {
channel->length = max_length;
if (channel->length_enabled && !next_frame_is_length) {
channel->length--;
}
HOOK(write_nrx4_trigger_new_length_abi, addr, value, channel->length);
}
if (channel->dac_enabled) {
channel->status = TRUE;
}
}
HOOK(write_nrx4_info_abii, addr, value, trigger, channel->length_enabled);
return trigger;
}
static void trigger_nrx4_envelope(Emulator* e, Envelope* envelope,
Address addr) {
envelope->volume = envelope->initial_volume;
envelope->timer = envelope->period ? envelope->period : ENVELOPE_MAX_PERIOD;
envelope->automatic = TRUE;
/* If the next APU frame will update the envelope, increment the timer. */
if (UNLIKELY(APU.frame + 1 == FRAME_SEQUENCER_UPDATE_ENVELOPE_FRAME)) {
envelope->timer++;
}
HOOK(trigger_nrx4_info_asii, addr, get_apu_reg_string(addr), envelope->volume,
envelope->timer);
}
static u16 calculate_sweep_frequency(Emulator* e) {
u16 f = SWEEP.frequency;
if (SWEEP.direction == SWEEP_DIRECTION_ADDITION) {
return f + (f >> SWEEP.shift);
} else {
SWEEP.calculated_subtract = TRUE;
return f - (f >> SWEEP.shift);
}
}
static void trigger_nr14_reg(Emulator* e, Channel* channel) {
SWEEP.enabled = SWEEP.period || SWEEP.shift;
SWEEP.frequency = channel->frequency;
SWEEP.timer = SWEEP.period ? SWEEP.period : SWEEP_MAX_PERIOD;
SWEEP.calculated_subtract = FALSE;
if (UNLIKELY(SWEEP.shift &&
calculate_sweep_frequency(e) > SOUND_MAX_FREQUENCY)) {
channel->status = FALSE;
HOOK0(trigger_nr14_sweep_overflow_v);
} else {
HOOK(trigger_nr14_info_i, SWEEP.frequency);
}
}
static void write_wave_period(Emulator* e, Channel* channel) {
WAVE.period = ((SOUND_MAX_FREQUENCY + 1) - channel->frequency) * 2;
HOOK(write_wave_period_info_iii, channel->frequency, WAVE.ticks, WAVE.period);
}
static void write_square_wave_period(Emulator* e, Channel* channel,
SquareWave* square) {
square->period = ((SOUND_MAX_FREQUENCY + 1) - channel->frequency) * 4;
HOOK(write_square_wave_period_info_iii, channel->frequency, square->ticks,
square->period);
}
static void write_noise_period(Emulator* e) {
static const u8 s_divisors[NOISE_DIVISOR_COUNT] = {8, 16, 32, 48,
64, 80, 96, 112};
u8 divisor = s_divisors[NOISE.divisor];
assert(NOISE.divisor < NOISE_DIVISOR_COUNT);
NOISE.period = divisor << NOISE.clock_shift;
HOOK(write_noise_period_info_iii, divisor, NOISE.clock_shift, NOISE.period);
}
static void write_apu(Emulator* e, MaskedAddress addr, u8 value) {
if (e->config.log_apu_writes || !APU.initialized) {
if (e->apu_log.write_count < MAX_APU_LOG_FRAME_WRITES) {
ApuWrite* write = &e->apu_log.writes[e->apu_log.write_count++];
write->addr = addr;
write->value = value;
}
}
if (!APU.enabled) {
if (!IS_CGB && (addr == APU_NR11_ADDR || addr == APU_NR21_ADDR ||
addr == APU_NR31_ADDR || addr == APU_NR41_ADDR)) {
/* DMG allows writes to the length counters when power is disabled. */
} else if (addr == APU_NR52_ADDR) {
/* Always can write to NR52; it's necessary to re-enable power to APU. */
} else {
/* Ignore all other writes. */
HOOK(write_apu_disabled_asb, addr, get_apu_reg_string(addr), value);
return;
}
}
if (APU.initialized) {
apu_synchronize(e);
}
HOOK(write_apu_asb, addr, get_apu_reg_string(addr), value);
switch (addr) {
case APU_NR10_ADDR: {
SweepDirection old_direction = SWEEP.direction;
SWEEP.period = UNPACK(value, NR10_SWEEP_PERIOD);
SWEEP.direction = UNPACK(value, NR10_SWEEP_DIRECTION);
SWEEP.shift = UNPACK(value, NR10_SWEEP_SHIFT);
if (old_direction == SWEEP_DIRECTION_SUBTRACTION &&
SWEEP.direction == SWEEP_DIRECTION_ADDITION &&
SWEEP.calculated_subtract) {
CHANNEL1.status = FALSE;
}
break;
}
case APU_NR11_ADDR:
write_nrx1_reg(e, &CHANNEL1, addr, value);
break;
case APU_NR12_ADDR:
write_nrx2_reg(e, &CHANNEL1, addr, value);
break;
case APU_NR13_ADDR:
write_nrx3_reg(e, &CHANNEL1, value);
write_square_wave_period(e, &CHANNEL1, &CHANNEL1.square_wave);
break;
case APU_NR14_ADDR: {
Bool trigger = write_nrx4_reg(e, &CHANNEL1, addr, value, NRX1_MAX_LENGTH);
write_square_wave_period(e, &CHANNEL1, &CHANNEL1.square_wave);
if (trigger) {
trigger_nrx4_envelope(e, &CHANNEL1.envelope, addr);
trigger_nr14_reg(e, &CHANNEL1);
CHANNEL1.square_wave.ticks = CHANNEL1.square_wave.period;
}
break;
}
case APU_NR21_ADDR:
write_nrx1_reg(e, &CHANNEL2, addr, value);
break;
case APU_NR22_ADDR:
write_nrx2_reg(e, &CHANNEL2, addr, value);
break;
case APU_NR23_ADDR:
write_nrx3_reg(e, &CHANNEL2, value);
write_square_wave_period(e, &CHANNEL2, &CHANNEL2.square_wave);
break;
case APU_NR24_ADDR: {
Bool trigger = write_nrx4_reg(e, &CHANNEL2, addr, value, NRX1_MAX_LENGTH);
write_square_wave_period(e, &CHANNEL2, &CHANNEL2.square_wave);
if (trigger) {
trigger_nrx4_envelope(e, &CHANNEL2.envelope, addr);
CHANNEL2.square_wave.ticks = CHANNEL2.square_wave.period;
}
break;
}
case APU_NR30_ADDR:
CHANNEL3.dac_enabled = UNPACK(value, NR30_DAC_ENABLED);
if (!CHANNEL3.dac_enabled) {
CHANNEL3.status = FALSE;
WAVE.playing = FALSE;
}
break;
case APU_NR31_ADDR:
CHANNEL3.length = NR31_MAX_LENGTH - value;
break;
case APU_NR32_ADDR:
WAVE.volume = UNPACK(value, NR32_SELECT_WAVE_VOLUME);
assert(WAVE.volume < WAVE_VOLUME_COUNT);
WAVE.volume_shift = s_wave_volume_shift[WAVE.volume];
break;
case APU_NR33_ADDR:
write_nrx3_reg(e, &CHANNEL3, value);
write_wave_period(e, &CHANNEL3);
break;
case APU_NR34_ADDR: {
Bool trigger = write_nrx4_reg(e, &CHANNEL3, addr, value, NR31_MAX_LENGTH);
write_wave_period(e, &CHANNEL3);
if (trigger) {
if (!IS_CGB && WAVE.playing) {
/* Triggering the wave channel while it is already playing will
* corrupt the wave RAM on DMG. */
if (WAVE.ticks == WAVE_TRIGGER_CORRUPTION_OFFSET_TICKS) {
assert(WAVE.position < 32);
u8 position = (WAVE.position + 1) & 31;
u8 byte = WAVE.ram[position >> 1];
switch (position >> 3) {
case 0:
WAVE.ram[0] = byte;
break;
case 1:
case 2:
case 3:
memcpy(&WAVE.ram[0], &WAVE.ram[(position >> 1) & 12], 4);
break;
}
HOOK(corrupt_wave_ram_i, position);
}
}
WAVE.position = 0;
WAVE.ticks = WAVE.period + WAVE_TRIGGER_DELAY_TICKS;
WAVE.playing = TRUE;
}
break;
}
case APU_NR41_ADDR:
write_nrx1_reg(e, &CHANNEL4, addr, value);
break;
case APU_NR42_ADDR:
write_nrx2_reg(e, &CHANNEL4, addr, value);
break;
case APU_NR43_ADDR: {
NOISE.clock_shift = UNPACK(value, NR43_CLOCK_SHIFT);
NOISE.lfsr_width = UNPACK(value, NR43_LFSR_WIDTH);
NOISE.divisor = UNPACK(value, NR43_DIVISOR);
write_noise_period(e);
break;
}
case APU_NR44_ADDR: {
Bool trigger = write_nrx4_reg(e, &CHANNEL4, addr, value, NRX1_MAX_LENGTH);
if (trigger) {
write_noise_period(e);
trigger_nrx4_envelope(e, &CHANNEL4.envelope, addr);
NOISE.lfsr = 0x7fff;
NOISE.sample = 1;
NOISE.ticks = NOISE.period;
}
break;
}
case APU_NR50_ADDR:
APU.so_output[VIN][1] = UNPACK(value, NR50_VIN_SO2);
APU.so_volume[1] = UNPACK(value, NR50_SO2_VOLUME);
APU.so_output[VIN][0] = UNPACK(value, NR50_VIN_SO1);
APU.so_volume[0] = UNPACK(value, NR50_SO1_VOLUME);
break;
case APU_NR51_ADDR:
APU.so_output[SOUND4][1] = UNPACK(value, NR51_SOUND4_SO2);
APU.so_output[SOUND3][1] = UNPACK(value, NR51_SOUND3_SO2);
APU.so_output[SOUND2][1] = UNPACK(value, NR51_SOUND2_SO2);
APU.so_output[SOUND1][1] = UNPACK(value, NR51_SOUND1_SO2);
APU.so_output[SOUND4][0] = UNPACK(value, NR51_SOUND4_SO1);
APU.so_output[SOUND3][0] = UNPACK(value, NR51_SOUND3_SO1);
APU.so_output[SOUND2][0] = UNPACK(value, NR51_SOUND2_SO1);
APU.so_output[SOUND1][0] = UNPACK(value, NR51_SOUND1_SO1);
break;
case APU_NR52_ADDR: {
Bool was_enabled = APU.enabled;
Bool is_enabled = UNPACK(value, NR52_ALL_SOUND_ENABLED);
if (was_enabled && !is_enabled) {
HOOK0(apu_power_down_v);
int i;
for (i = 0; i < APU_REG_COUNT; ++i) {
if (i != APU_NR52_ADDR) {
write_apu(e, i, 0);
}
}
} else if (!was_enabled && is_enabled) {
HOOK0(apu_power_up_v);
APU.frame = 7;
}
APU.enabled = is_enabled;
break;
}
}
}
static void write_wave_ram(Emulator* e, MaskedAddress addr, u8 value) {
apu_synchronize(e);
if (CHANNEL3.status) {
/* If the wave channel is playing, the byte is written to the sample
* position. On DMG, this is only allowed if the write occurs exactly when
* it is being accessed by the Wave channel. */
if (UNLIKELY(IS_CGB || TICKS == WAVE.sample_time)) {
WAVE.ram[WAVE.position >> 1] = value;
HOOK(write_wave_ram_while_playing_ab, addr, value);
}
} else {
WAVE.ram[addr] = value;
HOOK(write_wave_ram_ab, addr, value);
}
}
static void write_u8_pair(Emulator* e, MemoryTypeAddressPair pair, u8 value) {
switch (pair.type) {
case MEMORY_MAP_ROM0:
e->memory_map.write_rom(e, pair.addr, value);
break;
case MEMORY_MAP_ROM1:
e->memory_map.write_rom(e, pair.addr + 0x4000, value);
break;
case MEMORY_MAP_VRAM:
write_vram(e, pair.addr, value);
break;
case MEMORY_MAP_EXT_RAM:
e->memory_map.write_ext_ram(e, pair.addr, value);
break;
case MEMORY_MAP_WORK_RAM0:
WRAM.data[pair.addr] = value;
break;
case MEMORY_MAP_WORK_RAM1:
WRAM.data[WRAM.offset + pair.addr] = value;
break;
case MEMORY_MAP_OAM:
write_oam(e, pair.addr, value);
break;
case MEMORY_MAP_UNUSED:
break;
case MEMORY_MAP_IO:
write_io(e, pair.addr, value);
break;
case MEMORY_MAP_APU:
write_apu(e, pair.addr, value);
break;
case MEMORY_MAP_WAVE_RAM:
write_wave_ram(e, pair.addr, value);
break;
case MEMORY_MAP_HIGH_RAM:
HRAM[pair.addr] = value;
break;
}
}
static void write_u8_raw(Emulator* e, Address addr, u8 value) {
write_u8_pair(e, map_address(addr), value);
}
static void write_u8(Emulator* e, Address addr, u8 value) {
dma_synchronize(e);
if (UNLIKELY(!is_dma_access_ok(e, addr))) {
HOOK(write_during_dma_ab, addr, value);
return;
}
write_u8_pair(e, map_address(addr), value);
}
static void do_ppu_mode2(Emulator* e) {
dma_synchronize(e);
if (!LCDC.obj_display || e->config.disable_obj) {
return;
}
int line_obj_count = 0;
int i;
u8 obj_height = s_obj_size_to_height[LCDC.obj_size];
u8 y = PPU.line_y;
for (i = 0; i < OBJ_COUNT; ++i) {
/* Put the visible sprites into line_obj. Insert them so sprites with
* smaller X-coordinates are earlier, but only on DMG. On CGB, they are
* always ordered by obj index. */
Obj* o = &OAM[i];
u8 rel_y = y - o->y;
if (rel_y < obj_height) {
int j = line_obj_count;
if (!IS_CGB) {
while (j > 0 && o->x < PPU.line_obj[j - 1].x) {
PPU.line_obj[j] = PPU.line_obj[j - 1];
j--;
}
}
PPU.line_obj[j] = *o;
if (++line_obj_count == OBJ_PER_LINE_COUNT) {
break;
}
}
}
PPU.line_obj_count = line_obj_count;
}
static u32 mode3_tick_count(Emulator* e) {
s32 buckets[SCREEN_WIDTH / 8 + 2];
ZERO_MEMORY(buckets);
u8 scx_fine = PPU.scx & 7;
u32 ticks = PPU_MODE3_MIN_TICKS + scx_fine;
Bool has_zero = FALSE;
int i;
for (i = 0; i < PPU.line_obj_count; ++i) {
Obj* o = &PPU.line_obj[i];
u8 x = o->x + OBJ_X_OFFSET;
if (x >= SCREEN_WIDTH + OBJ_X_OFFSET) {
continue;
}
if (!has_zero && x == 0) {
has_zero = TRUE;
ticks += scx_fine;
}
x += scx_fine;
int bucket = x >> 3;
buckets[bucket] = MAX(buckets[bucket], 5 - (x & 7));
ticks += 6;
}
for (i = 0; i < (int)ARRAY_SIZE(buckets); ++i) {
ticks += buckets[i];
}
return ticks;
}
static void ppu_mode3_synchronize(Emulator* e) {
u8 x = PPU.render_x;
const u8 y = PPU.line_y;
if (STAT.mode != PPU_MODE_MODE3 || x >= SCREEN_WIDTH) return;
Bool display_bg = (IS_CGB || LCDC.bg_display) && !e->config.disable_bg;
const Bool display_obj = LCDC.obj_display && !e->config.disable_obj;
Bool rendering_window = PPU.rendering_window;
int window_counter = rendering_window ? 0 : 255;
if (!rendering_window && LCDC.window_display && !e->config.disable_window &&
PPU.wx <= WINDOW_MAX_X && y >= PPU.wy) {
window_counter = MAX(0, PPU.wx - (x + WINDOW_X_OFFSET));
}
const TileDataSelect data_select = LCDC.bg_tile_data_select;
u8 mx = PPU.scx + x;
u8 my = PPU.scy + y;
u16 map_base = map_select_to_address(LCDC.bg_tile_map_select) |
((my >> 3) * TILE_MAP_WIDTH);
RGBA* pixel;
if (SGB.mask != SGB_MASK_CANCEL) {
static RGBA s_dummy_frame_buffer_line[SCREEN_WIDTH];
pixel = s_dummy_frame_buffer_line;
} else {
pixel = &e->frame_buffer[y * SCREEN_WIDTH + x];
}
/* Cache map_addr info. */
u16 map_addr = 0;
PaletteRGBA* pal = NULL;
u8 lo = 0, hi = 0;
Bool priority = FALSE;
int i;
for (; PPU.mode3_render_ticks < TICKS && x < SCREEN_WIDTH;
PPU.mode3_render_ticks += CPU_TICK, pixel += 4, x += 4) {
Bool bg_is_zero[4] = {TRUE, TRUE, TRUE, TRUE},
bg_priority[4] = {FALSE, FALSE, FALSE, FALSE};
for (i = 0; i < 4; ++i, ++mx) {
if (UNLIKELY(window_counter-- == 0)) {
PPU.rendering_window = rendering_window = display_bg = TRUE;
mx = x + i + WINDOW_X_OFFSET - PPU.wx;
my = PPU.win_y;
map_base = map_select_to_address(LCDC.window_tile_map_select) |
((my >> 3) * TILE_MAP_WIDTH);
map_addr = 0;
}
if (display_bg) {
u16 new_map_addr = map_base | (mx >> 3);
if (map_addr == new_map_addr) {
lo <<= 1;
hi <<= 1;
} else {
map_addr = new_map_addr;
u16 tile_index = VRAM.data[map_addr];
u8 my7 = my & 7;
if (data_select == TILE_DATA_8800_97FF) {
tile_index = 256 + (s8)tile_index;
}
if (IS_CGB) {
u8 attr = VRAM.data[0x2000 + map_addr];
pal = &PPU.bgcp.palettes[attr & 0x7];
if (attr & 0x08) { tile_index += 0x200; }
if (attr & 0x40) { my7 = 7 - my7; }
priority = (attr & 0x80) != 0;
u16 tile_addr = (tile_index * TILE_HEIGHT + my7) * TILE_ROW_BYTES;
lo = VRAM.data[tile_addr];
hi = VRAM.data[tile_addr + 1];
if (attr & 0x20) {
lo = reverse_bits_u8(lo);
hi = reverse_bits_u8(hi);
}
} else {
if (IS_SGB) {
int idx = (y >> 3) * (SCREEN_WIDTH >> 3) + (x >> 3);
u8 palidx = (SGB.attr_map[idx >> 2] >> (2 * (3 - (idx & 3)))) & 3;
pal = &e->sgb_pal[palidx];
} else {
pal = &e->pal[PALETTE_TYPE_BGP];
}
priority = FALSE;
u16 tile_addr = (tile_index * TILE_HEIGHT + my7) * TILE_ROW_BYTES;
lo = VRAM.data[tile_addr];
hi = VRAM.data[tile_addr + 1];
}
u8 shift = mx & 7;
lo <<= shift;
hi <<= shift;
}
u8 palette_index = ((hi >> 6) & 2) | (lo >> 7);
pixel[i] = pal->color[palette_index];
bg_is_zero[i] = palette_index == 0;
bg_priority[i] = priority;
} else {
if (IS_CGB) {
pixel[i] = PPU.bgcp.palettes[0].color[0];
} else if (IS_SGB) {
pixel[i] = e->sgb_pal[0].color[0];
} else {
pixel[i] = e->color_to_rgba[0].color[0];
}
}
}
/* LCDC bit 0 works differently on cgb; when it's cleared OBJ will always
* have priority over bg and window. */
if (IS_CGB && !LCDC.bg_display) {
memset(&bg_is_zero, TRUE, sizeof(bg_is_zero));
memset(&bg_priority, FALSE, sizeof(bg_priority));
}
if (display_obj) {
u8 obj_height = s_obj_size_to_height[LCDC.obj_size];
int n;
for (n = PPU.line_obj_count - 1; n >= 0; --n) {
Obj* o = &PPU.line_obj[n];
/* Does [x, x + 4) intersect [o->x, o->x + 8)? Note that the sums must
* wrap at 256 (i.e. arithmetic is 8-bit). */
s8 ox_start = o->x - x;
s8 ox_end = ox_start + 7; /* ox_end is inclusive. */
u8 oy = y - o->y;
if (((u8)ox_start >= 4 && (u8)ox_end >= 8) || oy >= obj_height) {
continue;
}
if (o->yflip) {
oy = obj_height - 1 - oy;
}
u16 tile_index = o->tile;
if (obj_height == 16) {
if (oy < 8) {
/* Top tile of 8x16 sprite. */
tile_index &= 0xfe;
} else {
/* Bottom tile of 8x16 sprite. */
tile_index |= 0x01;
oy -= 8;
}
}
PaletteRGBA* pal = NULL;
if (IS_CGB) {
pal = &PPU.obcp.palettes[o->cgb_palette & 0x7];
if (o->bank) { tile_index += 0x200; }
} else {
pal = &e->pal[o->palette + 1];
}
u16 tile_addr = (tile_index * TILE_HEIGHT + (oy & 7)) * TILE_ROW_BYTES;
u8 lo = VRAM.data[tile_addr];
u8 hi = VRAM.data[tile_addr + 1];
if (!o->xflip) {
lo = reverse_bits_u8(lo);
hi = reverse_bits_u8(hi);
}
int tile_data_offset = MAX(0, -ox_start);
assert(tile_data_offset >= 0 && tile_data_offset < 8);
lo >>= tile_data_offset;
hi >>= tile_data_offset;
int start = MAX(0, ox_start);
assert(start >= 0 && start < 4);
int end = MIN(3, ox_end); /* end is inclusive. */
assert(end >= 0 && end < 4);
for (i = start; i <= end; ++i, lo >>= 1, hi >>= 1) {
u8 palette_index = ((hi & 1) << 1) | (lo & 1);
if (palette_index != 0 && (!bg_priority[i] || bg_is_zero[i]) &&
(o->priority == OBJ_PRIORITY_ABOVE_BG || bg_is_zero[i])) {
pixel[i] = pal->color[palette_index];
}
}
}
}
}
PPU.render_x = x;
}
static void ppu_synchronize(Emulator* e) {
assert(IS_ALIGNED(PPU.sync_ticks, CPU_TICK));
Ticks aligned_ticks = ALIGN_DOWN(TICKS, CPU_TICK);
if (aligned_ticks > PPU.sync_ticks) {
Ticks delta_ticks = aligned_ticks - PPU.sync_ticks;
if (LCDC.display) {
for (; delta_ticks > 0; delta_ticks -= CPU_TICK) {
INTR.if_ |= (INTR.new_if & (IF_VBLANK | IF_STAT));
STAT.mode2.trigger = FALSE;
STAT.y_compare.trigger = FALSE;
STAT.ly_eq_lyc = STAT.new_ly_eq_lyc;
PPU.last_ly = PPU.ly;
PPU.state_ticks -= CPU_TICK;
if (LIKELY(PPU.state_ticks != 0)) {
continue;
}
Ticks ticks = aligned_ticks - delta_ticks;
switch (PPU.state) {
case PPU_STATE_HBLANK:
case PPU_STATE_VBLANK_PLUS_4:
PPU.line_y++;
PPU.ly++;
PPU.line_start_ticks = ticks;
check_ly_eq_lyc(e, FALSE);
PPU.state_ticks = CPU_TICK;
if (PPU.state == PPU_STATE_HBLANK) {
STAT.mode2.trigger = TRUE;
if (PPU.ly == SCREEN_HEIGHT) {
PPU.state = PPU_STATE_VBLANK;
STAT.trigger_mode = PPU_MODE_VBLANK;
PPU.frame++;
INTR.new_if |= IF_VBLANK;
if (LIKELY(PPU.display_delay_frames == 0)) {
e->state.event |= EMULATOR_EVENT_NEW_FRAME;
} else {
PPU.display_delay_frames--;
}
} else {
PPU.state = PPU_STATE_HBLANK_PLUS_4;
STAT.trigger_mode = PPU_MODE_MODE2;
if (PPU.rendering_window) {
PPU.win_y++;
}
if (UNLIKELY(HDMA.mode == HDMA_TRANSFER_MODE_HDMA &&
(HDMA.blocks & 0x80) == 0)) {
HDMA.state = DMA_ACTIVE;
}
}
} else {
assert(PPU.state == PPU_STATE_VBLANK_PLUS_4);
if (PPU.ly == SCREEN_HEIGHT_WITH_VBLANK - 1) {
PPU.state = PPU_STATE_VBLANK_LY_0;
} else {
PPU.state_ticks = PPU_LINE_TICKS;
}
}
check_stat(e);
break;
case PPU_STATE_HBLANK_PLUS_4:
PPU.state = PPU_STATE_MODE2;
PPU.state_ticks = PPU_MODE2_TICKS;
STAT.mode = PPU_MODE_MODE2;
do_ppu_mode2(e);
break;
case PPU_STATE_VBLANK:
PPU.state = PPU_STATE_VBLANK_PLUS_4;
PPU.state_ticks = PPU_LINE_TICKS - CPU_TICK;
STAT.mode = PPU_MODE_VBLANK;
check_stat(e);
break;
case PPU_STATE_VBLANK_LY_0:
PPU.state = PPU_STATE_VBLANK_LY_0_PLUS_4;
PPU.state_ticks = CPU_TICK;
PPU.ly = 0;
break;
case PPU_STATE_VBLANK_LY_0_PLUS_4:
PPU.state = PPU_STATE_VBLANK_LINE_Y_0;
PPU.state_ticks = PPU_LINE_TICKS - CPU_TICK - CPU_TICK;
check_ly_eq_lyc(e, FALSE);
check_stat(e);
break;
case PPU_STATE_VBLANK_LINE_Y_0:
PPU.state = PPU_STATE_HBLANK_PLUS_4;
PPU.state_ticks = CPU_TICK;
PPU.line_start_ticks = ticks;
PPU.line_y = 0;
PPU.win_y = 0;
STAT.mode2.trigger = TRUE;
STAT.mode = PPU_MODE_HBLANK;
STAT.trigger_mode = PPU_MODE_MODE2;
check_stat(e);
break;
case PPU_STATE_LCD_ON_MODE2:
case PPU_STATE_MODE2:
PPU.state_ticks = mode3_tick_count(e);
if (PPU.state == PPU_STATE_LCD_ON_MODE2 ||
(PPU.state_ticks & 3) != 0) {
PPU.state = PPU_STATE_MODE3;
} else {
PPU.state = PPU_STATE_MODE3_EARLY_TRIGGER;
PPU.state_ticks--;
}
PPU.state_ticks &= ~3;
STAT.mode = STAT.trigger_mode = PPU_MODE_MODE3;
PPU.mode3_render_ticks = ticks;
PPU.render_x = 0;
PPU.rendering_window = FALSE;
check_stat(e);
break;
case PPU_STATE_MODE3_EARLY_TRIGGER:
PPU.state = PPU_STATE_MODE3_COMMON;
PPU.state_ticks = CPU_TICK;
STAT.trigger_mode = PPU_MODE_HBLANK;
check_stat(e);
break;
case PPU_STATE_MODE3:
STAT.trigger_mode = PPU_MODE_HBLANK;
/* fallthrough */
case PPU_STATE_MODE3_COMMON:
ppu_mode3_synchronize(e);
PPU.state = PPU_STATE_HBLANK;
PPU.state_ticks = PPU_LINE_TICKS + PPU.line_start_ticks - ticks;
STAT.mode = PPU_MODE_HBLANK;
check_stat(e);
break;
case PPU_STATE_COUNT:
assert(0);
break;
}
PPU.sync_ticks = ticks + CPU_TICK;
calculate_next_ppu_intr(e);
}
}
PPU.sync_ticks = aligned_ticks;
}
}
static void calculate_next_ppu_intr(Emulator* e) {
if (LCDC.display) {
/* TODO: Looser bounds on sync points. This syncs at every state
* transition, even though we often won't need to sync that often. */
PPU.next_intr_ticks = PPU.sync_ticks + PPU.state_ticks;
} else {
PPU.next_intr_ticks = INVALID_TICKS;
}
calculate_next_intr(e);
}
static void update_sweep(Emulator* e) {
if (!(CHANNEL1.status && SWEEP.enabled)) {
return;
}
u8 period = SWEEP.period;
if (--SWEEP.timer == 0) {
if (period) {
SWEEP.timer = period;
u16 new_frequency = calculate_sweep_frequency(e);
if (new_frequency > SOUND_MAX_FREQUENCY) {
HOOK0(sweep_overflow_v);
CHANNEL1.status = FALSE;
} else {
if (SWEEP.shift) {
HOOK(sweep_update_frequency_i, new_frequency);
SWEEP.frequency = CHANNEL1.frequency = new_frequency;
write_square_wave_period(e, &CHANNEL1, &CHANNEL1.square_wave);
}
/* Perform another overflow check. */
if (UNLIKELY(calculate_sweep_frequency(e) > SOUND_MAX_FREQUENCY)) {
HOOK0(sweep_overflow_2nd_v);
CHANNEL1.status = FALSE;
}
}
} else {
SWEEP.timer = SWEEP_MAX_PERIOD;
}
}
}
static void update_lengths(Emulator* e) {
int i;
for (i = 0; i < APU_CHANNEL_COUNT; ++i) {
Channel* channel = &APU.channel[i];
if (channel->length_enabled && channel->length > 0) {
if (--channel->length == 0) {
channel->status = FALSE;
}
}
}
}
static void update_envelopes(Emulator* e) {
int i;
for (i = 0; i < APU_CHANNEL_COUNT; ++i) {
Envelope* envelope = &APU.channel[i].envelope;
if (envelope->period) {
if (envelope->automatic && --envelope->timer == 0) {
envelope->timer = envelope->period;
u8 delta = envelope->direction == ENVELOPE_ATTENUATE ? -1 : 1;
u8 volume = envelope->volume + delta;
if (volume < ENVELOPE_MAX_VOLUME) {
envelope->volume = volume;
} else {
envelope->automatic = FALSE;
}
}
} else {
envelope->timer = ENVELOPE_MAX_PERIOD;
}
}
}
/* Convert from 1-bit sample to 4-bit sample. */
#define CHANNELX_SAMPLE(channel, sample) \
(-(sample) & (channel)->envelope.volume)
static void update_square_wave(Channel* channel, u32 total_frames) {
static u8 duty[WAVE_DUTY_COUNT][DUTY_CYCLE_COUNT] =
{[WAVE_DUTY_12_5] = {0, 0, 0, 0, 0, 0, 0, 1},
[WAVE_DUTY_25] = {1, 0, 0, 0, 0, 0, 0, 1},
[WAVE_DUTY_50] = {1, 0, 0, 0, 0, 1, 1, 1},
[WAVE_DUTY_75] = {0, 1, 1, 1, 1, 1, 1, 0}};
SquareWave* square = &channel->square_wave;
if (channel->status) {
while (total_frames) {
u32 frames = square->ticks / APU_TICKS;
u8 sample = CHANNELX_SAMPLE(channel, square->sample);
if (frames <= total_frames) {
square->ticks = square->period;
square->position = (square->position + 1) % DUTY_CYCLE_COUNT;
square->sample = duty[square->duty][square->position];
} else {
frames = total_frames;
square->ticks -= frames * APU_TICKS;
}
channel->accumulator += sample * frames;
total_frames -= frames;
}
}
}
static void update_wave(Emulator* e, u32 apu_ticks, u32 total_frames) {
if (CHANNEL3.status) {
while (total_frames) {
u32 frames = WAVE.ticks / APU_TICKS;
/* Modulate 4-bit sample by wave volume. */
u8 sample = WAVE.sample_data >> WAVE.volume_shift;
if (frames <= total_frames) {
WAVE.position = (WAVE.position + 1) % WAVE_SAMPLE_COUNT;
WAVE.sample_time = apu_ticks + WAVE.ticks;
u8 byte = WAVE.ram[WAVE.position >> 1];
if ((WAVE.position & 1) == 0) {
WAVE.sample_data = byte >> 4; /* High nybble. */
} else {
WAVE.sample_data = byte & 0x0f; /* Low nybble. */
}
WAVE.ticks = WAVE.period;
HOOK(wave_update_position_iii, WAVE.position, WAVE.sample_data,
WAVE.sample_time);
} else {
frames = total_frames;
WAVE.ticks -= frames * APU_TICKS;
}
apu_ticks += frames * APU_TICKS;
CHANNEL3.accumulator += sample * frames;
total_frames -= frames;
}
}
}
static void update_noise(Emulator* e, u32 total_frames) {
if (CHANNEL4.status) {
while (total_frames) {
u32 frames = NOISE.ticks / APU_TICKS;
u8 sample = CHANNELX_SAMPLE(&CHANNEL4, NOISE.sample);
if (NOISE.clock_shift <= NOISE_MAX_CLOCK_SHIFT) {
if (frames <= total_frames) {
u16 bit = (NOISE.lfsr ^ (NOISE.lfsr >> 1)) & 1;
if (NOISE.lfsr_width == LFSR_WIDTH_7) {
NOISE.lfsr = ((NOISE.lfsr >> 1) & ~0x40) | (bit << 6);
} else {
NOISE.lfsr = ((NOISE.lfsr >> 1) & ~0x4000) | (bit << 14);
}
NOISE.sample = ~NOISE.lfsr & 1;
NOISE.ticks = NOISE.period;
} else {
frames = total_frames;
NOISE.ticks -= frames * APU_TICKS;
}
} else {
frames = total_frames;
}
CHANNEL4.accumulator += sample * frames;
total_frames -= frames;
}
}
}
static u32 get_gb_frames_until_next_resampled_frame(Emulator* e) {
u32 result = 0;
u32 counter = e->audio_buffer.freq_counter;
while (!VALUE_WRAPPED(counter, APU_TICKS_PER_SECOND)) {
counter += e->audio_buffer.frequency;
result++;
}
return result;
}
static void write_audio_frame(Emulator* e, u32 gb_frames) {
int i, j;
AudioBuffer* buffer = &e->audio_buffer;
buffer->divisor += gb_frames;
buffer->freq_counter += buffer->frequency * gb_frames;
if (VALUE_WRAPPED(buffer->freq_counter, APU_TICKS_PER_SECOND)) {
for (i = 0; i < SOUND_OUTPUT_COUNT; ++i) {
u32 accumulator = 0;
for (j = 0; j < APU_CHANNEL_COUNT; ++j) {
if (!e->config.disable_sound[j]) {
accumulator += APU.channel[j].accumulator * APU.so_output[j][i];
}
}
accumulator *= (APU.so_volume[i] + 1) * 16; /* 4bit -> 8bit samples. */
accumulator /= ((SOUND_OUTPUT_MAX_VOLUME + 1) * APU_CHANNEL_COUNT);
*buffer->position++ = accumulator / buffer->divisor;
}
for (j = 0; j < APU_CHANNEL_COUNT; ++j) {
APU.channel[j].accumulator = 0;
}
buffer->divisor = 0;
}
assert(buffer->position <= buffer->end);
}
static void apu_update_channels(Emulator* e, u32 total_frames) {
while (total_frames) {
u32 frames = get_gb_frames_until_next_resampled_frame(e);
frames = MIN(frames, total_frames);
update_square_wave(&CHANNEL1, frames);
update_square_wave(&CHANNEL2, frames);
update_wave(e, APU.sync_ticks, frames);
update_noise(e, frames);
write_audio_frame(e, frames);
APU.sync_ticks += frames * APU_TICKS;
total_frames -= frames;
}
}
static void apu_update(Emulator* e, u32 total_ticks) {
while (total_ticks) {
Ticks next_seq_ticks = NEXT_MODULO(APU.sync_ticks, FRAME_SEQUENCER_TICKS);
if (next_seq_ticks == FRAME_SEQUENCER_TICKS) {
APU.frame = (APU.frame + 1) % FRAME_SEQUENCER_COUNT;
switch (APU.frame) {
case 2: case 6: update_sweep(e); /* Fallthrough. */
case 0: case 4: update_lengths(e); break;
case 7: update_envelopes(e); break;
}
}
Ticks ticks = MIN(next_seq_ticks, total_ticks);
apu_update_channels(e, ticks / APU_TICKS);
total_ticks -= ticks;
}
}
static void intr_synchronize(Emulator* e) {
dma_synchronize(e);
serial_synchronize(e);
ppu_synchronize(e);
timer_synchronize(e);
}
static void apu_synchronize(Emulator* e) {
if (TICKS > APU.sync_ticks) {
u32 ticks = TICKS - APU.sync_ticks;
if (APU.enabled) {
apu_update(e, ticks);
assert(APU.sync_ticks == TICKS);
} else {
for (; ticks; ticks -= APU_TICKS) {
write_audio_frame(e, 1);
}
APU.sync_ticks = TICKS;
}
}
}
static void dma_synchronize(Emulator* e) {
if (UNLIKELY(DMA.state != DMA_INACTIVE)) {
if (TICKS > DMA.sync_ticks) {
Ticks delta_ticks = TICKS - DMA.sync_ticks;
DMA.sync_ticks = TICKS;
Ticks cpu_tick = e->state.cpu_tick;
for (; delta_ticks > 0; delta_ticks -= cpu_tick) {
if (DMA.tick_count < DMA_DELAY_TICKS) {
DMA.tick_count += CPU_TICK;
if (DMA.tick_count >= DMA_DELAY_TICKS) {
DMA.tick_count = DMA_DELAY_TICKS;
DMA.state = DMA_ACTIVE;
}
continue;
}
u8 addr_offset = (DMA.tick_count - DMA_DELAY_TICKS) >> 2;
assert(addr_offset < OAM_TRANSFER_SIZE);
u8 value =
read_u8_pair(e, map_address(DMA.source + addr_offset), FALSE);
write_oam_no_mode_check(e, addr_offset, value);
DMA.tick_count += CPU_TICK;
if (VALUE_WRAPPED(DMA.tick_count, DMA_TICKS)) {
DMA.state = DMA_INACTIVE;
break;
}
}
}
}
}
static void hdma_copy_byte(Emulator* e) {
MemoryTypeAddressPair source_pair = map_hdma_source_address(HDMA.source++);
u8 value;
if (UNLIKELY(source_pair.type == MEMORY_MAP_VRAM)) {
/* TODO(binji): According to TCAGBD this should read "two unknown bytes",
* then 0xff for the rest. */
value = INVALID_READ_BYTE;
} else {
value = read_u8_pair(e, source_pair, FALSE);
}
write_vram(e, HDMA.dest++ & ADDR_MASK_8K, value);
HDMA.block_bytes++;
if (VALUE_WRAPPED(HDMA.block_bytes, 16)) {
--HDMA.blocks;
if (HDMA.mode == HDMA_TRANSFER_MODE_GDMA) {
if (HDMA.blocks == 0xff) {
HDMA.state = DMA_INACTIVE;
}
} else {
HDMA.state = DMA_INACTIVE;
}
}
}
static void calculate_next_serial_intr(Emulator* e) {
if (!SERIAL.transferring || SERIAL.clock != SERIAL_CLOCK_INTERNAL) {
SERIAL.next_intr_ticks = INVALID_TICKS;
calculate_next_intr(e);
return;
}
/* Should only be called when receiving a new byte. */
assert(SERIAL.tick_count == 0);
assert(SERIAL.transferred_bits == 0);
SERIAL.next_intr_ticks =
SERIAL.sync_ticks +
SERIAL_TICKS * (CPU_SPEED.speed == SPEED_NORMAL ? 8 : 4);
calculate_next_intr(e);
}
static void serial_synchronize(Emulator* e) {
if (TICKS > SERIAL.sync_ticks) {
Ticks delta_ticks = TICKS - SERIAL.sync_ticks;
if (UNLIKELY(SERIAL.transferring &&
SERIAL.clock == SERIAL_CLOCK_INTERNAL)) {
Ticks cpu_tick = e->state.cpu_tick;
for (; delta_ticks > 0; delta_ticks -= cpu_tick) {
SERIAL.tick_count += cpu_tick;
if (VALUE_WRAPPED(SERIAL.tick_count, SERIAL_TICKS)) {
/* Since we're never connected to another device, always shift in
* 0xff. */
SERIAL.sb = (SERIAL.sb << 1) | 1;
SERIAL.transferred_bits++;
if (VALUE_WRAPPED(SERIAL.transferred_bits, 8)) {
SERIAL.transferring = 0;
INTR.new_if |= IF_SERIAL;
SERIAL.sync_ticks = TICKS - delta_ticks;
calculate_next_serial_intr(e);
}
} else if (UNLIKELY(SERIAL.tick_count == 0 &&
SERIAL.transferred_bits == 0)) {
INTR.if_ |= (INTR.new_if & IF_SERIAL);
}
}
}
SERIAL.sync_ticks = TICKS;
}
}
static void tick(Emulator* e) {
INTR.if_ = INTR.new_if;
TICKS += e->state.cpu_tick;
}
static u8 read_u8_tick(Emulator* e, Address addr) {
tick(e);
return read_u8(e, addr);
}
static u16 read_u16_tick(Emulator* e, Address addr) {
u8 lo = read_u8_tick(e, addr);
u8 hi = read_u8_tick(e, addr + 1);
return (hi << 8) | lo;
}
static void write_u8_tick(Emulator* e, Address addr, u8 value) {
tick(e);
write_u8(e, addr, value);
}
static void write_u16_tick(Emulator* e, Address addr, u16 value) {
write_u8_tick(e, addr + 1, value >> 8);
write_u8_tick(e, addr, (u8)value);
}
static u16 get_af_reg(Emulator* e) {
return (REG.A << 8) | PACK(REG.F.Z, CPU_FLAG_Z) | PACK(REG.F.N, CPU_FLAG_N) |
PACK(REG.F.H, CPU_FLAG_H) | PACK(REG.F.C, CPU_FLAG_C);
}
static void set_af_reg(Emulator* e, u16 af) {
REG.A = af >> 8;
REG.F.Z = UNPACK(af, CPU_FLAG_Z);
REG.F.N = UNPACK(af, CPU_FLAG_N);
REG.F.H = UNPACK(af, CPU_FLAG_H);
REG.F.C = UNPACK(af, CPU_FLAG_C);
}
#define TICK tick(e)
#define RA REG.A
#define RSP REG.SP
#define FZ REG.F.Z
#define FC REG.F.C
#define FH REG.F.H
#define FN REG.F.N
#define FZ_EQ0(X) FZ = (u8)(X) == 0
#define SHIFT_FLAGS FZ_EQ0(u); FN = FH = 0
#define MASK8(X) ((X) & 0xf)
#define MASK16(X) ((X) & 0xfff)
#define READ8(X) read_u8_tick(e, X)
#define READ16(X) read_u16_tick(e, X)
#define WRITE8(X, V) write_u8_tick(e, X, V)
#define WRITE16(X, V) write_u16_tick(e, X, V)
#define READ_N (new_pc += 1, READ8(REG.PC))
#define READ_NN (new_pc += 2, READ16(REG.PC))
#define READMR(MR) READ8(REG.MR)
#define WRITEMR(MR, V) WRITE8(REG.MR, V)
#define BASIC_OP_R(R, OP) u = REG.R; OP; REG.R = u
#define BASIC_OP_MR(MR, OP) u = READMR(MR); OP; WRITEMR(MR, u)
#define FC_ADD(X, Y) FC = ((X) + (Y) > 0xff)
#define FH_ADD(X, Y) FH = (MASK8(X) + MASK8(Y) > 0xf)
#define FCH_ADD(X, Y) FC_ADD(X, Y); FH_ADD(X, Y)
#define FC_ADD16(X, Y) FC = ((X) + (Y) > 0xffff)
#define FH_ADD16(X, Y) FH = (MASK16(X) + MASK16(Y) > 0xfff)
#define FCH_ADD16(X, Y) FC_ADD16(X, Y); FH_ADD16(X, Y)
#define ADD_FLAGS(X, Y) FZ_EQ0((X) + (Y)); FN = 0; FCH_ADD(X, Y)
#define ADD_FLAGS16(X, Y) FN = 0; FCH_ADD16(X, Y)
#define ADD_SP_FLAGS(Y) FZ = FN = 0; FCH_ADD((u8)RSP, (u8)(Y))
#define ADD_R(R) ADD_FLAGS(RA, REG.R); RA += REG.R
#define ADD_MR(MR) u = READMR(MR); ADD_FLAGS(RA, u); RA += u
#define ADD_N u = READ_N; ADD_FLAGS(RA, u); RA += u
#define ADD_HL_RR(RR) TICK; ADD_FLAGS16(REG.HL, REG.RR); REG.HL += REG.RR
#define ADD_SP_N s = (s8)READ_N; ADD_SP_FLAGS(s); RSP += s; TICK; TICK
#define FC_ADC(X, Y, C) FC = ((X) + (Y) + (C) > 0xff)
#define FH_ADC(X, Y, C) FH = (MASK8(X) + MASK8(Y) + C > 0xf)
#define FCH_ADC(X, Y, C) FC_ADC(X, Y, C); FH_ADC(X, Y, C)
#define ADC_FLAGS(X, Y, C) FZ_EQ0((X) + (Y) + (C)); FN = 0; FCH_ADC(X, Y, C)
#define ADC_R(R) u = REG.R; c = FC; ADC_FLAGS(RA, u, c); RA += u + c
#define ADC_MR(MR) u = READMR(MR); c = FC; ADC_FLAGS(RA, u, c); RA += u + c
#define ADC_N u = READ_N; c = FC; ADC_FLAGS(RA, u, c); RA += u + c
#define AND_FLAGS FZ_EQ0(RA); FH = 1; FN = FC = 0
#define AND_R(R) RA &= REG.R; AND_FLAGS
#define AND_MR(MR) RA &= READMR(MR); AND_FLAGS
#define AND_N RA &= READ_N; AND_FLAGS
#define BIT_FLAGS(BIT, X) FZ_EQ0((X) & (1 << (BIT))); FN = 0; FH = 1
#define BIT_R(BIT, R) u = REG.R; BIT_FLAGS(BIT, u)
#define BIT_MR(BIT, MR) u = READMR(MR); BIT_FLAGS(BIT, u)
#define CALL(X) TICK; RSP -= 2; WRITE16(RSP, new_pc); new_pc = X
#define CALL_NN u16 = READ_NN; CALL(u16)
#define CALL_F_NN(COND) u16 = READ_NN; if (COND) { CALL(u16); }
#define CCF FC ^= 1; FN = FH = 0
#define CP_FLAGS(X, Y) FZ_EQ0((X) - (Y)); FN = 1; FCH_SUB(X, Y)
#define CP_R(R) CP_FLAGS(RA, REG.R)
#define CP_N u = READ_N; CP_FLAGS(RA, u)
#define CP_MR(MR) u = READMR(MR); CP_FLAGS(RA, u)
#define CPL RA = ~RA; FN = FH = 1
#define DAA \
do { \
u = 0; \
if (FH || (!FN && (RA & 0xf) > 9)) { \
u = 6; \
} \
if (FC || (!FN && RA > 0x99)) { \
u |= 0x60; \
FC = 1; \
} \
RA += FN ? -u : u; \
FZ_EQ0(RA); \
FH = 0; \
} while (0)
#define DEC u--
#define DEC_FLAGS FZ_EQ0(u); FN = 1; FH = MASK8(u) == 0xf
#define DEC_R(R) BASIC_OP_R(R, DEC); DEC_FLAGS
#define DEC_RR(RR) REG.RR--; TICK
#define DEC_MR(MR) BASIC_OP_MR(MR, DEC); DEC_FLAGS
#define DI INTR.state = CPU_STATE_NORMAL; INTR.ime = FALSE;
#define EI INTR.state = CPU_STATE_ENABLE_IME;
#define HALT \
if (INTR.ime) { \
INTR.state = CPU_STATE_HALT; \
} else if (INTR.ie & INTR.new_if & IF_ALL) { \
INTR.state = CPU_STATE_HALT_BUG; \
} else { \
INTR.state = CPU_STATE_HALT_DI; \
}
#define INC u++
#define INC_FLAGS FZ_EQ0(u); FN = 0; FH = MASK8(u) == 0
#define INC_R(R) BASIC_OP_R(R, INC); INC_FLAGS
#define INC_RR(RR) REG.RR++; TICK
#define INC_MR(MR) BASIC_OP_MR(MR, INC); INC_FLAGS
#define JP_F_NN(COND) u16 = READ_NN; if (COND) { new_pc = u16; TICK; }
#define JP_RR(RR) new_pc = REG.RR
#define JP_NN new_pc = READ_NN; TICK
#define JR new_pc += s; TICK
#define JR_F_N(COND) s = READ_N; if (COND) { JR; }
#define JR_N s = READ_N; JR
#define LD_R_R(RD, RS) REG.RD = REG.RS
#define LD_R_N(R) REG.R = READ_N
#define LD_RR_RR(RRD, RRS) REG.RRD = REG.RRS; TICK
#define LD_RR_NN(RR) REG.RR = READ_NN
#define LD_R_MR(R, MR) REG.R = READMR(MR)
#define LD_R_MN(R) REG.R = READ8(READ_NN)
#define LD_MR_R(MR, R) WRITEMR(MR, REG.R)
#define LD_MR_N(MR) WRITEMR(MR, READ_N)
#define LD_MN_R(R) WRITE8(READ_NN, REG.R)
#define LD_MFF00_N_R(R) WRITE8(0xFF00 + READ_N, RA)
#define LD_MFF00_R_R(R1, R2) WRITE8(0xFF00 + REG.R1, REG.R2)
#define LD_R_MFF00_N(R) REG.R = READ8(0xFF00 + READ_N)
#define LD_R_MFF00_R(R1, R2) REG.R1 = READ8(0xFF00 + REG.R2)
#define LD_MNN_SP u16 = READ_NN; WRITE16(u16, RSP)
#define LD_HL_SP_N s = (s8)READ_N; ADD_SP_FLAGS(s); REG.HL = RSP + s; TICK
#define OR_FLAGS FZ_EQ0(RA); FN = FH = FC = 0
#define OR_R(R) RA |= REG.R; OR_FLAGS
#define OR_MR(MR) RA |= READMR(MR); OR_FLAGS
#define OR_N RA |= READ_N; OR_FLAGS
#define POP_RR(RR) REG.RR = READ16(RSP); RSP += 2
#define POP_AF set_af_reg(e, READ16(RSP)); RSP += 2
#define PUSH_RR(RR) TICK; RSP -= 2; WRITE16(RSP, REG.RR)
#define PUSH_AF TICK; RSP -= 2; WRITE16(RSP, get_af_reg(e))
#define RES(BIT) u &= ~(1 << (BIT))
#define RES_R(BIT, R) BASIC_OP_R(R, RES(BIT))
#define RES_MR(BIT, MR) BASIC_OP_MR(MR, RES(BIT))
#define RET new_pc = READ16(RSP); RSP += 2; TICK
#define RET_F(COND) TICK; if (COND) { RET; }
#define RETI INTR.state = CPU_STATE_NORMAL; INTR.ime = TRUE; RET
#define RL c = (u >> 7) & 1; u = (u << 1) | FC; FC = c
#define RLA BASIC_OP_R(A, RL); FZ = FN = FH = 0
#define RL_R(R) BASIC_OP_R(R, RL); SHIFT_FLAGS
#define RL_MR(MR) BASIC_OP_MR(MR, RL); SHIFT_FLAGS
#define RLC c = (u >> 7) & 1; u = (u << 1) | c; FC = c
#define RLCA BASIC_OP_R(A, RLC); FZ = FN = FH = 0
#define RLC_R(R) BASIC_OP_R(R, RLC); SHIFT_FLAGS
#define RLC_MR(MR) BASIC_OP_MR(MR, RLC); SHIFT_FLAGS
#define RR c = u & 1; u = (FC << 7) | (u >> 1); FC = c
#define RRA BASIC_OP_R(A, RR); FZ = FN = FH = 0
#define RR_R(R) BASIC_OP_R(R, RR); SHIFT_FLAGS
#define RR_MR(MR) BASIC_OP_MR(MR, RR); SHIFT_FLAGS
#define RRC c = u & 1; u = (c << 7) | (u >> 1); FC = c
#define RRCA BASIC_OP_R(A, RRC); FZ = FN = FH = 0
#define RRC_R(R) BASIC_OP_R(R, RRC); SHIFT_FLAGS
#define RRC_MR(MR) BASIC_OP_MR(MR, RRC); SHIFT_FLAGS
#define SCF FC = 1; FN = FH = 0
#define SET(BIT) u |= (1 << BIT)
#define SET_R(BIT, R) BASIC_OP_R(R, SET(BIT))
#define SET_MR(BIT, MR) BASIC_OP_MR(MR, SET(BIT))
#define SLA FC = (u >> 7) & 1; u <<= 1
#define SLA_R(R) BASIC_OP_R(R, SLA); SHIFT_FLAGS
#define SLA_MR(MR) BASIC_OP_MR(MR, SLA); SHIFT_FLAGS
#define SRA FC = u & 1; u = (s8)u >> 1
#define SRA_R(R) BASIC_OP_R(R, SRA); SHIFT_FLAGS
#define SRA_MR(MR) BASIC_OP_MR(MR, SRA); SHIFT_FLAGS
#define SRL FC = u & 1; u >>= 1
#define SRL_R(R) BASIC_OP_R(R, SRL); SHIFT_FLAGS
#define SRL_MR(MR) BASIC_OP_MR(MR, SRL); SHIFT_FLAGS
#define STOP INTR.state = CPU_STATE_STOP;
#define FC_SUB(X, Y) FC = ((int)(X) - (int)(Y) < 0)
#define FH_SUB(X, Y) FH = ((int)MASK8(X) - (int)MASK8(Y) < 0)
#define FCH_SUB(X, Y) FC_SUB(X, Y); FH_SUB(X, Y)
#define SUB_FLAGS(X, Y) FZ_EQ0((X) - (Y)); FN = 1; FCH_SUB(X, Y)
#define SUB_R(R) SUB_FLAGS(RA, REG.R); RA -= REG.R
#define SUB_MR(MR) u = READMR(MR); SUB_FLAGS(RA, u); RA -= u
#define SUB_N u = READ_N; SUB_FLAGS(RA, u); RA -= u
#define FC_SBC(X, Y, C) FC = ((int)(X) - (int)(Y) - (int)(C) < 0)
#define FH_SBC(X, Y, C) FH = ((int)MASK8(X) - (int)MASK8(Y) - (int)C < 0)
#define FCH_SBC(X, Y, C) FC_SBC(X, Y, C); FH_SBC(X, Y, C)
#define SBC_FLAGS(X, Y, C) FZ_EQ0((X) - (Y) - (C)); FN = 1; FCH_SBC(X, Y, C)
#define SBC_R(R) u = REG.R; c = FC; SBC_FLAGS(RA, u, c); RA -= u + c
#define SBC_MR(MR) u = READMR(MR); c = FC; SBC_FLAGS(RA, u, c); RA -= u + c
#define SBC_N u = READ_N; c = FC; SBC_FLAGS(RA, u, c); RA -= u + c
#define SWAP u = (u << 4) | (u >> 4)
#define SWAP_FLAGS FZ_EQ0(u); FN = FH = FC = 0
#define SWAP_R(R) BASIC_OP_R(R, SWAP); SWAP_FLAGS
#define SWAP_MR(MR) BASIC_OP_MR(MR, SWAP); SWAP_FLAGS
#define XOR_FLAGS FZ_EQ0(RA); FN = FH = FC = 0
#define XOR_R(R) RA ^= REG.R; XOR_FLAGS
#define XOR_MR(MR) RA ^= READMR(MR); XOR_FLAGS
#define XOR_N RA ^= READ_N; XOR_FLAGS
static void dispatch_interrupt(Emulator* e) {
Bool was_halt = INTR.state >= CPU_STATE_HALT;
if (!(INTR.ime || was_halt)) {
return;
}
INTR.ime = FALSE;
INTR.state = CPU_STATE_NORMAL;
/* Write MSB of PC. */
RSP--; WRITE8(RSP, REG.PC >> 8);
/* Now check which interrupt to raise, after having written the MSB of PC.
* This behavior is needed to pass the ie_push mooneye-gb test. */
u8 interrupt = INTR.new_if & INTR.ie;
Bool delay = FALSE;
u8 mask = 0;
Address vector = 0;
if (interrupt & IF_VBLANK) {
HOOK(vblank_interrupt_i, PPU.frame);
vector = 0x40;
mask = IF_VBLANK;
} else if (interrupt & IF_STAT) {
HOOK(stat_interrupt_cccc, STAT.y_compare.irq ? 'Y' : '.',
STAT.mode2.irq ? 'O' : '.', STAT.vblank.irq ? 'V' : '.',
STAT.hblank.irq ? 'H' : '.');
vector = 0x48;
mask = IF_STAT;
} else if (interrupt & IF_TIMER) {
HOOK0(timer_interrupt_v);
vector = 0x50;
mask = IF_TIMER;
delay = was_halt;
} else if (interrupt & IF_SERIAL) {
HOOK0(serial_interrupt_v);
vector = 0x58;
mask = IF_SERIAL;
} else if (interrupt & IF_JOYPAD) {
HOOK0(joypad_interrupt_v);
vector = 0x60;
mask = IF_JOYPAD;
} else {
/* Interrupt was canceled. */
vector = 0;
mask = 0;
}
INTR.new_if &= ~mask;
/* Now write the LSB of PC. */
RSP--; WRITE8(RSP, REG.PC);
REG.PC = vector;
if (delay) {
tick(e);
}
tick(e);
tick(e);
}
static void execute_instruction(Emulator* e) {
u8 opcode = 0;
s8 s;
u8 u, c;
u16 u16;
Address new_pc;
if (UNLIKELY(TICKS >= e->state.next_intr_ticks)) {
if (TICKS >= TIMER.next_intr_ticks) {
timer_synchronize(e);
}
if (TICKS >= SERIAL.next_intr_ticks) {
serial_synchronize(e);
}
if (TICKS >= PPU.next_intr_ticks) {
ppu_synchronize(e);
}
}
Bool should_dispatch = FALSE;
if (LIKELY(INTR.state == CPU_STATE_NORMAL)) {
should_dispatch = INTR.ime && (INTR.new_if & INTR.ie) != 0;
opcode = read_u8_tick(e, REG.PC);
} else {
switch (INTR.state) {
case CPU_STATE_NORMAL:
assert(0);
case CPU_STATE_STOP:
should_dispatch = INTR.ime && (INTR.new_if & INTR.ie) != 0;
if (UNLIKELY(!should_dispatch)) {
// TODO(binji): proper timing of speed switching.
if (CPU_SPEED.switching) {
intr_synchronize(e);
CPU_SPEED.switching = FALSE;
CPU_SPEED.speed ^= 1;
INTR.state = CPU_STATE_NORMAL;
if (CPU_SPEED.speed == SPEED_NORMAL) {
e->state.cpu_tick = CPU_TICK;
HOOK(speed_switch_i, 1);
} else {
e->state.cpu_tick = CPU_2X_TICK;
HOOK(speed_switch_i, 2);
}
} else {
TICKS += CPU_TICK;
return;
}
}
opcode = read_u8_tick(e, REG.PC);
break;
case CPU_STATE_ENABLE_IME:
should_dispatch = INTR.ime && (INTR.new_if & INTR.ie) != 0;
INTR.ime = TRUE;
INTR.state = CPU_STATE_NORMAL;
opcode = read_u8_tick(e, REG.PC);
break;
case CPU_STATE_HALT_BUG:
/* When interrupts are disabled during a HALT, the following byte will
* be duplicated when decoding. */
should_dispatch = INTR.ime && (INTR.new_if & INTR.ie) != 0;
opcode = read_u8(e, REG.PC);
REG.PC--;
INTR.state = CPU_STATE_NORMAL;
break;
case CPU_STATE_HALT:
should_dispatch = (INTR.new_if & INTR.ie) != 0;
tick(e);
if (UNLIKELY(should_dispatch)) {
intr_synchronize(e);
dispatch_interrupt(e);
}
return;
case CPU_STATE_HALT_DI:
should_dispatch = (INTR.new_if & INTR.ie) != 0;
opcode = read_u8_tick(e, REG.PC);
if (UNLIKELY(should_dispatch)) {
HOOK0(interrupt_during_halt_di_v);
INTR.state = CPU_STATE_NORMAL;
should_dispatch = FALSE;
break;
}
return;
}
}
if (UNLIKELY(should_dispatch)) {
intr_synchronize(e);
dispatch_interrupt(e);
return;
}
#define REG_OPS(code, name) \
case code + 0: name##_R(B); break; \
case code + 1: name##_R(C); break; \
case code + 2: name##_R(D); break; \
case code + 3: name##_R(E); break; \
case code + 4: name##_R(H); break; \
case code + 5: name##_R(L); break; \
case code + 6: name##_MR(HL); break; \
case code + 7: name##_R(A); break;
#define REG_OPS_N(code, name, N) \
case code + 0: name##_R(N, B); break; \
case code + 1: name##_R(N, C); break; \
case code + 2: name##_R(N, D); break; \
case code + 3: name##_R(N, E); break; \
case code + 4: name##_R(N, H); break; \
case code + 5: name##_R(N, L); break; \
case code + 6: name##_MR(N, HL); break; \
case code + 7: name##_R(N, A); break;
#define LD_R_OPS(code, R) REG_OPS_N(code, LD_R, R)
HOOK(exec_op_ai, REG.PC, opcode);
new_pc = ++REG.PC;
switch (opcode) {
case 0x00: break;
case 0x01: LD_RR_NN(BC); break;
case 0x02: LD_MR_R(BC, A); break;
case 0x03: INC_RR(BC); break;
case 0x04: INC_R(B); break;
case 0x05: DEC_R(B); break;
case 0x06: LD_R_N(B); break;
case 0x07: RLCA; break;
case 0x08: LD_MNN_SP; break;
case 0x09: ADD_HL_RR(BC); break;
case 0x0a: LD_R_MR(A, BC); break;
case 0x0b: DEC_RR(BC); break;
case 0x0c: INC_R(C); break;
case 0x0d: DEC_R(C); break;
case 0x0e: LD_R_N(C); break;
case 0x0f: RRCA; break;
case 0x10: STOP; break;
case 0x11: LD_RR_NN(DE); break;
case 0x12: LD_MR_R(DE, A); break;
case 0x13: INC_RR(DE); break;
case 0x14: INC_R(D); break;
case 0x15: DEC_R(D); break;
case 0x16: LD_R_N(D); break;
case 0x17: RLA; break;
case 0x18: JR_N; break;
case 0x19: ADD_HL_RR(DE); break;
case 0x1a: LD_R_MR(A, DE); break;
case 0x1b: DEC_RR(DE); break;
case 0x1c: INC_R(E); break;
case 0x1d: DEC_R(E); break;
case 0x1e: LD_R_N(E); break;
case 0x1f: RRA; break;
case 0x20: JR_F_N(!FZ); break;
case 0x21: LD_RR_NN(HL); break;
case 0x22: LD_MR_R(HL, A); REG.HL++; break;
case 0x23: INC_RR(HL); break;
case 0x24: INC_R(H); break;
case 0x25: DEC_R(H); break;
case 0x26: LD_R_N(H); break;
case 0x27: DAA; break;
case 0x28: JR_F_N(FZ); break;
case 0x29: ADD_HL_RR(HL); break;
case 0x2a: LD_R_MR(A, HL); REG.HL++; break;
case 0x2b: DEC_RR(HL); break;
case 0x2c: INC_R(L); break;
case 0x2d: DEC_R(L); break;
case 0x2e: LD_R_N(L); break;
case 0x2f: CPL; break;
case 0x30: JR_F_N(!FC); break;
case 0x31: LD_RR_NN(SP); break;
case 0x32: LD_MR_R(HL, A); REG.HL--; break;
case 0x33: INC_RR(SP); break;
case 0x34: INC_MR(HL); break;
case 0x35: DEC_MR(HL); break;
case 0x36: LD_MR_N(HL); break;
case 0x37: SCF; break;
case 0x38: JR_F_N(FC); break;
case 0x39: ADD_HL_RR(SP); break;
case 0x3a: LD_R_MR(A, HL); REG.HL--; break;
case 0x3b: DEC_RR(SP); break;
case 0x3c: INC_R(A); break;
case 0x3d: DEC_R(A); break;
case 0x3e: LD_R_N(A); break;
case 0x3f: CCF; break;
LD_R_OPS(0x40, B)
LD_R_OPS(0x48, C)
LD_R_OPS(0x50, D)
LD_R_OPS(0x58, E)
LD_R_OPS(0x60, H)
LD_R_OPS(0x68, L)
case 0x70: LD_MR_R(HL, B); break;
case 0x71: LD_MR_R(HL, C); break;
case 0x72: LD_MR_R(HL, D); break;
case 0x73: LD_MR_R(HL, E); break;
case 0x74: LD_MR_R(HL, H); break;
case 0x75: LD_MR_R(HL, L); break;
case 0x76: HALT; break;
case 0x77: LD_MR_R(HL, A); break;
LD_R_OPS(0x78, A)
REG_OPS(0x80, ADD)
REG_OPS(0x88, ADC)
REG_OPS(0x90, SUB)
REG_OPS(0x98, SBC)
REG_OPS(0xa0, AND)
REG_OPS(0xa8, XOR)
REG_OPS(0xb0, OR)
REG_OPS(0xb8, CP)
case 0xc0: RET_F(!FZ); break;
case 0xc1: POP_RR(BC); break;
case 0xc2: JP_F_NN(!FZ); break;
case 0xc3: JP_NN; break;
case 0xc4: CALL_F_NN(!FZ); break;
case 0xc5: PUSH_RR(BC); break;
case 0xc6: ADD_N; break;
case 0xc7: CALL(0x00); break;
case 0xc8: RET_F(FZ); break;
case 0xc9: RET; break;
case 0xca: JP_F_NN(FZ); break;
case 0xcb: {
new_pc += 1;
u8 cb = read_u8_tick(e, REG.PC);
HOOK(exec_cb_op_i, cb);
switch (cb) {
REG_OPS(0x00, RLC)
REG_OPS(0x08, RRC)
REG_OPS(0x10, RL)
REG_OPS(0x18, RR)
REG_OPS(0x20, SLA)
REG_OPS(0x28, SRA)
REG_OPS(0x30, SWAP)
REG_OPS(0x38, SRL)
REG_OPS_N(0x40, BIT, 0)
REG_OPS_N(0x48, BIT, 1)
REG_OPS_N(0x50, BIT, 2)
REG_OPS_N(0x58, BIT, 3)
REG_OPS_N(0x60, BIT, 4)
REG_OPS_N(0x68, BIT, 5)
REG_OPS_N(0x70, BIT, 6)
REG_OPS_N(0x78, BIT, 7)
REG_OPS_N(0x80, RES, 0)
REG_OPS_N(0x88, RES, 1)
REG_OPS_N(0x90, RES, 2)
REG_OPS_N(0x98, RES, 3)
REG_OPS_N(0xa0, RES, 4)
REG_OPS_N(0xa8, RES, 5)
REG_OPS_N(0xb0, RES, 6)
REG_OPS_N(0xb8, RES, 7)
REG_OPS_N(0xc0, SET, 0)
REG_OPS_N(0xc8, SET, 1)
REG_OPS_N(0xd0, SET, 2)
REG_OPS_N(0xd8, SET, 3)
REG_OPS_N(0xe0, SET, 4)
REG_OPS_N(0xe8, SET, 5)
REG_OPS_N(0xf0, SET, 6)
REG_OPS_N(0xf8, SET, 7)
}
break;
}
case 0xcc: CALL_F_NN(FZ); break;
case 0xcd: CALL_NN; break;
case 0xce: ADC_N; break;
case 0xcf: CALL(0x08); break;
case 0xd0: RET_F(!FC); break;
case 0xd1: POP_RR(DE); break;
case 0xd2: JP_F_NN(!FC); break;
case 0xd4: CALL_F_NN(!FC); break;
case 0xd5: PUSH_RR(DE); break;
case 0xd6: SUB_N; break;
case 0xd7: CALL(0x10); break;
case 0xd8: RET_F(FC); break;
case 0xd9: RETI; break;
case 0xda: JP_F_NN(FC); break;
case 0xdc: CALL_F_NN(FC); break;
case 0xde: SBC_N; break;
case 0xdf: CALL(0x18); break;
case 0xe0: LD_MFF00_N_R(A); break;
case 0xe1: POP_RR(HL); break;
case 0xe2: LD_MFF00_R_R(C, A); break;
case 0xe5: PUSH_RR(HL); break;
case 0xe6: AND_N; break;
case 0xe7: CALL(0x20); break;
case 0xe8: ADD_SP_N; break;
case 0xe9: JP_RR(HL); break;
case 0xea: LD_MN_R(A); break;
case 0xee: XOR_N; break;
case 0xef: CALL(0x28); break;
case 0xf0: LD_R_MFF00_N(A); break;
case 0xf1: POP_AF; break;
case 0xf2: LD_R_MFF00_R(A, C); break;
case 0xf3: DI; break;
case 0xf5: PUSH_AF; break;
case 0xf6: OR_N; break;
case 0xf7: CALL(0x30); break;
case 0xf8: LD_HL_SP_N; break;
case 0xf9: LD_RR_RR(SP, HL); break;
case 0xfa: LD_R_MN(A); break;
case 0xfb: EI; break;
case 0xfe: CP_N; break;
case 0xff: CALL(0x38); break;
default:
e->state.event |= EMULATOR_EVENT_INVALID_OPCODE;
break;
}
REG.PC = new_pc;
}
#ifdef RGBDS_LIVE
static inline uint32_t emulator_get_banked_PC_inline(Emulator *e) {
#if BREAKPOINTS_MAX_BANKS_NUMBER > 1
uint16_t pc = REG.PC;
if (pc < 0x4000) {
return (MMAP_STATE.rom_base[0] << (16 - ROM_BANK_SHIFT)) | pc;
} else if (pc < 0x8000) {
return (MMAP_STATE.rom_base[1] << (16 - ROM_BANK_SHIFT)) | pc;
} else if (pc < 0xA000) {
return (e->state.vram.bank << 16) | pc;
} else if (pc < 0xC000) {
return (MMAP_STATE.ext_ram_base << (16 - EXT_RAM_BANK_SHIFT)) | pc;
} else if (pc < 0xE000) {
return (e->state.wram.bank << 16) | pc;
}
return pc;
#else
return REG.PC;
#endif
}
static inline bool is_breakpoint(Emulator* e, uint32_t banked_pc) {
uint32_t idx = banked_pc >> BREAKPOINTS_SHIFT;
return (e->breakpoint[idx] & ((breakpoints_type)1 << (banked_pc & BREAKPOINTS_MASK)));
}
#endif
static void emulator_step_internal(Emulator* e) {
if (HDMA.state == DMA_INACTIVE) {
if (HOOK0_FALSE(emulator_step)) {
return;
}
execute_instruction(e);
#ifdef RGBDS_LIVE
uint32_t banked_pc = emulator_get_banked_PC_inline(e);
if (is_breakpoint(e, banked_pc)) {
e->state.event |= EMULATOR_EVENT_BREAKPOINT;
}
#endif
} else {
tick(e);
hdma_copy_byte(e);
hdma_copy_byte(e);
}
}
EmulatorEvent emulator_run_until(Emulator* e, Ticks until_ticks) {
AudioBuffer* ab = &e->audio_buffer;
if (e->state.event & EMULATOR_EVENT_AUDIO_BUFFER_FULL) {
ab->position = ab->data;
}
check_joyp_intr(e);
e->state.event = 0;
u64 frames_left = ab->frames - audio_buffer_get_frames(ab);
Ticks max_audio_ticks =
APU.sync_ticks +
(u32)DIV_CEIL(frames_left * CPU_TICKS_PER_SECOND, ab->frequency);
Ticks check_ticks = MIN(until_ticks, max_audio_ticks);
while (e->state.event == 0 && TICKS < check_ticks) {
emulator_step_internal(e);
}
if (TICKS >= max_audio_ticks) {
e->state.event |= EMULATOR_EVENT_AUDIO_BUFFER_FULL;
}
if (TICKS >= until_ticks) {
e->state.event |= EMULATOR_EVENT_UNTIL_TICKS;
}
apu_synchronize(e);
return e->state.event;
}
EmulatorEvent emulator_step(Emulator* e) {
return emulator_run_until(e, TICKS + 1);
}
static Result validate_header_checksum(CartInfo* cart_info) {
u8 checksum = 0;
size_t i = 0;
for (i = HEADER_CHECKSUM_RANGE_START; i <= HEADER_CHECKSUM_RANGE_END; ++i) {
checksum = checksum - cart_info->data[i] - 1;
}
return checksum == cart_info->data[HEADER_CHECKSUM_ADDR] ? OK : ERROR;
}
static const char* get_result_string(Result value) {
static const char* s_strings[] = {[OK] = "OK", [ERROR] = "ERROR"};
return get_enum_string(s_strings, ARRAY_SIZE(s_strings), value);
}
static void log_cart_info(CartInfo* cart_info) {
unsigned char title[TITLE_MAX_LENGTH + 1] = {0};
char* title_start = (char*)cart_info->data + TITLE_START_ADDR;
char* title_end = memchr(title_start, '\0', TITLE_MAX_LENGTH);
int title_length =
(int)(title_end ? title_end - title_start : TITLE_MAX_LENGTH);
memcpy(title, title_start, title_length);
// Change all non-ascii characters to ' '.
int i;
for (i = 0; i < title_length; ++i) {
if (title[i] < 32 || title[i] >= 128) { title[i] = ' '; }
}
printf("title: \"%s\"\n", title);
printf("cgb flag: %s\n", get_cgb_flag_string(cart_info->cgb_flag));
printf("sgb flag: %s\n", get_sgb_flag_string(cart_info->sgb_flag));
printf("cart type: %s\n", get_cart_type_string(cart_info->cart_type));
printf("rom size: %s\n", get_rom_size_string(cart_info->rom_size));
printf("ext ram size: %s\n",
get_ext_ram_size_string(cart_info->ext_ram_size));
printf("header checksum: 0x%02x [%s]\n",
cart_info->data[HEADER_CHECKSUM_ADDR],
get_result_string(validate_header_checksum(cart_info)));
}
Result init_audio_buffer(Emulator* e, u32 frequency, u32 frames) {
AudioBuffer* audio_buffer = &e->audio_buffer;
audio_buffer->frames = frames;
size_t buffer_size =
(frames + AUDIO_BUFFER_EXTRA_FRAMES) * SOUND_OUTPUT_COUNT;
audio_buffer->data = xmalloc(buffer_size);
CHECK_MSG(audio_buffer->data != NULL, "Audio buffer allocation failed.\n");
audio_buffer->end = audio_buffer->data + buffer_size;
audio_buffer->position = audio_buffer->data;
audio_buffer->frequency = frequency;
return OK;
ON_ERROR_RETURN;
}
static u32 random_u32(u32* state) {
/* xorshift32: https://en.wikipedia.org/wiki/Xorshift */
u32 x = *state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
*state = x;
return x;
}
static void randomize_buffer(u32* seed, u8* buffer, u32 size) {
while (size >= sizeof(u32)) {
u32 x = random_u32(seed);
memcpy(buffer, &x, sizeof(x));
buffer += sizeof(u32);
size -= sizeof(u32);
}
if (size > 0) {
u32 x = random_u32(seed);
switch (size) {
case 3: *buffer++ = x & 0xff; x >>= 8; break;
case 2: *buffer++ = x & 0xff; x >>= 8; break;
case 1: *buffer++ = x & 0xff; x >>= 8; break;
}
}
}
Result init_emulator(Emulator* e, const EmulatorInit* init) {
static u8 s_initial_wave_ram[WAVE_RAM_SIZE] = {
0x60, 0x0d, 0xda, 0xdd, 0x50, 0x0f, 0xad, 0xed,
0xc0, 0xde, 0xf0, 0x0d, 0xbe, 0xef, 0xfe, 0xed,
};
CHECK(SUCCESS(get_cart_infos(e)));
log_cart_info(e->cart_info);
MMAP_STATE.rom_base[0] = 0;
MMAP_STATE.rom_base[1] = 1 << ROM_BANK_SHIFT;
IS_CGB = !init->force_dmg && (e->cart_info->cgb_flag == CGB_FLAG_SUPPORTED ||
e->cart_info->cgb_flag == CGB_FLAG_REQUIRED);
IS_SGB = !init->force_dmg && !IS_CGB &&
e->cart_info->sgb_flag == SGB_FLAG_SUPPORTED;
set_af_reg(e, 0xb0);
REG.A = IS_CGB ? 0x11 : 0x01;
REG.BC = 0x0013;
REG.DE = 0x00d8;
REG.HL = 0x014d;
REG.SP = 0xfffe;
REG.PC = 0x0100;
INTR.ime = FALSE;
TIMER.div_counter = 0xAC00;
TIMER.next_intr_ticks = SERIAL.next_intr_ticks = e->state.next_intr_ticks =
INVALID_TICKS;
WRAM.offset = 0x1000;
/* Enable apu first, so subsequent writes succeed. */
write_apu(e, APU_NR52_ADDR, 0xf1);
write_apu(e, APU_NR11_ADDR, 0x80);
write_apu(e, APU_NR12_ADDR, 0xf3);
write_apu(e, APU_NR14_ADDR, 0x80);
write_apu(e, APU_NR50_ADDR, 0x77);
write_apu(e, APU_NR51_ADDR, 0xf3);
APU.initialized = TRUE;
memcpy(&WAVE.ram, s_initial_wave_ram, WAVE_RAM_SIZE);
/* Turn down the volume on channel1, it is playing by default (because of the
* GB startup sound), but we don't want to hear it when starting the
* emulator. */
CHANNEL1.envelope.volume = 0;
write_io(e, IO_LCDC_ADDR, 0x91);
write_io(e, IO_SCY_ADDR, 0x00);
write_io(e, IO_SCX_ADDR, 0x00);
write_io(e, IO_LYC_ADDR, 0x00);
write_io(e, IO_BGP_ADDR, 0xfc);
write_io(e, IO_OBP0_ADDR, 0xff);
write_io(e, IO_OBP1_ADDR, 0xff);
write_io(e, IO_IF_ADDR, 0x1);
write_io(e, IO_IE_ADDR, 0x0);
HDMA.blocks = 0xff;
/* Set initial DMG/SGB palettes */
emulator_set_builtin_palette(e, init->builtin_palette);
/* Set up cgb color curve */
e->cgb_color_curve = init->cgb_color_curve;
/* Set initial CGB palettes to white. */
int pal_index;
for (pal_index = 0; pal_index < 2; ++pal_index) {
ColorPalettes* palette = pal_index == 0 ? &PPU.bgcp : &PPU.obcp;
int i;
for (i = 0; i < 32; ++i) {
palette->palettes[i >> 2].color[i & 3] = RGBA_WHITE;
palette->data[i * 2] = 0xff;
palette->data[i * 2 + 1] = 0x7f;
}
}
/* Randomize RAM */
u32 random_seed = init->random_seed;
e->state.random_seed = random_seed;
randomize_buffer(&random_seed, e->state.ext_ram.data, EXT_RAM_MAX_SIZE);
randomize_buffer(&random_seed, e->state.wram.data, WORK_RAM_SIZE);
randomize_buffer(&random_seed, e->state.hram, HIGH_RAM_SIZE);
e->state.cpu_tick = CPU_TICK;
calculate_next_ppu_intr(e);
return OK;
ON_ERROR_RETURN;
}
void emulator_set_joypad_buttons(Emulator* e, JoypadButtons* buttons) {
JOYP.buttons = *buttons;
}
void emulator_set_joypad_callback(Emulator* e, JoypadCallback callback,
void* user_data) {
e->joypad_info.callback = callback;
e->joypad_info.user_data = user_data;
}
JoypadCallbackInfo emulator_get_joypad_callback(Emulator* e) {
return e->joypad_info;
}
void emulator_set_config(Emulator* e, const EmulatorConfig* config) {
e->config = *config;
}
EmulatorConfig emulator_get_config(Emulator* e) {
return e->config;
}
FrameBuffer* emulator_get_frame_buffer(Emulator* e) {
return &e->frame_buffer;
}
SgbFrameBuffer* emulator_get_sgb_frame_buffer(Emulator* e) {
return &e->sgb_frame_buffer;
}
AudioBuffer* emulator_get_audio_buffer(Emulator* e) {
return &e->audio_buffer;
}
Ticks emulator_get_ticks(Emulator* e) {
return TICKS;
}
u32 emulator_get_ppu_frame(Emulator* e) {
return PPU.frame;
}
u32 audio_buffer_get_frames(AudioBuffer* audio_buffer) {
return (audio_buffer->position - audio_buffer->data) / SOUND_OUTPUT_COUNT;
}
void emulator_set_bw_palette(Emulator* e, PaletteType type,
const PaletteRGBA* palette) {
e->color_to_rgba[type] = *palette;
update_bw_palette_rgba(e, type);
}
void emulator_set_all_bw_palettes(Emulator* e, const PaletteRGBA* palette) {
e->color_to_rgba[PALETTE_TYPE_BGP] = *palette;
e->color_to_rgba[PALETTE_TYPE_OBP0] = *palette;
e->color_to_rgba[PALETTE_TYPE_OBP1] = *palette;
}
static Result set_rom_file_data(Emulator* e, const FileData* file_data) {
CHECK_MSG(file_data->size > 0, "File is empty.\n");
CHECK_MSG((file_data->size & (MINIMUM_ROM_SIZE - 1)) == 0,
"File size (%ld) should be a multiple of minimum rom size (%ld).\n",
(long)file_data->size, (long)MINIMUM_ROM_SIZE);
e->file_data = *file_data;
return OK;
ON_ERROR_RETURN;
}
Bool emulator_was_ext_ram_updated(Emulator* e) {
Bool result = e->state.ext_ram_updated;
e->state.ext_ram_updated = FALSE;
return result;
}
void emulator_init_state_file_data(FileData* file_data) {
file_data->size = sizeof(EmulatorState);
file_data->data = xmalloc(file_data->size);
}
void emulator_init_ext_ram_file_data(Emulator* e, FileData* file_data) {
file_data->size = EXT_RAM.size;
file_data->data = xmalloc(file_data->size);
}
Result emulator_read_state(Emulator* e, const FileData* file_data) {
CHECK_MSG(file_data->size == sizeof(EmulatorState),
"save state file is wrong size: %ld, expected %ld.\n",
(long)file_data->size, (long)sizeof(EmulatorState));
EmulatorState* new_state = (EmulatorState*)file_data->data;
CHECK_MSG(new_state->header == SAVE_STATE_HEADER,
"header mismatch: %u, expected %u.\n", new_state->header,
SAVE_STATE_HEADER);
memcpy(&e->state, new_state, sizeof(EmulatorState));
set_cart_info(e, e->state.cart_info_index);
if (IS_SGB) {
emulator_set_bw_palette(e, PALETTE_TYPE_OBP0, &SGB.screen_pal[0]);
emulator_set_bw_palette(e, PALETTE_TYPE_OBP1, &SGB.screen_pal[0]);
}
update_bw_palette_rgba(e, PALETTE_TYPE_BGP);
update_bw_palette_rgba(e, PALETTE_TYPE_OBP0);
update_bw_palette_rgba(e, PALETTE_TYPE_OBP1);
return OK;
ON_ERROR_RETURN;
}
Result emulator_write_state(Emulator* e, FileData* file_data) {
CHECK(file_data->size >= sizeof(EmulatorState));
e->state.header = SAVE_STATE_HEADER;
memcpy(file_data->data, &e->state, file_data->size);
return OK;
ON_ERROR_RETURN;
}
Result emulator_read_ext_ram(Emulator* e, const FileData* file_data) {
if (EXT_RAM.battery_type != BATTERY_TYPE_WITH_BATTERY)
return OK;
CHECK_MSG(file_data->size == EXT_RAM.size,
"save file is wrong size: %ld, expected %ld.\n",
(long)file_data->size, (long)EXT_RAM.size);
memcpy(EXT_RAM.data, file_data->data, file_data->size);
return OK;
ON_ERROR_RETURN;
}
Result emulator_write_ext_ram(Emulator* e, FileData* file_data) {
if (EXT_RAM.battery_type != BATTERY_TYPE_WITH_BATTERY)
return OK;
CHECK(file_data->size >= EXT_RAM.size);
memcpy(file_data->data, EXT_RAM.data, file_data->size);
return OK;
ON_ERROR_RETURN;
}
#ifndef __wasm__
Result emulator_read_ext_ram_from_file(Emulator* e, const char* filename) {
if (EXT_RAM.battery_type != BATTERY_TYPE_WITH_BATTERY)
return OK;
Result result = ERROR;
FileData file_data;
ZERO_MEMORY(file_data);
CHECK(SUCCESS(file_read(filename, &file_data)));
CHECK(SUCCESS(emulator_read_ext_ram(e, &file_data)));
result = OK;
error:
file_data_delete(&file_data);
return result;
}
Result emulator_write_ext_ram_to_file(Emulator* e, const char* filename) {
if (EXT_RAM.battery_type != BATTERY_TYPE_WITH_BATTERY)
return OK;
Result result = ERROR;
FileData file_data;
file_data.size = EXT_RAM.size;
file_data.data = xmalloc(file_data.size);
CHECK(SUCCESS(emulator_write_ext_ram(e, &file_data)));
CHECK(SUCCESS(file_write(filename, &file_data)));
result = OK;
error:
file_data_delete(&file_data);
return result;
}
Result emulator_read_state_from_file(Emulator* e, const char* filename) {
Result result = ERROR;
FileData file_data;
ZERO_MEMORY(file_data);
CHECK(SUCCESS(file_read(filename, &file_data)));
CHECK(SUCCESS(emulator_read_state(e, &file_data)));
result = OK;
error:
file_data_delete(&file_data);
return result;
}
Result emulator_write_state_to_file(Emulator* e, const char* filename) {
Result result = ERROR;
FileData file_data;
emulator_init_state_file_data(&file_data);
CHECK(SUCCESS(emulator_write_state(e, &file_data)));
CHECK(SUCCESS(file_write(filename, &file_data)));
result = OK;
error:
file_data_delete(&file_data);
return result;
}
#endif
Emulator* emulator_new(const EmulatorInit* init) {
Emulator* e = xcalloc(1, sizeof(Emulator));
CHECK(SUCCESS(set_rom_file_data(e, &init->rom)));
CHECK(SUCCESS(init_emulator(e, init)));
CHECK(
SUCCESS(init_audio_buffer(e, init->audio_frequency, init->audio_frames)));
return e;
error:
emulator_delete(e);
return NULL;
}
void emulator_delete(Emulator* e) {
if (e) {
xfree(e->audio_buffer.data);
file_data_delete(&e->file_data);
xfree(e);
}
}
void emulator_ticks_to_time(Ticks ticks, u32* day, u32* hr, u32* min, u32* sec,
u32* ms) {
u64 secs = ticks / CPU_TICKS_PER_SECOND;
*ms = (secs / 1000) % 1000;
*sec = secs % 60;
*min = (secs / 60) % 60;
*hr = (secs / (60 * 60)) % 24;
*day = secs / (60 * 60 * 24);
}
void emulator_set_builtin_palette(Emulator* e, u32 index) {
static const PaletteRGBA pals[][3] = {
#define PAL(b0, b1, b2, b3, o00, o01, o02, o03, o10, o11, o12, o13) \
{{{b0, b1, b2, b3}}, {{o00, o01, o02, o03}}, {{o10, o11, o12, o13}}},
#define PAL3(c0, c1, c2, c3) \
{{{c0, c1, c2, c3}}, {{c0, c1, c2, c3}}, {{c0, c1, c2, c3}}},
#include "builtin-palettes.def"
#undef PAL
#undef PAL3
};
size_t count = sizeof(pals) / sizeof(*pals);
if (index >= count) { return; }
emulator_set_bw_palette(e, 0, &pals[index][0]);
emulator_set_bw_palette(e, 1, &pals[index][1]);
emulator_set_bw_palette(e, 2, &pals[index][2]);
for (int i = 0; i < 4; ++i) {
SGB.screen_pal[i] = pals[index][0];
}
update_bw_palette_rgba(e, PALETTE_TYPE_BGP);
}
ApuLog* emulator_get_apu_log(Emulator* e) {
return &e->apu_log;
}
void emulator_reset_apu_log(Emulator* e) {
e->apu_log.write_count = 0;
}
u16 emulator_get_PC(Emulator* e) {
return REG.PC;
}
u8 emulator_get_A(Emulator* e) {
return REG.A;
}
u16 emulator_get_BC(Emulator* e) {
return REG.BC;
}
u16 emulator_get_DE(Emulator* e) {
return REG.DE;
}
u16 emulator_get_HL(Emulator* e) {
return REG.HL;
}
u8 emulator_get_F(Emulator* e) {
return PACK(REG.F.Z, CPU_FLAG_Z) | PACK(REG.F.N, CPU_FLAG_N) |
PACK(REG.F.H, CPU_FLAG_H) | PACK(REG.F.C, CPU_FLAG_C);
}
u16 emulator_get_SP(Emulator* e) {
return REG.SP;
}
void emulator_set_PC(Emulator* e, u16 pc) {
REG.PC = pc;
}
u8* emulator_get_wram_ptr(Emulator* e) {
return WRAM.data;
}
u8* emulator_get_hram_ptr(Emulator* e) {
return HRAM;
}
u8 emulator_read_mem(Emulator* e, u16 addr) {
return read_u8_raw(e, addr);
}
void emulator_write_mem(Emulator* e, u16 addr, u8 data) {
write_u8_raw(e, addr, data);
}
#ifdef RGBDS_LIVE
void emulator_set_breakpoint(Emulator* e, uint32_t addr) {
uint32_t idx = addr >> BREAKPOINTS_SHIFT;
e->breakpoint[idx] |= ((breakpoints_type)1 << (addr & BREAKPOINTS_MASK));
}
void emulator_clear_breakpoints(Emulator* e) {
ZERO_MEMORY(e->breakpoint);
}
uint32_t emulator_get_banked_PC(Emulator *e) {
return emulator_get_banked_PC_inline(e);
}
void emulator_render_vram(Emulator* e, u32* buffer) {
memset(buffer, 0, sizeof(u32) * 256 * 256);
for (int ty = 0; ty < 24; ty++) {
for (int bank = 0; bank < 2; bank++) {
for (int tx = 0; tx < 16; tx++) {
for (int row = 0; row < 8; row++) {
int n = tx * 16 + ty * 16 * 16 + row * 2 + (bank << 13);
u8 a = VRAM.data[n];
u8 b = VRAM.data[n + 1];
for (int x = 0; x < 8; x++) {
u32 color = 0xFFC2F0C4;
u8 bit = (0x80 >> x);
if ((a & bit) && (b & bit)) {
color = 0xFF001B2D;
} else if (a & bit) {
color = 0xFFA8B95A;
} else if (b & bit) {
color = 0xFF6E601E;
} else if (x == 7 || row == 7) {
color = 0xFFB2E0B4;
}
buffer[(tx * 8 + x + bank * 128) + (ty * 8 + row) * 256] = color;
}
}
}
}
}
if (IS_CGB) {
for (int idx = 0; idx < 8; idx++) {
for (int col = 0; col < PALETTE_COLOR_COUNT; col++) {
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
buffer[x + idx * 8 + (200 + col * 8 + y) * 256] =
PPU.bgcp.palettes[idx].color[col];
buffer[x + idx * 8 + (200 + col * 8 + y) * 256 + 128] =
PPU.obcp.palettes[idx].color[col];
}
}
}
}
} else {
for (int type = 0; type < PALETTE_TYPE_COUNT; type++) {
for (int col = 0; col < PALETTE_COLOR_COUNT; col++) {
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
buffer[x + type * 8 + (200 + col * 8 + y) * 256] =
e->pal[type].color[col];
}
}
}
}
}
}
void emulator_render_background(Emulator* e, u32* buffer, int type) {
memset(buffer, 0, sizeof(u32) * 256 * 256);
int tile_map = 0x1800 + ((type & 1) ? 0x400 : 0);
for (int ty = 0; ty < 32; ty++) {
for (int tx = 0; tx < 32; tx++) {
int map_index = tile_map + tx + ty * 32;
u8 tile = VRAM.data[map_index];
u8 attr = VRAM.data[0x2000 + map_index];
int tile_bank_offset = (IS_CGB && (attr & 0x08)) ? 0x2000 : 0;
int xflip = IS_CGB && (attr & 0x20);
int yflip = IS_CGB && (attr & 0x40);
int offset = 0;
if(tile < 128)
offset = (LCDC.bg_tile_data_select == TILE_DATA_8000_8FFF) ? 0 : 0x1000;
for (int row = 0; row < 8; row++) {
int py = yflip ? (7 - row) : row;
int n = offset + tile * 16 + py * 2;
u8 a = VRAM.data[tile_bank_offset + n];
u8 b = VRAM.data[tile_bank_offset + n + 1];
for (int x = 0; x < 8; x++) {
u32 color = 0xFFC2F0C4;
int px = xflip ? (7 - x) : x;
u8 bit = (0x80 >> px);
if ((a & bit) && (b & bit)) {
color = 0xFF001B2D;
} else if (a & bit) {
color = 0xFFA8B95A;
} else if (b & bit) {
color = 0xFF6E601E;
} else if (x == 7 || row == 7) {
color = 0xFFB2E0B4;
}
buffer[(tx * 8 + x) + (ty * 8 + row) * 256] = color;
}
}
}
}
for (int x = 0; x < SCREEN_WIDTH; x++) {
buffer[((PPU.scx + x) % 256) + (PPU.scy * 256)] &= 0xFF7F7F7F;
buffer[((PPU.scx + x) % 256) +
((PPU.scy + SCREEN_HEIGHT - 1) % 256) * 256] &= 0xFF7F7F7F;
}
for (int y = 0; y < SCREEN_HEIGHT; y++) {
buffer[PPU.scx + ((PPU.scy + y) % 256) * 256] &= 0xFF7F7F7F;
buffer[((PPU.scx + SCREEN_WIDTH) % 256) + ((PPU.scy + y) % 256) * 256] &=
0xFF7F7F7F;
}
}
#else // !RGBDS_LIVE
void emulator_set_breakpoint(Emulator* e, Address addr) {}
void emulator_clear_breakpoints(Emulator* e) {}
void emulator_render_vram(Emulator* e, u32* buffer) {}
void emulator_render_background(Emulator* e, u32* buffer, int type) {}
uint32_t emulator_get_banked_PC(Emulator *e) { return REG.PC; }
#endif
#ifdef GBSTUDIO
Bool set_audio_channel_mute(Emulator *e, int channel, Bool muted) {
EmulatorConfig emu_config = emulator_get_config(e);
emu_config.disable_sound[channel] = muted;
emulator_set_config(e, &emu_config);
return emu_config.disable_sound[channel];
}
#else // !GBSTUDIO
Bool set_audio_channel_mute(Emulator *e, int channel, Bool muted) {
return FALSE;
}
#endif