diff --git a/apps/Peanut-GB/app.elf b/apps/Peanut-GB/app.elf index 85c15091..7923d6e9 100644 Binary files a/apps/Peanut-GB/app.elf and b/apps/Peanut-GB/app.elf differ diff --git a/apps/Peanut-GB/main.c b/apps/Peanut-GB/main.c index fe852cf3..ced7ef2b 100644 --- a/apps/Peanut-GB/main.c +++ b/apps/Peanut-GB/main.c @@ -8,8 +8,6 @@ #include "peanut_gb.h" #include "lz4.h" -#define PEANUT_FULL_GBC_SUPPORT 1 - #define MAX_SCRIPTSTORE_SIZE 8192 #define SAVE_COOLDOWN 120 @@ -90,79 +88,54 @@ const uint16_t palette_gray_negative[4] = {0x0000, 0x52AA, 0xAD55, 0xFFFF}; const uint16_t * palette = palette_peanut_GB; uint16_t color_from_gb_pixel(uint8_t gb_pixel) { - uint8_t gb_color = gb_pixel & 0x3; - return palette[gb_color]; + uint8_t gb_color = gb_pixel & 0x3; + return palette[gb_color]; } -void lcd_draw_line_centered(struct gb_s *gb, const uint8_t * input_pixels, const uint_fast8_t line) { - uint16_t output_pixels[2*LCD_WIDTH]; +void lcd_draw_line_centered(struct gb_s *gb, const uint8_t pixels[LCD_WIDTH], const uint_fast8_t line) { + struct priv_t *priv = gb->direct.priv; - for(int i = 0; i < LCD_WIDTH; i++) { - if (gb->cgb.cgbMode) { - output_pixels[i] = gb->cgb.fixPalette[input_pixels[i]]; - output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x3E0) << 1 | (output_pixels[i] & 0x7C00) >> 10; - output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x7E0) | (output_pixels[i] & 0xF800) >> 11; - } else { - output_pixels[i] = color_from_gb_pixel(input_pixels[i]); - } + for(unsigned int x = 0; x < LCD_WIDTH; x++) { + priv->line_buffer[x] = color_from_gb_pixel(pixels[x]); } - extapp_pushRect((NW_LCD_WIDTH - LCD_WIDTH) / 2, (NW_LCD_HEIGHT - LCD_HEIGHT) / 2 + line, LCD_WIDTH, 1, output_pixels); + extapp_pushRect((NW_LCD_WIDTH - LCD_WIDTH) / 2, (NW_LCD_HEIGHT - LCD_HEIGHT) / 2 + line, LCD_WIDTH, 1, priv->line_buffer); } static void lcd_draw_line_maximized(struct gb_s * gb, const uint8_t * input_pixels, const uint_fast8_t line) { // Nearest neighbor scaling of a 160x144 texture to a 320x240 resolution // Horizontally, we just double - uint16_t output_pixels[LCD_WIDTH]; - uint16_t zoomPixels[2 * LCD_WIDTH]; - for (int i = 0; i < LCD_WIDTH; i++) { - if (gb->cgb.cgbMode) { - output_pixels[i] = gb->cgb.fixPalette[input_pixels[i]]; - output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x3E0) << 1 | (output_pixels[i] & 0x7C00) >> 10; - output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x7E0) | (output_pixels[i] & 0xF800) >> 11; - } else { - output_pixels[i] = color_from_gb_pixel(input_pixels[i]); - } - - uint16_t color = output_pixels[i]; - - zoomPixels[2 * i] = color; - zoomPixels[2 * i + 1] = color; + uint16_t output_pixels[2*LCD_WIDTH]; + for (int i=0; icgb.cgbMode) { - output_pixels[i] = gb->cgb.fixPalette[input_pixels[i]]; - output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x3E0) << 1 | (output_pixels[i] & 0x7C00) >> 10; - output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x7E0) | (output_pixels[i] & 0xF800) >> 11; - } else { - output_pixels[i] = color_from_gb_pixel(input_pixels[i]); - } - - uint16_t color = output_pixels[i]; - - zoomPixels[166 * i / 100] = color; - zoomPixels[166 * i / 100 + 1] = color; - zoomPixels[166 * i / 100 + 2] = color; + uint16_t output_pixels[266]; + for (int i=0; i /* Required for qsort and abort */ #include /* Required for int types */ -#include /* Required for memset */ #include /* 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 +# 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 1 +# define ENABLE_LCD 1 #endif -/* Adds more code to improve LCD rendering accuracy. */ -#ifndef PEANUT_GB_HIGH_LCD_ACCURACY -# define PEANUT_GB_HIGH_LCD_ACCURACY 1 -#endif - -/* Use intrinsic functions. This may produce smaller and faster code. */ -#ifndef PEANUT_GB_USE_INTRINSICS -# define PEANUT_GB_USE_INTRINSICS 1 -#endif - -#ifndef PEANUT_FULL_GBC_SUPPORT -# define PEANUT_FULL_GBC_SUPPORT 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 @@ -122,14 +57,9 @@ #define ANY_INTR 0x1F /* Memory section sizes for DMG */ -#if PEANUT_FULL_GBC_SUPPORT -#define WRAM_SIZE 0x8000 -#define VRAM_SIZE 0x4000 -#else #define WRAM_SIZE 0x2000 #define VRAM_SIZE 0x2000 -#endif -#define HRAM_IO_SIZE 0x0100 +#define HRAM_SIZE 0x0100 #define OAM_SIZE 0x00A0 /* Memory addresses */ @@ -158,20 +88,16 @@ /* Serial clock locked to 8192Hz on DMG. * 4194304 / (8192 / 8) = 4096 clock cycles for sending 1 byte. */ -#define SERIAL_CYCLES 4096 -#define SERIAL_CYCLES_1KB (SERIAL_CYCLES/1ul) -#define SERIAL_CYCLES_2KB (SERIAL_CYCLES/2ul) -#define SERIAL_CYCLES_32KB (SERIAL_CYCLES/32ul) -#define SERIAL_CYCLES_64KB (SERIAL_CYCLES/64ul) +#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) +#define DMG_CLOCK_FREQ 4194304.0 +#define SCREEN_REFRESH_CYCLES 70224.0 +#define VERTICAL_SYNC (DMG_CLOCK_FREQ/SCREEN_REFRESH_CYCLES) /* SERIAL SC register masks. */ -#define SERIAL_SC_TX_START 0x80 -#define SERIAL_SC_CLOCK_SRC 0x01 +#define SERIAL_SC_TX_START 0x80 +#define SERIAL_SC_CLOCK_SRC 0x01 /* STAT register masks */ #define STAT_LYC_INTR 0x40 @@ -192,16 +118,11 @@ #define LCDC_OBJ_ENABLE 0x02 #define LCDC_BG_ENABLE 0x01 -/** LCD characteristics **/ -/* PPU cycles through modes every 456 cycles. */ +/* LCD characteristics */ #define LCD_LINE_CYCLES 456 -/* Mode 0 starts on cycle 372. */ -#define LCD_MODE_0_CYCLES 372 -/* Mode 2 starts on cycle 204. */ +#define LCD_MODE_0_CYCLES 0 #define LCD_MODE_2_CYCLES 204 -/* Mode 3 starts on cycle 284. */ #define LCD_MODE_3_CYCLES 284 -/* There are 154 scanlines. LY < 154. */ #define LCD_VERT_LINES 154 #define LCD_WIDTH 160 #define LCD_HEIGHT 144 @@ -228,222 +149,70 @@ #define OBJ_FLIP_Y 0x40 #define OBJ_FLIP_X 0x20 #define OBJ_PALETTE 0x10 -#if PEANUT_FULL_GBC_SUPPORT -#define OBJ_BANK 0x08 -#define OBJ_CGB_PALETTE 0x07 -#endif #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])) - -#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 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 -# 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_bits.c = PGB_INTRIN_SBC(gb->cpu_reg.a,r,cin,temp);\ - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ - gb->cpu_reg.f_bits.n = 1; \ - gb->cpu_reg.f_bits.z = (temp == 0x00); \ - gb->cpu_reg.a = temp; \ - } - -# define PGB_INSTR_CP_R8(r) \ - { \ - uint8_t temp; \ - gb->cpu_reg.f_bits.c = PGB_INTRIN_SBC(gb->cpu_reg.a,r,0,temp); \ - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ - gb->cpu_reg.f_bits.n = 1; \ - gb->cpu_reg.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_bits.c = (temp & 0xFF00) ? 1 : 0; \ - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ - gb->cpu_reg.f_bits.n = 1; \ - gb->cpu_reg.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_bits.c = (temp & 0xFF00) ? 1 : 0; \ - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ - gb->cpu_reg.f_bits.n = 1; \ - gb->cpu_reg.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_bits.c = PGB_INTRIN_ADC(gb->cpu_reg.a,r,cin,temp);\ - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ - gb->cpu_reg.f_bits.n = 0; \ - gb->cpu_reg.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_bits.c = (temp & 0xFF00) ? 1 : 0; \ - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ - gb->cpu_reg.f_bits.n = 0; \ - gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); \ - gb->cpu_reg.a = (temp & 0xFF); \ - } -#endif /* PGB_INTRIN_ADC */ - -#define PGB_INSTR_DEC_R8(r) \ - r--; \ - gb->cpu_reg.f_bits.h = ((r & 0x0F) == 0x0F); \ - gb->cpu_reg.f_bits.n = 1; \ - gb->cpu_reg.f_bits.z = (r == 0x00); - -#define PGB_INSTR_XOR_R8(r) \ - gb->cpu_reg.a ^= r; \ - gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); \ - gb->cpu_reg.f_bits.n = 0; \ - gb->cpu_reg.f_bits.h = 0; \ - gb->cpu_reg.f_bits.c = 0; - -#define PGB_INSTR_OR_R8(r) \ - gb->cpu_reg.a |= r; \ - gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); \ - gb->cpu_reg.f_bits.n = 0; \ - gb->cpu_reg.f_bits.h = 0; \ - gb->cpu_reg.f_bits.c = 0; - -#define PGB_INSTR_AND_R8(r) \ - gb->cpu_reg.a &= r; \ - gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); \ - gb->cpu_reg.f_bits.n = 0; \ - gb->cpu_reg.f_bits.h = 1; \ - gb->cpu_reg.f_bits.c = 0; - -#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)) + #define MIN(a, b) ((a) < (b) ? (a) : (b)) #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. */ - struct - { - 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 a; - + /* Combine A and F registers. */ union { struct { - uint8_t PEANUT_GB_LE_REG(c,b); - } bytes; - uint16_t reg; - } bc; + /* Define specific bits of Flag register. */ + union + { + struct + { + uint8_t unused : 4; + 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 f; + }; + uint8_t a; + }; + uint16_t af; + }; union { struct { - uint8_t PEANUT_GB_LE_REG(e,d); - } bytes; - uint16_t reg; - } de; + uint8_t c; + uint8_t b; + }; + uint16_t bc; + }; union { struct { - uint8_t PEANUT_GB_LE_REG(l,h); - } bytes; - uint16_t reg; - } hl; + uint8_t e; + uint8_t d; + }; + uint16_t de; + }; - /* Stack pointer */ union { struct { - uint8_t PEANUT_GB_LE_REG(p, s); - } bytes; - uint16_t reg; - } sp; + uint8_t l; + uint8_t h; + }; + uint16_t hl; + }; - /* Program counter */ - union - { - struct - { - uint8_t PEANUT_GB_LE_REG(c, p); - } bytes; - uint16_t reg; - } pc; -#undef PEANUT_GB_LE_REG + uint16_t sp; /* Stack pointer */ + uint16_t pc; /* Program counter */ }; struct count_s @@ -454,11 +223,53 @@ struct count_s uint_fast16_t serial_count; /* Serial Counter */ }; +struct gb_registers_s +{ + /* TODO: Sort variables in address order. */ + /* Timing */ + uint8_t TIMA, TMA, DIV; + union + { + struct + { + uint8_t tac_rate : 2; /* Input clock select */ + uint8_t tac_enable : 1; /* Timer enable */ + uint8_t unused : 5; + }; + uint8_t TAC; + }; + + /* LCD */ + uint8_t LCDC; + uint8_t STAT; + uint8_t SCY; + uint8_t SCX; + uint8_t LY; + uint8_t LYC; + uint8_t DMA; + uint8_t BGP; + uint8_t OBP0; + uint8_t OBP1; + uint8_t WY; + uint8_t WX; + + /* Joypad info. */ + uint8_t P1; + + /* Serial data. */ + uint8_t SB; + uint8_t SC; + + /* Interrupt flag. */ + uint8_t IF; + + /* Interrupt enable. */ + uint8_t IE; +}; + #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. @@ -473,7 +284,6 @@ struct count_s * LCD_PALETTE_ALL == 0b11 --> NOT POSSIBLE */ #define LCD_PALETTE_ALL 0x30 -# endif #endif /** @@ -481,11 +291,10 @@ struct count_s */ enum gb_error_e { - GB_UNKNOWN_ERROR = 0, + GB_UNKNOWN_ERROR, GB_INVALID_OPCODE, GB_INVALID_READ, GB_INVALID_WRITE, - GB_HALT_FOREVER, GB_INVALID_MAX }; @@ -495,7 +304,7 @@ enum gb_error_e */ enum gb_init_error_e { - GB_INIT_NO_ERROR = 0, + GB_INIT_NO_ERROR, GB_INIT_CARTRIDGE_UNSUPPORTED, GB_INIT_INVALID_CHECKSUM }; @@ -548,38 +357,42 @@ struct gb_s /** * Notify front-end of error. * - * \param gb_s emulator context + * \param gb_s emulator context * \param gb_error_e error code - * \param addr address of where error occurred + * \param val arbitrary value related to error */ - void (*gb_error)(struct gb_s*, const enum gb_error_e, const uint16_t addr); + void (*gb_error)(struct gb_s*, const enum gb_error_e, const uint16_t val); /* 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 { - uint8_t gb_halt : 1; - uint8_t gb_ime : 1; - uint8_t gb_frame : 1; /* New frame drawn. */ - uint8_t lcd_blank : 1; + unsigned int gb_halt : 1; + unsigned int gb_ime : 1; + unsigned int gb_bios_enable : 1; + unsigned int gb_frame : 1; /* New frame drawn. */ + enum + { + LCD_HBLANK = 0, + LCD_VBLANK = 1, + LCD_SEARCH_OAM = 2, + LCD_TRANSFER = 3 + } lcd_mode : 2; }; /* Cartridge information: * Memory Bank Controller (MBC) type. */ - int8_t mbc; + uint8_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. */ + uint16_t num_rom_banks; + /* Number of RAM banks in cartridge. */ uint8_t num_ram_banks; - uint16_t selected_rom_bank; + uint8_t selected_rom_bank; /* WRAM and VRAM bank selection not available. */ uint8_t cart_ram_bank; uint8_t enable_cart_ram; @@ -599,14 +412,14 @@ struct gb_s }; struct cpu_registers_s cpu_reg; - //struct gb_registers_s gb_reg; + struct gb_registers_s gb_reg; struct count_s counter; /* TODO: Allow implementation to allocate WRAM, VRAM and Frame Buffer. */ uint8_t* wram; uint8_t* vram; + uint8_t* hram; uint8_t* oam; - uint8_t* hram_io; struct { @@ -614,7 +427,7 @@ struct gb_s * Draw line on screen. * * \param gb_s emulator context - * \param pixels The 160 pixels to draw. + * \param pixels pixels to draw. * Bits 1-0 are the colour to draw. * Bits 5-4 are the palette, where: * OBJ0 = 0b00, @@ -629,8 +442,8 @@ struct gb_s * \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, + void (*lcd_draw_line)(struct gb_s*, + const uint8_t pixels[static 160], const uint_fast8_t line); /* Palettes */ @@ -641,35 +454,10 @@ struct gb_s uint8_t WY; /* Only support 30fps frame skip. */ - uint8_t frame_skip_count : 1; - uint8_t interlace_count : 1; + unsigned int frame_skip_count : 1; + unsigned int interlace_count : 1; } display; -#if PEANUT_FULL_GBC_SUPPORT - /* Game Boy Color Mode*/ - struct { - uint8_t cgbMode; - uint8_t doubleSpeed; - uint8_t doubleSpeedPrep; - uint8_t wramBank; - uint16_t wramBankOffset; - uint8_t vramBank; - uint16_t vramBankOffset; - uint16_t fixPalette[0x40]; //BG then OAM palettes fixed for the screen - uint8_t OAMPalette[0x40]; - uint8_t BGPalette[0x40]; - uint8_t OAMPaletteID; - uint8_t BGPaletteID; - uint8_t OAMPaletteInc; - uint8_t BGPaletteInc; - uint8_t dmaActive; - uint8_t dmaMode; - uint8_t dmaSize; - uint16_t dmaSource; - uint16_t dmaDest; - } cgb; -#endif - /** * Variables that may be modified directly by the front-end. * This method seems to be easier and possibly less overhead than @@ -682,21 +470,21 @@ struct gb_s /* Set to enable interlacing. Interlacing will start immediately * (at the next line drawing). */ - uint8_t interlace : 1; - uint8_t frame_skip : 1; + unsigned int interlace : 1; + unsigned int frame_skip : 1; union { struct { - uint8_t a : 1; - uint8_t b : 1; - uint8_t select : 1; - uint8_t start : 1; - uint8_t right : 1; - uint8_t left : 1; - uint8_t up : 1; - uint8_t down : 1; + unsigned int a : 1; + unsigned int b : 1; + unsigned int select : 1; + unsigned int start : 1; + unsigned int right : 1; + unsigned int left : 1; + unsigned int up : 1; + unsigned int down : 1; } joypad_bits; uint8_t joypad; }; @@ -706,59 +494,65 @@ struct gb_s } 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_BOOT 0x50 -#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_BANK 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_SEARCH_OAM 2 -#define IO_STAT_MODE_SEARCH_TRANSFER 3 -#define IO_STAT_MODE_VBLANK_OR_TRANSFER_MASK 0x1 +/** + * Tick the internal RTC by one second. + * This was taken from SameBoy, which is released under MIT Licence. + */ +void gb_tick_rtc(struct gb_s *gb) +{ + /* is timer running? */ + if((gb->cart_rtc[4] & 0x40) == 0) + { + if(++gb->rtc_bits.sec == 60) + { + gb->rtc_bits.sec = 0; + + if(++gb->rtc_bits.min == 60) + { + gb->rtc_bits.min = 0; + + if(++gb->rtc_bits.hour == 24) + { + gb->rtc_bits.hour = 0; + + if(++gb->rtc_bits.yday == 0) + { + if(gb->rtc_bits.high & 1) /* Bit 8 of days*/ + { + gb->rtc_bits.high |= 0x80; /* Overflow bit */ + } + + gb->rtc_bits.high ^= 1; + } + } + } + } + } +} + +/** + * Set initial values in RTC. + * Should be called after gb_init(). + */ +void gb_set_rtc(struct gb_s *gb, const struct tm * const time) +{ + gb->cart_rtc[0] = time->tm_sec; + gb->cart_rtc[1] = time->tm_min; + gb->cart_rtc[2] = time->tm_hour; + gb->cart_rtc[3] = time->tm_yday & 0xFF; /* Low 8 bits of day counter. */ + gb->cart_rtc[4] = time->tm_yday >> 8; /* High 1 bit of day counter. */ +} /** * Internal function used to read bytes. - * addr is host platform endian. */ -uint8_t __gb_read(struct gb_s *gb, uint16_t addr) +uint8_t __gb_read(struct gb_s *gb, const uint_fast16_t addr) { - switch(PEANUT_GB_GET_MSN16(addr)) + switch(addr >> 12) { case 0x0: - /* IO_BANK is only set to 1 if gb->gb_bootrom_read was not NULL - * on reset. */ - if(gb->hram_io[IO_BANK] == 0 && addr < 0x0100) - { - return gb->gb_bootrom_read(gb, addr); - } - /* Fallthrough */ + /* TODO: BIOS support. */ case 0x1: case 0x2: case 0x3: @@ -776,25 +570,14 @@ uint8_t __gb_read(struct gb_s *gb, uint16_t addr) case 0x8: case 0x9: -#if PEANUT_FULL_GBC_SUPPORT - return gb->vram[addr - gb->cgb.vramBankOffset]; -#else return gb->vram[addr - VRAM_ADDR]; -#endif + case 0xA: case 0xB: - if(gb->mbc == 3 && gb->cart_ram_bank >= 0x08) - { - return gb->cart_rtc[gb->cart_ram_bank - 0x08]; - } - else if(gb->cart_ram && gb->enable_cart_ram) + 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); - } + if(gb->mbc == 3 && gb->cart_ram_bank >= 0x08) + return gb->cart_rtc[gb->cart_ram_bank - 0x08]; else if((gb->cart_mode_select || gb->mbc != 1) && gb->cart_ram_bank < gb->num_ram_banks) { @@ -805,136 +588,153 @@ uint8_t __gb_read(struct gb_s *gb, uint16_t addr) return gb->gb_cart_ram_read(gb, addr - CART_RAM_ADDR); } - return 0xFF; + return 0; case 0xC: - case 0xD: -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode && addr >= WRAM_1_ADDR) - return gb->wram[addr - gb->cgb.wramBankOffset]; -#endif return gb->wram[addr - WRAM_0_ADDR]; + case 0xD: + return gb->wram[addr - WRAM_1_ADDR + WRAM_BANK_SIZE]; + case 0xE: return gb->wram[addr - ECHO_ADDR]; case 0xF: if(addr < OAM_ADDR) -#if PEANUT_FULL_GBC_SUPPORT - return gb->wram[(addr - 0x2000) - gb->cgb.wramBankOffset]; -#else return gb->wram[addr - ECHO_ADDR]; -#endif if(addr < UNUSED_ADDR) return gb->oam[addr - OAM_ADDR]; - /* Unusable memory area. Reading from this area returns 0xFF.*/ + /* Unusable memory area. Reading from this area returns 0.*/ if(addr < IO_ADDR) return 0xFF; - /* APU registers. */ + /* HRAM */ + if(HRAM_ADDR <= addr && addr < INTR_EN_ADDR) + return gb->hram[addr - HRAM_ADDR]; + 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]; + return 1; #endif } -#if PEANUT_FULL_GBC_SUPPORT /* IO and Interrupts. */ - switch (addr & 0xFF) + switch(addr & 0xFF) { - /* Speed Switch*/ - case 0x4D: - return (gb->cgb.doubleSpeed << 7) + gb->cgb.doubleSpeedPrep; - /* CGB VRAM Bank*/ - case 0x4F: - return gb->cgb.vramBank | 0xFE; - /* CGB DMA*/ - case 0x51: - return (gb->cgb.dmaSource >> 8); - case 0x52: - return (gb->cgb.dmaSource & 0xF0); - case 0x53: - return (gb->cgb.dmaDest >> 8); - case 0x54: - return (gb->cgb.dmaDest & 0xF0); - case 0x55: - return (gb->cgb.dmaActive << 7) | (gb->cgb.dmaSize - 1); - /* IR Register*/ - case 0x56: - return gb->hram_io[0x56]; - /* CGB BG Palette Index*/ - case 0x68: - return (gb->cgb.BGPaletteID & 0x3F) + (gb->cgb.BGPaletteInc << 7); - /* CGB BG Palette*/ - case 0x69: - return gb->cgb.BGPalette[(gb->cgb.BGPaletteID & 0x3F)]; - /* CGB OAM Palette Index*/ - case 0x6A: - return (gb->cgb.OAMPaletteID & 0x3F) + (gb->cgb.OAMPaletteInc << 7); - /* CGB OAM Palette*/ - case 0x6B: - return gb->cgb.OAMPalette[(gb->cgb.OAMPaletteID & 0x3F)]; - /* CGB WRAM Bank*/ - case 0x70: - return gb->cgb.wramBank; + /* IO Registers */ + case 0x00: + return 0xC0 | gb->gb_reg.P1; + + case 0x01: + return gb->gb_reg.SB; + + case 0x02: + return gb->gb_reg.SC; + + /* Timer Registers */ + case 0x04: + return gb->gb_reg.DIV; + + case 0x05: + return gb->gb_reg.TIMA; + + case 0x06: + return gb->gb_reg.TMA; + + case 0x07: + return gb->gb_reg.TAC; + + /* Interrupt Flag Register */ + case 0x0F: + return gb->gb_reg.IF; + + /* LCD Registers */ + case 0x40: + return gb->gb_reg.LCDC; + + case 0x41: + return (gb->gb_reg.STAT & STAT_USER_BITS) | + (gb->gb_reg.LCDC & LCDC_ENABLE ? gb->lcd_mode : LCD_VBLANK); + + case 0x42: + return gb->gb_reg.SCY; + + case 0x43: + return gb->gb_reg.SCX; + + case 0x44: + return gb->gb_reg.LY; + + case 0x45: + return gb->gb_reg.LYC; + + /* DMA Register */ + case 0x46: + return gb->gb_reg.DMA; + + /* DMG Palette Registers */ + case 0x47: + return gb->gb_reg.BGP; + + case 0x48: + return gb->gb_reg.OBP0; + + case 0x49: + return gb->gb_reg.OBP1; + + /* Window Position Registers */ + case 0x4A: + return gb->gb_reg.WY; + + case 0x4B: + return gb->gb_reg.WX; + + /* Interrupt Enable Register */ + case 0xFF: + return gb->gb_reg.IE; + + /* Unused registers return 1 */ default: -#endif - /* HRAM */ - if(addr >= IO_ADDR) - return gb->hram_io[addr - IO_ADDR]; -#if PEANUT_FULL_GBC_SUPPORT + return 0xFF; } -#endif } - - /* Return address that caused read error. */ (gb->gb_error)(gb, GB_INVALID_READ, addr); - PGB_UNREACHABLE(); + return 0xFF; } /** * Internal function used to write bytes. */ -void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) +void __gb_write(struct gb_s *gb, const uint_fast16_t addr, const uint8_t val) { - switch(PEANUT_GB_GET_MSN16(addr)) + switch(addr >> 12) { 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); + if(gb->mbc == 2 && addr & 0x10) return; - } + else if(gb->mbc > 0 && 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; + gb->selected_rom_bank % gb->num_rom_banks; return; } /* Intentional fall through. */ + case 0x3: if(gb->mbc == 1) { @@ -944,22 +744,12 @@ void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) if((gb->selected_rom_bank & 0x1F) == 0x00) gb->selected_rom_bank++; } - else if(gb->mbc == 2) + else if(gb->mbc == 2 && addr & 0x10) { - /* 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; - } + gb->selected_rom_bank = val & 0x0F; + + if(!gb->selected_rom_bank) + gb->selected_rom_bank++; } else if(gb->mbc == 3) { @@ -971,7 +761,7 @@ void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) 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; + gb->selected_rom_bank = gb->selected_rom_bank % gb->num_rom_banks; return; case 0x4: @@ -980,7 +770,7 @@ void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) { 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; + gb->selected_rom_bank = gb->selected_rom_bank % gb->num_rom_banks; } else if(gb->mbc == 3) gb->cart_ram_bank = val; @@ -996,30 +786,15 @@ void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) case 0x8: case 0x9: -#if PEANUT_FULL_GBC_SUPPORT - gb->vram[addr - gb->cgb.vramBankOffset] = val; -#else gb->vram[addr - VRAM_ADDR] = val; -#endif return; case 0xA: case 0xB: - if(gb->mbc == 3 && gb->cart_ram_bank >= 0x08) + if(gb->cart_ram && gb->enable_cart_ram) { - gb->cart_rtc[gb->cart_ram_bank - 0x08] = val; - } - /* 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; - gb->gb_cart_ram_write(gb, addr, val); - } + if(gb->mbc == 3 && gb->cart_ram_bank >= 0x08) + gb->cart_rtc[gb->cart_ram_bank - 0x08] = val; else if(gb->cart_mode_select && gb->cart_ram_bank < gb->num_ram_banks) { @@ -1037,11 +812,7 @@ void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) return; case 0xD: -#if PEANUT_FULL_GBC_SUPPORT - gb->wram[addr - gb->cgb.wramBankOffset] = val; -#else gb->wram[addr - WRAM_1_ADDR + WRAM_BANK_SIZE] = val; -#endif return; case 0xE: @@ -1051,11 +822,7 @@ void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) case 0xF: if(addr < OAM_ADDR) { -#if PEANUT_FULL_GBC_SUPPORT - gb->wram[(addr - 0x2000) - gb->cgb.wramBankOffset] = val; -#else gb->wram[addr - ECHO_ADDR] = val; -#endif return; } @@ -1071,7 +838,7 @@ void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) if(HRAM_ADDR <= addr && addr < INTR_EN_ADDR) { - gb->hram_io[addr - IO_ADDR] = val; + gb->hram[addr - HRAM_ADDR] = val; return; } @@ -1079,277 +846,161 @@ void __gb_write(struct gb_s *gb, uint_fast16_t addr, uint8_t val) { #if ENABLE_SOUND audio_write(addr, val); -#else - gb->hram_io[addr - IO_ADDR] = val; #endif return; } -#if PEANUT_FULL_GBC_SUPPORT - uint16_t fixPaletteTemp; -#endif + /* IO and Interrupts. */ - switch(PEANUT_GB_GET_LSB16(addr)) + switch(addr & 0xFF) { /* 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; + gb->gb_reg.P1 = val; /* Direction keys selected */ - if((gb->hram_io[IO_JOYP] & 0x10) == 0) - gb->hram_io[IO_JOYP] |= (gb->direct.joypad >> 4); + if((gb->gb_reg.P1 & 0b010000) == 0) + gb->gb_reg.P1 |= (gb->direct.joypad >> 4); /* Button keys selected */ else - gb->hram_io[IO_JOYP] |= (gb->direct.joypad & 0x0F); + gb->gb_reg.P1 |= (gb->direct.joypad & 0x0F); return; /* Serial */ case 0x01: - gb->hram_io[IO_SB] = val; + gb->gb_reg.SB = val; return; case 0x02: - gb->hram_io[IO_SC] = val; + gb->gb_reg.SC = val; return; /* Timer Registers */ case 0x04: - gb->hram_io[IO_DIV] = 0x00; + gb->gb_reg.DIV = 0x00; return; case 0x05: - gb->hram_io[IO_TIMA] = val; + gb->gb_reg.TIMA = val; return; case 0x06: - gb->hram_io[IO_TMA] = val; + gb->gb_reg.TMA = val; return; case 0x07: - gb->hram_io[IO_TAC] = val; + gb->gb_reg.TAC = val; return; /* Interrupt Flag Register */ case 0x0F: - gb->hram_io[IO_IF] = (val | 0xE0); + gb->gb_reg.IF = (val | 0b11100000); return; /* LCD Registers */ case 0x40: - { - uint8_t lcd_enabled; + gb->gb_reg.LCDC = val; - /* 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 = 1; - } - /* Check if LCD is being switched off. */ - else if (lcd_enabled && !(val & LCDC_ENABLE)) + /* LY fixed to 0 when LCD turned off. */ + if((gb->gb_reg.LCDC & LCDC_ENABLE) == 0) { - /* 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; - /* Reset LCD timer. */ + /* Do not turn off LCD outside of VBLANK. This may + * happen due to poor timing in this emulator. */ + if(gb->lcd_mode != LCD_VBLANK) + { + gb->gb_reg.LCDC |= LCDC_ENABLE; + return; + } + + gb->gb_reg.STAT = (gb->gb_reg.STAT & ~0x03) | LCD_VBLANK; + gb->gb_reg.LY = 0; gb->counter.lcd_count = 0; } + return; - } case 0x41: - gb->hram_io[IO_STAT] = (val & STAT_USER_BITS) | (gb->hram_io[IO_STAT] & STAT_MODE); + gb->gb_reg.STAT = (val & 0b01111000); return; case 0x42: - gb->hram_io[IO_SCY] = val; + gb->gb_reg.SCY = val; return; case 0x43: - gb->hram_io[IO_SCX] = val; + gb->gb_reg.SCX = val; return; /* LY (0xFF44) is read only. */ case 0x45: - gb->hram_io[IO_LYC] = val; + gb->gb_reg.LYC = val; return; /* DMA Register */ case 0x46: - { - uint16_t dma_addr; - uint16_t i; -#if PEANUT_FULL_GBC_SUPPORT - dma_addr = (uint_fast16_t)(val % 0xF1) << 8; - gb->hram_io[IO_DMA] = (val % 0xF1); -#else - dma_addr = (uint_fast16_t)val << 8; - gb->hram_io[IO_DMA] = val; -#endif - for(i = 0; i < OAM_SIZE; i++) - { - gb->oam[i] = __gb_read(gb, dma_addr + i); - } + gb->gb_reg.DMA = (val % 0xF1); + + for(uint8_t i = 0; i < OAM_SIZE; i++) + gb->oam[i] = __gb_read(gb, (gb->gb_reg.DMA << 8) + 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; + gb->gb_reg.BGP = val; + gb->display.bg_palette[0] = (gb->gb_reg.BGP & 0x03); + gb->display.bg_palette[1] = (gb->gb_reg.BGP >> 2) & 0x03; + gb->display.bg_palette[2] = (gb->gb_reg.BGP >> 4) & 0x03; + gb->display.bg_palette[3] = (gb->gb_reg.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; + gb->gb_reg.OBP0 = val; + gb->display.sp_palette[0] = (gb->gb_reg.OBP0 & 0x03); + gb->display.sp_palette[1] = (gb->gb_reg.OBP0 >> 2) & 0x03; + gb->display.sp_palette[2] = (gb->gb_reg.OBP0 >> 4) & 0x03; + gb->display.sp_palette[3] = (gb->gb_reg.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; + gb->gb_reg.OBP1 = val; + gb->display.sp_palette[4] = (gb->gb_reg.OBP1 & 0x03); + gb->display.sp_palette[5] = (gb->gb_reg.OBP1 >> 2) & 0x03; + gb->display.sp_palette[6] = (gb->gb_reg.OBP1 >> 4) & 0x03; + gb->display.sp_palette[7] = (gb->gb_reg.OBP1 >> 6) & 0x03; return; /* Window Position Registers */ case 0x4A: - gb->hram_io[IO_WY] = val; + gb->gb_reg.WY = val; return; case 0x4B: - gb->hram_io[IO_WX] = val; + gb->gb_reg.WX = val; return; -#if PEANUT_FULL_GBC_SUPPORT - /* Prepare Speed Switch*/ - case 0x4D: - gb->cgb.doubleSpeedPrep = val & 1; - return; - - /* CGB VRAM Bank*/ - case 0x4F: - gb->cgb.vramBank = val & 0x01; - if(gb->cgb.cgbMode) gb->cgb.vramBankOffset = VRAM_ADDR - (gb->cgb.vramBank << 13); - return; -#endif /* Turn off boot ROM */ case 0x50: - gb->hram_io[IO_BANK] = val; - return; -#if PEANUT_FULL_GBC_SUPPORT - /* DMA Register */ - case 0x51: - gb->cgb.dmaSource = (gb->cgb.dmaSource & 0xFF) + (val << 8); - return; - case 0x52: - gb->cgb.dmaSource = (gb->cgb.dmaSource & 0xFF00) + val; - return; - case 0x53: - gb->cgb.dmaDest = (gb->cgb.dmaDest & 0xFF) + (val << 8); - return; - case 0x54: - gb->cgb.dmaDest = (gb->cgb.dmaDest & 0xFF00) + val; - return; - - /* DMA Register*/ - case 0x55: - gb->cgb.dmaSize = (val & 0x7F) + 1; - gb->cgb.dmaMode = val >> 7; - //DMA GBC - if(gb->cgb.dmaActive) - { // Only transfer if dma is not active (=1) otherwise treat it as a termination - if(gb->cgb.cgbMode && (!gb->cgb.dmaMode)) - { - for (int i = 0; i < (gb->cgb.dmaSize << 4); i++) - { - __gb_write(gb, ((gb->cgb.dmaDest & 0x1FF0) | 0x8000) + i, __gb_read(gb, (gb->cgb.dmaSource & 0xFFF0) + i)); - } - gb->cgb.dmaSource += (gb->cgb.dmaSize << 4); - gb->cgb.dmaDest += (gb->cgb.dmaSize << 4); - gb->cgb.dmaSize = 0; - } - } - gb->cgb.dmaActive = gb->cgb.dmaMode ^ 1; // set active if it's an HBlank DMA + gb->gb_bios_enable = 0; return; - /* IR Register*/ - case 0x56: - gb->hram_io[0x56] = val; - return; - - /* CGB BG Palette Index*/ - case 0x68: - gb->cgb.BGPaletteID = val & 0x3F; - gb->cgb.BGPaletteInc = val >> 7; + /* Interrupt Enable Register */ + case 0xFF: + gb->gb_reg.IE = val; return; + } + } - /* CGB BG Palette*/ - case 0x69: - gb->cgb.BGPalette[(gb->cgb.BGPaletteID & 0x3F)] = val; - fixPaletteTemp = (gb->cgb.BGPalette[(gb->cgb.BGPaletteID & 0x3E) + 1] << 8) + (gb->cgb.BGPalette[(gb->cgb.BGPaletteID & 0x3E)]); - gb->cgb.fixPalette[((gb->cgb.BGPaletteID & 0x3E) >> 1)] = ((fixPaletteTemp & 0x7C00) >> 10) | (fixPaletteTemp & 0x03E0) | ((fixPaletteTemp & 0x001F) << 10); // swap Red and Blue - if(gb->cgb.BGPaletteInc) gb->cgb.BGPaletteID = (++gb->cgb.BGPaletteID) & 0x3F; - return; - - /* CGB OAM Palette Index*/ - case 0x6A: - gb->cgb.OAMPaletteID = val & 0x3F; - gb->cgb.OAMPaletteInc = val >> 7; - return; - - /* CGB OAM Palette*/ - case 0x6B: - gb->cgb.OAMPalette[(gb->cgb.OAMPaletteID & 0x3F)] = val; - fixPaletteTemp = (gb->cgb.OAMPalette[(gb->cgb.OAMPaletteID & 0x3E) + 1] << 8) + (gb->cgb.OAMPalette[(gb->cgb.OAMPaletteID & 0x3E)]); - gb->cgb.fixPalette[0x20 + ((gb->cgb.OAMPaletteID & 0x3E) >> 1)] = ((fixPaletteTemp & 0x7C00) >> 10) | (fixPaletteTemp & 0x03E0) | ((fixPaletteTemp & 0x001F) << 10); // swap Red and Blue - if(gb->cgb.OAMPaletteInc) gb->cgb.OAMPaletteID = (++gb->cgb.OAMPaletteID) & 0x3F; - return; - - /* CGB WRAM Bank*/ - case 0x70: - gb->cgb.wramBank = val; - gb->cgb.wramBankOffset = WRAM_1_ADDR - (1 << 12); - if(gb->cgb.cgbMode && (gb->cgb.wramBank & 7) > 0) gb->cgb.wramBankOffset = WRAM_1_ADDR - ((gb->cgb.wramBank & 7) << 12); - return; -#endif - - /* Interrupt Enable Register */ - case 0xFF: - gb->hram_io[IO_IE] = val; - return; - } - } - - /* Invalid writes are ignored. */ - return; -} + (gb->gb_error)(gb, GB_INVALID_WRITE, addr); +} 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 cbop = __gb_read(gb, gb->cpu_reg.pc++); uint8_t r = (cbop & 0x7); uint8_t b = (cbop >> 3) & 0x7; uint8_t d = (cbop >> 3) & 0x1; @@ -1373,31 +1024,31 @@ uint8_t __gb_execute_cb(struct gb_s *gb) switch(r) { case 0: - val = gb->cpu_reg.bc.bytes.b; + val = gb->cpu_reg.b; break; case 1: - val = gb->cpu_reg.bc.bytes.c; + val = gb->cpu_reg.c; break; case 2: - val = gb->cpu_reg.de.bytes.d; + val = gb->cpu_reg.d; break; case 3: - val = gb->cpu_reg.de.bytes.e; + val = gb->cpu_reg.e; break; case 4: - val = gb->cpu_reg.hl.bytes.h; + val = gb->cpu_reg.h; break; case 5: - val = gb->cpu_reg.hl.bytes.l; + val = gb->cpu_reg.l; break; case 6: - val = __gb_read(gb, gb->cpu_reg.hl.reg); + val = __gb_read(gb, gb->cpu_reg.hl); break; /* Only values 0-7 are possible here, so we make the final case @@ -1506,31 +1157,31 @@ uint8_t __gb_execute_cb(struct gb_s *gb) switch(r) { case 0: - gb->cpu_reg.bc.bytes.b = val; + gb->cpu_reg.b = val; break; case 1: - gb->cpu_reg.bc.bytes.c = val; + gb->cpu_reg.c = val; break; case 2: - gb->cpu_reg.de.bytes.d = val; + gb->cpu_reg.d = val; break; case 3: - gb->cpu_reg.de.bytes.e = val; + gb->cpu_reg.e = val; break; case 4: - gb->cpu_reg.hl.bytes.h = val; + gb->cpu_reg.h = val; break; case 5: - gb->cpu_reg.hl.bytes.l = val; + gb->cpu_reg.l = val; break; case 6: - __gb_write(gb, gb->cpu_reg.hl.reg, val); + __gb_write(gb, gb->cpu_reg.hl, val); break; case 7: @@ -1542,27 +1193,6 @@ uint8_t __gb_execute_cb(struct gb_s *gb) } #if ENABLE_LCD -struct sprite_data { - uint8_t sprite_number; - uint8_t x; -}; - -#if PEANUT_GB_HIGH_LCD_ACCURACY -static int compare_sprites(const void *in1, const void *in2) -{ - const struct sprite_data *sd1, *sd2; - int x_res; - - sd1 = (struct sprite_data *)in1; - sd2 = (struct sprite_data *)in2; - 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}; @@ -1574,22 +1204,19 @@ void __gb_draw_line(struct gb_s *gb) if(gb->direct.frame_skip && !gb->display.frame_skip_count) return; -#if PEANUT_FULL_GBC_SUPPORT - uint8_t pixelsPrio[160] = {0}; //do these pixels have priority over OAM? -#endif /* If interlaced mode is activated, check if we need to draw the current * line. */ if(gb->direct.interlace) { if((gb->display.interlace_count == 0 - && (gb->hram_io[IO_LY] & 1) == 0) + && (gb->gb_reg.LY & 1) == 0) || (gb->display.interlace_count == 1 - && (gb->hram_io[IO_LY] & 1) == 1)) + && (gb->gb_reg.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) + if(gb->gb_reg.LCDC & LCDC_WINDOW_ENABLE + && gb->gb_reg.LY >= gb->display.WY + && gb->gb_reg.WX <= 166) gb->display.window_clear++; return; @@ -1597,291 +1224,138 @@ void __gb_draw_line(struct gb_s *gb) } /* If background is enabled, draw it. */ -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode || gb->hram_io[IO_LCDC] & LCDC_BG_ENABLE) -#else - if(gb->hram_io[IO_LCDC] & LCDC_BG_ENABLE) -#endif + if(gb->gb_reg.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]; + const uint8_t bg_y = gb->gb_reg.LY + gb->gb_reg.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) ? + const uint16_t bg_map = + ((gb->gb_reg.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; + uint8_t disp_x = LCD_WIDTH - 1; /* The X coordinate to begin drawing the background at. */ - bg_x = disp_x + gb->hram_io[IO_SCX]; + uint8_t bg_x = disp_x + gb->gb_reg.SCX; /* Get tile index for current background tile. */ - idx = gb->vram[bg_map + (bg_x >> 3)]; -#if PEANUT_FULL_GBC_SUPPORT - uint8_t idxAtt = gb->vram[bg_map + (bg_x >> 3) + 0x2000]; -#endif + uint8_t idx = gb->vram[bg_map + (bg_x >> 3)]; /* Y coordinate of tile pixel to draw. */ - py = (bg_y & 0x07); + const uint8_t py = (bg_y & 0x07); /* X coordinate of tile pixel to draw. */ - px = 7 - (bg_x & 0x07); + uint8_t px = 7 - (bg_x & 0x07); + + uint16_t tile; /* Select addressing mode. */ - if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + if(gb->gb_reg.LCDC & LCDC_TILE_SELECT) tile = VRAM_TILES_1 + idx * 0x10; else tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode) - { - if(idxAtt & 0x08) tile += 0x2000; //VRAM bank 2 - if(idxAtt & 0x40) tile += 2 * (7 - py); - } - if(!(idxAtt & 0x40)) - { - tile += 2 * py; - } - - /* fetch first tile */ - if(gb->cgb.cgbMode && (idxAtt & 0x20)) - { //Horizantal Flip - t1 = gb->vram[tile] << px; - t2 = gb->vram[tile + 1] << px; - } - else - { - t1 = gb->vram[tile] >> px; - t2 = gb->vram[tile + 1] >> px; - } -#else tile += 2 * py; /* fetch first tile */ - t1 = gb->vram[tile] >> px; - t2 = gb->vram[tile + 1] >> px; -#endif + uint8_t t1 = gb->vram[tile] >> px; + uint8_t 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]; + bg_x = disp_x + gb->gb_reg.SCX; idx = gb->vram[bg_map + (bg_x >> 3)]; -#if PEANUT_FULL_GBC_SUPPORT - idxAtt = gb->vram[bg_map + (bg_x >> 3) + 0x2000]; -#endif - if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + + if(gb->gb_reg.LCDC & LCDC_TILE_SELECT) tile = VRAM_TILES_1 + idx * 0x10; else tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode) - { - if(idxAtt & 0x08) tile += 0x2000; //VRAM bank 2 - if(idxAtt & 0x40) tile += 2 * (7 - py); - } - if(!(idxAtt & 0x40)) - { - tile += 2 * py; - } -#else tile += 2 * py; -#endif t1 = gb->vram[tile]; t2 = gb->vram[tile + 1]; } /* copy background */ -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode && (idxAtt & 0x20)) - { //Horizantal Flip - c = (((t1 & 0x80) >> 1) | (t2 & 0x80)) >> 6; - pixels[disp_x] = ((idxAtt & 0x07) << 2) + c; - pixelsPrio[disp_x] = (idxAtt >> 7); - t1 = t1 << 1; - t2 = t2 << 1; - } - else - { - c = (t1 & 0x1) | ((t2 & 0x1) << 1); - if(gb->cgb.cgbMode) - { - pixels[disp_x] = ((idxAtt & 0x07) << 2) + c; - pixelsPrio[disp_x] = (idxAtt >> 7); - } - else - { - 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; - } -#else - c = (t1 & 0x1) | ((t2 & 0x1) << 1); + uint8_t 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; -#endif 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) + if(gb->gb_reg.LCDC & LCDC_WINDOW_ENABLE + && gb->gb_reg.LY >= gb->display.WY + && gb->gb_reg.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) ? + uint16_t win_line = (gb->gb_reg.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; + uint8_t disp_x = LCD_WIDTH - 1; + uint8_t win_x = disp_x - gb->gb_reg.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 PEANUT_FULL_GBC_SUPPORT - uint8_t idxAtt = gb->vram[win_line + (win_x >> 3) + 0x2000]; -#endif + uint8_t py = gb->display.window_clear & 0x07; + uint8_t px = 7 - (win_x & 0x07); + uint8_t idx = gb->vram[win_line + (win_x >> 3)]; - if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + uint16_t tile; + + if(gb->gb_reg.LCDC & LCDC_TILE_SELECT) tile = VRAM_TILES_1 + idx * 0x10; else tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode) - { - if(idxAtt & 0x08) tile += 0x2000; //VRAM bank 2 - if(idxAtt & 0x40) tile += 2 * (7 - py); - } - if(!(idxAtt & 0x40)) - { - tile += 2 * py; - } - - // fetch first tile - if(gb->cgb.cgbMode && (idxAtt & 0x20)) - { //Horizantal Flip - t1 = gb->vram[tile] << px; - t2 = gb->vram[tile + 1] << px; - } - else - { - t1 = gb->vram[tile] >> px; - t2 = gb->vram[tile + 1] >> px; - } -#else tile += 2 * py; // fetch first tile - t1 = gb->vram[tile] >> px; - t2 = gb->vram[tile + 1] >> px; -#endif + uint8_t t1 = gb->vram[tile] >> px; + uint8_t t2 = gb->vram[tile + 1] >> px; + // loop & copy window - end = (gb->hram_io[IO_WX] < 7 ? 0 : gb->hram_io[IO_WX] - 7) - 1; + uint8_t end = (gb->gb_reg.WX < 7 ? 0 : gb->gb_reg.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; + win_x = disp_x - gb->gb_reg.WX + 7; idx = gb->vram[win_line + (win_x >> 3)]; -#if PEANUT_FULL_GBC_SUPPORT - idxAtt = gb->vram[win_line + (win_x >> 3) + 0x2000]; -#endif - if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + if(gb->gb_reg.LCDC & LCDC_TILE_SELECT) tile = VRAM_TILES_1 + idx * 0x10; else tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode) - { - if(idxAtt & 0x08) tile += 0x2000; //VRAM bank 2 - if(idxAtt & 0x40) tile += 2 * (7 - py); - } - if(!(idxAtt & 0x40)) - { - tile += 2 * py; - } -#else tile += 2 * py; -#endif t1 = gb->vram[tile]; t2 = gb->vram[tile + 1]; } // copy window -#if PEANUT_FULL_GBC_SUPPORT - if(idxAtt & 0x20) - { //Horizantal Flip - c = (((t1 & 0x80) >> 1) | (t2 & 0x80)) >> 6; - pixels[disp_x] = ((idxAtt & 0x07) << 2) + c; - pixelsPrio[disp_x] = (idxAtt >> 7); - t1 = t1 << 1; - t2 = t2 << 1; - } - else - { - c = (t1 & 0x1) | ((t2 & 0x1) << 1); - if(gb->cgb.cgbMode) - { - pixels[disp_x] = ((idxAtt & 0x07) << 2) + c; - pixelsPrio[disp_x] = (idxAtt >> 7); - } - else - { - 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; - } -#else - c = (t1 & 0x1) | ((t2 & 0x1) << 1); + uint8_t 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; -#endif px++; } @@ -1889,111 +1363,50 @@ void __gb_draw_line(struct gb_s *gb) } // draw sprites - if(gb->hram_io[IO_LCDC] & LCDC_OBJ_ENABLE) + if(gb->gb_reg.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[NUM_SPRITES]; - - /* 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 < PEANUT_GB_ARRAYSIZE(sprites_to_render); - 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; - - - sprites_to_render[number_of_sprites].sprite_number = sprite_number; - sprites_to_render[number_of_sprites].x = OX; - number_of_sprites++; - } -#if PEANUT_FULL_GBC_SUPPORT - if(!gb->cgb.cgbMode) - { -#endif - /* If maximum number of sprites reached, prioritise X - * coordinate and object location in OAM. */ - qsort(&sprites_to_render[0], number_of_sprites, - sizeof(sprites_to_render[0]), compare_sprites); -#if PEANUT_FULL_GBC_SUPPORT - } -#endif - if(number_of_sprites > MAX_SPRITES_LINE) - number_of_sprites = MAX_SPRITES_LINE; -#endif + uint8_t count = 0; - /* 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--) + for(uint8_t s = NUM_SPRITES - 1; + s != 0xFF /* && count < MAX_SPRITES_LINE */ ; + s--) { - 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); + & (gb->gb_reg.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) + if(gb->gb_reg.LY + + (gb->gb_reg.LCDC & LCDC_OBJ_SIZE ? + 0 : 8) >= OY + || gb->gb_reg.LY + 16 < OY) continue; -#endif + + count++; /* Continue if sprite not visible. */ if(OX == 0 || OX >= 168) continue; // y flip - py = gb->hram_io[IO_LY] - OY + 16; + uint8_t py = gb->gb_reg.LY - OY + 16; if(OF & OBJ_FLIP_Y) - py = (gb->hram_io[IO_LCDC] & LCDC_OBJ_SIZE ? 15 : 7) - py; + py = (gb->gb_reg.LCDC & LCDC_OBJ_SIZE ? 15 : 7) - py; // fetch the tile -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode) - { - t1 = gb->vram[((OF & OBJ_BANK) << 10) + VRAM_TILES_1 + OT * 0x10 + 2 * py]; - t2 = gb->vram[((OF & OBJ_BANK) << 10) + VRAM_TILES_1 + OT * 0x10 + 2 * py + 1]; - } - else -#endif - { - t1 = gb->vram[VRAM_TILES_1 + OT * 0x10 + 2 * py]; - t2 = gb->vram[VRAM_TILES_1 + OT * 0x10 + 2 * py + 1]; - } + uint8_t t1 = gb->vram[VRAM_TILES_1 + OT * 0x10 + 2 * py]; + uint8_t t2 = gb->vram[VRAM_TILES_1 + OT * 0x10 + 2 * py + 1]; // handle x flip + uint8_t dir, start, end, shift; + if(OF & OBJ_FLIP_X) { dir = 1; @@ -2003,7 +1416,7 @@ void __gb_draw_line(struct gb_s *gb) } else { - dir = (uint8_t)-1; + dir = -1; start = MIN(OX, LCD_WIDTH) - 1; end = (OX < 8 ? 0 : OX - 8) - 1; shift = OX - (start + 1); @@ -2013,43 +1426,30 @@ void __gb_draw_line(struct gb_s *gb) 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) + for(uint8_t disp_x = start; disp_x != end; disp_x += dir) { uint8_t c = (t1 & 0x1) | ((t2 & 0x1) << 1); // check transparency / sprite overlap / background overlap -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode) - { - uint8_t isBackgroundDisabled = c && !(gb->hram_io[IO_LCDC] & LCDC_BG_ENABLE); - uint8_t isPixelPriorityNonConflicting = c && - !(pixelsPrio[disp_x] && (pixels[disp_x] & 0x3)) && - !((OF & OBJ_PRIORITY) && (pixels[disp_x] & 0x3)); +#if 0 - if(isBackgroundDisabled || isPixelPriorityNonConflicting) - { - /* Set pixel colour. */ - pixels[disp_x] = ((OF & OBJ_CGB_PALETTE) << 2) + c + 0x20; // add 0x20 to differentiate from BG - } - } - else + if(c + // && OX <= fx[disp_x] + && !((OF & OBJ_PRIORITY) + && ((pixels[disp_x] & 0x3) + && fx[disp_x] == 0xFE))) +#else + if(c && !(OF & OBJ_PRIORITY + && pixels[disp_x] & 0x3)) #endif - 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 + ? gb->display.sp_palette[c + 4] + : gb->display.sp_palette[c]; /* Set pixel palette (OBJ0 or OBJ1). */ pixels[disp_x] |= (OF & OBJ_PALETTE); -#endif -#if PEANUT_FULL_GBC_SUPPORT /* Deselect BG palette. */ pixels[disp_x] &= ~LCD_PALETTE_BG; -#endif } t1 = t1 >> 1; @@ -2058,7 +1458,7 @@ void __gb_draw_line(struct gb_s *gb) } } - gb->display.lcd_draw_line(gb, pixels, gb->hram_io[IO_LY]); + gb->display.lcd_draw_line(gb, pixels, gb->gb_reg.LY); } #endif @@ -2067,8 +1467,7 @@ void __gb_draw_line(struct gb_s *gb) */ void __gb_step_cpu(struct gb_s *gb) { - uint8_t opcode; - uint_fast16_t inst_cycles; + uint8_t opcode, inst_cycles; static const uint8_t op_cycles[0x100] = { /* *INDENT-OFF* */ @@ -2080,7 +1479,7 @@ void __gb_step_cpu(struct gb_s *gb) 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 */ + 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 */ @@ -2091,59 +1490,53 @@ void __gb_step_cpu(struct gb_s *gb) 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 occured by the - * time we reach here, becuase 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)) + if((gb->gb_ime || gb->gb_halt) && + (gb->gb_reg.IF & gb->gb_reg.IE & ANY_INTR)) { gb->gb_halt = 0; - if(!gb->gb_ime) - break; - - /* Disable interrupts */ - gb->gb_ime = 0; + if(gb->gb_ime) + { + /* Disable interrupts */ + gb->gb_ime = 0; - /* 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); + /* Push Program Counter */ + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); - /* 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; + /* Call interrupt handler if required. */ + if(gb->gb_reg.IF & gb->gb_reg.IE & VBLANK_INTR) + { + gb->cpu_reg.pc = VBLANK_INTR_ADDR; + gb->gb_reg.IF ^= VBLANK_INTR; + } + else if(gb->gb_reg.IF & gb->gb_reg.IE & LCDC_INTR) + { + gb->cpu_reg.pc = LCDC_INTR_ADDR; + gb->gb_reg.IF ^= LCDC_INTR; + } + else if(gb->gb_reg.IF & gb->gb_reg.IE & TIMER_INTR) + { + gb->cpu_reg.pc = TIMER_INTR_ADDR; + gb->gb_reg.IF ^= TIMER_INTR; + } + else if(gb->gb_reg.IF & gb->gb_reg.IE & SERIAL_INTR) + { + gb->cpu_reg.pc = SERIAL_INTR_ADDR; + gb->gb_reg.IF ^= SERIAL_INTR; + } + else if(gb->gb_reg.IF & gb->gb_reg.IE & CONTROL_INTR) + { + gb->cpu_reg.pc = CONTROL_INTR_ADDR; + gb->gb_reg.IF ^= CONTROL_INTR; + } } - - break; } /* Obtain opcode */ - opcode = __gb_read(gb, gb->cpu_reg.pc.reg++); + opcode = (gb->gb_halt ? 0x00 : __gb_read(gb, gb->cpu_reg.pc++)); inst_cycles = op_cycles[opcode]; /* Execute opcode */ @@ -2153,31 +1546,34 @@ void __gb_step_cpu(struct gb_s *gb) 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++); + gb->cpu_reg.c = __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.b = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x02: /* LD (BC), A */ - __gb_write(gb, gb->cpu_reg.bc.reg, gb->cpu_reg.a); + __gb_write(gb, gb->cpu_reg.bc, gb->cpu_reg.a); break; case 0x03: /* INC BC */ - gb->cpu_reg.bc.reg++; + gb->cpu_reg.bc++; break; case 0x04: /* INC B */ - gb->cpu_reg.bc.bytes.b++; - gb->cpu_reg.f_bits.z = (gb->cpu_reg.bc.bytes.b == 0x00); + gb->cpu_reg.b++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.b == 0x00); gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.bc.bytes.b & 0x0F) == 0x00); + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.b & 0x0F) == 0x00); break; case 0x05: /* DEC B */ - PGB_INSTR_DEC_R8(gb->cpu_reg.bc.bytes.b); + gb->cpu_reg.b--; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.b == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.b & 0x0F) == 0x0F); break; case 0x06: /* LD B, imm */ - gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.b = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x07: /* RLCA */ @@ -2190,48 +1586,48 @@ void __gb_step_cpu(struct gb_s *gb) 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); + uint16_t temp = __gb_read(gb, gb->cpu_reg.pc++); + temp |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + __gb_write(gb, temp++, gb->cpu_reg.sp & 0xFF); + __gb_write(gb, temp, gb->cpu_reg.sp >> 8); break; } case 0x09: /* ADD HL, BC */ { - uint_fast32_t temp = gb->cpu_reg.hl.reg + gb->cpu_reg.bc.reg; + uint_fast32_t temp = gb->cpu_reg.hl + gb->cpu_reg.bc; gb->cpu_reg.f_bits.n = 0; gb->cpu_reg.f_bits.h = - (temp ^ gb->cpu_reg.hl.reg ^ gb->cpu_reg.bc.reg) & 0x1000 ? 1 : 0; + (temp ^ gb->cpu_reg.hl ^ gb->cpu_reg.bc) & 0x1000 ? 1 : 0; gb->cpu_reg.f_bits.c = (temp & 0xFFFF0000) ? 1 : 0; - gb->cpu_reg.hl.reg = (temp & 0x0000FFFF); + gb->cpu_reg.hl = (temp & 0x0000FFFF); break; } case 0x0A: /* LD A, (BC) */ - gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.bc.reg); + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.bc); break; case 0x0B: /* DEC BC */ - gb->cpu_reg.bc.reg--; + gb->cpu_reg.bc--; break; case 0x0C: /* INC C */ - gb->cpu_reg.bc.bytes.c++; - gb->cpu_reg.f_bits.z = (gb->cpu_reg.bc.bytes.c == 0x00); + gb->cpu_reg.c++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.c == 0x00); gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.bc.bytes.c & 0x0F) == 0x00); + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.c & 0x0F) == 0x00); break; case 0x0D: /* DEC C */ - PGB_INSTR_DEC_R8(gb->cpu_reg.bc.bytes.c); + gb->cpu_reg.c--; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.c == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.c & 0x0F) == 0x0F); break; case 0x0E: /* LD C, imm */ - gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.c = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x0F: /* RRCA */ @@ -2244,41 +1640,37 @@ void __gb_step_cpu(struct gb_s *gb) case 0x10: /* STOP */ //gb->gb_halt = 1; -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode & gb->cgb.doubleSpeedPrep) - { - gb->cgb.doubleSpeedPrep = 0; - gb->cgb.doubleSpeed ^= 1; - } -#endif 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++); + gb->cpu_reg.e = __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.d = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x12: /* LD (DE), A */ - __gb_write(gb, gb->cpu_reg.de.reg, gb->cpu_reg.a); + __gb_write(gb, gb->cpu_reg.de, gb->cpu_reg.a); break; case 0x13: /* INC DE */ - gb->cpu_reg.de.reg++; + gb->cpu_reg.de++; break; case 0x14: /* INC D */ - gb->cpu_reg.de.bytes.d++; - gb->cpu_reg.f_bits.z = (gb->cpu_reg.de.bytes.d == 0x00); + gb->cpu_reg.d++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.d == 0x00); gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.de.bytes.d & 0x0F) == 0x00); + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.d & 0x0F) == 0x00); break; case 0x15: /* DEC D */ - PGB_INSTR_DEC_R8(gb->cpu_reg.de.bytes.d); + gb->cpu_reg.d--; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.d == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.d & 0x0F) == 0x0F); break; case 0x16: /* LD D, imm */ - gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.d = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x17: /* RLA */ @@ -2294,43 +1686,46 @@ void __gb_step_cpu(struct gb_s *gb) case 0x18: /* JR imm */ { - int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++); - gb->cpu_reg.pc.reg += temp; + int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.pc += temp; break; } case 0x19: /* ADD HL, DE */ { - uint_fast32_t temp = gb->cpu_reg.hl.reg + gb->cpu_reg.de.reg; + uint_fast32_t temp = gb->cpu_reg.hl + gb->cpu_reg.de; gb->cpu_reg.f_bits.n = 0; gb->cpu_reg.f_bits.h = - (temp ^ gb->cpu_reg.hl.reg ^ gb->cpu_reg.de.reg) & 0x1000 ? 1 : 0; + (temp ^ gb->cpu_reg.hl ^ gb->cpu_reg.de) & 0x1000 ? 1 : 0; gb->cpu_reg.f_bits.c = (temp & 0xFFFF0000) ? 1 : 0; - gb->cpu_reg.hl.reg = (temp & 0x0000FFFF); + gb->cpu_reg.hl = (temp & 0x0000FFFF); break; } case 0x1A: /* LD A, (DE) */ - gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.de.reg); + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.de); break; case 0x1B: /* DEC DE */ - gb->cpu_reg.de.reg--; + gb->cpu_reg.de--; break; case 0x1C: /* INC E */ - gb->cpu_reg.de.bytes.e++; - gb->cpu_reg.f_bits.z = (gb->cpu_reg.de.bytes.e == 0x00); + gb->cpu_reg.e++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.e == 0x00); gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.de.bytes.e & 0x0F) == 0x00); + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.e & 0x0F) == 0x00); break; case 0x1D: /* DEC E */ - PGB_INSTR_DEC_R8(gb->cpu_reg.de.bytes.e); + gb->cpu_reg.e--; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.e == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.e & 0x0F) == 0x0F); break; case 0x1E: /* LD E, imm */ - gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.e = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x1F: /* RRA */ @@ -2344,51 +1739,53 @@ void __gb_step_cpu(struct gb_s *gb) break; } - case 0x20: /* JR NZ, imm */ + case 0x20: /* JP NZ, imm */ if(!gb->cpu_reg.f_bits.z) { - int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++); - gb->cpu_reg.pc.reg += temp; + int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.pc += temp; inst_cycles += 4; } else - gb->cpu_reg.pc.reg++; + gb->cpu_reg.pc++; 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++); + gb->cpu_reg.l = __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.h = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x22: /* LDI (HL), A */ - __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.a); - gb->cpu_reg.hl.reg++; + __gb_write(gb, gb->cpu_reg.hl, gb->cpu_reg.a); + gb->cpu_reg.hl++; break; case 0x23: /* INC HL */ - gb->cpu_reg.hl.reg++; + gb->cpu_reg.hl++; break; case 0x24: /* INC H */ - gb->cpu_reg.hl.bytes.h++; - gb->cpu_reg.f_bits.z = (gb->cpu_reg.hl.bytes.h == 0x00); + gb->cpu_reg.h++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.h == 0x00); gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.hl.bytes.h & 0x0F) == 0x00); + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.h & 0x0F) == 0x00); break; case 0x25: /* DEC H */ - PGB_INSTR_DEC_R8(gb->cpu_reg.hl.bytes.h); + gb->cpu_reg.h--; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.h == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.h & 0x0F) == 0x0F); break; case 0x26: /* LD H, imm */ - gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.h = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x27: /* DAA */ { - /* The following is from SameBoy. MIT License. */ - int16_t a = gb->cpu_reg.a; + uint16_t a = gb->cpu_reg.a; if(gb->cpu_reg.f_bits.n) { @@ -2417,48 +1814,52 @@ void __gb_step_cpu(struct gb_s *gb) break; } - case 0x28: /* JR Z, imm */ + case 0x28: /* JP Z, imm */ if(gb->cpu_reg.f_bits.z) { - int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++); - gb->cpu_reg.pc.reg += temp; + int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.pc += temp; inst_cycles += 4; } else - gb->cpu_reg.pc.reg++; + gb->cpu_reg.pc++; break; case 0x29: /* ADD HL, HL */ { - gb->cpu_reg.f_bits.c = (gb->cpu_reg.hl.reg & 0x8000) > 0; - gb->cpu_reg.hl.reg <<= 1; + uint_fast32_t temp = gb->cpu_reg.hl + gb->cpu_reg.hl; gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = (gb->cpu_reg.hl.reg & 0x1000) > 0; + gb->cpu_reg.f_bits.h = (temp & 0x1000) ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFFFF0000) ? 1 : 0; + gb->cpu_reg.hl = (temp & 0x0000FFFF); break; } case 0x2A: /* LD A, (HL+) */ - gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl.reg++); + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl++); break; case 0x2B: /* DEC HL */ - gb->cpu_reg.hl.reg--; + gb->cpu_reg.hl--; break; case 0x2C: /* INC L */ - gb->cpu_reg.hl.bytes.l++; - gb->cpu_reg.f_bits.z = (gb->cpu_reg.hl.bytes.l == 0x00); + gb->cpu_reg.l++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.l == 0x00); gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.hl.bytes.l & 0x0F) == 0x00); + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.l & 0x0F) == 0x00); break; case 0x2D: /* DEC L */ - PGB_INSTR_DEC_R8(gb->cpu_reg.hl.bytes.l); + gb->cpu_reg.l--; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.l == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.l & 0x0F) == 0x0F); break; case 0x2E: /* LD L, imm */ - gb->cpu_reg.hl.bytes.l = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.l = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x2F: /* CPL */ @@ -2467,54 +1868,54 @@ void __gb_step_cpu(struct gb_s *gb) gb->cpu_reg.f_bits.h = 1; break; - case 0x30: /* JR NC, imm */ + case 0x30: /* JP NC, imm */ if(!gb->cpu_reg.f_bits.c) { - int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++); - gb->cpu_reg.pc.reg += temp; + int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.pc += temp; inst_cycles += 4; } else - gb->cpu_reg.pc.reg++; + gb->cpu_reg.pc++; 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++); + gb->cpu_reg.sp = __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.sp |= __gb_read(gb, gb->cpu_reg.pc++) << 8; break; case 0x32: /* LD (HL), A */ - __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.a); - gb->cpu_reg.hl.reg--; + __gb_write(gb, gb->cpu_reg.hl, gb->cpu_reg.a); + gb->cpu_reg.hl--; break; case 0x33: /* INC SP */ - gb->cpu_reg.sp.reg++; + gb->cpu_reg.sp++; break; case 0x34: /* INC (HL) */ { - uint8_t temp = __gb_read(gb, gb->cpu_reg.hl.reg) + 1; + uint8_t temp = __gb_read(gb, gb->cpu_reg.hl) + 1; gb->cpu_reg.f_bits.z = (temp == 0x00); gb->cpu_reg.f_bits.n = 0; gb->cpu_reg.f_bits.h = ((temp & 0x0F) == 0x00); - __gb_write(gb, gb->cpu_reg.hl.reg, temp); + __gb_write(gb, gb->cpu_reg.hl, temp); break; } case 0x35: /* DEC (HL) */ { - uint8_t temp = __gb_read(gb, gb->cpu_reg.hl.reg) - 1; + uint8_t temp = __gb_read(gb, gb->cpu_reg.hl) - 1; gb->cpu_reg.f_bits.z = (temp == 0x00); gb->cpu_reg.f_bits.n = 1; gb->cpu_reg.f_bits.h = ((temp & 0x0F) == 0x0F); - __gb_write(gb, gb->cpu_reg.hl.reg, temp); + __gb_write(gb, gb->cpu_reg.hl, temp); break; } case 0x36: /* LD (HL), imm */ - __gb_write(gb, gb->cpu_reg.hl.reg, __gb_read(gb, gb->cpu_reg.pc.reg++)); + __gb_write(gb, gb->cpu_reg.hl, __gb_read(gb, gb->cpu_reg.pc++)); break; case 0x37: /* SCF */ @@ -2523,35 +1924,35 @@ void __gb_step_cpu(struct gb_s *gb) gb->cpu_reg.f_bits.c = 1; break; - case 0x38: /* JR C, imm */ + case 0x38: /* JP C, imm */ if(gb->cpu_reg.f_bits.c) { - int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++); - gb->cpu_reg.pc.reg += temp; + int8_t temp = (int8_t) __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.pc += temp; inst_cycles += 4; } else - gb->cpu_reg.pc.reg++; + gb->cpu_reg.pc++; break; case 0x39: /* ADD HL, SP */ { - uint_fast32_t temp = gb->cpu_reg.hl.reg + gb->cpu_reg.sp.reg; + uint_fast32_t temp = gb->cpu_reg.hl + gb->cpu_reg.sp; gb->cpu_reg.f_bits.n = 0; gb->cpu_reg.f_bits.h = - ((gb->cpu_reg.hl.reg & 0xFFF) + (gb->cpu_reg.sp.reg & 0xFFF)) & 0x1000 ? 1 : 0; + ((gb->cpu_reg.hl & 0xFFF) + (gb->cpu_reg.sp & 0xFFF)) & 0x1000 ? 1 : 0; gb->cpu_reg.f_bits.c = temp & 0x10000 ? 1 : 0; - gb->cpu_reg.hl.reg = (uint16_t)temp; + gb->cpu_reg.hl = (uint16_t)temp; break; } case 0x3A: /* LD A, (HL) */ - gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl.reg--); + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl--); break; case 0x3B: /* DEC SP */ - gb->cpu_reg.sp.reg--; + gb->cpu_reg.sp--; break; case 0x3C: /* INC A */ @@ -2569,7 +1970,7 @@ void __gb_step_cpu(struct gb_s *gb) break; case 0x3E: /* LD A, imm */ - gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.pc++); break; case 0x3F: /* CCF */ @@ -2582,414 +1983,530 @@ void __gb_step_cpu(struct gb_s *gb) break; case 0x41: /* LD B, C */ - gb->cpu_reg.bc.bytes.b = gb->cpu_reg.bc.bytes.c; + gb->cpu_reg.b = gb->cpu_reg.c; break; case 0x42: /* LD B, D */ - gb->cpu_reg.bc.bytes.b = gb->cpu_reg.de.bytes.d; + gb->cpu_reg.b = gb->cpu_reg.d; break; case 0x43: /* LD B, E */ - gb->cpu_reg.bc.bytes.b = gb->cpu_reg.de.bytes.e; + gb->cpu_reg.b = gb->cpu_reg.e; break; case 0x44: /* LD B, H */ - gb->cpu_reg.bc.bytes.b = gb->cpu_reg.hl.bytes.h; + gb->cpu_reg.b = gb->cpu_reg.h; break; case 0x45: /* LD B, L */ - gb->cpu_reg.bc.bytes.b = gb->cpu_reg.hl.bytes.l; + gb->cpu_reg.b = gb->cpu_reg.l; break; case 0x46: /* LD B, (HL) */ - gb->cpu_reg.bc.bytes.b = __gb_read(gb, gb->cpu_reg.hl.reg); + gb->cpu_reg.b = __gb_read(gb, gb->cpu_reg.hl); break; case 0x47: /* LD B, A */ - gb->cpu_reg.bc.bytes.b = gb->cpu_reg.a; + gb->cpu_reg.b = gb->cpu_reg.a; break; case 0x48: /* LD C, B */ - gb->cpu_reg.bc.bytes.c = gb->cpu_reg.bc.bytes.b; + gb->cpu_reg.c = gb->cpu_reg.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; + gb->cpu_reg.c = gb->cpu_reg.d; break; case 0x4B: /* LD C, E */ - gb->cpu_reg.bc.bytes.c = gb->cpu_reg.de.bytes.e; + gb->cpu_reg.c = gb->cpu_reg.e; break; case 0x4C: /* LD C, H */ - gb->cpu_reg.bc.bytes.c = gb->cpu_reg.hl.bytes.h; + gb->cpu_reg.c = gb->cpu_reg.h; break; case 0x4D: /* LD C, L */ - gb->cpu_reg.bc.bytes.c = gb->cpu_reg.hl.bytes.l; + gb->cpu_reg.c = gb->cpu_reg.l; break; case 0x4E: /* LD C, (HL) */ - gb->cpu_reg.bc.bytes.c = __gb_read(gb, gb->cpu_reg.hl.reg); + gb->cpu_reg.c = __gb_read(gb, gb->cpu_reg.hl); break; case 0x4F: /* LD C, A */ - gb->cpu_reg.bc.bytes.c = gb->cpu_reg.a; + gb->cpu_reg.c = gb->cpu_reg.a; break; case 0x50: /* LD D, B */ - gb->cpu_reg.de.bytes.d = gb->cpu_reg.bc.bytes.b; + gb->cpu_reg.d = gb->cpu_reg.b; break; case 0x51: /* LD D, C */ - gb->cpu_reg.de.bytes.d = gb->cpu_reg.bc.bytes.c; + gb->cpu_reg.d = gb->cpu_reg.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; + gb->cpu_reg.d = gb->cpu_reg.e; break; case 0x54: /* LD D, H */ - gb->cpu_reg.de.bytes.d = gb->cpu_reg.hl.bytes.h; + gb->cpu_reg.d = gb->cpu_reg.h; break; case 0x55: /* LD D, L */ - gb->cpu_reg.de.bytes.d = gb->cpu_reg.hl.bytes.l; + gb->cpu_reg.d = gb->cpu_reg.l; break; case 0x56: /* LD D, (HL) */ - gb->cpu_reg.de.bytes.d = __gb_read(gb, gb->cpu_reg.hl.reg); + gb->cpu_reg.d = __gb_read(gb, gb->cpu_reg.hl); break; case 0x57: /* LD D, A */ - gb->cpu_reg.de.bytes.d = gb->cpu_reg.a; + gb->cpu_reg.d = gb->cpu_reg.a; break; case 0x58: /* LD E, B */ - gb->cpu_reg.de.bytes.e = gb->cpu_reg.bc.bytes.b; + gb->cpu_reg.e = gb->cpu_reg.b; break; case 0x59: /* LD E, C */ - gb->cpu_reg.de.bytes.e = gb->cpu_reg.bc.bytes.c; + gb->cpu_reg.e = gb->cpu_reg.c; break; case 0x5A: /* LD E, D */ - gb->cpu_reg.de.bytes.e = gb->cpu_reg.de.bytes.d; + gb->cpu_reg.e = gb->cpu_reg.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; + gb->cpu_reg.e = gb->cpu_reg.h; break; case 0x5D: /* LD E, L */ - gb->cpu_reg.de.bytes.e = gb->cpu_reg.hl.bytes.l; + gb->cpu_reg.e = gb->cpu_reg.l; break; case 0x5E: /* LD E, (HL) */ - gb->cpu_reg.de.bytes.e = __gb_read(gb, gb->cpu_reg.hl.reg); + gb->cpu_reg.e = __gb_read(gb, gb->cpu_reg.hl); break; case 0x5F: /* LD E, A */ - gb->cpu_reg.de.bytes.e = gb->cpu_reg.a; + gb->cpu_reg.e = gb->cpu_reg.a; break; case 0x60: /* LD H, B */ - gb->cpu_reg.hl.bytes.h = gb->cpu_reg.bc.bytes.b; + gb->cpu_reg.h = gb->cpu_reg.b; break; case 0x61: /* LD H, C */ - gb->cpu_reg.hl.bytes.h = gb->cpu_reg.bc.bytes.c; + gb->cpu_reg.h = gb->cpu_reg.c; break; case 0x62: /* LD H, D */ - gb->cpu_reg.hl.bytes.h = gb->cpu_reg.de.bytes.d; + gb->cpu_reg.h = gb->cpu_reg.d; break; case 0x63: /* LD H, E */ - gb->cpu_reg.hl.bytes.h = gb->cpu_reg.de.bytes.e; + gb->cpu_reg.h = gb->cpu_reg.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; + gb->cpu_reg.h = gb->cpu_reg.l; break; case 0x66: /* LD H, (HL) */ - gb->cpu_reg.hl.bytes.h = __gb_read(gb, gb->cpu_reg.hl.reg); + gb->cpu_reg.h = __gb_read(gb, gb->cpu_reg.hl); break; case 0x67: /* LD H, A */ - gb->cpu_reg.hl.bytes.h = gb->cpu_reg.a; + gb->cpu_reg.h = gb->cpu_reg.a; break; case 0x68: /* LD L, B */ - gb->cpu_reg.hl.bytes.l = gb->cpu_reg.bc.bytes.b; + gb->cpu_reg.l = gb->cpu_reg.b; break; case 0x69: /* LD L, C */ - gb->cpu_reg.hl.bytes.l = gb->cpu_reg.bc.bytes.c; + gb->cpu_reg.l = gb->cpu_reg.c; break; case 0x6A: /* LD L, D */ - gb->cpu_reg.hl.bytes.l = gb->cpu_reg.de.bytes.d; + gb->cpu_reg.l = gb->cpu_reg.d; break; case 0x6B: /* LD L, E */ - gb->cpu_reg.hl.bytes.l = gb->cpu_reg.de.bytes.e; + gb->cpu_reg.l = gb->cpu_reg.e; break; case 0x6C: /* LD L, H */ - gb->cpu_reg.hl.bytes.l = gb->cpu_reg.hl.bytes.h; + gb->cpu_reg.l = gb->cpu_reg.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); + gb->cpu_reg.l = __gb_read(gb, gb->cpu_reg.hl); break; case 0x6F: /* LD L, A */ - gb->cpu_reg.hl.bytes.l = gb->cpu_reg.a; + gb->cpu_reg.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); + __gb_write(gb, gb->cpu_reg.hl, gb->cpu_reg.b); break; case 0x71: /* LD (HL), C */ - __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.bc.bytes.c); + __gb_write(gb, gb->cpu_reg.hl, gb->cpu_reg.c); break; case 0x72: /* LD (HL), D */ - __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.de.bytes.d); + __gb_write(gb, gb->cpu_reg.hl, gb->cpu_reg.d); break; case 0x73: /* LD (HL), E */ - __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.de.bytes.e); + __gb_write(gb, gb->cpu_reg.hl, gb->cpu_reg.e); break; case 0x74: /* LD (HL), H */ - __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.hl.bytes.h); + __gb_write(gb, gb->cpu_reg.hl, gb->cpu_reg.h); break; case 0x75: /* LD (HL), L */ - __gb_write(gb, gb->cpu_reg.hl.reg, gb->cpu_reg.hl.bytes.l); + __gb_write(gb, gb->cpu_reg.hl, gb->cpu_reg.l); break; case 0x76: /* HALT */ - { - int_fast16_t halt_cycles = INT_FAST16_MAX; - /* TODO: Emulate HALT bug? */ gb->gb_halt = 1; - - if (gb->hram_io[IO_IE] == 0) - { - /* Return program counter where this halt forever state started. */ - /* This may be intentional, but this is required to stop an infinite - * loop. */ - (gb->gb_error)(gb, GB_HALT_FOREVER, gb->cpu_reg.pc.reg - 1); - PGB_UNREACHABLE(); - } - - 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_MODE_2_CYCLES - - gb->counter.lcd_count; - } - else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_OAM) - { - lcd_cycles = LCD_MODE_3_CYCLES - - gb->counter.lcd_count; - } - else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_TRANSFER) - { - lcd_cycles = LCD_MODE_0_CYCLES - - 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); + __gb_write(gb, gb->cpu_reg.hl, gb->cpu_reg.a); break; case 0x78: /* LD A, B */ - gb->cpu_reg.a = gb->cpu_reg.bc.bytes.b; + gb->cpu_reg.a = gb->cpu_reg.b; break; case 0x79: /* LD A, C */ - gb->cpu_reg.a = gb->cpu_reg.bc.bytes.c; + gb->cpu_reg.a = gb->cpu_reg.c; break; case 0x7A: /* LD A, D */ - gb->cpu_reg.a = gb->cpu_reg.de.bytes.d; + gb->cpu_reg.a = gb->cpu_reg.d; break; case 0x7B: /* LD A, E */ - gb->cpu_reg.a = gb->cpu_reg.de.bytes.e; + gb->cpu_reg.a = gb->cpu_reg.e; break; case 0x7C: /* LD A, H */ - gb->cpu_reg.a = gb->cpu_reg.hl.bytes.h; + gb->cpu_reg.a = gb->cpu_reg.h; break; case 0x7D: /* LD A, L */ - gb->cpu_reg.a = gb->cpu_reg.hl.bytes.l; + gb->cpu_reg.a = gb->cpu_reg.l; break; case 0x7E: /* LD A, (HL) */ - gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl.reg); + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.hl); break; case 0x7F: /* LD A, A */ break; case 0x80: /* ADD A, B */ - PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.b, 0); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.b; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.b ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x81: /* ADD A, C */ - PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.c, 0); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.c ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x82: /* ADD A, D */ - PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.d, 0); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.d; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.d ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x83: /* ADD A, E */ - PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.e, 0); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.e; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.e ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x84: /* ADD A, H */ - PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.h, 0); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.h; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.h ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x85: /* ADD A, L */ - PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.l, 0); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.l; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.l ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x86: /* ADD A, (HL) */ - PGB_INSTR_ADC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), 0); + { + uint8_t hl = __gb_read(gb, gb->cpu_reg.hl); + uint16_t temp = gb->cpu_reg.a + hl; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ hl ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x87: /* ADD A, A */ - PGB_INSTR_ADC_R8(gb->cpu_reg.a, 0); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.a; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = temp & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x88: /* ADC A, B */ - PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.b, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.b + gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.b ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x89: /* ADC A, C */ - PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.c, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.c + gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.c ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x8A: /* ADC A, D */ - PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.d, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.d + gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.d ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x8B: /* ADC A, E */ - PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.e, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.e + gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.e ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x8C: /* ADC A, H */ - PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.h, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.h + gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.h ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x8D: /* ADC A, L */ - PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.l, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.l + gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.l ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x8E: /* ADC A, (HL) */ - PGB_INSTR_ADC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), gb->cpu_reg.f_bits.c); + { + uint8_t val = __gb_read(gb, gb->cpu_reg.hl); + uint16_t temp = gb->cpu_reg.a + val + gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ val ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x8F: /* ADC A, A */ - PGB_INSTR_ADC_R8(gb->cpu_reg.a, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a + gb->cpu_reg.a + gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 0; + /* TODO: Optimisation here? */ + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.a ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x90: /* SUB B */ - PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.b, 0); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.b; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.b ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x91: /* SUB C */ - PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.c, 0); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.c ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x92: /* SUB D */ - PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.d, 0); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.d; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.d ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x93: /* SUB E */ - PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.e, 0); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.e; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.e ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x94: /* SUB H */ - PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.h, 0); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.h; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.h ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x95: /* SUB L */ - PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.l, 0); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.l; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.l ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x96: /* SUB (HL) */ - PGB_INSTR_SBC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), 0); + { + uint8_t val = __gb_read(gb, gb->cpu_reg.hl); + uint16_t temp = gb->cpu_reg.a - val; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ val ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x97: /* SUB A */ gb->cpu_reg.a = 0; @@ -3000,163 +2517,365 @@ void __gb_step_cpu(struct gb_s *gb) break; case 0x98: /* SBC A, B */ - PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.b, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.b - gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.b ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x99: /* SBC A, C */ - PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.c, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.c - gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.c ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x9A: /* SBC A, D */ - PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.d, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.d - gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.d ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x9B: /* SBC A, E */ - PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.e, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.e - gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.e ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x9C: /* SBC A, H */ - PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.h, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.h - gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.h ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x9D: /* SBC A, L */ - PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.l, gb->cpu_reg.f_bits.c); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.l - gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.l ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x9E: /* SBC A, (HL) */ - PGB_INSTR_SBC_R8(__gb_read(gb, gb->cpu_reg.hl.reg), gb->cpu_reg.f_bits.c); + { + uint8_t val = __gb_read(gb, gb->cpu_reg.hl); + uint16_t temp = gb->cpu_reg.a - val - gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ val ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp & 0xFF); break; + } case 0x9F: /* SBC A, A */ gb->cpu_reg.a = gb->cpu_reg.f_bits.c ? 0xFF : 0x00; - gb->cpu_reg.f_bits.z = !gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = gb->cpu_reg.f_bits.c ? 0x00 : 0x01; gb->cpu_reg.f_bits.n = 1; gb->cpu_reg.f_bits.h = gb->cpu_reg.f_bits.c; break; case 0xA0: /* AND B */ - PGB_INSTR_AND_R8(gb->cpu_reg.bc.bytes.b); + gb->cpu_reg.a = gb->cpu_reg.a & gb->cpu_reg.b; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 1; + gb->cpu_reg.f_bits.c = 0; break; case 0xA1: /* AND C */ - PGB_INSTR_AND_R8(gb->cpu_reg.bc.bytes.c); + gb->cpu_reg.a = gb->cpu_reg.a & gb->cpu_reg.c; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 1; + gb->cpu_reg.f_bits.c = 0; break; case 0xA2: /* AND D */ - PGB_INSTR_AND_R8(gb->cpu_reg.de.bytes.d); + gb->cpu_reg.a = gb->cpu_reg.a & gb->cpu_reg.d; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 1; + gb->cpu_reg.f_bits.c = 0; break; case 0xA3: /* AND E */ - PGB_INSTR_AND_R8(gb->cpu_reg.de.bytes.e); + gb->cpu_reg.a = gb->cpu_reg.a & gb->cpu_reg.e; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 1; + gb->cpu_reg.f_bits.c = 0; break; case 0xA4: /* AND H */ - PGB_INSTR_AND_R8(gb->cpu_reg.hl.bytes.h); + gb->cpu_reg.a = gb->cpu_reg.a & gb->cpu_reg.h; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 1; + gb->cpu_reg.f_bits.c = 0; break; case 0xA5: /* AND L */ - PGB_INSTR_AND_R8(gb->cpu_reg.hl.bytes.l); + gb->cpu_reg.a = gb->cpu_reg.a & gb->cpu_reg.l; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 1; + gb->cpu_reg.f_bits.c = 0; break; - case 0xA6: /* AND (HL) */ - PGB_INSTR_AND_R8(__gb_read(gb, gb->cpu_reg.hl.reg)); + case 0xA6: /* AND B */ + gb->cpu_reg.a = gb->cpu_reg.a & __gb_read(gb, gb->cpu_reg.hl); + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 1; + gb->cpu_reg.f_bits.c = 0; break; case 0xA7: /* AND A */ - PGB_INSTR_AND_R8(gb->cpu_reg.a); + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 1; + gb->cpu_reg.f_bits.c = 0; break; case 0xA8: /* XOR B */ - PGB_INSTR_XOR_R8(gb->cpu_reg.bc.bytes.b); + gb->cpu_reg.a = gb->cpu_reg.a ^ gb->cpu_reg.b; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xA9: /* XOR C */ - PGB_INSTR_XOR_R8(gb->cpu_reg.bc.bytes.c); + gb->cpu_reg.a = gb->cpu_reg.a ^ gb->cpu_reg.c; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xAA: /* XOR D */ - PGB_INSTR_XOR_R8(gb->cpu_reg.de.bytes.d); + gb->cpu_reg.a = gb->cpu_reg.a ^ gb->cpu_reg.d; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xAB: /* XOR E */ - PGB_INSTR_XOR_R8(gb->cpu_reg.de.bytes.e); + gb->cpu_reg.a = gb->cpu_reg.a ^ gb->cpu_reg.e; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xAC: /* XOR H */ - PGB_INSTR_XOR_R8(gb->cpu_reg.hl.bytes.h); + gb->cpu_reg.a = gb->cpu_reg.a ^ gb->cpu_reg.h; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xAD: /* XOR L */ - PGB_INSTR_XOR_R8(gb->cpu_reg.hl.bytes.l); + gb->cpu_reg.a = gb->cpu_reg.a ^ gb->cpu_reg.l; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xAE: /* XOR (HL) */ - PGB_INSTR_XOR_R8(__gb_read(gb, gb->cpu_reg.hl.reg)); + gb->cpu_reg.a = gb->cpu_reg.a ^ __gb_read(gb, gb->cpu_reg.hl); + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xAF: /* XOR A */ - PGB_INSTR_XOR_R8(gb->cpu_reg.a); + gb->cpu_reg.a = 0x00; + gb->cpu_reg.f_bits.z = 1; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xB0: /* OR B */ - PGB_INSTR_OR_R8(gb->cpu_reg.bc.bytes.b); + gb->cpu_reg.a = gb->cpu_reg.a | gb->cpu_reg.b; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xB1: /* OR C */ - PGB_INSTR_OR_R8(gb->cpu_reg.bc.bytes.c); + gb->cpu_reg.a = gb->cpu_reg.a | gb->cpu_reg.c; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xB2: /* OR D */ - PGB_INSTR_OR_R8(gb->cpu_reg.de.bytes.d); + gb->cpu_reg.a = gb->cpu_reg.a | gb->cpu_reg.d; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xB3: /* OR E */ - PGB_INSTR_OR_R8(gb->cpu_reg.de.bytes.e); + gb->cpu_reg.a = gb->cpu_reg.a | gb->cpu_reg.e; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xB4: /* OR H */ - PGB_INSTR_OR_R8(gb->cpu_reg.hl.bytes.h); + gb->cpu_reg.a = gb->cpu_reg.a | gb->cpu_reg.h; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xB5: /* OR L */ - PGB_INSTR_OR_R8(gb->cpu_reg.hl.bytes.l); + gb->cpu_reg.a = gb->cpu_reg.a | gb->cpu_reg.l; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xB6: /* OR (HL) */ - PGB_INSTR_OR_R8(__gb_read(gb, gb->cpu_reg.hl.reg)); + gb->cpu_reg.a = gb->cpu_reg.a | __gb_read(gb, gb->cpu_reg.hl); + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xB7: /* OR A */ - PGB_INSTR_OR_R8(gb->cpu_reg.a); + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; break; case 0xB8: /* CP B */ - PGB_INSTR_CP_R8(gb->cpu_reg.bc.bytes.b); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.b; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.b ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; break; + } case 0xB9: /* CP C */ - PGB_INSTR_CP_R8(gb->cpu_reg.bc.bytes.c); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.c; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.c ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; break; + } case 0xBA: /* CP D */ - PGB_INSTR_CP_R8(gb->cpu_reg.de.bytes.d); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.d; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.d ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; break; + } case 0xBB: /* CP E */ - PGB_INSTR_CP_R8(gb->cpu_reg.de.bytes.e); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.e; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.e ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; break; + } case 0xBC: /* CP H */ - PGB_INSTR_CP_R8(gb->cpu_reg.hl.bytes.h); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.h; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.h ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; break; + } case 0xBD: /* CP L */ - PGB_INSTR_CP_R8(gb->cpu_reg.hl.bytes.l); + { + uint16_t temp = gb->cpu_reg.a - gb->cpu_reg.l; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ gb->cpu_reg.l ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; break; + } - case 0xBE: /* CP (HL) */ - PGB_INSTR_CP_R8(__gb_read(gb, gb->cpu_reg.hl.reg)); + /* TODO: Optimsation by combining similar opcode routines. */ + case 0xBE: /* CP B */ + { + uint8_t val = __gb_read(gb, gb->cpu_reg.hl); + uint16_t temp = gb->cpu_reg.a - val; + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ val ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp & 0xFF00) ? 1 : 0; break; + } case 0xBF: /* CP A */ gb->cpu_reg.f_bits.z = 1; @@ -3168,106 +2887,108 @@ void __gb_step_cpu(struct gb_s *gb) case 0xC0: /* RET NZ */ if(!gb->cpu_reg.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++); + gb->cpu_reg.pc = __gb_read(gb, gb->cpu_reg.sp++); + gb->cpu_reg.pc |= __gb_read(gb, gb->cpu_reg.sp++) << 8; 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++); + gb->cpu_reg.c = __gb_read(gb, gb->cpu_reg.sp++); + gb->cpu_reg.b = __gb_read(gb, gb->cpu_reg.sp++); break; case 0xC2: /* JP NZ, imm */ if(!gb->cpu_reg.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; + uint16_t temp = __gb_read(gb, gb->cpu_reg.pc++); + temp |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + gb->cpu_reg.pc = temp; inst_cycles += 4; } else - gb->cpu_reg.pc.reg += 2; + gb->cpu_reg.pc += 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; + uint16_t temp = __gb_read(gb, gb->cpu_reg.pc++); + temp |= __gb_read(gb, gb->cpu_reg.pc) << 8; + gb->cpu_reg.pc = temp; break; } case 0xC4: /* CALL NZ imm */ if(!gb->cpu_reg.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; + uint16_t temp = __gb_read(gb, gb->cpu_reg.pc++); + temp |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = temp; inst_cycles += 12; } else - gb->cpu_reg.pc.reg += 2; + gb->cpu_reg.pc += 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); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.b); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.c); break; case 0xC6: /* ADD A, imm */ { - uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++); - PGB_INSTR_ADC_R8(val, 0); + /* Taken from SameBoy, which is released under MIT Licence. */ + uint8_t value = __gb_read(gb, gb->cpu_reg.pc++); + uint16_t calc = gb->cpu_reg.a + value; + gb->cpu_reg.f_bits.z = ((uint8_t)calc == 0) ? 1 : 0; + gb->cpu_reg.f_bits.h = + ((gb->cpu_reg.a & 0xF) + (value & 0xF) > 0x0F) ? 1 : 0; + gb->cpu_reg.f_bits.c = calc > 0xFF ? 1 : 0; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.a = (uint8_t)calc; 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; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = 0x0000; break; case 0xC8: /* RET Z */ if(gb->cpu_reg.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++); + uint16_t temp = __gb_read(gb, gb->cpu_reg.sp++); + temp |= __gb_read(gb, gb->cpu_reg.sp++) << 8; + gb->cpu_reg.pc = temp; 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++); + uint16_t temp = __gb_read(gb, gb->cpu_reg.sp++); + temp |= __gb_read(gb, gb->cpu_reg.sp++) << 8; + gb->cpu_reg.pc = temp; break; } case 0xCA: /* JP Z, imm */ if(gb->cpu_reg.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; + uint16_t temp = __gb_read(gb, gb->cpu_reg.pc++); + temp |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + gb->cpu_reg.pc = temp; inst_cycles += 4; } else - gb->cpu_reg.pc.reg += 2; + gb->cpu_reg.pc += 2; break; @@ -3278,100 +2999,103 @@ void __gb_step_cpu(struct gb_s *gb) case 0xCC: /* CALL Z, imm */ if(gb->cpu_reg.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; + uint16_t temp = __gb_read(gb, gb->cpu_reg.pc++); + temp |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = temp; inst_cycles += 12; } else - gb->cpu_reg.pc.reg += 2; + gb->cpu_reg.pc += 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; + uint16_t addr = __gb_read(gb, gb->cpu_reg.pc++); + addr |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = addr; } 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_bits.c); + uint8_t value, a, carry; + value = __gb_read(gb, gb->cpu_reg.pc++); + a = gb->cpu_reg.a; + carry = gb->cpu_reg.f_bits.c; + gb->cpu_reg.a = a + value + carry; + + gb->cpu_reg.f_bits.z = gb->cpu_reg.a == 0 ? 1 : 0; + gb->cpu_reg.f_bits.h = + ((a & 0xF) + (value & 0xF) + carry > 0x0F) ? 1 : 0; + gb->cpu_reg.f_bits.c = + (((uint16_t) a) + ((uint16_t) value) + carry > 0xFF) ? 1 : 0; + gb->cpu_reg.f_bits.n = 0; 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; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = 0x0008; break; case 0xD0: /* RET NC */ if(!gb->cpu_reg.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++); + uint16_t temp = __gb_read(gb, gb->cpu_reg.sp++); + temp |= __gb_read(gb, gb->cpu_reg.sp++) << 8; + gb->cpu_reg.pc = temp; 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++); + gb->cpu_reg.e = __gb_read(gb, gb->cpu_reg.sp++); + gb->cpu_reg.d = __gb_read(gb, gb->cpu_reg.sp++); break; case 0xD2: /* JP NC, imm */ if(!gb->cpu_reg.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; + uint16_t temp = __gb_read(gb, gb->cpu_reg.pc++); + temp |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + gb->cpu_reg.pc = temp; inst_cycles += 4; } else - gb->cpu_reg.pc.reg += 2; + gb->cpu_reg.pc += 2; break; case 0xD4: /* CALL NC, imm */ if(!gb->cpu_reg.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; + uint16_t temp = __gb_read(gb, gb->cpu_reg.pc++); + temp |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = temp; inst_cycles += 12; } else - gb->cpu_reg.pc.reg += 2; + gb->cpu_reg.pc += 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); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.d); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.e); break; case 0xD6: /* SUB imm */ { - uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++); + uint8_t val = __gb_read(gb, gb->cpu_reg.pc++); uint16_t temp = gb->cpu_reg.a - val; gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); gb->cpu_reg.f_bits.n = 1; @@ -3383,16 +3107,17 @@ void __gb_step_cpu(struct gb_s *gb) } 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; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = 0x0010; break; case 0xD8: /* RET C */ if(gb->cpu_reg.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++); + uint16_t temp = __gb_read(gb, gb->cpu_reg.sp++); + temp |= __gb_read(gb, gb->cpu_reg.sp++) << 8; + gb->cpu_reg.pc = temp; inst_cycles += 12; } @@ -3400,8 +3125,9 @@ void __gb_step_cpu(struct gb_s *gb) 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++); + uint16_t temp = __gb_read(gb, gb->cpu_reg.sp++); + temp |= __gb_read(gb, gb->cpu_reg.sp++) << 8; + gb->cpu_reg.pc = temp; gb->gb_ime = 1; } break; @@ -3409,70 +3135,72 @@ void __gb_step_cpu(struct gb_s *gb) case 0xDA: /* JP C, imm */ if(gb->cpu_reg.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; + uint16_t addr = __gb_read(gb, gb->cpu_reg.pc++); + addr |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + gb->cpu_reg.pc = addr; inst_cycles += 4; } else - gb->cpu_reg.pc.reg += 2; + gb->cpu_reg.pc += 2; break; case 0xDC: /* CALL C, imm */ if(gb->cpu_reg.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; + uint16_t temp = __gb_read(gb, gb->cpu_reg.pc++); + temp |= __gb_read(gb, gb->cpu_reg.pc++) << 8; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = temp; inst_cycles += 12; } else - gb->cpu_reg.pc.reg += 2; + gb->cpu_reg.pc += 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_bits.c); + uint8_t temp_8 = __gb_read(gb, gb->cpu_reg.pc++); + uint16_t temp_16 = gb->cpu_reg.a - temp_8 - gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.z = ((temp_16 & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ temp_8 ^ temp_16) & 0x10 ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp_16 & 0xFF00) ? 1 : 0; + gb->cpu_reg.a = (temp_16 & 0xFF); 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; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = 0x0018; break; case 0xE0: /* LD (0xFF00+imm), A */ - __gb_write(gb, 0xFF00 | __gb_read(gb, gb->cpu_reg.pc.reg++), + __gb_write(gb, 0xFF00 | __gb_read(gb, gb->cpu_reg.pc++), 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++); + gb->cpu_reg.l = __gb_read(gb, gb->cpu_reg.sp++); + gb->cpu_reg.h = __gb_read(gb, gb->cpu_reg.sp++); break; case 0xE2: /* LD (C), A */ - __gb_write(gb, 0xFF00 | gb->cpu_reg.bc.bytes.c, gb->cpu_reg.a); + __gb_write(gb, 0xFF00 | gb->cpu_reg.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); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.h); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.l); break; case 0xE6: /* AND imm */ /* TODO: Optimisation? */ - gb->cpu_reg.a = gb->cpu_reg.a & __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.a = gb->cpu_reg.a & __gb_read(gb, gb->cpu_reg.pc++); gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); gb->cpu_reg.f_bits.n = 0; gb->cpu_reg.f_bits.h = 1; @@ -3480,65 +3208,67 @@ void __gb_step_cpu(struct gb_s *gb) 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; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = 0x0020; break; case 0xE8: /* ADD SP, imm */ { - int8_t offset = (int8_t) __gb_read(gb, gb->cpu_reg.pc.reg++); + int8_t offset = (int8_t) __gb_read(gb, gb->cpu_reg.pc++); + /* TODO: Move flag assignments for optimisation. */ gb->cpu_reg.f_bits.z = 0; gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.sp.reg & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0; - gb->cpu_reg.f_bits.c = ((gb->cpu_reg.sp.reg & 0xFF) + (offset & 0xFF) > 0xFF); - gb->cpu_reg.sp.reg += offset; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.sp & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0; + gb->cpu_reg.f_bits.c = ((gb->cpu_reg.sp & 0xFF) + (offset & 0xFF) > 0xFF); + gb->cpu_reg.sp += offset; break; } case 0xE9: /* JP (HL) */ - gb->cpu_reg.pc.reg = gb->cpu_reg.hl.reg; + gb->cpu_reg.pc = gb->cpu_reg.hl; 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); + uint16_t addr = __gb_read(gb, gb->cpu_reg.pc++); + addr |= __gb_read(gb, gb->cpu_reg.pc++) << 8; __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++)); + gb->cpu_reg.a = gb->cpu_reg.a ^ __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; 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; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = 0x0028; break; case 0xF0: /* LD A, (0xFF00+imm) */ gb->cpu_reg.a = - __gb_read(gb, 0xFF00 | __gb_read(gb, gb->cpu_reg.pc.reg++)); + __gb_read(gb, 0xFF00 | __gb_read(gb, gb->cpu_reg.pc++)); break; case 0xF1: /* POP AF */ { - uint8_t temp_8 = __gb_read(gb, gb->cpu_reg.sp.reg++); + uint8_t temp_8 = __gb_read(gb, gb->cpu_reg.sp++); gb->cpu_reg.f_bits.z = (temp_8 >> 7) & 1; gb->cpu_reg.f_bits.n = (temp_8 >> 6) & 1; gb->cpu_reg.f_bits.h = (temp_8 >> 5) & 1; gb->cpu_reg.f_bits.c = (temp_8 >> 4) & 1; - gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.sp.reg++); + gb->cpu_reg.a = __gb_read(gb, gb->cpu_reg.sp++); break; } case 0xF2: /* LD A, (C) */ - gb->cpu_reg.a = __gb_read(gb, 0xFF00 | gb->cpu_reg.bc.bytes.c); + gb->cpu_reg.a = __gb_read(gb, 0xFF00 | gb->cpu_reg.c); break; case 0xF3: /* DI */ @@ -3546,46 +3276,47 @@ void __gb_step_cpu(struct gb_s *gb) 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_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.a); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.f_bits.z << 7 | gb->cpu_reg.f_bits.n << 6 | gb->cpu_reg.f_bits.h << 5 | gb->cpu_reg.f_bits.c << 4); break; case 0xF6: /* OR imm */ - PGB_INSTR_OR_R8(__gb_read(gb, gb->cpu_reg.pc.reg++)); + gb->cpu_reg.a = gb->cpu_reg.a | __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; 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; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = 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; + int8_t offset = (int8_t) __gb_read(gb, gb->cpu_reg.pc++); + gb->cpu_reg.hl = gb->cpu_reg.sp + offset; gb->cpu_reg.f_bits.z = 0; gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = ((gb->cpu_reg.sp.reg & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0; - gb->cpu_reg.f_bits.c = ((gb->cpu_reg.sp.reg & 0xFF) + (offset & 0xFF) > 0xFF) ? 1 : + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.sp & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0; + gb->cpu_reg.f_bits.c = ((gb->cpu_reg.sp & 0xFF) + (offset & 0xFF) > 0xFF) ? 1 : 0; break; } case 0xF9: /* LD SP, HL */ - gb->cpu_reg.sp.reg = gb->cpu_reg.hl.reg; + gb->cpu_reg.sp = gb->cpu_reg.hl; 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); + uint16_t addr = __gb_read(gb, gb->cpu_reg.pc++); + addr |= __gb_read(gb, gb->cpu_reg.pc++) << 8; gb->cpu_reg.a = __gb_read(gb, addr); break; } @@ -3596,241 +3327,198 @@ void __gb_step_cpu(struct gb_s *gb) case 0xFE: /* CP imm */ { - uint8_t val = __gb_read(gb, gb->cpu_reg.pc.reg++); - PGB_INSTR_CP_R8(val); + uint8_t temp_8 = __gb_read(gb, gb->cpu_reg.pc++); + uint16_t temp_16 = gb->cpu_reg.a - temp_8; + gb->cpu_reg.f_bits.z = ((temp_16 & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ temp_8 ^ temp_16) & 0x10) ? 1 : 0; + gb->cpu_reg.f_bits.c = (temp_16 & 0xFF00) ? 1 : 0; 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; + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc >> 8); + __gb_write(gb, --gb->cpu_reg.sp, gb->cpu_reg.pc & 0xFF); + gb->cpu_reg.pc = 0x0038; break; default: - /* Return address where invlid opcode that was read. */ - (gb->gb_error)(gb, GB_INVALID_OPCODE, gb->cpu_reg.pc.reg - 1); - PGB_UNREACHABLE(); + (gb->gb_error)(gb, GB_INVALID_OPCODE, opcode); } - 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; - } + /* DIV register timing */ + gb->counter.div_count += inst_cycles; - /* Check serial transmission. */ - if(gb->hram_io[IO_SC] & SERIAL_SC_TX_START) - { - unsigned int serial_cycles = SERIAL_CYCLES_1KB; + if(gb->counter.div_count >= DIV_CYCLES) + { + gb->gb_reg.DIV++; + gb->counter.div_count -= DIV_CYCLES; + } - /* 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]); + /* Check serial transmission. */ + if(gb->gb_reg.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->gb_reg.SB); -#if PEANUT_FULL_GBC_SUPPORT - if(gb->hram_io[IO_SC] & 0x3) - serial_cycles = SERIAL_CYCLES_32KB; -#endif + gb->counter.serial_count += inst_cycles; - 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 it's time to receive byte, call RX function. */ - if(gb->counter.serial_count >= serial_cycles) + if(gb->gb_serial_rx != NULL && + (gb->gb_serial_rx(gb, &rx) == + GB_SERIAL_RX_SUCCESS)) { - /* 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->gb_reg.SB = rx; - gb->counter.serial_count = 0; + /* Inform game of serial TX/RX completion. */ + gb->gb_reg.SC &= 0x01; + gb->gb_reg.IF |= SERIAL_INTR; + } + else if(gb->gb_reg.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->gb_reg.SB = 0xFF; + + /* Inform game of serial TX/RX completion. */ + gb->gb_reg.SC &= 0x01; + gb->gb_reg.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) + /* TIMA register timing */ + /* TODO: Change tac_enable to struct of TAC timer control bits. */ + if(gb->gb_reg.tac_enable) + { + static const uint_fast16_t TAC_CYCLES[4] = {1024, 16, 64, 256}; + + gb->counter.tima_count += inst_cycles; + + while(gb->counter.tima_count >= TAC_CYCLES[gb->gb_reg.tac_rate]) { - gb->counter.tima_count += inst_cycles; + gb->counter.tima_count -= TAC_CYCLES[gb->gb_reg.tac_rate]; - while(gb->counter.tima_count >= - TAC_CYCLES[gb->hram_io[IO_TAC] & IO_TAC_RATE_MASK]) + if(++gb->gb_reg.TIMA == 0) { - 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]; - } + gb->gb_reg.IF |= TIMER_INTR; + /* On overflow, set TMA to TIMA. */ + gb->gb_reg.TIMA = gb->gb_reg.TMA; } } + } + + /* TODO Check behaviour of LCD during LCD power off state. */ + /* If LCD is off, don't update LCD state. */ + if((gb->gb_reg.LCDC & LCDC_ENABLE) == 0) + return; - /* If LCD is off, don't update LCD state or increase the LCD - * ticks. */ - if(!(gb->hram_io[IO_LCDC] & LCDC_ENABLE)) - continue; + /* LCD Timing */ + gb->counter.lcd_count += inst_cycles; - /* LCD Timing */ -#if PEANUT_FULL_GBC_SUPPORT - if (inst_cycles > 1) - gb->counter.lcd_count += (inst_cycles >> gb->cgb.doubleSpeed); - else -#endif - gb->counter.lcd_count += inst_cycles; + /* New Scanline */ + if(gb->counter.lcd_count > LCD_LINE_CYCLES) + { + gb->counter.lcd_count -= LCD_LINE_CYCLES; - /* New Scanline */ - if(gb->counter.lcd_count >= LCD_LINE_CYCLES) + /* LYC Update */ + if(gb->gb_reg.LY == gb->gb_reg.LYC) { - gb->counter.lcd_count -= LCD_LINE_CYCLES; - - /* Next line */ - gb->hram_io[IO_LY] = (gb->hram_io[IO_LY] + 1) % LCD_VERT_LINES; + gb->gb_reg.STAT |= STAT_LYC_COINC; - /* LYC Update */ - if(gb->hram_io[IO_LY] == gb->hram_io[IO_LYC]) - { - gb->hram_io[IO_STAT] |= STAT_LYC_COINC; + if(gb->gb_reg.STAT & STAT_LYC_INTR) + gb->gb_reg.IF |= LCDC_INTR; + } + else + gb->gb_reg.STAT &= 0xFB; - if(gb->hram_io[IO_STAT] & STAT_LYC_INTR) - gb->hram_io[IO_IF] |= LCDC_INTR; - } - else - gb->hram_io[IO_STAT] &= 0xFB; + /* Next line */ + gb->gb_reg.LY = (gb->gb_reg.LY + 1) % LCD_VERT_LINES; - /* VBLANK Start */ - 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 = 1; - gb->hram_io[IO_IF] |= VBLANK_INTR; - gb->lcd_blank = 0; + /* VBLANK Start */ + if(gb->gb_reg.LY == LCD_HEIGHT) + { + gb->lcd_mode = LCD_VBLANK; + gb->gb_frame = 1; + gb->gb_reg.IF |= VBLANK_INTR; - if(gb->hram_io[IO_STAT] & STAT_MODE_1_INTR) - gb->hram_io[IO_IF] |= LCDC_INTR; + if(gb->gb_reg.STAT & STAT_MODE_1_INTR) + gb->gb_reg.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 - } - /* Normal Line */ - else if(gb->hram_io[IO_LY] < LCD_HEIGHT) + /* If frame skip is activated, check if we need to draw + * the frame or skip it. */ + if(gb->direct.frame_skip) { - if(gb->hram_io[IO_LY] == 0) - { - /* Clear Screen */ - gb->display.WY = gb->hram_io[IO_WY]; - gb->display.window_clear = 0; - } + gb->display.frame_skip_count = + !gb->display.frame_skip_count; + } - gb->hram_io[IO_STAT] = - (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_HBLANK; + /* 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; + } -#if PEANUT_FULL_GBC_SUPPORT - //DMA GBC - if(gb->cgb.cgbMode && !gb->cgb.dmaActive && gb->cgb.dmaMode) - { - for (uint8_t i = 0; i < 0x10; i++) - { - __gb_write(gb, ((gb->cgb.dmaDest & 0x1FF0) | 0x8000) + i, - __gb_read(gb, (gb->cgb.dmaSource & 0xFFF0) + i)); - } - gb->cgb.dmaSource += 0x10; - gb->cgb.dmaDest += 0x10; - if(!(--gb->cgb.dmaSize)) gb->cgb.dmaActive = 1; - } #endif - if(gb->hram_io[IO_STAT] & STAT_MODE_0_INTR) - gb->hram_io[IO_IF] |= LCDC_INTR; - - /* If halted immediately jump to next LCD mode. */ - if(gb->counter.lcd_count < LCD_MODE_2_CYCLES) - inst_cycles = LCD_MODE_2_CYCLES - gb->counter.lcd_count; - } } - /* OAM access */ - else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_HBLANK && - gb->counter.lcd_count >= LCD_MODE_2_CYCLES) + /* Normal Line */ + else if(gb->gb_reg.LY < LCD_HEIGHT) { - gb->hram_io[IO_STAT] = - (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_SEARCH_OAM; + if(gb->gb_reg.LY == 0) + { + /* Clear Screen */ + gb->display.WY = gb->gb_reg.WY; + gb->display.window_clear = 0; + } - if(gb->hram_io[IO_STAT] & STAT_MODE_2_INTR) - gb->hram_io[IO_IF] |= LCDC_INTR; + gb->lcd_mode = LCD_HBLANK; - /* If halted immediately jump to next LCD mode. */ - if (gb->counter.lcd_count < LCD_MODE_3_CYCLES) - inst_cycles = LCD_MODE_3_CYCLES - gb->counter.lcd_count; + if(gb->gb_reg.STAT & STAT_MODE_0_INTR) + gb->gb_reg.IF |= LCDC_INTR; } - /* Update LCD */ - else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_OAM && - gb->counter.lcd_count >= LCD_MODE_3_CYCLES) - { - gb->hram_io[IO_STAT] = - (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_SEARCH_TRANSFER; + } + /* OAM access */ + else if(gb->lcd_mode == LCD_HBLANK + && gb->counter.lcd_count >= LCD_MODE_2_CYCLES) + { + gb->lcd_mode = LCD_SEARCH_OAM; + + if(gb->gb_reg.STAT & STAT_MODE_2_INTR) + gb->gb_reg.IF |= LCDC_INTR; + } + /* Update LCD */ + else if(gb->lcd_mode == LCD_SEARCH_OAM + && gb->counter.lcd_count >= LCD_MODE_3_CYCLES) + { + gb->lcd_mode = LCD_TRANSFER; #if ENABLE_LCD - if(!gb->lcd_blank) - __gb_draw_line(gb); + __gb_draw_line(gb); #endif - /* If halted immediately jump to next LCD mode. */ - if (gb->counter.lcd_count < LCD_MODE_0_CYCLES) - inst_cycles = LCD_MODE_0_CYCLES - 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) @@ -3852,14 +3540,15 @@ uint_fast32_t gb_get_save_size(struct gb_s *gb) 0x00, 0x800, 0x2000, 0x8000, 0x20000 }; uint8_t ram_size = gb->gb_rom_read(gb, ram_size_location); - - /* MBC2 always has 512 half-bytes of cart RAM. */ - if(gb->mbc == 2) - return 0x200; - return ram_sizes[ram_size]; } +/** + * Set the function used to handle serial transfer in the front-end. This is + * optional. + * gb_serial_transfer takes a byte to transmit and returns the received byte. If + * no cable is connected to the console, return 0xFF. + */ 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*, @@ -3875,21 +3564,22 @@ uint8_t gb_colour_hash(struct gb_s *gb) #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++) + for(uint16_t 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. + * Resets the context, and initialises startup values. */ void gb_reset(struct gb_s *gb) { gb->gb_halt = 0; gb->gb_ime = 1; + gb->gb_bios_enable = 0; + gb->lcd_mode = LCD_HBLANK; /* Initialise MBC values. */ gb->selected_rom_bank = 1; @@ -3897,111 +3587,52 @@ void gb_reset(struct gb_s *gb) 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_bits.z = 1; - gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = hdr_chk; - gb->cpu_reg.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_BANK] = 0x01; -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode) - { - gb->cpu_reg.a = 0x11; - gb->cpu_reg.f_bits.z = 1; - gb->cpu_reg.f_bits.n = 0; - gb->cpu_reg.f_bits.h = hdr_chk; - gb->cpu_reg.f_bits.c = hdr_chk; - gb->cpu_reg.bc.reg = 0x0000; - gb->cpu_reg.de.reg = 0x0008; - gb->cpu_reg.hl.reg = 0x007C; - gb->hram_io[IO_DIV] = 0xFF; - } -#endif - - 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_BANK] = 0x00; - } + /* Initialise CPU registers as though a DMG. */ + gb->cpu_reg.af = 0x01B0; + gb->cpu_reg.bc = 0x0013; + gb->cpu_reg.de = 0x00D8; + gb->cpu_reg.hl = 0x014D; + gb->cpu_reg.sp = 0xFFFE; + /* TODO: Add BIOS support. */ + gb->cpu_reg.pc = 0x0100; gb->counter.lcd_count = 0; gb->counter.div_count = 0; gb->counter.tima_count = 0; gb->counter.serial_count = 0; + gb->gb_reg.TIMA = 0x00; + gb->gb_reg.TMA = 0x00; + gb->gb_reg.TAC = 0xF8; + gb->gb_reg.DIV = 0xAC; + + gb->gb_reg.IF = 0xE1; + + gb->gb_reg.LCDC = 0x91; + gb->gb_reg.SCY = 0x00; + gb->gb_reg.SCX = 0x00; + gb->gb_reg.LYC = 0x00; + + /* Appease valgrind for invalid reads and unconditional jumps. */ + gb->gb_reg.SC = 0x7E; + gb->gb_reg.STAT = 0; + gb->gb_reg.LY = 0; + + __gb_write(gb, 0xFF47, 0xFC); // BGP + __gb_write(gb, 0xFF48, 0xFF); // OBJP0 + __gb_write(gb, 0xFF49, 0x0F); // OBJP1 + gb->gb_reg.WY = 0x00; + gb->gb_reg.WX = 0x00; + gb->gb_reg.IE = 0x00; + gb->direct.joypad = 0xFF; - gb->hram_io[IO_JOYP] = 0xCF; - gb->hram_io[IO_SB ] = 0x00; - gb->hram_io[IO_SC ] = 0x7E; -#if PEANUT_FULL_GBC_SUPPORT - if(gb->cgb.cgbMode) gb->hram_io[IO_SC] = 0x7F; -#endif - /* 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; -#if PEANUT_FULL_GBC_SUPPORT - /* Initialize some CGB registers */ - gb->cgb.doubleSpeed = 0; - gb->cgb.doubleSpeedPrep = 0; - gb->cgb.wramBank = 1; - gb->cgb.wramBankOffset = WRAM_0_ADDR; - gb->cgb.vramBank = 0; - gb->cgb.vramBankOffset = VRAM_ADDR; - for (int i = 0; i < 0x20; i++) - { - gb->cgb.OAMPalette[(i << 1)] = gb->cgb.BGPalette[(i << 1)] = 0x7F; - gb->cgb.OAMPalette[(i << 1) + 1] = gb->cgb.BGPalette[(i << 1) + 1] = 0xFF; - } - gb->cgb.OAMPaletteID = 0; - gb->cgb.BGPaletteID = 0; - gb->cgb.OAMPaletteInc = 0; - gb->cgb.BGPaletteInc = 0; - gb->cgb.dmaActive = 1; // Not active - gb->cgb.dmaMode = 0; - gb->cgb.dmaSize = 0; - gb->cgb.dmaSource = 0; - gb->cgb.dmaDest = 0; -#endif + gb->gb_reg.P1 = 0xCF; } +/** + * Initialise the emulator context. gb_reset() is also called to initialise + * the CPU. + */ 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), @@ -4009,9 +3640,6 @@ enum gb_init_error_e gb_init(struct gb_s *gb, void (*gb_error)(struct gb_s*, const enum gb_error_e, const uint16_t), void *priv) { -#if PEANUT_FULL_GBC_SUPPORT - const uint16_t cgb_flag = 0x0143; -#endif const uint16_t mbc_location = 0x0147; const uint16_t bank_count_location = 0x0148; const uint16_t ram_size_location = 0x0149; @@ -4025,19 +3653,24 @@ enum gb_init_error_e gb_init(struct gb_s *gb, * TODO: HuC3 is unsupported. * TODO: HuC1 is unsupported. **/ - const int8_t cart_mbc[] = + const uint8_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 }; const uint8_t cart_ram[] = { - 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 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 }; - const uint16_t num_rom_banks_mask[] = + const uint16_t num_rom_banks[] = { - 2, 4, 8, 16, 32, 64, 128, 256, 512 + 2, 4, 8, 16, 32, 64, 128, 256, 512, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 72, 80, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const uint8_t num_ram_banks[] = { 0, 1, 1, 4, 16, 8 }; @@ -4053,14 +3686,11 @@ enum gb_init_error_e gb_init(struct gb_s *gb, 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++) + for(uint16_t 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)) @@ -4069,25 +3699,17 @@ enum gb_init_error_e gb_init(struct gb_s *gb, /* Check if cartridge type is supported, and set MBC type. */ { -#if PEANUT_FULL_GBC_SUPPORT - gb->cgb.cgbMode = (gb->gb_rom_read(gb, cgb_flag) & 0x80) >> 7; -#endif 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) + (gb->mbc = cart_mbc[gb->gb_rom_read(gb, mbc_location)]) == 255u) return GB_INIT_CARTRIDGE_UNSUPPORTED; } gb->cart_ram = cart_ram[gb->gb_rom_read(gb, mbc_location)]; - gb->num_rom_banks_mask = num_rom_banks_mask[gb->gb_rom_read(gb, bank_count_location)] - 1; + gb->num_rom_banks = num_rom_banks[gb->gb_rom_read(gb, bank_count_location)]; gb->num_ram_banks = num_ram_banks[gb->gb_rom_read(gb, ram_size_location)]; - /* 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 = 0; gb->display.lcd_draw_line = NULL; gb_reset(gb); @@ -4095,7 +3717,14 @@ enum gb_init_error_e gb_init(struct gb_s *gb, return GB_INIT_NO_ERROR; } -const char* gb_get_rom_name(struct gb_s* gb, char *title_str) +/** + * Returns the title of ROM. + * + * \param gb Initialised context. + * \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[static 16]) { uint_fast16_t title_loc = 0x134; /* End of title may be 0x13E for newer games. */ @@ -4121,8 +3750,8 @@ const char* gb_get_rom_name(struct gb_s* gb, char *title_str) #if ENABLE_LCD void gb_init_lcd(struct gb_s *gb, - void (*lcd_draw_line)(struct gb_s *gb, - const uint8_t *pixels, + void (*lcd_draw_line)(struct gb_s*, + const uint8_t pixels[static 160], const uint_fast8_t line)) { gb->display.lcd_draw_line = lcd_draw_line; @@ -4138,216 +3767,3 @@ void gb_init_lcd(struct gb_s *gb, 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; -} - -/** - * This was taken from SameBoy, which is released under MIT Licence. - */ -void gb_tick_rtc(struct gb_s *gb) -{ - /* is timer running? */ - if((gb->cart_rtc[4] & 0x40) == 0) - { - if(++gb->rtc_bits.sec == 60) - { - gb->rtc_bits.sec = 0; - - if(++gb->rtc_bits.min == 60) - { - gb->rtc_bits.min = 0; - - if(++gb->rtc_bits.hour == 24) - { - gb->rtc_bits.hour = 0; - - if(++gb->rtc_bits.yday == 0) - { - if(gb->rtc_bits.high & 1) /* Bit 8 of days*/ - { - gb->rtc_bits.high |= 0x80; /* Overflow bit */ - } - - gb->rtc_bits.high ^= 1; - } - } - } - } - } -} - -void gb_set_rtc(struct gb_s *gb, const struct tm * const time) -{ - gb->cart_rtc[0] = time->tm_sec; - gb->cart_rtc[1] = time->tm_min; - gb->cart_rtc[2] = time->tm_hour; - gb->cart_rtc[3] = time->tm_yday & 0xFF; /* Low 8 bits of day counter. */ - gb->cart_rtc[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 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. - * \returns Size of the Cart RAM in bytes. 0 if Cartridge has not battery - * backed RAM. - */ -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); - -/** - * Tick the internal RTC by one second. This does not affect games with no RTC - * support. - * - * \param gb An initialised emulator context. Must not be NULL. - */ -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 diff --git a/apps/Peanut-GBC/Makefile b/apps/Peanut-GBC/Makefile new file mode 100644 index 00000000..bd11c1e7 --- /dev/null +++ b/apps/Peanut-GBC/Makefile @@ -0,0 +1,22 @@ +API=../../api +CC=arm-none-eabi-gcc +OBJCOPY=arm-none-eabi-objcopy +CFLAGS=-DNDEBUG -ggdb3 -I$(API) -Os -mcpu=cortex-m7 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard -fno-common -fdata-sections -ffunction-sections -fno-exceptions +LDFLAGS=-Wl,-L$(API) -Wl,--gc-sections -Wl,--entry=entrypoint --specs=nosys.specs -nostartfiles -Wl,-Ur -lapi + +NES_ROM = epsilon/2048.nes + +CFLAGS += -I./ + +OBJS = main.o selector.o lz4.o + +app.elf: $(OBJS) + $(CC) $^ -o $@ $(CFLAGS) $(LDFLAGS) + +FORCE: + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJS) app.elf diff --git a/apps/Peanut-GBC/app.elf b/apps/Peanut-GBC/app.elf new file mode 100644 index 00000000..85c15091 Binary files /dev/null and b/apps/Peanut-GBC/app.elf differ diff --git a/apps/Peanut-GBC/app.icon b/apps/Peanut-GBC/app.icon new file mode 100644 index 00000000..41159cf5 Binary files /dev/null and b/apps/Peanut-GBC/app.icon differ diff --git a/apps/Peanut-GBC/icon.png b/apps/Peanut-GBC/icon.png new file mode 100644 index 00000000..2eed24e9 Binary files /dev/null and b/apps/Peanut-GBC/icon.png differ diff --git a/apps/Peanut-GBC/lz4.c b/apps/Peanut-GBC/lz4.c new file mode 100644 index 00000000..fc3fb880 --- /dev/null +++ b/apps/Peanut-GBC/lz4.c @@ -0,0 +1,2402 @@ +/* + LZ4 - Fast LZ compression algorithm + Copyright (C) 2011-present, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + +/*-************************************ +* Tuning parameters +**************************************/ +/* + * LZ4_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4_HEAPMODE +# define LZ4_HEAPMODE 0 +#endif + +/* + * ACCELERATION_DEFAULT : + * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 + */ +#define ACCELERATION_DEFAULT 1 + + +/*-************************************ +* CPU Feature Detection +**************************************/ +/* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which assembly generation depends on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */ +# if defined(__GNUC__) && \ + ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \ + || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define LZ4_FORCE_MEMORY_ACCESS 2 +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) +# define LZ4_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + + + +/*-************************************ +* Dependency +**************************************/ +/* + * LZ4_SRC_INCLUDED: + * Amalgamation flag, whether lz4.c is included + */ +#ifndef LZ4_SRC_INCLUDED +# define LZ4_SRC_INCLUDED 1 +#endif + +#ifndef LZ4_STATIC_LINKING_ONLY +#define LZ4_STATIC_LINKING_ONLY +#endif + +#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS +#define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ +#endif + +#define LZ4_STATIC_LINKING_ONLY /* LZ4_DISTANCE_MAX */ +#include "lz4.h" +/* see also "memory routines" below */ + + +/*-************************************ +* Compiler Options +**************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# include +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#endif /* _MSC_VER */ + +#ifndef LZ4_FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define LZ4_FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define LZ4_FORCE_INLINE static inline +# endif +# else +# define LZ4_FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* LZ4_FORCE_INLINE */ + +/* LZ4_FORCE_O2_GCC_PPC64LE and LZ4_FORCE_O2_INLINE_GCC_PPC64LE + * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8, + * together with a simple 8-byte copy loop as a fall-back path. + * However, this optimization hurts the decompression speed by >30%, + * because the execution does not go to the optimized loop + * for typical compressible data, and all of the preamble checks + * before going to the fall-back path become useless overhead. + * This optimization happens only with the -O3 flag, and -O2 generates + * a simple 8-byte copy loop. + * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8 + * functions are annotated with __attribute__((optimize("O2"))), + * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute + * of LZ4_wildCopy8 does not affect the compression speed. + */ +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__) +# define LZ4_FORCE_O2_GCC_PPC64LE __attribute__((optimize("O2"))) +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE __attribute__((optimize("O2"))) LZ4_FORCE_INLINE +#else +# define LZ4_FORCE_O2_GCC_PPC64LE +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE static +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#ifndef likely +#define likely(expr) expect((expr) != 0, 1) +#endif +#ifndef unlikely +#define unlikely(expr) expect((expr) != 0, 0) +#endif + + +/*-************************************ +* Memory routines +**************************************/ +#include /* malloc, calloc, free */ +#define ALLOC(s) malloc(s) +#define ALLOC_AND_ZERO(s) calloc(1,s) +#define FREEMEM(p) free(p) +#include /* memset, memcpy */ +#define MEM_INIT(p,v,s) memset((p),(v),(s)) + + +/*-************************************ +* Common Constants +**************************************/ +#define MINMATCH 4 + +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */ +#define FASTLOOP_SAFE_DISTANCE 64 +static const int LZ4_minLength = (MFLIMIT+1); + +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define LZ4_DISTANCE_ABSOLUTE_MAX 65535 +#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */ +# error "LZ4_DISTANCE_MAX is too big : must be <= 65535" +#endif + +#define ML_BITS 4 +#define ML_MASK ((1U<=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) +# include + static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + + +/*-************************************ +* Types +**************************************/ +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef uintptr_t uptrval; +#else +# include +# if UINT_MAX != 4294967295UL +# error "LZ4 code (when not C++ or C99) assumes that sizeof(int) == 4" +# endif + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; + typedef size_t uptrval; /* generally true, except OpenVMS-64 */ +#endif + +#if defined(__x86_64__) + typedef U64 reg_t; /* 64-bits in x32 mode */ +#else + typedef size_t reg_t; /* 32-bits in x32 mode */ +#endif + +typedef enum { + notLimited = 0, + limitedOutput = 1, + fillOutput = 2 +} limitedOutput_directive; + + +/*-************************************ +* Reading and writing into memory +**************************************/ +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + + +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } + +static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) unalign; + +static U16 LZ4_read16(const void* ptr) { return ((const unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const unalign*)ptr)->uArch; } + +static void LZ4_write16(void* memPtr, U16 value) { ((unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((unalign*)memPtr)->u32 = value; } + +#else /* safe and portable access using memcpy() */ + +static U16 LZ4_read16(const void* memPtr) +{ + U16 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static U32 LZ4_read32(const void* memPtr) +{ + U32 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static void LZ4_write16(void* memPtr, U16 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +static void LZ4_write32(void* memPtr, U32 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + + +static U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +LZ4_FORCE_O2_INLINE_GCC_PPC64LE +void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { memcpy(d,s,8); d+=8; s+=8; } while (d= 16. */ +LZ4_FORCE_O2_INLINE_GCC_PPC64LE void +LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { memcpy(d,s,16); memcpy(d+16,s+16,16); d+=32; s+=32; } while (d= dstPtr + MINMATCH + * - there is at least 8 bytes available to write after dstEnd */ +LZ4_FORCE_O2_INLINE_GCC_PPC64LE void +LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset) +{ + BYTE v[8]; + + assert(dstEnd >= dstPtr + MINMATCH); + LZ4_write32(dstPtr, 0); /* silence an msan warning when offset==0 */ + + switch(offset) { + case 1: + memset(v, *srcPtr, 8); + break; + case 2: + memcpy(v, srcPtr, 2); + memcpy(&v[2], srcPtr, 2); + memcpy(&v[4], &v[0], 4); + break; + case 4: + memcpy(v, srcPtr, 4); + memcpy(&v[4], srcPtr, 4); + break; + default: + LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset); + return; + } + + memcpy(dstPtr, v, 8); + dstPtr += 8; + while (dstPtr < dstEnd) { + memcpy(dstPtr, v, 8); + dstPtr += 8; + } +} +#endif + + +/*-************************************ +* Common functions +**************************************/ +static unsigned LZ4_NbCommonBytes (reg_t val) +{ + if (LZ4_isLittleEndian()) { + if (sizeof(val)==8) { +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanForward64( &r, (U64)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctzll((U64)val) >> 3; +# else + static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, + 0, 3, 1, 3, 1, 4, 2, 7, + 0, 2, 3, 6, 1, 5, 3, 5, + 1, 3, 4, 4, 2, 5, 6, 7, + 7, 0, 1, 2, 3, 3, 4, 6, + 2, 6, 5, 5, 3, 4, 5, 6, + 7, 1, 2, 4, 6, 4, 4, 5, + 7, 2, 6, 5, 7, 6, 7, 7 }; + return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward( &r, (U32)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctz((U32)val) >> 3; +# else + static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, + 3, 2, 2, 1, 3, 2, 0, 1, + 3, 3, 1, 2, 2, 2, 2, 0, + 3, 1, 2, 0, 1, 0, 1, 1 }; + return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { /* 64-bits */ +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse64( &r, val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clzll((U64)val) >> 3; +# else + static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ + unsigned r; + if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse( &r, (unsigned long)val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clz((U32)val) >> 3; +# else + unsigned r; + if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } + r += (!val); + return r; +# endif + } + } +} + +#define STEPSIZE sizeof(reg_t) +LZ4_FORCE_INLINE +unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + if (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn+=STEPSIZE; pMatch+=STEPSIZE; + } else { + return LZ4_NbCommonBytes(diff); + } } + + while (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } + pIn += LZ4_NbCommonBytes(diff); + return (unsigned)(pIn - pStart); + } + + if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; } + if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; } + if ((pIn compression run slower on incompressible data */ + + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Like usingExtDict, but everything concerning the preceding + * content is in a separate context, pointed to by + * ctx->dictCtx. ctx->dictionary, ctx->dictSize, and table + * entries in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + + +/*-************************************ +* Local Utils +**************************************/ +int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +int LZ4_sizeofState() { return LZ4_STREAMSIZE; } + + +/*-************************************ +* Internal Definitions used in Tests +**************************************/ +#if defined (__cplusplus) +extern "C" { +#endif + +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize); + +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize); + +#if defined (__cplusplus) +} +#endif + +/*-****************************** +* Compression functions +********************************/ +static U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +static U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) { + const U64 prime5bytes = 889523592379ULL; + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + } else { + const U64 prime8bytes = 11400714785074694791ULL; + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } +} + +LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +static void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; } + } +} + +static void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +static void LZ4_putPositionOnHash(const BYTE* p, U32 h, + void* tableBase, tableType_t const tableType, + const BYTE* srcBase) +{ + switch (tableType) + { + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +static U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { + const U32* const hashTable = (const U32*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-2))); + return hashTable[h]; + } + if (tableType == byU16) { + const U16* const hashTable = (const U16*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-1))); + return hashTable[h]; + } + assert(0); return 0; /* forbidden case */ +} + +static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +LZ4_FORCE_INLINE const BYTE* +LZ4_getPosition(const BYTE* p, + const void* tableBase, tableType_t tableType, + const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +LZ4_FORCE_INLINE void +LZ4_prepareTable(LZ4_stream_t_internal* const cctx, + const int inputSize, + const tableType_t tableType) { + /* If compression failed during the previous step, then the context + * is marked as dirty, therefore, it has to be fully reset. + */ + if (cctx->dirty) { + DEBUGLOG(5, "LZ4_prepareTable: Full reset for %p", cctx); + MEM_INIT(cctx, 0, sizeof(LZ4_stream_t_internal)); + return; + } + + /* If the table hasn't been used, it's guaranteed to be zeroed out, and is + * therefore safe to use no matter what mode we're in. Otherwise, we figure + * out if it's safe to leave as is or whether it needs to be reset. + */ + if (cctx->tableType != clearedTable) { + assert(inputSize >= 0); + if (cctx->tableType != tableType + || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU) + || ((tableType == byU32) && cctx->currentOffset > 1 GB) + || tableType == byPtr + || inputSize >= 4 KB) + { + DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); + MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); + cctx->currentOffset = 0; + cctx->tableType = clearedTable; + } else { + DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); + } + } + + /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back, is faster + * than compressing without a gap. However, compressing with + * currentOffset == 0 is faster still, so we preserve that case. + */ + if (cctx->currentOffset != 0 && tableType == byU32) { + DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); + cctx->currentOffset += 64 KB; + } + + /* Finally, clear history */ + cctx->dictCtx = NULL; + cctx->dictionary = NULL; + cctx->dictSize = 0; +} + +/** LZ4_compress_generic() : + inlined, to ensure branches are decided at compilation time */ +LZ4_FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int maxOutputSize, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + int result; + const BYTE* ip = (const BYTE*) source; + + U32 const startIndex = cctx->currentOffset; + const BYTE* base = (const BYTE*) source - startIndex; + const BYTE* lowLimit; + + const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; + const BYTE* const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE* const dictEnd = dictionary + dictSize; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; + const BYTE* const matchlimit = iend - LASTLITERALS; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = (dictDirective == usingDictCtx) ? + dictionary + dictSize - dictCtx->currentOffset : + dictionary + dictSize - startIndex; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, tableType=%u", inputSize, tableType); + /* If init conditions are not met, we don't have to mark stream + * as having dirty context, since no action was taken yet */ + if (outputDirective == fillOutput && maxOutputSize < 1) { return 0; } /* Impossible to store anything */ + if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported inputSize, too large (or negative) */ + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) { return 0; } /* Size too large (not within 64K limit) */ + if (tableType==byPtr) assert(dictDirective==noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; + } + cctx->currentOffset += (U32)inputSize; + cctx->tableType = (U16)tableType; + + if (inputSizehashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + const BYTE* filledIp; + + /* Find a match */ + if (tableType == byPtr) { + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( (match+LZ4_DISTANCE_MAX < ip) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + + } else { /* byU32, byU16 */ + + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + assert(tableType == byU32); + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + assert(startIndex - matchIndex >= MINMATCH); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex); + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */ + assert(matchIndex < current); + if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX)) + && (matchIndex+LZ4_DISTANCE_MAX < current)) { + continue; + } /* too far */ + assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + + } while(1); + } + + /* Catch up */ + filledIp = ip; + while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputDirective == limitedOutput) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) { + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + if ((outputDirective == fillOutput) && + (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) { + op--; + goto _last_literals; + } + if (litLength >= RUN_MASK) { + int len = (int)(litLength - RUN_MASK); + *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< olimit)) { + /* the match was too close to the end, rewind and go to last literals */ + op = token; + goto _last_literals; + } + + /* Encode Offset */ + if (maybe_extMem) { /* static test */ + DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source)); + assert(offset <= LZ4_DISTANCE_MAX && offset > 0); + LZ4_writeLE16(op, (U16)offset); op+=2; + } else { + DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); + assert(ip-match <= LZ4_DISTANCE_MAX); + LZ4_writeLE16(op, (U16)(ip - match)); op+=2; + } + + /* Encode MatchLength */ + { unsigned matchCode; + + if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx) + && (lowLimit==dictionary) /* match within extDict */ ) { + const BYTE* limit = ip + (dictEnd-match); + assert(dictEnd > match); + if (limit > matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += (size_t)matchCode + MINMATCH; + if (ip==limit) { + unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += (size_t)matchCode + MINMATCH; + DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); + } + + if ((outputDirective) && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) { + if (outputDirective == fillOutput) { + /* Match description too long : reduce it */ + U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255; + ip -= matchCode - newMatchCode; + assert(newMatchCode < matchCode); + matchCode = newMatchCode; + if (unlikely(ip <= filledIp)) { + /* We have already filled up to filledIp so if ip ends up less than filledIp + * we have positions in the hash table beyond the current position. This is + * a problem if we reuse the hash table. So we have to remove these positions + * from the hash table. + */ + const BYTE* ptr; + DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip)); + for (ptr = ip; ptr <= filledIp; ++ptr) { + U32 const h = LZ4_hashPosition(ptr, tableType); + LZ4_clearHash(h, cctx->hashTable, tableType); + } + } + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) { + op+=4; + LZ4_write32(op, 0xFFFFFFFF); + matchCode -= 4*255; + } + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + /* Ensure we have enough space for the last literals. */ + assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit)); + + anchor = ip; + + /* Test end of chunk */ + if (ip >= mflimitPlusOne) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( (match+LZ4_DISTANCE_MAX >= ip) + && (LZ4_read32(match) == LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip-base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + assert(matchIndex < current); + if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current)) + && (LZ4_read32(match) == LZ4_read32(ip)) ) { + token=op++; + *token=0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); + goto _next_match; + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRun = (size_t)(iend - anchor); + if ( (outputDirective) && /* Check output buffer overflow */ + (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) { + if (outputDirective == fillOutput) { + /* adapt lastRun to fill 'dst' */ + assert(olimit >= op); + lastRun = (size_t)(olimit-op) - 1; + lastRun -= (lastRun+240)/255; + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun< 0); + return result; +} + + +int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse; + assert(ctx != NULL); + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +/** + * LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of + * "correctly initialized"). + */ +int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + if (dstCapacity >= LZ4_compressBound(srcSize)) { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + + +int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + int result; +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctxPtr = ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; +#else + LZ4_stream_t ctx; + LZ4_stream_t* const ctxPtr = &ctx; +#endif + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + +#if (LZ4_HEAPMODE) + FREEMEM(ctxPtr); +#endif + return result; +} + + +int LZ4_compress_default(const char* src, char* dst, int srcSize, int maxOutputSize) +{ + return LZ4_compress_fast(src, dst, srcSize, maxOutputSize, 1); +} + + +/* hidden debug function */ +/* strangely enough, gcc generates faster code when this function is uncommented, even if unused */ +int LZ4_compress_fast_force(const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t ctx; + LZ4_initStream(&ctx, sizeof(ctx)); + + if (srcSize < LZ4_64Klimit) { + return LZ4_compress_generic(&ctx.internal_donotuse, src, dst, srcSize, NULL, dstCapacity, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + tableType_t const addrMode = (sizeof(void*) > 4) ? byU32 : byPtr; + return LZ4_compress_generic(&ctx.internal_donotuse, src, dst, srcSize, NULL, dstCapacity, limitedOutput, addrMode, noDict, noDictIssue, acceleration); + } +} + + +/* Note!: This function leaves the stream in an unclean/broken state! + * It is not safe to subsequently use the same state with a _fastReset() or + * _continue() call without resetting it. */ +static int LZ4_compress_destSize_extState (LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ + void* const s = LZ4_initStream(state, sizeof (*state)); + assert(s != NULL); (void)s; + + if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ + return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); + } else { + if (*srcSizePtr < LZ4_64Klimit) { + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, 1); + } else { + tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, 1); + } } +} + + +int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctx == NULL) return 0; +#else + LZ4_stream_t ctxBody; + LZ4_stream_t* ctx = &ctxBody; +#endif + + int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); + +#if (LZ4_HEAPMODE) + FREEMEM(ctx); +#endif + return result; +} + + + +/*-****************************** +* Streaming functions +********************************/ + +LZ4_stream_t* LZ4_createStream(void) +{ + LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMSIZE >= sizeof(LZ4_stream_t_internal)); /* A compilation error here means LZ4_STREAMSIZE is not large enough */ + DEBUGLOG(4, "LZ4_createStream %p", lz4s); + if (lz4s == NULL) return NULL; + LZ4_initStream(lz4s, sizeof(*lz4s)); + return lz4s; +} + +#ifndef _MSC_VER /* for some reason, Visual fails the aligment test on 32-bit x86 : + it reports an aligment of 8-bytes, + while actually aligning LZ4_stream_t on 4 bytes. */ +static size_t LZ4_stream_t_alignment(void) +{ + struct { char c; LZ4_stream_t t; } t_a; + return sizeof(t_a) - sizeof(t_a.t); +} +#endif + +LZ4_stream_t* LZ4_initStream (void* buffer, size_t size) +{ + DEBUGLOG(5, "LZ4_initStream"); + if (buffer == NULL) { return NULL; } + if (size < sizeof(LZ4_stream_t)) { return NULL; } +#ifndef _MSC_VER /* for some reason, Visual fails the aligment test on 32-bit x86 : + it reports an aligment of 8-bytes, + while actually aligning LZ4_stream_t on 4 bytes. */ + if (((size_t)buffer) & (LZ4_stream_t_alignment() - 1)) { return NULL; } /* alignment check */ +#endif + MEM_INIT(buffer, 0, sizeof(LZ4_stream_t)); + return (LZ4_stream_t*)buffer; +} + +/* resetStream is now deprecated, + * prefer initStream() which is more general */ +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t)); +} + +void LZ4_resetStream_fast(LZ4_stream_t* ctx) { + LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); +} + +int LZ4_freeStream (LZ4_stream_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); + FREEMEM(LZ4_stream); + return (0); +} + + +#define HASH_UNIT sizeof(reg_t) +int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse; + const tableType_t tableType = byU32; + const BYTE* p = (const BYTE*)dictionary; + const BYTE* const dictEnd = p + dictSize; + const BYTE* base; + + DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); + + /* It's necessary to reset the context, + * and not just continue it with prepareTable() + * to avoid any risk of generating overflowing matchIndex + * when compressing using this dictionary */ + LZ4_resetStream(LZ4_dict); + + /* We always increment the offset by 64 KB, since, if the dict is longer, + * we truncate it to the last 64k, and if it's shorter, we still want to + * advance by a whole window length so we can provide the guarantee that + * there are only valid offsets in the window, which allows an optimization + * in LZ4_compress_fast_continue() where it uses noDictIssue even when the + * dictionary isn't a full 64k. */ + dict->currentOffset += 64 KB; + + if (dictSize < (int)HASH_UNIT) { + return 0; + } + + if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; + base = dictEnd - dict->currentOffset; + dict->dictionary = p; + dict->dictSize = (U32)(dictEnd - p); + dict->tableType = tableType; + + while (p <= dictEnd-HASH_UNIT) { + LZ4_putPosition(p, dict->hashTable, tableType, base); + p+=3; + } + + return (int)dict->dictSize; +} + +void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream) { + const LZ4_stream_t_internal* dictCtx = dictionaryStream == NULL ? NULL : + &(dictionaryStream->internal_donotuse); + + DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)", + workingStream, dictionaryStream, + dictCtx != NULL ? dictCtx->dictSize : 0); + + /* Calling LZ4_resetStream_fast() here makes sure that changes will not be + * erased by subsequent calls to LZ4_resetStream_fast() in case stream was + * marked as having dirty context, e.g. requiring full reset. + */ + LZ4_resetStream_fast(workingStream); + + if (dictCtx != NULL) { + /* If the current offset is zero, we will never look in the + * external dictionary context, since there is no value a table + * entry can take that indicate a miss. In that case, we need + * to bump the offset to something non-zero. + */ + if (workingStream->internal_donotuse.currentOffset == 0) { + workingStream->internal_donotuse.currentOffset = 64 KB; + } + + /* Don't actually attach an empty dictionary. + */ + if (dictCtx->dictSize == 0) { + dictCtx = NULL; + } + } + workingStream->internal_donotuse.dictCtx = dictCtx; +} + + +static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) +{ + assert(nextSize >= 0); + if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ + /* rescale hash table */ + U32 const delta = LZ4_dict->currentOffset - 64 KB; + const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; + int i; + DEBUGLOG(4, "LZ4_renormDictT"); + for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; + else LZ4_dict->hashTable[i] -= delta; + } + LZ4_dict->currentOffset = 64 KB; + if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB; + LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; + } +} + + +int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, + const char* source, char* dest, + int inputSize, int maxOutputSize, + int acceleration) +{ + const tableType_t tableType = byU32; + LZ4_stream_t_internal* streamPtr = &LZ4_stream->internal_donotuse; + const BYTE* dictEnd = streamPtr->dictionary + streamPtr->dictSize; + + DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i)", inputSize); + + if (streamPtr->dirty) { return 0; } /* Uninitialized structure detected */ + LZ4_renormDictT(streamPtr, inputSize); /* avoid index overflow */ + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + /* invalidate tiny dictionaries */ + if ( (streamPtr->dictSize-1 < 4-1) /* intentional underflow */ + && (dictEnd != (const BYTE*)source) ) { + DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, streamPtr->dictionary); + streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)source; + dictEnd = (const BYTE*)source; + } + + /* Check overlapping input/dictionary space */ + { const BYTE* sourceEnd = (const BYTE*) source + inputSize; + if ((sourceEnd > streamPtr->dictionary) && (sourceEnd < dictEnd)) { + streamPtr->dictSize = (U32)(dictEnd - sourceEnd); + if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; + if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; + streamPtr->dictionary = dictEnd - streamPtr->dictSize; + } + } + + /* prefix mode : source data follows dictionary */ + if (dictEnd == (const BYTE*)source) { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); + else + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); + } + + /* external dictionary mode */ + { int result; + if (streamPtr->dictCtx) { + /* We depend here on the fact that dictCtx'es (produced by + * LZ4_loadDict) guarantee that their tables contain no references + * to offsets between dictCtx->currentOffset - 64 KB and + * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe + * to use noDictIssue even when the dict isn't a full 64 KB. + */ + if (inputSize > 4 KB) { + /* For compressing large blobs, it is faster to pay the setup + * cost to copy the dictionary's tables into the active context, + * so that the compression loop is only looking into one table. + */ + memcpy(streamPtr, streamPtr->dictCtx, sizeof(LZ4_stream_t)); + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); + } + } else { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } + } + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + return result; + } +} + + +/* Hidden debug function, to force-test external dictionary mode */ +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) +{ + LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse; + int result; + + LZ4_renormDictT(streamPtr, srcSize); + + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + } + + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)srcSize; + + return result; +} + + +/*! LZ4_saveDict() : + * If previously compressed data block is not guaranteed to remain available at its memory location, + * save it into a safer place (char* safeBuffer). + * Note : you don't need to call LZ4_loadDict() afterwards, + * dictionary is immediately usable, you can therefore call LZ4_compress_fast_continue(). + * Return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + */ +int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) +{ + LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; + const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; + + if ((U32)dictSize > 64 KB) { dictSize = 64 KB; } /* useless to define a dictionary > 64 KB */ + if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; } + + memmove(safeBuffer, previousDictEnd - dictSize, dictSize); + + dict->dictionary = (const BYTE*)safeBuffer; + dict->dictSize = (U32)dictSize; + + return dictSize; +} + + + +/*-******************************* + * Decompression functions + ********************************/ + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; + +#undef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + +/* Read the variable-length literal or match length. + * + * ip - pointer to use as input. + * lencheck - end ip. Return an error if ip advances >= lencheck. + * loop_check - check ip >= lencheck in body of loop. Returns loop_error if so. + * initial_check - check ip >= lencheck before start of loop. Returns initial_error if so. + * error (output) - error code. Should be set to 0 before call. + */ +typedef enum { loop_error = -2, initial_error = -1, ok = 0 } variable_length_error; +LZ4_FORCE_INLINE unsigned +read_variable_length(const BYTE**ip, const BYTE* lencheck, int loop_check, int initial_check, variable_length_error* error) +{ + U32 length = 0; + U32 s; + if (initial_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = initial_error; + return length; + } + do { + s = **ip; + (*ip)++; + length += s; + if (loop_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = loop_error; + return length; + } + } while (s==255); + + return length; +} + +/*! LZ4_decompress_generic() : + * This generic decompression function covers all use cases. + * It shall be instantiated several times, using different sets of directives. + * Note that it is important for performance that this function really get inlined, + * in order to remove useless branches during compilation optimization. + */ +LZ4_FORCE_INLINE int +LZ4_decompress_generic( + const char* const src, + char* const dst, + int srcSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ + + endCondition_directive endOnInput, /* endOnOutputSize, endOnInputSize */ + earlyEnd_directive partialDecoding, /* full, partial */ + dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + if (src == NULL) { return -1; } + + { const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + outputSize; + BYTE* cpy; + + const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize; + + const int safeDecode = (endOnInput==endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Set up the "end" pointers for the shortcut. */ + const BYTE* const shortiend = iend - (endOnInput ? 14 : 8) /*maxLL*/ - 2 /*offset*/; + const BYTE* const shortoend = oend - (endOnInput ? 14 : 8) /*maxLL*/ - 18 /*maxML*/; + + const BYTE* match; + size_t offset; + unsigned token; + size_t length; + + + DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); + + /* Special cases */ + assert(lowPrefix <= op); + if ((endOnInput) && (unlikely(outputSize==0))) { + /* Empty output buffer */ + if (partialDecoding) return 0; + return ((srcSize==1) && (*ip==0)) ? 0 : -1; + } + if ((!endOnInput) && (unlikely(outputSize==0))) { return (*ip==0 ? 1 : -1); } + if ((endOnInput) && unlikely(srcSize==0)) { return -1; } + + /* Currently the fast loop shows a regression on qualcomm arm chips. */ +#if LZ4_FAST_DEC_LOOP + if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { + DEBUGLOG(6, "skip fast decode loop"); + goto safe_decode; + } + + /* Fast loop : decode sequences as long as output < iend-FASTLOOP_SAFE_DISTANCE */ + while (1) { + /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */ + assert(oend - op >= FASTLOOP_SAFE_DISTANCE); + if (endOnInput) { assert(ip < iend); } + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend-RUN_MASK, endOnInput, endOnInput, &error); + if (error == initial_error) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + + /* copy literals */ + cpy = op+length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if (endOnInput) { /* LZ4_decompress_safe() */ + if ((cpy>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; } + LZ4_wildCopy32(op, ip, cpy); + } else { /* LZ4_decompress_fast() */ + if (cpy>oend-8) { goto safe_literal_copy; } + LZ4_wildCopy8(op, ip, cpy); /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and only relies on end-of-block properties */ + } + ip += length; op = cpy; + } else { + cpy = op+length; + if (endOnInput) { /* LZ4_decompress_safe() */ + DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); + /* We don't need to check oend, since we check it once for each loop below */ + if (ip > iend-(16 + 1/*max lit + offset + nextToken*/)) { goto safe_literal_copy; } + /* Literals can only be 14, but hope compilers optimize if we copy by a register size */ + memcpy(op, ip, 16); + } else { /* LZ4_decompress_fast() */ + /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and relies on end-of-block properties */ + memcpy(op, ip, 8); + if (length > 8) { memcpy(op+8, ip+8, 8); } + } + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + assert(match <= op); + + /* get matchlength */ + length = token & ML_MASK; + + if (length == ML_MASK) { + variable_length_error error = ok; + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + length += read_variable_length(&ip, iend - LASTLITERALS + 1, endOnInput, 0, &error); + if (error != ok) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */ + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + } else { + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + + /* Fastpath check: Avoids a branch in LZ4_wildCopy32 if true */ + if ((dict == withPrefix64k) || (match >= lowPrefix)) { + if (offset >= 8) { + assert(match >= lowPrefix); + assert(match <= op); + assert(op + 18 <= oend); + + memcpy(op, match, 8); + memcpy(op+8, match+8, 8); + memcpy(op+16, match+16, 2); + op += length; + continue; + } } } + + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) { + length = MIN(length, (size_t)(oend-op)); /* reach end of buffer */ + } else { + goto _output_error; /* end-of-block condition violated */ + } } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) { *op++ = *copyFrom++; } + } else { + memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + + assert((op <= oend) && (oend-op >= 32)); + if (unlikely(offset<16)) { + LZ4_memcpy_using_offset(op, match, cpy, offset); + } else { + LZ4_wildCopy32(op, match, cpy); + } + + op = cpy; /* wildcopy correction */ + } + safe_decode: +#endif + + /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */ + while (1) { + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* A two-stage shortcut for the most common case: + * 1) If the literal length is 0..14, and there is enough space, + * enter the shortcut and copy 16 bytes on behalf of the literals + * (in the fast mode, only 8 bytes can be safely copied this way). + * 2) Further if the match length is 4..18, copy 18 bytes in a similar + * manner; but we ensure that there's enough space in the output for + * those 18 bytes earlier, upon entering the shortcut (in other words, + * there is a combined check for both stages). + */ + if ( (endOnInput ? length != RUN_MASK : length <= 8) + /* strictly "less than" on input, to re-enter the loop with at least one byte */ + && likely((endOnInput ? ip < shortiend : 1) & (op <= shortoend)) ) { + /* Copy the literals */ + memcpy(op, ip, endOnInput ? 16 : 8); + op += length; ip += length; + + /* The second stage: prepare for match copying, decode full info. + * If it doesn't work out, the info won't be wasted. */ + length = token & ML_MASK; /* match length */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + assert(match <= op); /* check overflow */ + + /* Do not deal with overlapping matches. */ + if ( (length != ML_MASK) + && (offset >= 8) + && (dict==withPrefix64k || match >= lowPrefix) ) { + /* Copy the match. */ + memcpy(op + 0, match + 0, 8); + memcpy(op + 8, match + 8, 8); + memcpy(op +16, match +16, 2); + op += length + MINMATCH; + /* Both stages worked, load the next token. */ + continue; + } + + /* The second stage didn't work out, but the info is ready. + * Propel it right to the point of match copying. */ + goto _copy_match; + } + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend-RUN_MASK, endOnInput, endOnInput, &error); + if (error == initial_error) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + } + + /* copy literals */ + cpy = op+length; +#if LZ4_FAST_DEC_LOOP + safe_literal_copy: +#endif + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ( ((endOnInput) && ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) ) + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) + { + /* We've either hit the input parsing restriction or the output parsing restriction. + * If we've hit the input parsing condition then this must be the last sequence. + * If we've hit the output parsing condition then we are either using partialDecoding + * or we've hit the output parsing condition. + */ + if (partialDecoding) { + /* Since we are partial decoding we may be in this block because of the output parsing + * restriction, which is not valid since the output buffer is allowed to be undersized. + */ + assert(endOnInput); + /* If we're in this block because of the input parsing condition, then we must be on the + * last sequence (or invalid), so we must check that we exactly consume the input. + */ + if ((ip+length>iend-(2+1+LASTLITERALS)) && (ip+length != iend)) { goto _output_error; } + assert(ip+length <= iend); + /* We are finishing in the middle of a literals segment. + * Break after the copy. + */ + if (cpy > oend) { + cpy = oend; + assert(op<=oend); + length = (size_t)(oend-op); + } + assert(ip+length <= iend); + } else { + /* We must be on the last sequence because of the parsing limitations so check + * that we exactly regenerate the original size (must be exact when !endOnInput). + */ + if ((!endOnInput) && (cpy != oend)) { goto _output_error; } + /* We must be on the last sequence (or invalid) because of the parsing limitations + * so check that we exactly consume the input and don't overrun the output buffer. + */ + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) { goto _output_error; } + } + memmove(op, ip, length); /* supports overlapping memory regions, which only matters for in-place decompression scenarios */ + ip += length; + op += length; + /* Necessarily EOF when !partialDecoding. When partialDecoding + * it is EOF if we've either filled the output buffer or hit + * the input parsing restriction. + */ + if (!partialDecoding || (cpy == oend) || (ip == iend)) { + break; + } + } else { + LZ4_wildCopy8(op, ip, cpy); /* may overwrite up to WILDCOPYLENGTH beyond cpy */ + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + + /* get matchlength */ + length = token & ML_MASK; + + _copy_match: + if (length == ML_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend - LASTLITERALS + 1, endOnInput, 0, &error); + if (error != ok) goto _output_error; + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + +#if LZ4_FAST_DEC_LOOP + safe_match_copy: +#endif + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend-op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + assert(match >= lowPrefix); + + /* copy match within block */ + cpy = op + length; + + /* partialDecoding : may end anywhere within the block */ + assert(op<=oend); + if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + size_t const mlen = MIN(length, (size_t)(oend-op)); + const BYTE* const matchEnd = match + mlen; + BYTE* const copyEnd = op + mlen; + if (matchEnd > op) { /* overlap copy */ + while (op < copyEnd) { *op++ = *match++; } + } else { + memcpy(op, match, mlen); + } + op = copyEnd; + if (op == oend) { break; } + continue; + } + + if (unlikely(offset<8)) { + LZ4_write32(op, 0); /* silence msan warning when offset==0 */ + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + memcpy(op+4, match, 4); + match -= dec64table[offset]; + } else { + memcpy(op, match, 8); + match += 8; + } + op += 8; + + if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy8(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op < cpy) { *op++ = *match++; } + } else { + memcpy(op, match, 8); + if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); } + } + op = cpy; /* wildcopy correction */ + } + + /* end of decoding */ + if (endOnInput) { + return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ + } else { + return (int) (((const char*)ip)-src); /* Nb of input bytes read */ + } + + /* Overflow error detected */ + _output_error: + return (int) (-(((const char*)ip)-src))-1; + } +} + + +/*===== Instantiate the API decoding functions. =====*/ + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, + endOnInputSize, decode_full_block, noDict, + (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity, + endOnInputSize, partial_decode, + noDict, (BYTE*)dst, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/*===== Instantiate a few more decoding cases, used more than once. =====*/ + +LZ4_FORCE_O2_GCC_PPC64LE /* Exported, an obsolete API function. */ +int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/* Another obsolete API function, paired with the previous one. */ +int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +{ + /* LZ4_decompress_fast doesn't validate match offsets, + * and thus serves well with any prefixed dictionary. */ + return LZ4_decompress_fast(source, dest, originalSize); +} + +LZ4_FORCE_O2_GCC_PPC64LE +static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2_GCC_PPC64LE +static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +/* The "double dictionary" mode, for use with e.g. ring buffers: the first part + * of the dictionary is passed as prefix, and the second via dictStart + dictSize. + * These routines are used only once, in LZ4_decompress_*_continue(). + */ +LZ4_FORCE_INLINE +int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_INLINE +int LZ4_decompress_fast_doubleDict(const char* source, char* dest, int originalSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +/*===== streaming decompression functions =====*/ + +LZ4_streamDecode_t* LZ4_createStreamDecode(void) +{ + LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMDECODESIZE >= sizeof(LZ4_streamDecode_t_internal)); /* A compilation error here means LZ4_STREAMDECODESIZE is not large enough */ + return lz4s; +} + +int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) +{ + if (LZ4_stream == NULL) { return 0; } /* support free on NULL */ + FREEMEM(LZ4_stream); + return 0; +} + +/*! LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * @return : 1 if OK, 0 if error + */ +int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + lz4sd->prefixSize = (size_t) dictSize; + lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + lz4sd->externalDict = NULL; + lz4sd->extDictSize = 0; + return 1; +} + +/*! LZ4_decoderRingBufferSize() : + * when setting a ring buffer for streaming decompression (optional scenario), + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * Note : in a ring buffer scenario, + * blocks are presumed decompressed next to each other. + * When not enough space remains for next block (remainingSize < maxBlockSize), + * decoding resumes from beginning of ring buffer. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +int LZ4_decoderRingBufferSize(int maxBlockSize) +{ + if (maxBlockSize < 0) return 0; + if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0; + if (maxBlockSize < 16) maxBlockSize = 16; + return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize); +} + +/* +*_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks must still be available at the memory position where they were decoded. + If it's not possible, save the relevant part of decoded data into a safe buffer, + and indicate where it stands using LZ4_setStreamDecode() +*/ +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixSize == 0) { + /* The first call, no dictionary yet. */ + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + /* They're rolling the current segment. */ + if (lz4sd->prefixSize >= 64 KB - 1) + result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + else if (lz4sd->extDictSize == 0) + result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize); + else + result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)result; + lz4sd->prefixEnd += result; + } else { + /* The buffer wraps around, or they're switching to another buffer. */ + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } + + return result; +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + assert(originalSize >= 0); + + if (lz4sd->prefixSize == 0) { + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_fast(source, dest, originalSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + if (lz4sd->prefixSize >= 64 KB - 1 || lz4sd->extDictSize == 0) + result = LZ4_decompress_fast(source, dest, originalSize); + else + result = LZ4_decompress_fast_doubleDict(source, dest, originalSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)originalSize; + lz4sd->prefixEnd += originalSize; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_fast_extDict(source, dest, originalSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } + + return result; +} + + +/* +Advanced decoding functions : +*_usingDict() : + These decoding functions work the same as "_continue" ones, + the dictionary must be explicitly provided within parameters +*/ + +int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (dictStart+dictSize == dest) { + if (dictSize >= 64 KB - 1) { + return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize); +} + +int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) +{ + if (dictSize==0 || dictStart+dictSize == dest) + return LZ4_decompress_fast(source, dest, originalSize); + assert(dictSize >= 0); + return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize); +} + + +/*=************************************************* +* Obsolete Functions +***************************************************/ +/* obsolete compression functions */ +int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) +{ + return LZ4_compress_default(source, dest, inputSize, maxOutputSize); +} +int LZ4_compress(const char* src, char* dest, int srcSize) +{ + return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize)); +} +int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); +} +int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); +} +int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity) +{ + return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1); +} +int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) +{ + return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); +} + +/* +These decompression functions are deprecated and should no longer be used. +They are only provided here for compatibility with older user programs. +- LZ4_uncompress is totally equivalent to LZ4_decompress_fast +- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe +*/ +int LZ4_uncompress (const char* source, char* dest, int outputSize) +{ + return LZ4_decompress_fast(source, dest, outputSize); +} +int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) +{ + return LZ4_decompress_safe(source, dest, isize, maxOutputSize); +} + +/* Obsolete Streaming functions */ + +int LZ4_sizeofStreamState() { return LZ4_STREAMSIZE; } + +int LZ4_resetStreamState(void* state, char* inputBuffer) +{ + (void)inputBuffer; + LZ4_resetStream((LZ4_stream_t*)state); + return 0; +} + +void* LZ4_create (char* inputBuffer) +{ + (void)inputBuffer; + return LZ4_createStream(); +} + +char* LZ4_slideInputBuffer (void* state) +{ + /* avoid const char * -> char * conversion warning */ + return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; +} + +#endif /* LZ4_COMMONDEFS_ONLY */ diff --git a/apps/Peanut-GBC/lz4.h b/apps/Peanut-GBC/lz4.h new file mode 100644 index 00000000..32108e23 --- /dev/null +++ b/apps/Peanut-GBC/lz4.h @@ -0,0 +1,764 @@ +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-present, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef LZ4_H_2983827168210 +#define LZ4_H_2983827168210 + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + It gives full buffer control to user. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). + Decompressing such a compressed block requires additional metadata. + Exact metadata depends on exact decompression function. + For the typical case of LZ4_decompress_safe(), + metadata includes block's compressed size, and maximum bound of decompressed size. + Each application is free to encode and pass such metadata in whichever way it wants. + + lz4.h only handle blocks, it can not generate Frames. + + Blocks are different from Frames (doc/lz4_Frame_format.md). + Frames bundle both blocks and metadata in a specified manner. + Embedding metadata is required for compressed data to be self-contained and portable. + Frame format is delivered through a companion API, declared in lz4frame.h. + The `lz4` CLI can only manage frames. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 2 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version */ + + +/*-************************************ +* Tuning parameter +**************************************/ +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) + * Increasing memory usage improves compression ratio. + * Reduced memory usage may improve speed, thanks to better cache locality. + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE 14 +#endif + + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + * compressedSize : is the exact complete size of the compressed block. + * dstCapacity : is the size of destination buffer (which must be already allocated), presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'targetDestSize'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + * or 0 if compression fails. +*/ +LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); + + +/*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective, + * which can boost performance when only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= dstCapacity) + * If source stream is detected malformed, function returns a negative result. + * + * Note : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : this function features 2 parameters, targetOutputSize and dstCapacity, + * and expects targetOutputSize <= dstCapacity. + * It effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in a previous version of this function, + * decoding operation would not "break" a sequence in the middle. + * As a consequence, there was no guarantee that decoding would stop at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * This is no longer necessary. + * The function nonetheless keeps its signature, in an effort to not break API. + */ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); + +/*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 accept any input as dictionary, + * results are generally better when using Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (necessarily <= 64 KB) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking context. + * A tracking context can be re-used multiple times. + */ +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ + +/*! LZ4_decompress_*_continue() : + * These decoding functions allow decompression of consecutive blocks in "streaming" mode. + * A block is an unsplittable entity, it must be presented entirely to a decompression function. + * Decompression functions only accepts one block at a time. + * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. +*/ +LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity); + + +/*! LZ4_decompress_*_usingDict() : + * These decoding functions work the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() + * They are stand-alone, and don't need an LZ4_streamDecode_t structure. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize); + +#endif /* LZ4_H_2983827168210 */ + + +/*^************************************* + * !!!!!! STATIC LINKING ONLY !!!!!! + ***************************************/ + +/*-**************************************************************************** + * Experimental section + * + * Symbols declared in this section must be considered unstable. Their + * signatures or semantics may change, or they may be removed altogether in the + * future. They are therefore only safe to depend on when the caller is + * statically linked against the library. + * + * To protect against unsafe usage, not only are the declarations guarded, + * the definitions are hidden by default + * when building LZ4 as a shared/dynamic library. + * + * In order to access these declarations, + * define LZ4_STATIC_LINKING_ONLY in your application + * before including LZ4's headers. + * + * In order to make their implementations accessible dynamically, you must + * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. + ******************************************************************************/ + +#ifdef LZ4_STATIC_LINKING_ONLY + +#ifndef LZ4_STATIC_3504398509 +#define LZ4_STATIC_3504398509 + +#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS +#define LZ4LIB_STATIC_API LZ4LIB_API +#else +#define LZ4LIB_STATIC_API +#endif + + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. + * It is only safe to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). + * From a high level, the difference is that + * this function initializes the provided state with a call to something like LZ4_resetStream_fast() + * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_attach_dictionary() : + * This is an experimental API that allows + * efficient use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDict() should + * be expected to work. + * + * Alternatively, the provided dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the first compression call on the stream. + */ +LZ4LIB_STATIC_API void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream); + + +/*! In-place compression and decompression + * + * It's possible to have input and output sharing the same buffer, + * for highly contrained memory environments. + * In both cases, it requires input to lay at the end of the buffer, + * and decompression to start at beginning of the buffer. + * Buffer size must feature some margin, hence be larger than final size. + * + * |<------------------------buffer--------------------------------->| + * |<-----------compressed data--------->| + * |<-----------decompressed size------------------>| + * |<----margin---->| + * + * This technique is more useful for decompression, + * since decompressed size is typically larger, + * and margin is short. + * + * In-place decompression will work inside any buffer + * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). + * This presumes that decompressedSize > compressedSize. + * Otherwise, it means compression actually expanded data, + * and it would be more efficient to store such data with a flag indicating it's not compressed. + * This can happen when data is not compressible (already compressed, or encrypted). + * + * For in-place compression, margin is larger, as it must be able to cope with both + * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, + * and data expansion, which can happen when input is not compressible. + * As a consequence, buffer size requirements are much higher, + * and memory savings offered by in-place compression are more limited. + * + * There are ways to limit this cost for compression : + * - Reduce history size, by modifying LZ4_DISTANCE_MAX. + * Note that it is a compile-time constant, so all compressions will apply this limit. + * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, + * so it's a reasonable trick when inputs are known to be small. + * - Require the compressor to deliver a "maximum compressed size". + * This is the `dstCapacity` parameter in `LZ4_compress*()`. + * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, + * in which case, the return code will be 0 (zero). + * The caller must be ready for these cases to happen, + * and typically design a backup scheme to send data uncompressed. + * The combination of both techniques can significantly reduce + * the amount of margin required for in-place compression. + * + * In-place compression can work in any buffer + * which size is >= (maxCompressedSize) + * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. + * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, + * so it's possible to reduce memory requirements by playing with them. + */ + +#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) +#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ + +#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ +# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ +#endif + +#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ +#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ + +#endif /* LZ4_STATIC_3504398509 */ +#endif /* LZ4_STATIC_LINKING_ONLY */ + + + +#ifndef LZ4_H_98237428734687 +#define LZ4_H_98237428734687 + +/*-************************************************************ + * PRIVATE DEFINITIONS + ************************************************************** + * Do not use these definitions directly. + * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Accessing members will expose code to API and/or ABI break in future versions of the library. + **************************************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +#include + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + uint32_t hashTable[LZ4_HASH_SIZE_U32]; + uint32_t currentOffset; + uint16_t dirty; + uint16_t tableType; + const uint8_t* dictionary; + const LZ4_stream_t_internal* dictCtx; + uint32_t dictSize; +}; + +typedef struct { + const uint8_t* externalDict; + size_t extDictSize; + const uint8_t* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#else + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + unsigned int hashTable[LZ4_HASH_SIZE_U32]; + unsigned int currentOffset; + unsigned short dirty; + unsigned short tableType; + const unsigned char* dictionary; + const LZ4_stream_t_internal* dictCtx; + unsigned int dictSize; +}; + +typedef struct { + const unsigned char* externalDict; + const unsigned char* prefixEnd; + size_t extDictSize; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#endif + +/*! LZ4_stream_t : + * information structure to track an LZ4 stream. + * LZ4_stream_t can also be created using LZ4_createStream(), which is recommended. + * The structure definition can be convenient for static allocation + * (on stack, or as part of larger structure). + * Init this structure with LZ4_initStream() before first use. + * note : only use this definition in association with static linking ! + * this definition is not API/ABI safe, and may change in a future version. + */ +#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4 + ((sizeof(void*)==16) ? 4 : 0) /*AS-400*/ ) +#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) +union LZ4_stream_u { + unsigned long long table[LZ4_STREAMSIZE_U64]; + LZ4_stream_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_stream_t */ + +/*! LZ4_initStream() : v1.9.0+ + * An LZ4_stream_t structure must be initialized at least once. + * This is automatically done when invoking LZ4_createStream(), + * but it's not when the structure is simply declared on stack (for example). + * + * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. + * It can also initialize any arbitrary buffer of sufficient size, + * and will @return a pointer of proper type upon initialization. + * + * Note : initialization fails if size and alignment conditions are not respected. + * In which case, the function will @return NULL. + * Note2: An LZ4_stream_t structure guarantees correct alignment and size. + * Note3: Before v1.9.0, use LZ4_resetStream() instead + */ +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size); + + +/*! LZ4_streamDecode_t : + * information structure to track an LZ4 stream during decompression. + * init this structure using LZ4_setStreamDecode() before first use. + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMDECODESIZE_U64 (4 + ((sizeof(void*)==16) ? 2 : 0) /*AS-400*/ ) +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +union LZ4_streamDecode_u { + unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + * + * Deprecated functions make the compiler generate a warning when invoked. + * This is meant to invite users to update their source code. + * Should deprecation warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc + * or _CRT_SECURE_NO_WARNINGS in Visual. + * + * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS + * before including the header file. + */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif (LZ4_GCC_VERSION >= 405) || defined(__clang__) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif (LZ4_GCC_VERSION >= 301) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# else +# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") +# define LZ4_DEPRECATED(message) +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/* Obsolete compression functions */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/* Obsolete decompression functions */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions; degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/* Obsolete streaming decoding functions */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +/*! LZ4_decompress_fast() : **unsafe!** + * These functions used to be faster than LZ4_decompress_safe(), + * but it has changed, and they are now slower than LZ4_decompress_safe(). + * This is because LZ4_decompress_fast() doesn't know the input size, + * and therefore must progress more cautiously in the input buffer to not read beyond the end of block. + * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. + * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. + * + * The last remaining LZ4_decompress_fast() specificity is that + * it can decompress a block without knowing its compressed size. + * Such functionality could be achieved in a more secure manner, + * by also providing the maximum size of input buffer, + * but it would require new prototypes, and adaptation of the implementation to this new use case. + * + * Parameters: + * originalSize : is the uncompressed size to regenerate. + * `dst` must be already allocated, its size must be >= 'originalSize' bytes. + * @return : number of bytes read from source buffer (== compressed size). + * The function expects to finish at block's end exactly. + * If the source stream is detected malformed, the function stops decoding and returns a negative result. + * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. + * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. + * Also, since match offsets are not validated, match reads from 'src' may underflow too. + * These issues never happen if input (compressed) data is correct. + * But they may happen if input data is invalid (error or intentional tampering). + * As a consequence, use these functions in trusted environments with trusted data **only**. + */ + +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead") +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure must be initialized at least once. + * This is done with LZ4_initStream(), or LZ4_resetStream(). + * Consider switching to LZ4_initStream(), + * invoking LZ4_resetStream() will trigger deprecation warnings in the future. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + + +#endif /* LZ4_H_98237428734687 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/apps/Peanut-GBC/main.c b/apps/Peanut-GBC/main.c new file mode 100644 index 00000000..fe852cf3 --- /dev/null +++ b/apps/Peanut-GBC/main.c @@ -0,0 +1,398 @@ +#include + +#include +#include +#include + +#include "selector.h" +#include "peanut_gb.h" +#include "lz4.h" + +#define PEANUT_FULL_GBC_SUPPORT 1 + +#define MAX_SCRIPTSTORE_SIZE 8192 + +#define SAVE_COOLDOWN 120 + +#define NW_LCD_WIDTH 320 +#define NW_LCD_HEIGHT 240 + +#define DUMMY_ROM 0 +#define DUMMY_ROM_NAME Tetris + + +#ifndef DUMMY_ROM +#define DUMMY_ROM 0 +#endif + +#if DUMMY_ROM +#define _DUMMY_ROM_VAR(NAME) NAME ## _GB +#define DUMMY_ROM_VAR(NAME) _DUMMY_ROM_VAR(NAME) +#define _DUMMY_ROM_FILE(NAME) #NAME ".gb" +#define DUMMY_ROM_FILE(NAME) _DUMMY_ROM_FILE(NAME) + +#include "out.h" +#endif + +static bool running = false; + +struct priv_t { + // Pointer to allocated memory holding GB file. + const uint8_t *rom; + // Pointer to allocated memory holding save file. + uint8_t *cart_ram; + // Line buffer + uint16_t line_buffer[LCD_WIDTH]; +}; + +// Returns a byte from the ROM file at the given address. +uint8_t gb_rom_read(struct gb_s *gb, const uint_fast32_t addr) { + const struct priv_t * const p = gb->direct.priv; + return p->rom[addr]; +} + +// Returns a byte from the cartridge RAM at the given address. +uint8_t gb_cart_ram_read(struct gb_s *gb, const uint_fast32_t addr) { + const struct priv_t * const p = gb->direct.priv; + return p->cart_ram[addr]; +} + +// Writes a given byte to the cartridge RAM at the given address. +void gb_cart_ram_write(struct gb_s *gb, const uint_fast32_t addr, const uint8_t val) { + const struct priv_t * const p = gb->direct.priv; + p->cart_ram[addr] = val; +} + +// Ignore all errors. +void gb_error(struct gb_s *gb, const enum gb_error_e gb_err, const uint16_t val) { + + const char* gb_err_str[4] = { + "UNKNOWN", + "INVALID OPCODE", + "INVALID READ", + "INVALID WRITE" + }; + + switch (gb_err) { + case GB_INVALID_WRITE: + case GB_INVALID_READ: + return; + default: + running = false; + } + + // TODO: Handle errors. +} +const uint16_t palette_peanut_GB[4] = {0x9DE1, 0x8D61, 0x3306, 0x09C1}; +const uint16_t palette_original[4] = {0x8F80, 0x24CC, 0x4402, 0x0A40}; +const uint16_t palette_gray[4] = {0xFFFF, 0xAD55, 0x52AA, 0x0000}; +const uint16_t palette_gray_negative[4] = {0x0000, 0x52AA, 0xAD55, 0xFFFF}; +const uint16_t * palette = palette_peanut_GB; + +uint16_t color_from_gb_pixel(uint8_t gb_pixel) { + uint8_t gb_color = gb_pixel & 0x3; + return palette[gb_color]; +} + +void lcd_draw_line_centered(struct gb_s *gb, const uint8_t * input_pixels, const uint_fast8_t line) { + uint16_t output_pixels[2*LCD_WIDTH]; + + for(int i = 0; i < LCD_WIDTH; i++) { + if (gb->cgb.cgbMode) { + output_pixels[i] = gb->cgb.fixPalette[input_pixels[i]]; + output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x3E0) << 1 | (output_pixels[i] & 0x7C00) >> 10; + output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x7E0) | (output_pixels[i] & 0xF800) >> 11; + } else { + output_pixels[i] = color_from_gb_pixel(input_pixels[i]); + } + } + + extapp_pushRect((NW_LCD_WIDTH - LCD_WIDTH) / 2, (NW_LCD_HEIGHT - LCD_HEIGHT) / 2 + line, LCD_WIDTH, 1, output_pixels); +} + +static void lcd_draw_line_maximized(struct gb_s * gb, const uint8_t * input_pixels, const uint_fast8_t line) { + // Nearest neighbor scaling of a 160x144 texture to a 320x240 resolution + // Horizontally, we just double + uint16_t output_pixels[LCD_WIDTH]; + uint16_t zoomPixels[2 * LCD_WIDTH]; + for (int i = 0; i < LCD_WIDTH; i++) { + if (gb->cgb.cgbMode) { + output_pixels[i] = gb->cgb.fixPalette[input_pixels[i]]; + output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x3E0) << 1 | (output_pixels[i] & 0x7C00) >> 10; + output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x7E0) | (output_pixels[i] & 0xF800) >> 11; + } else { + output_pixels[i] = color_from_gb_pixel(input_pixels[i]); + } + + uint16_t color = output_pixels[i]; + + zoomPixels[2 * i] = color; + zoomPixels[2 * i + 1] = color; + } + // Vertically, we want to scale by a 5/3 ratio. So we need to make 5 lines out of three: we double two lines out of three. + uint16_t y = (5 * line) / 3; + extapp_pushRect(0, y, 2 * LCD_WIDTH, 1, zoomPixels); + if (line % 3 != 0) { + extapp_pushRect(0, y + 1, 2 * LCD_WIDTH, 1, zoomPixels); + } +} + +static void lcd_draw_line_maximized_ratio(struct gb_s * gb, const uint8_t * input_pixels, const uint_fast8_t line) { + // Nearest neighbor scaling of a 160x144 texture to a 266x240 resolution (to keep the ratio) + // Horizontally, we multiply by 1.66 (160*1.66 = 266) + uint16_t output_pixels[LCD_WIDTH]; + uint16_t zoomPixels[266]; + for (int i = 0; i < LCD_WIDTH; i++) { + if (gb->cgb.cgbMode) { + output_pixels[i] = gb->cgb.fixPalette[input_pixels[i]]; + output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x3E0) << 1 | (output_pixels[i] & 0x7C00) >> 10; + output_pixels[i] = (output_pixels[i] & 0x1F) << 11 | (output_pixels[i] & 0x7E0) | (output_pixels[i] & 0xF800) >> 11; + } else { + output_pixels[i] = color_from_gb_pixel(input_pixels[i]); + } + + uint16_t color = output_pixels[i]; + + zoomPixels[166 * i / 100] = color; + zoomPixels[166 * i / 100 + 1] = color; + zoomPixels[166 * i / 100 + 2] = color; + } + // We can't use floats, so we use a fixed point representation + // Vertically, we want to scale by a 5/3 ratio. So we need to make 5 lines out of three: we double two lines out of three. + uint16_t y = (5 * line) / 3; + extapp_pushRect((NW_LCD_WIDTH - 266) / 2, y, 266, 1, zoomPixels); + if (line % 3 != 0) { + extapp_pushRect((NW_LCD_WIDTH - 266) / 2, y + 1, 266, 1, zoomPixels); + } +} + +enum save_status_e { + SAVE_READ_OK, + SAVE_WRITE_OK, + SAVE_READ_ERR, + SAVE_WRITE_ERR, + SAVE_COMPRESS_ERR, + SAVE_NODISP +}; +static enum save_status_e saveMessage = SAVE_NODISP; + +char* read_save_file(const char* name, size_t size) { + char* save_name = malloc(strlen(name) + 3); + strcpy(save_name, name); + osd_newextension(save_name, ".gbs"); + + char* output = malloc(size); + + if (extapp_fileExists(save_name, EXTAPP_RAM_FILE_SYSTEM)) { + size_t file_len = 0; + const char* save_content = extapp_fileRead(save_name, &file_len, EXTAPP_RAM_FILE_SYSTEM); + int error = LZ4_decompress_safe(save_content, output, file_len, size); + + // Handling corrupted save. + if (error <= 0) { + memset(output, 0xFF, size); + extapp_fileErase(save_name, EXTAPP_RAM_FILE_SYSTEM); + saveMessage = SAVE_READ_ERR; + } else { + saveMessage = SAVE_READ_OK; + } + } else { + memset(output, 0xFF, size); + } + + free(save_name); + + return output; +} + +void write_save_file(const char* name, char* data, size_t size) { + char* save_name = malloc(strlen(name) + 3); + strcpy(save_name, name); + osd_newextension(save_name, ".gbs"); + + char* output = malloc((size_t) MAX_SCRIPTSTORE_SIZE); + + int compressed_size = LZ4_compress_default(data, output, size, MAX_SCRIPTSTORE_SIZE); + + if (compressed_size > 0) { + if (extapp_fileWrite(save_name, output, compressed_size, EXTAPP_RAM_FILE_SYSTEM)) { + saveMessage = SAVE_WRITE_OK; + } else { + saveMessage = SAVE_WRITE_ERR; + } + } else { + saveMessage = SAVE_COMPRESS_ERR; + } + + free(save_name); + free(output); +} + +static bool wasSavePressed = false; +static bool wasMSpFPressed = false; +static uint8_t saveCooldown = 0; +static bool MSpFfCounter = false; + +void extapp_main() { + struct gb_s gb; + enum gb_init_error_e gb_ret; + struct priv_t priv = { + .rom = NULL, + .cart_ram = NULL + }; + enum gb_init_error_e ret; + + #if DUMMY_ROM + priv.rom = DUMMY_ROM_VAR(DUMMY_ROM_NAME); + const char * file_name = DUMMY_ROM_FILE(DUMMY_ROM_NAME); + #else + const char * file_name = select_rom(); + if (!file_name) + return; + + size_t file_len = 0; + priv.rom = (const uint8_t*) extapp_fileRead(file_name, &file_len, EXTAPP_FLASH_FILE_SYSTEM); + #endif + + // Alloc internal RAM. + gb.wram = malloc(WRAM_SIZE); + gb.vram = malloc(VRAM_SIZE); + gb.hram_io = malloc(HRAM_IO_SIZE); + gb.oam = malloc(OAM_SIZE); + + gb_ret = gb_init(&gb, &gb_rom_read, &gb_cart_ram_read, &gb_cart_ram_write, &gb_error, &priv); + + // TODO: Handle init errors. + switch(gb_ret) { + case GB_INIT_NO_ERROR: + break; + default: + return; + } + + // Alloc and init save RAM. + size_t save_size = gb_get_save_size(&gb); + priv.cart_ram = read_save_file(file_name, save_size); + saveCooldown = SAVE_COOLDOWN; + + // Init LCD + gb_init_lcd(&gb, &lcd_draw_line_centered); + + extapp_pushRectUniform(0, 0, NW_LCD_WIDTH, NW_LCD_HEIGHT, 0); + + running = true; + while(running) { + uint64_t start = extapp_millis(); + uint64_t kb = extapp_scanKeyboard(); + + gb.direct.joypad_bits.a = (kb & SCANCODE_Back) ? 0 : 1; + gb.direct.joypad_bits.b = (kb & SCANCODE_OK) ? 0 : 1; + gb.direct.joypad_bits.select = (kb & ((uint64_t)1 << 8)) ? 0 : 1; + gb.direct.joypad_bits.start = (kb & SCANCODE_Home) ? 0 : 1; + gb.direct.joypad_bits.up = (kb & SCANCODE_Up) ? 0 : 1; + gb.direct.joypad_bits.right = (kb & SCANCODE_Right) ? 0 : 1; + gb.direct.joypad_bits.left = (kb & SCANCODE_Left) ? 0 : 1; + gb.direct.joypad_bits.down = (kb & SCANCODE_Down) ? 0 : 1; + + if (kb & SCANCODE_Backspace) + gb_reset(&gb); + if (kb & SCANCODE_Toolbox) { + if (!wasSavePressed && saveCooldown == 0) { + write_save_file(file_name, priv.cart_ram, save_size); + saveCooldown = SAVE_COOLDOWN; + wasSavePressed = true; + } + } else if (wasSavePressed) { + wasSavePressed = false; + } + + if (kb & SCANCODE_Alpha) { + if (!wasMSpFPressed) { + MSpFfCounter = !MSpFfCounter; + wasMSpFPressed = true; + extapp_pushRectUniform(0, NW_LCD_HEIGHT / 2 + LCD_HEIGHT / 2, NW_LCD_WIDTH, NW_LCD_HEIGHT - (NW_LCD_HEIGHT / 2 + LCD_HEIGHT / 2), 0); + } + } else if (wasMSpFPressed) { + wasMSpFPressed = false; + } + + if (kb & SCANCODE_Zero) { + running = false; + break; + } + + if (kb & SCANCODE_Plus) { + gb.display.lcd_draw_line = lcd_draw_line_maximized; + } + if (kb & SCANCODE_Minus) { + gb.display.lcd_draw_line = lcd_draw_line_centered; + extapp_pushRectUniform(0, 0, NW_LCD_WIDTH, NW_LCD_HEIGHT, 0); + } + if (kb & SCANCODE_Multiplication) { + gb.display.lcd_draw_line = lcd_draw_line_maximized_ratio; + extapp_pushRectUniform(0, 0, NW_LCD_WIDTH, NW_LCD_HEIGHT, 0); + } + + if (kb & SCANCODE_One) { + palette = palette_peanut_GB; + } + if (kb & SCANCODE_Two) { + palette = palette_original; + } + if (kb & SCANCODE_Three) { + palette = palette_gray; + } + if (kb & SCANCODE_Four) { + palette = palette_gray_negative; + } + + gb.gb_frame = 0; + int i = 0; + for(i = 0; !gb.gb_frame && i < 32000; i++) + __gb_step_cpu(&gb); + + if (saveCooldown > 1) { + saveCooldown--; + switch(saveMessage) { + case SAVE_READ_OK: + extapp_drawTextSmall("Loaded save!", 10, NW_LCD_HEIGHT - 30, 65535, 0, false); + break; + case SAVE_READ_ERR: + extapp_drawTextSmall("Error while loading save!", 10, NW_LCD_HEIGHT - 30, 65535, 0, false); + break; + case SAVE_WRITE_OK: + extapp_drawTextSmall("Saved!", 10, NW_LCD_HEIGHT - 30, 65535, 0, false); + break; + case SAVE_WRITE_ERR: + extapp_drawTextSmall("Error while writing save!", 10, NW_LCD_HEIGHT - 30, 65535, 0, false); + break; + case SAVE_COMPRESS_ERR: + extapp_drawTextSmall("Error while compressing save!", 10, NW_LCD_HEIGHT - 30, 65535, 0, false); + break; + default: + break; + } + } else if (saveCooldown == 1) { + saveCooldown--; + extapp_pushRectUniform(0, NW_LCD_HEIGHT / 2 + LCD_HEIGHT / 2, NW_LCD_WIDTH, NW_LCD_HEIGHT - (NW_LCD_HEIGHT / 2 + LCD_HEIGHT / 2), 0); + } + uint64_t end = extapp_millis(); + + if (MSpFfCounter) { + uint16_t MSpF = (uint16_t)(end - start); + char buffer[30]; + sprintf(buffer, "%d ms/f", MSpF); + extapp_drawTextSmall(buffer, 2, NW_LCD_HEIGHT - 10, 65535, 0, false); + } + + } + + + free(gb.wram); + free(gb.vram); + free(gb.hram_io); + free(gb.oam); + + write_save_file(file_name, priv.cart_ram, save_size); + free(priv.cart_ram); +} diff --git a/apps/Peanut-GBC/peanut_gb.h b/apps/Peanut-GBC/peanut_gb.h new file mode 100644 index 00000000..67499922 --- /dev/null +++ b/apps/Peanut-GBC/peanut_gb.h @@ -0,0 +1,4353 @@ +/** + * 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 three 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 /* Required for qsort and abort */ +#include /* Required for int types */ +#include /* Required for memset */ +#include /* 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 1 +#endif + +/* Adds more code to improve LCD rendering accuracy. */ +#ifndef PEANUT_GB_HIGH_LCD_ACCURACY +# define PEANUT_GB_HIGH_LCD_ACCURACY 1 +#endif + +/* Use intrinsic functions. This may produce smaller and faster code. */ +#ifndef PEANUT_GB_USE_INTRINSICS +# define PEANUT_GB_USE_INTRINSICS 1 +#endif + +#ifndef PEANUT_FULL_GBC_SUPPORT +# define PEANUT_FULL_GBC_SUPPORT 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 */ +#if PEANUT_FULL_GBC_SUPPORT +#define WRAM_SIZE 0x8000 +#define VRAM_SIZE 0x4000 +#else +#define WRAM_SIZE 0x2000 +#define VRAM_SIZE 0x2000 +#endif +#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 +#define SERIAL_CYCLES_1KB (SERIAL_CYCLES/1ul) +#define SERIAL_CYCLES_2KB (SERIAL_CYCLES/2ul) +#define SERIAL_CYCLES_32KB (SERIAL_CYCLES/32ul) +#define SERIAL_CYCLES_64KB (SERIAL_CYCLES/64ul) + +/* Calculating VSYNC. */ +#define DMG_CLOCK_FREQ 4194304.0 +#define SCREEN_REFRESH_CYCLES 70224.0 +#define VERTICAL_SYNC (DMG_CLOCK_FREQ/SCREEN_REFRESH_CYCLES) + +/* 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 **/ +/* PPU cycles through modes every 456 cycles. */ +#define LCD_LINE_CYCLES 456 +/* Mode 0 starts on cycle 372. */ +#define LCD_MODE_0_CYCLES 372 +/* Mode 2 starts on cycle 204. */ +#define LCD_MODE_2_CYCLES 204 +/* Mode 3 starts on cycle 284. */ +#define LCD_MODE_3_CYCLES 284 +/* There are 154 scanlines. LY < 154. */ +#define LCD_VERT_LINES 154 +#define LCD_WIDTH 160 +#define LCD_HEIGHT 144 + +/* 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 +#if PEANUT_FULL_GBC_SUPPORT +#define OBJ_BANK 0x08 +#define OBJ_CGB_PALETTE 0x07 +#endif + +#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])) + +#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 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 +# 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_bits.c = PGB_INTRIN_SBC(gb->cpu_reg.a,r,cin,temp);\ + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f_bits.n = 1; \ + gb->cpu_reg.f_bits.z = (temp == 0x00); \ + gb->cpu_reg.a = temp; \ + } + +# define PGB_INSTR_CP_R8(r) \ + { \ + uint8_t temp; \ + gb->cpu_reg.f_bits.c = PGB_INTRIN_SBC(gb->cpu_reg.a,r,0,temp); \ + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f_bits.n = 1; \ + gb->cpu_reg.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_bits.c = (temp & 0xFF00) ? 1 : 0; \ + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f_bits.n = 1; \ + gb->cpu_reg.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_bits.c = (temp & 0xFF00) ? 1 : 0; \ + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f_bits.n = 1; \ + gb->cpu_reg.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_bits.c = PGB_INTRIN_ADC(gb->cpu_reg.a,r,cin,temp);\ + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f_bits.n = 0; \ + gb->cpu_reg.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_bits.c = (temp & 0xFF00) ? 1 : 0; \ + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a ^ r ^ temp) & 0x10) > 0; \ + gb->cpu_reg.f_bits.n = 0; \ + gb->cpu_reg.f_bits.z = ((temp & 0xFF) == 0x00); \ + gb->cpu_reg.a = (temp & 0xFF); \ + } +#endif /* PGB_INTRIN_ADC */ + +#define PGB_INSTR_DEC_R8(r) \ + r--; \ + gb->cpu_reg.f_bits.h = ((r & 0x0F) == 0x0F); \ + gb->cpu_reg.f_bits.n = 1; \ + gb->cpu_reg.f_bits.z = (r == 0x00); + +#define PGB_INSTR_XOR_R8(r) \ + gb->cpu_reg.a ^= r; \ + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); \ + gb->cpu_reg.f_bits.n = 0; \ + gb->cpu_reg.f_bits.h = 0; \ + gb->cpu_reg.f_bits.c = 0; + +#define PGB_INSTR_OR_R8(r) \ + gb->cpu_reg.a |= r; \ + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); \ + gb->cpu_reg.f_bits.n = 0; \ + gb->cpu_reg.f_bits.h = 0; \ + gb->cpu_reg.f_bits.c = 0; + +#define PGB_INSTR_AND_R8(r) \ + gb->cpu_reg.a &= r; \ + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); \ + gb->cpu_reg.f_bits.n = 0; \ + gb->cpu_reg.f_bits.h = 1; \ + gb->cpu_reg.f_bits.c = 0; + +#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. */ + struct + { + 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 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 */ +}; + +#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, + GB_INVALID_READ, + GB_INVALID_WRITE, + GB_HALT_FOREVER, + + GB_INVALID_MAX +}; + +/** + * 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 +}; + +/** + * 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 +}; + +/** + * 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 + { + uint8_t gb_halt : 1; + uint8_t gb_ime : 1; + uint8_t gb_frame : 1; /* New frame drawn. */ + uint8_t lcd_blank : 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 + { + struct + { + uint8_t sec; + uint8_t min; + uint8_t hour; + uint8_t yday; + uint8_t high; + } rtc_bits; + uint8_t cart_rtc[5]; + }; + + 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; + uint8_t* vram; + uint8_t* oam; + uint8_t* hram_io; + + 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. */ + uint8_t frame_skip_count : 1; + uint8_t interlace_count : 1; + } display; + +#if PEANUT_FULL_GBC_SUPPORT + /* Game Boy Color Mode*/ + struct { + uint8_t cgbMode; + uint8_t doubleSpeed; + uint8_t doubleSpeedPrep; + uint8_t wramBank; + uint16_t wramBankOffset; + uint8_t vramBank; + uint16_t vramBankOffset; + uint16_t fixPalette[0x40]; //BG then OAM palettes fixed for the screen + uint8_t OAMPalette[0x40]; + uint8_t BGPalette[0x40]; + uint8_t OAMPaletteID; + uint8_t BGPaletteID; + uint8_t OAMPaletteInc; + uint8_t BGPaletteInc; + uint8_t dmaActive; + uint8_t dmaMode; + uint8_t dmaSize; + uint16_t dmaSource; + uint16_t dmaDest; + } cgb; +#endif + + /** + * 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). + */ + uint8_t interlace : 1; + uint8_t frame_skip : 1; + + union + { + struct + { + uint8_t a : 1; + uint8_t b : 1; + uint8_t select : 1; + uint8_t start : 1; + uint8_t right : 1; + uint8_t left : 1; + uint8_t up : 1; + uint8_t 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_BOOT 0x50 +#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_BANK 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_SEARCH_OAM 2 +#define IO_STAT_MODE_SEARCH_TRANSFER 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_BANK is only set to 1 if gb->gb_bootrom_read was not NULL + * on reset. */ + if(gb->hram_io[IO_BANK] == 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: +#if PEANUT_FULL_GBC_SUPPORT + return gb->vram[addr - gb->cgb.vramBankOffset]; +#else + return gb->vram[addr - VRAM_ADDR]; +#endif + case 0xA: + case 0xB: + if(gb->mbc == 3 && gb->cart_ram_bank >= 0x08) + { + return gb->cart_rtc[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: +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode && addr >= WRAM_1_ADDR) + return gb->wram[addr - gb->cgb.wramBankOffset]; +#endif + return gb->wram[addr - WRAM_0_ADDR]; + + case 0xE: + return gb->wram[addr - ECHO_ADDR]; + + case 0xF: + if(addr < OAM_ADDR) +#if PEANUT_FULL_GBC_SUPPORT + return gb->wram[(addr - 0x2000) - gb->cgb.wramBankOffset]; +#else + return gb->wram[addr - ECHO_ADDR]; +#endif + + 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 + } + +#if PEANUT_FULL_GBC_SUPPORT + /* IO and Interrupts. */ + switch (addr & 0xFF) + { + /* Speed Switch*/ + case 0x4D: + return (gb->cgb.doubleSpeed << 7) + gb->cgb.doubleSpeedPrep; + /* CGB VRAM Bank*/ + case 0x4F: + return gb->cgb.vramBank | 0xFE; + /* CGB DMA*/ + case 0x51: + return (gb->cgb.dmaSource >> 8); + case 0x52: + return (gb->cgb.dmaSource & 0xF0); + case 0x53: + return (gb->cgb.dmaDest >> 8); + case 0x54: + return (gb->cgb.dmaDest & 0xF0); + case 0x55: + return (gb->cgb.dmaActive << 7) | (gb->cgb.dmaSize - 1); + /* IR Register*/ + case 0x56: + return gb->hram_io[0x56]; + /* CGB BG Palette Index*/ + case 0x68: + return (gb->cgb.BGPaletteID & 0x3F) + (gb->cgb.BGPaletteInc << 7); + /* CGB BG Palette*/ + case 0x69: + return gb->cgb.BGPalette[(gb->cgb.BGPaletteID & 0x3F)]; + /* CGB OAM Palette Index*/ + case 0x6A: + return (gb->cgb.OAMPaletteID & 0x3F) + (gb->cgb.OAMPaletteInc << 7); + /* CGB OAM Palette*/ + case 0x6B: + return gb->cgb.OAMPalette[(gb->cgb.OAMPaletteID & 0x3F)]; + /* CGB WRAM Bank*/ + case 0x70: + return gb->cgb.wramBank; + default: +#endif + /* HRAM */ + if(addr >= IO_ADDR) + return gb->hram_io[addr - IO_ADDR]; +#if PEANUT_FULL_GBC_SUPPORT + } +#endif + } + + + /* 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 & 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; + else if(gb->mbc == 5) + gb->cart_ram_bank = (val & 0x0F); + + return; + + case 0x6: + case 0x7: + gb->cart_mode_select = (val & 1); + return; + + case 0x8: + case 0x9: +#if PEANUT_FULL_GBC_SUPPORT + gb->vram[addr - gb->cgb.vramBankOffset] = val; +#else + gb->vram[addr - VRAM_ADDR] = val; +#endif + return; + + case 0xA: + case 0xB: + if(gb->mbc == 3 && gb->cart_ram_bank >= 0x08) + { + gb->cart_rtc[gb->cart_ram_bank - 0x08] = val; + } + /* 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; + gb->gb_cart_ram_write(gb, addr, val); + } + else if(gb->cart_mode_select && + 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: +#if PEANUT_FULL_GBC_SUPPORT + gb->wram[addr - gb->cgb.wramBankOffset] = val; +#else + gb->wram[addr - WRAM_1_ADDR + WRAM_BANK_SIZE] = val; +#endif + return; + + case 0xE: + gb->wram[addr - ECHO_ADDR] = val; + return; + + case 0xF: + if(addr < OAM_ADDR) + { +#if PEANUT_FULL_GBC_SUPPORT + gb->wram[(addr - 0x2000) - gb->cgb.wramBankOffset] = val; +#else + gb->wram[addr - ECHO_ADDR] = val; +#endif + 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; + } +#if PEANUT_FULL_GBC_SUPPORT + uint16_t fixPaletteTemp; +#endif + /* 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 = 1; + } + /* 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; + /* Reset LCD timer. */ + gb->counter.lcd_count = 0; + } + return; + } + + case 0x41: + gb->hram_io[IO_STAT] = (val & STAT_USER_BITS) | (gb->hram_io[IO_STAT] & STAT_MODE); + 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; +#if PEANUT_FULL_GBC_SUPPORT + dma_addr = (uint_fast16_t)(val % 0xF1) << 8; + gb->hram_io[IO_DMA] = (val % 0xF1); +#else + dma_addr = (uint_fast16_t)val << 8; + gb->hram_io[IO_DMA] = val; +#endif + 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; + +#if PEANUT_FULL_GBC_SUPPORT + /* Prepare Speed Switch*/ + case 0x4D: + gb->cgb.doubleSpeedPrep = val & 1; + return; + + /* CGB VRAM Bank*/ + case 0x4F: + gb->cgb.vramBank = val & 0x01; + if(gb->cgb.cgbMode) gb->cgb.vramBankOffset = VRAM_ADDR - (gb->cgb.vramBank << 13); + return; +#endif + /* Turn off boot ROM */ + case 0x50: + gb->hram_io[IO_BANK] = val; + return; +#if PEANUT_FULL_GBC_SUPPORT + /* DMA Register */ + case 0x51: + gb->cgb.dmaSource = (gb->cgb.dmaSource & 0xFF) + (val << 8); + return; + case 0x52: + gb->cgb.dmaSource = (gb->cgb.dmaSource & 0xFF00) + val; + return; + case 0x53: + gb->cgb.dmaDest = (gb->cgb.dmaDest & 0xFF) + (val << 8); + return; + case 0x54: + gb->cgb.dmaDest = (gb->cgb.dmaDest & 0xFF00) + val; + return; + + /* DMA Register*/ + case 0x55: + gb->cgb.dmaSize = (val & 0x7F) + 1; + gb->cgb.dmaMode = val >> 7; + //DMA GBC + if(gb->cgb.dmaActive) + { // Only transfer if dma is not active (=1) otherwise treat it as a termination + if(gb->cgb.cgbMode && (!gb->cgb.dmaMode)) + { + for (int i = 0; i < (gb->cgb.dmaSize << 4); i++) + { + __gb_write(gb, ((gb->cgb.dmaDest & 0x1FF0) | 0x8000) + i, __gb_read(gb, (gb->cgb.dmaSource & 0xFFF0) + i)); + } + gb->cgb.dmaSource += (gb->cgb.dmaSize << 4); + gb->cgb.dmaDest += (gb->cgb.dmaSize << 4); + gb->cgb.dmaSize = 0; + } + } + gb->cgb.dmaActive = gb->cgb.dmaMode ^ 1; // set active if it's an HBlank DMA + return; + + /* IR Register*/ + case 0x56: + gb->hram_io[0x56] = val; + return; + + /* CGB BG Palette Index*/ + case 0x68: + gb->cgb.BGPaletteID = val & 0x3F; + gb->cgb.BGPaletteInc = val >> 7; + return; + + /* CGB BG Palette*/ + case 0x69: + gb->cgb.BGPalette[(gb->cgb.BGPaletteID & 0x3F)] = val; + fixPaletteTemp = (gb->cgb.BGPalette[(gb->cgb.BGPaletteID & 0x3E) + 1] << 8) + (gb->cgb.BGPalette[(gb->cgb.BGPaletteID & 0x3E)]); + gb->cgb.fixPalette[((gb->cgb.BGPaletteID & 0x3E) >> 1)] = ((fixPaletteTemp & 0x7C00) >> 10) | (fixPaletteTemp & 0x03E0) | ((fixPaletteTemp & 0x001F) << 10); // swap Red and Blue + if(gb->cgb.BGPaletteInc) gb->cgb.BGPaletteID = (++gb->cgb.BGPaletteID) & 0x3F; + return; + + /* CGB OAM Palette Index*/ + case 0x6A: + gb->cgb.OAMPaletteID = val & 0x3F; + gb->cgb.OAMPaletteInc = val >> 7; + return; + + /* CGB OAM Palette*/ + case 0x6B: + gb->cgb.OAMPalette[(gb->cgb.OAMPaletteID & 0x3F)] = val; + fixPaletteTemp = (gb->cgb.OAMPalette[(gb->cgb.OAMPaletteID & 0x3E) + 1] << 8) + (gb->cgb.OAMPalette[(gb->cgb.OAMPaletteID & 0x3E)]); + gb->cgb.fixPalette[0x20 + ((gb->cgb.OAMPaletteID & 0x3E) >> 1)] = ((fixPaletteTemp & 0x7C00) >> 10) | (fixPaletteTemp & 0x03E0) | ((fixPaletteTemp & 0x001F) << 10); // swap Red and Blue + if(gb->cgb.OAMPaletteInc) gb->cgb.OAMPaletteID = (++gb->cgb.OAMPaletteID) & 0x3F; + return; + + /* CGB WRAM Bank*/ + case 0x70: + gb->cgb.wramBank = val; + gb->cgb.wramBankOffset = WRAM_1_ADDR - (1 << 12); + if(gb->cgb.cgbMode && (gb->cgb.wramBank & 7) > 0) gb->cgb.wramBankOffset = WRAM_1_ADDR - ((gb->cgb.wramBank & 7) << 12); + return; +#endif + + /* 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; + } + + /* TODO: Find out WTF this is doing. */ + 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_bits.c << 7) : (temp << 7); + gb->cpu_reg.f_bits.z = (val == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = (temp & 0x01); + } + else /* RLC R / RL R */ + { + uint8_t temp = val; + val = (val << 1); + val |= cbop ? gb->cpu_reg.f_bits.c : (temp >> 7); + gb->cpu_reg.f_bits.z = (val == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = (temp >> 7); + } + + break; + + case 0x2: + if(d) /* SRA R */ + { + gb->cpu_reg.f_bits.c = val & 0x01; + val = (val >> 1) | (val & 0x80); + gb->cpu_reg.f_bits.z = (val == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + } + else /* SLA R */ + { + gb->cpu_reg.f_bits.c = (val >> 7); + val = val << 1; + gb->cpu_reg.f_bits.z = (val == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + } + + break; + + case 0x3: + if(d) /* SRL R */ + { + gb->cpu_reg.f_bits.c = val & 0x01; + val = val >> 1; + gb->cpu_reg.f_bits.z = (val == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + } + else /* SWAP R */ + { + uint8_t temp = (val >> 4) & 0x0F; + temp |= (val << 4) & 0xF0; + val = temp; + gb->cpu_reg.f_bits.z = (val == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; + } + + break; + } + + break; + + case 0x1: /* BIT B, R */ + gb->cpu_reg.f_bits.z = !((val >> b) & 0x1); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.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 void *in1, const void *in2) +{ + const struct sprite_data *sd1, *sd2; + int x_res; + + sd1 = (struct sprite_data *)in1; + sd2 = (struct sprite_data *)in2; + 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 PEANUT_FULL_GBC_SUPPORT + uint8_t pixelsPrio[160] = {0}; //do these pixels have priority over OAM? +#endif + /* If interlaced mode is activated, check if we need to draw the current + * line. */ + if(gb->direct.interlace) + { + if((gb->display.interlace_count == 0 + && (gb->hram_io[IO_LY] & 1) == 0) + || (gb->display.interlace_count == 1 + && (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 PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode || gb->hram_io[IO_LCDC] & LCDC_BG_ENABLE) +#else + if(gb->hram_io[IO_LCDC] & LCDC_BG_ENABLE) +#endif + { + 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)]; +#if PEANUT_FULL_GBC_SUPPORT + uint8_t idxAtt = gb->vram[bg_map + (bg_x >> 3) + 0x2000]; +#endif + /* 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; + +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode) + { + if(idxAtt & 0x08) tile += 0x2000; //VRAM bank 2 + if(idxAtt & 0x40) tile += 2 * (7 - py); + } + if(!(idxAtt & 0x40)) + { + tile += 2 * py; + } + + /* fetch first tile */ + if(gb->cgb.cgbMode && (idxAtt & 0x20)) + { //Horizantal Flip + t1 = gb->vram[tile] << px; + t2 = gb->vram[tile + 1] << px; + } + else + { + t1 = gb->vram[tile] >> px; + t2 = gb->vram[tile + 1] >> px; + } +#else + tile += 2 * py; + + /* fetch first tile */ + t1 = gb->vram[tile] >> px; + t2 = gb->vram[tile + 1] >> px; +#endif + 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 PEANUT_FULL_GBC_SUPPORT + idxAtt = gb->vram[bg_map + (bg_x >> 3) + 0x2000]; +#endif + if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + tile = VRAM_TILES_1 + idx * 0x10; + else + tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; + +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode) + { + if(idxAtt & 0x08) tile += 0x2000; //VRAM bank 2 + if(idxAtt & 0x40) tile += 2 * (7 - py); + } + if(!(idxAtt & 0x40)) + { + tile += 2 * py; + } +#else + tile += 2 * py; +#endif + t1 = gb->vram[tile]; + t2 = gb->vram[tile + 1]; + } + + /* copy background */ +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode && (idxAtt & 0x20)) + { //Horizantal Flip + c = (((t1 & 0x80) >> 1) | (t2 & 0x80)) >> 6; + pixels[disp_x] = ((idxAtt & 0x07) << 2) + c; + pixelsPrio[disp_x] = (idxAtt >> 7); + t1 = t1 << 1; + t2 = t2 << 1; + } + else + { + c = (t1 & 0x1) | ((t2 & 0x1) << 1); + if(gb->cgb.cgbMode) + { + pixels[disp_x] = ((idxAtt & 0x07) << 2) + c; + pixelsPrio[disp_x] = (idxAtt >> 7); + } + else + { + 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; + } +#else + 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; +#endif + 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 PEANUT_FULL_GBC_SUPPORT + uint8_t idxAtt = gb->vram[win_line + (win_x >> 3) + 0x2000]; +#endif + + if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + tile = VRAM_TILES_1 + idx * 0x10; + else + tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; + +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode) + { + if(idxAtt & 0x08) tile += 0x2000; //VRAM bank 2 + if(idxAtt & 0x40) tile += 2 * (7 - py); + } + if(!(idxAtt & 0x40)) + { + tile += 2 * py; + } + + // fetch first tile + if(gb->cgb.cgbMode && (idxAtt & 0x20)) + { //Horizantal Flip + t1 = gb->vram[tile] << px; + t2 = gb->vram[tile + 1] << px; + } + else + { + t1 = gb->vram[tile] >> px; + t2 = gb->vram[tile + 1] >> px; + } +#else + tile += 2 * py; + + // fetch first tile + t1 = gb->vram[tile] >> px; + t2 = gb->vram[tile + 1] >> px; +#endif + // 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 PEANUT_FULL_GBC_SUPPORT + idxAtt = gb->vram[win_line + (win_x >> 3) + 0x2000]; +#endif + + if(gb->hram_io[IO_LCDC] & LCDC_TILE_SELECT) + tile = VRAM_TILES_1 + idx * 0x10; + else + tile = VRAM_TILES_2 + ((idx + 0x80) % 0x100) * 0x10; + +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode) + { + if(idxAtt & 0x08) tile += 0x2000; //VRAM bank 2 + if(idxAtt & 0x40) tile += 2 * (7 - py); + } + if(!(idxAtt & 0x40)) + { + tile += 2 * py; + } +#else + tile += 2 * py; +#endif + t1 = gb->vram[tile]; + t2 = gb->vram[tile + 1]; + } + + // copy window +#if PEANUT_FULL_GBC_SUPPORT + if(idxAtt & 0x20) + { //Horizantal Flip + c = (((t1 & 0x80) >> 1) | (t2 & 0x80)) >> 6; + pixels[disp_x] = ((idxAtt & 0x07) << 2) + c; + pixelsPrio[disp_x] = (idxAtt >> 7); + t1 = t1 << 1; + t2 = t2 << 1; + } + else + { + c = (t1 & 0x1) | ((t2 & 0x1) << 1); + if(gb->cgb.cgbMode) + { + pixels[disp_x] = ((idxAtt & 0x07) << 2) + c; + pixelsPrio[disp_x] = (idxAtt >> 7); + } + else + { + 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; + } +#else + 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; +#endif + 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[NUM_SPRITES]; + + /* 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 < PEANUT_GB_ARRAYSIZE(sprites_to_render); + 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; + + + sprites_to_render[number_of_sprites].sprite_number = sprite_number; + sprites_to_render[number_of_sprites].x = OX; + number_of_sprites++; + } +#if PEANUT_FULL_GBC_SUPPORT + if(!gb->cgb.cgbMode) + { +#endif + /* If maximum number of sprites reached, prioritise X + * coordinate and object location in OAM. */ + qsort(&sprites_to_render[0], number_of_sprites, + sizeof(sprites_to_render[0]), compare_sprites); +#if PEANUT_FULL_GBC_SUPPORT + } +#endif + if(number_of_sprites > MAX_SPRITES_LINE) + number_of_sprites = MAX_SPRITES_LINE; +#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 +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode) + { + t1 = gb->vram[((OF & OBJ_BANK) << 10) + VRAM_TILES_1 + OT * 0x10 + 2 * py]; + t2 = gb->vram[((OF & OBJ_BANK) << 10) + VRAM_TILES_1 + OT * 0x10 + 2 * py + 1]; + } + else +#endif + { + 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 PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode) + { + uint8_t isBackgroundDisabled = c && !(gb->hram_io[IO_LCDC] & LCDC_BG_ENABLE); + uint8_t isPixelPriorityNonConflicting = c && + !(pixelsPrio[disp_x] && (pixels[disp_x] & 0x3)) && + !((OF & OBJ_PRIORITY) && (pixels[disp_x] & 0x3)); + + if(isBackgroundDisabled || isPixelPriorityNonConflicting) + { + /* Set pixel colour. */ + pixels[disp_x] = ((OF & OBJ_CGB_PALETTE) << 2) + c + 0x20; // add 0x20 to differentiate from BG + } + } + else +#endif + 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 +#if PEANUT_FULL_GBC_SUPPORT + /* Deselect BG palette. */ + pixels[disp_x] &= ~LCD_PALETTE_BG; +#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 occured by the + * time we reach here, becuase 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 = 0; + + if(!gb->gb_ime) + break; + + /* Disable interrupts */ + gb->gb_ime = 0; + + /* 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 */ + gb->cpu_reg.bc.bytes.b++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.bc.bytes.b == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.bc.bytes.b & 0x0F) == 0x00); + 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_bits.z = 0; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.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_bits.n = 0; + gb->cpu_reg.f_bits.h = + (temp ^ gb->cpu_reg.hl.reg ^ gb->cpu_reg.bc.reg) & 0x1000 ? 1 : 0; + gb->cpu_reg.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 */ + gb->cpu_reg.bc.bytes.c++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.bc.bytes.c == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.bc.bytes.c & 0x0F) == 0x00); + 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_bits.c = gb->cpu_reg.a & 0x01; + gb->cpu_reg.a = (gb->cpu_reg.a >> 1) | (gb->cpu_reg.a << 7); + gb->cpu_reg.f_bits.z = 0; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + break; + + case 0x10: /* STOP */ + //gb->gb_halt = 1; +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode & gb->cgb.doubleSpeedPrep) + { + gb->cgb.doubleSpeedPrep = 0; + gb->cgb.doubleSpeed ^= 1; + } +#endif + 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 */ + gb->cpu_reg.de.bytes.d++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.de.bytes.d == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.de.bytes.d & 0x0F) == 0x00); + 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_bits.c; + gb->cpu_reg.f_bits.z = 0; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.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_bits.n = 0; + gb->cpu_reg.f_bits.h = + (temp ^ gb->cpu_reg.hl.reg ^ gb->cpu_reg.de.reg) & 0x1000 ? 1 : 0; + gb->cpu_reg.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 */ + gb->cpu_reg.de.bytes.e++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.de.bytes.e == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.de.bytes.e & 0x0F) == 0x00); + 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_bits.c << 7); + gb->cpu_reg.f_bits.z = 0; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = temp & 0x1; + break; + } + + case 0x20: /* JR NZ, imm */ + if(!gb->cpu_reg.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 */ + gb->cpu_reg.hl.bytes.h++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.hl.bytes.h == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.hl.bytes.h & 0x0F) == 0x00); + 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_bits.n) + { + if(gb->cpu_reg.f_bits.h) + a = (a - 0x06) & 0xFF; + + if(gb->cpu_reg.f_bits.c) + a -= 0x60; + } + else + { + if(gb->cpu_reg.f_bits.h || (a & 0x0F) > 9) + a += 0x06; + + if(gb->cpu_reg.f_bits.c || a > 0x9F) + a += 0x60; + } + + if((a & 0x100) == 0x100) + gb->cpu_reg.f_bits.c = 1; + + gb->cpu_reg.a = a; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0); + gb->cpu_reg.f_bits.h = 0; + + break; + } + + case 0x28: /* JR Z, imm */ + if(gb->cpu_reg.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_bits.c = (gb->cpu_reg.hl.reg & 0x8000) > 0; + gb->cpu_reg.hl.reg <<= 1; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.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 */ + gb->cpu_reg.hl.bytes.l++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.hl.bytes.l == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.hl.bytes.l & 0x0F) == 0x00); + 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_bits.n = 1; + gb->cpu_reg.f_bits.h = 1; + break; + + case 0x30: /* JR NC, imm */ + if(!gb->cpu_reg.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) + 1; + gb->cpu_reg.f_bits.z = (temp == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((temp & 0x0F) == 0x00); + __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) - 1; + gb->cpu_reg.f_bits.z = (temp == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = ((temp & 0x0F) == 0x0F); + __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_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 1; + break; + + case 0x38: /* JR C, imm */ + if(gb->cpu_reg.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_bits.n = 0; + gb->cpu_reg.f_bits.h = + ((gb->cpu_reg.hl.reg & 0xFFF) + (gb->cpu_reg.sp.reg & 0xFFF)) & 0x1000 ? 1 : 0; + gb->cpu_reg.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 */ + gb->cpu_reg.a++; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a & 0x0F) == 0x00); + break; + + case 0x3D: /* DEC A */ + gb->cpu_reg.a--; + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.a & 0x0F) == 0x0F); + 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_bits.n = 0; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = ~gb->cpu_reg.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 = 1; + + if (gb->hram_io[IO_IE] == 0) + { + /* Return program counter where this halt forever state started. */ + /* This may be intentional, but this is required to stop an infinite + * loop. */ + (gb->gb_error)(gb, GB_HALT_FOREVER, gb->cpu_reg.pc.reg - 1); + PGB_UNREACHABLE(); + } + + 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_MODE_2_CYCLES - + gb->counter.lcd_count; + } + else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_OAM) + { + lcd_cycles = LCD_MODE_3_CYCLES - + gb->counter.lcd_count; + } + else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_TRANSFER) + { + lcd_cycles = LCD_MODE_0_CYCLES - + 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_bits.c); + break; + + case 0x89: /* ADC A, C */ + PGB_INSTR_ADC_R8(gb->cpu_reg.bc.bytes.c, gb->cpu_reg.f_bits.c); + break; + + case 0x8A: /* ADC A, D */ + PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.d, gb->cpu_reg.f_bits.c); + break; + + case 0x8B: /* ADC A, E */ + PGB_INSTR_ADC_R8(gb->cpu_reg.de.bytes.e, gb->cpu_reg.f_bits.c); + break; + + case 0x8C: /* ADC A, H */ + PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.h, gb->cpu_reg.f_bits.c); + break; + + case 0x8D: /* ADC A, L */ + PGB_INSTR_ADC_R8(gb->cpu_reg.hl.bytes.l, gb->cpu_reg.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_bits.c); + break; + + case 0x8F: /* ADC A, A */ + PGB_INSTR_ADC_R8(gb->cpu_reg.a, gb->cpu_reg.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_bits.z = 1; + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; + break; + + case 0x98: /* SBC A, B */ + PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.b, gb->cpu_reg.f_bits.c); + break; + + case 0x99: /* SBC A, C */ + PGB_INSTR_SBC_R8(gb->cpu_reg.bc.bytes.c, gb->cpu_reg.f_bits.c); + break; + + case 0x9A: /* SBC A, D */ + PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.d, gb->cpu_reg.f_bits.c); + break; + + case 0x9B: /* SBC A, E */ + PGB_INSTR_SBC_R8(gb->cpu_reg.de.bytes.e, gb->cpu_reg.f_bits.c); + break; + + case 0x9C: /* SBC A, H */ + PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.h, gb->cpu_reg.f_bits.c); + break; + + case 0x9D: /* SBC A, L */ + PGB_INSTR_SBC_R8(gb->cpu_reg.hl.bytes.l, gb->cpu_reg.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_bits.c); + break; + + case 0x9F: /* SBC A, A */ + gb->cpu_reg.a = gb->cpu_reg.f_bits.c ? 0xFF : 0x00; + gb->cpu_reg.f_bits.z = !gb->cpu_reg.f_bits.c; + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = gb->cpu_reg.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_bits.z = 1; + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = 0; + gb->cpu_reg.f_bits.c = 0; + break; + + case 0xC0: /* RET NZ */ + if(!gb->cpu_reg.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_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_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_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_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_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_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_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_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_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_bits.z = ((temp & 0xFF) == 0x00); + gb->cpu_reg.f_bits.n = 1; + gb->cpu_reg.f_bits.h = + (gb->cpu_reg.a ^ val ^ temp) & 0x10 ? 1 : 0; + gb->cpu_reg.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_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 = 1; + } + break; + + case 0xDA: /* JP C, imm */ + if(gb->cpu_reg.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_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_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 */ + /* TODO: Optimisation? */ + gb->cpu_reg.a = gb->cpu_reg.a & __gb_read(gb, gb->cpu_reg.pc.reg++); + gb->cpu_reg.f_bits.z = (gb->cpu_reg.a == 0x00); + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = 1; + gb->cpu_reg.f_bits.c = 0; + 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_bits.z = 0; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.sp.reg & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0; + gb->cpu_reg.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_bits.z = (temp_8 >> 7) & 1; + gb->cpu_reg.f_bits.n = (temp_8 >> 6) & 1; + gb->cpu_reg.f_bits.h = (temp_8 >> 5) & 1; + gb->cpu_reg.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 = 0; + 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_bits.z << 7 | gb->cpu_reg.f_bits.n << 6 | + gb->cpu_reg.f_bits.h << 5 | gb->cpu_reg.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_bits.z = 0; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = ((gb->cpu_reg.sp.reg & 0xF) + (offset & 0xF) > 0xF) ? 1 : 0; + gb->cpu_reg.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 = 1; + 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 invlid 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 serial transmission. */ + if(gb->hram_io[IO_SC] & SERIAL_SC_TX_START) + { + unsigned int serial_cycles = SERIAL_CYCLES_1KB; + + /* 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]); + +#if PEANUT_FULL_GBC_SUPPORT + if(gb->hram_io[IO_SC] & 0x3) + serial_cycles = SERIAL_CYCLES_32KB; +#endif + + 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. */ + if(!(gb->hram_io[IO_LCDC] & LCDC_ENABLE)) + continue; + + /* LCD Timing */ +#if PEANUT_FULL_GBC_SUPPORT + if (inst_cycles > 1) + gb->counter.lcd_count += (inst_cycles >> gb->cgb.doubleSpeed); + else +#endif + gb->counter.lcd_count += inst_cycles; + + /* New Scanline */ + 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) % LCD_VERT_LINES; + + /* 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; + + /* VBLANK Start */ + 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 = 1; + gb->hram_io[IO_IF] |= VBLANK_INTR; + gb->lcd_blank = 0; + + 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 + } + /* Normal Line */ + 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; + } + + gb->hram_io[IO_STAT] = + (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_HBLANK; + +#if PEANUT_FULL_GBC_SUPPORT + //DMA GBC + if(gb->cgb.cgbMode && !gb->cgb.dmaActive && gb->cgb.dmaMode) + { + for (uint8_t i = 0; i < 0x10; i++) + { + __gb_write(gb, ((gb->cgb.dmaDest & 0x1FF0) | 0x8000) + i, + __gb_read(gb, (gb->cgb.dmaSource & 0xFFF0) + i)); + } + gb->cgb.dmaSource += 0x10; + gb->cgb.dmaDest += 0x10; + if(!(--gb->cgb.dmaSize)) gb->cgb.dmaActive = 1; + } +#endif + if(gb->hram_io[IO_STAT] & STAT_MODE_0_INTR) + gb->hram_io[IO_IF] |= LCDC_INTR; + + /* If halted immediately jump to next LCD mode. */ + if(gb->counter.lcd_count < LCD_MODE_2_CYCLES) + inst_cycles = LCD_MODE_2_CYCLES - gb->counter.lcd_count; + } + } + /* OAM access */ + else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_HBLANK && + gb->counter.lcd_count >= LCD_MODE_2_CYCLES) + { + gb->hram_io[IO_STAT] = + (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_SEARCH_OAM; + + 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. */ + if (gb->counter.lcd_count < LCD_MODE_3_CYCLES) + inst_cycles = LCD_MODE_3_CYCLES - gb->counter.lcd_count; + } + /* Update LCD */ + else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_OAM && + gb->counter.lcd_count >= LCD_MODE_3_CYCLES) + { + gb->hram_io[IO_STAT] = + (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_SEARCH_TRANSFER; +#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_MODE_0_CYCLES) + inst_cycles = LCD_MODE_0_CYCLES - 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 = 0; + + while(!gb->gb_frame) + __gb_step_cpu(gb); +} + +/** + * Gets the size of the save file required for the ROM. + */ +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[] = + { + 0x00, 0x800, 0x2000, 0x8000, 0x20000 + }; + uint8_t ram_size = gb->gb_rom_read(gb, ram_size_location); + + /* MBC2 always has 512 half-bytes of cart RAM. */ + if(gb->mbc == 2) + return 0x200; + + return ram_sizes[ram_size]; +} + +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 = 0; + gb->gb_ime = 1; + + /* 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_bits.z = 1; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = hdr_chk; + gb->cpu_reg.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_BANK] = 0x01; +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode) + { + gb->cpu_reg.a = 0x11; + gb->cpu_reg.f_bits.z = 1; + gb->cpu_reg.f_bits.n = 0; + gb->cpu_reg.f_bits.h = hdr_chk; + gb->cpu_reg.f_bits.c = hdr_chk; + gb->cpu_reg.bc.reg = 0x0000; + gb->cpu_reg.de.reg = 0x0008; + gb->cpu_reg.hl.reg = 0x007C; + gb->hram_io[IO_DIV] = 0xFF; + } +#endif + + 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_BANK] = 0x00; + } + + gb->counter.lcd_count = 0; + gb->counter.div_count = 0; + gb->counter.tima_count = 0; + gb->counter.serial_count = 0; + + gb->direct.joypad = 0xFF; + gb->hram_io[IO_JOYP] = 0xCF; + gb->hram_io[IO_SB ] = 0x00; + gb->hram_io[IO_SC ] = 0x7E; +#if PEANUT_FULL_GBC_SUPPORT + if(gb->cgb.cgbMode) gb->hram_io[IO_SC] = 0x7F; +#endif + /* 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; +#if PEANUT_FULL_GBC_SUPPORT + /* Initialize some CGB registers */ + gb->cgb.doubleSpeed = 0; + gb->cgb.doubleSpeedPrep = 0; + gb->cgb.wramBank = 1; + gb->cgb.wramBankOffset = WRAM_0_ADDR; + gb->cgb.vramBank = 0; + gb->cgb.vramBankOffset = VRAM_ADDR; + for (int i = 0; i < 0x20; i++) + { + gb->cgb.OAMPalette[(i << 1)] = gb->cgb.BGPalette[(i << 1)] = 0x7F; + gb->cgb.OAMPalette[(i << 1) + 1] = gb->cgb.BGPalette[(i << 1) + 1] = 0xFF; + } + gb->cgb.OAMPaletteID = 0; + gb->cgb.BGPaletteID = 0; + gb->cgb.OAMPaletteInc = 0; + gb->cgb.BGPaletteInc = 0; + gb->cgb.dmaActive = 1; // Not active + gb->cgb.dmaMode = 0; + gb->cgb.dmaSize = 0; + gb->cgb.dmaSource = 0; + gb->cgb.dmaDest = 0; +#endif +} + +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) +{ +#if PEANUT_FULL_GBC_SUPPORT + const uint16_t cgb_flag = 0x0143; +#endif + 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 + }; + 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 + }; + const uint16_t num_rom_banks_mask[] = + { + 2, 4, 8, 16, 32, 64, 128, 256, 512 + }; + 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. */ + { +#if PEANUT_FULL_GBC_SUPPORT + gb->cgb.cgbMode = (gb->gb_rom_read(gb, cgb_flag) & 0x80) >> 7; +#endif + 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->cart_ram = cart_ram[gb->gb_rom_read(gb, mbc_location)]; + gb->num_rom_banks_mask = num_rom_banks_mask[gb->gb_rom_read(gb, bank_count_location)] - 1; + gb->num_ram_banks = num_ram_banks[gb->gb_rom_read(gb, ram_size_location)]; + + /* 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 = 0; + 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 = 0; + gb->display.interlace_count = 0; + gb->direct.frame_skip = 0; + gb->display.frame_skip_count = 0; + + 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; +} + +/** + * This was taken from SameBoy, which is released under MIT Licence. + */ +void gb_tick_rtc(struct gb_s *gb) +{ + /* is timer running? */ + if((gb->cart_rtc[4] & 0x40) == 0) + { + if(++gb->rtc_bits.sec == 60) + { + gb->rtc_bits.sec = 0; + + if(++gb->rtc_bits.min == 60) + { + gb->rtc_bits.min = 0; + + if(++gb->rtc_bits.hour == 24) + { + gb->rtc_bits.hour = 0; + + if(++gb->rtc_bits.yday == 0) + { + if(gb->rtc_bits.high & 1) /* Bit 8 of days*/ + { + gb->rtc_bits.high |= 0x80; /* Overflow bit */ + } + + gb->rtc_bits.high ^= 1; + } + } + } + } + } +} + +void gb_set_rtc(struct gb_s *gb, const struct tm * const time) +{ + gb->cart_rtc[0] = time->tm_sec; + gb->cart_rtc[1] = time->tm_min; + gb->cart_rtc[2] = time->tm_hour; + gb->cart_rtc[3] = time->tm_yday & 0xFF; /* Low 8 bits of day counter. */ + gb->cart_rtc[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 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. + * \returns Size of the Cart RAM in bytes. 0 if Cartridge has not battery + * backed RAM. + */ +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); + +/** + * Tick the internal RTC by one second. This does not affect games with no RTC + * support. + * + * \param gb An initialised emulator context. Must not be NULL. + */ +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 diff --git a/apps/Peanut-GBC/selector.c b/apps/Peanut-GBC/selector.c new file mode 100644 index 00000000..129ff4bd --- /dev/null +++ b/apps/Peanut-GBC/selector.c @@ -0,0 +1,135 @@ +#include "selector.h" +#include +#include + +#define FILENAME_LENGHT_MAX 512 + +char *osd_newextension(char *string, char *ext) { + int l=strlen(string); + while(l && string[l]!='.') { + l--; + } + if (l) string[l]=0; + strcat(string, ext); + return string; +} + +static void waitForKeyReleasedTimeout(int timeout) { + while(extapp_scanKeyboard() && timeout > 0) { + extapp_msleep(10); + timeout -= 10; + } +} + +static void waitForKeyReleased() { + while(extapp_scanKeyboard()) { + extapp_msleep(10); + } +} + +static void waitForKeyPressed() { + while(!extapp_scanKeyboard()) { + extapp_msleep(10); + } +} + +static void drawRomList(char **filenames, int nb, int selected_rom) { + char name_buffer[FILENAME_LENGHT_MAX]; + for(int i = 0; i < nb; i++) { + strncpy(name_buffer, filenames[i], FILENAME_LENGHT_MAX); + name_buffer[26] = '\0'; + extapp_drawTextLarge(name_buffer, 0, (i+1)*20, selected_rom == i ? SELECTOR_COLOR_FG_SELECT : SELECTOR_COLOR_FG, SELECTOR_COLOR_BG, false); + strncpy(name_buffer, filenames[i], FILENAME_LENGHT_MAX); + if(extapp_fileExists(osd_newextension(name_buffer, ".gbs"), EXTAPP_RAM_FILE_SYSTEM)) { + extapp_drawTextLarge("Saved", LCD_WIDTH - 50, (i+1)*20, selected_rom == i ? SELECTOR_COLOR_FG_SELECT : SELECTOR_COLOR_FG, SELECTOR_COLOR_BG, false); + } else { + extapp_drawTextLarge(" ", LCD_WIDTH - 50, (i+1)*20, selected_rom == i ? SELECTOR_COLOR_FG_SELECT : SELECTOR_COLOR_FG, SELECTOR_COLOR_BG, false); + } + } +} + +static char ** remove(char ** first, char ** last) { + char ** result = first; + while (first != last) { + if (!(*first == NULL)) { + *result = *first; + ++result; + } + ++first; + } + return result; +} + +const char * select_rom() { + const int max_roms = 10; + char * filenames[max_roms]; + int selected_rom = 0; + char name_buffer[FILENAME_LENGHT_MAX]; + + waitForKeyReleased(); + + extapp_pushRectUniform(0, 0, LCD_WIDTH, LCD_HEIGHT, SELECTOR_COLOR_BG); + extapp_drawTextLarge(" Select a ROM ", 0, 0, SELECTOR_COLOR_HEAD_FG, SELECTOR_COLOR_HEAD_BG, false); + + int nb = extapp_fileListWithExtension(filenames, max_roms, "", EXTAPP_FLASH_FILE_SYSTEM); + + size_t len; + for(int i = 0; i < nb; i++) { + const char * data = extapp_fileRead(filenames[i], &len, EXTAPP_FLASH_FILE_SYSTEM); + // Calculate header checksum to validate that the rom is indeed a gameboy rom. + // Calculation goes as follow: + // x=0:FOR i=0134h TO 014Ch:x=x-MEM[i]-1:NEXT + // (See: https://gbdev.io/pandocs/#_014d-header-checksum) + if(len < 0x0150) { + filenames[i] = NULL; + } else { + uint8_t checksum = 0; + for(uint16_t i = 0x0134; i < 0x014D; i++) { + checksum += ~data[i]; + } + + if (checksum != data[0x014D]) { + filenames[i] = NULL; + } + } + + + } + nb = remove(filenames, filenames + nb) - filenames; + + if(nb == 0) { + extapp_drawTextLarge(" No ROM found ", 0, 120, SELECTOR_COLOR_FG, SELECTOR_COLOR_BG, false); + extapp_msleep(10); + waitForKeyPressed(); + return NULL; + } else { + drawRomList(filenames, nb, selected_rom); + for(;;) { + extapp_msleep(10); + uint64_t scancode = extapp_scanKeyboard(); + if(scancode & SCANCODE_Down) { + selected_rom = (selected_rom + 1) % nb; + drawRomList(filenames, nb, selected_rom); + waitForKeyReleasedTimeout(200); + } else if(scancode & SCANCODE_Up) { + selected_rom = (selected_rom - 1) % nb; + if(selected_rom < 0) { + selected_rom = nb + selected_rom; + } + drawRomList(filenames, nb, selected_rom); + waitForKeyReleasedTimeout(200); + } else if(scancode & (SCANCODE_OK | SCANCODE_EXE)) { + break; + } else if(scancode & SCANCODE_Backspace) { + strncpy(name_buffer, filenames[selected_rom], FILENAME_LENGHT_MAX); + extapp_fileErase(osd_newextension(name_buffer, ".gbs"), EXTAPP_RAM_FILE_SYSTEM); + drawRomList(filenames, nb, selected_rom); + waitForKeyReleased(); + } else if(scancode & SCANCODE_Back) { + return NULL; + } + } + extapp_pushRectUniform(0, 0, LCD_WIDTH, LCD_HEIGHT, SELECTOR_COLOR_BG); + return filenames[selected_rom]; + } +} diff --git a/apps/Peanut-GBC/selector.h b/apps/Peanut-GBC/selector.h new file mode 100644 index 00000000..be3dc01c --- /dev/null +++ b/apps/Peanut-GBC/selector.h @@ -0,0 +1,14 @@ +#ifndef SELECTOR_H +#define SELECTOR_H + +#define SELECTOR_COLOR_BG 0x09C1 +#define SELECTOR_COLOR_FG 0x3306 +#define SELECTOR_COLOR_FG_SELECT 0x9DE1 +#define SELECTOR_COLOR_HEAD_BG 0x8D61 +#define SELECTOR_COLOR_HEAD_FG 0x09C1 + +char *osd_newextension(char *string, char *ext); +const char * select_rom(); + +#endif + diff --git a/apps/Peanut-GBC/sources.mak b/apps/Peanut-GBC/sources.mak new file mode 100644 index 00000000..1da37dfa --- /dev/null +++ b/apps/Peanut-GBC/sources.mak @@ -0,0 +1,4 @@ +app_external_src += $(addprefix apps/external/app/,\ + main.c \ + selector.c \ +) diff --git a/apps/apps.js b/apps/apps.js index 0da50353..ac475166 100644 --- a/apps/apps.js +++ b/apps/apps.js @@ -5,6 +5,7 @@ angular.module('nwas').service('apps', function() { { name: "KhiCAS", description: {en: "Computer algebra system", fr: "Système de calcul formel"} }, { name: "Periodic", description: {en: "Periodic table of elements", fr: "Tableau périodique des éléments"} }, { name: "Nofrendo", description: {en: "NES emulator", fr: "Émulateur NES"} }, + { name: "Peanut-GBC", description: {en: "GameBoy Color emulator", fr: "Émulateur GameBoy Color"} }, { name: "Peanut-GB", description: {en: "GameBoy emulator", fr: "Émulateur GameBoy"} }, { name: "HexEdit", description: {en: "Hexadecimal editor", fr: "Éditeur hexadécimal"} }, { name: "BadApple", description: {en: "Bad Apple demo", fr: "Démo Bad Apple"} },