// DGen/SDL v1.17+ // Megadrive C++ module #include #include #include #include #include #ifdef HAVE_MEMCPY_H #include "memcpy.h" #endif #include "md.h" #include "system.h" #include "romload.h" #include "rc-vars.h" #include "debug.h" #include "decode.h" extern FILE *debug_log; #ifdef WITH_STAR extern "C" unsigned star_readbyte(unsigned a, unsigned d); extern "C" unsigned star_readword(unsigned a, unsigned d); extern "C" unsigned star_writebyte(unsigned a, unsigned d); extern "C" unsigned star_writeword(unsigned a, unsigned d); /** * This sets up an array of memory locations. * This method is StarScream specific. * @return 0 on success */ int md::memory_map() { int i=0,j=0; int rommax=romlen; if (rommax>0xa00000) rommax=0xa00000; if (rommax<0) rommax=0; // FETCH: Set up 2 or 3 FETCH sections i=0; if (rommax>0) { fetch[i].lowaddr=0x000000; fetch[i].highaddr=rommax-1; fetch[i].offset=(unsigned)rom-0x000000; i++; } fetch[i].lowaddr=0xff0000; fetch[i].highaddr=0xffffff; fetch[i].offset=(unsigned)ram- 0xff0000; i++; // Testing fetch[i].lowaddr=0xffff0000; fetch[i].highaddr=0xffffffff; fetch[i].offset=(unsigned)ram-0xffff0000; i++; // Testing 2 fetch[i].lowaddr=0xff000000; fetch[i].highaddr=0xff000000+rommax-1; fetch[i].offset=(unsigned)rom-0xff000000; i++; fetch[i].lowaddr=fetch[i].highaddr=0xffffffff; fetch[i].offset=0; i++; if (debug_log!=NULL) fprintf (debug_log,"StarScream memory_map has %d fetch sections\n",i); i=0; j=0; #if 0 // Simple version *************** readbyte[i].lowaddr= readword[i].lowaddr= writebyte[j].lowaddr= writeword[j].lowaddr= 0; readbyte[i].highaddr= readword[i].highaddr= writebyte[j].highaddr= writeword[j].highaddr= 0xffffffff; readbyte[i].memorycall=(void *)star_readbyte; readword[i].memorycall=(void *)star_readword; writebyte[j].memorycall=(void *)star_writebyte; writeword[j].memorycall=(void *)star_writeword; readbyte[i].userdata= readword[i].userdata= writebyte[j].userdata= writeword[j].userdata= NULL; i++; j++; // Simple version end *************** #else // Faster version *************** // IO: Set up 3/4 read sections, and 2/3 write sections if (rommax>0) { // Cartridge save RAM memory if(save_len) { readbyte[i].lowaddr= readword[i].lowaddr= writebyte[j].lowaddr= writeword[j].lowaddr= save_start; readbyte[i].highaddr= readword[i].highaddr= writebyte[j].highaddr= writeword[j].highaddr= save_start+save_len-1; readbyte[i].memorycall = star_readbyte; readword[j].memorycall = star_readword; writebyte[i].memorycall = star_writebyte; writeword[j].memorycall = star_writeword; readbyte[i].userdata= readword[i].userdata= writebyte[j].userdata= writeword[j].userdata= NULL; i++; j++; } // Cartridge ROM memory (read only) readbyte[i].lowaddr= readword[i].lowaddr= 0x000000; readbyte[i].highaddr= readword[i].highaddr= rommax-1; readbyte[i].memorycall=readword[i].memorycall=NULL; readbyte[i].userdata= readword[i].userdata= rom; i++; // misc memory (e.g. aoo and coo) through star_rw readbyte[i].lowaddr= readword[i].lowaddr= writebyte[j].lowaddr= writeword[j].lowaddr= rommax; } else readbyte[i].lowaddr= readword[i].lowaddr= writebyte[j].lowaddr= writeword[j].lowaddr= 0; readbyte[i].highaddr= readword[i].highaddr= writebyte[j].highaddr= writeword[j].highaddr= 0xfeffff; readbyte[i].memorycall = star_readbyte; readword[i].memorycall = star_readword; writebyte[j].memorycall = star_writebyte; writeword[j].memorycall = star_writeword; readbyte[i].userdata= readword[i].userdata= writebyte[j].userdata= writeword[j].userdata= NULL; i++; j++; // scratch RAM memory readbyte[i].lowaddr = readword[i].lowaddr = writebyte[j].lowaddr = writeword[j].lowaddr = 0xff0000; readbyte[i].highaddr= readword[i].highaddr= writebyte[j].highaddr= writeword[j].highaddr= 0xffffff; readbyte[i].memorycall= readword[i].memorycall= writebyte[j].memorycall=writeword[j].memorycall= NULL; readbyte[i].userdata= readword[i].userdata = writebyte[j].userdata= writeword[j].userdata = ram; i++; j++; // Faster version end *************** #endif // The end readbyte[i].lowaddr = readword[i].lowaddr = writebyte[j].lowaddr = writeword[j].lowaddr = readbyte[i].highaddr = readword[i].highaddr = writebyte[j].highaddr = writeword[j].highaddr = 0xffffffff; readbyte[i].memorycall = 0; readword[i].memorycall = 0; writebyte[j].memorycall = 0; writeword[j].memorycall = 0; readbyte[i].userdata = 0; readword[i].userdata = 0; writebyte[j].userdata = 0; writeword[j].userdata = 0; i++; j++; if (debug_log!=NULL) fprintf (debug_log,"StarScream memory_map has %d read sections and %d write sections\n",i,j); return 0; } void star_irq_callback(void) { assert(md::md_star != NULL); md::md_star->m68k_vdp_irq_handler(); } #endif #ifdef WITH_MUSA /** * This sets up an array of memory locations for Musashi. */ void md::musa_memory_map() { unsigned int rom0_len = romlen; unsigned int rom1_sta = 0; unsigned int rom1_len = 0; m68k_register_memory(NULL, 0); if (save_len) { DEBUG(("[%06x-%06x] ???? (SAVE)", save_start, (save_start + save_len - 1))); if (save_start < romlen) { /* Punch a hole through the ROM area. */ rom0_len = save_start; /* Add entry for ROM leftovers, if any. */ if ((save_start + save_len) < romlen) { rom1_sta = (save_start + save_len); rom1_len = (romlen - rom1_sta); } } } #ifdef ROM_BYTESWAP #define S 1 #else #define S 0 #endif const m68k_mem_t mem[3] = { // r, w, x, swab, addr, size, mask, mem { 1, 0, 1, S, 0x000000, rom0_len, 0x7fffff, rom }, // M68K ROM { 1, 1, 1, 1, 0xe00000, 0x200000, 0x00ffff, ram }, // M68K RAM { 1, 0, 1, S, rom1_sta, rom1_len, 0x7fffff, &rom[rom1_sta] } }; unsigned int i; unsigned int j = 0; for (i = 0; ((i < elemof(mem)) && (j < elemof(musa_memory))); ++i) { if (mem[i].size == 0) continue; DEBUG(("[%06x-%06x] %c%c%c%c (%s)", mem[i].addr, (mem[i].addr + mem[i].size - 1), (mem[i].r ? 'r' : '-'), (mem[i].w ? 'w' : '-'), (mem[i].x ? 'x' : '-'), (mem[i].swab ? 's' : '-'), (mem[i].w ? "RAM" : "ROM"))); musa_memory[j] = mem[i]; ++j; } if (j) m68k_register_memory(musa_memory, j); else DEBUG(("no memory region defined")); } int musa_irq_callback(int level) { (void)level; assert(md::md_musa != NULL); md::md_musa->m68k_vdp_irq_handler(); return M68K_INT_ACK_AUTOVECTOR; } #endif #ifdef WITH_CYCLONE extern "C" uint32_t cyclone_read_memory_8(uint32_t address); extern "C" uint32_t cyclone_read_memory_16(uint32_t address); extern "C" uint32_t cyclone_read_memory_32(uint32_t address); extern "C" void cyclone_write_memory_8(uint32_t address, uint8_t value); extern "C" void cyclone_write_memory_16(uint32_t address, uint16_t value); extern "C" void cyclone_write_memory_32(uint32_t address, uint32_t value); extern "C" uintptr_t cyclone_checkpc(uintptr_t pc); int cyclone_irq_callback(int level) { (void)level; assert(md::md_cyclone != NULL); md::md_cyclone->m68k_vdp_irq_handler(); return CYCLONE_INT_ACK_AUTOVECTOR; } #endif /** * Resets everything (Z80, M68K, VDP, etc). * @return 0 on success */ int md::reset() { // Clear memory. memset(mem, 0, 0x20000); // Reset the VDP. vdp.reset(); // Erase CPU states. memset(&m68k_state, 0, sizeof(m68k_state)); memset(&z80_state, 0, sizeof(z80_state)); m68k_state_restore(); z80_state_restore(); #ifdef WITH_STAR md_set_star(1); s68000reset(); md_set_star(0); #endif #ifdef WITH_MUSA md_set_musa(1); m68k_pulse_reset(); md_set_musa(0); #endif #ifdef WITH_CYCLONE md_set_cyclone(1); CycloneReset(&cyclonecpu); md_set_cyclone(0); #endif #ifdef WITH_DEBUGGER debug_m68k_instr_count = 0; debug_z80_instr_count = 0; #endif if (debug_log) fprintf (debug_log,"reset()\n"); aoo3_toggle=aoo5_toggle=aoo3_six=aoo5_six =aoo3_six_timeout=aoo5_six_timeout =coo4=coo5=0; pad[0] = MD_PAD_UNTOUCHED; pad[1] = MD_PAD_UNTOUCHED; memset(pad_com, 0, sizeof(pad_com)); #ifdef WITH_PICO // Initialize Pico pen X, Y coordinates pico_pen_coords[0] = 0x3c; pico_pen_coords[1] = 0x1fc; #endif // Reset FM registers fm_reset(); dac_init(); memset(&odo, 0, sizeof(odo)); ras = 0; z80_st_running = 0; m68k_st_running = 0; z80_reset(); z80_st_busreq = 1; z80_st_reset = 1; z80_st_irq = 0; return 0; } #ifdef WITH_MZ80 extern "C" UINT8 mz80_read(UINT32 a, struct MemoryReadByte *unused); extern "C" void mz80_write(UINT32 a, UINT8 d, struct MemoryWriteByte *unused); extern "C" UINT16 mz80_ioread(UINT16 a, struct z80PortRead *unused); extern "C" void mz80_iowrite(UINT16 a, UINT8 d, struct z80PortWrite *unused); static struct MemoryReadByte mem_read[] = { { 0x0000, 0xffff, mz80_read, NULL }, { (UINT32)-1, (UINT32)-1, NULL, NULL } }; static struct MemoryWriteByte mem_write[] = { { 0x0000, 0xffff, mz80_write, NULL }, { (UINT32)-1, (UINT32)-1, NULL, NULL } }; static struct z80PortRead io_read[] = { { 0x00, 0xff, mz80_ioread, NULL }, { (UINT16)-1, (UINT16)-1, NULL, NULL } }; static struct z80PortWrite io_write[] = { { 0x00, 0xff, mz80_iowrite, NULL }, { (UINT16)-1, (UINT16)-1, NULL, NULL } }; #endif // WITH_MZ80 #ifdef WITH_CZ80 extern "C" uint8_t cz80_memread(void *ctx, uint16_t a); extern "C" void cz80_memwrite(void *ctx, uint16_t a, uint8_t d); extern "C" uint16_t cz80_memread16(void *ctx, uint16_t a); extern "C" void cz80_memwrite16(void *ctx, uint16_t a, uint16_t d); extern "C" uint8_t cz80_ioread(void *ctx, uint16_t a); extern "C" void cz80_iowrite(void *ctx, uint16_t a, uint8_t d); #endif // WITH_CZ80 #ifdef WITH_DRZ80 extern uintptr_t drz80_rebaseSP(uint16_t new_sp); extern uintptr_t drz80_rebasePC(uint16_t new_pc); extern uint8_t drz80_read8(uint16_t a); extern uint16_t drz80_read16(uint16_t a); extern void drz80_write8(uint8_t d, uint16_t a); extern void drz80_write16(uint16_t d, uint16_t a); extern uint8_t drz80_in(uint16_t p); extern void drz80_out(uint16_t p, uint8_t d); void drz80_irq_callback() { md::md_drz80->drz80_irq_cb(); } void md::drz80_irq_cb() { drz80.Z80_IRQ = 0x00; // lower irq when in accepted } #endif // WITH_DRZ80 /** * Initialise the Z80. */ void md::z80_init() { #ifdef WITH_MZ80 md_set_mz80(1); mz80init(); mz80reset(); // Erase local context with global context. mz80GetContext(&z80); // Configure callbacks in local context. z80.z80Base = z80ram; z80.z80MemRead = mem_read; z80.z80MemWrite = mem_write; z80.z80IoRead = io_read; z80.z80IoWrite = io_write; // Erase global context with the above. mz80SetContext(&z80); md_set_mz80(0); #endif #ifdef WITH_CZ80 Cz80_Set_Ctx(&cz80, this); Cz80_Set_Fetch(&cz80, 0x0000, 0xffff, (void *)z80ram); Cz80_Set_ReadB(&cz80, cz80_memread); Cz80_Set_WriteB(&cz80, cz80_memwrite); Cz80_Set_ReadW(&cz80, cz80_memread16); Cz80_Set_WriteW(&cz80, cz80_memwrite16); Cz80_Set_INPort(&cz80, cz80_ioread); Cz80_Set_OUTPort(&cz80, cz80_iowrite); Cz80_Reset(&cz80); #endif #ifdef WITH_DRZ80 memset(&drz80, 0, sizeof(drz80)); drz80.z80_write8 = drz80_write8; drz80.z80_write16 = drz80_write16; drz80.z80_in = drz80_in; drz80.z80_out = drz80_out; drz80.z80_read8 = drz80_read8; drz80.z80_read16 = drz80_read16; drz80.z80_rebasePC = drz80_rebasePC; drz80.z80_rebaseSP = drz80_rebaseSP; drz80.z80_irq_callback = drz80_irq_callback; #endif z80_st_busreq = 1; z80_st_reset = 0; z80_bank68k = 0xff8000; } /** * Reset the Z80. */ void md::z80_reset() { z80_bank68k = 0xff8000; #ifdef WITH_MZ80 md_set_mz80(1); mz80reset(); md_set_mz80(0); #endif #ifdef WITH_CZ80 Cz80_Reset(&cz80); #endif #ifdef WITH_DRZ80 md_set_drz80(1); drz80.Z80A = (1 << 2); // set ZFlag drz80.Z80F = (1 << 2); // set ZFlag drz80.Z80BC = 0; drz80.Z80DE = 0; drz80.Z80HL = 0; drz80.Z80A2 = 0; drz80.Z80F2 = 0; drz80.Z80BC2 = 0x0000 << 16; drz80.Z80DE2 = 0x0000 << 16; drz80.Z80HL2 = 0x0000 << 16; drz80.Z80IX = 0xFFFF << 16; drz80.Z80IY = 0xFFFF << 16; drz80.Z80I = 0x00; drz80.Z80_IRQ = 0x00; drz80.Z80IF = 0x00; drz80.Z80IM = 0x00; drz80.Z80R = 0x00; drz80.Z80PC = drz80_rebasePC(0); drz80.Z80SP = drz80_rebaseSP(0x2000); md_set_drz80(0); #endif } /** * Initialise sound. * @return True when successful. */ bool md::init_sound() { if (lock == false) return false; if (ok_ym2612) { YM2612Shutdown(); ok_ym2612 = false; } if (ok_sn76496) { (void)0; ok_sn76496 = false; } // Initialize two additional chips when MJazz is enabled. if (YM2612Init((dgen_mjazz ? 3 : 1), (((pal) ? PAL_MCLK : NTSC_MCLK) / 7), dgen_soundrate, dgen_mjazz, NULL, NULL)) return false; ok_ym2612 = true; if (SN76496_init(0, (((pal) ? PAL_MCLK : NTSC_MCLK) / 15), dgen_soundrate, 16)) return false; ok_sn76496 = true; return true; } /** * Switch to PAL or NTSC. * This method's name is a bit misleading. This switches to PAL or not * depending on "md::pal". */ void md::init_pal() { unsigned int hc; if (pal) { mclk = PAL_MCLK; lines = PAL_LINES; vhz = PAL_HZ; } else { mclk = NTSC_MCLK; lines = NTSC_LINES; vhz = NTSC_HZ; } clk0 = (mclk / 15); clk1 = (mclk / 7); // Initialize horizontal counter table (Gens style) for (hc = 0; (hc < 512); ++hc) { // H32 hc_table[hc][0] = (((hc * 170) / M68K_CYCLES_PER_LINE) - 0x18); // H40 hc_table[hc][1] = (((hc * 205) / M68K_CYCLES_PER_LINE) - 0x1c); } } bool md::lock = false; /** * MD constructor. * @param pal True if we are running the MD in PAL mode. * @param region Region to emulate ('J', 'U', or 'E'). */ md::md(bool pal, char region): #ifdef WITH_MUSA md_musa_ref(0), md_musa_prev(0), #endif #ifdef WITH_CYCLONE md_cyclone_ref(0), md_cyclone_prev(0), #endif #ifdef WITH_STAR md_star_ref(0), md_star_prev(0), #endif #ifdef WITH_CZ80 md_cz80_ref(0), #endif #ifdef WITH_MZ80 md_mz80_ref(0), md_mz80_prev(0), #endif pal(pal), ok_ym2612(false), ok_sn76496(false), vdp(*this), region(region), plugged(false) { // Only one MD object is allowed to exist at once. if (lock) return; lock = true; // PAL or NTSC. init_pal(); // Start up the sound chips. if (init_sound() == false) goto cleanup; romlen = no_rom_size; rom = (uint8_t*)no_rom; mem=ram=z80ram=saveram=NULL; save_start=save_len=save_prot=save_active=0; fm_reset(); #ifdef WITH_VGMDUMP vgm_dump_file = NULL; vgm_dump_samples_total = 0; vgm_dump_dac_wait = 0; vgm_dump_dac_samples = 0; vgm_dump = false; #endif #ifdef WITH_PICO pico_enabled = false; #endif memset(&m68k_state, 0, sizeof(m68k_state)); memset(&z80_state, 0, sizeof(z80_state)); #ifdef WITH_MUSA ctx_musa = calloc(1, m68k_context_size()); if (ctx_musa == NULL) goto cleanup; md_set_musa(1); m68k_init(); m68k_set_cpu_type(M68K_CPU_TYPE_68000); m68k_register_memory(NULL, 0); m68k_set_int_ack_callback(musa_irq_callback); md_set_musa(0); #endif #ifdef WITH_STAR fetch=NULL; readbyte=readword=writebyte=writeword=NULL; memset(&cpu,0,sizeof(cpu)); #endif #ifdef WITH_CYCLONE memset(&cyclonecpu, 0, sizeof(cyclonecpu)); cyclonecpu.read8 = cyclone_read_memory_8; cyclonecpu.read16 = cyclone_read_memory_16; cyclonecpu.read32 = cyclone_read_memory_32; cyclonecpu.write8 = cyclone_write_memory_8; cyclonecpu.write16 = cyclone_write_memory_16; cyclonecpu.write32 = cyclone_write_memory_32; cyclonecpu.checkpc = cyclone_checkpc; cyclonecpu.fetch8 = cyclone_read_memory_8; cyclonecpu.fetch16 = cyclone_read_memory_16; cyclonecpu.fetch32 = cyclone_read_memory_32; cyclonecpu.IrqCallback = cyclone_irq_callback; md_set_cyclone(1); CycloneInit(); md_set_cyclone(0); #endif #ifdef WITH_MZ80 memset(&z80,0,sizeof(z80)); #endif #ifdef WITH_CZ80 Cz80_Init(&cz80); #endif #ifdef WITH_DRZ80 memset(&drz80, 0, sizeof(drz80)); #endif memset(&cart_head, 0, sizeof(cart_head)); memset(romname, 0, sizeof(romname)); ok=0; // Format of pad is: __SA____ UDLRBC__ rom = (uint8_t*)no_rom; romlen = no_rom_size; mem=ram=z80ram=NULL; mem=(unsigned char *)malloc(0x20008); if (mem == NULL) goto cleanup; memset(mem,0,0x20000); ram= mem+0x00000; z80ram=mem+0x10000; // Hack for DrZ80 to avoid crashing when PC leaves z80ram. z80ram[0x10000] = 0x00; // NOP z80ram[0x10001] = 0x00; // NOP z80ram[0x10002] = 0x00; // NOP z80ram[0x10003] = 0x00; // NOP z80ram[0x10004] = 0x00; // NOP z80ram[0x10005] = 0xc3; // JP 0x0000 z80ram[0x10006] = 0x00; z80ram[0x10007] = 0x00; #ifdef WITH_MUSA md_set_musa(1); musa_memory_map(); md_set_musa(0); #endif #ifdef WITH_STAR md_set_star(1); if (s68000init() != 0) { md_set_star(0); printf ("s68000init failed!\n"); goto cleanup; } md_set_star(0); // Dave: Rich said doing point star stuff is done after s68000init // in Asgard68000, so just in case... if (((fetch = new STARSCREAM_PROGRAMREGION [6]) == NULL) || ((readbyte = new STARSCREAM_DATAREGION [5]) == NULL) || ((readword = new STARSCREAM_DATAREGION [5]) == NULL) || ((writebyte = new STARSCREAM_DATAREGION [5]) == NULL) || ((writeword = new STARSCREAM_DATAREGION [5]) == NULL)) goto cleanup; memory_map(); // point star stuff cpu.s_fetch = cpu.u_fetch = fetch; cpu.s_readbyte = cpu.u_readbyte = readbyte; cpu.s_readword = cpu.u_readword = readword; cpu.s_writebyte = cpu.u_writebyte = writebyte; cpu.s_writeword = cpu.u_writeword = writeword; cpu.inthandler = star_irq_callback; md_set_star(1); s68000reset(); md_set_star(0); #endif // M68K: 0 = none, 1 = StarScream, 2 = Musashi, 3 = Cyclone switch (dgen_emu_m68k) { #ifdef WITH_STAR case 1: cpu_emu = CPU_EMU_STAR; break; #endif #ifdef WITH_MUSA case 2: cpu_emu = CPU_EMU_MUSA; break; #endif #ifdef WITH_CYCLONE case 3: cpu_emu = CPU_EMU_CYCLONE; break; #endif default: cpu_emu = CPU_EMU_NONE; break; } // Z80: 0 = none, 1 = CZ80, 2 = MZ80, 3 = DrZ80 switch (dgen_emu_z80) { #ifdef WITH_MZ80 case 1: z80_core = Z80_CORE_MZ80; break; #endif #ifdef WITH_CZ80 case 2: z80_core = Z80_CORE_CZ80; break; #endif #ifdef WITH_DRZ80 case 3: z80_core = Z80_CORE_DRZ80; break; #endif default: z80_core = Z80_CORE_NONE; break; } #ifdef WITH_MUSA md_set_musa(1); m68k_pulse_reset(); md_set_musa(0); #endif #ifdef WITH_DEBUGGER debug_init(); #endif z80_init(); reset(); // reset megadrive patch_elem = NULL; ok=1; return; cleanup: if (ok_ym2612) YM2612Shutdown(); if (ok_sn76496) (void)0; #ifdef WITH_MUSA free(ctx_musa); #endif #ifdef WITH_STAR delete [] fetch; delete [] readbyte; delete [] readword; delete [] writebyte; delete [] writeword; #endif free(mem); memset(this, 0, sizeof(*this)); lock = false; } md::~md() { #ifdef WITH_VGMDUMP vgm_dump_stop(); #endif assert(rom != NULL); if (rom != no_rom) unplug(); free(mem); rom=mem=ram=z80ram=NULL; #ifdef WITH_DEBUGGER debug_leave(); #endif #ifdef WITH_MUSA free(ctx_musa); #endif #ifdef WITH_STAR delete [] fetch; delete [] readbyte; delete [] readword; delete [] writebyte; delete [] writeword; #endif if (ok_ym2612) YM2612Shutdown(); if (ok_sn76496) (void)0; ok=0; memset(this, 0, sizeof(*this)); lock = false; } #ifdef ROM_BYTESWAP /** * Byteswaps memory. * @param[in] start Byte array of cart memory. * @param len How many bytes to byteswap. * @return 0 on success (always 0). */ // Byteswaps memory int byteswap_memory(unsigned char *start,int len) { int i; unsigned char tmp; for (i=0;i= (unsigned int)romlen) save_active = 1; } else { save_start = save_len = 0; saveram = NULL; } } else { save_start = save_len = 0; saveram = NULL; } #ifdef WITH_MUSA md_set_musa(1); musa_memory_map(); md_set_musa(0); #endif #ifdef WITH_STAR md_set_star(1); memory_map(); // Update memory map to include this cartridge md_set_star(0); #endif reset(); // Reset megadrive return 0; } /** * Region to emulate according to dgen_region_order and ROM header. * @return Region identifier ('J', 'U' or 'E'). */ uint8_t md::region_guess() { char const* order = dgen_region_order.val; char const* avail = this->cart_head.countries; size_t r; size_t i; assert(order != NULL); assert(avail != NULL); for (r = 0; (order[r] != '\0'); ++r) for (i = 0; (i != sizeof(this->cart_head.countries)); ++i) if ((isprint(order[r])) && (toupper(order[r]) == toupper(avail[i]))) return toupper(order[r]); // Use default region. return dgen_region; } /** * Unplug a cart from the system. * @return 0 on success. */ int md::unplug() { assert(rom != NULL); assert(romlen != 0); if (rom == no_rom) return 1; unload_rom(rom); rom = (uint8_t*)no_rom; romlen = no_rom_size; free(saveram); saveram = NULL; save_start = save_len = 0; #ifdef WITH_MUSA md_set_musa(1); musa_memory_map(); md_set_musa(0); #endif #ifdef WITH_STAR md_set_star(1); memory_map(); // Update memory map to include no rom md_set_star(0); #endif memset(romname, 0, sizeof(romname)); memset(&cart_head, 0, sizeof(cart_head)); reset(); while (patch_elem != NULL) { struct patch_elem *next = patch_elem->next; free(patch_elem); patch_elem = next; } plugged = false; return 0; } /** * Load a ROM. * @param[in] name File name of cart to load. * @return 0 on success. */ int md::load(const char *name) { uint8_t *temp; size_t size; const char *b_name; if ((name == NULL) || ((b_name = dgen_basename(name)) == NULL)) return 1; temp = load_rom(&size, name); if (temp == NULL) return 1; // Register name romname[0] = '\0'; if ((b_name[0] != '\0')) { unsigned int i; snprintf(romname, sizeof(romname), "%s", b_name); for (i = 0; (romname[i] != '\0'); ++i) if (romname[i] == '.') { memset(&(romname[i]), 0, (sizeof(romname) - i)); break; } } if (romname[0] == '\0') snprintf(romname, sizeof(romname), "%s", "unknown"); // Fill the header with ROM info (god this is ugly) memcpy((void*)cart_head.system_name, (void*)(temp + 0x100), 0x10); memcpy((void*)cart_head.copyright, (void*)(temp + 0x110), 0x10); memcpy((void*)cart_head.domestic_name,(void*)(temp + 0x120), 0x30); memcpy((void*)cart_head.overseas_name,(void*)(temp + 0x150), 0x30); memcpy((void*)cart_head.product_no, (void*)(temp + 0x180), 0x0e); cart_head.checksum = temp[0x18e]<<8 | temp[0x18f]; // ugly, but endian-neutral memcpy((void*)cart_head.control_data, (void*)(temp + 0x190), 0x10); cart_head.rom_start = temp[0x1a0]<<24 | temp[0x1a1]<<16 | temp[0x1a2]<<8 | temp[0x1a3]; cart_head.rom_end = temp[0x1a4]<<24 | temp[0x1a5]<<16 | temp[0x1a6]<<8 | temp[0x1a7]; cart_head.ram_start = temp[0x1a8]<<24 | temp[0x1a9]<<16 | temp[0x1aa]<<8 | temp[0x1ab]; cart_head.ram_end = temp[0x1ac]<<24 | temp[0x1ad]<<16 | temp[0x1ae]<<8 | temp[0x1af]; cart_head.save_magic = temp[0x1b0]<<8 | temp[0x1b1]; cart_head.save_flags = temp[0x1b2]<<8 | temp[0x1b3]; cart_head.save_start = temp[0x1b4]<<24 | temp[0x1b5]<<16 | temp[0x1b6]<<8 | temp[0x1b7]; cart_head.save_end = temp[0x1b8]<<24 | temp[0x1b9]<<16 | temp[0x1ba]<<8 | temp[0x1bb]; memcpy((void*)cart_head.memo, (void*)(temp + 0x1c8), 0x28); memcpy((void*)cart_head.countries, (void*)(temp + 0x1f0), 0x10); #ifdef WITH_PICO // Check if cartridge inserted is intended for Sega Pico. // If it is, the Sega Pico I/O area will be enabled, and the // Megadrive I/O area will be disabled. if ((!strncmp(cart_head.system_name, "SEGA PICO", 9)) || (!strncmp(cart_head.system_name, "SEGATOYS PICO", 13))) pico_enabled = true; else pico_enabled = false; #endif // Plug it into the memory map plug_in(temp, size); // md then deallocates it when it's done plugged = true; return 0; } /** * Cycle through Z80 CPU implementations. */ void md::cycle_z80() { z80_state_dump(); z80_core = (enum z80_core)((z80_core + 1) % Z80_CORE_TOTAL); z80_state_restore(); } /** * Cycle between M68K CPU implementations. */ void md::cycle_cpu() { m68k_state_dump(); cpu_emu = (enum cpu_emu)((cpu_emu + 1) % CPU_EMU_TOTAL); m68k_state_restore(); } /** * Dump Z80 ram to a file named "dgz80ram". * @return Always returns 0. */ int md::z80dump() { FILE *hand; hand = dgen_fopen(NULL, "dgz80ram", DGEN_WRITE); if (hand!=NULL) { fwrite(z80ram,1,0x10000,hand); fclose(hand); } return 0; } /** * This takes a comma or whitespace-separated list of Game Genie and/or hex * codes to patch the ROM with. * @param[in] list List of codes separated by '\\t', '\\n', or ','. * @param[out] errors Number of codes that failed to apply. * @param[out] applied Number of codes that applied correctly. * @param[out] reverted Number of codes that were reverted. * @return 0 on success. */ int md::patch(const char *list, unsigned int *errors, unsigned int *applied, unsigned int *reverted) { static const char delims[] = " \t\n,"; char *worklist, *tok; struct patch p; int ret = 0; size_t wl_sz; if (errors != NULL) *errors = 0; if (applied != NULL) *applied = 0; if (reverted != NULL) *reverted = 0; // Copy the given list to a working list so we can strtok it wl_sz = strlen(list) + 1; worklist = (char *)malloc(wl_sz); if (worklist == NULL) return -1; strncpy(worklist, list, wl_sz); for(tok = strtok(worklist, delims); tok; tok = strtok(NULL, delims)) { struct patch_elem *elem = patch_elem; struct patch_elem *prev = NULL; uint8_t *dest = rom; size_t mask = ~(size_t)0; int rev = 0; bool swap = true; // If it's empty, toss it if(*tok == '\0') continue; // Decode it decode(tok, &p); // Discard it if it was bad code if (((signed)p.addr == -1) || (p.addr >= (size_t)(romlen - 1))) { if ((p.addr < 0xff0000) || (p.addr >= 0xffffff)) { printf("Bad patch \"%s\"\n", tok); if (errors != NULL) ++(*errors); ret = -1; continue; } // This is a RAM patch. dest = ram; mask = 0xffff; } if (dest == no_rom) { printf("Cannot patch this ROM\n"); continue; } #ifndef ROM_BYTESWAP if (dest == rom) swap = false; #endif // Put it into dest (remember byteswapping) while (elem != NULL) { if (elem->addr == p.addr) { // Revert a previous patch. p.data = elem->data; if (prev != NULL) prev->next = elem->next; else patch_elem = NULL; free(elem); rev = 1; break; } prev = elem; elem = elem->next; } if (rev) { printf("Reverting patch \"%s\" -> %06X\n", tok, p.addr); if (reverted != NULL) ++(*reverted); } else { printf("Patch \"%s\" -> %06X:%04X\n", tok, p.addr, p.data); if (applied != NULL) ++(*applied); if ((elem = (struct patch_elem *)malloc(sizeof(*elem))) != NULL) { elem->next = patch_elem; elem->addr = p.addr; elem->data = ((dest[((p.addr + 0) ^ swap) & mask] << 8) | (dest[((p.addr + 1) ^ swap) & mask])); patch_elem = elem; } } dest[((p.addr + 0) ^ swap) & mask] = (uint8_t)(p.data >> 8); dest[((p.addr + 1) ^ swap) & mask] = (uint8_t)(p.data & 0xff); } // Done! free(worklist); return ret; } /** * Get saveram from FILE*. * @param from File to read from. * @return 0 on success. */ int md::get_save_ram(FILE *from) { return !fread((void*)saveram, save_len, 1, from); } /** * Write a saveram to FILE*. * @param into File to write to. * @return 0 on success. */ int md::put_save_ram(FILE *into) { return !fwrite((void*)saveram, save_len, 1, into); } /** * Calculates a ROM's checksum. * @param rom ROM memory area. * @param len ROM size. * @return Checksum. */ static unsigned short calculate_checksum(unsigned char *rom,int len) { unsigned short checksum=0; int i; for (i=512;i<=(len-2);i+=2) { checksum+=(rom[ROM_ADDR(i+0)]<<8); checksum+=rom[ROM_ADDR(i+1)]; } return checksum; } /** * Replace the in-memory ROM checksum with a calculated checksum. */ void md::fix_rom_checksum() { unsigned short cs; cs=calculate_checksum(rom,romlen); if (romlen>=0x190) { rom[ROM_ADDR(0x18e)]=cs>>8; rom[ROM_ADDR(0x18f)]=cs&255; } } /** * This is the default ROM, used when nothing is loaded. */ #ifdef ROM_BYTESWAP const uint8_t md::no_rom[] = { // Note: everything is byte swapped. "\x72\x4e" "\xff\xff" // stop #0xffff "\x71\x4e" // nop "\x71\x4e" // nop "\xf6\x60" // bra.b 0 }; #else const uint8_t md::no_rom[] = { "\x4e\x72" "\xff\xff" // stop #0xffff "\x4e\x71" // nop "\x4e\x71" // nop "\x60\xf6" // bra.b 0 }; #endif const size_t md::no_rom_size = sizeof(no_rom);