5353 lines
171 KiB
C
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
|