/* gameplaySP * * Copyright (C) 2006 Exophase * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "streams/file_stream.h" /* Sound */ #define gbc_sound_tone_control_low(channel, regn) \ { \ render_gbc_sound(); \ u32 initial_volume = (value >> 12) & 0x0F; \ u32 envelope_ticks = ((value >> 8) & 0x07) * 4; \ gbc_sound_channel[channel].length_ticks = 64 - (value & 0x3F); \ gbc_sound_channel[channel].sample_table_idx = ((value >> 6) & 0x03); \ gbc_sound_channel[channel].envelope_direction = (value >> 11) & 0x01; \ gbc_sound_channel[channel].envelope_initial_volume = initial_volume; \ gbc_sound_channel[channel].envelope_volume = initial_volume; \ gbc_sound_channel[channel].envelope_initial_ticks = envelope_ticks; \ gbc_sound_channel[channel].envelope_ticks = envelope_ticks; \ gbc_sound_channel[channel].envelope_status = (envelope_ticks != 0); \ gbc_sound_channel[channel].envelope_volume = initial_volume; \ write_ioreg(regn, value); \ } \ #define gbc_sound_tone_control_high(channel, regn) \ { \ render_gbc_sound(); \ u32 rate = value & 0x7FF; \ gbc_sound_channel[channel].rate = rate; \ gbc_sound_channel[channel].frequency_step = \ float_to_fp16_16(((131072.0 / (2048 - rate)) * 8.0) / sound_frequency); \ gbc_sound_channel[channel].length_status = (value >> 14) & 0x01; \ if(value & 0x8000) \ { \ gbc_sound_channel[channel].active_flag = 1; \ gbc_sound_channel[channel].sample_index -= float_to_fp16_16(1.0 / 12.0); \ gbc_sound_channel[channel].envelope_ticks = \ gbc_sound_channel[channel].envelope_initial_ticks; \ gbc_sound_channel[channel].envelope_volume = \ gbc_sound_channel[channel].envelope_initial_volume; \ } \ \ write_ioreg(regn, value & 0x47FF); \ } \ #define gbc_sound_tone_control_sweep() \ { \ render_gbc_sound(); \ value &= 0x007F; \ u32 sweep_ticks = ((value >> 4) & 0x07) * 2; \ gbc_sound_channel[0].sweep_shift = value & 0x07; \ gbc_sound_channel[0].sweep_direction = (value >> 3) & 0x01; \ gbc_sound_channel[0].sweep_status = (value != 8); \ gbc_sound_channel[0].sweep_ticks = sweep_ticks; \ gbc_sound_channel[0].sweep_initial_ticks = sweep_ticks; \ write_ioreg(REG_SOUND1CNT_L, value); \ } \ #define gbc_sound_wave_control() \ { \ render_gbc_sound(); \ gbc_sound_channel[2].wave_type = (value >> 5) & 0x01; \ gbc_sound_channel[2].wave_bank = (value >> 6) & 0x01; \ gbc_sound_channel[2].master_enable = 0; \ if(value & 0x80) \ gbc_sound_channel[2].master_enable = 1; \ \ write_ioreg(REG_SOUND3CNT_L, value & 0x00E0); \ } \ static const u32 gbc_sound_wave_volume[4] = { 0, 16384, 8192, 4096 }; #define gbc_sound_tone_control_low_wave() \ { \ render_gbc_sound(); \ gbc_sound_channel[2].length_ticks = 256 - (value & 0xFF); \ if((value >> 15) & 0x01) \ gbc_sound_channel[2].wave_volume = 12288; \ else \ gbc_sound_channel[2].wave_volume = \ gbc_sound_wave_volume[(value >> 13) & 0x03]; \ write_ioreg(REG_SOUND3CNT_H, value); \ } \ #define gbc_sound_tone_control_high_wave() \ { \ render_gbc_sound(); \ u32 rate = value & 0x7FF; \ gbc_sound_channel[2].rate = rate; \ gbc_sound_channel[2].frequency_step = \ float_to_fp16_16((2097152.0 / (2048 - rate)) / sound_frequency); \ gbc_sound_channel[2].length_status = (value >> 14) & 0x01; \ if(value & 0x8000) \ { \ gbc_sound_channel[2].sample_index = 0; \ gbc_sound_channel[2].active_flag = 1; \ } \ write_ioreg(REG_SOUND3CNT_X, value); \ } \ #define gbc_sound_noise_control() \ { \ u32 dividing_ratio = value & 0x07; \ u32 frequency_shift = (value >> 4) & 0x0F; \ render_gbc_sound(); \ if(dividing_ratio == 0) \ { \ gbc_sound_channel[3].frequency_step = \ float_to_fp16_16(1048576.0 / (1 << (frequency_shift + 1)) / \ sound_frequency); \ } \ else \ { \ gbc_sound_channel[3].frequency_step = \ float_to_fp16_16(524288.0 / (dividing_ratio * \ (1 << (frequency_shift + 1))) / sound_frequency); \ } \ gbc_sound_channel[3].noise_type = (value >> 3) & 0x01; \ gbc_sound_channel[3].length_status = (value >> 14) & 0x01; \ if(value & 0x8000) \ { \ gbc_sound_channel[3].sample_index = 0; \ gbc_sound_channel[3].active_flag = 1; \ gbc_sound_channel[3].envelope_ticks = \ gbc_sound_channel[3].envelope_initial_ticks; \ gbc_sound_channel[3].envelope_volume = \ gbc_sound_channel[3].envelope_initial_volume; \ } \ write_ioreg(REG_SOUND4CNT_H, value & 0x40FF); \ } \ static void gbc_trigger_sound(u32 value) { u32 channel; render_gbc_sound(); /* Trigger all 4 GBC sound channels */ for (channel = 0; channel < 4; channel++) { gbc_sound_master_volume_right = value & 0x07; gbc_sound_master_volume_left = (value >> 4) & 0x07; gbc_sound_channel[channel].status = (((value >> (channel + 8)) & 0x1) | ((value >> (channel + 11)) & 0x3)); } write_ioreg(REG_SOUNDCNT_L, value & 0xFF77); } #define trigger_sound() \ { \ render_gbc_sound(); \ timer[0].direct_sound_channels = \ ((((value >> 10) & 0x01) == 0) | ((((value >> 14) & 0x01) == 0) << 1)); \ timer[1].direct_sound_channels = \ ((((value >> 10) & 0x01) == 1) | ((((value >> 14) & 0x01) == 1) << 1)); \ direct_sound_channel[0].volume_halve = ((~(value >> 2)) & 0x01); \ direct_sound_channel[0].status = ((value >> 8) & 0x03); \ direct_sound_channel[1].volume_halve = ((~(value >> 3)) & 0x01); \ direct_sound_channel[1].status = ((value >> 12) & 0x03); \ gbc_sound_master_volume = value & 0x03; \ \ if((value >> 11) & 0x01) \ sound_reset_fifo(0); \ if((value >> 15) & 0x01) \ sound_reset_fifo(1); \ write_ioreg(REG_SOUNDCNT_H, value & 0x770F); \ } \ static void sound_control_x(u32 value) { render_gbc_sound(); if (value & 0x80) { if (sound_on != 1) sound_on = 1; } else { u32 i; for (i = 0; i < 4; i++) gbc_sound_channel[i].active_flag = 0; sound_on = 0; } value = (value & 0xFFF0) | (read_ioreg(REG_SOUNDCNT_X) & 0x000F); write_ioreg(REG_SOUNDCNT_X, value); } #define sound_update_frequency_step(timer_number) \ timer[timer_number].frequency_step = \ float_to_fp8_24((GBC_BASE_RATE / sound_frequency) / (timer_reload)) \ /* Main */ extern timer_type timer[4]; static const u32 prescale_table[] = { 0, 6, 8, 10 }; #define count_timer(timer_number) \ timer[timer_number].reload = 0x10000 - value; \ if(timer_number < 2) \ { \ u32 timer_reload = \ timer[timer_number].reload << timer[timer_number].prescale; \ sound_update_frequency_step(timer_number); \ } \ #define adjust_sound_buffer(timer_number, channel) \ if(timer[timer_number].direct_sound_channels & (0x01 << channel)) \ { \ direct_sound_channel[channel].buffer_index = \ (gbc_sound_buffer_index + buffer_adjust) % BUFFER_SIZE; \ } \ static void trigger_timer(u32 timer_number, u32 value) { if (value & 0x80) { if(timer[timer_number].status == TIMER_INACTIVE) { u32 prescale = prescale_table[value & 0x03]; u32 timer_reload = timer[timer_number].reload; if((value >> 2) & 0x01) timer[timer_number].status = TIMER_CASCADE; else timer[timer_number].status = TIMER_PRESCALE; timer[timer_number].prescale = prescale; timer[timer_number].irq = ((value >> 6) & 0x1); write_ioreg(REG_TMXD(timer_number), (u32)(-timer_reload)); timer_reload <<= prescale; timer[timer_number].count = timer_reload; if(timer_reload < execute_cycles) execute_cycles = timer_reload; if(timer_number < 2) { u32 buffer_adjust = (u32)(((float)(cpu_ticks - gbc_sound_last_cpu_ticks) * sound_frequency) / GBC_BASE_RATE) * 2; sound_update_frequency_step(timer_number); adjust_sound_buffer(timer_number, 0); adjust_sound_buffer(timer_number, 1); } } } else { if(timer[timer_number].status != TIMER_INACTIVE) { timer[timer_number].status = TIMER_INACTIVE; } } write_ioreg(REG_TMXCNT(timer_number), value); } /* Memory timings */ const u8 ws012_nonseq[] = {4, 3, 2, 8}; const u8 ws0_seq[] = {2, 1}; const u8 ws1_seq[] = {4, 1}; const u8 ws2_seq[] = {8, 1}; /* Divided by region and bus width (16/32) */ u8 ws_cyc_seq[16][2] = { { 1, 1 }, // BIOS { 1, 1 }, // Invalid { 3, 6 }, // EWRAM (default settings) { 1, 1 }, // IWRAM { 1, 1 }, // IO Registers { 1, 2 }, // Palette RAM { 1, 2 }, // VRAM { 1, 2 }, // OAM { 0, 0 }, // Gamepak (wait 0) { 0, 0 }, // Gamepak (wait 0) { 0, 0 }, // Gamepak (wait 1) { 0, 0 }, // Gamepak (wait 1) { 0, 0 }, // Gamepak (wait 2) { 0, 0 }, // Gamepak (wait 2) { 1, 1 }, // Invalid { 1, 1 }, // Invalid }; u8 ws_cyc_nseq[16][2] = { { 1, 1 }, // BIOS { 1, 1 }, // Invalid { 3, 6 }, // EWRAM (default settings) { 1, 1 }, // IWRAM { 1, 1 }, // IO Registers { 1, 2 }, // Palette RAM { 1, 2 }, // VRAM { 1, 2 }, // OAM { 0, 0 }, // Gamepak (wait 0) { 0, 0 }, // Gamepak (wait 0) { 0, 0 }, // Gamepak (wait 1) { 0, 0 }, // Gamepak (wait 1) { 0, 0 }, // Gamepak (wait 2) { 0, 0 }, // Gamepak (wait 2) { 1, 1 }, // Invalid { 1, 1 }, // Invalid }; const u32 def_seq_cycles[16][2] = { { 1, 1 }, // BIOS { 1, 1 }, // Invalid { 3, 6 }, // EWRAM (default settings) { 1, 1 }, // IWRAM { 1, 1 }, // IO Registers { 1, 2 }, // Palette RAM { 1, 2 }, // VRAM { 1, 2 }, // OAM { 3, 6 }, // Gamepak (wait 0) { 3, 6 }, // Gamepak (wait 0) { 5, 9 }, // Gamepak (wait 1) { 5, 9 }, // Gamepak (wait 1) { 9, 17 }, // Gamepak (wait 2) { 9, 17 }, // Gamepak (wait 2) }; u8 bios_rom[1024 * 16]; // Up to 128kb, store SRAM, flash ROM, or EEPROM here. u8 gamepak_backup[1024 * 128]; dma_transfer_type dma[4]; // ROM memory is allocated in blocks of 1MB to better map the native block // mapping system. We will try to allocate 32 of them to allow loading // ROMs up to 32MB, but we might fail on memory constrained systems. u8 *gamepak_buffers[32]; /* Pointers to malloc'ed blocks */ u32 gamepak_buffer_count; /* Value between 1 and 32 */ u32 gamepak_size; /* Size of the ROM in bytes */ // We allocate in 1MB chunks. const unsigned gamepak_buffer_blocksize = 1024*1024; // LRU queue with the loaded blocks and what they map to struct { u16 next_lru; /* Index in the struct to the next LRU entry */ s16 phy_rom; /* ROM page number (-1 means not mapped) */ } gamepak_blk_queue[1024]; u16 gamepak_lru_head; u16 gamepak_lru_tail; // Stick page bit: prevents page eviction for a frame. This is used to prevent // unmapping code pages while being used (ie. in the interpreter). u32 gamepak_sticky_bit[1024/32]; #define gamepak_sb_test(idx) \ (gamepak_sticky_bit[((unsigned)(idx)) >> 5] & (1 << (((unsigned)(idx)) & 31))) // This is global so that it can be kept open for large ROMs to swap // pages from, so there's no slowdown with opening and closing the file // a lot. RFILE *gamepak_file_large = NULL; // Writes to these respective locations should trigger an update // so the related subsystem may react to it. // If the GBC audio waveform is modified: u32 gbc_sound_wave_update = 0; u32 backup_type = BACKUP_UNKN; u32 backup_type_reset = BACKUP_UNKN; u32 flash_mode = FLASH_BASE_MODE; u32 flash_command_position = 0; u32 flash_bank_num; // 0 or 1 u32 flash_bank_cnt; u32 flash_device_id = FLASH_DEVICE_MACRONIX_64KB; void reload_timing_info() { int i; uint16_t waitcnt = read_ioreg(REG_WAITCNT); /* Sequential 16 and 32 bit accesses to ROM */ ws_cyc_seq[0x8][0] = ws_cyc_seq[0x9][0] = 1 + ws0_seq[(waitcnt >> 4) & 1]; ws_cyc_seq[0xA][0] = ws_cyc_seq[0xB][0] = 1 + ws1_seq[(waitcnt >> 7) & 1]; ws_cyc_seq[0xC][0] = ws_cyc_seq[0xD][0] = 1 + ws2_seq[(waitcnt >> 10) & 1]; for (i = 0x8; i <= 0xD; i++) { /* 32 bit accesses just cost double due to 16 bit bus */ ws_cyc_seq[i][1] = ws_cyc_seq[i][0] * 2; } /* Sequential 16 and 32 bit accesses to ROM */ ws_cyc_nseq[0x8][0] = ws_cyc_nseq[0x9][0] = 1 + ws012_nonseq[(waitcnt >> 2) & 3]; ws_cyc_nseq[0xA][0] = ws_cyc_nseq[0xB][0] = 1 + ws012_nonseq[(waitcnt >> 5) & 3]; ws_cyc_nseq[0xC][0] = ws_cyc_nseq[0xD][0] = 1 + ws012_nonseq[(waitcnt >> 8) & 3]; for (i = 0x8; i <= 0xD; i++) { /* 32 bit accesses are a non-seq (16) + seq access (16) */ ws_cyc_nseq[i][1] = 1 + ws_cyc_nseq[i][0] + ws_cyc_seq[i][0]; } } u8 read_backup(u32 address) { u8 value = 0; if(backup_type == BACKUP_EEPROM) return 0xff; if(backup_type == BACKUP_UNKN) backup_type = BACKUP_SRAM; if(backup_type == BACKUP_SRAM) value = gamepak_backup[address]; else if(flash_mode == FLASH_ID_MODE) { if (flash_bank_cnt == FLASH_SIZE_128KB) { /* ID manufacturer type */ if(address == 0x0000) value = FLASH_MANUFACTURER_MACRONIX; /* ID device type */ else if(address == 0x0001) value = FLASH_DEVICE_MACRONIX_128KB; } else { /* ID manufacturer type */ if(address == 0x0000) value = FLASH_MANUFACTURER_PANASONIC; /* ID device type */ else if(address == 0x0001) value = FLASH_DEVICE_PANASONIC_64KB; } } else { u32 fulladdr = address + 64*1024*flash_bank_num; value = gamepak_backup[fulladdr]; } return value; } #define read_backup8() \ value = read_backup(address & 0xFFFF) \ #define read_backup16() \ value = 0 \ #define read_backup32() \ value = 0 \ #define write_eeprom8(addr, value) #define write_eeprom16(addr, value) \ write_eeprom(addr, value) #define write_eeprom32(addr, value) // EEPROM is 512 bytes by default; it is autodetecte as 8KB if // 14bit address DMAs are made (this is done in the DMA handler). u32 eeprom_size = EEPROM_512_BYTE; u32 eeprom_mode = EEPROM_BASE_MODE; u32 eeprom_address = 0; u32 eeprom_counter = 0; void function_cc write_eeprom(u32 unused_address, u32 value) { switch(eeprom_mode) { case EEPROM_BASE_MODE: backup_type = BACKUP_EEPROM; eeprom_address |= (value & 0x01) << (1 - eeprom_counter); if(++eeprom_counter == 2) { switch(eeprom_address & 0x03) { case 0x02: eeprom_mode = EEPROM_WRITE_ADDRESS_MODE; break; case 0x03: eeprom_mode = EEPROM_ADDRESS_MODE; break; } eeprom_counter = 0; eeprom_address = 0; } break; case EEPROM_ADDRESS_MODE: case EEPROM_WRITE_ADDRESS_MODE: eeprom_address |= (value & 0x01) << (15 - (eeprom_counter % 16)); if(++eeprom_counter == (eeprom_size == EEPROM_512_BYTE ? 6 : 14)) { eeprom_counter = 0; if (eeprom_size == EEPROM_512_BYTE) eeprom_address >>= 10; // Addr is just 6 bits (drop 10LSB) else eeprom_address >>= 2; // Addr is 14 bits (drop 2LSB) eeprom_address <<= 3; // EEPROM accessed in blocks of 8 bytes if(eeprom_mode == EEPROM_ADDRESS_MODE) eeprom_mode = EEPROM_ADDRESS_FOOTER_MODE; else { eeprom_mode = EEPROM_WRITE_MODE; memset(gamepak_backup + eeprom_address, 0, 8); } } break; case EEPROM_WRITE_MODE: gamepak_backup[eeprom_address + (eeprom_counter / 8)] |= (value & 0x01) << (7 - (eeprom_counter % 8)); eeprom_counter++; if(eeprom_counter == 64) { eeprom_counter = 0; eeprom_mode = EEPROM_WRITE_FOOTER_MODE; } break; case EEPROM_ADDRESS_FOOTER_MODE: case EEPROM_WRITE_FOOTER_MODE: eeprom_counter = 0; if(eeprom_mode == EEPROM_ADDRESS_FOOTER_MODE) eeprom_mode = EEPROM_READ_HEADER_MODE; else eeprom_mode = EEPROM_BASE_MODE; break; default: break; } } #define read_memory_gamepak(type) \ u32 gamepak_index = address >> 15; \ u8 *map = memory_map_read[gamepak_index]; \ \ if(!map) \ map = load_gamepak_page(gamepak_index & 0x3FF); \ \ value = readaddress##type(map, address & 0x7FFF) \ #define unmapped_rom_read8(addr) \ (((addr) >> 1) >> (((addr) & 1) * 8)) & 0xFF #define unmapped_rom_read16(addr) \ ((addr) >> 1) & 0xFFFF #define unmapped_rom_read32(addr) \ ((((addr) & ~3) >> 1) & 0xFFFF) | (((((addr) & ~3) + 2) >> 1) << 16) #define read_open8() \ if(!(reg[REG_CPSR] & 0x20)) \ value = read_memory8(reg[REG_PC] + 8 + (address & 0x03)); \ else \ value = read_memory8(reg[REG_PC] + 4 + (address & 0x01)) \ #define read_open16() \ if(!(reg[REG_CPSR] & 0x20)) \ value = read_memory16(reg[REG_PC] + 8 + (address & 0x02)); \ else \ value = read_memory16(reg[REG_PC] + 4) \ #define read_open32() \ if(!(reg[REG_CPSR] & 0x20)) \ value = read_memory32(reg[REG_PC] + 8); \ else \ { \ u32 current_instruction = read_memory16(reg[REG_PC] + 4); \ value = current_instruction | (current_instruction << 16); \ } \ u32 function_cc read_eeprom(void) { u32 value; switch(eeprom_mode) { case EEPROM_BASE_MODE: value = 1; break; case EEPROM_READ_MODE: value = (gamepak_backup[eeprom_address + (eeprom_counter / 8)] >> (7 - (eeprom_counter % 8))) & 0x01; eeprom_counter++; if(eeprom_counter == 64) { eeprom_counter = 0; eeprom_mode = EEPROM_BASE_MODE; } break; case EEPROM_READ_HEADER_MODE: value = 0; eeprom_counter++; if(eeprom_counter == 4) { eeprom_mode = EEPROM_READ_MODE; eeprom_counter = 0; } break; default: value = 0; break; } return value; } #define read_memory(type) \ switch(address >> 24) \ { \ case 0x00: \ /* BIOS */ \ if (address < 0x4000) { \ if(reg[REG_PC] >= 0x4000) \ value = (u##type)(reg[REG_BUS_VALUE] >> ((address & 0x03) << 3)); \ else \ value = readaddress##type(bios_rom, address & 0x3FFF); \ } else { \ read_open##type(); \ } \ break; \ \ case 0x02: \ /* external work RAM */ \ value = readaddress##type(ewram, (address & 0x3FFFF)); \ break; \ \ case 0x03: \ /* internal work RAM */ \ value = readaddress##type(iwram, (address & 0x7FFF) + 0x8000); \ break; \ \ case 0x04: \ /* I/O registers */ \ value = readaddress##type(io_registers, address & 0x3FF); \ break; \ \ case 0x05: \ /* palette RAM */ \ value = readaddress##type(palette_ram, address & 0x3FF); \ break; \ \ case 0x06: \ /* VRAM */ \ address &= 0x1FFFF; \ if(address >= 0x18000) \ address -= 0x8000; \ \ value = readaddress##type(vram, address); \ break; \ \ case 0x07: \ /* OAM RAM */ \ value = readaddress##type(oam_ram, address & 0x3FF); \ break; \ \ case 0x0D: \ if (backup_type == BACKUP_EEPROM) { \ value = read_eeprom(); \ break; \ } \ /* fallthrough */ \ case 0x08: \ case 0x09: \ case 0x0A: \ case 0x0B: \ case 0x0C: \ /* gamepak ROM */ \ if((address & 0x1FFFFFF) >= gamepak_size) \ value = unmapped_rom_read##type(address); \ else \ { \ read_memory_gamepak(type); \ } \ break; \ \ case 0x0E: \ case 0x0F: \ read_backup##type(); \ break; \ \ default: \ read_open##type(); \ break; \ } \ static cpu_alert_type trigger_dma(u32 dma_number, u32 value) { if(value & 0x8000) { if(dma[dma_number].start_type == DMA_INACTIVE) { u32 start_type = ((value >> 12) & 0x03); u32 src_address = 0xFFFFFFF & (read_dmareg(REG_DMA0SAD, dma_number) | (read_dmareg(REG_DMA0SAD + 1, dma_number) << 16)); u32 dst_address = 0xFFFFFFF & (read_dmareg(REG_DMA0DAD, dma_number) | (read_dmareg(REG_DMA0DAD + 1, dma_number) << 16)); dma[dma_number].source_address = src_address; dma[dma_number].dest_address = dst_address; dma[dma_number].source_direction = ((value >> 7) & 3); dma[dma_number].repeat_type = ((value >> 9) & 0x01); dma[dma_number].start_type = start_type; dma[dma_number].irq = ((value >> 14) & 0x1); /* If it is sound FIFO DMA make sure the settings are a certain way */ if((dma_number >= 1) && (dma_number <= 2) && (start_type == DMA_START_SPECIAL)) { dma[dma_number].length_type = DMA_32BIT; dma[dma_number].length = 4; dma[dma_number].dest_direction = DMA_FIXED; if(dst_address == 0x40000A4) dma[dma_number].direct_sound_channel = DMA_DIRECT_SOUND_B; else dma[dma_number].direct_sound_channel = DMA_DIRECT_SOUND_A; } else { u32 length = read_dmareg(REG_DMA0CNT_L, dma_number); if((dma_number == 3) && ((dst_address >> 24) == 0x0D) && ((length & 0x1F) == 17)) eeprom_size = EEPROM_8_KBYTE; if(dma_number < 3) length &= 0x3FFF; if(length == 0) { if(dma_number == 3) length = 0x10000; else length = 0x04000; } dma[dma_number].length = length; dma[dma_number].length_type = ((value >> 10) & 0x01); dma[dma_number].dest_direction = ((value >> 5) & 3); } write_dmareg(REG_DMA0CNT_H, dma_number, value); if(start_type == DMA_START_IMMEDIATELY) { // Excutes the DMA now! Copies the data and returns side effects. int dma_cycles = 0; cpu_alert_type ret = dma_transfer(dma_number, &dma_cycles); if (!dma_cycles) return ret; // Sleep CPU for N cycles and return HALT as side effect (so it does). reg[CPU_HALT_STATE] = CPU_DMA; reg[REG_SLEEP_CYCLES] = 0x80000000 | (u32)dma_cycles; return CPU_ALERT_HALT | ret; } } } else { dma[dma_number].start_type = DMA_INACTIVE; dma[dma_number].direct_sound_channel = DMA_NO_DIRECT_SOUND; write_dmareg(REG_DMA0CNT_H, dma_number, value); } return CPU_ALERT_NONE; } static inline s32 signext28(u32 value) { s32 ret = (s32)(value << 4); return ret >> 4; } cpu_alert_type function_cc write_io_register16(u32 address, u32 value) { uint16_t ioreg = (address & 0x3FE) >> 1; value &= 0xffff; switch(ioreg) { case REG_DISPCNT: // Changing the lowest 3 bits might require object re-sorting reg[OAM_UPDATED] |= ((value & 0x07) != (read_ioreg(REG_DISPCNT) & 0x07)); write_ioreg(REG_DISPCNT, value); break; // DISPSTAT has 3 read only bits, controlled by the LCD controller case REG_DISPSTAT: write_ioreg(REG_DISPSTAT, (read_ioreg(REG_DISPSTAT) & 0x07) | (value & ~0x07)); break; // BG2 reference X case REG_BG2X_L: case REG_BG2X_H: write_ioreg(ioreg, value); affine_reference_x[0] = signext28(read_ioreg32(REG_BG2X_L)); break; // BG2 reference Y case REG_BG2Y_L: case REG_BG2Y_H: write_ioreg(ioreg, value); affine_reference_y[0] = signext28(read_ioreg32(REG_BG2Y_L)); break; // BG3 reference X case REG_BG3X_L: case REG_BG3X_H: write_ioreg(ioreg, value); affine_reference_x[1] = signext28(read_ioreg32(REG_BG3X_L)); break; // BG3 reference Y case REG_BG3Y_L: case REG_BG3Y_H: write_ioreg(ioreg, value); affine_reference_y[1] = signext28(read_ioreg32(REG_BG3Y_L)); break; // Sound 1 registers case REG_SOUND1CNT_L: // control sweep gbc_sound_tone_control_sweep(); break; case REG_SOUND1CNT_H: // control duty/length/envelope gbc_sound_tone_control_low(0, REG_SOUND1CNT_H); break; case REG_SOUND1CNT_X: // control frequency gbc_sound_tone_control_high(0, REG_SOUND1CNT_X); break; // Sound 2 registers case REG_SOUND2CNT_L: // control duty/length/envelope gbc_sound_tone_control_low(1, REG_SOUND2CNT_L); break; case REG_SOUND2CNT_H: // control frequency gbc_sound_tone_control_high(1, REG_SOUND2CNT_H); break; // Sound 3 registers case REG_SOUND3CNT_L: // control wave gbc_sound_wave_control(); break; case REG_SOUND3CNT_H: // control length/volume gbc_sound_tone_control_low_wave(); break; case REG_SOUND3CNT_X: // control frequency gbc_sound_tone_control_high_wave(); break; // Sound 4 registers case REG_SOUND4CNT_L: // length/envelope gbc_sound_tone_control_low(3, REG_SOUND4CNT_L); break; case REG_SOUND4CNT_H: // control frequency gbc_sound_noise_control(); break; // Sound control registers case REG_SOUNDCNT_L: gbc_trigger_sound(value); break; case REG_SOUNDCNT_H: trigger_sound(); break; case REG_SOUNDCNT_X: sound_control_x(value); break; // Sound wave RAM, flag wave table update case REG_SOUNDWAVE_0 ... REG_SOUNDWAVE_7: render_gbc_sound(); gbc_sound_wave_update = 1; write_ioreg(ioreg, value); break; // DMA control register: can cause an IRQ case REG_DMA0CNT_H: return trigger_dma(0, value); case REG_DMA1CNT_H: return trigger_dma(1, value); case REG_DMA2CNT_H: return trigger_dma(2, value); case REG_DMA3CNT_H: return trigger_dma(3, value); // Timer counter reload case REG_TM0D: count_timer(0); break; case REG_TM1D: count_timer(1); break; case REG_TM2D: count_timer(2); break; case REG_TM3D: count_timer(3); break; /* Timer control register (0..3)*/ case REG_TM0CNT: trigger_timer(0, value); break; case REG_TM1CNT: trigger_timer(1, value); break; case REG_TM2CNT: trigger_timer(2, value); break; case REG_TM3CNT: trigger_timer(3, value); break; // Interrupt flag, clears the bits it tries to write case REG_IF: write_ioreg(REG_IF, read_ioreg(REG_IF) & (~value)); break; // Register writes with side-effects, can raise an IRQ case REG_IE: case REG_IME: write_ioreg(ioreg, value); return check_interrupt(); // Read-only registers case REG_P1: case REG_VCOUNT: break; // Do nothing case REG_WAITCNT: write_ioreg(REG_WAITCNT, value); reload_timing_info(); break; // Registers without side effects default: write_ioreg(ioreg, value); break; } return CPU_ALERT_NONE; } cpu_alert_type function_cc write_io_register8(u32 address, u32 value) { if (address == 0x301) { if (value & 1) reg[CPU_HALT_STATE] = CPU_STOP; else reg[CPU_HALT_STATE] = CPU_HALT; return CPU_ALERT_HALT; } // Partial 16 bit write, treat like a regular merge-write if (address & 1) value = (value << 8) | (read_ioreg(address >> 1) & 0x00ff); else value = (value & 0xff) | (read_ioreg(address >> 1) & 0xff00); return write_io_register16(address & 0x3FE, value); } cpu_alert_type function_cc write_io_register32(u32 address, u32 value) { // Handle sound FIFO data write if (address == 0xA0) { sound_timer_queue32(0, value); return CPU_ALERT_NONE; } else if (address == 0xA4) { sound_timer_queue32(1, value); return CPU_ALERT_NONE; } // Perform two 16 bit writes. Low part goes first apparently. // Some Count+Control DMA writes use 32 bit, so control must be last. cpu_alert_type allow = write_io_register16(address, value & 0xFFFF); cpu_alert_type alhigh = write_io_register16(address + 2, value >> 16); return allow | alhigh; } #define write_palette8(address, value) \ { \ u32 aladdr = address & ~1U; \ u16 val16 = (value << 8) | value; \ address16(palette_ram, aladdr) = eswap16(val16); \ address16(palette_ram_converted, aladdr) = convert_palette(val16); \ } #define write_palette16(address, value) \ { \ u32 palette_address = address; \ address16(palette_ram, palette_address) = eswap16(value); \ value = convert_palette(value); \ address16(palette_ram_converted, palette_address) = value; \ } \ #define write_palette32(address, value) \ { \ u32 palette_address = address; \ u32 value_high = value >> 16; \ u32 value_low = value & 0xFFFF; \ address32(palette_ram, palette_address) = eswap32(value); \ value_high = convert_palette(value_high); \ address16(palette_ram_converted, palette_address + 2) = value_high; \ value_low = convert_palette(value_low); \ address16(palette_ram_converted, palette_address) = value_low; \ } \ void function_cc write_backup(u32 address, u32 value) { value &= 0xFF; if(backup_type == BACKUP_EEPROM) return; if(backup_type == BACKUP_UNKN) backup_type = BACKUP_SRAM; // gamepak SRAM or Flash ROM if((address == 0x5555) && (flash_mode != FLASH_WRITE_MODE)) { if((flash_command_position == 0) && (value == 0xAA)) { backup_type = BACKUP_FLASH; flash_command_position = 1; } if(flash_command_position == 2) { switch(value) { case 0x90: // Enter ID mode, this also tells the emulator that we're using // flash, not SRAM if(flash_mode == FLASH_BASE_MODE) flash_mode = FLASH_ID_MODE; break; case 0x80: // Enter erase mode if(flash_mode == FLASH_BASE_MODE) flash_mode = FLASH_ERASE_MODE; break; case 0xF0: // Terminate ID mode if(flash_mode == FLASH_ID_MODE) flash_mode = FLASH_BASE_MODE; break; case 0xA0: // Write mode if(flash_mode == FLASH_BASE_MODE) flash_mode = FLASH_WRITE_MODE; break; case 0xB0: // Bank switch // Here the chip is now officially 128KB. flash_bank_cnt = FLASH_SIZE_128KB; if(flash_mode == FLASH_BASE_MODE) flash_mode = FLASH_BANKSWITCH_MODE; break; case 0x10: // Erase chip if(flash_mode == FLASH_ERASE_MODE) { memset(gamepak_backup, 0xFF, 1024 * 128); flash_mode = FLASH_BASE_MODE; } break; default: break; } flash_command_position = 0; } if(backup_type == BACKUP_SRAM) gamepak_backup[0x5555] = value; } else if((address == 0x2AAA) && (value == 0x55) && (flash_command_position == 1)) flash_command_position = 2; else { if((flash_command_position == 2) && (flash_mode == FLASH_ERASE_MODE) && (value == 0x30)) { // Erase sector 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; } else if((flash_command_position == 0) && (flash_mode == FLASH_BANKSWITCH_MODE) && (address == 0x0000) && (flash_bank_cnt == FLASH_SIZE_128KB)) { flash_bank_num = value & 1; flash_mode = FLASH_BASE_MODE; } else if((flash_command_position == 0) && (flash_mode == FLASH_WRITE_MODE)) { // Write value to flash ROM u32 fulladdr = address + 64*1024*flash_bank_num; gamepak_backup[fulladdr] = value; flash_mode = FLASH_BASE_MODE; } else if(backup_type == BACKUP_SRAM) { // Write value to SRAM gamepak_backup[address] = value; } } } #define write_backup8() \ write_backup(address & 0xFFFF, value) \ #define write_backup16() \ #define write_backup32() \ #define write_vram8() \ address &= ~0x01; \ address16(vram, address) = eswap16((value << 8) | value) \ #define write_vram16() \ address16(vram, address) = eswap16(value) \ #define write_vram32() \ address32(vram, address) = eswap32(value) \ // RTC code derived from VBA's (due to lack of any real publically available // documentation...) #define RTC_DISABLED 0 #define RTC_IDLE 1 #define RTC_COMMAND 2 #define RTC_OUTPUT_DATA 3 #define RTC_INPUT_DATA 4 typedef enum { RTC_COMMAND_RESET = 0x60, RTC_COMMAND_WRITE_STATUS = 0x62, RTC_COMMAND_READ_STATUS = 0x63, RTC_COMMAND_OUTPUT_TIME_FULL = 0x65, RTC_COMMAND_OUTPUT_TIME = 0x67 } rtc_command_type; #define RTC_WRITE_TIME 0 #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; u32 rtc_command; u64 rtc_data; u32 rtc_data_bits; u32 rtc_status = 0x40; s32 rtc_bit_count; // 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; value = value % 100; l = value % 10; h = value / 10; return h * 16 + l; } 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; } } } } #define GPIO_RTC_CLK 0x1 #define GPIO_RTC_DAT 0x2 #define GPIO_RTC_CSS 0x4 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; } // 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) \ { \ case 0x02: \ /* external work RAM */ \ address##type(ewram, (address & 0x3FFFF)) = eswap##type(value); \ break; \ \ case 0x03: \ /* internal work RAM */ \ address##type(iwram, (address & 0x7FFF) + 0x8000) = eswap##type(value); \ break; \ \ case 0x04: \ /* I/O registers */ \ return write_io_register##type(address & 0x3FF, value); \ \ case 0x05: \ /* palette RAM */ \ write_palette##type(address & 0x3FF, value); \ break; \ \ case 0x06: \ /* VRAM */ \ address &= 0x1FFFF; \ if(address >= 0x18000) \ address -= 0x8000; \ \ write_vram##type(); \ break; \ \ case 0x07: \ /* OAM RAM */ \ if (type != 8) { \ reg[OAM_UPDATED] = 1; \ address##type(oam_ram, address & 0x3FF) = eswap##type(value); \ } \ break; \ \ case 0x08: \ /* gamepak ROM or RTC */ \ write_gpio##type(); \ break; \ \ case 0x09: \ case 0x0A: \ case 0x0B: \ case 0x0C: \ /* gamepak ROM space */ \ break; \ \ case 0x0D: \ write_eeprom##type(address, value); \ break; \ \ case 0x0E: \ write_backup##type(); \ break; \ } \ u32 function_cc read_memory8(u32 address) { u8 value; read_memory(8); return value; } u32 function_cc read_memory8s(u32 address) { return (u32)((s8)read_memory8(address)); } u16 function_cc read_memory16_signed(u32 address) { u16 value; if(address & 0x01) return (s8)read_memory8(address); read_memory(16); return value; } u32 function_cc read_memory16s(u32 address) { return (u32)((s16)read_memory16_signed(address)); } // unaligned reads are actually 32bit u32 function_cc read_memory16(u32 address) { u32 value; bool unaligned = (address & 0x01); address &= ~0x01; read_memory(16); if (unaligned) { ror(value, value, 8); } return value; } u32 function_cc read_memory32(u32 address) { u32 value; u32 rotate = (address & 0x03) * 8; address &= ~0x03; read_memory(32); ror(value, value, rotate); return value; } cpu_alert_type function_cc write_memory8(u32 address, u8 value) { write_memory(8); return CPU_ALERT_NONE; } cpu_alert_type function_cc write_memory16(u32 address, u16 value) { write_memory(16); return CPU_ALERT_NONE; } cpu_alert_type function_cc write_memory32(u32 address, u32 value) { write_memory(32); return CPU_ALERT_NONE; } typedef struct { char gamepak_title[13]; char gamepak_code[5]; char gamepak_maker[3]; u16 flags; u32 idle_loop_target_pc; u32 translation_gate_target_1; u32 translation_gate_target_2; u32 translation_gate_target_3; } ini_t; typedef struct { char gamepak_title[13]; char gamepak_code[5]; char gamepak_maker[3]; } gamepak_info_t; #define FLAGS_FLASH_128KB 0x0001 #define FLAGS_RUMBLE 0x0002 #define FLAGS_RTC 0x0004 #define FLAGS_EEPROM 0x0010 #include "gba_over.h" static void load_game_config_over(gamepak_info_t *gpinfo) { unsigned i = 0; for (i = 0; i < sizeof(gbaover)/sizeof(gbaover[0]); i++) { if (strcmp(gbaover[i].gamepak_code, gpinfo->gamepak_code)) continue; if (strcmp(gbaover[i].gamepak_title, gpinfo->gamepak_title)) continue; printf("gamepak title: %s\n", gbaover[i].gamepak_title); printf("gamepak code : %s\n", gbaover[i].gamepak_code); printf("gamepak maker: %s\n", gbaover[i].gamepak_maker); printf("INPUT gamepak title: %s\n", gpinfo->gamepak_title); printf("INPUT gamepak code : %s\n", gpinfo->gamepak_code); printf("INPUT gamepak maker: %s\n", gpinfo->gamepak_maker); if (gbaover[i].idle_loop_target_pc != 0) idle_loop_target_pc = gbaover[i].idle_loop_target_pc; if (gbaover[i].flags & FLAGS_FLASH_128KB) { flash_device_id = FLASH_DEVICE_MACRONIX_128KB; 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].flags & FLAGS_EEPROM) backup_type_reset = BACKUP_EEPROM; if (gbaover[i].translation_gate_target_1 != 0) { translation_gate_target_pc[translation_gate_targets] = gbaover[i].translation_gate_target_1; translation_gate_targets++; } if (gbaover[i].translation_gate_target_2 != 0) { translation_gate_target_pc[translation_gate_targets] = gbaover[i].translation_gate_target_2; translation_gate_targets++; } if (gbaover[i].translation_gate_target_3 != 0) { translation_gate_target_pc[translation_gate_targets] = gbaover[i].translation_gate_target_3; translation_gate_targets++; } } } // DMA memory regions can be one of the following: // IWRAM - 32kb offset from the contiguous iwram region. // EWRAM - also contiguous but with self modifying code check mirror. // VRAM - 96kb offset from the contiguous vram region, should take care // Palette RAM - Converts palette entries when written to. // OAM RAM - Sets OAM modified flag to true. // I/O registers - Uses the I/O register function. // of mirroring properly. // Segmented RAM/ROM - a region >= 32kb, the translated address has to // be reloaded if it wraps around the limit (cartride ROM) // Ext - should be handled by the memory read/write function. // The following map determines the region of each (assumes DMA access // is not done out of bounds) typedef enum { DMA_REGION_IWRAM = 0, DMA_REGION_EWRAM = 1, DMA_REGION_VRAM = 2, DMA_REGION_PALETTE_RAM = 3, DMA_REGION_OAM_RAM = 4, DMA_REGION_IO = 5, DMA_REGION_EXT = 6, DMA_REGION_GAMEPAK = 7, DMA_REGION_BIOS = 8, DMA_REGION_NULL = 9 } dma_region_type; const dma_region_type dma_region_map[17] = { DMA_REGION_BIOS, // 0x00 - BIOS DMA_REGION_NULL, // 0x01 - Nothing DMA_REGION_EWRAM, // 0x02 - EWRAM DMA_REGION_IWRAM, // 0x03 - IWRAM DMA_REGION_IO, // 0x04 - I/O registers DMA_REGION_PALETTE_RAM, // 0x05 - palette RAM DMA_REGION_VRAM, // 0x06 - VRAM DMA_REGION_OAM_RAM, // 0x07 - OAM RAM DMA_REGION_GAMEPAK, // 0x08 - gamepak ROM DMA_REGION_GAMEPAK, // 0x09 - gamepak ROM DMA_REGION_GAMEPAK, // 0x0A - gamepak ROM DMA_REGION_GAMEPAK, // 0x0B - gamepak ROM DMA_REGION_GAMEPAK, // 0x0C - gamepak ROM DMA_REGION_EXT, // 0x0D - EEPROM DMA_REGION_EXT, // 0x0E - gamepak SRAM/flash ROM DMA_REGION_EXT, // 0x0F - gamepak SRAM/flash ROM DMA_REGION_NULL // 0x10 - Not possible (undefined?) }; #define dma_print(src_op, dest_op, tfsize) \ printf("dma from %x (%s) to %x (%s) for %x (%s) (%d) (pc %x)\n", \ src_ptr, #src_op, dest_ptr, #dest_op, length, #tfsize, \ dma->irq, reg[15]); \ #define dma_oam_ram_dest() \ reg[OAM_UPDATED] = 1 \ #define dma_vars_oam_ram(type) \ dma_oam_ram_##type() \ #define dma_vars_iwram(type) #define dma_vars_ewram(type) #define dma_vars_io(type) #define dma_vars_vram(type) #define dma_vars_palette_ram(type) #define dma_vars_bios(type) #define dma_vars_ext(type) #define dma_oam_ram_src() #define dma_segmented_load_src() \ memory_map_read[src_current_region] \ #define dma_vars_gamepak(type) \ u32 type##_new_region; \ u32 type##_current_region = type##_ptr >> 15; \ u8 *type##_address_block = dma_segmented_load_##type(); \ if(type##_address_block == NULL) \ { \ if((type##_ptr & 0x1FFFFFF) >= gamepak_size) \ break; \ type##_address_block = load_gamepak_page(type##_current_region & 0x3FF); \ } \ #define dma_gamepak_check_region(type) \ type##_new_region = (type##_ptr >> 15); \ if(type##_new_region != type##_current_region) \ { \ type##_current_region = type##_new_region; \ type##_address_block = dma_segmented_load_##type(); \ if(type##_address_block == NULL) \ { \ type##_address_block = \ load_gamepak_page(type##_current_region & 0x3FF); \ } \ } \ #define dma_read_iwram(type, tfsize) \ read_value = readaddress##tfsize(iwram + 0x8000, type##_ptr & 0x7FFF) \ #define dma_read_vram(type, tfsize) { \ u32 rdaddr = type##_ptr & 0x1FFFF; \ if (rdaddr >= 0x18000) rdaddr -= 0x8000; \ read_value = readaddress##tfsize(vram, rdaddr); \ } #define dma_read_io(type, tfsize) \ read_value = readaddress##tfsize(io_registers, type##_ptr & 0x3FF) \ #define dma_read_oam_ram(type, tfsize) \ read_value = readaddress##tfsize(oam_ram, type##_ptr & 0x3FF) \ #define dma_read_palette_ram(type, tfsize) \ read_value = readaddress##tfsize(palette_ram, type##_ptr & 0x3FF) \ #define dma_read_ewram(type, tfsize) \ read_value = readaddress##tfsize(ewram, type##_ptr & 0x3FFFF) \ #define dma_read_gamepak(type, tfsize) \ dma_gamepak_check_region(type); \ read_value = readaddress##tfsize(type##_address_block, \ type##_ptr & 0x7FFF) \ // DMAing from the BIOS is funny, just returns 0.. #define dma_read_bios(type, tfsize) \ read_value = 0 \ #define dma_read_ext(type, tfsize) \ read_value = read_memory##tfsize(type##_ptr) \ #define dma_write_iwram(type, tfsize) \ address##tfsize(iwram + 0x8000, type##_ptr & 0x7FFF) = \ eswap##tfsize(read_value); \ if (address##tfsize(iwram, type##_ptr & 0x7FFF)) \ alerts |= CPU_ALERT_SMC; \ #define dma_write_vram(type, tfsize) { \ u32 wraddr = type##_ptr & 0x1FFFF; \ if (wraddr >= 0x18000) wraddr -= 0x8000; \ address##tfsize(vram, wraddr) = eswap##tfsize(read_value); \ } #define dma_write_io(type, tfsize) \ alerts |= write_io_register##tfsize(type##_ptr & 0x3FF, read_value) \ #define dma_write_oam_ram(type, tfsize) \ address##tfsize(oam_ram, type##_ptr & 0x3FF) = eswap##tfsize(read_value) \ #define dma_write_palette_ram(type, tfsize) \ write_palette##tfsize(type##_ptr & 0x3FF, read_value) \ #define dma_write_ext(type, tfsize) \ write_memory##tfsize(type##_ptr, read_value) \ #define dma_write_ewram(type, tfsize) \ address##tfsize(ewram, type##_ptr & 0x3FFFF) = eswap##tfsize(read_value); \ if (address##tfsize(ewram, (type##_ptr & 0x3FFFF) + 0x40000)) \ alerts |= CPU_ALERT_SMC; \ #define print_line() \ dma_print(src_op, dest_op, tfsize); \ #define dma_tfloop(src_region_type, dest_region_type, src_op, dest_op, tfsize)\ { \ dma_vars_##src_region_type(src); \ dma_vars_##dest_region_type(dest); \ \ for(i = 0; i < length; i++) \ { \ dma_read_##src_region_type(src, tfsize); \ dma_write_##dest_region_type(dest, tfsize); \ src_ptr += src_op; \ dest_ptr += dest_op; \ } \ break; \ } \ #define dma_tf_loop_builder(tfsize) \ \ cpu_alert_type dma_tf_loop##tfsize( \ u32 src_ptr, u32 dest_ptr, int src_strd, int dest_strd, \ bool wb, u32 length, dma_transfer_type *dma) \ { \ u32 i; \ u32 read_value; \ cpu_alert_type alerts = CPU_ALERT_NONE; \ u32 src_region = MIN(src_ptr >> 24, 16); \ u32 dest_region = MIN(dest_ptr >> 24, 16); \ dma_region_type src_region_type = dma_region_map[src_region]; \ dma_region_type dest_region_type = dma_region_map[dest_region]; \ \ switch(src_region_type | (dest_region_type << 4)) \ { \ default: \ /* Do nothing (read-only destination or unmapped area...) */ \ return CPU_ALERT_NONE; \ \ case (DMA_REGION_BIOS | (DMA_REGION_IWRAM << 4)): \ dma_tfloop(bios, iwram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IWRAM | (DMA_REGION_IWRAM << 4)): \ dma_tfloop(iwram, iwram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EWRAM | (DMA_REGION_IWRAM << 4)): \ dma_tfloop(ewram, iwram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_VRAM | (DMA_REGION_IWRAM << 4)): \ dma_tfloop(vram, iwram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_PALETTE_RAM | (DMA_REGION_IWRAM << 4)): \ dma_tfloop(palette_ram, iwram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_OAM_RAM | (DMA_REGION_IWRAM << 4)): \ dma_tfloop(oam_ram, iwram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IO | (DMA_REGION_IWRAM << 4)): \ dma_tfloop(io, iwram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_GAMEPAK | (DMA_REGION_IWRAM << 4)): \ dma_tfloop(gamepak, iwram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EXT | (DMA_REGION_IWRAM << 4)): \ dma_tfloop(ext, iwram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_BIOS | (DMA_REGION_EWRAM << 4)): \ dma_tfloop(bios, ewram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IWRAM | (DMA_REGION_EWRAM << 4)): \ dma_tfloop(iwram, ewram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EWRAM | (DMA_REGION_EWRAM << 4)): \ dma_tfloop(ewram, ewram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_VRAM | (DMA_REGION_EWRAM << 4)): \ dma_tfloop(vram, ewram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_PALETTE_RAM | (DMA_REGION_EWRAM << 4)): \ dma_tfloop(palette_ram, ewram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_OAM_RAM | (DMA_REGION_EWRAM << 4)): \ dma_tfloop(oam_ram, ewram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IO | (DMA_REGION_EWRAM << 4)): \ dma_tfloop(io, ewram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_GAMEPAK | (DMA_REGION_EWRAM << 4)): \ dma_tfloop(gamepak, ewram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EXT | (DMA_REGION_EWRAM << 4)): \ dma_tfloop(ext, ewram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_BIOS | (DMA_REGION_VRAM << 4)): \ dma_tfloop(bios, vram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IWRAM | (DMA_REGION_VRAM << 4)): \ dma_tfloop(iwram, vram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EWRAM | (DMA_REGION_VRAM << 4)): \ dma_tfloop(ewram, vram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_VRAM | (DMA_REGION_VRAM << 4)): \ dma_tfloop(vram, vram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_PALETTE_RAM | (DMA_REGION_VRAM << 4)): \ dma_tfloop(palette_ram, vram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_OAM_RAM | (DMA_REGION_VRAM << 4)): \ dma_tfloop(oam_ram, vram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IO | (DMA_REGION_VRAM << 4)): \ dma_tfloop(io, vram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_GAMEPAK | (DMA_REGION_VRAM << 4)): \ dma_tfloop(gamepak, vram, src_strd, dest_strd,tfsize); \ \ case (DMA_REGION_EXT | (DMA_REGION_VRAM << 4)): \ dma_tfloop(ext, vram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_BIOS | (DMA_REGION_PALETTE_RAM << 4)): \ dma_tfloop(bios, palette_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IWRAM | (DMA_REGION_PALETTE_RAM << 4)): \ dma_tfloop(iwram, palette_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EWRAM | (DMA_REGION_PALETTE_RAM << 4)): \ dma_tfloop(ewram, palette_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_VRAM | (DMA_REGION_PALETTE_RAM << 4)): \ dma_tfloop(vram, palette_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_PALETTE_RAM | (DMA_REGION_PALETTE_RAM << 4)): \ dma_tfloop(palette_ram, palette_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_OAM_RAM | (DMA_REGION_PALETTE_RAM << 4)): \ dma_tfloop(oam_ram, palette_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IO | (DMA_REGION_PALETTE_RAM << 4)): \ dma_tfloop(io, palette_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_GAMEPAK | (DMA_REGION_PALETTE_RAM << 4)): \ dma_tfloop(gamepak, palette_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EXT | (DMA_REGION_PALETTE_RAM << 4)): \ dma_tfloop(ext, palette_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_BIOS | (DMA_REGION_OAM_RAM << 4)): \ dma_tfloop(bios, oam_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IWRAM | (DMA_REGION_OAM_RAM << 4)): \ dma_tfloop(iwram, oam_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EWRAM | (DMA_REGION_OAM_RAM << 4)): \ dma_tfloop(ewram, oam_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_VRAM | (DMA_REGION_OAM_RAM << 4)): \ dma_tfloop(vram, oam_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_PALETTE_RAM | (DMA_REGION_OAM_RAM << 4)): \ dma_tfloop(palette_ram, oam_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_OAM_RAM | (DMA_REGION_OAM_RAM << 4)): \ dma_tfloop(oam_ram, oam_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IO | (DMA_REGION_OAM_RAM << 4)): \ dma_tfloop(io, oam_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_GAMEPAK | (DMA_REGION_OAM_RAM << 4)): \ dma_tfloop(gamepak, oam_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EXT | (DMA_REGION_OAM_RAM << 4)): \ dma_tfloop(ext, oam_ram, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_BIOS | (DMA_REGION_IO << 4)): \ dma_tfloop(bios, io, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IWRAM | (DMA_REGION_IO << 4)): \ dma_tfloop(iwram, io, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EWRAM | (DMA_REGION_IO << 4)): \ dma_tfloop(ewram, io, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_VRAM | (DMA_REGION_IO << 4)): \ dma_tfloop(vram, io, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_PALETTE_RAM | (DMA_REGION_IO << 4)): \ dma_tfloop(palette_ram, io, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_OAM_RAM | (DMA_REGION_IO << 4)): \ dma_tfloop(oam_ram, io, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IO | (DMA_REGION_IO << 4)): \ dma_tfloop(io, io, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_GAMEPAK | (DMA_REGION_IO << 4)): \ dma_tfloop(gamepak, io, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EXT | (DMA_REGION_IO << 4)): \ dma_tfloop(ext, io, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_BIOS | (DMA_REGION_EXT << 4)): \ dma_tfloop(bios, ext, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IWRAM | (DMA_REGION_EXT << 4)): \ dma_tfloop(iwram, ext, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EWRAM | (DMA_REGION_EXT << 4)): \ dma_tfloop(ewram, ext, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_VRAM | (DMA_REGION_EXT << 4)): \ dma_tfloop(vram, ext, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_PALETTE_RAM | (DMA_REGION_EXT << 4)): \ dma_tfloop(palette_ram, ext, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_OAM_RAM | (DMA_REGION_EXT << 4)): \ dma_tfloop(oam_ram, ext, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_IO | (DMA_REGION_EXT << 4)): \ dma_tfloop(io, ext, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_GAMEPAK | (DMA_REGION_EXT << 4)): \ dma_tfloop(gamepak, ext, src_strd, dest_strd, tfsize); \ \ case (DMA_REGION_EXT | (DMA_REGION_EXT << 4)): \ dma_tfloop(ext, ext, src_strd, dest_strd, tfsize); \ } \ \ dma->source_address = src_ptr; \ if (wb) /* Update destination pointer if requested */ \ dma->dest_address = dest_ptr; \ \ return alerts; \ } \ dma_tf_loop_builder(16); dma_tf_loop_builder(32); static const int dma_stride[4] = {1, -1, 0, 1}; static cpu_alert_type dma_transfer_copy( dma_transfer_type *dmach, u32 src_ptr, u32 dest_ptr, u32 length) { if (dmach->source_direction < 3) { int dst_stride = dma_stride[dmach->dest_direction]; int src_stride = dma_stride[dmach->source_direction]; bool dst_wb = dmach->dest_direction < 3; if(dmach->length_type == DMA_16BIT) return dma_tf_loop16(src_ptr, dest_ptr, 2 * src_stride, 2 * dst_stride, dst_wb, length, dmach); else return dma_tf_loop32(src_ptr, dest_ptr, 4 * src_stride, 4 * dst_stride, dst_wb, length, dmach); } return CPU_ALERT_NONE; } cpu_alert_type dma_transfer(unsigned dma_chan, int *usedcycles) { dma_transfer_type *dmach = &dma[dma_chan]; u32 src_ptr = dmach->source_address & ( dmach->length_type == DMA_16BIT ? ~1U : ~3U); u32 dst_ptr = dmach->dest_address & ( dmach->length_type == DMA_16BIT ? ~1U : ~3U); cpu_alert_type ret = CPU_ALERT_NONE; u32 tfsizes = dmach->length_type == DMA_16BIT ? 1 : 2; u32 byte_length = dmach->length << tfsizes; // Divide the DMA transaction into up to three transactions depending on // the source and destination memory regions. u32 src_end = MIN(0x10000000, src_ptr + byte_length * dma_stride[dmach->source_direction]); u32 dst_end = MIN(0x10000000, dst_ptr + byte_length * dma_stride[dmach->dest_direction]); dma_region_type src_reg0 = dma_region_map[src_ptr >> 24]; dma_region_type src_reg1 = dma_region_map[src_end >> 24]; dma_region_type dst_reg0 = dma_region_map[dst_ptr >> 24]; dma_region_type dst_reg1 = dma_region_map[dst_end >> 24]; if (src_reg0 == src_reg1 && dst_reg0 == dst_reg1) ret = dma_transfer_copy(dmach, src_ptr, dst_ptr, byte_length >> tfsizes); else if (src_reg0 == src_reg1) { // Source stays within the region, dest crosses over u32 blen0 = dma_stride[dmach->dest_direction] < 0 ? dst_ptr & 0xFFFFFF : 0x1000000 - (dst_ptr & 0xFFFFFF); u32 src1 = src_ptr + blen0 * dma_stride[dmach->source_direction]; u32 dst1 = dst_ptr + blen0 * dma_stride[dmach->dest_direction]; ret = dma_transfer_copy(dmach, src_ptr, dst_ptr, blen0 >> tfsizes); ret |= dma_transfer_copy(dmach, src1, dst1, (byte_length - blen0) >> tfsizes); } else if (dst_reg0 == dst_reg1) { // Dest stays within the region, source crosses over u32 blen0 = dma_stride[dmach->source_direction] < 0 ? src_ptr & 0xFFFFFF : 0x1000000 - (src_ptr & 0xFFFFFF); u32 src1 = src_ptr + blen0 * dma_stride[dmach->source_direction]; u32 dst1 = dst_ptr + blen0 * dma_stride[dmach->dest_direction]; ret = dma_transfer_copy(dmach, src_ptr, dst_ptr, blen0 >> tfsizes); ret |= dma_transfer_copy(dmach, src1, dst1, (byte_length - blen0) >> tfsizes); } // TODO: We do not cover the three-region case, seems no game uses that? // Lucky Luke does cross dest region due to some off-by-one error. if((dmach->repeat_type == DMA_NO_REPEAT) || (dmach->start_type == DMA_START_IMMEDIATELY)) { u32 cntrl = read_dmareg(REG_DMA0CNT_H, dma_chan); write_dmareg(REG_DMA0CNT_H, dma_chan, cntrl & (~0x8000)); dmach->start_type = DMA_INACTIVE; } // Trigger an IRQ if configured to do so. if (dmach->irq) ret |= flag_interrupt(IRQ_DMA0 << dma_chan); // This is an approximation for the most common case (no region cross) if (usedcycles) *usedcycles += dmach->length * ( def_seq_cycles[src_ptr >> 24][tfsizes - 1] + def_seq_cycles[dst_ptr >> 24][tfsizes - 1]); return ret; } // Be sure to do this after loading ROMs. #define map_rom_entry(type, idx, ptr, mirror_blocks) { \ unsigned mcount; \ for(mcount = 0; mcount < 1024; mcount += (mirror_blocks)) { \ memory_map_##type[(0x8000000 / (32 * 1024)) + (idx) + mcount] = (ptr); \ memory_map_##type[(0xA000000 / (32 * 1024)) + (idx) + mcount] = (ptr); \ } \ for(mcount = 0; mcount < 512; mcount += (mirror_blocks)) { \ memory_map_##type[(0xC000000 / (32 * 1024)) + (idx) + mcount] = (ptr); \ } \ } #define map_region(type, start, end, mirror_blocks, region) \ for(map_offset = (start) / 0x8000; map_offset < \ ((end) / 0x8000); map_offset++) \ { \ memory_map_##type[map_offset] = \ ((u8 *)region) + ((map_offset % mirror_blocks) * 0x8000); \ } \ #define map_null(type, start, end) { \ u32 map_offset; \ for(map_offset = start / 0x8000; map_offset < (end / 0x8000); \ map_offset++) \ memory_map_##type[map_offset] = NULL; \ } #define map_vram(type) \ for(map_offset = 0x6000000 / 0x8000; map_offset < (0x7000000 / 0x8000); \ map_offset += 4) \ { \ memory_map_##type[map_offset] = vram; \ memory_map_##type[map_offset + 1] = vram + 0x8000; \ memory_map_##type[map_offset + 2] = vram + (0x8000 * 2); \ memory_map_##type[map_offset + 3] = vram + (0x8000 * 2); \ } \ static u32 evict_gamepak_page(void) { u32 ret; s16 phyrom; do { // Return the index to the last used entry u32 newhead = gamepak_blk_queue[gamepak_lru_head].next_lru; phyrom = gamepak_blk_queue[gamepak_lru_head].phy_rom; ret = gamepak_lru_head; // Second elem becomes head now gamepak_lru_head = newhead; // The evicted element goes at the end of the queue gamepak_blk_queue[gamepak_lru_tail].next_lru = ret; gamepak_lru_tail = ret; // If this page is marked as sticky, we keep going through the list } while (phyrom >= 0 && gamepak_sb_test(phyrom)); // We unmap the ROM page if it was mapped, ensure we do not access it // without triggering a "page fault" if (phyrom >= 0) { map_rom_entry(read, phyrom, NULL, gamepak_size >> 15); } return ret; } u8 *load_gamepak_page(u32 physical_index) { if(physical_index >= (gamepak_size >> 15)) return &gamepak_buffers[0][0]; u32 entry = evict_gamepak_page(); u32 block_idx = entry / 32; u32 block_off = entry % 32; u8 *swap_location = &gamepak_buffers[block_idx][32 * 1024 * block_off]; // Fill in the entry gamepak_blk_queue[entry].phy_rom = physical_index; filestream_seek(gamepak_file_large, physical_index * (32 * 1024), SEEK_SET); filestream_read(gamepak_file_large, swap_location, (32 * 1024)); // Map it to the read handlers now map_rom_entry(read, physical_index, swap_location, gamepak_size >> 15); // When mapping page 0, we might need to reflect the GPIO regs. if (physical_index == 0) update_gpio_romregs(); return swap_location; } void init_gamepak_buffer(void) { unsigned i; // Try to allocate up to 32 blocks of 1MB each gamepak_buffer_count = 0; while (gamepak_buffer_count < ROM_BUFFER_SIZE) { void *ptr = malloc(gamepak_buffer_blocksize); if (!ptr) break; gamepak_buffers[gamepak_buffer_count++] = (u8*)ptr; } // Initialize the memory map structure for (i = 0; i < 1024; i++) { gamepak_blk_queue[i].next_lru = (u16)(i + 1); gamepak_blk_queue[i].phy_rom = -1; } gamepak_lru_head = 0; gamepak_lru_tail = 32 * gamepak_buffer_count - 1; } bool gamepak_must_swap(void) { // Returns whether the current gamepak buffer is not big enough to hold // the full gamepak ROM. In these cases the device must swap. return gamepak_buffer_count * gamepak_buffer_blocksize < gamepak_size; } void init_memory(void) { u32 map_offset = 0, i; for (i = 0; i < DMA_CHAN_CNT; i++) { dma[i].start_type = DMA_INACTIVE; dma[i].irq = DMA_NO_IRQ; dma[i].source_address = dma[i].dest_address = 0; dma[i].source_direction = dma[i].dest_direction = 0; dma[i].length = 0; dma[i].length_type = DMA_16BIT; dma[i].repeat_type = DMA_NO_REPEAT; dma[i].direct_sound_channel = DMA_NO_DIRECT_SOUND; } // Fill memory map regions, areas marked as NULL must be checked directly map_region(read, 0x0000000, 0x1000000, 1, bios_rom); map_null(read, 0x1000000, 0x2000000); map_region(read, 0x2000000, 0x3000000, 8, ewram); map_region(read, 0x3000000, 0x4000000, 1, &iwram[0x8000]); map_region(read, 0x4000000, 0x5000000, 1, io_registers); map_null(read, 0x5000000, 0x6000000); map_null(read, 0x6000000, 0x7000000); map_vram(read); map_null(read, 0x7000000, 0x8000000); map_null(read, 0xE000000, 0x10000000); memset(io_registers, 0, sizeof(io_registers)); memset(oam_ram, 0, sizeof(oam_ram)); memset(palette_ram, 0, sizeof(palette_ram)); memset(iwram, 0, sizeof(iwram)); memset(ewram, 0, sizeof(ewram)); memset(vram, 0, sizeof(vram)); write_ioreg(REG_DISPCNT, 0x80); write_ioreg(REG_P1, 0x3FF); write_ioreg(REG_BG2PA, 0x100); write_ioreg(REG_BG2PD, 0x100); write_ioreg(REG_BG3PA, 0x100); write_ioreg(REG_BG3PD, 0x100); write_ioreg(REG_RCNT, 0x8000); reload_timing_info(); backup_type = backup_type_reset; flash_bank_num = 0; flash_command_position = 0; eeprom_size = EEPROM_512_BYTE; 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(gpio_regs, 0, sizeof(gpio_regs)); reg[REG_BUS_VALUE] = 0xe129f000; } void memory_term(void) { if (gamepak_file_large) { filestream_close(gamepak_file_large); gamepak_file_large = NULL; } while (gamepak_buffer_count) { free(gamepak_buffers[--gamepak_buffer_count]); } } 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-bit-cnt", "rtc-bit-cnt", }; static const char *dmavars32[] = { "src-addr", "dst-addr", "src-dir", "dst-dir", "len", "size", "repeat", "start", "dsc", "irq" }; 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; // Check memory buffers (TODO: check sizes!) if (!bson_contains_key(memdoc, "iwram", BSON_TYPE_BIN) || !bson_contains_key(memdoc, "ewram", BSON_TYPE_BIN) || !bson_contains_key(memdoc, "vram", BSON_TYPE_BIN) || !bson_contains_key(memdoc, "oamram", BSON_TYPE_BIN) || !bson_contains_key(memdoc, "palram", BSON_TYPE_BIN) || !bson_contains_key(memdoc, "ioregs", BSON_TYPE_BIN)) return false; // Check backup variables for (i = 0; i < sizeof(vars32)/sizeof(vars32[0]); i++) if (!bson_contains_key(bakdoc, vars32[i], BSON_TYPE_INT32)) return false; if (!bson_contains_key(bakdoc, "gpio-regs", BSON_TYPE_BIN) || !bson_contains_key(bakdoc, "rtc-data-words", BSON_TYPE_ARR)) return false; for (i = 0; i < DMA_CHAN_CNT; i++) { char tname[2] = {'0' + i, 0}; const u8 *dmastr = bson_find_key(dmadoc, tname); if (!dmastr) return false; for (i = 0; i < sizeof(dmavars32)/sizeof(dmavars32[0]); i++) if (!bson_contains_key(dmastr, dmavars32[i], BSON_TYPE_INT32)) return false; } return true; } 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"); if (!memdoc || !bakdoc || !dmadoc) return false; 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)) && bson_read_int32(bakdoc, "backup-type", &backup_type) && 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-size", &flash_bank_cnt) && 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) && 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-bit-cnt", &rtc_data_bits) && bson_read_int32(bakdoc, "rtc-bit-cnt", (u32*)&rtc_bit_count) && bson_read_int32_array(bakdoc, "rtc-data-words", rtc_data_array, 2))) return false; 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; } rtc_data = rtc_data_array[0] | (((u64)rtc_data_array[1]) << 32); return true; } 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); 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", (u32)backup_type); 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-size", flash_bank_cnt); 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_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-bit-cnt", rtc_data_bits); bson_write_int32(dst, "rtc-bit-cnt", rtc_bit_count); bson_write_int32array(dst, "rtc-data-words", rtc_data_array, 2); 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); } static s32 load_gamepak_raw(const char *name) { unsigned i, j; gamepak_file_large = filestream_open(name, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); if(gamepak_file_large) { // Round size to 32KB pages gamepak_size = (u32)filestream_get_size(gamepak_file_large); gamepak_size = (gamepak_size + 0x7FFF) & ~0x7FFF; // Load stuff in 1MB chunks u32 buf_blocks = (gamepak_size + gamepak_buffer_blocksize-1) / (gamepak_buffer_blocksize); u32 rom_blocks = gamepak_size >> 15; u32 ldblks = buf_blocks < gamepak_buffer_count ? buf_blocks : gamepak_buffer_count; // Unmap the ROM space since we will re-map it now map_null(read, 0x8000000, 0xD000000); // Proceed to read the whole ROM or as much as possible. for (i = 0; i < ldblks; i++) { // Load 1MB chunk and map it filestream_read(gamepak_file_large, gamepak_buffers[i], gamepak_buffer_blocksize); for (j = 0; j < 32 && i*32 + j < rom_blocks; j++) { u32 phyn = i*32 + j; u8* blkptr = &gamepak_buffers[i][32 * 1024 * j]; u32 entry = evict_gamepak_page(); gamepak_blk_queue[entry].phy_rom = phyn; // Map it to the read handlers now map_rom_entry(read, phyn, blkptr, rom_blocks); } } return 0; } return -1; } u32 load_gamepak(const struct retro_game_info* info, const char *name, int force_rtc, int force_rumble) { gamepak_info_t gpinfo; if (load_gamepak_raw(name)) return -1; // Buffer 0 always has the first 1MB chunk of the ROM memset(&gpinfo, 0, sizeof(gpinfo)); memcpy(gpinfo.gamepak_title, &gamepak_buffers[0][0xA0], 12); memcpy(gpinfo.gamepak_code, &gamepak_buffers[0][0xAC], 4); memcpy(gpinfo.gamepak_maker, &gamepak_buffers[0][0xB0], 2); idle_loop_target_pc = 0xFFFFFFFF; translation_gate_targets = 0; flash_device_id = FLASH_DEVICE_MACRONIX_64KB; flash_bank_cnt = FLASH_SIZE_64KB; rtc_enabled = false; rumble_enabled = false; backup_type_reset = BACKUP_UNKN; 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; } s32 load_bios(char *name) { RFILE *fd = filestream_open(name, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); if(!fd) return -1; filestream_read(fd, bios_rom, 0x4000); filestream_close(fd); return 0; }