diff --git a/gba_memory.c b/gba_memory.c index 978c58f..a6a6093 100644 --- a/gba_memory.c +++ b/gba_memory.c @@ -1179,16 +1179,24 @@ typedef enum #define RTC_WRITE_TIME_FULL 1 #define RTC_WRITE_STATUS 2 +static bool rtc_enabled = false, rumble_enabled = false; + +// I/O registers (for RTC, rumble, etc) +u8 gpio_regs[3]; + +// RTC tracking variables u32 rtc_state = RTC_DISABLED; u32 rtc_write_mode; -u8 rtc_registers[3]; u32 rtc_command; -u32 rtc_data[12]; +u64 rtc_data; +u32 rtc_data_bits; u32 rtc_status = 0x40; -u32 rtc_data_bytes; s32 rtc_bit_count; -static u32 encode_bcd(u8 value) +// Rumble trackin vars, not really preserved (it's just aproximate) +static u32 rumble_enable_tick, rumble_ticks; + +static u8 encode_bcd(u8 value) { int l = 0; int h = 0; @@ -1200,224 +1208,189 @@ static u32 encode_bcd(u8 value) return h * 16 + l; } -// RTC writes need to reflect in the bytes [0xC4..0xC9] of the gamepak -#define write_rtc_register(index, _value) \ - update_address = 0x80000C4 + (index * 2); \ - rtc_registers[index] = _value; \ - rtc_page_index = update_address >> 15; \ - map = memory_map_read[rtc_page_index]; \ - \ - if(map) { \ - address16(map, update_address & 0x7FFF) = eswap16(_value); \ - } \ - -void function_cc write_rtc(u32 address, u32 value) -{ - u32 rtc_page_index; - u32 update_address; - u8 *map; - - value &= 0xFFFF; - - switch(address) - { - // RTC command - // Bit 0: SCHK, perform action - // Bit 1: IO, input/output command data - // Bit 2: CS, select input/output? If high make I/O write only - case 0xC4: - if(rtc_state == RTC_DISABLED) - rtc_state = RTC_IDLE; - if(!(rtc_registers[0] & 0x04)) - value = (rtc_registers[0] & 0x02) | (value & ~0x02); - if(rtc_registers[2] & 0x01) - { - // To begin writing a command 1, 5 must be written to the command - // registers. - if((rtc_state == RTC_IDLE) && (rtc_registers[0] == 0x01) && - (value == 0x05)) - { - // We're now ready to begin receiving a command. - write_rtc_register(0, value); - rtc_state = RTC_COMMAND; - rtc_command = 0; - rtc_bit_count = 7; - } - else - { - write_rtc_register(0, value); - switch(rtc_state) - { - // Accumulate RTC command by receiving the next bit, and if we - // have accumulated enough bits to form a complete command - // execute it. - case RTC_COMMAND: - if(rtc_registers[0] & 0x01) - { - rtc_command |= ((value & 0x02) >> 1) << rtc_bit_count; - rtc_bit_count--; - } - - // Have we received a full RTC command? If so execute it. - if(rtc_bit_count < 0) - { - switch(rtc_command) - { - // Resets RTC - case RTC_COMMAND_RESET: - rtc_state = RTC_IDLE; - memset(rtc_registers, 0, sizeof(rtc_registers)); - break; - - // Sets status of RTC - case RTC_COMMAND_WRITE_STATUS: - rtc_state = RTC_INPUT_DATA; - rtc_data_bytes = 1; - rtc_write_mode = RTC_WRITE_STATUS; - break; - - // Outputs current status of RTC - case RTC_COMMAND_READ_STATUS: - rtc_state = RTC_OUTPUT_DATA; - rtc_data_bytes = 1; - rtc_data[0] = rtc_status; - break; - - // Actually outputs the time, all of it - case RTC_COMMAND_OUTPUT_TIME_FULL: - { - struct tm *current_time; - time_t current_time_flat; - - time(¤t_time_flat); - current_time = localtime(¤t_time_flat); - - rtc_state = RTC_OUTPUT_DATA; - rtc_data_bytes = 7; - rtc_data[0] = encode_bcd(current_time->tm_year); - rtc_data[1] = encode_bcd(current_time->tm_mon + 1); - rtc_data[2] = encode_bcd(current_time->tm_mday); - rtc_data[3] = encode_bcd(current_time->tm_wday); - rtc_data[4] = encode_bcd(current_time->tm_hour); - rtc_data[5] = encode_bcd(current_time->tm_min); - rtc_data[6] = encode_bcd(current_time->tm_sec); - - break; - } - - // Only outputs the current time of day. - case RTC_COMMAND_OUTPUT_TIME: - { - struct tm *current_time; - time_t current_time_flat; - - time(¤t_time_flat); - current_time = localtime(¤t_time_flat); - - rtc_state = RTC_OUTPUT_DATA; - rtc_data_bytes = 3; - rtc_data[0] = encode_bcd(current_time->tm_hour); - rtc_data[1] = encode_bcd(current_time->tm_min); - rtc_data[2] = encode_bcd(current_time->tm_sec); - break; - } - } - rtc_bit_count = 0; - } - break; - - // Receive parameters from the game as input to the RTC - // for a given command. Read one bit at a time. - case RTC_INPUT_DATA: - // Bit 1 of parameter A must be high for input - if(rtc_registers[1] & 0x02) - { - // Read next bit for input - if(!(value & 0x01)) - { - rtc_data[rtc_bit_count >> 3] |= - ((value & 0x01) << (7 - (rtc_bit_count & 0x07))); - } - else - { - rtc_bit_count++; - - if(rtc_bit_count == (signed)(rtc_data_bytes * 8)) - { - rtc_state = RTC_IDLE; - switch(rtc_write_mode) - { - case RTC_WRITE_STATUS: - rtc_status = rtc_data[0]; - break; - - default: - break; - } - } - } - } - break; - - case RTC_OUTPUT_DATA: - // Bit 1 of parameter A must be low for output - if(!(rtc_registers[1] & 0x02)) - { - // Write next bit to output, on bit 1 of parameter B - if(!(value & 0x01)) - { - u8 current_output_byte = rtc_registers[2]; - - current_output_byte = - (current_output_byte & ~0x02) | - (((rtc_data[rtc_bit_count >> 3] >> - (rtc_bit_count & 0x07)) & 0x01) << 1); - - write_rtc_register(0, current_output_byte); - - } - else - { - rtc_bit_count++; - - if(rtc_bit_count == (signed)(rtc_data_bytes * 8)) - { - rtc_state = RTC_IDLE; - memset(rtc_registers, 0, sizeof(rtc_registers)); - } - } - } - break; - - default: - break; - } - } +void update_gpio_romregs() { + if (rtc_enabled || rumble_enabled) { + // Update the registers in the ROM mapped buffer. + u8 *map = memory_map_read[0x8000000 >> 15]; + if (map) { + if (gpio_regs[2]) { + // Registers are visible, readable: + address16(map, 0xC4) = eswap16(gpio_regs[0]); + address16(map, 0xC6) = eswap16(gpio_regs[1]); + address16(map, 0xC8) = eswap16(gpio_regs[2]); + } else { + // Registers are write-only, just read out zero + address16(map, 0xC4) = 0; + address16(map, 0xC6) = 0; + address16(map, 0xC8) = 0; } - else - { - write_rtc_register(2, value); - } - break; - - // Write parameter A - case 0xC6: - write_rtc_register(1, value); - break; - - // Write parameter B - case 0xC8: - write_rtc_register(2, value); - break; + } } } -#define write_rtc8() \ +#define GPIO_RTC_CLK 0x1 +#define GPIO_RTC_DAT 0x2 +#define GPIO_RTC_CSS 0x4 -#define write_rtc16() \ - write_rtc(address & 0xFF, value) \ +static void write_rtc(u8 old, u8 new) +{ + // RTC works using a high CS and falling edge capture for the clock signal. + if (!(new & GPIO_RTC_CSS)) { + // Chip select is down, reset the RTC protocol. And do not process input. + rtc_state = RTC_IDLE; + rtc_command = 0; + rtc_bit_count = 0; + return; + } -#define write_rtc32() \ + // CS low to high transition! + if (!(old & GPIO_RTC_CSS)) + rtc_state = RTC_COMMAND; + + if ((old & GPIO_RTC_CLK) && !(new & GPIO_RTC_CLK)) { + // Advance clock state, input/ouput data. + switch (rtc_state) { + case RTC_COMMAND: + rtc_command <<= 1; + rtc_command |= ((new >> 1) & 1); + // 8 bit command read, process: + if (++rtc_bit_count == 8) { + switch (rtc_command) { + case RTC_COMMAND_RESET: + case RTC_COMMAND_WRITE_STATUS: + rtc_state = RTC_INPUT_DATA; + rtc_data = 0; + rtc_data_bits = 8; + rtc_write_mode = RTC_WRITE_STATUS; + break; + case RTC_COMMAND_READ_STATUS: + rtc_state = RTC_OUTPUT_DATA; + rtc_data_bits = 8; + rtc_data = rtc_status; + break; + case RTC_COMMAND_OUTPUT_TIME_FULL: + { + struct tm *current_time; + time_t current_time_flat; + time(¤t_time_flat); + current_time = localtime(¤t_time_flat); + + rtc_state = RTC_OUTPUT_DATA; + rtc_data_bits = 56; + rtc_data = ((u64)encode_bcd(current_time->tm_year)) | + ((u64)encode_bcd(current_time->tm_mon+1)<< 8) | + ((u64)encode_bcd(current_time->tm_mday) << 16) | + ((u64)encode_bcd(current_time->tm_wday) << 24) | + ((u64)encode_bcd(current_time->tm_hour) << 32) | + ((u64)encode_bcd(current_time->tm_min) << 40) | + ((u64)encode_bcd(current_time->tm_sec) << 48); + } + break; + case RTC_COMMAND_OUTPUT_TIME: + { + struct tm *current_time; + time_t current_time_flat; + time(¤t_time_flat); + current_time = localtime(¤t_time_flat); + + rtc_state = RTC_OUTPUT_DATA; + rtc_data_bits = 24; + rtc_data = (encode_bcd(current_time->tm_hour)) | + (encode_bcd(current_time->tm_min) << 8) | + (encode_bcd(current_time->tm_sec) << 16); + } + break; + }; + rtc_bit_count = 0; + } + break; + + case RTC_INPUT_DATA: + rtc_data <<= 1; + rtc_data |= ((new >> 1) & 1); + rtc_data_bits--; + if (!rtc_data_bits) { + rtc_status = rtc_data; // HACK: assuming write status here. + rtc_state = RTC_IDLE; + } + break; + + case RTC_OUTPUT_DATA: + // Output the next bit from rtc_data + if (!(gpio_regs[1] & 0x2)) { + // Only output if the port is set to OUT! + u32 bit = rtc_data & 1; + gpio_regs[0] = (new & ~0x2) | ((bit) << 1); + } + rtc_data >>= 1; + rtc_data_bits--; + + if (!rtc_data_bits) + rtc_state = RTC_IDLE; // Finish transmission! + + break; + }; + } +} + +static void write_rumble(u8 old, u8 new) { + if (new && !old) + rumble_enable_tick = cpu_ticks; + else if (!new && old) { + rumble_ticks += (cpu_ticks - rumble_enable_tick); + rumble_enable_tick = 0; + } +} + +void rumble_frame_reset() { + // Reset the tick initial value to frame start (only if active) + rumble_ticks = 0; + if (rumble_enable_tick) + rumble_enable_tick = cpu_ticks; +} + +float rumble_active_pct() { + // Calculate the percentage of Rumble active for this frame. + u32 active_ticks = rumble_ticks; + // If the rumble is still active, account for the due cycles + if (rumble_enable_tick) + active_ticks += (cpu_ticks - rumble_enable_tick); + + return active_ticks / (GBC_BASE_RATE / 60); +} + +void function_cc write_gpio(u32 address, u32 value) { + u8 prev_value = gpio_regs[0]; + switch(address) { + case 0xC4: + // Any writes do not affect input pins: + gpio_regs[0] = (gpio_regs[0] & ~gpio_regs[1]) | (value & gpio_regs[1]); + break; + case 0xC6: + gpio_regs[1] = value & 0xF; + break; + case 0xC8: /* I/O port control */ + gpio_regs[2] = value & 1; + break; + }; + + // If the game has an RTC, ensure it gets the data + if (rtc_enabled && (prev_value & 0x7) != (gpio_regs[0] & 0x7)) + write_rtc(prev_value & 0x7, gpio_regs[0] & 0x7); + + if (rumble_enabled && (prev_value & 0x8) != (gpio_regs[0] & 0x8)) + write_rumble(prev_value & 0x8, gpio_regs[0] & 0x8); + + // Reflect the values + update_gpio_romregs(); +} + +#define write_gpio8() \ + +#define write_gpio16() \ + write_gpio(address & 0xFF, value) \ + +#define write_gpio32() \ #define write_memory(type) \ switch(address >> 24) \ @@ -1460,7 +1433,7 @@ void function_cc write_rtc(u32 address, u32 value) \ case 0x08: \ /* gamepak ROM or RTC */ \ - write_rtc##type(); \ + write_gpio##type(); \ break; \ \ case 0x09: \ @@ -1571,6 +1544,7 @@ typedef struct #define FLAGS_FLASH_128KB 0x0001 #define FLAGS_RUMBLE 0x0002 +#define FLAGS_RTC 0x0004 #include "gba_over.h" @@ -1602,6 +1576,12 @@ static void load_game_config_over(gamepak_info_t *gpinfo) flash_bank_cnt = FLASH_SIZE_128KB; } + if (gbaover[i].flags & FLAGS_RTC) + rtc_enabled = true; + + if (gbaover[i].flags & FLAGS_RUMBLE) + rumble_enabled = true; + if (gbaover[i].translation_gate_target_1 != 0) { translation_gate_target_pc[translation_gate_targets] = gbaover[i].translation_gate_target_1; @@ -2192,12 +2172,9 @@ u8 *load_gamepak_page(u32 physical_index) // Map it to the read handlers now map_rom_entry(read, physical_index, swap_location, gamepak_size >> 15); - // If RTC is active page the RTC register bytes so they can be read - if ((rtc_state != RTC_DISABLED) && (physical_index == 0)) { - address16(swap_location, 0xC4) = eswap16(rtc_registers[0]); - address16(swap_location, 0xC6) = eswap16(rtc_registers[1]); - address16(swap_location, 0xC8) = eswap16(rtc_registers[2]); - } + // When mapping page 0, we might need to reflect the GPIO regs. + if (physical_index == 0) + update_gpio_romregs(); return swap_location; } @@ -2288,11 +2265,13 @@ void init_memory(void) eeprom_mode = EEPROM_BASE_MODE; eeprom_address = 0; eeprom_counter = 0; + rumble_enable_tick = 0; + rumble_ticks = 0; flash_mode = FLASH_BASE_MODE; rtc_state = RTC_DISABLED; - memset(rtc_registers, 0, sizeof(rtc_registers)); + memset(gpio_regs, 0, sizeof(gpio_regs)); reg[REG_BUS_VALUE] = 0xe129f000; } @@ -2315,7 +2294,7 @@ bool memory_check_savestate(const u8 *src) static const char *vars32[] = { "backup-type","flash-mode", "flash-cmd-pos", "flash-bank-num", "flash-dev-id", "flash-size", "eeprom-size", "eeprom-mode", "eeprom-addr", "eeprom-counter", - "rtc-state", "rtc-write-mode", "rtc-cmd", "rtc-status", "rtc-data-byte-cnt", "rtc-bit-cnt", + "rtc-state", "rtc-write-mode", "rtc-cmd", "rtc-status", "rtc-data-bit-cnt", "rtc-bit-cnt", }; static const char *dmavars32[] = { "src-addr", "dst-addr", "src-dir", "dst-dir", @@ -2342,7 +2321,7 @@ bool memory_check_savestate(const u8 *src) if (!bson_contains_key(bakdoc, vars32[i], BSON_TYPE_INT32)) return false; - if (!bson_contains_key(bakdoc, "rtc-regs", BSON_TYPE_BIN) || + if (!bson_contains_key(bakdoc, "gpio-regs", BSON_TYPE_BIN) || !bson_contains_key(bakdoc, "rtc-data-words", BSON_TYPE_ARR)) return false; @@ -2364,6 +2343,7 @@ bool memory_check_savestate(const u8 *src) bool memory_read_savestate(const u8 *src) { int i; + u32 rtc_data_array[2]; const u8 *memdoc = bson_find_key(src, "memory"); const u8 *bakdoc = bson_find_key(src, "backup"); const u8 *dmadoc = bson_find_key(src, "dma"); @@ -2391,15 +2371,15 @@ bool memory_read_savestate(const u8 *src) bson_read_int32(bakdoc, "eeprom-addr", &eeprom_address) && bson_read_int32(bakdoc, "eeprom-counter", &eeprom_counter) && + bson_read_bytes(bakdoc, "gpio-regs", gpio_regs, sizeof(gpio_regs)) && + 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-data-bit-cnt", &rtc_data_bits) && 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])))) + bson_read_int32_array(bakdoc, "rtc-data-words", rtc_data_array, 2))) return false; for (i = 0; i < DMA_CHAN_CNT; i++) @@ -2420,6 +2400,8 @@ bool memory_read_savestate(const u8 *src) return false; } + rtc_data = rtc_data_array[0] | (((u64)rtc_data_array[1]) << 32); + return true; } @@ -2427,6 +2409,8 @@ unsigned memory_write_savestate(u8 *dst) { int i; u8 *wbptr, *wbptr2, *startp = dst; + u32 rtc_data_array[2] = { (u32)rtc_data, (u32)(rtc_data >> 32) }; + bson_start_document(dst, "memory", wbptr); bson_write_bytes(dst, "iwram", &iwram[0x8000], 0x8000); bson_write_bytes(dst, "ewram", ewram, 0x40000); @@ -2450,15 +2434,14 @@ unsigned memory_write_savestate(u8 *dst) bson_write_int32(dst, "eeprom-addr", eeprom_address); bson_write_int32(dst, "eeprom-counter", eeprom_counter); + bson_write_bytes(dst, "gpio-regs", gpio_regs, sizeof(gpio_regs)); 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-data-bit-cnt", rtc_data_bits); 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_write_int32array(dst, "rtc-data-words", rtc_data_array, 2); bson_finish_document(dst, wbptr); bson_start_document(dst, "dma", wbptr); @@ -2525,7 +2508,8 @@ static s32 load_gamepak_raw(const char *name) return -1; } -u32 load_gamepak(const struct retro_game_info* info, const char *name) +u32 load_gamepak(const struct retro_game_info* info, const char *name, + int force_rtc, int force_rumble) { gamepak_info_t gpinfo; @@ -2542,9 +2526,17 @@ u32 load_gamepak(const struct retro_game_info* info, const char *name) translation_gate_targets = 0; flash_device_id = FLASH_DEVICE_MACRONIX_64KB; flash_bank_cnt = FLASH_SIZE_64KB; + rtc_enabled = false; + rumble_enabled = false; load_game_config_over(&gpinfo); + // Forced RTC / Rumble modes, override the autodetect logic. + if (force_rtc != FEAT_AUTODETECT) + rtc_enabled = (force_rtc == FEAT_ENABLE); + if (force_rumble != FEAT_AUTODETECT) + rumble_enabled = (force_rumble == FEAT_ENABLE); + return 0; } diff --git a/gba_memory.h b/gba_memory.h index 7292b38..13d8044 100644 --- a/gba_memory.h +++ b/gba_memory.h @@ -22,6 +22,10 @@ #include "libretro.h" +#define FEAT_AUTODETECT -1 +#define FEAT_DISABLE 0 +#define FEAT_ENABLE 1 + #define DMA_CHAN_CNT 4 #define DMA_START_IMMEDIATELY 0 @@ -221,7 +225,10 @@ u32 function_cc read_eeprom(void); void function_cc write_eeprom(u32 address, u32 value); u8 read_backup(u32 address); void function_cc write_backup(u32 address, u32 value); -void function_cc write_rtc(u32 address, u32 value); +void function_cc write_gpio(u32 address, u32 value); + +void rumble_frame_reset(); +float rumble_active_pct(); /* EDIT: Shouldn't this be extern ?! */ extern const u32 def_seq_cycles[16][2]; @@ -237,7 +244,8 @@ extern char gamepak_filename[512]; cpu_alert_type dma_transfer(unsigned dma_chan, int *cycles); u8 *memory_region(u32 address, u32 *memory_limit); -u32 load_gamepak(const struct retro_game_info* info, const char *name); +u32 load_gamepak(const struct retro_game_info* info, const char *name, + int force_rtc, int force_rumble); s32 load_bios(char *name); void init_memory(void); void init_gamepak_buffer(void); diff --git a/gba_over.h b/gba_over.h index 7fd6654..fdf0aed 100644 --- a/gba_over.h +++ b/gba_over.h @@ -109,6 +109,17 @@ static const ini_t gbaover[] = { 0, /* translation_gate_target_2 */ 0, /* translation_gate_target_3 */ }, + { + // Shin Bokura no Taiyou: Gyakushuu no Sabata (J) + "BOKTAI3", /* gamepak_title */ + "U33J", /* gamepak_code */ + "A4", /* gamepak_maker */ + FLAGS_RTC, /* flags */ + 0, /* idle_loop_target_pc */ + 0, /* translation_gate_target_1 */ + 0, /* translation_gate_target_2 */ + 0, /* translation_gate_target_3 */ + }, { // Bomberman Jetters Game Collection (J) "BOMBERMANJGC", /* gamepak_title */ @@ -498,6 +509,39 @@ static const ini_t gbaover[] = { 0, /* translation_gate_target_2 */ 0, /* translation_gate_target_3 */ }, + { + // Legendz - Sign of Nekuromu (J) + "LEGENDZSON__", /* gamepak_title */ + "BLVJ", /* gamepak_code */ + "B2", /* gamepak_maker */ + FLAGS_RTC, /* flags */ + 0, /* idle_loop_target_pc */ + 0, /* translation_gate_target_1 */ + 0, /* translation_gate_target_2 */ + 0, /* translation_gate_target_3 */ + }, + { + // Legendz - Yomigaeru Shiren no Shima (J) + "LEGENDZSHIMA", /* gamepak_title */ + "BLJJ", /* gamepak_code */ + "B2", /* gamepak_maker */ + FLAGS_RTC, /* flags */ + 0, /* idle_loop_target_pc */ + 0, /* translation_gate_target_1 */ + 0, /* translation_gate_target_2 */ + 0, /* translation_gate_target_3 */ + }, + { + // Legendz - Buhwarhaneun Siryeonyi Seom (K) + "LEGENDZSHIMA", /* gamepak_title */ + "BLJK", /* gamepak_code */ + "B2", /* gamepak_maker */ + FLAGS_RTC, /* flags */ + 0, /* idle_loop_target_pc */ + 0, /* translation_gate_target_1 */ + 0, /* translation_gate_target_2 */ + 0, /* translation_gate_target_3 */ + }, { // Magical Houshin (J) "M HOUSHIN", /* gamepak_title */ @@ -976,7 +1020,7 @@ static const ini_t gbaover[] = { "POKEMON EMER", /* gamepak_title */ "BPEE", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0x80008ce, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -987,7 +1031,7 @@ static const ini_t gbaover[] = { "POKEMON EMER", /* gamepak_title */ "BPEJ", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0x80008ce, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -998,7 +1042,7 @@ static const ini_t gbaover[] = { "POKEMON EMER", /* gamepak_title */ "BPED", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0x80008ce, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1009,7 +1053,7 @@ static const ini_t gbaover[] = { "POKEMON EMER", /* gamepak_title */ "BPEF", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0x80008ce, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1020,7 +1064,7 @@ static const ini_t gbaover[] = { "POKEMON EMER", /* gamepak_title */ "BPES", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0x80008ce, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1031,7 +1075,7 @@ static const ini_t gbaover[] = { "POKEMON EMER", /* gamepak_title */ "BPEI", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0x80008ce, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1042,7 +1086,7 @@ static const ini_t gbaover[] = { "POKEMON SAPP", /* gamepak_title */ "AXPE", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1053,7 +1097,7 @@ static const ini_t gbaover[] = { "POKEMON SAPP", /* gamepak_title */ "AXPJ", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1064,7 +1108,7 @@ static const ini_t gbaover[] = { "POKEMON SAPP", /* gamepak_title */ "AXPD", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1075,7 +1119,7 @@ static const ini_t gbaover[] = { "POKEMON SAPP", /* gamepak_title */ "AXPI", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1086,7 +1130,7 @@ static const ini_t gbaover[] = { "POKEMON SAPP", /* gamepak_title */ "AXPS", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1097,7 +1141,7 @@ static const ini_t gbaover[] = { "POKEMON SAPP", /* gamepak_title */ "AXPF", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1108,7 +1152,7 @@ static const ini_t gbaover[] = { "POKEMON RUBY", /* gamepak_title */ "AXVE", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1119,7 +1163,7 @@ static const ini_t gbaover[] = { "POKEMON RUBY", /* gamepak_title */ "AXVJ", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1130,7 +1174,7 @@ static const ini_t gbaover[] = { "POKEMON RUBY", /* gamepak_title */ "AXVD", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1141,7 +1185,7 @@ static const ini_t gbaover[] = { "POKEMON RUBY", /* gamepak_title */ "AXVI", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1152,7 +1196,7 @@ static const ini_t gbaover[] = { "POKEMON RUBY", /* gamepak_title */ "AXVS", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1163,7 +1207,7 @@ static const ini_t gbaover[] = { "POKEMON RUBY", /* gamepak_title */ "AXVF", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1452,12 +1496,23 @@ static const ini_t gbaover[] = { 0, /* translation_gate_target_2 */ 0, /* translation_gate_target_3 */ }, + { + // RockMan EXE 4.5 - Real Operation (J) + "ROCKEXE4.5RO", /* gamepak_title */ + "BR4J", /* gamepak_code */ + "08", /* gamepak_maker */ + FLAGS_RTC, /* flags */ + 0, /* idle_loop_target_pc */ + 0, /* translation_gate_target_1 */ + 0, /* translation_gate_target_2 */ + 0, /* translation_gate_target_3 */ + }, { // Sennen Kazoku (J) "SENNENKAZOKU", /* gamepak_title */ "BKAJ", /* gamepak_code */ "01", /* gamepak_maker */ - FLAGS_FLASH_128KB, /* flags */ + FLAGS_FLASH_128KB | FLAGS_RTC, /* flags */ 0, /* idle_loop_target_pc */ 0, /* translation_gate_target_1 */ 0, /* translation_gate_target_2 */ @@ -1782,6 +1837,39 @@ static const ini_t gbaover[] = { 0, /* translation_gate_target_2 */ 0, /* translation_gate_target_3 */ }, + { + // Wario Ware, Twisted (U) + "WARIOTWISTED", /* gamepak_title */ + "RZWE", /* gamepak_code */ + "01", /* gamepak_maker */ + FLAGS_RUMBLE, /* flags */ + 0, /* idle_loop_target_pc */ + 0, /* translation_gate_target_1 */ + 0, /* translation_gate_target_2 */ + 0, /* translation_gate_target_3 */ + }, + { + // Wario Ware, Twisted (E) + "WARIOTWISTED", /* gamepak_title */ + "RZWP", /* gamepak_code */ + "01", /* gamepak_maker */ + FLAGS_RUMBLE, /* flags */ + 0, /* idle_loop_target_pc */ + 0, /* translation_gate_target_1 */ + 0, /* translation_gate_target_2 */ + 0, /* translation_gate_target_3 */ + }, + { + // Wario Ware, Twisted (J) + "MAWARUWARIO", /* gamepak_title */ + "RZWJ", /* gamepak_code */ + "01", /* gamepak_maker */ + FLAGS_RUMBLE, /* flags */ + 0, /* idle_loop_target_pc */ + 0, /* translation_gate_target_1 */ + 0, /* translation_gate_target_2 */ + 0, /* translation_gate_target_3 */ + }, { // Yu-Gi-Oh! - Dungeon Dice Monsters (U) "YU-GI-OH DDM", /* gamepak_title */ diff --git a/libretro/libretro.c b/libretro/libretro.c index 8cd96e5..697e59d 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -84,6 +84,7 @@ static retro_video_refresh_t video_cb; static retro_audio_sample_batch_t audio_batch_cb; static retro_input_poll_t input_poll_cb; static retro_environment_t environ_cb; +static retro_set_rumble_state_t rumble_cb; struct retro_perf_callback perf_cb; @@ -91,6 +92,8 @@ int dynarec_enable; boot_mode selected_boot_mode = boot_game; int sprite_limit = 1; +static int rtc_mode = FEAT_AUTODETECT, rumble_mode = FEAT_AUTODETECT; + u32 idle_loop_target_pc = 0xFFFFFFFF; u32 translation_gate_target_pc[MAX_TRANSLATION_GATES]; u32 translation_gate_targets = 0; @@ -744,7 +747,7 @@ static void extract_directory(char* buf, const char* path, size_t size) strncpy(buf, ".", size); } -static void check_variables(int started_from_load) +static void check_variables(bool started_from_load) { struct retro_variable var; bool frameskip_type_prev; @@ -776,7 +779,6 @@ static void check_variables(int started_from_load) if (started_from_load) { var.key = "gpsp_bios"; var.value = 0; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "auto")) @@ -789,7 +791,6 @@ static void check_variables(int started_from_load) var.key = "gpsp_boot_mode"; var.value = 0; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "game")) @@ -797,6 +798,30 @@ static void check_variables(int started_from_load) else if (!strcmp(var.value, "bios")) selected_boot_mode = boot_bios; } + + var.key = "gpsp_rtc"; + var.value = 0; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (!strcmp(var.value, "disabled")) + rtc_mode = FEAT_DISABLE; + else if (!strcmp(var.value, "enabled")) + rtc_mode = FEAT_ENABLE; + else + rtc_mode = FEAT_AUTODETECT; + } + + var.key = "gpsp_rumble"; + var.value = 0; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (!strcmp(var.value, "disabled")) + rumble_mode = FEAT_DISABLE; + else if (!strcmp(var.value, "enabled")) + rumble_mode = FEAT_ENABLE; + else + rumble_mode = FEAT_AUTODETECT; + } } var.key = "gpsp_sprlim"; @@ -956,7 +981,7 @@ bool retro_load_game(const struct retro_game_info* info) if (!info) return false; - check_variables(1); + check_variables(true); set_input_descriptors(); char filename_bios[MAX_PATH]; @@ -1000,12 +1025,18 @@ bool retro_load_game(const struct retro_game_info* info) } memset(gamepak_backup, 0xff, sizeof(gamepak_backup)); - if (load_gamepak(info, info->path) != 0) + if (load_gamepak(info, info->path, rtc_mode, rumble_mode) != 0) { error_msg("Could not load the game file."); return false; } + struct retro_rumble_interface rumbleif; + if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumbleif)) + rumble_cb = rumbleif.set_rumble_state; + else + rumble_cb = NULL; + reset_gba(); set_memory_descriptors(); @@ -1063,6 +1094,8 @@ void retro_run(void) input_poll_cb(); update_input(); + rumble_frame_reset(); + /* Check whether current frame should * be skipped */ skip_next_frame = 0; @@ -1145,11 +1178,18 @@ void retro_run(void) execute_arm(execute_cycles); } + if (rumble_cb) { + // TODO: Add some user-option to select a rumble policy + u32 strength = 0xffff * rumble_active_pct(); + rumble_cb(0, RETRO_RUMBLE_WEAK, MIN(strength, 0xffff)); + rumble_cb(0, RETRO_RUMBLE_STRONG, MIN(strength, 0xffff) / 2); + } + audio_run(); video_run(); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) - check_variables(0); + check_variables(false); } unsigned retro_api_version(void) diff --git a/libretro/libretro_core_options.h b/libretro/libretro_core_options.h index 5d8a790..864ee84 100644 --- a/libretro/libretro_core_options.h +++ b/libretro/libretro_core_options.h @@ -87,6 +87,30 @@ struct retro_core_option_definition option_defs_us[] = { }, "disabled" }, + { + "gpsp_rtc", + "RTC support", + "Sets the RTC support for the emulated cartridge. Autodetect uses a ROM database that works with most commercial titles. You might need to force RTC when using homebrew or ROM hacks.", + { + { "auto", NULL }, + { "enabled", NULL }, + { "disabled", NULL }, + { NULL, NULL }, + }, + "auto" + }, + { + "gpsp_rumble", + "Rumble support", + "Sets the Rumble support for the emulated cartridge. Autodetect uses a ROM database that works with most commercial titles. You might want to force Rumble when using homebrew or ROM hacks that support it. You can also force-disable it if you don't like it.", + { + { "auto", NULL }, + { "enabled", NULL }, + { "disabled", NULL }, + { NULL, NULL }, + }, + "auto" + }, { "gpsp_frameskip", "Frameskip", diff --git a/mips/mips_emit.h b/mips/mips_emit.h index 6a4448d..68c1638 100644 --- a/mips/mips_emit.h +++ b/mips/mips_emit.h @@ -2471,7 +2471,7 @@ static void emit_saveaccess_stub(u8 **tr_ptr) { // Writes to region 8 are directed to RTC (only 16 bit ones though) tmemld[1][8] = (u32)translation_ptr; - emit_mem_call(&write_rtc, 0xFE); + emit_mem_call(&write_gpio, 0xFE); // These are for region 0xD where EEPROM is mapped. Addr is ignored // Value is limited to one bit (both reading and writing!) @@ -2524,7 +2524,7 @@ static void emit_saveaccess_stub(u8 **tr_ptr) { mips_emit_xori(reg_rv, reg_temp, 0x08); mips_emit_b(bne, reg_rv, reg_zero, st_phndlr_branch(strop)); if (strop == 1) { - emit_mem_call(&write_rtc, 0xFF); // Addr + emit_mem_call(&write_gpio, 0xFF); // Addr } else { mips_emit_nop(); mips_emit_jr(mips_reg_ra); // Do nothing diff --git a/savestate.h b/savestate.h index aece48a..80ecd05 100644 --- a/savestate.h +++ b/savestate.h @@ -92,7 +92,7 @@ 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 0x00010002 +#define GBA_STATE_VERSION 0x00010003 bool gba_load_state(const void *src); void gba_save_state(void *dst); diff --git a/x86/x86_stub.S b/x86/x86_stub.S index c46c8ab..b077e98 100644 --- a/x86/x86_stub.S +++ b/x86/x86_stub.S @@ -219,8 +219,8 @@ defsymbl(x86_indirect_branch_dual) # General ext memory routines -ext_store_rtc8: # No RTC writes on byte or word access -ext_store_rtc32: +ext_store_gpio8: # No GPIO/RTC writes on byte or word access +ext_store_gpio32: ext_store_backup16: # Backup (flash) accessed via byte writes ext_store_backup32: ext_store_eeprom8: # EEPROM accesses are performed using 16 bit DMA @@ -228,11 +228,11 @@ ext_store_eeprom32: ext_store_ignore: ret # ignore these writes -ext_store_rtc16: +ext_store_gpio16: and $0xFFFF, %edx # make value 16bit and $0xFF, %eax # mask address SETUP_ARGS # Setup addr, value - CALL_FUNC(write_rtc) # write out RTC register + CALL_FUNC(write_gpio) # write out RTC register ret ext_store_backup8: @@ -565,7 +565,7 @@ return_to_main: ADDR_TYPE ext_store_palette##asize /* 0x05 Palette RAM */;\ ADDR_TYPE ext_store_vram##asize /* 0x06 VRAM */;\ ADDR_TYPE ext_store_oam##asize /* 0x07 OAM RAM */;\ - ADDR_TYPE ext_store_rtc##asize /* 0x08 gamepak (RTC or ignore) */;\ + ADDR_TYPE ext_store_gpio##asize /* 0x08 gamepak (RTC or ignore) */;\ ADDR_TYPE ext_store_ignore /* 0x09 gamepak, ignore */;\ ADDR_TYPE ext_store_ignore /* 0x0A gamepak, ignore */;\ ADDR_TYPE ext_store_ignore /* 0x0B gamepak, ignore */;\