mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 15:17:48 +01:00
4043 lines
100 KiB
C
4043 lines
100 KiB
C
/**
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2018-2023 Mahyar Koshkouei
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*
|
|
* Please note that at least two parts of source code within this project was
|
|
* taken from the SameBoy project at https://github.com/LIJI32/SameBoy/ which at
|
|
* the time of this writing is released under the MIT License. Occurrences of
|
|
* this code is marked as being taken from SameBoy with a comment.
|
|
* SameBoy, and code marked as being taken from SameBoy,
|
|
* is Copyright (c) 2015-2019 Lior Halphon.
|
|
*/
|
|
|
|
#ifndef PEANUT_GB_H
|
|
#define PEANUT_GB_H
|
|
|
|
#if defined(__has_include)
|
|
# if __has_include("version.all")
|
|
# include "version.all" /* Version information */
|
|
# endif
|
|
#else
|
|
/* Stub __has_include for later. */
|
|
# define __has_include(x) 0
|
|
#endif
|
|
|
|
#include <stdlib.h> /* Required for abort */
|
|
#include <stdbool.h> /* Required for bool types */
|
|
#include <stdint.h> /* Required for int types */
|
|
#include <string.h> /* Required for memset */
|
|
#include <time.h> /* Required for tm struct */
|
|
|
|
/**
|
|
* If PEANUT_GB_IS_LITTLE_ENDIAN is positive, then Peanut-GB will be configured
|
|
* for a little endian platform. If 0, then big endian.
|
|
*/
|
|
#if !defined(PEANUT_GB_IS_LITTLE_ENDIAN)
|
|
/* If endian is not defined, then attempt to detect it. */
|
|
# if defined(__BYTE_ORDER__)
|
|
# if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
|
/* Building for a big endian platform. */
|
|
# define PEANUT_GB_IS_LITTLE_ENDIAN 0
|
|
# else
|
|
# define PEANUT_GB_IS_LITTLE_ENDIAN 1
|
|
# endif /* __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ */
|
|
# elif defined(_WIN32)
|
|
/* We assume that Windows is always little endian by default. */
|
|
# define PEANUT_GB_IS_LITTLE_ENDIAN 1
|
|
# elif !defined(PEANUT_GB_IS_LITTLE_ENDIAN)
|
|
# error "Could not detect target platform endian. Please define PEANUT_GB_IS_LITTLE_ENDIAN"
|
|
# endif
|
|
#endif /* !defined(PEANUT_GB_IS_LITTLE_ENDIAN) */
|
|
|
|
#if PEANUT_GB_IS_LITTLE_ENDIAN == 0
|
|
# error "Peanut-GB only supports little endian targets"
|
|
/* This is because the logic has been written with assumption of little
|
|
* endian byte order. */
|
|
#endif
|
|
|
|
/** Definitions for compile-time setting of features. **/
|
|
/**
|
|
* Sound support must be provided by an external library. When audio_read() and
|
|
* audio_write() functions are provided, define ENABLE_SOUND to a non-zero value
|
|
* before including peanut_gb.h in order for these functions to be used.
|
|
*/
|
|
#ifndef ENABLE_SOUND
|
|
# define ENABLE_SOUND 0
|
|
#endif
|
|
|
|
/* Enable LCD drawing. On by default. May be turned off for testing purposes. */
|
|
#ifndef ENABLE_LCD
|
|
# define ENABLE_LCD 1
|
|
#endif
|
|
|
|
/* Enable 16 bit colour palette. If disabled, only four colour shades are set in
|
|
* pixel data. */
|
|
#ifndef PEANUT_GB_12_COLOUR
|
|
# define PEANUT_GB_12_COLOUR 0
|
|
#endif
|
|
|
|
/* Adds more code to improve LCD rendering accuracy. */
|
|
#ifndef PEANUT_GB_HIGH_LCD_ACCURACY
|
|
# define PEANUT_GB_HIGH_LCD_ACCURACY 0
|
|
#endif
|
|
|
|
/* Use intrinsic functions. This may produce smaller and faster code. */
|
|
#ifndef PEANUT_GB_USE_INTRINSICS
|
|
# define PEANUT_GB_USE_INTRINSICS 1
|
|
#endif
|
|
|
|
/* Only include function prototypes. At least one file must *not* have this
|
|
* defined. */
|
|
// #define PEANUT_GB_HEADER_ONLY
|
|
|
|
/** Internal source code. **/
|
|
/* Interrupt masks */
|
|
#define VBLANK_INTR 0x01
|
|
#define LCDC_INTR 0x02
|
|
#define TIMER_INTR 0x04
|
|
#define SERIAL_INTR 0x08
|
|
#define CONTROL_INTR 0x10
|
|
#define ANY_INTR 0x1F
|
|
|
|
/* Memory section sizes for DMG */
|
|
#define WRAM_SIZE 0x2000
|
|
#define VRAM_SIZE 0x2000
|
|
#define HRAM_IO_SIZE 0x0100
|
|
#define OAM_SIZE 0x00A0
|
|
|
|
/* Memory addresses */
|
|
#define ROM_0_ADDR 0x0000
|
|
#define ROM_N_ADDR 0x4000
|
|
#define VRAM_ADDR 0x8000
|
|
#define CART_RAM_ADDR 0xA000
|
|
#define WRAM_0_ADDR 0xC000
|
|
#define WRAM_1_ADDR 0xD000
|
|
#define ECHO_ADDR 0xE000
|
|
#define OAM_ADDR 0xFE00
|
|
#define UNUSED_ADDR 0xFEA0
|
|
#define IO_ADDR 0xFF00
|
|
#define HRAM_ADDR 0xFF80
|
|
#define INTR_EN_ADDR 0xFFFF
|
|
|
|
/* Cart section sizes */
|
|
#define ROM_BANK_SIZE 0x4000
|
|
#define WRAM_BANK_SIZE 0x1000
|
|
#define CRAM_BANK_SIZE 0x2000
|
|
#define VRAM_BANK_SIZE 0x2000
|
|
|
|
/* DIV Register is incremented at rate of 16384Hz.
|
|
* 4194304 / 16384 = 256 clock cycles for one increment. */
|
|
#define DIV_CYCLES 256
|
|
|
|
/* Serial clock locked to 8192Hz on DMG.
|
|
* 4194304 / (8192 / 8) = 4096 clock cycles for sending 1 byte. */
|
|
#define SERIAL_CYCLES 4096
|
|
|
|
/* Calculating VSYNC. */
|
|
#define DMG_CLOCK_FREQ 4194304.0
|
|
#define SCREEN_REFRESH_CYCLES 70224.0
|
|
#define VERTICAL_SYNC (DMG_CLOCK_FREQ/SCREEN_REFRESH_CYCLES)
|
|
|
|
/* Real Time Clock is locked to 1Hz. */
|
|
#define RTC_CYCLES ((uint_fast32_t)DMG_CLOCK_FREQ)
|
|
|
|
/* SERIAL SC register masks. */
|
|
#define SERIAL_SC_TX_START 0x80
|
|
#define SERIAL_SC_CLOCK_SRC 0x01
|
|
|
|
/* STAT register masks */
|
|
#define STAT_LYC_INTR 0x40
|
|
#define STAT_MODE_2_INTR 0x20
|
|
#define STAT_MODE_1_INTR 0x10
|
|
#define STAT_MODE_0_INTR 0x08
|
|
#define STAT_LYC_COINC 0x04
|
|
#define STAT_MODE 0x03
|
|
#define STAT_USER_BITS 0xF8
|
|
|
|
/* LCDC control masks */
|
|
#define LCDC_ENABLE 0x80
|
|
#define LCDC_WINDOW_MAP 0x40
|
|
#define LCDC_WINDOW_ENABLE 0x20
|
|
#define LCDC_TILE_SELECT 0x10
|
|
#define LCDC_BG_MAP 0x08
|
|
#define LCDC_OBJ_SIZE 0x04
|
|
#define LCDC_OBJ_ENABLE 0x02
|
|
#define LCDC_BG_ENABLE 0x01
|
|
|
|
/** LCD characteristics **/
|
|
/* There are 154 scanlines. LY < 154. */
|
|
#define LCD_VERT_LINES 154
|
|
#define LCD_WIDTH 160
|
|
#define LCD_HEIGHT 144
|
|
/* PPU cycles through modes every 456 cycles. */
|
|
#define LCD_LINE_CYCLES 456
|
|
#define LCD_MODE0_HBLANK_MAX_DRUATION 204
|
|
#define LCD_MODE0_HBLANK_MIN_DRUATION 87
|
|
#define LCD_MODE2_OAM_SCAN_DURATION 80
|
|
#define LCD_MODE3_LCD_DRAW_MIN_DURATION 172
|
|
#define LCD_MODE3_LCD_DRAW_MAX_DURATION 289
|
|
#define LCD_MODE1_VBLANK_DURATION (LCD_LINE_CYCLES * (LCD_VERT_LINES - LCD_HEIGHT))
|
|
#define LCD_FRAME_CYCLES (LCD_LINE_CYCLES * LCD_VERT_LINES)
|
|
/* The following assumes that Hblank starts on cycle 0. */
|
|
/* Mode 2 (OAM Scan) starts on cycle 204 (although this is dependent on the
|
|
* duration of Mode 3 (LCD Draw). */
|
|
#define LCD_MODE_2_CYCLES LCD_MODE0_HBLANK_MAX_DRUATION
|
|
/* Mode 3 starts on cycle 284. */
|
|
#define LCD_MODE_3_CYCLES (LCD_MODE_2_CYCLES + LCD_MODE2_OAM_SCAN_DURATION)
|
|
/* Mode 0 starts on cycle 376. */
|
|
#define LCD_MODE_0_CYCLES (LCD_MODE_3_CYCLES + LCD_MODE3_LCD_DRAW_MIN_DURATION)
|
|
|
|
#define LCD_MODE2_OAM_SCAN_START 0
|
|
#define LCD_MODE2_OAM_SCAN_END (LCD_MODE2_OAM_SCAN_DURATION)
|
|
#define LCD_MODE3_LCD_DRAW_END (LCD_MODE2_OAM_SCAN_END + LCD_MODE3_LCD_DRAW_MIN_DURATION)
|
|
#define LCD_MODE0_HBLANK_END (LCD_MODE3_LCD_DRAW_END + LCD_MODE0_HBLANK_MAX_DRUATION)
|
|
#if LCD_MODE0_HBLANK_END != LCD_LINE_CYCLES
|
|
#error "LCD length not equal"
|
|
#endif
|
|
|
|
/* VRAM Locations */
|
|
#define VRAM_TILES_1 (0x8000 - VRAM_ADDR)
|
|
#define VRAM_TILES_2 (0x8800 - VRAM_ADDR)
|
|
#define VRAM_BMAP_1 (0x9800 - VRAM_ADDR)
|
|
#define VRAM_BMAP_2 (0x9C00 - VRAM_ADDR)
|
|
#define VRAM_TILES_3 (0x8000 - VRAM_ADDR + VRAM_BANK_SIZE)
|
|
#define VRAM_TILES_4 (0x8800 - VRAM_ADDR + VRAM_BANK_SIZE)
|
|
|
|
/* Interrupt jump addresses */
|
|
#define VBLANK_INTR_ADDR 0x0040
|
|
#define LCDC_INTR_ADDR 0x0048
|
|
#define TIMER_INTR_ADDR 0x0050
|
|
#define SERIAL_INTR_ADDR 0x0058
|
|
#define CONTROL_INTR_ADDR 0x0060
|
|
|
|
/* SPRITE controls */
|
|
#define NUM_SPRITES 0x28
|
|
#define MAX_SPRITES_LINE 0x0A
|
|
#define OBJ_PRIORITY 0x80
|
|
#define OBJ_FLIP_Y 0x40
|
|
#define OBJ_FLIP_X 0x20
|
|
#define OBJ_PALETTE 0x10
|
|
|
|
/* Joypad buttons */
|
|
#define JOYPAD_A 0x01
|
|
#define JOYPAD_B 0x02
|
|
#define JOYPAD_SELECT 0x04
|
|
#define JOYPAD_START 0x08
|
|
#define JOYPAD_RIGHT 0x10
|
|
#define JOYPAD_LEFT 0x20
|
|
#define JOYPAD_UP 0x40
|
|
#define JOYPAD_DOWN 0x80
|
|
|
|
#define ROM_HEADER_CHECKSUM_LOC 0x014D
|
|
|
|
/* Local macros. */
|
|
#ifndef MIN
|
|
# define MIN(a, b) ((a) < (b) ? (a) : (b))
|
|
#endif
|
|
|
|
#define PEANUT_GB_ARRAYSIZE(array) (sizeof(array)/sizeof(array[0]))
|
|
|
|
/** Allow setting deprecated functions and variables. */
|
|
#if (defined(__GNUC__) && __GNUC__ >= 6) || (defined(__clang__) && __clang_major__ >= 4)
|
|
# define PGB_DEPRECATED(msg) __attribute__((deprecated(msg)))
|
|
#else
|
|
# define PGB_DEPRECATED(msg)
|
|
#endif
|
|
|
|
#if !defined(__has_builtin)
|
|
/* Stub __has_builtin if it isn't available. */
|
|
# define __has_builtin(x) 0
|
|
#endif
|
|
|
|
/* The PGB_UNREACHABLE() macro tells the compiler that the code path will never
|
|
* be reached, allowing for further optimisation. */
|
|
#if !defined(PGB_UNREACHABLE)
|
|
# if __has_builtin(__builtin_unreachable)
|
|
# define PGB_UNREACHABLE() __builtin_unreachable()
|
|
# elif defined(_MSC_VER) && _MSC_VER >= 1200
|
|
# /* __assume is not available before VC6. */
|
|
# define PGB_UNREACHABLE() __assume(0)
|
|
# else
|
|
# define PGB_UNREACHABLE() abort()
|
|
# endif
|
|
#endif /* !defined(PGB_UNREACHABLE) */
|
|
|
|
#if !defined(PGB_UNLIKELY)
|
|
# if __has_builtin(__builtin_expect)
|
|
# define PGB_UNLIKELY(expr) __builtin_expect(!!(expr), 0)
|
|
# else
|
|
# define PGB_UNLIKELY(expr) (expr)
|
|
# endif
|
|
#endif /* !defined(PGB_UNLIKELY) */
|
|
#if !defined(PGB_LIKELY)
|
|
# if __has_builtin(__builtin_expect)
|
|
# define PGB_LIKELY(expr) __builtin_expect(!!(expr), 1)
|
|
# else
|
|
# define PGB_LIKELY(expr) (expr)
|
|
# endif
|
|
#endif /* !defined(PGB_LIKELY) */
|
|
|
|
#if PEANUT_GB_USE_INTRINSICS
|
|
/* If using MSVC, only enable intrinsics for x86 platforms*/
|
|
# if defined(_MSC_VER) && __has_include("intrin.h") && \
|
|
(defined(_M_IX86_FP) || defined(_M_AMD64) || defined(_M_X64))
|
|
/* Define intrinsic functions for MSVC. */
|
|
# include <intrin.h>
|
|
# define PGB_INTRIN_SBC(x,y,cin,res) _subborrow_u8(cin,x,y,&res)
|
|
# define PGB_INTRIN_ADC(x,y,cin,res) _addcarry_u8(cin,x,y,&res)
|
|
# endif /* MSVC */
|
|
|
|
/* Check for intrinsic functions in GCC and Clang. */
|
|
# if __has_builtin(__builtin_sub_overflow)
|
|
# define PGB_INTRIN_SBC(x,y,cin,res) __builtin_sub_overflow(x,y+cin,&res)
|
|
# define PGB_INTRIN_ADC(x,y,cin,res) __builtin_add_overflow(x,y+cin,&res)
|
|
# endif
|
|
#endif /* PEANUT_GB_USE_INTRINSICS */
|
|
|
|
#if defined(PGB_INTRIN_SBC)
|
|
# define PGB_INSTR_SBC_R8(r,cin) \
|
|
{ \
|
|
uint8_t temp; \
|
|
gb->cpu_reg.f.f_bits.c = PGB_INTRIN_SBC(gb->cpu_reg.a,r,cin,temp);\
|
|
gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0;\
|
|
gb->cpu_reg.f.f_bits.n = 1; \
|
|
gb->cpu_reg.f.f_bits.z = (temp == 0x00); \
|
|
gb->cpu_reg.a = temp; \
|
|
}
|
|
|
|
# define PGB_INSTR_CP_R8(r) \
|
|
{ \
|
|
uint8_t temp; \
|
|
gb->cpu_reg.f.f_bits.c = PGB_INTRIN_SBC(gb->cpu_reg.a,r,0,temp);\
|
|
gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0;\
|
|
gb->cpu_reg.f.f_bits.n = 1; \
|
|
gb->cpu_reg.f.f_bits.z = (temp == 0x00); \
|
|
}
|
|
#else
|
|
# define PGB_INSTR_SBC_R8(r,cin) \
|
|
{ \
|
|
uint16_t temp = gb->cpu_reg.a - (r + cin); \
|
|
gb->cpu_reg.f.f_bits.c = (temp & 0xFF00) ? 1 : 0; \
|
|
gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \
|
|
gb->cpu_reg.f.f_bits.n = 1; \
|
|
gb->cpu_reg.f.f_bits.z = ((temp & 0xFF) == 0x00); \
|
|
gb->cpu_reg.a = (temp & 0xFF); \
|
|
}
|
|
|
|
# define PGB_INSTR_CP_R8(r) \
|
|
{ \
|
|
uint16_t temp = gb->cpu_reg.a - r; \
|
|
gb->cpu_reg.f.f_bits.c = (temp & 0xFF00) ? 1 : 0; \
|
|
gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \
|
|
gb->cpu_reg.f.f_bits.n = 1; \
|
|
gb->cpu_reg.f.f_bits.z = ((temp & 0xFF) == 0x00); \
|
|
}
|
|
#endif /* PGB_INTRIN_SBC */
|
|
|
|
#if defined(PGB_INTRIN_ADC)
|
|
# define PGB_INSTR_ADC_R8(r,cin) \
|
|
{ \
|
|
uint8_t temp; \
|
|
gb->cpu_reg.f.f_bits.c = PGB_INTRIN_ADC(gb->cpu_reg.a,r,cin,temp);\
|
|
gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \
|
|
gb->cpu_reg.f.f_bits.n = 0; \
|
|
gb->cpu_reg.f.f_bits.z = (temp == 0x00); \
|
|
gb->cpu_reg.a = temp; \
|
|
}
|
|
#else
|
|
# define PGB_INSTR_ADC_R8(r,cin) \
|
|
{ \
|
|
uint16_t temp = gb->cpu_reg.a + r + cin; \
|
|
gb->cpu_reg.f.f_bits.c = (temp & 0xFF00) ? 1 : 0; \
|
|
gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \
|
|
gb->cpu_reg.f.f_bits.n = 0; \
|
|
gb->cpu_reg.f.f_bits.z = ((temp & 0xFF) == 0x00); \
|
|
gb->cpu_reg.a = (temp & 0xFF); \
|
|
}
|
|
#endif /* PGB_INTRIN_ADC */
|
|
|
|
#define PGB_INSTR_INC_R8(r) \
|
|
r++; \
|
|
gb->cpu_reg.f.f_bits.h = ((r & 0x0F) == 0x00); \
|
|
gb->cpu_reg.f.f_bits.n = 0; \
|
|
gb->cpu_reg.f.f_bits.z = (r == 0x00)
|
|
|
|
#define PGB_INSTR_DEC_R8(r) \
|
|
r--; \
|
|
gb->cpu_reg.f.f_bits.h = ((r & 0x0F) == 0x0F); \
|
|
gb->cpu_reg.f.f_bits.n = 1; \
|
|
gb->cpu_reg.f.f_bits.z = (r == 0x00)
|
|
|
|
#define PGB_INSTR_XOR_R8(r) \
|
|
gb->cpu_reg.a ^= r; \
|
|
gb->cpu_reg.f.reg = 0; \
|
|
gb->cpu_reg.f.f_bits.z = (gb->cpu_reg.a == 0x00)
|
|
|
|
#define PGB_INSTR_OR_R8(r) \
|
|
gb->cpu_reg.a |= r; \
|
|
gb->cpu_reg.f.reg = 0; \
|
|
gb->cpu_reg.f.f_bits.z = (gb->cpu_reg.a == 0x00)
|
|
|
|
#define PGB_INSTR_AND_R8(r) \
|
|
gb->cpu_reg.a &= r; \
|
|
gb->cpu_reg.f.reg = 0; \
|
|
gb->cpu_reg.f.f_bits.z = (gb->cpu_reg.a == 0x00); \
|
|
gb->cpu_reg.f.f_bits.h = 1
|
|
|
|
#if PEANUT_GB_IS_LITTLE_ENDIAN
|
|
# define PEANUT_GB_GET_LSB16(x) (x & 0xFF)
|
|
# define PEANUT_GB_GET_MSB16(x) (x >> 8)
|
|
# define PEANUT_GB_GET_MSN16(x) (x >> 12)
|
|
# define PEANUT_GB_U8_TO_U16(h,l) ((l) | ((h) << 8))
|
|
#else
|
|
# define PEANUT_GB_GET_LSB16(x) (x >> 8)
|
|
# define PEANUT_GB_GET_MSB16(x) (x & 0xFF)
|
|
# define PEANUT_GB_GET_MSN16(x) ((x & 0xF0) >> 4)
|
|
# define PEANUT_GB_U8_TO_U16(h,l) ((h) | ((l) << 8))
|
|
#endif
|
|
|
|
struct cpu_registers_s
|
|
{
|
|
/* Change register order if big endian.
|
|
* Macro receives registers in little endian order. */
|
|
#if PEANUT_GB_IS_LITTLE_ENDIAN
|
|
# define PEANUT_GB_LE_REG(x,y) x,y
|
|
#else
|
|
# define PEANUT_GB_LE_REG(x,y) y,x
|
|
#endif
|
|
/* Define specific bits of Flag register. */
|
|
union {
|
|
struct {
|
|
uint8_t : 4; /* Unused. */
|
|
uint8_t c: 1; /* Carry flag. */
|
|
uint8_t h: 1; /* Half carry flag. */
|
|
uint8_t n: 1; /* Add/sub flag. */
|
|
uint8_t z: 1; /* Zero flag. */
|
|
} f_bits;
|
|
uint8_t reg;
|
|
} f;
|
|
uint8_t a;
|
|
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
uint8_t PEANUT_GB_LE_REG(c,b);
|
|
} bytes;
|
|
uint16_t reg;
|
|
} bc;
|
|
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
uint8_t PEANUT_GB_LE_REG(e,d);
|
|
} bytes;
|
|
uint16_t reg;
|
|
} de;
|
|
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
uint8_t PEANUT_GB_LE_REG(l,h);
|
|
} bytes;
|
|
uint16_t reg;
|
|
} hl;
|
|
|
|
/* Stack pointer */
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
uint8_t PEANUT_GB_LE_REG(p, s);
|
|
} bytes;
|
|
uint16_t reg;
|
|
} sp;
|
|
|
|
/* Program counter */
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
uint8_t PEANUT_GB_LE_REG(c, p);
|
|
} bytes;
|
|
uint16_t reg;
|
|
} pc;
|
|
#undef PEANUT_GB_LE_REG
|
|
};
|
|
|
|
struct count_s
|
|
{
|
|
uint_fast16_t lcd_count; /* LCD Timing */
|
|
uint_fast16_t div_count; /* Divider Register Counter */
|
|
uint_fast16_t tima_count; /* Timer Counter */
|
|
uint_fast16_t serial_count; /* Serial Counter */
|
|
uint_fast32_t rtc_count; /* RTC Counter */
|
|
uint_fast32_t lcd_off_count; /* Cycles LCD has been disabled */
|
|
};
|
|
|
|
#if ENABLE_LCD
|
|
/* Bit mask for the shade of pixel to display */
|
|
#define LCD_COLOUR 0x03
|
|
|
|
# if PEANUT_GB_12_COLOUR
|
|
/**
|
|
* Bit mask for whether a pixel is OBJ0, OBJ1, or BG. Each may have a different
|
|
* palette when playing a DMG game on CGB.
|
|
*/
|
|
#define LCD_PALETTE_OBJ 0x10
|
|
#define LCD_PALETTE_BG 0x20
|
|
/**
|
|
* Bit mask for the two bits listed above.
|
|
* LCD_PALETTE_ALL == 0b00 --> OBJ0
|
|
* LCD_PALETTE_ALL == 0b01 --> OBJ1
|
|
* LCD_PALETTE_ALL == 0b10 --> BG
|
|
* LCD_PALETTE_ALL == 0b11 --> NOT POSSIBLE
|
|
*/
|
|
#define LCD_PALETTE_ALL 0x30
|
|
# endif
|
|
#endif
|
|
|
|
/**
|
|
* Errors that may occur during emulation.
|
|
*/
|
|
enum gb_error_e
|
|
{
|
|
GB_UNKNOWN_ERROR = 0,
|
|
GB_INVALID_OPCODE = 1,
|
|
GB_INVALID_READ = 2,
|
|
GB_INVALID_WRITE = 3,
|
|
|
|
/* GB_HALT_FOREVER is deprecated and will no longer be issued as an
|
|
* error by Peanut-GB. */
|
|
GB_HALT_FOREVER PGB_DEPRECATED("Error no longer issued by Peanut-GB") = 4,
|
|
|
|
GB_INVALID_MAX = 5
|
|
};
|
|
|
|
/**
|
|
* Errors that may occur during library initialisation.
|
|
*/
|
|
enum gb_init_error_e
|
|
{
|
|
GB_INIT_NO_ERROR = 0,
|
|
GB_INIT_CARTRIDGE_UNSUPPORTED,
|
|
GB_INIT_INVALID_CHECKSUM,
|
|
|
|
GB_INIT_INVALID_MAX
|
|
};
|
|
|
|
/**
|
|
* Return codes for serial receive function, mainly for clarity.
|
|
*/
|
|
enum gb_serial_rx_ret_e
|
|
{
|
|
GB_SERIAL_RX_SUCCESS = 0,
|
|
GB_SERIAL_RX_NO_CONNECTION = 1
|
|
};
|
|
|
|
union cart_rtc
|
|
{
|
|
struct
|
|
{
|
|
uint8_t sec;
|
|
uint8_t min;
|
|
uint8_t hour;
|
|
uint8_t yday;
|
|
uint8_t high;
|
|
} reg;
|
|
uint8_t bytes[5];
|
|
};
|
|
|
|
/**
|
|
* Emulator context.
|
|
*
|
|
* Only values within the `direct` struct may be modified directly by the
|
|
* front-end implementation. Other variables must not be modified.
|
|
*/
|
|
struct gb_s
|
|
{
|
|
/**
|
|
* Return byte from ROM at given address.
|
|
*
|
|
* \param gb_s emulator context
|
|
* \param addr address
|
|
* \return byte at address in ROM
|
|
*/
|
|
uint8_t (*gb_rom_read)(struct gb_s*, const uint_fast32_t addr);
|
|
|
|
/**
|
|
* Return byte from cart RAM at given address.
|
|
*
|
|
* \param gb_s emulator context
|
|
* \param addr address
|
|
* \return byte at address in RAM
|
|
*/
|
|
uint8_t (*gb_cart_ram_read)(struct gb_s*, const uint_fast32_t addr);
|
|
|
|
/**
|
|
* Write byte to cart RAM at given address.
|
|
*
|
|
* \param gb_s emulator context
|
|
* \param addr address
|
|
* \param val value to write to address in RAM
|
|
*/
|
|
void (*gb_cart_ram_write)(struct gb_s*, const uint_fast32_t addr,
|
|
const uint8_t val);
|
|
|
|
/**
|
|
* Notify front-end of error.
|
|
*
|
|
* \param gb_s emulator context
|
|
* \param gb_error_e error code
|
|
* \param addr address of where error occurred
|
|
*/
|
|
void (*gb_error)(struct gb_s*, const enum gb_error_e, const uint16_t addr);
|
|
|
|
/* Transmit one byte and return the received byte. */
|
|
void (*gb_serial_tx)(struct gb_s*, const uint8_t tx);
|
|
enum gb_serial_rx_ret_e (*gb_serial_rx)(struct gb_s*, uint8_t* rx);
|
|
|
|
/* Read byte from boot ROM at given address. */
|
|
uint8_t (*gb_bootrom_read)(struct gb_s*, const uint_fast16_t addr);
|
|
|
|
struct
|
|
{
|
|
bool gb_halt : 1;
|
|
bool gb_ime : 1;
|
|
/* gb_frame is set when 0.016742706298828125 seconds have
|
|
* passed. It is likely that a new frame has been drawn since
|
|
* then, but it is possible that the LCD was switched off and
|
|
* nothing was drawn. */
|
|
bool gb_frame : 1;
|
|
bool lcd_blank : 1;
|
|
/* Set if MBC3O cart is used. */
|
|
bool cart_is_mbc3O : 1;
|
|
};
|
|
|
|
/* Cartridge information:
|
|
* Memory Bank Controller (MBC) type. */
|
|
int8_t mbc;
|
|
/* Whether the MBC has internal RAM. */
|
|
uint8_t cart_ram;
|
|
/* Number of ROM banks in cartridge. */
|
|
uint16_t num_rom_banks_mask;
|
|
/* Number of RAM banks in cartridge. Ignore for MBC2. */
|
|
uint8_t num_ram_banks;
|
|
|
|
uint16_t selected_rom_bank;
|
|
/* WRAM and VRAM bank selection not available. */
|
|
uint8_t cart_ram_bank;
|
|
uint8_t enable_cart_ram;
|
|
/* Cartridge ROM/RAM mode select. */
|
|
uint8_t cart_mode_select;
|
|
|
|
union cart_rtc rtc_latched, rtc_real;
|
|
|
|
struct cpu_registers_s cpu_reg;
|
|
//struct gb_registers_s gb_reg;
|
|
struct count_s counter;
|
|
|
|
/* TODO: Allow implementation to allocate WRAM, VRAM and Frame Buffer. */
|
|
uint8_t wram[WRAM_SIZE];
|
|
uint8_t vram[VRAM_SIZE];
|
|
uint8_t oam[OAM_SIZE];
|
|
uint8_t hram_io[HRAM_IO_SIZE];
|
|
|
|
struct
|
|
{
|
|
/**
|
|
* Draw line on screen.
|
|
*
|
|
* \param gb_s emulator context
|
|
* \param pixels The 160 pixels to draw.
|
|
* Bits 1-0 are the colour to draw.
|
|
* Bits 5-4 are the palette, where:
|
|
* OBJ0 = 0b00,
|
|
* OBJ1 = 0b01,
|
|
* BG = 0b10
|
|
* Other bits are undefined.
|
|
* Bits 5-4 are only required by front-ends
|
|
* which want to use a different colour for
|
|
* different object palettes. This is what
|
|
* the Game Boy Color (CGB) does to DMG
|
|
* games.
|
|
* \param line Line to draw pixels on. This is
|
|
* guaranteed to be between 0-144 inclusive.
|
|
*/
|
|
void (*lcd_draw_line)(struct gb_s *gb,
|
|
const uint8_t *pixels,
|
|
const uint_fast8_t line);
|
|
|
|
/* Palettes */
|
|
uint8_t bg_palette[4];
|
|
uint8_t sp_palette[8];
|
|
|
|
uint8_t window_clear;
|
|
uint8_t WY;
|
|
|
|
/* Only support 30fps frame skip. */
|
|
bool frame_skip_count : 1;
|
|
bool interlace_count : 1;
|
|
} display;
|
|
|
|
/**
|
|
* Variables that may be modified directly by the front-end.
|
|
* This method seems to be easier and possibly less overhead than
|
|
* calling a function to modify these variables each time.
|
|
*
|
|
* None of this is thread-safe.
|
|
*/
|
|
struct
|
|
{
|
|
/* Set to enable interlacing. Interlacing will start immediately
|
|
* (at the next line drawing).
|
|
*/
|
|
bool interlace : 1;
|
|
bool frame_skip : 1;
|
|
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
/* Using this bitfield is deprecated due to
|
|
* portability concerns. It is recommended to
|
|
* use the JOYPAD_* defines instead.
|
|
*/
|
|
bool a : 1;
|
|
bool b : 1;
|
|
bool select : 1;
|
|
bool start : 1;
|
|
bool right : 1;
|
|
bool left : 1;
|
|
bool up : 1;
|
|
bool down : 1;
|
|
} joypad_bits;
|
|
uint8_t joypad;
|
|
};
|
|
|
|
/* Implementation defined data. Set to NULL if not required. */
|
|
void *priv;
|
|
} direct;
|
|
};
|
|
|
|
#ifndef PEANUT_GB_HEADER_ONLY
|
|
|
|
#define IO_JOYP 0x00
|
|
#define IO_SB 0x01
|
|
#define IO_SC 0x02
|
|
#define IO_DIV 0x04
|
|
#define IO_TIMA 0x05
|
|
#define IO_TMA 0x06
|
|
#define IO_TAC 0x07
|
|
#define IO_IF 0x0F
|
|
#define IO_LCDC 0x40
|
|
#define IO_STAT 0x41
|
|
#define IO_SCY 0x42
|
|
#define IO_SCX 0x43
|
|
#define IO_LY 0x44
|
|
#define IO_LYC 0x45
|
|
#define IO_DMA 0x46
|
|
#define IO_BGP 0x47
|
|
#define IO_OBP0 0x48
|
|
#define IO_OBP1 0x49
|
|
#define IO_WY 0x4A
|
|
#define IO_WX 0x4B
|
|
#define IO_BOOT 0x50
|
|
#define IO_IE 0xFF
|
|
|
|
#define IO_TAC_RATE_MASK 0x3
|
|
#define IO_TAC_ENABLE_MASK 0x4
|
|
|
|
/* LCD Mode defines. */
|
|
#define IO_STAT_MODE_HBLANK 0
|
|
#define IO_STAT_MODE_VBLANK 1
|
|
#define IO_STAT_MODE_OAM_SCAN 2
|
|
#define IO_STAT_MODE_LCD_DRAW 3
|
|
#define IO_STAT_MODE_VBLANK_OR_TRANSFER_MASK 0x1
|
|
|
|
/**
|
|
* Internal function used to read bytes.
|
|
* addr is host platform endian.
|
|
*/
|
|
uint8_t __gb_read(struct gb_s *gb, uint16_t addr)
|
|
{
|
|
switch(PEANUT_GB_GET_MSN16(addr))
|
|
{
|
|
case 0x0:
|
|
/* IO_BOOT is only set to 1 if gb->gb_bootrom_read was not NULL
|
|
* on reset. */
|
|
if(gb->hram_io[IO_BOOT] == 0 && addr < 0x0100)
|
|
{
|
|
return gb->gb_bootrom_read(gb, addr);
|
|
}
|
|
|
|
/* Fallthrough */
|
|
case 0x1:
|
|
case 0x2:
|
|
case 0x3:
|
|
return gb->gb_rom_read(gb, addr);
|
|
|
|
case 0x4:
|
|
case 0x5:
|
|
case 0x6:
|
|
case 0x7:
|
|
if(gb->mbc == 1 && gb->cart_mode_select)
|
|
return gb->gb_rom_read(gb,
|
|
addr + ((gb->selected_rom_bank & 0x1F) - 1) * ROM_BANK_SIZE);
|
|
else
|
|
return gb->gb_rom_read(gb, addr + (gb->selected_rom_bank - 1) * ROM_BANK_SIZE);
|
|
|
|
case 0x8:
|
|
case 0x9:
|
|
return gb->vram[addr - VRAM_ADDR];
|
|
|
|
case 0xA:
|
|
case 0xB:
|
|
if(gb->mbc == 3 && gb->cart_ram_bank >= 0x08)
|
|
{
|
|
return gb->rtc_latched.bytes[gb->cart_ram_bank - 0x08];
|
|
}
|
|
else if(gb->cart_ram && gb->enable_cart_ram)
|
|
{
|
|
if(gb->mbc == 2)
|
|
{
|
|
/* Only 9 bits are available in address. */
|
|
addr &= 0x1FF;
|
|
return gb->gb_cart_ram_read(gb, addr);
|
|
}
|
|
else if((gb->cart_mode_select || gb->mbc != 1) &&
|
|
gb->cart_ram_bank < gb->num_ram_banks)
|
|
{
|
|
return gb->gb_cart_ram_read(gb, addr - CART_RAM_ADDR +
|
|
(gb->cart_ram_bank * CRAM_BANK_SIZE));
|
|
}
|
|
else
|
|
return gb->gb_cart_ram_read(gb, addr - CART_RAM_ADDR);
|
|
}
|
|
|
|
return 0xFF;
|
|
|
|
case 0xC:
|
|
case 0xD:
|
|
return gb->wram[addr - WRAM_0_ADDR];
|
|
|
|
case 0xE:
|
|
return gb->wram[addr - ECHO_ADDR];
|
|
|
|
case 0xF:
|
|
if(addr < OAM_ADDR)
|
|
return gb->wram[addr - ECHO_ADDR];
|
|
|
|
if(addr < UNUSED_ADDR)
|
|
return gb->oam[addr - OAM_ADDR];
|
|
|
|
/* Unusable memory area. Reading from this area returns 0xFF.*/
|
|
if(addr < IO_ADDR)
|
|
return 0xFF;
|
|
|
|
/* APU registers. */
|
|
if((addr >= 0xFF10) && (addr <= 0xFF3F))
|
|
{
|
|
#if ENABLE_SOUND
|
|
return audio_read(addr);
|
|
#else
|
|
static const uint8_t ortab[] = {
|
|
0x80, 0x3f, 0x00, 0xff, 0xbf,
|
|
0xff, 0x3f, 0x00, 0xff, 0xbf,
|
|
0x7f, 0xff, 0x9f, 0xff, 0xbf,
|
|
0xff, 0xff, 0x00, 0x00, 0xbf,
|
|
0x00, 0x00, 0x70,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
return gb->hram_io[addr - IO_ADDR] | ortab[addr - IO_ADDR];
|
|
#endif
|
|
}
|
|
|
|
/* HRAM */
|
|
if(addr >= IO_ADDR)
|
|
return gb->hram_io[addr - IO_ADDR];
|
|
}
|
|
|
|
|
|
/* Return address that caused read error. */
|
|
(gb->gb_error)(gb, GB_INVALID_READ, addr);
|
|
PGB_UNREACHABLE();
|
|
}
|
|
|
|
/**
|
|
* Internal function used to write bytes.
|
|
*/
|
|
void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val)
|
|
{
|
|
switch(PEANUT_GB_GET_MSN16(addr))
|
|
{
|
|
case 0x0:
|
|
case 0x1:
|
|
/* Set RAM enable bit. MBC2 is handled in fall-through. */
|
|
if(gb->mbc > 0 && gb->mbc != 2 && gb->cart_ram)
|
|
{
|
|
gb->enable_cart_ram = ((val & 0x0F) == 0x0A);
|
|
return;
|
|
}
|
|
|
|
/* Intentional fall through. */
|
|
case 0x2:
|
|
if(gb->mbc == 5)
|
|
{
|
|
gb->selected_rom_bank = (gb->selected_rom_bank & 0x100) | val;
|
|
gb->selected_rom_bank =
|
|
gb->selected_rom_bank & gb->num_rom_banks_mask;
|
|
return;
|
|
}
|
|
|
|
/* Intentional fall through. */
|
|
case 0x3:
|
|
if(gb->mbc == 1)
|
|
{
|
|
//selected_rom_bank = val & 0x7;
|
|
gb->selected_rom_bank = (val & 0x1F) | (gb->selected_rom_bank & 0x60);
|
|
|
|
if((gb->selected_rom_bank & 0x1F) == 0x00)
|
|
gb->selected_rom_bank++;
|
|
}
|
|
else if(gb->mbc == 2)
|
|
{
|
|
/* If bit 8 is 1, then set ROM bank number. */
|
|
if(addr & 0x100)
|
|
{
|
|
gb->selected_rom_bank = val & 0x0F;
|
|
/* Setting ROM bank to 0, sets it to 1. */
|
|
if(!gb->selected_rom_bank)
|
|
gb->selected_rom_bank++;
|
|
}
|
|
/* Otherwise set whether RAM is enabled or not. */
|
|
else
|
|
{
|
|
gb->enable_cart_ram = ((val & 0x0F) == 0x0A);
|
|
return;
|
|
}
|
|
}
|
|
else if(gb->mbc == 3)
|
|
{
|
|
gb->selected_rom_bank = val;
|
|
if(!gb->cart_is_mbc3O)
|
|
gb->selected_rom_bank = val & 0x7F;
|
|
|
|
if(!gb->selected_rom_bank)
|
|
gb->selected_rom_bank++;
|
|
}
|
|
else if(gb->mbc == 5)
|
|
gb->selected_rom_bank = (val & 0x01) << 8 | (gb->selected_rom_bank & 0xFF);
|
|
|
|
gb->selected_rom_bank = gb->selected_rom_bank & gb->num_rom_banks_mask;
|
|
return;
|
|
|
|
case 0x4:
|
|
case 0x5:
|
|
if(gb->mbc == 1)
|
|
{
|
|
gb->cart_ram_bank = (val & 3);
|
|
gb->selected_rom_bank = ((val & 3) << 5) | (gb->selected_rom_bank & 0x1F);
|
|
gb->selected_rom_bank = gb->selected_rom_bank & gb->num_rom_banks_mask;
|
|
}
|
|
else if(gb->mbc == 3)
|
|
{
|
|
gb->cart_ram_bank = val;
|
|
/* If not using MBC3, only the first 4 cart RAM banks are useable.
|
|
* If cart RAM bank 0x8-0xC are selected, then the corresponding
|
|
* RTC register is selected instead of cart RAM. */
|
|
if(!gb->cart_is_mbc3O && gb->cart_ram_bank < 0x8)
|
|
gb->cart_ram_bank &= 0x3;
|
|
}
|
|
|
|
else if(gb->mbc == 5)
|
|
gb->cart_ram_bank = (val & 0x0F);
|
|
|
|
return;
|
|
|
|
case 0x6:
|
|
case 0x7:
|
|
val &= 1;
|
|
if(gb->mbc == 3 && val && gb->cart_mode_select == 0)
|
|
memcpy(&gb->rtc_latched.bytes, &gb->rtc_real.bytes, sizeof(gb->rtc_latched.bytes));
|
|
|
|
/* Set banking mode select. */
|
|
gb->cart_mode_select = val;
|
|
return;
|
|
|
|
case 0x8:
|
|
case 0x9:
|
|
gb->vram[addr - VRAM_ADDR] = val;
|
|
return;
|
|
|
|
case 0xA:
|
|
case 0xB:
|
|
if(gb->mbc == 3 && gb->cart_ram_bank >= 0x08)
|
|
{
|
|
const uint8_t rtc_reg_mask[5] = {
|
|
0x3F, 0x3F, 0x1F, 0xFF, 0xC1
|
|
};
|
|
uint8_t reg = gb->cart_ram_bank - 0x08;
|
|
//if(reg == 0) gb->counter.rtc_count = 0;
|
|
|
|
gb->rtc_real.bytes[reg] = val & rtc_reg_mask[reg];
|
|
}
|
|
/* Do not write to RAM if unavailable or disabled. */
|
|
else if(gb->cart_ram && gb->enable_cart_ram)
|
|
{
|
|
if(gb->mbc == 2)
|
|
{
|
|
/* Only 9 bits are available in address. */
|
|
addr &= 0x1FF;
|
|
/* Data is only 4 bits wide in MBC2 RAM. */
|
|
val &= 0x0F;
|
|
/* Upper nibble is set to high. */
|
|
val |= 0xF0;
|
|
gb->gb_cart_ram_write(gb, addr, val);
|
|
}
|
|
/* If cart has RAM, use this. If MBC1, only the first
|
|
* RAM bank can be written to if the advanced banking
|
|
* mode is selected. */
|
|
else if(((gb->mbc == 1 && gb->cart_mode_select) || gb->mbc != 1) &&
|
|
gb->cart_ram_bank < gb->num_ram_banks)
|
|
{
|
|
gb->gb_cart_ram_write(gb,
|
|
addr - CART_RAM_ADDR + (gb->cart_ram_bank * CRAM_BANK_SIZE), val);
|
|
}
|
|
else if(gb->num_ram_banks)
|
|
gb->gb_cart_ram_write(gb, addr - CART_RAM_ADDR, val);
|
|
}
|
|
|
|
return;
|
|
|
|
case 0xC:
|
|
gb->wram[addr - WRAM_0_ADDR] = val;
|
|
return;
|
|
|
|
case 0xD:
|
|
gb->wram[addr - WRAM_1_ADDR + WRAM_BANK_SIZE] = val;
|
|
return;
|
|
|
|
case 0xE:
|
|
gb->wram[addr - ECHO_ADDR] = val;
|
|
return;
|
|
|
|
case 0xF:
|
|
if(addr < OAM_ADDR)
|
|
{
|
|
gb->wram[addr - ECHO_ADDR] = val;
|
|
return;
|
|
}
|
|
|
|
if(addr < UNUSED_ADDR)
|
|
{
|
|
gb->oam[addr - OAM_ADDR] = val;
|
|
return;
|
|
}
|
|
|
|
/* Unusable memory area. */
|
|
if(addr < IO_ADDR)
|
|
return;
|
|
|
|
if(HRAM_ADDR <= addr && addr < INTR_EN_ADDR)
|
|
{
|
|
gb->hram_io[addr - IO_ADDR] = val;
|
|
return;
|
|
}
|
|
|
|
if((addr >= 0xFF10) && (addr <= 0xFF3F))
|
|
{
|
|
#if ENABLE_SOUND
|
|
audio_write(addr, val);
|
|
#else
|
|
gb->hram_io[addr - IO_ADDR] = val;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/* IO and Interrupts. */
|
|
switch(PEANUT_GB_GET_LSB16(addr))
|
|
{
|
|
/* Joypad */
|
|
case 0x00:
|
|
/* Only bits 5 and 4 are R/W.
|
|
* The lower bits are overwritten later, and the two most
|
|
* significant bits are unused. */
|
|
gb->hram_io[IO_JOYP] = val;
|
|
|
|
/* Direction keys selected */
|
|
if((gb->hram_io[IO_JOYP] & 0x10) == 0)
|
|
gb->hram_io[IO_JOYP] |= (gb->direct.joypad >> 4);
|
|
/* Button keys selected */
|
|
else
|
|
gb->hram_io[IO_JOYP] |= (gb->direct.joypad & 0x0F);
|
|
|
|
return;
|
|
|
|
/* Serial */
|
|
case 0x01:
|
|
gb->hram_io[IO_SB] = val;
|
|
return;
|
|
|
|
case 0x02:
|
|
gb->hram_io[IO_SC] = val;
|
|
return;
|
|
|
|
/* Timer Registers */
|
|
case 0x04:
|
|
gb->hram_io[IO_DIV] = 0x00;
|
|
return;
|
|
|
|
case 0x05:
|
|
gb->hram_io[IO_TIMA] = val;
|
|
return;
|
|
|
|
case 0x06:
|
|
gb->hram_io[IO_TMA] = val;
|
|
return;
|
|
|
|
case 0x07:
|
|
gb->hram_io[IO_TAC] = val;
|
|
return;
|
|
|
|
/* Interrupt Flag Register */
|
|
case 0x0F:
|
|
gb->hram_io[IO_IF] = (val | 0xE0);
|
|
return;
|
|
|
|
/* LCD Registers */
|
|
case 0x40:
|
|
{
|
|
uint8_t lcd_enabled;
|
|
|
|
/* Check if LCD is already enabled. */
|
|
lcd_enabled = (gb->hram_io[IO_LCDC] & LCDC_ENABLE);
|
|
|
|
gb->hram_io[IO_LCDC] = val;
|
|
|
|
/* Check if LCD is going to be switched on. */
|
|
if (!lcd_enabled && (val & LCDC_ENABLE))
|
|
{
|
|
gb->lcd_blank = true;
|
|
}
|
|
/* Check if LCD is being switched off. */
|
|
else if (lcd_enabled && !(val & LCDC_ENABLE))
|
|
{
|
|
/* Peanut-GB will happily turn off LCD outside
|
|
* of VBLANK even though this damages real
|
|
* hardware. */
|
|
|
|
/* Set LCD to Mode 0. */
|
|
gb->hram_io[IO_STAT] =
|
|
(gb->hram_io[IO_STAT] & ~STAT_MODE) |
|
|
IO_STAT_MODE_HBLANK;
|
|
/* LY fixed to 0 when LCD turned off. */
|
|
gb->hram_io[IO_LY] = 0;
|
|
/* Keep track of lcd_count to correctly track
|
|
* passing time. */
|
|
gb->counter.lcd_off_count += gb->counter.lcd_count;
|
|
/* Reset LCD timer, since the LCD starts from
|
|
* the beginning on power on. */
|
|
gb->counter.lcd_count = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x41:
|
|
gb->hram_io[IO_STAT] = (val & STAT_USER_BITS) | (gb->hram_io[IO_STAT] & STAT_MODE) | 0x80;
|
|
return;
|
|
|
|
case 0x42:
|
|
gb->hram_io[IO_SCY] = val;
|
|
return;
|
|
|
|
case 0x43:
|
|
gb->hram_io[IO_SCX] = val;
|
|
return;
|
|
|
|
/* LY (0xFF44) is read only. */
|
|
case 0x45:
|
|
gb->hram_io[IO_LYC] = val;
|
|
return;
|
|
|
|
/* DMA Register */
|
|
case 0x46:
|
|
{
|
|
uint16_t dma_addr;
|
|
uint16_t i;
|
|
|
|
dma_addr = (uint_fast16_t)val << 8;
|
|
gb->hram_io[IO_DMA] = val;
|
|
|
|
for(i = 0; i < OAM_SIZE; i++)
|
|
{
|
|
gb->oam[i] = __gb_read(gb, dma_addr + i);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* DMG Palette Registers */
|
|
case 0x47:
|
|
gb->hram_io[IO_BGP] = val;
|
|
gb->display.bg_palette[0] = (gb->hram_io[IO_BGP] & 0x03);
|
|
gb->display.bg_palette[1] = (gb->hram_io[IO_BGP] >> 2) & 0x03;
|
|
gb->display.bg_palette[2] = (gb->hram_io[IO_BGP] >> 4) & 0x03;
|
|
gb->display.bg_palette[3] = (gb->hram_io[IO_BGP] >> 6) & 0x03;
|
|
return;
|
|
|
|
case 0x48:
|
|
gb->hram_io[IO_OBP0] = val;
|
|
gb->display.sp_palette[0] = (gb->hram_io[IO_OBP0] & 0x03);
|
|
gb->display.sp_palette[1] = (gb->hram_io[IO_OBP0] >> 2) & 0x03;
|
|
gb->display.sp_palette[2] = (gb->hram_io[IO_OBP0] >> 4) & 0x03;
|
|
gb->display.sp_palette[3] = (gb->hram_io[IO_OBP0] >> 6) & 0x03;
|
|
return;
|
|
|
|
case 0x49:
|
|
gb->hram_io[IO_OBP1] = val;
|
|
gb->display.sp_palette[4] = (gb->hram_io[IO_OBP1] & 0x03);
|
|
gb->display.sp_palette[5] = (gb->hram_io[IO_OBP1] >> 2) & 0x03;
|
|
gb->display.sp_palette[6] = (gb->hram_io[IO_OBP1] >> 4) & 0x03;
|
|
gb->display.sp_palette[7] = (gb->hram_io[IO_OBP1] >> 6) & 0x03;
|
|
return;
|
|
|
|
/* Window Position Registers */
|
|
case 0x4A:
|
|
gb->hram_io[IO_WY] = val;
|
|
return;
|
|
|
|
case 0x4B:
|
|
gb->hram_io[IO_WX] = val;
|
|
return;
|
|
|
|
/* Turn off boot ROM */
|
|
case 0x50:
|
|
gb->hram_io[IO_BOOT] = 0x01;
|
|
return;
|
|
|
|
/* Interrupt Enable Register */
|
|
case 0xFF:
|
|
gb->hram_io[IO_IE] = val;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Invalid writes are ignored. */
|
|
return;
|
|
}
|
|
|
|
uint8_t __gb_execute_cb(struct gb_s *gb)
|
|
{
|
|
uint8_t inst_cycles;
|
|
uint8_t cbop = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
uint8_t r = (cbop & 0x7);
|
|
uint8_t b = (cbop >> 3) & 0x7;
|
|
uint8_t d = (cbop >> 3) & 0x1;
|
|
uint8_t val;
|
|
uint8_t writeback = 1;
|
|
|
|
inst_cycles = 8;
|
|
/* Add an additional 8 cycles to these sets of instructions. */
|
|
switch(cbop & 0xC7)
|
|
{
|
|
case 0x06:
|
|
case 0x86:
|
|
case 0xC6:
|
|
inst_cycles += 8;
|
|
break;
|
|
case 0x46:
|
|
inst_cycles += 4;
|
|
break;
|
|
}
|
|
|
|
switch(r)
|
|
{
|
|
case 0:
|
|
val = gb->cpu_reg.bc.bytes.b;
|
|
break;
|
|
|
|
case 1:
|
|
val = gb->cpu_reg.bc.bytes.c;
|
|
break;
|
|
|
|
case 2:
|
|
val = gb->cpu_reg.de.bytes.d;
|
|
break;
|
|
|
|
case 3:
|
|
val = gb->cpu_reg.de.bytes.e;
|
|
break;
|
|
|
|
case 4:
|
|
val = gb->cpu_reg.hl.bytes.h;
|
|
break;
|
|
|
|
case 5:
|
|
val = gb->cpu_reg.hl.bytes.l;
|
|
break;
|
|
|
|
case 6:
|
|
val = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
break;
|
|
|
|
/* Only values 0-7 are possible here, so we make the final case
|
|
* default to satisfy -Wmaybe-uninitialized warning. */
|
|
default:
|
|
val = gb->cpu_reg.a;
|
|
break;
|
|
}
|
|
|
|
switch(cbop >> 6)
|
|
{
|
|
case 0x0:
|
|
cbop = (cbop >> 4) & 0x3;
|
|
|
|
switch(cbop)
|
|
{
|
|
case 0x0: /* RdC R */
|
|
case 0x1: /* Rd R */
|
|
if(d) /* RRC R / RR R */
|
|
{
|
|
uint8_t temp = val;
|
|
val = (val >> 1);
|
|
val |= cbop ? (gb->cpu_reg.f.f_bits.c << 7) : (temp << 7);
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.z = (val == 0x00);
|
|
gb->cpu_reg.f.f_bits.c = (temp & 0x01);
|
|
}
|
|
else /* RLC R / RL R */
|
|
{
|
|
uint8_t temp = val;
|
|
val = (val << 1);
|
|
val |= cbop ? gb->cpu_reg.f.f_bits.c : (temp >> 7);
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.z = (val == 0x00);
|
|
gb->cpu_reg.f.f_bits.c = (temp >> 7);
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2:
|
|
if(d) /* SRA R */
|
|
{
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.c = val & 0x01;
|
|
val = (val >> 1) | (val & 0x80);
|
|
gb->cpu_reg.f.f_bits.z = (val == 0x00);
|
|
}
|
|
else /* SLA R */
|
|
{
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.c = (val >> 7);
|
|
val = val << 1;
|
|
gb->cpu_reg.f.f_bits.z = (val == 0x00);
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x3:
|
|
if(d) /* SRL R */
|
|
{
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.c = val & 0x01;
|
|
val = val >> 1;
|
|
gb->cpu_reg.f.f_bits.z = (val == 0x00);
|
|
}
|
|
else /* SWAP R */
|
|
{
|
|
uint8_t temp = (val >> 4) & 0x0F;
|
|
temp |= (val << 4) & 0xF0;
|
|
val = temp;
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.z = (val == 0x00);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x1: /* BIT B, R */
|
|
gb->cpu_reg.f.f_bits.z = !((val >> b) & 0x1);
|
|
gb->cpu_reg.f.f_bits.n = 0;
|
|
gb->cpu_reg.f.f_bits.h = 1;
|
|
writeback = 0;
|
|
break;
|
|
|
|
case 0x2: /* RES B, R */
|
|
val &= (0xFE << b) | (0xFF >> (8 - b));
|
|
break;
|
|
|
|
case 0x3: /* SET B, R */
|
|
val |= (0x1 << b);
|
|
break;
|
|
}
|
|
|
|
if(writeback)
|
|
{
|
|
switch(r)
|
|
{
|
|
case 0:
|
|
gb->cpu_reg.bc.bytes.b = val;
|
|
break;
|
|
|
|
case 1:
|
|
gb->cpu_reg.bc.bytes.c = val;
|
|
break;
|
|
|
|
case 2:
|
|
gb->cpu_reg.de.bytes.d = val;
|
|
break;
|
|
|
|
case 3:
|
|
gb->cpu_reg.de.bytes.e = val;
|
|
break;
|
|
|
|
case 4:
|
|
gb->cpu_reg.hl.bytes.h = val;
|
|
break;
|
|
|
|
case 5:
|
|
gb->cpu_reg.hl.bytes.l = val;
|
|
break;
|
|
|
|
case 6:
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, val);
|
|
break;
|
|
|
|
case 7:
|
|
gb->cpu_reg.a = val;
|
|
break;
|
|
}
|
|
}
|
|
return inst_cycles;
|
|
}
|
|
|
|
#if ENABLE_LCD
|
|
struct sprite_data {
|
|
uint8_t sprite_number;
|
|
uint8_t x;
|
|
};
|
|
|
|
#if PEANUT_GB_HIGH_LCD_ACCURACY
|
|
static int compare_sprites(const struct sprite_data *const sd1, const struct sprite_data *const sd2)
|
|
{
|
|
int x_res;
|
|
|
|
x_res = (int)sd1->x - (int)sd2->x;
|
|
if(x_res != 0)
|
|
return x_res;
|
|
|
|
return (int)sd1->sprite_number - (int)sd2->sprite_number;
|
|
}
|
|
#endif
|
|
|
|
void __gb_draw_line(struct gb_s *gb)
|
|
{
|
|
uint8_t pixels[160] = {0};
|
|
|
|
/* If LCD not initialised by front-end, don't render anything. */
|
|
if(gb->display.lcd_draw_line == NULL)
|
|
return;
|
|
|
|
if(gb->direct.frame_skip && !gb->display.frame_skip_count)
|
|
return;
|
|
|
|
/* If interlaced mode is activated, check if we need to draw the current
|
|
* line. */
|
|
if(gb->direct.interlace)
|
|
{
|
|
if((!gb->display.interlace_count
|
|
&& (gb->hram_io[IO_LY] & 1) == 0)
|
|
|| (gb->display.interlace_count
|
|
&& (gb->hram_io[IO_LY] & 1) == 1))
|
|
{
|
|
/* Compensate for missing window draw if required. */
|
|
if(gb->hram_io[IO_LCDC] & LCDC_WINDOW_ENABLE
|
|
&& gb->hram_io[IO_LY] >= gb->display.WY
|
|
&& gb->hram_io[IO_WX] <= 166)
|
|
gb->display.window_clear++;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* If background is enabled, draw it. */
|
|
if(gb->hram_io[IO_LCDC] & LCDC_BG_ENABLE)
|
|
{
|
|
uint8_t bg_y, disp_x, bg_x, idx, py, px, t1, t2;
|
|
uint16_t bg_map, tile;
|
|
|
|
/* Calculate current background line to draw. Constant because
|
|
* this function draws only this one line each time it is
|
|
* called. */
|
|
bg_y = gb->hram_io[IO_LY] + gb->hram_io[IO_SCY];
|
|
|
|
/* Get selected background map address for first tile
|
|
* corresponding to current line.
|
|
* 0x20 (32) is the width of a background tile, and the bit
|
|
* shift is to calculate the address. */
|
|
bg_map =
|
|
((gb->hram_io[IO_LCDC] & LCDC_BG_MAP) ?
|
|
VRAM_BMAP_2 : VRAM_BMAP_1)
|
|
+ (bg_y >> 3) * 0x20;
|
|
|
|
/* The displays (what the player sees) X coordinate, drawn right
|
|
* to left. */
|
|
disp_x = LCD_WIDTH - 1;
|
|
|
|
/* The X coordinate to begin drawing the background at. */
|
|
bg_x = disp_x + gb->hram_io[IO_SCX];
|
|
|
|
/* Get tile index for current background tile. */
|
|
idx = gb->vram[bg_map + (bg_x >> 3)];
|
|
/* Y coordinate of tile pixel to draw. */
|
|
py = (bg_y & 0x07);
|
|
/* X coordinate of tile pixel to draw. */
|
|
px = 7 - (bg_x & 0x07);
|
|
|
|
/* Select addressing mode. */
|
|
if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT)
|
|
tile = VRAM_TILES_1 + idx * 0x10;
|
|
else
|
|
tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10;
|
|
|
|
tile += 2 * py;
|
|
|
|
/* fetch first tile */
|
|
t1 = gb->vram[tile] >> px;
|
|
t2 = gb->vram[tile + 1] >> px;
|
|
|
|
for(; disp_x != 0xFF; disp_x--)
|
|
{
|
|
uint8_t c;
|
|
|
|
if(px == 8)
|
|
{
|
|
/* fetch next tile */
|
|
px = 0;
|
|
bg_x = disp_x + gb->hram_io[IO_SCX];
|
|
idx = gb->vram[bg_map + (bg_x >> 3)];
|
|
|
|
if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT)
|
|
tile = VRAM_TILES_1 + idx * 0x10;
|
|
else
|
|
tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10;
|
|
|
|
tile += 2 * py;
|
|
t1 = gb->vram[tile];
|
|
t2 = gb->vram[tile + 1];
|
|
}
|
|
|
|
/* copy background */
|
|
c = (t1 & 0x1) | ((t2 & 0x1) << 1);
|
|
pixels[disp_x] = gb->display.bg_palette[c];
|
|
#if PEANUT_GB_12_COLOUR
|
|
pixels[disp_x] |= LCD_PALETTE_BG;
|
|
#endif
|
|
t1 = t1 >> 1;
|
|
t2 = t2 >> 1;
|
|
px++;
|
|
}
|
|
}
|
|
|
|
/* draw window */
|
|
if(gb->hram_io[IO_LCDC] & LCDC_WINDOW_ENABLE
|
|
&& gb->hram_io[IO_LY] >= gb->display.WY
|
|
&& gb->hram_io[IO_WX] <= 166)
|
|
{
|
|
uint16_t win_line, tile;
|
|
uint8_t disp_x, win_x, py, px, idx, t1, t2, end;
|
|
|
|
/* Calculate Window Map Address. */
|
|
win_line = (gb->hram_io[IO_LCDC] & LCDC_WINDOW_MAP) ?
|
|
VRAM_BMAP_2 : VRAM_BMAP_1;
|
|
win_line += (gb->display.window_clear >> 3) * 0x20;
|
|
|
|
disp_x = LCD_WIDTH - 1;
|
|
win_x = disp_x - gb->hram_io[IO_WX] + 7;
|
|
|
|
// look up tile
|
|
py = gb->display.window_clear & 0x07;
|
|
px = 7 - (win_x & 0x07);
|
|
idx = gb->vram[win_line + (win_x >> 3)];
|
|
|
|
if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT)
|
|
tile = VRAM_TILES_1 + idx * 0x10;
|
|
else
|
|
tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10;
|
|
|
|
tile += 2 * py;
|
|
|
|
// fetch first tile
|
|
t1 = gb->vram[tile] >> px;
|
|
t2 = gb->vram[tile + 1] >> px;
|
|
|
|
// loop & copy window
|
|
end = (gb->hram_io[IO_WX] < 7 ? 0 : gb->hram_io[IO_WX] - 7) - 1;
|
|
|
|
for(; disp_x != end; disp_x--)
|
|
{
|
|
uint8_t c;
|
|
|
|
if(px == 8)
|
|
{
|
|
// fetch next tile
|
|
px = 0;
|
|
win_x = disp_x - gb->hram_io[IO_WX] + 7;
|
|
idx = gb->vram[win_line + (win_x >> 3)];
|
|
|
|
if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT)
|
|
tile = VRAM_TILES_1 + idx * 0x10;
|
|
else
|
|
tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10;
|
|
|
|
tile += 2 * py;
|
|
t1 = gb->vram[tile];
|
|
t2 = gb->vram[tile + 1];
|
|
}
|
|
|
|
// copy window
|
|
c = (t1 & 0x1) | ((t2 & 0x1) << 1);
|
|
pixels[disp_x] = gb->display.bg_palette[c];
|
|
#if PEANUT_GB_12_COLOUR
|
|
pixels[disp_x] |= LCD_PALETTE_BG;
|
|
#endif
|
|
t1 = t1 >> 1;
|
|
t2 = t2 >> 1;
|
|
px++;
|
|
}
|
|
|
|
gb->display.window_clear++; // advance window line
|
|
}
|
|
|
|
// draw sprites
|
|
if(gb->hram_io[IO_LCDC] & LCDC_OBJ_ENABLE)
|
|
{
|
|
uint8_t sprite_number;
|
|
#if PEANUT_GB_HIGH_LCD_ACCURACY
|
|
uint8_t number_of_sprites = 0;
|
|
|
|
struct sprite_data sprites_to_render[MAX_SPRITES_LINE];
|
|
|
|
/* Record number of sprites on the line being rendered, limited
|
|
* to the maximum number sprites that the Game Boy is able to
|
|
* render on each line (10 sprites). */
|
|
for(sprite_number = 0;
|
|
sprite_number < NUM_SPRITES;
|
|
sprite_number++)
|
|
{
|
|
/* Sprite Y position. */
|
|
uint8_t OY = gb->oam[4 * sprite_number + 0];
|
|
/* Sprite X position. */
|
|
uint8_t OX = gb->oam[4 * sprite_number + 1];
|
|
|
|
/* If sprite isn't on this line, continue. */
|
|
if (gb->hram_io[IO_LY] +
|
|
(gb->hram_io[IO_LCDC] & LCDC_OBJ_SIZE ? 0 : 8) >= OY
|
|
|| gb->hram_io[IO_LY] + 16 < OY)
|
|
continue;
|
|
|
|
struct sprite_data current;
|
|
|
|
current.sprite_number = sprite_number;
|
|
current.x = OX;
|
|
|
|
uint8_t place;
|
|
for (place = number_of_sprites; place != 0; place--)
|
|
{
|
|
if(compare_sprites(&sprites_to_render[place - 1], ¤t) < 0)
|
|
break;
|
|
}
|
|
if(place >= MAX_SPRITES_LINE)
|
|
continue;
|
|
for (uint8_t i = number_of_sprites; i > place; --i) {
|
|
sprites_to_render[i] = sprites_to_render[i - 1];
|
|
}
|
|
if(number_of_sprites < MAX_SPRITES_LINE)
|
|
number_of_sprites++;
|
|
sprites_to_render[place] = current;
|
|
}
|
|
#endif
|
|
|
|
/* Render each sprite, from low priority to high priority. */
|
|
#if PEANUT_GB_HIGH_LCD_ACCURACY
|
|
/* Render the top ten prioritised sprites on this scanline. */
|
|
for(sprite_number = number_of_sprites - 1;
|
|
sprite_number != 0xFF;
|
|
sprite_number--)
|
|
{
|
|
uint8_t s = sprites_to_render[sprite_number].sprite_number;
|
|
#else
|
|
for (sprite_number = NUM_SPRITES - 1;
|
|
sprite_number != 0xFF;
|
|
sprite_number--)
|
|
{
|
|
uint8_t s = sprite_number;
|
|
#endif
|
|
uint8_t py, t1, t2, dir, start, end, shift, disp_x;
|
|
/* Sprite Y position. */
|
|
uint8_t OY = gb->oam[4 * s + 0];
|
|
/* Sprite X position. */
|
|
uint8_t OX = gb->oam[4 * s + 1];
|
|
/* Sprite Tile/Pattern Number. */
|
|
uint8_t OT = gb->oam[4 * s + 2]
|
|
& (gb->hram_io[IO_LCDC] & LCDC_OBJ_SIZE ? 0xFE : 0xFF);
|
|
/* Additional attributes. */
|
|
uint8_t OF = gb->oam[4 * s + 3];
|
|
|
|
#if !PEANUT_GB_HIGH_LCD_ACCURACY
|
|
/* If sprite isn't on this line, continue. */
|
|
if(gb->hram_io[IO_LY] +
|
|
(gb->hram_io[IO_LCDC] & LCDC_OBJ_SIZE ? 0 : 8) >= OY ||
|
|
gb->hram_io[IO_LY] + 16 < OY)
|
|
continue;
|
|
#endif
|
|
|
|
/* Continue if sprite not visible. */
|
|
if(OX == 0 || OX >= 168)
|
|
continue;
|
|
|
|
// y flip
|
|
py = gb->hram_io[IO_LY] - OY + 16;
|
|
|
|
if(OF & OBJ_FLIP_Y)
|
|
py = (gb->hram_io[IO_LCDC] & LCDC_OBJ_SIZE ? 15 : 7) - py;
|
|
|
|
// fetch the tile
|
|
t1 = gb->vram[VRAM_TILES_1 + OT * 0x10 + 2 * py];
|
|
t2 = gb->vram[VRAM_TILES_1 + OT * 0x10 + 2 * py + 1];
|
|
|
|
// handle x flip
|
|
if(OF & OBJ_FLIP_X)
|
|
{
|
|
dir = 1;
|
|
start = (OX < 8 ? 0 : OX - 8);
|
|
end = MIN(OX, LCD_WIDTH);
|
|
shift = 8 - OX + start;
|
|
}
|
|
else
|
|
{
|
|
dir = (uint8_t)-1;
|
|
start = MIN(OX, LCD_WIDTH) - 1;
|
|
end = (OX < 8 ? 0 : OX - 8) - 1;
|
|
shift = OX - (start + 1);
|
|
}
|
|
|
|
// copy tile
|
|
t1 >>= shift;
|
|
t2 >>= shift;
|
|
|
|
/* TODO: Put for loop within the to if statements
|
|
* because the BG priority bit will be the same for
|
|
* all the pixels in the tile. */
|
|
for(disp_x = start; disp_x != end; disp_x += dir)
|
|
{
|
|
uint8_t c = (t1 & 0x1) | ((t2 & 0x1) << 1);
|
|
// check transparency / sprite overlap / background overlap
|
|
|
|
if(c && !(OF & OBJ_PRIORITY && !((pixels[disp_x] & 0x3) == gb->display.bg_palette[0])))
|
|
{
|
|
/* Set pixel colour. */
|
|
pixels[disp_x] = (OF & OBJ_PALETTE)
|
|
? gb->display.sp_palette[c + 4]
|
|
: gb->display.sp_palette[c];
|
|
#if PEANUT_GB_12_COLOUR
|
|
/* Set pixel palette (OBJ0 or OBJ1). */
|
|
pixels[disp_x] |= (OF & OBJ_PALETTE);
|
|
#endif
|
|
}
|
|
|
|
t1 = t1 >> 1;
|
|
t2 = t2 >> 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
gb->display.lcd_draw_line(gb, pixels, gb->hram_io[IO_LY]);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Internal function used to step the CPU.
|
|
*/
|
|
void __gb_step_cpu(struct gb_s *gb)
|
|
{
|
|
uint8_t opcode;
|
|
uint_fast16_t inst_cycles;
|
|
static const uint8_t op_cycles[0x100] =
|
|
{
|
|
/* *INDENT-OFF* */
|
|
/*0 1 2 3 4 5 6 7 8 9 A B C D E F */
|
|
4,12, 8, 8, 4, 4, 8, 4,20, 8, 8, 8, 4, 4, 8, 4, /* 0x00 */
|
|
4,12, 8, 8, 4, 4, 8, 4,12, 8, 8, 8, 4, 4, 8, 4, /* 0x10 */
|
|
8,12, 8, 8, 4, 4, 8, 4, 8, 8, 8, 8, 4, 4, 8, 4, /* 0x20 */
|
|
8,12, 8, 8,12,12,12, 4, 8, 8, 8, 8, 4, 4, 8, 4, /* 0x30 */
|
|
4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x40 */
|
|
4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x50 */
|
|
4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x60 */
|
|
8, 8, 8, 8, 8, 8, 4, 8, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x70 */
|
|
4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x80 */
|
|
4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x90 */
|
|
4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0xA0 */
|
|
4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0xB0 */
|
|
8,12,12,16,12,16, 8,16, 8,16,12, 8,12,24, 8,16, /* 0xC0 */
|
|
8,12,12, 0,12,16, 8,16, 8,16,12, 0,12, 0, 8,16, /* 0xD0 */
|
|
12,12,8, 0, 0,16, 8,16,16, 4,16, 0, 0, 0, 8,16, /* 0xE0 */
|
|
12,12,8, 4, 0,16, 8,16,12, 8,16, 4, 0, 0, 8,16 /* 0xF0 */
|
|
/* *INDENT-ON* */
|
|
};
|
|
static const uint_fast16_t TAC_CYCLES[4] = {1024, 16, 64, 256};
|
|
|
|
/* Handle interrupts */
|
|
/* If gb_halt is positive, then an interrupt must have occurred by the
|
|
* time we reach here, because on HALT, we jump to the next interrupt
|
|
* immediately. */
|
|
while(gb->gb_halt || (gb->gb_ime &&
|
|
gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & ANY_INTR))
|
|
{
|
|
gb->gb_halt = false;
|
|
|
|
if(!gb->gb_ime)
|
|
break;
|
|
|
|
/* Disable interrupts */
|
|
gb->gb_ime = false;
|
|
|
|
/* Push Program Counter */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
|
|
/* Call interrupt handler if required. */
|
|
if(gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & VBLANK_INTR)
|
|
{
|
|
gb->cpu_reg.pc.reg = VBLANK_INTR_ADDR;
|
|
gb->hram_io[IO_IF] ^= VBLANK_INTR;
|
|
}
|
|
else if(gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & LCDC_INTR)
|
|
{
|
|
gb->cpu_reg.pc.reg = LCDC_INTR_ADDR;
|
|
gb->hram_io[IO_IF] ^= LCDC_INTR;
|
|
}
|
|
else if(gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & TIMER_INTR)
|
|
{
|
|
gb->cpu_reg.pc.reg = TIMER_INTR_ADDR;
|
|
gb->hram_io[IO_IF] ^= TIMER_INTR;
|
|
}
|
|
else if(gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & SERIAL_INTR)
|
|
{
|
|
gb->cpu_reg.pc.reg = SERIAL_INTR_ADDR;
|
|
gb->hram_io[IO_IF] ^= SERIAL_INTR;
|
|
}
|
|
else if(gb->hram_io[IO_IF] & gb->hram_io[IO_IE] & CONTROL_INTR)
|
|
{
|
|
gb->cpu_reg.pc.reg = CONTROL_INTR_ADDR;
|
|
gb->hram_io[IO_IF] ^= CONTROL_INTR;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Obtain opcode */
|
|
opcode = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
inst_cycles = op_cycles[opcode];
|
|
|
|
/* Execute opcode */
|
|
switch(opcode)
|
|
{
|
|
case 0x00: /* NOP */
|
|
break;
|
|
|
|
case 0x01: /* LD BC, imm */
|
|
gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x02: /* LD (BC), A */
|
|
__gb_write(gb, gb->cpu_reg.bc.reg, gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0x03: /* INC BC */
|
|
gb->cpu_reg.bc.reg++;
|
|
break;
|
|
|
|
case 0x04: /* INC B */
|
|
PGB_INSTR_INC_R8(gb->cpu_reg.bc.bytes.b);
|
|
break;
|
|
|
|
case 0x05: /* DEC B */
|
|
PGB_INSTR_DEC_R8(gb->cpu_reg.bc.bytes.b);
|
|
break;
|
|
|
|
case 0x06: /* LD B, imm */
|
|
gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x07: /* RLCA */
|
|
gb->cpu_reg.a = (gb->cpu_reg.a << 1) | (gb->cpu_reg.a >> 7);
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.c = (gb->cpu_reg.a & 0x01);
|
|
break;
|
|
|
|
case 0x08: /* LD (imm), SP */
|
|
{
|
|
uint8_t h, l;
|
|
uint16_t temp;
|
|
l = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
h = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
temp = PEANUT_GB_U8_TO_U16(h,l);
|
|
__gb_write(gb, temp++, gb->cpu_reg.sp.bytes.p);
|
|
__gb_write(gb, temp, gb->cpu_reg.sp.bytes.s);
|
|
break;
|
|
}
|
|
|
|
case 0x09: /* ADD HL, BC */
|
|
{
|
|
uint_fast32_t temp = gb->cpu_reg.hl.reg + gb->cpu_reg.bc.reg;
|
|
gb->cpu_reg.f.f_bits.n = 0;
|
|
gb->cpu_reg.f.f_bits.h =
|
|
(temp ^ gb->cpu_reg.hl.reg ^ gb->cpu_reg.bc.reg) & 0x1000 ? 1 : 0;
|
|
gb->cpu_reg.f.f_bits.c = (temp & 0xFFFF0000) ? 1 : 0;
|
|
gb->cpu_reg.hl.reg = (temp & 0x0000FFFF);
|
|
break;
|
|
}
|
|
|
|
case 0x0A: /* LD A, (BC) */
|
|
gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.bc.reg);
|
|
break;
|
|
|
|
case 0x0B: /* DEC BC */
|
|
gb->cpu_reg.bc.reg--;
|
|
break;
|
|
|
|
case 0x0C: /* INC C */
|
|
PGB_INSTR_INC_R8(gb->cpu_reg.bc.bytes.c);
|
|
break;
|
|
|
|
case 0x0D: /* DEC C */
|
|
PGB_INSTR_DEC_R8(gb->cpu_reg.bc.bytes.c);
|
|
break;
|
|
|
|
case 0x0E: /* LD C, imm */
|
|
gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x0F: /* RRCA */
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.c = gb->cpu_reg.a & 0x01;
|
|
gb->cpu_reg.a = (gb->cpu_reg.a >> 1) | (gb->cpu_reg.a << 7);
|
|
break;
|
|
|
|
case 0x10: /* STOP */
|
|
//gb->gb_halt = true;
|
|
break;
|
|
|
|
case 0x11: /* LD DE, imm */
|
|
gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x12: /* LD (DE), A */
|
|
__gb_write(gb, gb->cpu_reg.de.reg, gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0x13: /* INC DE */
|
|
gb->cpu_reg.de.reg++;
|
|
break;
|
|
|
|
case 0x14: /* INC D */
|
|
PGB_INSTR_INC_R8(gb->cpu_reg.de.bytes.d);
|
|
break;
|
|
|
|
case 0x15: /* DEC D */
|
|
PGB_INSTR_DEC_R8(gb->cpu_reg.de.bytes.d);
|
|
break;
|
|
|
|
case 0x16: /* LD D, imm */
|
|
gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x17: /* RLA */
|
|
{
|
|
uint8_t temp = gb->cpu_reg.a;
|
|
gb->cpu_reg.a = (gb->cpu_reg.a << 1) | gb->cpu_reg.f.f_bits.c;
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.c = (temp >> 7) & 0x01;
|
|
break;
|
|
}
|
|
|
|
case 0x18: /* JR imm */
|
|
{
|
|
int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.pc.reg += temp;
|
|
break;
|
|
}
|
|
|
|
case 0x19: /* ADD HL, DE */
|
|
{
|
|
uint_fast32_t temp = gb->cpu_reg.hl.reg + gb->cpu_reg.de.reg;
|
|
gb->cpu_reg.f.f_bits.n = 0;
|
|
gb->cpu_reg.f.f_bits.h =
|
|
(temp ^ gb->cpu_reg.hl.reg ^ gb->cpu_reg.de.reg) & 0x1000 ? 1 : 0;
|
|
gb->cpu_reg.f.f_bits.c = (temp & 0xFFFF0000) ? 1 : 0;
|
|
gb->cpu_reg.hl.reg = (temp & 0x0000FFFF);
|
|
break;
|
|
}
|
|
|
|
case 0x1A: /* LD A, (DE) */
|
|
gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.de.reg);
|
|
break;
|
|
|
|
case 0x1B: /* DEC DE */
|
|
gb->cpu_reg.de.reg--;
|
|
break;
|
|
|
|
case 0x1C: /* INC E */
|
|
PGB_INSTR_INC_R8(gb->cpu_reg.de.bytes.e);
|
|
break;
|
|
|
|
case 0x1D: /* DEC E */
|
|
PGB_INSTR_DEC_R8(gb->cpu_reg.de.bytes.e);
|
|
break;
|
|
|
|
case 0x1E: /* LD E, imm */
|
|
gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x1F: /* RRA */
|
|
{
|
|
uint8_t temp = gb->cpu_reg.a;
|
|
gb->cpu_reg.a = gb->cpu_reg.a >> 1 | (gb->cpu_reg.f.f_bits.c << 7);
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.c = temp & 0x1;
|
|
break;
|
|
}
|
|
|
|
case 0x20: /* JR NZ, imm */
|
|
if(!gb->cpu_reg.f.f_bits.z)
|
|
{
|
|
int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.pc.reg += temp;
|
|
inst_cycles += 4;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg++;
|
|
|
|
break;
|
|
|
|
case 0x21: /* LD HL, imm */
|
|
gb->cpu_reg.hl.bytes.l = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x22: /* LDI (HL), A */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.a);
|
|
gb->cpu_reg.hl.reg++;
|
|
break;
|
|
|
|
case 0x23: /* INC HL */
|
|
gb->cpu_reg.hl.reg++;
|
|
break;
|
|
|
|
case 0x24: /* INC H */
|
|
PGB_INSTR_INC_R8(gb->cpu_reg.hl.bytes.h);
|
|
break;
|
|
|
|
case 0x25: /* DEC H */
|
|
PGB_INSTR_DEC_R8(gb->cpu_reg.hl.bytes.h);
|
|
break;
|
|
|
|
case 0x26: /* LD H, imm */
|
|
gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x27: /* DAA */
|
|
{
|
|
/* The following is from SameBoy. MIT License. */
|
|
int16_t a = gb->cpu_reg.a;
|
|
|
|
if(gb->cpu_reg.f.f_bits.n)
|
|
{
|
|
if(gb->cpu_reg.f.f_bits.h)
|
|
a = (a - 0x06) & 0xFF;
|
|
|
|
if(gb->cpu_reg.f.f_bits.c)
|
|
a -= 0x60;
|
|
}
|
|
else
|
|
{
|
|
if(gb->cpu_reg.f.f_bits.h || (a & 0x0F) > 9)
|
|
a += 0x06;
|
|
|
|
if(gb->cpu_reg.f.f_bits.c || a > 0x9F)
|
|
a += 0x60;
|
|
}
|
|
|
|
if((a & 0x100) == 0x100)
|
|
gb->cpu_reg.f.f_bits.c = 1;
|
|
|
|
gb->cpu_reg.a = a;
|
|
gb->cpu_reg.f.f_bits.z = (gb->cpu_reg.a == 0);
|
|
gb->cpu_reg.f.f_bits.h = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case 0x28: /* JR Z, imm */
|
|
if(gb->cpu_reg.f.f_bits.z)
|
|
{
|
|
int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.pc.reg += temp;
|
|
inst_cycles += 4;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg++;
|
|
|
|
break;
|
|
|
|
case 0x29: /* ADD HL, HL */
|
|
{
|
|
gb->cpu_reg.f.f_bits.c = (gb->cpu_reg.hl.reg & 0x8000) > 0;
|
|
gb->cpu_reg.hl.reg <<= 1;
|
|
gb->cpu_reg.f.f_bits.n = 0;
|
|
gb->cpu_reg.f.f_bits.h = (gb->cpu_reg.hl.reg & 0x1000) > 0;
|
|
break;
|
|
}
|
|
|
|
case 0x2A: /* LD A, (HL+) */
|
|
gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl.reg++);
|
|
break;
|
|
|
|
case 0x2B: /* DEC HL */
|
|
gb->cpu_reg.hl.reg--;
|
|
break;
|
|
|
|
case 0x2C: /* INC L */
|
|
PGB_INSTR_INC_R8(gb->cpu_reg.hl.bytes.l);
|
|
break;
|
|
|
|
case 0x2D: /* DEC L */
|
|
PGB_INSTR_DEC_R8(gb->cpu_reg.hl.bytes.l);
|
|
break;
|
|
|
|
case 0x2E: /* LD L, imm */
|
|
gb->cpu_reg.hl.bytes.l = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x2F: /* CPL */
|
|
gb->cpu_reg.a = ~gb->cpu_reg.a;
|
|
gb->cpu_reg.f.f_bits.n = 1;
|
|
gb->cpu_reg.f.f_bits.h = 1;
|
|
break;
|
|
|
|
case 0x30: /* JR NC, imm */
|
|
if(!gb->cpu_reg.f.f_bits.c)
|
|
{
|
|
int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.pc.reg += temp;
|
|
inst_cycles += 4;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg++;
|
|
|
|
break;
|
|
|
|
case 0x31: /* LD SP, imm */
|
|
gb->cpu_reg.sp.bytes.p = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.sp.bytes.s = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x32: /* LD (HL), A */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.a);
|
|
gb->cpu_reg.hl.reg--;
|
|
break;
|
|
|
|
case 0x33: /* INC SP */
|
|
gb->cpu_reg.sp.reg++;
|
|
break;
|
|
|
|
case 0x34: /* INC (HL) */
|
|
{
|
|
uint8_t temp = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
PGB_INSTR_INC_R8(temp);
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, temp);
|
|
break;
|
|
}
|
|
|
|
case 0x35: /* DEC (HL) */
|
|
{
|
|
uint8_t temp = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
PGB_INSTR_DEC_R8(temp);
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, temp);
|
|
break;
|
|
}
|
|
|
|
case 0x36: /* LD (HL), imm */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, __gb_read(gb, gb->cpu_reg.pc.reg++));
|
|
break;
|
|
|
|
case 0x37: /* SCF */
|
|
gb->cpu_reg.f.f_bits.n = 0;
|
|
gb->cpu_reg.f.f_bits.h = 0;
|
|
gb->cpu_reg.f.f_bits.c = 1;
|
|
break;
|
|
|
|
case 0x38: /* JR C, imm */
|
|
if(gb->cpu_reg.f.f_bits.c)
|
|
{
|
|
int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.pc.reg += temp;
|
|
inst_cycles += 4;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg++;
|
|
|
|
break;
|
|
|
|
case 0x39: /* ADD HL, SP */
|
|
{
|
|
uint_fast32_t temp = gb->cpu_reg.hl.reg + gb->cpu_reg.sp.reg;
|
|
gb->cpu_reg.f.f_bits.n = 0;
|
|
gb->cpu_reg.f.f_bits.h =
|
|
((gb->cpu_reg.hl.reg & 0xFFF) + (gb->cpu_reg.sp.reg & 0xFFF)) & 0x1000 ? 1 : 0;
|
|
gb->cpu_reg.f.f_bits.c = temp & 0x10000 ? 1 : 0;
|
|
gb->cpu_reg.hl.reg = (uint16_t)temp;
|
|
break;
|
|
}
|
|
|
|
case 0x3A: /* LD A, (HL) */
|
|
gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl.reg--);
|
|
break;
|
|
|
|
case 0x3B: /* DEC SP */
|
|
gb->cpu_reg.sp.reg--;
|
|
break;
|
|
|
|
case 0x3C: /* INC A */
|
|
PGB_INSTR_INC_R8(gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0x3D: /* DEC A */
|
|
PGB_INSTR_DEC_R8(gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0x3E: /* LD A, imm */
|
|
gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
break;
|
|
|
|
case 0x3F: /* CCF */
|
|
gb->cpu_reg.f.f_bits.n = 0;
|
|
gb->cpu_reg.f.f_bits.h = 0;
|
|
gb->cpu_reg.f.f_bits.c = ~gb->cpu_reg.f.f_bits.c;
|
|
break;
|
|
|
|
case 0x40: /* LD B, B */
|
|
break;
|
|
|
|
case 0x41: /* LD B, C */
|
|
gb->cpu_reg.bc.bytes.b = gb->cpu_reg.bc.bytes.c;
|
|
break;
|
|
|
|
case 0x42: /* LD B, D */
|
|
gb->cpu_reg.bc.bytes.b = gb->cpu_reg.de.bytes.d;
|
|
break;
|
|
|
|
case 0x43: /* LD B, E */
|
|
gb->cpu_reg.bc.bytes.b = gb->cpu_reg.de.bytes.e;
|
|
break;
|
|
|
|
case 0x44: /* LD B, H */
|
|
gb->cpu_reg.bc.bytes.b = gb->cpu_reg.hl.bytes.h;
|
|
break;
|
|
|
|
case 0x45: /* LD B, L */
|
|
gb->cpu_reg.bc.bytes.b = gb->cpu_reg.hl.bytes.l;
|
|
break;
|
|
|
|
case 0x46: /* LD B, (HL) */
|
|
gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
break;
|
|
|
|
case 0x47: /* LD B, A */
|
|
gb->cpu_reg.bc.bytes.b = gb->cpu_reg.a;
|
|
break;
|
|
|
|
case 0x48: /* LD C, B */
|
|
gb->cpu_reg.bc.bytes.c = gb->cpu_reg.bc.bytes.b;
|
|
break;
|
|
|
|
case 0x49: /* LD C, C */
|
|
break;
|
|
|
|
case 0x4A: /* LD C, D */
|
|
gb->cpu_reg.bc.bytes.c = gb->cpu_reg.de.bytes.d;
|
|
break;
|
|
|
|
case 0x4B: /* LD C, E */
|
|
gb->cpu_reg.bc.bytes.c = gb->cpu_reg.de.bytes.e;
|
|
break;
|
|
|
|
case 0x4C: /* LD C, H */
|
|
gb->cpu_reg.bc.bytes.c = gb->cpu_reg.hl.bytes.h;
|
|
break;
|
|
|
|
case 0x4D: /* LD C, L */
|
|
gb->cpu_reg.bc.bytes.c = gb->cpu_reg.hl.bytes.l;
|
|
break;
|
|
|
|
case 0x4E: /* LD C, (HL) */
|
|
gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
break;
|
|
|
|
case 0x4F: /* LD C, A */
|
|
gb->cpu_reg.bc.bytes.c = gb->cpu_reg.a;
|
|
break;
|
|
|
|
case 0x50: /* LD D, B */
|
|
gb->cpu_reg.de.bytes.d = gb->cpu_reg.bc.bytes.b;
|
|
break;
|
|
|
|
case 0x51: /* LD D, C */
|
|
gb->cpu_reg.de.bytes.d = gb->cpu_reg.bc.bytes.c;
|
|
break;
|
|
|
|
case 0x52: /* LD D, D */
|
|
break;
|
|
|
|
case 0x53: /* LD D, E */
|
|
gb->cpu_reg.de.bytes.d = gb->cpu_reg.de.bytes.e;
|
|
break;
|
|
|
|
case 0x54: /* LD D, H */
|
|
gb->cpu_reg.de.bytes.d = gb->cpu_reg.hl.bytes.h;
|
|
break;
|
|
|
|
case 0x55: /* LD D, L */
|
|
gb->cpu_reg.de.bytes.d = gb->cpu_reg.hl.bytes.l;
|
|
break;
|
|
|
|
case 0x56: /* LD D, (HL) */
|
|
gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
break;
|
|
|
|
case 0x57: /* LD D, A */
|
|
gb->cpu_reg.de.bytes.d = gb->cpu_reg.a;
|
|
break;
|
|
|
|
case 0x58: /* LD E, B */
|
|
gb->cpu_reg.de.bytes.e = gb->cpu_reg.bc.bytes.b;
|
|
break;
|
|
|
|
case 0x59: /* LD E, C */
|
|
gb->cpu_reg.de.bytes.e = gb->cpu_reg.bc.bytes.c;
|
|
break;
|
|
|
|
case 0x5A: /* LD E, D */
|
|
gb->cpu_reg.de.bytes.e = gb->cpu_reg.de.bytes.d;
|
|
break;
|
|
|
|
case 0x5B: /* LD E, E */
|
|
break;
|
|
|
|
case 0x5C: /* LD E, H */
|
|
gb->cpu_reg.de.bytes.e = gb->cpu_reg.hl.bytes.h;
|
|
break;
|
|
|
|
case 0x5D: /* LD E, L */
|
|
gb->cpu_reg.de.bytes.e = gb->cpu_reg.hl.bytes.l;
|
|
break;
|
|
|
|
case 0x5E: /* LD E, (HL) */
|
|
gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
break;
|
|
|
|
case 0x5F: /* LD E, A */
|
|
gb->cpu_reg.de.bytes.e = gb->cpu_reg.a;
|
|
break;
|
|
|
|
case 0x60: /* LD H, B */
|
|
gb->cpu_reg.hl.bytes.h = gb->cpu_reg.bc.bytes.b;
|
|
break;
|
|
|
|
case 0x61: /* LD H, C */
|
|
gb->cpu_reg.hl.bytes.h = gb->cpu_reg.bc.bytes.c;
|
|
break;
|
|
|
|
case 0x62: /* LD H, D */
|
|
gb->cpu_reg.hl.bytes.h = gb->cpu_reg.de.bytes.d;
|
|
break;
|
|
|
|
case 0x63: /* LD H, E */
|
|
gb->cpu_reg.hl.bytes.h = gb->cpu_reg.de.bytes.e;
|
|
break;
|
|
|
|
case 0x64: /* LD H, H */
|
|
break;
|
|
|
|
case 0x65: /* LD H, L */
|
|
gb->cpu_reg.hl.bytes.h = gb->cpu_reg.hl.bytes.l;
|
|
break;
|
|
|
|
case 0x66: /* LD H, (HL) */
|
|
gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
break;
|
|
|
|
case 0x67: /* LD H, A */
|
|
gb->cpu_reg.hl.bytes.h = gb->cpu_reg.a;
|
|
break;
|
|
|
|
case 0x68: /* LD L, B */
|
|
gb->cpu_reg.hl.bytes.l = gb->cpu_reg.bc.bytes.b;
|
|
break;
|
|
|
|
case 0x69: /* LD L, C */
|
|
gb->cpu_reg.hl.bytes.l = gb->cpu_reg.bc.bytes.c;
|
|
break;
|
|
|
|
case 0x6A: /* LD L, D */
|
|
gb->cpu_reg.hl.bytes.l = gb->cpu_reg.de.bytes.d;
|
|
break;
|
|
|
|
case 0x6B: /* LD L, E */
|
|
gb->cpu_reg.hl.bytes.l = gb->cpu_reg.de.bytes.e;
|
|
break;
|
|
|
|
case 0x6C: /* LD L, H */
|
|
gb->cpu_reg.hl.bytes.l = gb->cpu_reg.hl.bytes.h;
|
|
break;
|
|
|
|
case 0x6D: /* LD L, L */
|
|
break;
|
|
|
|
case 0x6E: /* LD L, (HL) */
|
|
gb->cpu_reg.hl.bytes.l = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
break;
|
|
|
|
case 0x6F: /* LD L, A */
|
|
gb->cpu_reg.hl.bytes.l = gb->cpu_reg.a;
|
|
break;
|
|
|
|
case 0x70: /* LD (HL), B */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.bc.bytes.b);
|
|
break;
|
|
|
|
case 0x71: /* LD (HL), C */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.bc.bytes.c);
|
|
break;
|
|
|
|
case 0x72: /* LD (HL), D */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.de.bytes.d);
|
|
break;
|
|
|
|
case 0x73: /* LD (HL), E */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.de.bytes.e);
|
|
break;
|
|
|
|
case 0x74: /* LD (HL), H */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.hl.bytes.h);
|
|
break;
|
|
|
|
case 0x75: /* LD (HL), L */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.hl.bytes.l);
|
|
break;
|
|
|
|
case 0x76: /* HALT */
|
|
{
|
|
int_fast16_t halt_cycles = INT_FAST16_MAX;
|
|
|
|
/* TODO: Emulate HALT bug? */
|
|
gb->gb_halt = true;
|
|
|
|
if(gb->hram_io[IO_SC] & SERIAL_SC_TX_START)
|
|
{
|
|
int serial_cycles = SERIAL_CYCLES -
|
|
gb->counter.serial_count;
|
|
|
|
if(serial_cycles < halt_cycles)
|
|
halt_cycles = serial_cycles;
|
|
}
|
|
|
|
if(gb->hram_io[IO_TAC] & IO_TAC_ENABLE_MASK)
|
|
{
|
|
int tac_cycles = TAC_CYCLES[gb->hram_io[IO_TAC] & IO_TAC_RATE_MASK] -
|
|
gb->counter.tima_count;
|
|
|
|
if(tac_cycles < halt_cycles)
|
|
halt_cycles = tac_cycles;
|
|
}
|
|
|
|
if((gb->hram_io[IO_LCDC] & LCDC_ENABLE))
|
|
{
|
|
int lcd_cycles;
|
|
|
|
/* If LCD is in HBlank, calculate the number of cycles
|
|
* until the end of HBlank and the start of mode 2 or
|
|
* mode 1. */
|
|
if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_HBLANK)
|
|
{
|
|
lcd_cycles = LCD_MODE0_HBLANK_MAX_DRUATION - gb->counter.lcd_count;
|
|
}
|
|
else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_OAM_SCAN)
|
|
{
|
|
lcd_cycles = LCD_MODE3_LCD_DRAW_MIN_DURATION - gb->counter.lcd_count;
|
|
}
|
|
else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_LCD_DRAW)
|
|
{
|
|
lcd_cycles = LCD_MODE0_HBLANK_MAX_DRUATION - gb->counter.lcd_count;
|
|
}
|
|
else
|
|
{
|
|
/* VBlank */
|
|
lcd_cycles = LCD_LINE_CYCLES - gb->counter.lcd_count;
|
|
}
|
|
|
|
if(lcd_cycles < halt_cycles)
|
|
halt_cycles = lcd_cycles;
|
|
}
|
|
|
|
/* Some halt cycles may already be very high, so make sure we
|
|
* don't underflow here. */
|
|
if(halt_cycles <= 0)
|
|
halt_cycles = 4;
|
|
|
|
inst_cycles = (uint_fast16_t)halt_cycles;
|
|
break;
|
|
}
|
|
|
|
case 0x77: /* LD (HL), A */
|
|
__gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0x78: /* LD A, B */
|
|
gb->cpu_reg.a = gb->cpu_reg.bc.bytes.b;
|
|
break;
|
|
|
|
case 0x79: /* LD A, C */
|
|
gb->cpu_reg.a = gb->cpu_reg.bc.bytes.c;
|
|
break;
|
|
|
|
case 0x7A: /* LD A, D */
|
|
gb->cpu_reg.a = gb->cpu_reg.de.bytes.d;
|
|
break;
|
|
|
|
case 0x7B: /* LD A, E */
|
|
gb->cpu_reg.a = gb->cpu_reg.de.bytes.e;
|
|
break;
|
|
|
|
case 0x7C: /* LD A, H */
|
|
gb->cpu_reg.a = gb->cpu_reg.hl.bytes.h;
|
|
break;
|
|
|
|
case 0x7D: /* LD A, L */
|
|
gb->cpu_reg.a = gb->cpu_reg.hl.bytes.l;
|
|
break;
|
|
|
|
case 0x7E: /* LD A, (HL) */
|
|
gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl.reg);
|
|
break;
|
|
|
|
case 0x7F: /* LD A, A */
|
|
break;
|
|
|
|
case 0x80: /* ADD A, B */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.b, 0);
|
|
break;
|
|
|
|
case 0x81: /* ADD A, C */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.c, 0);
|
|
break;
|
|
|
|
case 0x82: /* ADD A, D */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.d, 0);
|
|
break;
|
|
|
|
case 0x83: /* ADD A, E */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.e, 0);
|
|
break;
|
|
|
|
case 0x84: /* ADD A, H */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.h, 0);
|
|
break;
|
|
|
|
case 0x85: /* ADD A, L */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.l, 0);
|
|
break;
|
|
|
|
case 0x86: /* ADD A, (HL) */
|
|
PGB_INSTR_ADC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), 0);
|
|
break;
|
|
|
|
case 0x87: /* ADD A, A */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.a, 0);
|
|
break;
|
|
|
|
case 0x88: /* ADC A, B */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.b, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x89: /* ADC A, C */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.c, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x8A: /* ADC A, D */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.d, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x8B: /* ADC A, E */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.e, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x8C: /* ADC A, H */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.h, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x8D: /* ADC A, L */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.l, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x8E: /* ADC A, (HL) */
|
|
PGB_INSTR_ADC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x8F: /* ADC A, A */
|
|
PGB_INSTR_ADC_R8(gb->cpu_reg.a, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x90: /* SUB B */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.b, 0);
|
|
break;
|
|
|
|
case 0x91: /* SUB C */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.c, 0);
|
|
break;
|
|
|
|
case 0x92: /* SUB D */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.d, 0);
|
|
break;
|
|
|
|
case 0x93: /* SUB E */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.e, 0);
|
|
break;
|
|
|
|
case 0x94: /* SUB H */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.h, 0);
|
|
break;
|
|
|
|
case 0x95: /* SUB L */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.l, 0);
|
|
break;
|
|
|
|
case 0x96: /* SUB (HL) */
|
|
PGB_INSTR_SBC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), 0);
|
|
break;
|
|
|
|
case 0x97: /* SUB A */
|
|
gb->cpu_reg.a = 0;
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.z = 1;
|
|
gb->cpu_reg.f.f_bits.n = 1;
|
|
break;
|
|
|
|
case 0x98: /* SBC A, B */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.b, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x99: /* SBC A, C */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.c, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x9A: /* SBC A, D */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.d, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x9B: /* SBC A, E */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.e, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x9C: /* SBC A, H */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.h, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x9D: /* SBC A, L */
|
|
PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.l, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x9E: /* SBC A, (HL) */
|
|
PGB_INSTR_SBC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
|
|
case 0x9F: /* SBC A, A */
|
|
gb->cpu_reg.a = gb->cpu_reg.f.f_bits.c ? 0xFF : 0x00;
|
|
gb->cpu_reg.f.f_bits.z = !gb->cpu_reg.f.f_bits.c;
|
|
gb->cpu_reg.f.f_bits.n = 1;
|
|
gb->cpu_reg.f.f_bits.h = gb->cpu_reg.f.f_bits.c;
|
|
break;
|
|
|
|
case 0xA0: /* AND B */
|
|
PGB_INSTR_AND_R8(gb->cpu_reg.bc.bytes.b);
|
|
break;
|
|
|
|
case 0xA1: /* AND C */
|
|
PGB_INSTR_AND_R8(gb->cpu_reg.bc.bytes.c);
|
|
break;
|
|
|
|
case 0xA2: /* AND D */
|
|
PGB_INSTR_AND_R8(gb->cpu_reg.de.bytes.d);
|
|
break;
|
|
|
|
case 0xA3: /* AND E */
|
|
PGB_INSTR_AND_R8(gb->cpu_reg.de.bytes.e);
|
|
break;
|
|
|
|
case 0xA4: /* AND H */
|
|
PGB_INSTR_AND_R8(gb->cpu_reg.hl.bytes.h);
|
|
break;
|
|
|
|
case 0xA5: /* AND L */
|
|
PGB_INSTR_AND_R8(gb->cpu_reg.hl.bytes.l);
|
|
break;
|
|
|
|
case 0xA6: /* AND (HL) */
|
|
PGB_INSTR_AND_R8(__gb_read(gb, gb->cpu_reg.hl.reg));
|
|
break;
|
|
|
|
case 0xA7: /* AND A */
|
|
PGB_INSTR_AND_R8(gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0xA8: /* XOR B */
|
|
PGB_INSTR_XOR_R8(gb->cpu_reg.bc.bytes.b);
|
|
break;
|
|
|
|
case 0xA9: /* XOR C */
|
|
PGB_INSTR_XOR_R8(gb->cpu_reg.bc.bytes.c);
|
|
break;
|
|
|
|
case 0xAA: /* XOR D */
|
|
PGB_INSTR_XOR_R8(gb->cpu_reg.de.bytes.d);
|
|
break;
|
|
|
|
case 0xAB: /* XOR E */
|
|
PGB_INSTR_XOR_R8(gb->cpu_reg.de.bytes.e);
|
|
break;
|
|
|
|
case 0xAC: /* XOR H */
|
|
PGB_INSTR_XOR_R8(gb->cpu_reg.hl.bytes.h);
|
|
break;
|
|
|
|
case 0xAD: /* XOR L */
|
|
PGB_INSTR_XOR_R8(gb->cpu_reg.hl.bytes.l);
|
|
break;
|
|
|
|
case 0xAE: /* XOR (HL) */
|
|
PGB_INSTR_XOR_R8(__gb_read(gb, gb->cpu_reg.hl.reg));
|
|
break;
|
|
|
|
case 0xAF: /* XOR A */
|
|
PGB_INSTR_XOR_R8(gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0xB0: /* OR B */
|
|
PGB_INSTR_OR_R8(gb->cpu_reg.bc.bytes.b);
|
|
break;
|
|
|
|
case 0xB1: /* OR C */
|
|
PGB_INSTR_OR_R8(gb->cpu_reg.bc.bytes.c);
|
|
break;
|
|
|
|
case 0xB2: /* OR D */
|
|
PGB_INSTR_OR_R8(gb->cpu_reg.de.bytes.d);
|
|
break;
|
|
|
|
case 0xB3: /* OR E */
|
|
PGB_INSTR_OR_R8(gb->cpu_reg.de.bytes.e);
|
|
break;
|
|
|
|
case 0xB4: /* OR H */
|
|
PGB_INSTR_OR_R8(gb->cpu_reg.hl.bytes.h);
|
|
break;
|
|
|
|
case 0xB5: /* OR L */
|
|
PGB_INSTR_OR_R8(gb->cpu_reg.hl.bytes.l);
|
|
break;
|
|
|
|
case 0xB6: /* OR (HL) */
|
|
PGB_INSTR_OR_R8(__gb_read(gb, gb->cpu_reg.hl.reg));
|
|
break;
|
|
|
|
case 0xB7: /* OR A */
|
|
PGB_INSTR_OR_R8(gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0xB8: /* CP B */
|
|
PGB_INSTR_CP_R8(gb->cpu_reg.bc.bytes.b);
|
|
break;
|
|
|
|
case 0xB9: /* CP C */
|
|
PGB_INSTR_CP_R8(gb->cpu_reg.bc.bytes.c);
|
|
break;
|
|
|
|
case 0xBA: /* CP D */
|
|
PGB_INSTR_CP_R8(gb->cpu_reg.de.bytes.d);
|
|
break;
|
|
|
|
case 0xBB: /* CP E */
|
|
PGB_INSTR_CP_R8(gb->cpu_reg.de.bytes.e);
|
|
break;
|
|
|
|
case 0xBC: /* CP H */
|
|
PGB_INSTR_CP_R8(gb->cpu_reg.hl.bytes.h);
|
|
break;
|
|
|
|
case 0xBD: /* CP L */
|
|
PGB_INSTR_CP_R8(gb->cpu_reg.hl.bytes.l);
|
|
break;
|
|
|
|
case 0xBE: /* CP (HL) */
|
|
PGB_INSTR_CP_R8(__gb_read(gb, gb->cpu_reg.hl.reg));
|
|
break;
|
|
|
|
case 0xBF: /* CP A */
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.z = 1;
|
|
gb->cpu_reg.f.f_bits.n = 1;
|
|
break;
|
|
|
|
case 0xC0: /* RET NZ */
|
|
if(!gb->cpu_reg.f.f_bits.z)
|
|
{
|
|
gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
inst_cycles += 12;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xC1: /* POP BC */
|
|
gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
break;
|
|
|
|
case 0xC2: /* JP NZ, imm */
|
|
if(!gb->cpu_reg.f.f_bits.z)
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
inst_cycles += 4;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg += 2;
|
|
|
|
break;
|
|
|
|
case 0xC3: /* JP imm */
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
break;
|
|
}
|
|
|
|
case 0xC4: /* CALL NZ imm */
|
|
if(!gb->cpu_reg.f.f_bits.z)
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
inst_cycles += 12;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg += 2;
|
|
|
|
break;
|
|
|
|
case 0xC5: /* PUSH BC */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.bc.bytes.b);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.bc.bytes.c);
|
|
break;
|
|
|
|
case 0xC6: /* ADD A, imm */
|
|
{
|
|
uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
PGB_INSTR_ADC_R8(val, 0);
|
|
break;
|
|
}
|
|
|
|
case 0xC7: /* RST 0x0000 */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.reg = 0x0000;
|
|
break;
|
|
|
|
case 0xC8: /* RET Z */
|
|
if(gb->cpu_reg.f.f_bits.z)
|
|
{
|
|
gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
inst_cycles += 12;
|
|
}
|
|
break;
|
|
|
|
case 0xC9: /* RET */
|
|
{
|
|
gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
break;
|
|
}
|
|
|
|
case 0xCA: /* JP Z, imm */
|
|
if(gb->cpu_reg.f.f_bits.z)
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
inst_cycles += 4;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg += 2;
|
|
|
|
break;
|
|
|
|
case 0xCB: /* CB INST */
|
|
inst_cycles = __gb_execute_cb(gb);
|
|
break;
|
|
|
|
case 0xCC: /* CALL Z, imm */
|
|
if(gb->cpu_reg.f.f_bits.z)
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
inst_cycles += 12;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg += 2;
|
|
|
|
break;
|
|
|
|
case 0xCD: /* CALL imm */
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
}
|
|
break;
|
|
|
|
case 0xCE: /* ADC A, imm */
|
|
{
|
|
uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
PGB_INSTR_ADC_R8(val, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
}
|
|
|
|
case 0xCF: /* RST 0x0008 */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.reg = 0x0008;
|
|
break;
|
|
|
|
case 0xD0: /* RET NC */
|
|
if(!gb->cpu_reg.f.f_bits.c)
|
|
{
|
|
gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
inst_cycles += 12;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xD1: /* POP DE */
|
|
gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
break;
|
|
|
|
case 0xD2: /* JP NC, imm */
|
|
if(!gb->cpu_reg.f.f_bits.c)
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
inst_cycles += 4;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg += 2;
|
|
|
|
break;
|
|
|
|
case 0xD4: /* CALL NC, imm */
|
|
if(!gb->cpu_reg.f.f_bits.c)
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
inst_cycles += 12;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg += 2;
|
|
|
|
break;
|
|
|
|
case 0xD5: /* PUSH DE */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.de.bytes.d);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.de.bytes.e);
|
|
break;
|
|
|
|
case 0xD6: /* SUB imm */
|
|
{
|
|
uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
uint16_t temp = gb->cpu_reg.a - val;
|
|
gb->cpu_reg.f.f_bits.z = ((temp & 0xFF) == 0x00);
|
|
gb->cpu_reg.f.f_bits.n = 1;
|
|
gb->cpu_reg.f.f_bits.h =
|
|
(gb->cpu_reg.a ^ val ^ temp) & 0x10 ? 1 : 0;
|
|
gb->cpu_reg.f.f_bits.c = (temp & 0xFF00) ? 1 : 0;
|
|
gb->cpu_reg.a = (temp & 0xFF);
|
|
break;
|
|
}
|
|
|
|
case 0xD7: /* RST 0x0010 */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.reg = 0x0010;
|
|
break;
|
|
|
|
case 0xD8: /* RET C */
|
|
if(gb->cpu_reg.f.f_bits.c)
|
|
{
|
|
gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
inst_cycles += 12;
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xD9: /* RETI */
|
|
{
|
|
gb->cpu_reg.pc.bytes.c = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.pc.bytes.p = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->gb_ime = true;
|
|
}
|
|
break;
|
|
|
|
case 0xDA: /* JP C, imm */
|
|
if(gb->cpu_reg.f.f_bits.c)
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
inst_cycles += 4;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg += 2;
|
|
|
|
break;
|
|
|
|
case 0xDC: /* CALL C, imm */
|
|
if(gb->cpu_reg.f.f_bits.c)
|
|
{
|
|
uint8_t p, c;
|
|
c = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
p = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.bytes.c = c;
|
|
gb->cpu_reg.pc.bytes.p = p;
|
|
inst_cycles += 12;
|
|
}
|
|
else
|
|
gb->cpu_reg.pc.reg += 2;
|
|
|
|
break;
|
|
|
|
case 0xDE: /* SBC A, imm */
|
|
{
|
|
uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
PGB_INSTR_SBC_R8(val, gb->cpu_reg.f.f_bits.c);
|
|
break;
|
|
}
|
|
|
|
case 0xDF: /* RST 0x0018 */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.reg = 0x0018;
|
|
break;
|
|
|
|
case 0xE0: /* LD (0xFF00+imm), A */
|
|
__gb_write(gb, 0xFF00 | __gb_read(gb, gb->cpu_reg.pc.reg++),
|
|
gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0xE1: /* POP HL */
|
|
gb->cpu_reg.hl.bytes.l = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
break;
|
|
|
|
case 0xE2: /* LD (C), A */
|
|
__gb_write(gb, 0xFF00 | gb->cpu_reg.bc.bytes.c, gb->cpu_reg.a);
|
|
break;
|
|
|
|
case 0xE5: /* PUSH HL */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.hl.bytes.h);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.hl.bytes.l);
|
|
break;
|
|
|
|
case 0xE6: /* AND imm */
|
|
{
|
|
uint8_t temp = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
PGB_INSTR_AND_R8(temp);
|
|
break;
|
|
}
|
|
|
|
case 0xE7: /* RST 0x0020 */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.reg = 0x0020;
|
|
break;
|
|
|
|
case 0xE8: /* ADD SP, imm */
|
|
{
|
|
int8_t offset = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.sp.reg & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0;
|
|
gb->cpu_reg.f.f_bits.c = ((gb->cpu_reg.sp.reg & 0xFF) + (offset & 0xFF) > 0xFF);
|
|
gb->cpu_reg.sp.reg += offset;
|
|
break;
|
|
}
|
|
|
|
case 0xE9: /* JP (HL) */
|
|
gb->cpu_reg.pc.reg = gb->cpu_reg.hl.reg;
|
|
break;
|
|
|
|
case 0xEA: /* LD (imm), A */
|
|
{
|
|
uint8_t h, l;
|
|
uint16_t addr;
|
|
l = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
h = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
addr = PEANUT_GB_U8_TO_U16(h, l);
|
|
__gb_write(gb, addr, gb->cpu_reg.a);
|
|
break;
|
|
}
|
|
|
|
case 0xEE: /* XOR imm */
|
|
PGB_INSTR_XOR_R8(__gb_read(gb, gb->cpu_reg.pc.reg++));
|
|
break;
|
|
|
|
case 0xEF: /* RST 0x0028 */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.reg = 0x0028;
|
|
break;
|
|
|
|
case 0xF0: /* LD A, (0xFF00+imm) */
|
|
gb->cpu_reg.a =
|
|
__gb_read(gb, 0xFF00 | __gb_read(gb, gb->cpu_reg.pc.reg++));
|
|
break;
|
|
|
|
case 0xF1: /* POP AF */
|
|
{
|
|
uint8_t temp_8 = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
gb->cpu_reg.f.f_bits.z = (temp_8 >> 7) & 1;
|
|
gb->cpu_reg.f.f_bits.n = (temp_8 >> 6) & 1;
|
|
gb->cpu_reg.f.f_bits.h = (temp_8 >> 5) & 1;
|
|
gb->cpu_reg.f.f_bits.c = (temp_8 >> 4) & 1;
|
|
gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.sp.reg++);
|
|
break;
|
|
}
|
|
|
|
case 0xF2: /* LD A, (C) */
|
|
gb->cpu_reg.a = __gb_read(gb, 0xFF00 | gb->cpu_reg.bc.bytes.c);
|
|
break;
|
|
|
|
case 0xF3: /* DI */
|
|
gb->gb_ime = false;
|
|
break;
|
|
|
|
case 0xF5: /* PUSH AF */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.a);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg,
|
|
gb->cpu_reg.f.f_bits.z << 7 | gb->cpu_reg.f.f_bits.n << 6 |
|
|
gb->cpu_reg.f.f_bits.h << 5 | gb->cpu_reg.f.f_bits.c << 4);
|
|
break;
|
|
|
|
case 0xF6: /* OR imm */
|
|
PGB_INSTR_OR_R8(__gb_read(gb, gb->cpu_reg.pc.reg++));
|
|
break;
|
|
|
|
case 0xF7: /* PUSH AF */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.reg = 0x0030;
|
|
break;
|
|
|
|
case 0xF8: /* LD HL, SP+/-imm */
|
|
{
|
|
/* Taken from SameBoy, which is released under MIT Licence. */
|
|
int8_t offset = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
gb->cpu_reg.hl.reg = gb->cpu_reg.sp.reg + offset;
|
|
gb->cpu_reg.f.reg = 0;
|
|
gb->cpu_reg.f.f_bits.h = ((gb->cpu_reg.sp.reg & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0;
|
|
gb->cpu_reg.f.f_bits.c = ((gb->cpu_reg.sp.reg & 0xFF) + (offset & 0xFF) > 0xFF) ? 1 : 0;
|
|
break;
|
|
}
|
|
|
|
case 0xF9: /* LD SP, HL */
|
|
gb->cpu_reg.sp.reg = gb->cpu_reg.hl.reg;
|
|
break;
|
|
|
|
case 0xFA: /* LD A, (imm) */
|
|
{
|
|
uint8_t h, l;
|
|
uint16_t addr;
|
|
l = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
h = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
addr = PEANUT_GB_U8_TO_U16(h, l);
|
|
gb->cpu_reg.a = __gb_read(gb, addr);
|
|
break;
|
|
}
|
|
|
|
case 0xFB: /* EI */
|
|
gb->gb_ime = true;
|
|
break;
|
|
|
|
case 0xFE: /* CP imm */
|
|
{
|
|
uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++);
|
|
PGB_INSTR_CP_R8(val);
|
|
break;
|
|
}
|
|
|
|
case 0xFF: /* RST 0x0038 */
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.p);
|
|
__gb_write(gb, --gb->cpu_reg.sp.reg, gb->cpu_reg.pc.bytes.c);
|
|
gb->cpu_reg.pc.reg = 0x0038;
|
|
break;
|
|
|
|
default:
|
|
/* Return address where invalid opcode that was read. */
|
|
(gb->gb_error)(gb, GB_INVALID_OPCODE, gb->cpu_reg.pc.reg - 1);
|
|
PGB_UNREACHABLE();
|
|
}
|
|
|
|
do
|
|
{
|
|
/* DIV register timing */
|
|
gb->counter.div_count += inst_cycles;
|
|
while(gb->counter.div_count >= DIV_CYCLES)
|
|
{
|
|
gb->hram_io[IO_DIV]++;
|
|
gb->counter.div_count -= DIV_CYCLES;
|
|
}
|
|
|
|
/* Check for RTC tick. */
|
|
if(gb->mbc == 3 && (gb->rtc_real.reg.high & 0x40) == 0)
|
|
{
|
|
gb->counter.rtc_count += inst_cycles;
|
|
while(PGB_UNLIKELY(gb->counter.rtc_count >= RTC_CYCLES))
|
|
{
|
|
gb->counter.rtc_count -= RTC_CYCLES;
|
|
|
|
/* Detect invalid rollover. */
|
|
if(PGB_UNLIKELY(gb->rtc_real.reg.sec == 63))
|
|
{
|
|
gb->rtc_real.reg.sec = 0;
|
|
continue;
|
|
}
|
|
|
|
if(++gb->rtc_real.reg.sec != 60)
|
|
continue;
|
|
|
|
gb->rtc_real.reg.sec = 0;
|
|
if(gb->rtc_real.reg.min == 63)
|
|
{
|
|
gb->rtc_real.reg.min = 0;
|
|
continue;
|
|
}
|
|
if(++gb->rtc_real.reg.min != 60)
|
|
continue;
|
|
|
|
gb->rtc_real.reg.min = 0;
|
|
if(gb->rtc_real.reg.hour == 31)
|
|
{
|
|
gb->rtc_real.reg.hour = 0;
|
|
continue;
|
|
}
|
|
if(++gb->rtc_real.reg.hour != 24)
|
|
continue;
|
|
|
|
gb->rtc_real.reg.hour = 0;
|
|
if(++gb->rtc_real.reg.yday != 0)
|
|
continue;
|
|
|
|
if(gb->rtc_real.reg.high & 1) /* Bit 8 of days*/
|
|
gb->rtc_real.reg.high |= 0x80; /* Overflow bit */
|
|
|
|
gb->rtc_real.reg.high ^= 1;
|
|
}
|
|
}
|
|
|
|
/* Check serial transmission. */
|
|
if(gb->hram_io[IO_SC] & SERIAL_SC_TX_START)
|
|
{
|
|
/* If new transfer, call TX function. */
|
|
if(gb->counter.serial_count == 0 &&
|
|
gb->gb_serial_tx != NULL)
|
|
(gb->gb_serial_tx)(gb, gb->hram_io[IO_SB]);
|
|
|
|
gb->counter.serial_count += inst_cycles;
|
|
|
|
/* If it's time to receive byte, call RX function. */
|
|
if(gb->counter.serial_count >= SERIAL_CYCLES)
|
|
{
|
|
/* If RX can be done, do it. */
|
|
/* If RX failed, do not change SB if using external
|
|
* clock, or set to 0xFF if using internal clock. */
|
|
uint8_t rx;
|
|
|
|
if(gb->gb_serial_rx != NULL &&
|
|
(gb->gb_serial_rx(gb, &rx) ==
|
|
GB_SERIAL_RX_SUCCESS))
|
|
{
|
|
gb->hram_io[IO_SB] = rx;
|
|
|
|
/* Inform game of serial TX/RX completion. */
|
|
gb->hram_io[IO_SC] &= 0x01;
|
|
gb->hram_io[IO_IF] |= SERIAL_INTR;
|
|
}
|
|
else if(gb->hram_io[IO_SC] & SERIAL_SC_CLOCK_SRC)
|
|
{
|
|
/* If using internal clock, and console is not
|
|
* attached to any external peripheral, shifted
|
|
* bits are replaced with logic 1. */
|
|
gb->hram_io[IO_SB] = 0xFF;
|
|
|
|
/* Inform game of serial TX/RX completion. */
|
|
gb->hram_io[IO_SC] &= 0x01;
|
|
gb->hram_io[IO_IF] |= SERIAL_INTR;
|
|
}
|
|
else
|
|
{
|
|
/* If using external clock, and console is not
|
|
* attached to any external peripheral, bits are
|
|
* not shifted, so SB is not modified. */
|
|
}
|
|
|
|
gb->counter.serial_count = 0;
|
|
}
|
|
}
|
|
|
|
/* TIMA register timing */
|
|
/* TODO: Change tac_enable to struct of TAC timer control bits. */
|
|
if(gb->hram_io[IO_TAC] & IO_TAC_ENABLE_MASK)
|
|
{
|
|
gb->counter.tima_count += inst_cycles;
|
|
|
|
while(gb->counter.tima_count >=
|
|
TAC_CYCLES[gb->hram_io[IO_TAC] & IO_TAC_RATE_MASK])
|
|
{
|
|
gb->counter.tima_count -=
|
|
TAC_CYCLES[gb->hram_io[IO_TAC] & IO_TAC_RATE_MASK];
|
|
|
|
if(++gb->hram_io[IO_TIMA] == 0)
|
|
{
|
|
gb->hram_io[IO_IF] |= TIMER_INTR;
|
|
/* On overflow, set TMA to TIMA. */
|
|
gb->hram_io[IO_TIMA] = gb->hram_io[IO_TMA];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If LCD is off, don't update LCD state or increase the LCD
|
|
* ticks. Instead, keep track of the amount of time that is
|
|
* being passed. */
|
|
if(!(gb->hram_io[IO_LCDC] & LCDC_ENABLE))
|
|
{
|
|
gb->counter.lcd_off_count += inst_cycles;
|
|
if(gb->counter.lcd_off_count >= LCD_FRAME_CYCLES)
|
|
{
|
|
gb->counter.lcd_off_count -= LCD_FRAME_CYCLES;
|
|
gb->gb_frame = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* LCD Timing */
|
|
gb->counter.lcd_count += inst_cycles;
|
|
|
|
/* New Scanline. HBlank -> VBlank or OAM Scan */
|
|
if(gb->counter.lcd_count >= LCD_LINE_CYCLES)
|
|
{
|
|
gb->counter.lcd_count -= LCD_LINE_CYCLES;
|
|
|
|
/* Next line */
|
|
gb->hram_io[IO_LY] = gb->hram_io[IO_LY] + 1;
|
|
if (gb->hram_io[IO_LY] == LCD_VERT_LINES)
|
|
gb->hram_io[IO_LY] = 0;
|
|
|
|
/* LYC Update */
|
|
if(gb->hram_io[IO_LY] == gb->hram_io[IO_LYC])
|
|
{
|
|
gb->hram_io[IO_STAT] |= STAT_LYC_COINC;
|
|
|
|
if(gb->hram_io[IO_STAT] & STAT_LYC_INTR)
|
|
gb->hram_io[IO_IF] |= LCDC_INTR;
|
|
}
|
|
else
|
|
gb->hram_io[IO_STAT] &= 0xFB;
|
|
|
|
/* Check if LCD should be in Mode 1 (VBLANK) state */
|
|
if(gb->hram_io[IO_LY] == LCD_HEIGHT)
|
|
{
|
|
gb->hram_io[IO_STAT] =
|
|
(gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_VBLANK;
|
|
gb->gb_frame = true;
|
|
gb->hram_io[IO_IF] |= VBLANK_INTR;
|
|
gb->lcd_blank = false;
|
|
|
|
if(gb->hram_io[IO_STAT] & STAT_MODE_1_INTR)
|
|
gb->hram_io[IO_IF] |= LCDC_INTR;
|
|
|
|
#if ENABLE_LCD
|
|
/* If frame skip is activated, check if we need to draw
|
|
* the frame or skip it. */
|
|
if(gb->direct.frame_skip)
|
|
{
|
|
gb->display.frame_skip_count =
|
|
!gb->display.frame_skip_count;
|
|
}
|
|
|
|
/* If interlaced is activated, change which lines get
|
|
* updated. Also, only update lines on frames that are
|
|
* actually drawn when frame skip is enabled. */
|
|
if(gb->direct.interlace &&
|
|
(!gb->direct.frame_skip ||
|
|
gb->display.frame_skip_count))
|
|
{
|
|
gb->display.interlace_count =
|
|
!gb->display.interlace_count;
|
|
}
|
|
#endif
|
|
/* If halted forever, then return on VBLANK. */
|
|
if(gb->gb_halt && !gb->hram_io[IO_IE])
|
|
break;
|
|
}
|
|
/* Start of normal Line (not in VBLANK) */
|
|
else if(gb->hram_io[IO_LY] < LCD_HEIGHT)
|
|
{
|
|
if(gb->hram_io[IO_LY] == 0)
|
|
{
|
|
/* Clear Screen */
|
|
gb->display.WY = gb->hram_io[IO_WY];
|
|
gb->display.window_clear = 0;
|
|
}
|
|
|
|
/* OAM Search occurs at the start of the line. */
|
|
gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_OAM_SCAN;
|
|
gb->counter.lcd_count = 0;
|
|
|
|
if(gb->hram_io[IO_STAT] & STAT_MODE_2_INTR)
|
|
gb->hram_io[IO_IF] |= LCDC_INTR;
|
|
|
|
/* If halted immediately jump to next LCD mode.
|
|
* From OAM Search to LCD Draw. */
|
|
//if(gb->counter.lcd_count < LCD_MODE2_OAM_SCAN_END)
|
|
// inst_cycles = LCD_MODE2_OAM_SCAN_END - gb->counter.lcd_count;
|
|
inst_cycles = LCD_MODE2_OAM_SCAN_DURATION;
|
|
}
|
|
}
|
|
/* Go from Mode 3 (LCD Draw) to Mode 0 (HBLANK). */
|
|
else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_LCD_DRAW &&
|
|
gb->counter.lcd_count >= LCD_MODE3_LCD_DRAW_END)
|
|
{
|
|
gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_HBLANK;
|
|
|
|
if(gb->hram_io[IO_STAT] & STAT_MODE_0_INTR)
|
|
gb->hram_io[IO_IF] |= LCDC_INTR;
|
|
|
|
/* If halted immediately, jump from OAM Scan to LCD Draw. */
|
|
if (gb->counter.lcd_count < LCD_MODE0_HBLANK_MAX_DRUATION)
|
|
inst_cycles = LCD_MODE0_HBLANK_MAX_DRUATION - gb->counter.lcd_count;
|
|
}
|
|
/* Go from Mode 2 (OAM Scan) to Mode 3 (LCD Draw). */
|
|
else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_OAM_SCAN &&
|
|
gb->counter.lcd_count >= LCD_MODE2_OAM_SCAN_END)
|
|
{
|
|
gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_LCD_DRAW;
|
|
#if ENABLE_LCD
|
|
if(!gb->lcd_blank)
|
|
__gb_draw_line(gb);
|
|
#endif
|
|
/* If halted immediately jump to next LCD mode. */
|
|
if (gb->counter.lcd_count < LCD_MODE3_LCD_DRAW_MIN_DURATION)
|
|
inst_cycles = LCD_MODE3_LCD_DRAW_MIN_DURATION - gb->counter.lcd_count;
|
|
}
|
|
} while(gb->gb_halt && (gb->hram_io[IO_IF] & gb->hram_io[IO_IE]) == 0);
|
|
/* If halted, loop until an interrupt occurs. */
|
|
}
|
|
|
|
void gb_run_frame(struct gb_s *gb)
|
|
{
|
|
gb->gb_frame = false;
|
|
|
|
while(!gb->gb_frame)
|
|
__gb_step_cpu(gb);
|
|
}
|
|
|
|
int gb_get_save_size_s(struct gb_s *gb, size_t *ram_size)
|
|
{
|
|
const uint_fast16_t ram_size_location = 0x0149;
|
|
const uint_fast32_t ram_sizes[] =
|
|
{
|
|
/* 0, 2KiB, 8KiB, 32KiB, 128KiB, 64KiB */
|
|
0x00, 0x800, 0x2000, 0x8000, 0x20000, 0x10000
|
|
};
|
|
uint8_t ram_size_code = gb->gb_rom_read(gb, ram_size_location);
|
|
|
|
/* MBC2 always has 512 half-bytes of cart RAM.
|
|
* This assumes that only the lower nibble of each byte is used; the
|
|
* nibbles are not packed. */
|
|
if(gb->mbc == 2)
|
|
{
|
|
*ram_size = 0x200;
|
|
return 0;
|
|
}
|
|
|
|
/* Return -1 on invalid or unsupported RAM size. */
|
|
if(ram_size_code >= PEANUT_GB_ARRAYSIZE(ram_sizes))
|
|
return -1;
|
|
|
|
*ram_size = ram_sizes[ram_size_code];
|
|
return 0;
|
|
}
|
|
|
|
PGB_DEPRECATED("Does not return error code. Use gb_get_save_size_s instead.")
|
|
uint_fast32_t gb_get_save_size(struct gb_s *gb)
|
|
{
|
|
const uint_fast16_t ram_size_location = 0x0149;
|
|
const uint_fast32_t ram_sizes[] =
|
|
{
|
|
/* 0, 2KiB, 8KiB, 32KiB, 128KiB, 64KiB */
|
|
0x00, 0x800, 0x2000, 0x8000, 0x20000, 0x10000
|
|
};
|
|
uint8_t ram_size_code = gb->gb_rom_read(gb, ram_size_location);
|
|
|
|
/* MBC2 always has 512 half-bytes of cart RAM.
|
|
* This assumes that only the lower nibble of each byte is used; the
|
|
* nibbles are not packed. */
|
|
if(gb->mbc == 2)
|
|
return 0x200;
|
|
|
|
/* Return 0 on invalid or unsupported RAM size. */
|
|
if(ram_size_code >= PEANUT_GB_ARRAYSIZE(ram_sizes))
|
|
return 0;
|
|
|
|
return ram_sizes[ram_size_code];
|
|
}
|
|
|
|
void gb_init_serial(struct gb_s *gb,
|
|
void (*gb_serial_tx)(struct gb_s*, const uint8_t),
|
|
enum gb_serial_rx_ret_e (*gb_serial_rx)(struct gb_s*,
|
|
uint8_t*))
|
|
{
|
|
gb->gb_serial_tx = gb_serial_tx;
|
|
gb->gb_serial_rx = gb_serial_rx;
|
|
}
|
|
|
|
uint8_t gb_colour_hash(struct gb_s *gb)
|
|
{
|
|
#define ROM_TITLE_START_ADDR 0x0134
|
|
#define ROM_TITLE_END_ADDR 0x0143
|
|
|
|
uint8_t x = 0;
|
|
uint16_t i;
|
|
|
|
for(i = ROM_TITLE_START_ADDR; i <= ROM_TITLE_END_ADDR; i++)
|
|
x += gb->gb_rom_read(gb, i);
|
|
|
|
return x;
|
|
}
|
|
|
|
/**
|
|
* Resets the context, and initialises startup values for a DMG console.
|
|
*/
|
|
void gb_reset(struct gb_s *gb)
|
|
{
|
|
gb->gb_halt = false;
|
|
gb->gb_ime = true;
|
|
|
|
/* Initialise MBC values. */
|
|
gb->selected_rom_bank = 1;
|
|
gb->cart_ram_bank = 0;
|
|
gb->enable_cart_ram = 0;
|
|
gb->cart_mode_select = 0;
|
|
|
|
/* Use values as though the boot ROM was already executed. */
|
|
if(gb->gb_bootrom_read == NULL)
|
|
{
|
|
uint8_t hdr_chk;
|
|
hdr_chk = gb->gb_rom_read(gb, ROM_HEADER_CHECKSUM_LOC) != 0;
|
|
|
|
gb->cpu_reg.a = 0x01;
|
|
gb->cpu_reg.f.f_bits.z = 1;
|
|
gb->cpu_reg.f.f_bits.n = 0;
|
|
gb->cpu_reg.f.f_bits.h = hdr_chk;
|
|
gb->cpu_reg.f.f_bits.c = hdr_chk;
|
|
gb->cpu_reg.bc.reg = 0x0013;
|
|
gb->cpu_reg.de.reg = 0x00D8;
|
|
gb->cpu_reg.hl.reg = 0x014D;
|
|
gb->cpu_reg.sp.reg = 0xFFFE;
|
|
gb->cpu_reg.pc.reg = 0x0100;
|
|
|
|
gb->hram_io[IO_DIV ] = 0xAB;
|
|
gb->hram_io[IO_LCDC] = 0x91;
|
|
gb->hram_io[IO_STAT] = 0x85;
|
|
gb->hram_io[IO_BOOT] = 0x01;
|
|
|
|
__gb_write(gb, 0xFF26, 0xF1);
|
|
|
|
memset(gb->vram, 0x00, VRAM_SIZE);
|
|
}
|
|
else
|
|
{
|
|
/* Set value as though the console was just switched on.
|
|
* CPU registers are uninitialised. */
|
|
gb->cpu_reg.pc.reg = 0x0000;
|
|
gb->hram_io[IO_DIV ] = 0x00;
|
|
gb->hram_io[IO_LCDC] = 0x00;
|
|
gb->hram_io[IO_STAT] = 0x84;
|
|
gb->hram_io[IO_BOOT] = 0x00;
|
|
}
|
|
|
|
gb->counter.lcd_count = 0;
|
|
gb->counter.div_count = 0;
|
|
gb->counter.tima_count = 0;
|
|
gb->counter.serial_count = 0;
|
|
gb->counter.rtc_count = 0;
|
|
gb->counter.lcd_off_count = 0;
|
|
|
|
gb->direct.joypad = 0xFF;
|
|
gb->hram_io[IO_JOYP] = 0xCF;
|
|
gb->hram_io[IO_SB ] = 0x00;
|
|
gb->hram_io[IO_SC ] = 0x7E;
|
|
/* DIV */
|
|
gb->hram_io[IO_TIMA] = 0x00;
|
|
gb->hram_io[IO_TMA ] = 0x00;
|
|
gb->hram_io[IO_TAC ] = 0xF8;
|
|
gb->hram_io[IO_IF ] = 0xE1;
|
|
|
|
/* LCDC */
|
|
/* STAT */
|
|
gb->hram_io[IO_SCY ] = 0x00;
|
|
gb->hram_io[IO_SCX ] = 0x00;
|
|
gb->hram_io[IO_LY ] = 0x00;
|
|
gb->hram_io[IO_LYC ] = 0x00;
|
|
__gb_write(gb, 0xFF47, 0xFC); // BGP
|
|
__gb_write(gb, 0xFF48, 0xFF); // OBJP0
|
|
__gb_write(gb, 0xFF49, 0xFF); // OBJP1
|
|
gb->hram_io[IO_WY] = 0x00;
|
|
gb->hram_io[IO_WX] = 0x00;
|
|
gb->hram_io[IO_IE] = 0x00;
|
|
gb->hram_io[IO_IF] = 0xE1;
|
|
}
|
|
|
|
enum gb_init_error_e gb_init(struct gb_s *gb,
|
|
uint8_t (*gb_rom_read)(struct gb_s*, const uint_fast32_t),
|
|
uint8_t (*gb_cart_ram_read)(struct gb_s*, const uint_fast32_t),
|
|
void (*gb_cart_ram_write)(struct gb_s*, const uint_fast32_t, const uint8_t),
|
|
void (*gb_error)(struct gb_s*, const enum gb_error_e, const uint16_t),
|
|
void *priv)
|
|
{
|
|
const uint16_t mbc_location = 0x0147;
|
|
const uint16_t bank_count_location = 0x0148;
|
|
const uint16_t ram_size_location = 0x0149;
|
|
/**
|
|
* Table for cartridge type (MBC). -1 if invalid.
|
|
* TODO: MMM01 is untested.
|
|
* TODO: MBC6 is untested.
|
|
* TODO: MBC7 is unsupported.
|
|
* TODO: POCKET CAMERA is unsupported.
|
|
* TODO: BANDAI TAMA5 is unsupported.
|
|
* TODO: HuC3 is unsupported.
|
|
* TODO: HuC1 is unsupported.
|
|
**/
|
|
const int8_t cart_mbc[] =
|
|
{
|
|
0, 1, 1, 1, -1, 2, 2, -1, 0, 0, -1, 0, 0, 0, -1, 3,
|
|
3, 3, 3, 3, -1, -1, -1, -1, -1, 5, 5, 5, 5, 5, 5, -1
|
|
};
|
|
/* Whether cart has RAM. */
|
|
const uint8_t cart_ram[] =
|
|
{
|
|
0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0,
|
|
1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0
|
|
};
|
|
/* How large the ROM is in banks of 16 KiB. */
|
|
const uint16_t num_rom_banks_mask[] =
|
|
{
|
|
2, 4, 8, 16, 32, 64, 128, 256, 512
|
|
};
|
|
/* How large the cart RAM is in banks of 8 KiB. Code $01 is unused, but
|
|
* some early homebrew ROMs supposedly may use this value. */
|
|
const uint8_t num_ram_banks[] = { 0, 1, 1, 4, 16, 8 };
|
|
|
|
gb->gb_rom_read = gb_rom_read;
|
|
gb->gb_cart_ram_read = gb_cart_ram_read;
|
|
gb->gb_cart_ram_write = gb_cart_ram_write;
|
|
gb->gb_error = gb_error;
|
|
gb->direct.priv = priv;
|
|
|
|
/* Initialise serial transfer function to NULL. If the front-end does
|
|
* not provide serial support, Peanut-GB will emulate no cable connected
|
|
* automatically. */
|
|
gb->gb_serial_tx = NULL;
|
|
gb->gb_serial_rx = NULL;
|
|
|
|
gb->gb_bootrom_read = NULL;
|
|
|
|
/* Check valid ROM using checksum value. */
|
|
{
|
|
uint8_t x = 0;
|
|
uint16_t i;
|
|
|
|
for(i = 0x0134; i <= 0x014C; i++)
|
|
x = x - gb->gb_rom_read(gb, i) - 1;
|
|
|
|
if(x != gb->gb_rom_read(gb, ROM_HEADER_CHECKSUM_LOC))
|
|
return GB_INIT_INVALID_CHECKSUM;
|
|
}
|
|
|
|
/* Check if cartridge type is supported, and set MBC type. */
|
|
{
|
|
const uint8_t mbc_value = gb->gb_rom_read(gb, mbc_location);
|
|
|
|
if(mbc_value > sizeof(cart_mbc) - 1 ||
|
|
(gb->mbc = cart_mbc[mbc_value]) == -1)
|
|
return GB_INIT_CARTRIDGE_UNSUPPORTED;
|
|
}
|
|
|
|
gb->num_rom_banks_mask = num_rom_banks_mask[gb->gb_rom_read(gb, bank_count_location)] - 1;
|
|
gb->cart_ram = cart_ram[gb->gb_rom_read(gb, mbc_location)];
|
|
gb->num_ram_banks = num_ram_banks[gb->gb_rom_read(gb, ram_size_location)];
|
|
|
|
/* If the ROM says that it support RAM, but has 0 RAM banks, then
|
|
* disable RAM reads from the cartridge. */
|
|
if(gb->cart_ram == 0 || gb->num_ram_banks == 0)
|
|
{
|
|
gb->cart_ram = 0;
|
|
gb->num_ram_banks = 0;
|
|
}
|
|
|
|
/* If MBC3 and number of ROM or RAM banks are larger than 128 or 8,
|
|
* respectively, then select MBC3O mode. */
|
|
if(gb->mbc == 3)
|
|
gb->cart_is_mbc3O = gb->num_rom_banks_mask > 128 || gb->num_ram_banks > 4;
|
|
|
|
/* Note that MBC2 will appear to have no RAM banks, but it actually
|
|
* always has 512 half-bytes of RAM. Hence, gb->num_ram_banks must be
|
|
* ignored for MBC2. */
|
|
|
|
gb->lcd_blank = false;
|
|
gb->display.lcd_draw_line = NULL;
|
|
|
|
gb_reset(gb);
|
|
|
|
return GB_INIT_NO_ERROR;
|
|
}
|
|
|
|
const char* gb_get_rom_name(struct gb_s* gb, char *title_str)
|
|
{
|
|
uint_fast16_t title_loc = 0x134;
|
|
/* End of title may be 0x13E for newer games. */
|
|
const uint_fast16_t title_end = 0x143;
|
|
const char* title_start = title_str;
|
|
|
|
for(; title_loc <= title_end; title_loc++)
|
|
{
|
|
const char title_char = gb->gb_rom_read(gb, title_loc);
|
|
|
|
if(title_char >= ' ' && title_char <= '_')
|
|
{
|
|
*title_str = title_char;
|
|
title_str++;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
*title_str = '\0';
|
|
return title_start;
|
|
}
|
|
|
|
#if ENABLE_LCD
|
|
void gb_init_lcd(struct gb_s *gb,
|
|
void (*lcd_draw_line)(struct gb_s *gb,
|
|
const uint8_t *pixels,
|
|
const uint_fast8_t line))
|
|
{
|
|
gb->display.lcd_draw_line = lcd_draw_line;
|
|
|
|
gb->direct.interlace = false;
|
|
gb->display.interlace_count = false;
|
|
gb->direct.frame_skip = false;
|
|
gb->display.frame_skip_count = false;
|
|
|
|
gb->display.window_clear = 0;
|
|
gb->display.WY = 0;
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
void gb_set_bootrom(struct gb_s *gb,
|
|
uint8_t (*gb_bootrom_read)(struct gb_s*, const uint_fast16_t))
|
|
{
|
|
gb->gb_bootrom_read = gb_bootrom_read;
|
|
}
|
|
|
|
/**
|
|
* Deprecated. Will be removed in the next major version.
|
|
*/
|
|
PGB_DEPRECATED("RTC is now ticked internally; this function has no effect")
|
|
void gb_tick_rtc(struct gb_s *gb)
|
|
{
|
|
(void) gb;
|
|
return;
|
|
}
|
|
|
|
void gb_set_rtc(struct gb_s *gb, const struct tm * const time)
|
|
{
|
|
gb->rtc_real.bytes[0] = time->tm_sec;
|
|
gb->rtc_real.bytes[1] = time->tm_min;
|
|
gb->rtc_real.bytes[2] = time->tm_hour;
|
|
gb->rtc_real.bytes[3] = time->tm_yday & 0xFF; /* Low 8 bits of day counter. */
|
|
gb->rtc_real.bytes[4] = time->tm_yday >> 8; /* High 1 bit of day counter. */
|
|
}
|
|
#endif // PEANUT_GB_HEADER_ONLY
|
|
|
|
/** Function prototypes: Required functions **/
|
|
/**
|
|
* Initialises the emulator context to a known state. Call this before calling
|
|
* any other peanut-gb function.
|
|
* To reset the emulator, you can call gb_reset() instead.
|
|
*
|
|
* \param gb Allocated emulator context. Must not be NULL.
|
|
* \param gb_rom_read Pointer to function that reads ROM data. ROM banking is
|
|
* already handled by Peanut-GB. Must not be NULL.
|
|
* \param gb_cart_ram_read Pointer to function that reads Cart RAM. Must not be
|
|
* NULL.
|
|
* \param gb_cart_ram_write Pointer to function to writes to Cart RAM. Must not
|
|
* be NULL.
|
|
* \param gb_error Pointer to function that is called when an unrecoverable
|
|
* error occurs. Must not be NULL. Returning from this
|
|
* function is undefined and will result in SIGABRT.
|
|
* \param priv Private data that is stored within the emulator context. Set to
|
|
* NULL if unused.
|
|
* \returns 0 on success or an enum that describes the error.
|
|
*/
|
|
enum gb_init_error_e gb_init(struct gb_s *gb,
|
|
uint8_t (*gb_rom_read)(struct gb_s*, const uint_fast32_t),
|
|
uint8_t (*gb_cart_ram_read)(struct gb_s*, const uint_fast32_t),
|
|
void (*gb_cart_ram_write)(struct gb_s*, const uint_fast32_t, const uint8_t),
|
|
void (*gb_error)(struct gb_s*, const enum gb_error_e, const uint16_t),
|
|
void *priv);
|
|
|
|
/**
|
|
* Executes the emulator and runs for the duration of time equal to one frame.
|
|
*
|
|
* \param An initialised emulator context. Must not be NULL.
|
|
*/
|
|
void gb_run_frame(struct gb_s *gb);
|
|
|
|
/**
|
|
* Internal function used to step the CPU. Used mainly for testing.
|
|
* Use gb_run_frame() instead.
|
|
*
|
|
* \param An initialised emulator context. Must not be NULL.
|
|
*/
|
|
void __gb_step_cpu(struct gb_s *gb);
|
|
|
|
/** Function prototypes: Optional Functions **/
|
|
/**
|
|
* Reset the emulator, like turning the Game Boy off and on again.
|
|
* This function can be called at any time.
|
|
*
|
|
* \param An initialised emulator context. Must not be NULL.
|
|
*/
|
|
void gb_reset(struct gb_s *gb);
|
|
|
|
/**
|
|
* Initialises the display context of the emulator. Only available when
|
|
* ENABLE_LCD is defined to a non-zero value.
|
|
* The pixel data sent to lcd_draw_line comes with both shade and layer data.
|
|
* The first two least significant bits are the shade data (black, dark, light,
|
|
* white). Bits 4 and 5 are layer data (OBJ0, OBJ1, BG), which can be used to
|
|
* add more colours to the game in the same way that the Game Boy Color does to
|
|
* older Game Boy games.
|
|
* This function can be called at any time.
|
|
*
|
|
* \param gb An initialised emulator context. Must not be NULL.
|
|
* \param lcd_draw_line Pointer to function that draws the 2-bit pixel data on the line
|
|
* "line". Must not be NULL.
|
|
*/
|
|
#if ENABLE_LCD
|
|
void gb_init_lcd(struct gb_s *gb,
|
|
void (*lcd_draw_line)(struct gb_s *gb,
|
|
const uint8_t *pixels,
|
|
const uint_fast8_t line));
|
|
#endif
|
|
|
|
/**
|
|
* Initialises the serial connection of the emulator. This function is optional,
|
|
* and if not called, the emulator will assume that no link cable is connected
|
|
* to the game.
|
|
*
|
|
* \param gb An initialised emulator context. Must not be NULL.
|
|
* \param gb_serial_tx Pointer to function that transmits a byte of data over
|
|
* the serial connection. Must not be NULL.
|
|
* \param gb_serial_rx Pointer to function that receives a byte of data over the
|
|
* serial connection. If no byte is received,
|
|
* return GB_SERIAL_RX_NO_CONNECTION. Must not be NULL.
|
|
*/
|
|
void gb_init_serial(struct gb_s *gb,
|
|
void (*gb_serial_tx)(struct gb_s*, const uint8_t),
|
|
enum gb_serial_rx_ret_e (*gb_serial_rx)(struct gb_s*,
|
|
uint8_t*));
|
|
|
|
/**
|
|
* Obtains the save size of the game (size of the Cart RAM). Required by the
|
|
* frontend to allocate enough memory for the Cart RAM.
|
|
*
|
|
* \param gb An initialised emulator context. Must not be NULL.
|
|
* \param ram_size Pointer to size_t variable that will be set to the size of
|
|
* the Cart RAM in bytes. Must not be NULL.
|
|
* If the Cart RAM is not battery backed, this will be set to 0.
|
|
* If the Cart RAM size is invalid or unknown, this will not be
|
|
* set.
|
|
* \returns 0 on success, or -1 if the RAM size is invalid or unknown.
|
|
*/
|
|
int gb_get_save_size_s(struct gb_s *gb, size_t *ram_size);
|
|
|
|
/**
|
|
* Deprecated. Use gb_get_save_size_s() instead.
|
|
* Obtains the save size of the game (size of the Cart RAM). Required by the
|
|
* frontend to allocate enough memory for the Cart RAM.
|
|
*
|
|
* \param gb An initialised emulator context. Must not be NULL.
|
|
* \returns Size of the Cart RAM in bytes. 0 if Cartridge has not battery
|
|
* backed RAM.
|
|
* 0 is also returned on invalid or unknown RAM size.
|
|
*/
|
|
uint_fast32_t gb_get_save_size(struct gb_s *gb);
|
|
|
|
/**
|
|
* Calculates and returns a hash of the game header in the same way the Game
|
|
* Boy Color does for colourising old Game Boy games. The frontend can use this
|
|
* hash to automatically set a colour palette.
|
|
*
|
|
* \param gb An initialised emulator context. Must not be NULL.
|
|
* \returns Hash of the game header.
|
|
*/
|
|
uint8_t gb_colour_hash(struct gb_s *gb);
|
|
|
|
/**
|
|
* Returns the title of ROM.
|
|
*
|
|
* \param gb An initialised emulator context. Must not be NULL.
|
|
* \param title_str Allocated string at least 16 characters.
|
|
* \returns Pointer to start of string, null terminated.
|
|
*/
|
|
const char* gb_get_rom_name(struct gb_s* gb, char *title_str);
|
|
|
|
/**
|
|
* Deprecated. Will be removed in the next major version.
|
|
* RTC is ticked internally and this function has no effect.
|
|
*/
|
|
void gb_tick_rtc(struct gb_s *gb);
|
|
|
|
/**
|
|
* Set initial values in RTC.
|
|
* Should be called after gb_init().
|
|
*
|
|
* \param gb An initialised emulator context. Must not be NULL.
|
|
* \param time Time structure with date and time.
|
|
*/
|
|
void gb_set_rtc(struct gb_s *gb, const struct tm * const time);
|
|
|
|
/**
|
|
* Use boot ROM on reset. gb_reset() must be called for this to take affect.
|
|
* \param gb An initialised emulator context. Must not be NULL.
|
|
* \param gb_bootrom_read Function pointer to read boot ROM binary.
|
|
*/
|
|
void gb_set_bootrom(struct gb_s *gb,
|
|
uint8_t (*gb_bootrom_read)(struct gb_s*, const uint_fast16_t));
|
|
|
|
/* Undefine CPU Flag helper functions. */
|
|
#undef PEANUT_GB_CPUFLAG_MASK_CARRY
|
|
#undef PEANUT_GB_CPUFLAG_MASK_HALFC
|
|
#undef PEANUT_GB_CPUFLAG_MASK_ARITH
|
|
#undef PEANUT_GB_CPUFLAG_MASK_ZERO
|
|
#undef PEANUT_GB_CPUFLAG_BIT_CARRY
|
|
#undef PEANUT_GB_CPUFLAG_BIT_HALFC
|
|
#undef PEANUT_GB_CPUFLAG_BIT_ARITH
|
|
#undef PEANUT_GB_CPUFLAG_BIT_ZERO
|
|
#undef PGB_SET_CARRY
|
|
#undef PGB_SET_HALFC
|
|
#undef PGB_SET_ARITH
|
|
#undef PGB_SET_ZERO
|
|
#undef PGB_GET_CARRY
|
|
#undef PGB_GET_HALFC
|
|
#undef PGB_GET_ARITH
|
|
#undef PGB_GET_ZERO
|
|
#endif //PEANUT_GB_H
|