From 7068cbc95b0d85dabe537d18d39033f60e9a65a7 Mon Sep 17 00:00:00 2001 From: David Guillen Fandos Date: Tue, 24 Aug 2021 17:15:27 +0200 Subject: [PATCH] New savestate implementation This uses BSON as savestate format, to allow external tools to parse it (so that we can add proper test of the states). The BSON is not 100% correct according to spec (no ordered keys) but can be parsed by most libraries. This fixes also a bug in the savestate palette color recalculation that was wrongly overwritting the original palette (which could cause some problems on some games). Also fixes some potential issues by serializing some more stuff and cleans up unused stuff. Testing shows that states look good and there's only minor differences in audio ticks, related to buffer sizes (since buffer flushes are de-synced from video frames due to different frequency). --- Makefile.common | 1 + common.h | 7 +- cpu.c | 29 ++++-- cpu.h | 5 +- gba_memory.c | 263 +++++++++++++++++++++++++++--------------------- gba_memory.h | 46 ++------- input.c | 22 ++-- input.h | 4 +- libretro.c | 4 +- main.c | 71 +++++++++++-- main.h | 4 +- savestate.c | 153 ++++++++++++++++++++++++++++ savestate.h | 76 ++++++++++++++ sound.c | 168 +++++++++++++++++++++++++++---- sound.h | 5 +- video.c | 2 +- 16 files changed, 644 insertions(+), 216 deletions(-) create mode 100644 savestate.c create mode 100644 savestate.h diff --git a/Makefile.common b/Makefile.common index 79ae145..85e1948 100644 --- a/Makefile.common +++ b/Makefile.common @@ -5,6 +5,7 @@ SOURCES_ASM := $(CORE_DIR)/bios_data.S SOURCES_C := $(CORE_DIR)/main.c \ $(CORE_DIR)/cpu.c \ $(CORE_DIR)/gba_memory.c \ + $(CORE_DIR)/savestate.c \ $(CORE_DIR)/video.c \ $(CORE_DIR)/input.c \ $(CORE_DIR)/sound.c \ diff --git a/common.h b/common.h index 09b7f51..ba5cfc5 100644 --- a/common.h +++ b/common.h @@ -77,13 +77,13 @@ #ifdef USE_BGR_FORMAT #define convert_palette(value) \ - value = ((value & 0x7FE0) << 1) | (value & 0x1F) + (((value & 0x7FE0) << 1) | (value & 0x1F)) #elif defined(USE_XBGR1555_FORMAT) #define convert_palette(value) \ - value = (value & 0x7FFF) + (value & 0x7FFF) #else #define convert_palette(value) \ - value = ((value & 0x1F) << 11) | ((value & 0x03E0) << 1) | (value >> 10) + (((value & 0x1F) << 11) | ((value & 0x03E0) << 1) | (value >> 10)) #endif #define GBA_SCREEN_WIDTH (240) @@ -153,6 +153,7 @@ typedef u32 fixed8_24; #include #include "cpu.h" #include "gba_memory.h" +#include "savestate.h" #include "video.h" #include "input.h" #include "sound.h" diff --git a/cpu.c b/cpu.c index 0cb0754..6fbecf0 100644 --- a/cpu.c +++ b/cpu.c @@ -3774,13 +3774,26 @@ void init_cpu(void) reg_mode[MODE_SUPERVISOR][5] = 0x03007FE0; } -#define cpu_savestate_builder(type) \ -void cpu_##type##_savestate(void) \ -{ \ - state_mem_##type(reg, 4 * REG_IGNORE); \ - state_mem_##type##_array(spsr); \ - state_mem_##type##_array(reg_mode); \ +bool cpu_read_savestate(const u8 *src) +{ + const u8 *cpudoc = bson_find_key(src, "cpu"); + return bson_read_int32(cpudoc, "bus-value", &bios_read_protect) && + bson_read_int32_array(cpudoc, "regs", reg, REG_IGNORE) && + bson_read_int32_array(cpudoc, "spsr", spsr, 6) && + bson_read_int32_array(cpudoc, "regmod", (u32*)reg_mode, 7*7); } -cpu_savestate_builder(read) -cpu_savestate_builder(write) +unsigned cpu_write_savestate(u8 *dst) +{ + u8 *wbptr, *startp = dst; + bson_start_document(dst, "cpu", wbptr); + bson_write_int32array(dst, "regs", reg, REG_IGNORE); + bson_write_int32array(dst, "spsr", spsr, 6); + bson_write_int32array(dst, "regmod", reg_mode, 7*7); + bson_write_int32(dst, "bus-value", bios_read_protect); + + bson_finish_document(dst, wbptr); + return (unsigned int)(dst - startp); +} + + diff --git a/cpu.h b/cpu.h index cc36f26..3b44530 100644 --- a/cpu.h +++ b/cpu.h @@ -20,6 +20,7 @@ #ifndef CPU_H #define CPU_H +#include #include "gpsp_config.h" // System mode and user mode are represented as the same here @@ -118,8 +119,8 @@ void function_cc execute_store_u16(u32 address, u32 source); void function_cc execute_store_u32(u32 address, u32 source); u32 function_cc execute_arm_translate(u32 cycles); void init_translater(void); -void cpu_write_savestate(void); -void cpu_read_savestate(void); +unsigned cpu_write_savestate(u8* dst); +bool cpu_read_savestate(const u8 *src); u8 function_cc *block_lookup_address_arm(u32 pc); u8 function_cc *block_lookup_address_thumb(u32 pc); diff --git a/gba_memory.c b/gba_memory.c index b061ecb..e37c262 100644 --- a/gba_memory.c +++ b/gba_memory.c @@ -360,7 +360,7 @@ sram_size_type sram_size = SRAM_SIZE_32KB; flash_mode_type flash_mode = FLASH_BASE_MODE; u32 flash_command_position = 0; -u8 *flash_bank_ptr = gamepak_backup; +u32 flash_bank_num; // 0 or 1 flash_device_id_type flash_device_id = FLASH_DEVICE_MACRONIX_64KB; flash_manufacturer_id_type flash_manufacturer_id = @@ -398,7 +398,10 @@ u8 read_backup(u32 address) } } else - value = flash_bank_ptr[address]; + { + u32 fulladdr = address + 64*1024*flash_bank_num; + value = gamepak_backup[fulladdr]; + } return value; } @@ -656,7 +659,6 @@ static cpu_alert_type trigger_dma(u32 dma_number, u32 value) u32 dst_address = 0xFFFFFFF & (read_dmareg(REG_DMA0DAD, dma_number) | (read_dmareg(REG_DMA0DAD + 1, dma_number) << 16)); - dma[dma_number].dma_channel = dma_number; dma[dma_number].source_address = src_address; dma[dma_number].dest_address = dst_address; dma[dma_number].source_direction = (dma_increment_type)((value >> 7) & 3); @@ -702,7 +704,7 @@ static cpu_alert_type trigger_dma(u32 dma_number, u32 value) write_dmareg(REG_DMA0CNT_H, dma_number, value); if(start_type == DMA_START_IMMEDIATELY) - return dma_transfer(&dma[dma_number]); + return dma_transfer(dma_number); } } else @@ -1431,7 +1433,7 @@ cpu_alert_type function_cc write_io_register32(u32 address, u32 value) { \ u32 palette_address = address; \ address16(palette_ram, palette_address) = eswap16(value); \ - convert_palette(value); \ + value = convert_palette(value); \ address16(palette_ram_converted, palette_address) = value; \ } \ @@ -1441,9 +1443,9 @@ cpu_alert_type function_cc write_io_register32(u32 address, u32 value) u32 value_high = value >> 16; \ u32 value_low = value & 0xFFFF; \ address32(palette_ram, palette_address) = eswap32(value); \ - convert_palette(value_high); \ + value_high = convert_palette(value_high); \ address16(palette_ram_converted, palette_address + 2) = value_high; \ - convert_palette(value_low); \ + value_low = convert_palette(value_low); \ address16(palette_ram_converted, palette_address) = value_low; \ } \ @@ -1535,7 +1537,8 @@ void function_cc write_backup(u32 address, u32 value) (flash_mode == FLASH_ERASE_MODE) && (value == 0x30)) { // Erase sector - memset(flash_bank_ptr + (address & 0xF000), 0xFF, 1024 * 4); + u32 fulladdr = (address & 0xF000) + 64*1024*flash_bank_num; + memset(&gamepak_backup[fulladdr], 0xFF, 1024 * 4); flash_mode = FLASH_BASE_MODE; flash_command_position = 0; } @@ -1545,7 +1548,7 @@ void function_cc write_backup(u32 address, u32 value) (flash_mode == FLASH_BANKSWITCH_MODE) && (address == 0x0000) && (flash_size == FLASH_SIZE_128KB)) { - flash_bank_ptr = gamepak_backup + ((value & 0x01) * (1024 * 64)); + flash_bank_num = value & 1; flash_mode = FLASH_BASE_MODE; } else @@ -1553,7 +1556,8 @@ void function_cc write_backup(u32 address, u32 value) if((flash_command_position == 0) && (flash_mode == FLASH_WRITE_MODE)) { // Write value to flash ROM - flash_bank_ptr[address] = value; + u32 fulladdr = address + 64*1024*flash_bank_num; + gamepak_backup[fulladdr] = value; flash_mode = FLASH_BASE_MODE; } else @@ -2791,11 +2795,12 @@ cpu_alert_type dma_tf_loop##transfer_size( \ dma_tf_loop_builder(16); dma_tf_loop_builder(32); -cpu_alert_type dma_transfer(dma_transfer_type *dma) +cpu_alert_type dma_transfer(unsigned dma_chan) { - u32 length = dma->length; - u32 src_ptr = dma->source_address; - uintptr_t dest_ptr = dma->dest_address; + dma_transfer_type *dmach = &dma[dma_chan]; + u32 length = dmach->length; + u32 src_ptr = dmach->source_address; + uintptr_t dest_ptr = dmach->dest_address; cpu_alert_type ret = CPU_ALERT_NONE; // Technically this should be done for source and destination, but @@ -2805,49 +2810,49 @@ cpu_alert_type dma_transfer(dma_transfer_type *dma) { u32 first_length = ((dest_ptr & 0xFF000000) + 0x1000000) - dest_ptr; u32 second_length = length - first_length; - dma->length = first_length; + dmach->length = first_length; - dma_transfer(dma); + dma_transfer(dma_chan); - dma->length = length; + dmach->length = length; length = second_length; dest_ptr += first_length; src_ptr += first_length; } - if (dma->source_direction < 3) + if (dmach->source_direction < 3) { const int stridetbl[4] = {1, -1, 0, 1}; - int dst_stride = stridetbl[dma->dest_direction]; - int src_stride = stridetbl[dma->source_direction]; - bool dst_wb = dma->dest_direction < 3; + int dst_stride = stridetbl[dmach->dest_direction]; + int src_stride = stridetbl[dmach->source_direction]; + bool dst_wb = dmach->dest_direction < 3; - if(dma->length_type == DMA_16BIT) + if(dmach->length_type == DMA_16BIT) { src_ptr &= ~0x01; dest_ptr &= ~0x01; - ret = dma_tf_loop16(src_ptr, dest_ptr, 2 * src_stride, 2 * dst_stride, dst_wb, length, dma); + ret = dma_tf_loop16(src_ptr, dest_ptr, 2 * src_stride, 2 * dst_stride, dst_wb, length, dmach); } else { src_ptr &= ~0x03; dest_ptr &= ~0x03; - ret = dma_tf_loop32(src_ptr, dest_ptr, 4 * src_stride, 4 * dst_stride, dst_wb, length, dma); + ret = dma_tf_loop32(src_ptr, dest_ptr, 4 * src_stride, 4 * dst_stride, dst_wb, length, dmach); } } - if((dma->repeat_type == DMA_NO_REPEAT) || - (dma->start_type == DMA_START_IMMEDIATELY)) + if((dmach->repeat_type == DMA_NO_REPEAT) || + (dmach->start_type == DMA_START_IMMEDIATELY)) { - u32 cntrl = read_dmareg(REG_DMA0CNT_H, dma->dma_channel); - write_dmareg(REG_DMA0CNT_H, dma->dma_channel, cntrl & (~0x8000)); - dma->start_type = DMA_INACTIVE; + u32 cntrl = read_dmareg(REG_DMA0CNT_H, dma_chan); + write_dmareg(REG_DMA0CNT_H, dma_chan, cntrl & (~0x8000)); + dmach->start_type = DMA_INACTIVE; } - if(dma->irq) + if(dmach->irq) { - raise_interrupt(IRQ_DMA0 << dma->dma_channel); + raise_interrupt(IRQ_DMA0 << dma_chan); ret = CPU_ALERT_IRQ; } @@ -3010,7 +3015,7 @@ void init_memory(void) sram_size = SRAM_SIZE_32KB; //flash_size = FLASH_SIZE_64KB; - flash_bank_ptr = gamepak_backup; + flash_bank_num = 0; flash_command_position = 0; eeprom_size = EEPROM_512_BYTE; eeprom_mode = EEPROM_BASE_MODE; @@ -3038,100 +3043,132 @@ void memory_term(void) } } -#define savestate_block(type) \ - cpu_##type##_savestate(); \ - input_##type##_savestate(); \ - main_##type##_savestate(); \ - memory_##type##_savestate(); \ - sound_##type##_savestate(); - - -const u8 *state_mem_read_ptr; -u8 *state_mem_write_ptr; - -void gba_load_state(const void* src) +bool memory_read_savestate(const u8 *src) { - u32 i; - u32 current_color; + int i; + const u8 *memdoc = bson_find_key(src, "memory"); + const u8 *bakdoc = bson_find_key(src, "backup"); + const u8 *dmadoc = bson_find_key(src, "dma"); + if (!memdoc || !bakdoc || !dmadoc) + return false; - state_mem_read_ptr = (u8*)src; - savestate_block(read); + if (!( + bson_read_bytes(memdoc, "iwram", &iwram[0x8000], 0x8000) && + bson_read_bytes(memdoc, "ewram", ewram, 0x40000) && + bson_read_bytes(memdoc, "vram", vram, sizeof(vram)) && + bson_read_bytes(memdoc, "oamram", oam_ram, sizeof(oam_ram)) && + bson_read_bytes(memdoc, "palram", palette_ram, sizeof(palette_ram)) && + bson_read_bytes(memdoc, "ioregs", io_registers, sizeof(io_registers)) && -#ifdef HAVE_DYNAREC - if (dynarec_enable) - init_caches(); -#endif + bson_read_int32(bakdoc, "backup-type", &backup_type) && + bson_read_int32(bakdoc, "sram-size", &sram_size) && - reg[OAM_UPDATED] = 1; - gbc_sound_update = 1; + bson_read_int32(bakdoc, "flash-mode", &flash_mode) && + bson_read_int32(bakdoc, "flash-cmd-pos", &flash_command_position) && + bson_read_int32(bakdoc, "flash-bank-num", &flash_bank_num) && + bson_read_int32(bakdoc, "flash-dev-id", &flash_device_id) && + bson_read_int32(bakdoc, "flash-man-id", &flash_manufacturer_id) && + bson_read_int32(bakdoc, "flash-size", &flash_size) && - for(i = 0; i < 512; i++) - { - current_color = palette_ram[i]; - palette_ram_converted[i] = - convert_palette(current_color); - } + bson_read_int32(bakdoc, "eeprom-size", &eeprom_size) && + bson_read_int32(bakdoc, "eeprom-mode", &eeprom_mode) && + bson_read_int32(bakdoc, "eeprom-addr", &eeprom_address) && + bson_read_int32(bakdoc, "eeprom-counter", &eeprom_counter) && - video_reload_counters(); + bson_read_int32(bakdoc, "rtc-state", &rtc_state) && + bson_read_int32(bakdoc, "rtc-write-mode", &rtc_write_mode) && + bson_read_int32(bakdoc, "rtc-cmd", &rtc_command) && + bson_read_int32(bakdoc, "rtc-status", &rtc_status) && + bson_read_int32(bakdoc, "rtc-data-byte-cnt", &rtc_data_bytes) && + bson_read_int32(bakdoc, "rtc-bit-cnt", (u32*)&rtc_bit_count) && + bson_read_bytes(bakdoc, "rtc-regs", rtc_registers, sizeof(rtc_registers)) && + bson_read_int32_array(bakdoc, "rtc-data-words", rtc_data, + sizeof(rtc_data)/sizeof(rtc_data[0])))) + return false; - // Oops, these contain raw pointers - for(i = 0; i < 4; i++) - gbc_sound_channel[i].sample_table_idx = 2; + for (i = 0; i < DMA_CHAN_CNT; i++) + { + char tname[2] = {'0' + i, 0}; + const u8 *dmastr = bson_find_key(dmadoc, tname); + if (!( + bson_read_int32(dmastr, "src-addr", &dma[i].source_address) && + bson_read_int32(dmastr, "dst-addr", &dma[i].dest_address) && + bson_read_int32(dmastr, "src-dir", &dma[i].source_direction) && + bson_read_int32(dmastr, "dst-dir", &dma[i].dest_direction) && + bson_read_int32(dmastr, "len", &dma[i].length) && + bson_read_int32(dmastr, "size", &dma[i].length_type) && + bson_read_int32(dmastr, "repeat", &dma[i].repeat_type) && + bson_read_int32(dmastr, "start", &dma[i].start_type) && + bson_read_int32(dmastr, "dsc", &dma[i].direct_sound_channel) && + bson_read_int32(dmastr, "irq", &dma[i].irq))) + return false; + } - instruction_count = 0; - - reg[CHANGED_PC_STATUS] = 1; + return true; } -void gba_save_state(void* dst) +unsigned memory_write_savestate(u8 *dst) { - state_mem_write_ptr = (u8*)dst; - savestate_block(write); + int i; + u8 *wbptr, *wbptr2, *startp = dst; + bson_start_document(dst, "memory", wbptr); + bson_write_bytes(dst, "iwram", &iwram[0x8000], 0x8000); + bson_write_bytes(dst, "ewram", ewram, 0x40000); + bson_write_bytes(dst, "vram", vram, sizeof(vram)); + bson_write_bytes(dst, "oamram", oam_ram, sizeof(oam_ram)); + bson_write_bytes(dst, "palram", palette_ram, sizeof(palette_ram)); + bson_write_bytes(dst, "ioregs", io_registers, sizeof(io_registers)); + bson_finish_document(dst, wbptr); + + bson_start_document(dst, "backup", wbptr); + bson_write_int32(dst, "backup-type", backup_type); + bson_write_int32(dst, "sram-size", sram_size); + + bson_write_int32(dst, "flash-mode", flash_mode); + bson_write_int32(dst, "flash-cmd-pos", flash_command_position); + bson_write_int32(dst, "flash-bank-num", flash_bank_num); + bson_write_int32(dst, "flash-dev-id", flash_device_id); + bson_write_int32(dst, "flash-man-id", flash_manufacturer_id); + bson_write_int32(dst, "flash-size", flash_size); + + bson_write_int32(dst, "eeprom-size", eeprom_size); + bson_write_int32(dst, "eeprom-mode", eeprom_mode); + bson_write_int32(dst, "eeprom-addr", eeprom_address); + bson_write_int32(dst, "eeprom-counter", eeprom_counter); + + bson_write_int32(dst, "rtc-state", rtc_state); + bson_write_int32(dst, "rtc-write-mode", rtc_write_mode); + bson_write_int32(dst, "rtc-cmd", rtc_command); + bson_write_int32(dst, "rtc-status", rtc_status); + bson_write_int32(dst, "rtc-data-byte-cnt", rtc_data_bytes); + bson_write_int32(dst, "rtc-bit-cnt", rtc_bit_count); + bson_write_bytes(dst, "rtc-regs", rtc_registers, sizeof(rtc_registers)); + bson_write_int32array(dst, "rtc-data-words", rtc_data, + sizeof(rtc_data)/sizeof(rtc_data[0])); + bson_finish_document(dst, wbptr); + + bson_start_document(dst, "dma", wbptr); + for (i = 0; i < DMA_CHAN_CNT; i++) + { + char tname[2] = {'0' + i, 0}; + bson_start_document(dst, tname, wbptr2); + bson_write_int32(dst, "src-addr", dma[i].source_address); + bson_write_int32(dst, "dst-addr", dma[i].dest_address); + bson_write_int32(dst, "src-dir", dma[i].source_direction); + bson_write_int32(dst, "dst-dir", dma[i].dest_direction); + bson_write_int32(dst, "len", dma[i].length); + bson_write_int32(dst, "size", dma[i].length_type); + bson_write_int32(dst, "repeat", dma[i].repeat_type); + bson_write_int32(dst, "start", dma[i].start_type); + bson_write_int32(dst, "dsc", dma[i].direct_sound_channel); + bson_write_int32(dst, "irq", dma[i].irq); + bson_finish_document(dst, wbptr2); + } + bson_finish_document(dst, wbptr); + + return (unsigned int)(dst - startp); } - -#define memory_savestate_builder(type) \ -void memory_##type##_savestate(void) \ -{ \ - state_mem_##type##_variable(backup_type); \ - state_mem_##type##_variable(sram_size); \ - state_mem_##type##_variable(flash_mode); \ - state_mem_##type##_variable(flash_command_position); \ - state_mem_##type##_pointer(flash_bank_ptr, gamepak_backup); \ - state_mem_##type##_variable(flash_device_id); \ - state_mem_##type##_variable(flash_manufacturer_id); \ - state_mem_##type##_variable(flash_size); \ - state_mem_##type##_variable(eeprom_size); \ - state_mem_##type##_variable(eeprom_mode); \ - state_mem_##type##_variable(eeprom_address); \ - state_mem_##type##_variable(eeprom_counter); \ - state_mem_##type##_variable(rtc_state); \ - state_mem_##type##_variable(rtc_write_mode); \ - state_mem_##type##_array(rtc_registers); \ - state_mem_##type##_variable(rtc_command); \ - state_mem_##type##_array(rtc_data); \ - state_mem_##type##_variable(rtc_status); \ - state_mem_##type##_variable(rtc_data_bytes); \ - state_mem_##type##_variable(rtc_bit_count); \ - state_mem_##type##_array(dma); \ - \ - state_mem_##type(iwram + 0x8000, 0x8000); \ - state_mem_##type(ewram, 0x40000); \ - state_mem_##type(vram, 0x18000); \ - state_mem_##type(oam_ram, 0x400); \ - state_mem_##type(palette_ram, 0x400); \ - state_mem_##type(io_registers, 0x400); \ - \ - /* This should not happen anymore :P */ \ - if((flash_bank_ptr < gamepak_backup) || \ - (flash_bank_ptr > (&gamepak_backup[sizeof(gamepak_backup)])))\ - flash_bank_ptr = gamepak_backup; \ -} - -memory_savestate_builder(read) -memory_savestate_builder(write) - - static s32 load_gamepak_raw(const char *name) { unsigned i, j; diff --git a/gba_memory.h b/gba_memory.h index 92199af..bb8e0f9 100644 --- a/gba_memory.h +++ b/gba_memory.h @@ -23,6 +23,8 @@ #include "libretro.h" extern int use_libretro_save_method; +#define DMA_CHAN_CNT 4 + typedef enum { DMA_START_IMMEDIATELY = 0, @@ -67,7 +69,6 @@ typedef enum typedef struct { - u32 dma_channel; u32 source_address; u32 dest_address; u32 length; @@ -213,7 +214,7 @@ extern char gamepak_code[5]; extern char gamepak_maker[3]; extern char gamepak_filename[512]; -cpu_alert_type dma_transfer(dma_transfer_type *dma); +cpu_alert_type dma_transfer(unsigned dma_chan); u8 *memory_region(u32 address, u32 *memory_limit); u32 load_gamepak(const struct retro_game_info* info, const char *name); u32 load_backup(char *name); @@ -227,7 +228,7 @@ u8 *load_gamepak_page(u32 physical_index); extern u32 oam_update; extern u32 gbc_sound_update; extern u32 gbc_sound_wave_update; -extern dma_transfer_type dma[4]; +extern dma_transfer_type dma[DMA_CHAN_CNT]; extern u8 open_gba_bios_rom[1024*16]; extern u32 bios_read_protect; @@ -247,9 +248,6 @@ extern u32 reg[64]; extern flash_device_id_type flash_device_id; -extern const u8 *state_mem_read_ptr; -extern u8 *state_mem_write_ptr; - typedef enum { BACKUP_SRAM, @@ -306,12 +304,6 @@ extern eeprom_size_type eeprom_size; extern u8 gamepak_backup[1024 * 128]; -static inline void state_mem_write(const void* src, size_t size) -{ - memcpy(state_mem_write_ptr, src, size); - state_mem_write_ptr += size; -} - // Page sticky bit routines extern u32 gamepak_sticky_bit[1024/32]; static inline void touch_gamepak_page(u32 physical_index) @@ -327,33 +319,7 @@ static inline void clear_gamepak_stickybits(void) memset(gamepak_sticky_bit, 0, sizeof(gamepak_sticky_bit)); } -/* this is an upper limit, ToDo : calculate the exact state size */ -#define GBA_STATE_MEM_SIZE (512*1024) - -#define state_mem_write_array(array) state_mem_write(array, sizeof(array)) -#define state_mem_write_variable(variable) state_mem_write(&variable, sizeof(variable)) -#define state_mem_write_pointer(ptr, base) { \ - u32 offset = ((u8*)ptr) - ((u8*)base); \ - state_mem_write(&offset, sizeof(offset)); \ -} - -static inline void state_mem_read(void* dst, size_t size) -{ - memcpy(dst, state_mem_read_ptr, size); - state_mem_read_ptr += size; -} - -#define state_mem_read_array(array) state_mem_read(array, sizeof(array)) -#define state_mem_read_variable(variable) state_mem_read(&variable, sizeof(variable)) -#define state_mem_read_pointer(ptr, base) { \ - u32 offset; \ - state_mem_read(&offset, sizeof(offset)); \ - ptr = (typeof(ptr))(((u8*)base) + offset); \ -} - -void memory_write_savestate(void); -void memory_read_savestate(void); -void gba_load_state(const void *src); -void gba_save_state(void *dst); +unsigned memory_write_savestate(u8 *dst); +bool memory_read_savestate(const u8*src); #endif diff --git a/input.c b/input.c index e030596..f74b161 100644 --- a/input.c +++ b/input.c @@ -133,11 +133,21 @@ u32 update_input(void) return 0; } -#define input_savestate_builder(type) \ -void input_##type##_savestate(void) \ -{ \ - state_mem_##type##_variable(old_key); \ +bool input_read_savestate(const u8 *src) +{ + const u8 *p = bson_find_key(src, "input"); + if (p) + return bson_read_int32(p, "prevkey", &old_key); + return false; } -input_savestate_builder(read) -input_savestate_builder(write) +unsigned input_write_savestate(u8 *dst) +{ + u8 *wbptr1, *startp = dst; + bson_start_document(dst, "input", wbptr1); + bson_write_int32(dst, "prevkey", old_key); + bson_finish_document(dst, wbptr1); + return (unsigned int)(dst - startp); +} + + diff --git a/input.h b/input.h index 72c7e1e..cc6b3e0 100644 --- a/input.h +++ b/input.h @@ -75,7 +75,7 @@ extern unsigned turbo_b_counter; void init_input(void); u32 update_input(void); -void input_write_savestate(void); -void input_read_savestate(void); +unsigned input_write_savestate(u8* dst); +bool input_read_savestate(const u8 *src); #endif diff --git a/libretro.c b/libretro.c index f31235b..37da99d 100644 --- a/libretro.c +++ b/libretro.c @@ -664,9 +664,7 @@ bool retro_unserialize(const void* data, size_t size) if (size != GBA_STATE_MEM_SIZE) return false; - gba_load_state(data); - - return true; + return gba_load_state(data); } void retro_cheat_reset(void) diff --git a/main.c b/main.c index de56043..0d7b346 100644 --- a/main.c +++ b/main.c @@ -152,7 +152,7 @@ u32 update_gba(void) for(i = 0; i < 4; i++) { if(dma[i].start_type == DMA_START_HBLANK) - dma_transfer(&dma[i]); + dma_transfer(i); } } @@ -183,7 +183,7 @@ u32 update_gba(void) for(i = 0; i < 4; i++) { if(dma[i].start_type == DMA_START_VBLANK) - dma_transfer(&dma[i]); + dma_transfer(i); } } else @@ -279,16 +279,65 @@ void change_ext(const char *src, char *buffer, const char *extension) strcpy(dot_position, extension); } -#define main_savestate_builder(type) \ -void main_##type##_savestate(void) \ -{ \ - state_mem_##type##_variable(cpu_ticks); \ - state_mem_##type##_variable(execute_cycles); \ - state_mem_##type##_variable(video_count); \ - state_mem_##type##_array(timer); \ +bool main_read_savestate(const u8 *src) +{ + int i; + const u8 *p1 = bson_find_key(src, "emu"); + const u8 *p2 = bson_find_key(src, "timers"); + if (!p1 || !p2) + return false; + execute_cycles = 123; + if (!(bson_read_int32(p1, "cpu-ticks", &cpu_ticks) && + bson_read_int32(p1, "exec-cycles", &execute_cycles) && + bson_read_int32(p1, "video-count", (u32*)&video_count))) + return false; + + for (i = 0; i < 4; i++) + { + char tname[2] = {'0' + i, 0}; + const u8 *p = bson_find_key(p2, tname); + + if (!( + bson_read_int32(p, "count", (u32*)&timer[i].count) && + bson_read_int32(p, "reload", &timer[i].reload) && + bson_read_int32(p, "prescale", &timer[i].prescale) && + bson_read_int32(p, "freq-step", &timer[i].frequency_step) && + bson_read_int32(p, "dsc", &timer[i].direct_sound_channels) && + bson_read_int32(p, "irq", &timer[i].irq) && + bson_read_int32(p, "status", &timer[i].status))) + return false; + } + + return true; } -main_savestate_builder(read) -main_savestate_builder(write) +unsigned main_write_savestate(u8* dst) +{ + int i; + u8 *wbptr, *wbptr2, *startp = dst; + bson_start_document(dst, "emu", wbptr); + bson_write_int32(dst, "cpu-ticks", cpu_ticks); + bson_write_int32(dst, "exec-cycles", execute_cycles); + bson_write_int32(dst, "video-count", video_count); + bson_finish_document(dst, wbptr); + + bson_start_document(dst, "timers", wbptr); + for (i = 0; i < 4; i++) + { + char tname[2] = {'0' + i, 0}; + bson_start_document(dst, tname, wbptr2); + bson_write_int32(dst, "count", timer[i].count); + bson_write_int32(dst, "reload", timer[i].reload); + bson_write_int32(dst, "prescale", timer[i].prescale); + bson_write_int32(dst, "freq-step", timer[i].frequency_step); + bson_write_int32(dst, "dsc", timer[i].direct_sound_channels); + bson_write_int32(dst, "irq", timer[i].irq); + bson_write_int32(dst, "status", timer[i].status); + bson_finish_document(dst, wbptr2); + } + bson_finish_document(dst, wbptr); + + return (unsigned int)(dst - startp); +} diff --git a/main.h b/main.h index 669bb9e..1ca400b 100644 --- a/main.h +++ b/main.h @@ -92,8 +92,8 @@ void reset_gba(void); void init_main(void); void game_name_ext(char *src, char *buffer, char *extension); -void main_write_savestate(void); -void main_read_savestate(void); +unsigned main_write_savestate(u8* ptr); +bool main_read_savestate(const u8 *src); u32 file_length(FILE *fp); diff --git a/savestate.c b/savestate.c new file mode 100644 index 0000000..61d579d --- /dev/null +++ b/savestate.c @@ -0,0 +1,153 @@ + +#include "common.h" + +const u8 *state_mem_read_ptr; +u8 *state_mem_write_ptr; + +const u8* bson_find_key(const u8 *srcp, const char *key) +{ + unsigned keyl = strlen(key) + 1; + unsigned doclen = bson_read_u32(srcp); + const u8* p = &srcp[4]; + while (*p != 0 && (p - srcp) < doclen) { + u8 tp = *p; + unsigned tlen = strlen((char*)&p[1]) + 1; + if (keyl == tlen && !memcmp(key, &p[1], tlen)) + return &p[tlen + 1]; + p += 1 + tlen; + if (tp == 3 || tp == 4) + p += bson_read_u32(p); + else if (tp == 5) + p += bson_read_u32(p) + 1 + 4; + else if (tp == 0x10) + p += 4; + } + return NULL; +} + +bool bson_read_int32(const u8 *srcp, const char *key, u32* value) +{ + const u8* p = srcp ? bson_find_key(srcp, key) : NULL; + if (!p) + return false; + *value = bson_read_u32(p); + return true; +} + +bool bson_read_int32_array(const u8 *srcp, const char *key, u32* value, unsigned cnt) +{ + const u8* p = srcp ? bson_find_key(srcp, key) : NULL; + if (p) { + unsigned arrsz = bson_read_u32(p); + p += 4; + if (arrsz < 5) + return false; + arrsz = (arrsz - 5) >> 3; + while (arrsz--) { + p += 4; // type and name + *value++ = bson_read_u32(p); + p += 4; // value + } + return true; + } + *value = bson_read_u32(p); + return false; +} + +bool bson_read_bytes(const u8 *srcp, const char *key, void* buffer, unsigned cnt) +{ + const u8* p = srcp ? bson_find_key(srcp, key) : NULL; + if (p) { + unsigned bufsz = bson_read_u32(p); + if (bufsz != cnt) + return false; + + // Skip byte array type and size + memcpy(buffer, &p[5], cnt); + return true; + } + return false; +} + +bool gba_load_state(const void* src) +{ + u32 i, tmp; + u8* srcptr = (u8*)src; + u32 docsize = bson_read_u32(srcptr); + if (docsize != GBA_STATE_MEM_SIZE) + return false; + + if (!bson_read_int32(srcptr, "info-magic", &tmp) || tmp != GBA_STATE_MAGIC) + return false; + if (!bson_read_int32(srcptr, "info-version", &tmp) || tmp != GBA_STATE_VERSION) + return false; + + if (!(cpu_read_savestate(srcptr) && + input_read_savestate(srcptr) && + main_read_savestate(srcptr) && + memory_read_savestate(srcptr) && + sound_read_savestate(srcptr))) + { + // TODO: reset state instead! Should revert instead?? + return false; + } + + // Generate converted palette (since it is not saved) + for(i = 0; i < 512; i++) + { + palette_ram_converted[i] = convert_palette(palette_ram[i]); + } + + video_reload_counters(); + + // Reset most of the frame state and dynarec state +#ifdef HAVE_DYNAREC + if (dynarec_enable) + init_caches(); +#endif + + instruction_count = 0; + reg[CHANGED_PC_STATUS] = 1; + reg[COMPLETED_FRAME] = 0; + reg[OAM_UPDATED] = 1; + gbc_sound_update = 1; + + return true; +} + +void gba_save_state(void* dst) +{ + u8 *stptr = (u8*)dst; + u8 *wrptr = (u8*)dst; + + // Initial bson size + bson_write_u32(wrptr, 0); + + // Add some info fields + bson_write_int32(wrptr, "info-magic", GBA_STATE_MAGIC); + bson_write_int32(wrptr, "info-version", GBA_STATE_VERSION); + + wrptr += cpu_write_savestate(wrptr); + wrptr += input_write_savestate(wrptr); + wrptr += main_write_savestate(wrptr); + wrptr += memory_write_savestate(wrptr); + wrptr += sound_write_savestate(wrptr); + + // The padding space is pushed into a padding field for easy parsing + { + unsigned padsize = GBA_STATE_MEM_SIZE - (wrptr - stptr); + padsize -= 1 + 9 + 4 + 1 + 1; + *wrptr++ = 0x05; // Byte array + bson_write_cstring(wrptr, "zpadding"); + bson_write_u32(wrptr, padsize); + *wrptr++ = 0; + wrptr += padsize; + } + + *wrptr++ = 0; + + // Update the doc size + bson_write_u32(stptr, wrptr - stptr); +} + + diff --git a/savestate.h b/savestate.h new file mode 100644 index 0000000..2f98102 --- /dev/null +++ b/savestate.h @@ -0,0 +1,76 @@ + +#ifndef SAVESTATE_H +#define SAVESTATE_H + +#define bson_write_u32(p, value) \ +{ \ + u32 __tval = (value); \ + *p++ = (u8)((__tval)); \ + *p++ = (u8)((__tval) >> 8); \ + *p++ = (u8)((__tval) >> 16); \ + *p++ = (u8)((__tval) >> 24); \ +} + +#define bson_read_u32(p) \ + ((p[3] << 24) | (p[2] << 16) | \ + (p[1] << 8) | (p[0] << 0)) + +#define bson_write_cstring(p, value) \ + memcpy(p, value, strlen(value)+1); \ + p += strlen(value)+1; + +#define bson_write_int32(p, key, value) \ + *p++ = 0x10; \ + bson_write_cstring(p, key); \ + bson_write_u32(p, value); + +#define bson_write_int32array(p, key, arr, cnt) \ +{ \ + u32 *arrptr = (u32*)(arr); \ + int _n; \ + *p++ = 0x4; \ + bson_write_cstring(p, key); \ + bson_write_u32(p, 5 + (cnt) * 8); \ + for (_n = 0; _n < (cnt); _n++) { \ + char ak[3] = {'0'+(_n/10), '0'+(_n%10), 0}; \ + bson_write_int32(p, ak, arrptr[_n]); \ + } \ + *p++ = 0; \ +} + +#define bson_write_bytes(p, key, value, vlen) \ + *p++ = 0x05; \ + bson_write_cstring(p, key); \ + bson_write_u32(p, vlen); \ + *p++ = 0; \ + memcpy(p, value, vlen); \ + p += vlen; + +#define bson_start_document(p, key, hdrptr) \ + *p++ = 0x03; \ + bson_write_cstring(p, key); \ + hdrptr = p; \ + bson_write_u32(p, 0); + +#define bson_finish_document(p, hdrptr) \ +{ \ + u32 _sz = p - hdrptr + 1; \ + *p++ = 0; \ + bson_write_u32(hdrptr, _sz); \ +} + +const u8* bson_find_key(const u8 *srcp, const char *key); +bool bson_read_int32(const u8 *srcp, const char *key, u32* value); +bool bson_read_int32_array(const u8 *srcp, const char *key, u32* value, unsigned cnt); +bool bson_read_bytes(const u8 *srcp, const char *key, void* buffer, unsigned cnt); + +/* this is an upper limit, leave room for future (?) stuff */ +#define GBA_STATE_MEM_SIZE (416*1024) +#define GBA_STATE_MAGIC 0x06BAC0DE +#define GBA_STATE_VERSION 0x00010000 + +bool gba_load_state(const void *src); +void gba_save_state(void *dst); + +#endif + diff --git a/sound.c b/sound.c index 46cdc19..6529663 100644 --- a/sound.c +++ b/sound.c @@ -29,7 +29,6 @@ u32 sound_on; static s16 sound_buffer[BUFFER_SIZE]; static u32 sound_buffer_base; -static u32 sound_last_cpu_ticks; static fixed16_16 gbc_sound_tick_step; /* Queue 1 sample to the top of the DS FIFO, wrap around circularly */ @@ -164,10 +163,10 @@ void sound_timer(fixed8_24 frequency_step, u32 channel) if(((ds->fifo_top - ds->fifo_base) % 32) <= 16) { if(dma[1].direct_sound_channel == channel) - dma_transfer(&dma[1]); + dma_transfer(1); if(dma[2].direct_sound_channel == channel) - dma_transfer(&dma[2]); + dma_transfer(2); } } @@ -567,7 +566,6 @@ void reset_sound(void) sound_on = 0; sound_buffer_base = 0; - sound_last_cpu_ticks = 0; memset(sound_buffer, 0, sizeof(sound_buffer)); for(i = 0; i < 2; i++, ds++) @@ -577,7 +575,6 @@ void reset_sound(void) ds->fifo_top = 0; ds->fifo_base = 0; ds->fifo_fractional = 0; - ds->last_cpu_ticks = 0; memset(ds->fifo, 0, 32); } @@ -610,25 +607,152 @@ void init_sound(int need_reset) reset_sound(); } -#define sound_savestate_builder(type) \ -void sound_##type##_savestate(void) \ -{ \ - state_mem_##type##_variable(sound_on); \ - state_mem_##type##_variable(sound_buffer_base); \ - state_mem_##type##_variable(sound_last_cpu_ticks); \ - state_mem_##type##_variable(gbc_sound_buffer_index); \ - state_mem_##type##_variable(gbc_sound_last_cpu_ticks); \ - state_mem_##type##_variable(gbc_sound_partial_ticks); \ - state_mem_##type##_variable(gbc_sound_master_volume_left); \ - state_mem_##type##_variable(gbc_sound_master_volume_right); \ - state_mem_##type##_variable(gbc_sound_master_volume); \ - state_mem_##type##_array(wave_samples); \ - state_mem_##type##_array(direct_sound_channel); \ - state_mem_##type##_array(gbc_sound_channel); \ +bool sound_read_savestate(const u8 *src) +{ + int i; + const u8 *snddoc = bson_find_key(src, "sound"); + + if (!( + bson_read_int32(snddoc, "on", &sound_on) && + bson_read_int32(snddoc, "buf-base", &sound_buffer_base) && + bson_read_int32(snddoc, "gbc-buf-idx", &gbc_sound_buffer_index) && + bson_read_int32(snddoc, "gbc-last-cpu-ticks", &gbc_sound_last_cpu_ticks) && + bson_read_int32(snddoc, "gbc-partial-ticks", &gbc_sound_partial_ticks) && + bson_read_int32(snddoc, "gbc-ms-vol-left", &gbc_sound_master_volume_left) && + bson_read_int32(snddoc, "gbc-ms-vol-right", &gbc_sound_master_volume_right) && + bson_read_int32(snddoc, "gbc-ms-vol", &gbc_sound_master_volume) && + bson_read_bytes(snddoc, "wav-samples", wave_samples, sizeof(wave_samples)))) + return false; + + for (i = 0; i < 2; i++) + { + direct_sound_struct *ds = &direct_sound_channel[i]; + char tn[4] = {'d', 's', '0' + i, 0}; + const u8 *sndchan = bson_find_key(snddoc, tn); + if (!( + bson_read_int32(sndchan, "status", &ds->status) && + bson_read_int32(sndchan, "volume", &ds->volume) && + bson_read_int32(sndchan, "fifo-base", &ds->fifo_base) && + bson_read_int32(sndchan, "fifo-top", &ds->fifo_top) && + bson_read_int32(sndchan, "fifo-frac", &ds->fifo_fractional) && + bson_read_bytes(sndchan, "fifo-bytes", ds->fifo, sizeof(ds->fifo)) && + bson_read_int32(sndchan, "buf-idx", &ds->buffer_index))) + return false; + } + + for (i = 0; i < 4; i++) + { + gbc_sound_struct *gs = &gbc_sound_channel[i]; + char tn[4] = {'g', 's', '0' + i, 0}; + const u8 *sndchan = bson_find_key(snddoc, tn); + if (!( + bson_read_int32(sndchan, "status", &gs->status) && + bson_read_int32(sndchan, "rate", &gs->rate) && + bson_read_int32(sndchan, "freq-step", &gs->frequency_step) && + bson_read_int32(sndchan, "sample-idx", &gs->sample_index) && + bson_read_int32(sndchan, "tick-cnt", &gs->tick_counter) && + bson_read_int32(sndchan, "volume", &gs->total_volume) && + bson_read_int32(sndchan, "active", &gs->active_flag) && + bson_read_int32(sndchan, "enable", &gs->master_enable) && + + bson_read_int32(sndchan, "env-vol0", &gs->envelope_initial_volume) && + bson_read_int32(sndchan, "env-vol", &gs->envelope_volume) && + bson_read_int32(sndchan, "env-dir", &gs->envelope_direction) && + bson_read_int32(sndchan, "env-status", &gs->envelope_status) && + bson_read_int32(sndchan, "env-step", &gs->envelope_step) && + bson_read_int32(sndchan, "env-ticks0", &gs->envelope_initial_ticks) && + bson_read_int32(sndchan, "env-ticks", &gs->envelope_ticks) && + bson_read_int32(sndchan, "sweep-status", &gs->sweep_status) && + bson_read_int32(sndchan, "sweep-dir", &gs->sweep_direction) && + bson_read_int32(sndchan, "sweep-ticks0", &gs->sweep_initial_ticks) && + bson_read_int32(sndchan, "sweep-ticks", &gs->sweep_ticks) && + bson_read_int32(sndchan, "sweep-shift", &gs->sweep_shift) && + bson_read_int32(sndchan, "wav-type", &gs->wave_type) && + bson_read_int32(sndchan, "wav-bank", &gs->wave_bank) && + bson_read_int32(sndchan, "wav-vol", &gs->wave_volume) && + + bson_read_int32(sndchan, "len-status", &gs->length_status) && + bson_read_int32(sndchan, "len-ticks", &gs->length_ticks) && + bson_read_int32(sndchan, "noise-type", &gs->noise_type) && + bson_read_int32(sndchan, "sample-tbl", &gs->sample_table_idx))) + return false; + } + + return true; } -sound_savestate_builder(read) -sound_savestate_builder(write) +unsigned sound_write_savestate(u8 *dst) +{ + int i; + u8 *wbptr, *startp = dst; + bson_start_document(dst, "sound", wbptr); + bson_write_int32(dst, "on", sound_on); + bson_write_int32(dst, "buf-base", sound_buffer_base); + bson_write_int32(dst, "gbc-buf-idx", gbc_sound_buffer_index); + bson_write_int32(dst, "gbc-last-cpu-ticks", gbc_sound_last_cpu_ticks); + bson_write_int32(dst, "gbc-partial-ticks", gbc_sound_partial_ticks); + bson_write_int32(dst, "gbc-ms-vol-left", gbc_sound_master_volume_left); + bson_write_int32(dst, "gbc-ms-vol-right", gbc_sound_master_volume_right); + bson_write_int32(dst, "gbc-ms-vol", gbc_sound_master_volume); + bson_write_bytes(dst, "wav-samples", wave_samples, sizeof(wave_samples)); + + for (i = 0; i < 2; i++) + { + u8 *wbptr2; + char tn[4] = {'d', 's', '0' + i, 0}; + bson_start_document(dst, tn, wbptr2); + bson_write_int32(dst, "status", direct_sound_channel[i].status); + bson_write_int32(dst, "volume", direct_sound_channel[i].volume); + bson_write_int32(dst, "fifo-base", direct_sound_channel[i].fifo_base); + bson_write_int32(dst, "fifo-top", direct_sound_channel[i].fifo_top); + bson_write_int32(dst, "fifo-frac", direct_sound_channel[i].fifo_fractional); + bson_write_bytes(dst, "fifo-bytes", direct_sound_channel[i].fifo, + sizeof(direct_sound_channel[i].fifo)); + bson_write_int32(dst, "buf-idx", direct_sound_channel[i].buffer_index); + bson_finish_document(dst, wbptr2); + } + + for (i = 0; i < 4; i++) + { + gbc_sound_struct *gs = &gbc_sound_channel[i]; + u8 *wbptr2; + char tn[4] = {'g', 's', '0' + i, 0}; + bson_start_document(dst, tn, wbptr2); + bson_write_int32(dst, "status", gs->status); + bson_write_int32(dst, "rate", gs->rate); + bson_write_int32(dst, "freq-step", gs->frequency_step); + bson_write_int32(dst, "sample-idx", gs->sample_index); + bson_write_int32(dst, "tick-cnt", gs->tick_counter); + bson_write_int32(dst, "volume", gs->total_volume); + bson_write_int32(dst, "active", gs->active_flag); + bson_write_int32(dst, "enable", gs->master_enable); + + bson_write_int32(dst, "env-vol0", gs->envelope_initial_volume); + bson_write_int32(dst, "env-vol", gs->envelope_volume); + bson_write_int32(dst, "env-dir", gs->envelope_direction); + bson_write_int32(dst, "env-status", gs->envelope_status); + bson_write_int32(dst, "env-step", gs->envelope_step); + bson_write_int32(dst, "env-ticks0", gs->envelope_initial_ticks); + bson_write_int32(dst, "env-ticks", gs->envelope_ticks); + bson_write_int32(dst, "sweep-status", gs->sweep_status); + bson_write_int32(dst, "sweep-dir", gs->sweep_direction); + bson_write_int32(dst, "sweep-ticks0", gs->sweep_initial_ticks); + bson_write_int32(dst, "sweep-ticks", gs->sweep_ticks); + bson_write_int32(dst, "sweep-shift", gs->sweep_shift); + bson_write_int32(dst, "wav-type", gs->wave_type); + bson_write_int32(dst, "wav-bank", gs->wave_bank); + bson_write_int32(dst, "wav-vol", gs->wave_volume); + + bson_write_int32(dst, "len-status", gs->length_status); + bson_write_int32(dst, "len-ticks", gs->length_ticks); + bson_write_int32(dst, "noise-type", gs->noise_type); + bson_write_int32(dst, "sample-tbl", gs->sample_table_idx); + bson_finish_document(dst, wbptr2); + } + + bson_finish_document(dst, wbptr); + return (unsigned int)(dst - startp); +} #include "libretro.h" diff --git a/sound.h b/sound.h index f8b2ef2..4dcd38f 100644 --- a/sound.h +++ b/sound.h @@ -52,7 +52,6 @@ typedef struct u32 buffer_index; direct_sound_status_type status; direct_sound_volume_type volume; - u32 last_cpu_ticks; } direct_sound_struct; typedef enum @@ -114,8 +113,8 @@ void sound_timer(fixed8_24 frequency_step, u32 channel); void sound_reset_fifo(u32 channel); void update_gbc_sound(u32 cpu_ticks); void init_sound(int need_reset); -void sound_write_savestate(void); -void sound_read_savestate(void); +unsigned sound_write_savestate(u8 *dst); +bool sound_read_savestate(const u8 *src); void render_audio(void); diff --git a/video.c b/video.c index 691e51f..e4a3797 100644 --- a/video.c +++ b/video.c @@ -2584,7 +2584,7 @@ render_scanline_affine_builder(transparent, alpha); #define bitmap_render_pixel_mode3(alpha_op) \ - convert_palette(current_pixel); \ + current_pixel = convert_palette(current_pixel); \ *dest_ptr = current_pixel \ #define bitmap_render_pixel_mode4(alpha_op) \