pavone@467: /* pavone@467: Copyright 2013 Michael Pavone pavone@469: This file is part of BlastEm. pavone@467: BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. pavone@467: */ pavone@883: #include pavone@883: #include pavone@883: #include pavone@883: #include pavone@883: pavone@88: #include "68kinst.h" pavone@569: #include "m68k_core.h" pavone@260: #include "z80_to_x86.h" pavone@88: #include "mem.h" pavone@88: #include "vdp.h" pavone@88: #include "render.h" pavone@88: #include "blastem.h" pavone@515: #include "gdb_remote.h" pavone@451: #include "gst.h" pavone@496: #include "util.h" pavone@764: #include "romdb.h" pavone@832: #include "terminal.h" pavone@883: #include "arena.h" pavone@88: pavone@815: #define BLASTEM_VERSION "0.3.X" pavone@464: pavone@380: #define MCLKS_NTSC 53693175 pavone@380: #define MCLKS_PAL 53203395 pavone@380: pavone@948: uint32_t MCLKS_PER_68K; pavone@948: #define MCLKS_PER_YM 7 pavone@260: #define MCLKS_PER_Z80 15 pavone@354: #define MCLKS_PER_PSG (MCLKS_PER_Z80*16) pavone@785: #define DEFAULT_SYNC_INTERVAL MCLKS_LINE pavone@380: pavone@88: //TODO: Figure out the exact value for this pavone@342: #define LINES_NTSC 262 pavone@342: #define LINES_PAL 312 pavone@342: pavone@483: #define MAX_SOUND_CYCLES 100000 pavone@483: pavone@860: #ifdef __ANDROID__ pavone@860: #define FULLSCREEN_DEFAULT 1 pavone@860: #else pavone@860: #define FULLSCREEN_DEFAULT 0 pavone@860: #endif pavone@860: pavone@776: uint16_t *cart; pavone@883: uint16_t *ram; pavone@153: uint8_t z80_ram[Z80_RAM_BYTES]; pavone@88: pavone@215: int headless = 0; pavone@505: int exit_after = 0; pavone@265: int z80_enabled = 1; pavone@356: int frame_limit = 0; pavone@215: pavone@430: tern_node * config; pavone@430: pavone@88: #ifndef MIN pavone@88: #define MIN(a,b) ((a) < (b) ? (a) : (b)) pavone@88: #endif pavone@88: pavone@166: #define SMD_HEADER_SIZE 512 pavone@166: #define SMD_MAGIC1 0x03 pavone@166: #define SMD_MAGIC2 0xAA pavone@166: #define SMD_MAGIC3 0xBB pavone@166: #define SMD_BLOCK_SIZE 0x4000 pavone@166: pavone@166: int load_smd_rom(long filesize, FILE * f) pavone@166: { pavone@166: uint8_t block[SMD_BLOCK_SIZE]; pavone@166: filesize -= SMD_HEADER_SIZE; pavone@166: fseek(f, SMD_HEADER_SIZE, SEEK_SET); pavone@488: pavone@975: uint16_t * dst = cart = malloc(nearest_pow2(filesize)); pavone@766: int rom_size = filesize; pavone@166: while (filesize > 0) { pavone@166: fread(block, 1, SMD_BLOCK_SIZE, f); pavone@166: for (uint8_t *low = block, *high = (block+SMD_BLOCK_SIZE/2), *end = block+SMD_BLOCK_SIZE; high < end; high++, low++) { pavone@764: *(dst++) = *low << 8 | *high; pavone@166: } pavone@166: filesize -= SMD_BLOCK_SIZE; pavone@166: } pavone@975: return rom_size; pavone@166: } pavone@166: pavone@776: void byteswap_rom(int filesize) pavone@764: { pavone@776: for(unsigned short * cur = cart; cur - cart < filesize/2; ++cur) pavone@764: { pavone@764: *cur = (*cur >> 8) | (*cur << 8); pavone@764: } pavone@764: } pavone@764: pavone@88: int load_rom(char * filename) pavone@88: { pavone@166: uint8_t header[10]; pavone@88: FILE * f = fopen(filename, "rb"); pavone@88: if (!f) { pavone@88: return 0; pavone@88: } pavone@776: if (sizeof(header) != fread(header, 1, sizeof(header), f)) { pavone@792: fatal_error("Error reading from %s\n", filename); pavone@776: } pavone@88: fseek(f, 0, SEEK_END); pavone@88: long filesize = ftell(f); pavone@88: fseek(f, 0, SEEK_SET); pavone@166: if (header[1] == SMD_MAGIC1 && header[8] == SMD_MAGIC2 && header[9] == SMD_MAGIC3) { pavone@166: int i; pavone@166: for (i = 3; i < 8; i++) { pavone@166: if (header[i] != 0) { pavone@166: break; pavone@166: } pavone@166: } pavone@166: if (i == 8) { pavone@166: if (header[2]) { pavone@792: fatal_error("%s is a split SMD ROM which is not currently supported", filename); pavone@166: } pavone@166: return load_smd_rom(filesize, f); pavone@166: } pavone@166: } pavone@777: cart = malloc(nearest_pow2(filesize)); pavone@776: if (filesize != fread(cart, 1, filesize, f)) { pavone@792: fatal_error("Error reading from %s\n", filename); pavone@776: } pavone@88: fclose(f); pavone@766: return filesize; pavone@88: } pavone@88: pavone@88: uint16_t read_dma_value(uint32_t address) pavone@88: { pavone@88: //addresses here are word addresses (i.e. bit 0 corresponds to A1), so no need to do div by 2 pavone@88: if (address < 0x200000) { pavone@88: return cart[address]; pavone@88: } else if(address >= 0x700000) { pavone@88: return ram[address & 0x7FFF]; pavone@918: } else { pavone@918: uint16_t *ptr = get_native_pointer(address*2, (void **)genesis->m68k->mem_pointers, &genesis->m68k->options->gen); pavone@918: if (ptr) { pavone@918: return *ptr; pavone@918: } pavone@88: } pavone@88: //TODO: Figure out what happens when you try to DMA from weird adresses like IO or banked Z80 area pavone@88: return 0; pavone@88: } pavone@88: pavone@981: uint16_t get_open_bus_value() pavone@981: { pavone@981: return read_dma_value(genesis->m68k->last_prefetch_address/2); pavone@981: } pavone@981: pavone@186: void adjust_int_cycle(m68k_context * context, vdp_context * v_context) pavone@186: { pavone@717: //static int old_int_cycle = CYCLE_NEVER; pavone@696: genesis_context *gen = context->system; pavone@696: if (context->sync_cycle - context->current_cycle > gen->max_cycles) { pavone@696: context->sync_cycle = context->current_cycle + gen->max_cycles; pavone@696: } pavone@317: context->int_cycle = CYCLE_NEVER; pavone@317: if ((context->status & 0x7) < 6) { pavone@317: uint32_t next_vint = vdp_next_vint(v_context); pavone@317: if (next_vint != CYCLE_NEVER) { pavone@317: context->int_cycle = next_vint; pavone@317: context->int_num = 6; pavone@317: } pavone@317: if ((context->status & 0x7) < 4) { pavone@317: uint32_t next_hint = vdp_next_hint(v_context); pavone@317: if (next_hint != CYCLE_NEVER) { pavone@317: if (next_hint < context->int_cycle) { pavone@317: context->int_cycle = next_hint; pavone@317: context->int_num = 4; pavone@488: pavone@317: } pavone@317: } pavone@186: } pavone@186: } pavone@846: if (context->int_cycle > context->current_cycle) { pavone@846: context->int_pending = 0; pavone@846: } pavone@717: /*if (context->int_cycle != old_int_cycle) { pavone@717: printf("int cycle changed to: %d, level: %d @ %d(%d), frame: %d, vcounter: %d, hslot: %d, mask: %d, hint_counter: %d\n", context->int_cycle, context->int_num, v_context->cycles, context->current_cycle, v_context->frame, v_context->vcounter, v_context->hslot, context->status & 0x7, v_context->hint_counter); pavone@717: old_int_cycle = context->int_cycle; pavone@717: }*/ pavone@317: pavone@317: context->target_cycle = context->int_cycle < context->sync_cycle ? context->int_cycle : context->sync_cycle; pavone@872: if (context->should_return) { pavone@872: context->target_cycle = context->current_cycle; pavone@891: } else if (context->target_cycle < context->current_cycle) { pavone@891: //Changes to SR can result in an interrupt cycle that's in the past pavone@891: //This can cause issues with the implementation of STOP though pavone@891: context->target_cycle = context->current_cycle; pavone@872: } pavone@488: /*printf("Cyc: %d, Trgt: %d, Int Cyc: %d, Int: %d, Mask: %X, V: %d, H: %d, HICount: %d, HReg: %d, Line: %d\n", pavone@488: context->current_cycle, context->target_cycle, context->int_cycle, context->int_num, (context->status & 0x7), pavone@317: v_context->regs[REG_MODE_2] & 0x20, v_context->regs[REG_MODE_1] & 0x10, v_context->hint_counter, v_context->regs[REG_HINT], v_context->cycles / MCLKS_LINE);*/ pavone@186: } pavone@186: pavone@198: int break_on_sync = 0; pavone@955: char *save_state_path; pavone@260: pavone@280: //#define DO_DEBUG_PRINT pavone@268: #ifdef DO_DEBUG_PRINT pavone@268: #define dprintf printf pavone@271: #define dputs puts pavone@268: #else pavone@268: #define dprintf pavone@271: #define dputs pavone@268: #endif pavone@268: pavone@402: #define Z80_VINT_DURATION 128 pavone@402: pavone@668: void z80_next_int_pulse(z80_context * z_context) pavone@668: { pavone@682: genesis_context * gen = z_context->system; pavone@668: z_context->int_pulse_start = vdp_next_vint_z80(gen->vdp); pavone@670: z_context->int_pulse_end = z_context->int_pulse_start + Z80_VINT_DURATION * MCLKS_PER_Z80; pavone@682: } pavone@668: pavone@268: void sync_z80(z80_context * z_context, uint32_t mclks) pavone@88: { pavone@565: #ifndef NO_Z80 pavone@668: if (z80_enabled) { pavone@668: z80_run(z_context, mclks); pavone@548: } else pavone@548: #endif pavone@548: { pavone@667: z_context->current_cycle = mclks; pavone@260: } pavone@268: } pavone@380: pavone@380: void sync_sound(genesis_context * gen, uint32_t target) pavone@380: { pavone@380: //printf("YM | Cycle: %d, bpos: %d, PSG | Cycle: %d, bpos: %d\n", gen->ym->current_cycle, gen->ym->buffer_pos, gen->psg->cycles, gen->psg->buffer_pos * 2); pavone@483: while (target > gen->psg->cycles && target - gen->psg->cycles > MAX_SOUND_CYCLES) { pavone@483: uint32_t cur_target = gen->psg->cycles + MAX_SOUND_CYCLES; pavone@483: //printf("Running PSG to cycle %d\n", cur_target); pavone@483: psg_run(gen->psg, cur_target); pavone@483: //printf("Running YM-2612 to cycle %d\n", cur_target); pavone@483: ym_run(gen->ym, cur_target); pavone@483: } pavone@380: psg_run(gen->psg, target); pavone@380: ym_run(gen->ym, target); pavone@488: pavone@380: //printf("Target: %d, YM bufferpos: %d, PSG bufferpos: %d\n", target, gen->ym->buffer_pos, gen->psg->buffer_pos * 2); pavone@380: } pavone@380: pavone@978: uint32_t last_frame_num; pavone@978: pavone@978: //My refresh emulation isn't currently good enough and causes more problems than it solves pavone@978: #ifdef REFRESH_EMULATION pavone@928: #define REFRESH_INTERVAL 128 pavone@889: #define REFRESH_DELAY 2 pavone@889: uint32_t last_sync_cycle; pavone@889: uint32_t refresh_counter; pavone@978: #endif pavone@978: pavone@268: m68k_context * sync_components(m68k_context * context, uint32_t address) pavone@268: { pavone@288: genesis_context * gen = context->system; pavone@288: vdp_context * v_context = gen->vdp; pavone@288: z80_context * z_context = gen->z80; pavone@978: #ifdef REFRESH_EMULATION pavone@889: //lame estimation of refresh cycle delay pavone@891: if (!gen->bus_busy) { pavone@891: refresh_counter += context->current_cycle - last_sync_cycle; pavone@891: context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL)); pavone@891: refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL); pavone@891: } pavone@978: #endif pavone@889: pavone@667: uint32_t mclks = context->current_cycle; pavone@268: sync_z80(z_context, mclks); pavone@695: sync_sound(gen, mclks); pavone@697: vdp_run_context(v_context, mclks); pavone@697: if (v_context->frame != last_frame_num) { pavone@697: //printf("reached frame end %d | MCLK Cycles: %d, Target: %d, VDP cycles: %d, vcounter: %d, hslot: %d\n", last_frame_num, mclks, gen->frame_end, v_context->cycles, v_context->vcounter, v_context->hslot); pavone@697: last_frame_num = v_context->frame; pavone@488: pavone@697: if (!headless) { pavone@697: break_on_sync |= wait_render_frame(v_context, frame_limit); pavone@697: } else if(exit_after){ pavone@697: --exit_after; pavone@697: if (!exit_after) { pavone@697: exit(0); pavone@505: } pavone@88: } pavone@832: pavone@697: vdp_adjust_cycles(v_context, mclks); pavone@697: io_adjust_cycles(gen->ports, context->current_cycle, mclks); pavone@697: io_adjust_cycles(gen->ports+1, context->current_cycle, mclks); pavone@697: io_adjust_cycles(gen->ports+2, context->current_cycle, mclks); pavone@697: context->current_cycle -= mclks; pavone@697: z80_adjust_cycles(z_context, mclks); pavone@697: gen->ym->current_cycle -= mclks; pavone@697: gen->psg->cycles -= mclks; pavone@697: if (gen->ym->write_cycle != CYCLE_NEVER) { pavone@697: gen->ym->write_cycle = gen->ym->write_cycle >= mclks ? gen->ym->write_cycle - mclks : 0; pavone@697: } pavone@88: } pavone@700: gen->frame_end = vdp_cycles_to_frame_end(v_context); pavone@697: context->sync_cycle = gen->frame_end; pavone@697: //printf("Set sync cycle to: %d @ %d, vcounter: %d, hslot: %d\n", context->sync_cycle, context->current_cycle, v_context->vcounter, v_context->hslot); pavone@317: if (context->int_ack) { pavone@718: //printf("acknowledging %d @ %d:%d, vcounter: %d, hslot: %d\n", context->int_ack, context->current_cycle, v_context->cycles, v_context->vcounter, v_context->hslot); pavone@953: vdp_int_ack(v_context); pavone@317: context->int_ack = 0; pavone@317: } pavone@961: if (!address && (break_on_sync || gen->save_state)) { pavone@721: context->sync_cycle = context->current_cycle + 1; pavone@721: } pavone@186: adjust_int_cycle(context, v_context); pavone@451: if (address) { pavone@451: if (break_on_sync) { pavone@695: break_on_sync = 0; pavone@695: debugger(context, address); pavone@695: } pavone@961: if (gen->save_state && (z_context->pc || (!z_context->reset && !z_context->busreq))) { pavone@961: uint8_t slot = gen->save_state - 1; pavone@961: gen->save_state = 0; pavone@667: //advance Z80 core to the start of an instruction pavone@451: while (!z_context->pc) pavone@451: { pavone@667: sync_z80(z_context, z_context->current_cycle + MCLKS_PER_Z80); pavone@451: } pavone@961: char *save_path; pavone@961: if (slot == QUICK_SAVE_SLOT) { pavone@961: save_path = save_state_path; pavone@961: } else { pavone@961: char slotname[] = "slot_0.gst"; pavone@961: slotname[5] = '0' + slot; pavone@961: char const *parts[] = {gen->save_dir, "/", slotname}; pavone@961: save_path = alloc_concat_m(3, parts); pavone@961: } pavone@961: save_gst(gen, save_path, address); pavone@961: printf("Saved state to %s\n", save_path); pavone@961: if (slot != QUICK_SAVE_SLOT) { pavone@961: free(save_path); pavone@961: } pavone@961: } else if(gen->save_state) { pavone@736: context->sync_cycle = context->current_cycle + 1; pavone@451: } pavone@198: } pavone@978: #ifdef REFRESH_EMULATION pavone@889: last_sync_cycle = context->current_cycle; pavone@978: #endif pavone@88: return context; pavone@88: } pavone@88: pavone@88: m68k_context * vdp_port_write(uint32_t vdp_port, m68k_context * context, uint16_t value) pavone@88: { pavone@343: if (vdp_port & 0x2700E0) { pavone@792: fatal_error("machine freeze due to write to address %X\n", 0xC00000 | vdp_port); pavone@343: } pavone@343: vdp_port &= 0x1F; pavone@88: //printf("vdp_port write: %X, value: %X, cycle: %d\n", vdp_port, value, context->current_cycle); pavone@198: sync_components(context, 0); pavone@508: genesis_context * gen = context->system; pavone@874: vdp_context *v_context = gen->vdp; pavone@88: if (vdp_port < 0x10) { pavone@149: int blocked; pavone@345: uint32_t before_cycle = v_context->cycles; pavone@88: if (vdp_port < 4) { pavone@832: pavone@149: while (vdp_data_port_write(v_context, value) < 0) { pavone@88: while(v_context->flags & FLAG_DMA_RUN) { pavone@696: vdp_run_dma_done(v_context, gen->frame_end); pavone@696: if (v_context->cycles >= gen->frame_end) { pavone@667: context->current_cycle = v_context->cycles; pavone@697: gen->bus_busy = 1; pavone@534: sync_components(context, 0); pavone@697: gen->bus_busy = 0; pavone@88: } pavone@88: } pavone@667: //context->current_cycle = v_context->cycles; pavone@149: } pavone@149: } else if(vdp_port < 8) { pavone@149: blocked = vdp_control_port_write(v_context, value); pavone@149: if (blocked) { pavone@149: while (blocked) { pavone@149: while(v_context->flags & FLAG_DMA_RUN) { pavone@696: vdp_run_dma_done(v_context, gen->frame_end); pavone@696: if (v_context->cycles >= gen->frame_end) { pavone@667: context->current_cycle = v_context->cycles; pavone@697: gen->bus_busy = 1; pavone@534: sync_components(context, 0); pavone@697: gen->bus_busy = 0; pavone@149: } pavone@149: } pavone@149: if (blocked < 0) { pavone@149: blocked = vdp_control_port_write(v_context, value); pavone@149: } else { pavone@149: blocked = 0; pavone@149: } pavone@149: } pavone@88: } else { pavone@697: context->sync_cycle = gen->frame_end = vdp_cycles_to_frame_end(v_context); pavone@697: //printf("Set sync cycle to: %d @ %d, vcounter: %d, hslot: %d\n", context->sync_cycle, context->current_cycle, v_context->vcounter, v_context->hslot); pavone@186: adjust_int_cycle(context, v_context); pavone@88: } pavone@88: } else { pavone@792: fatal_error("Illegal write to HV Counter port %X\n", vdp_port); pavone@88: } pavone@345: if (v_context->cycles != before_cycle) { pavone@667: //printf("68K paused for %d (%d) cycles at cycle %d (%d) for write\n", v_context->cycles - context->current_cycle, v_context->cycles - before_cycle, context->current_cycle, before_cycle); pavone@978: context->current_cycle = v_context->cycles; pavone@978: #ifdef REFRESH_EMULATION pavone@978: last_sync_cycle = context->current_cycle; pavone@978: #endif pavone@680: //Lock the Z80 out of the bus until the VDP access is complete pavone@680: gen->bus_busy = 1; pavone@680: sync_z80(gen->z80, v_context->cycles); pavone@680: gen->bus_busy = 0; pavone@345: } pavone@88: } else if (vdp_port < 0x18) { pavone@354: psg_write(gen->psg, value); pavone@88: } else { pavone@88: //TODO: Implement undocumented test register(s) pavone@88: } pavone@88: return context; pavone@88: } pavone@88: pavone@343: m68k_context * vdp_port_write_b(uint32_t vdp_port, m68k_context * context, uint8_t value) pavone@343: { pavone@357: return vdp_port_write(vdp_port, context, vdp_port < 0x10 ? value | value << 8 : ((vdp_port & 1) ? value : 0)); pavone@357: } pavone@357: pavone@592: void * z80_vdp_port_write(uint32_t vdp_port, void * vcontext, uint8_t value) pavone@357: { pavone@592: z80_context * context = vcontext; pavone@357: genesis_context * gen = context->system; pavone@660: vdp_port &= 0xFF; pavone@358: if (vdp_port & 0xE0) { pavone@792: fatal_error("machine freeze due to write to Z80 address %X\n", 0x7F00 | vdp_port); pavone@358: } pavone@358: if (vdp_port < 0x10) { pavone@358: //These probably won't currently interact well with the 68K accessing the VDP pavone@667: vdp_run_context(gen->vdp, context->current_cycle); pavone@358: if (vdp_port < 4) { pavone@358: vdp_data_port_write(gen->vdp, value << 8 | value); pavone@358: } else if (vdp_port < 8) { pavone@358: vdp_control_port_write(gen->vdp, value << 8 | value); pavone@358: } else { pavone@792: fatal_error("Illegal write to HV Counter port %X\n", vdp_port); pavone@358: } pavone@358: } else if (vdp_port < 0x18) { pavone@667: sync_sound(gen, context->current_cycle); pavone@358: psg_write(gen->psg, value); pavone@358: } else { pavone@470: vdp_test_port_write(gen->vdp, value); pavone@358: } pavone@357: return context; pavone@343: } pavone@343: pavone@343: uint16_t vdp_port_read(uint32_t vdp_port, m68k_context * context) pavone@88: { pavone@343: if (vdp_port & 0x2700E0) { pavone@792: fatal_error("machine freeze due to read from address %X\n", 0xC00000 | vdp_port); pavone@343: } pavone@343: vdp_port &= 0x1F; pavone@343: uint16_t value; pavone@198: sync_components(context, 0); pavone@874: genesis_context *gen = context->system; pavone@874: vdp_context * v_context = gen->vdp; pavone@470: uint32_t before_cycle = v_context->cycles; pavone@88: if (vdp_port < 0x10) { pavone@88: if (vdp_port < 4) { pavone@343: value = vdp_data_port_read(v_context); pavone@88: } else if(vdp_port < 8) { pavone@343: value = vdp_control_port_read(v_context); pavone@88: } else { pavone@343: value = vdp_hv_counter_read(v_context); pavone@343: //printf("HV Counter: %X at cycle %d\n", value, v_context->cycles); pavone@88: } pavone@470: } else if (vdp_port < 0x18){ pavone@792: fatal_error("Illegal read from PSG port %X\n", vdp_port); pavone@88: } else { pavone@470: value = vdp_test_port_read(v_context); pavone@470: } pavone@470: if (v_context->cycles != before_cycle) { pavone@667: //printf("68K paused for %d (%d) cycles at cycle %d (%d) for read\n", v_context->cycles - context->current_cycle, v_context->cycles - before_cycle, context->current_cycle, before_cycle); pavone@978: context->current_cycle = v_context->cycles; pavone@978: #ifdef REFRES_EMULATION pavone@978: last_sync_cycle = context->current_cycle; pavone@978: #endif pavone@680: //Lock the Z80 out of the bus until the VDP access is complete pavone@680: genesis_context *gen = context->system; pavone@680: gen->bus_busy = 1; pavone@680: sync_z80(gen->z80, v_context->cycles); pavone@680: gen->bus_busy = 0; pavone@88: } pavone@343: return value; pavone@343: } pavone@343: pavone@343: uint8_t vdp_port_read_b(uint32_t vdp_port, m68k_context * context) pavone@343: { pavone@343: uint16_t value = vdp_port_read(vdp_port, context); pavone@343: if (vdp_port & 1) { pavone@343: return value; pavone@343: } else { pavone@343: return value >> 8; pavone@343: } pavone@88: } pavone@88: pavone@592: uint8_t z80_vdp_port_read(uint32_t vdp_port, void * vcontext) pavone@592: { pavone@592: z80_context * context = vcontext; pavone@592: if (vdp_port & 0xE0) { pavone@792: fatal_error("machine freeze due to read from Z80 address %X\n", 0x7F00 | vdp_port); pavone@592: } pavone@592: genesis_context * gen = context->system; pavone@736: //VDP access goes over the 68K bus like a bank area access pavone@736: //typical delay from bus arbitration pavone@736: context->current_cycle += 3 * MCLKS_PER_Z80; pavone@736: //TODO: add cycle for an access right after a previous one pavone@736: //TODO: Below cycle time is an estimate based on the time between 68K !BG goes low and Z80 !MREQ goes high pavone@736: // Needs a new logic analyzer capture to get the actual delay on the 68K side pavone@736: gen->m68k->current_cycle += 8 * MCLKS_PER_68K; pavone@832: pavone@832: pavone@592: vdp_port &= 0x1F; pavone@592: uint16_t ret; pavone@592: if (vdp_port < 0x10) { pavone@592: //These probably won't currently interact well with the 68K accessing the VDP pavone@667: vdp_run_context(gen->vdp, context->current_cycle); pavone@592: if (vdp_port < 4) { pavone@592: ret = vdp_data_port_read(gen->vdp); pavone@592: } else if (vdp_port < 8) { pavone@592: ret = vdp_control_port_read(gen->vdp); pavone@592: } else { pavone@792: fatal_error("Illegal write to HV Counter port %X\n", vdp_port); pavone@592: } pavone@592: } else { pavone@592: //TODO: Figure out the correct value today pavone@592: ret = 0xFFFF; pavone@592: } pavone@592: return vdp_port & 1 ? ret : ret >> 8; pavone@592: } pavone@592: pavone@279: uint32_t zram_counter = 0; pavone@279: pavone@88: m68k_context * io_write(uint32_t location, m68k_context * context, uint8_t value) pavone@88: { pavone@288: genesis_context * gen = context->system; pavone@153: if (location < 0x10000) { pavone@844: //Access to Z80 memory incurs a one 68K cycle wait state pavone@844: context->current_cycle += MCLKS_PER_68K; pavone@668: if (!z80_enabled || z80_get_busack(gen->z80, context->current_cycle)) { pavone@153: location &= 0x7FFF; pavone@153: if (location < 0x4000) { pavone@153: z80_ram[location & 0x1FFF] = value; pavone@565: #ifndef NO_Z80 pavone@288: z80_handle_code_write(location & 0x1FFF, gen->z80); pavone@548: #endif pavone@288: } else if (location < 0x6000) { pavone@667: sync_sound(gen, context->current_cycle); pavone@288: if (location & 1) { pavone@288: ym_data_write(gen->ym, value); pavone@288: } else if(location & 2) { pavone@288: ym_address_write_part2(gen->ym, value); pavone@288: } else { pavone@288: ym_address_write_part1(gen->ym, value); pavone@288: } pavone@405: } else if (location == 0x6000) { pavone@405: gen->z80->bank_reg = (gen->z80->bank_reg >> 1 | value << 8) & 0x1FF; pavone@405: if (gen->z80->bank_reg < 0x80) { pavone@405: gen->z80->mem_pointers[1] = (gen->z80->bank_reg << 15) + ((char *)gen->z80->mem_pointers[2]); pavone@405: } else { pavone@405: gen->z80->mem_pointers[1] = NULL; pavone@405: } pavone@395: } else { pavone@792: fatal_error("68K write to unhandled Z80 address %X\n", location); pavone@153: } pavone@88: } pavone@88: } else { pavone@153: location &= 0x1FFF; pavone@153: if (location < 0x100) { pavone@153: switch(location/2) pavone@153: { pavone@153: case 0x1: pavone@421: io_data_write(gen->ports, value, context->current_cycle); pavone@153: break; pavone@153: case 0x2: pavone@421: io_data_write(gen->ports+1, value, context->current_cycle); pavone@153: break; pavone@421: case 0x3: pavone@421: io_data_write(gen->ports+2, value, context->current_cycle); pavone@153: break; pavone@153: case 0x4: pavone@421: gen->ports[0].control = value; pavone@153: break; pavone@153: case 0x5: pavone@421: gen->ports[1].control = value; pavone@421: break; pavone@421: case 0x6: pavone@421: gen->ports[2].control = value; pavone@153: break; pavone@88: } pavone@153: } else { pavone@153: if (location == 0x1100) { pavone@153: if (value & 1) { pavone@271: dputs("bus requesting Z80"); pavone@668: if (z80_enabled) { pavone@668: z80_assert_busreq(gen->z80, context->current_cycle); pavone@677: } else { pavone@677: gen->z80->busack = 1; pavone@153: } pavone@153: } else { pavone@668: if (gen->z80->busreq) { pavone@271: dputs("releasing z80 bus"); pavone@280: #ifdef DO_DEBUG_PRINT pavone@279: char fname[20]; pavone@279: sprintf(fname, "zram-%d", zram_counter++); pavone@279: FILE * f = fopen(fname, "wb"); pavone@279: fwrite(z80_ram, 1, sizeof(z80_ram), f); pavone@279: fclose(f); pavone@280: #endif pavone@260: } pavone@668: if (z80_enabled) { pavone@668: z80_clear_busreq(gen->z80, context->current_cycle); pavone@677: } else { pavone@677: gen->z80->busack = 0; pavone@668: } pavone@88: } pavone@153: } else if (location == 0x1200) { pavone@667: sync_z80(gen->z80, context->current_cycle); pavone@153: if (value & 1) { pavone@668: if (z80_enabled) { pavone@668: z80_clear_reset(gen->z80, context->current_cycle); pavone@668: } else { pavone@668: gen->z80->reset = 0; pavone@153: } pavone@668: } else { pavone@668: if (z80_enabled) { pavone@668: z80_assert_reset(gen->z80, context->current_cycle); pavone@668: } else { pavone@668: gen->z80->reset = 1; pavone@260: } pavone@153: } pavone@88: } pavone@88: } pavone@88: } pavone@88: return context; pavone@88: } pavone@88: pavone@88: m68k_context * io_write_w(uint32_t location, m68k_context * context, uint16_t value) pavone@88: { pavone@404: if (location < 0x10000 || (location & 0x1FFF) >= 0x100) { pavone@404: return io_write(location, context, value >> 8); pavone@88: } else { pavone@404: return io_write(location, context, value); pavone@88: } pavone@88: } pavone@88: pavone@130: #define USA 0x80 pavone@130: #define JAP 0x00 pavone@130: #define EUR 0xC0 pavone@130: #define NO_DISK 0x20 pavone@130: uint8_t version_reg = NO_DISK | USA; pavone@130: pavone@343: uint8_t io_read(uint32_t location, m68k_context * context) pavone@88: { pavone@343: uint8_t value; pavone@288: genesis_context *gen = context->system; pavone@153: if (location < 0x10000) { pavone@844: //Access to Z80 memory incurs a one 68K cycle wait state pavone@844: context->current_cycle += MCLKS_PER_68K; pavone@668: if (!z80_enabled || z80_get_busack(gen->z80, context->current_cycle)) { pavone@153: location &= 0x7FFF; pavone@153: if (location < 0x4000) { pavone@343: value = z80_ram[location & 0x1FFF]; pavone@288: } else if (location < 0x6000) { pavone@667: sync_sound(gen, context->current_cycle); pavone@343: value = ym_read_status(gen->ym); pavone@153: } else { pavone@343: value = 0xFF; pavone@343: } pavone@343: } else { pavone@343: value = 0xFF; pavone@343: } pavone@343: } else { pavone@343: location &= 0x1FFF; pavone@343: if (location < 0x100) { pavone@343: switch(location/2) pavone@343: { pavone@343: case 0x0: pavone@343: //version bits should be 0 for now since we're not emulating TMSS pavone@343: value = version_reg; pavone@343: break; pavone@343: case 0x1: pavone@421: value = io_data_read(gen->ports, context->current_cycle); pavone@343: break; pavone@343: case 0x2: pavone@421: value = io_data_read(gen->ports+1, context->current_cycle); pavone@343: break; pavone@421: case 0x3: pavone@421: value = io_data_read(gen->ports+2, context->current_cycle); pavone@343: break; pavone@343: case 0x4: pavone@421: value = gen->ports[0].control; pavone@343: break; pavone@343: case 0x5: pavone@421: value = gen->ports[1].control; pavone@343: break; pavone@421: case 0x6: pavone@421: value = gen->ports[2].control; pavone@405: break; pavone@343: default: pavone@343: value = 0xFF; pavone@153: } pavone@153: } else { pavone@343: if (location == 0x1100) { pavone@677: value = z80_enabled ? !z80_get_busack(gen->z80, context->current_cycle) : !gen->z80->busack; pavone@981: value |= (get_open_bus_value() >> 8) & 0xFE; pavone@668: dprintf("Byte read of BUSREQ returned %d @ %d (reset: %d)\n", value, context->current_cycle, gen->z80->reset); pavone@343: } else if (location == 0x1200) { pavone@668: value = !gen->z80->reset; pavone@343: } else { pavone@343: value = 0xFF; pavone@343: printf("Byte read of unknown IO location: %X\n", location); pavone@343: } pavone@343: } pavone@343: } pavone@343: return value; pavone@343: } pavone@343: pavone@343: uint16_t io_read_w(uint32_t location, m68k_context * context) pavone@343: { pavone@404: uint16_t value = io_read(location, context); pavone@404: if (location < 0x10000 || (location & 0x1FFF) < 0x100) { pavone@404: value = value | (value << 8); pavone@88: } else { pavone@404: value <<= 8; pavone@981: value |= get_open_bus_value() & 0xFF; pavone@88: } pavone@343: return value; pavone@88: } pavone@88: pavone@592: void * z80_write_ym(uint32_t location, void * vcontext, uint8_t value) pavone@290: { pavone@592: z80_context * context = vcontext; pavone@290: genesis_context * gen = context->system; pavone@667: sync_sound(gen, context->current_cycle); pavone@290: if (location & 1) { pavone@290: ym_data_write(gen->ym, value); pavone@290: } else if (location & 2) { pavone@290: ym_address_write_part2(gen->ym, value); pavone@290: } else { pavone@290: ym_address_write_part1(gen->ym, value); pavone@290: } pavone@290: return context; pavone@290: } pavone@290: pavone@592: uint8_t z80_read_ym(uint32_t location, void * vcontext) pavone@290: { pavone@592: z80_context * context = vcontext; pavone@290: genesis_context * gen = context->system; pavone@667: sync_sound(gen, context->current_cycle); pavone@290: return ym_read_status(gen->ym); pavone@290: } pavone@290: pavone@592: uint8_t z80_read_bank(uint32_t location, void * vcontext) pavone@592: { pavone@592: z80_context * context = vcontext; pavone@672: genesis_context *gen = context->system; pavone@672: if (gen->bus_busy) { pavone@672: context->current_cycle = context->sync_cycle; pavone@672: } pavone@660: //typical delay from bus arbitration pavone@671: context->current_cycle += 3 * MCLKS_PER_Z80; pavone@672: //TODO: add cycle for an access right after a previous one pavone@736: //TODO: Below cycle time is an estimate based on the time between 68K !BG goes low and Z80 !MREQ goes high pavone@736: // Needs a new logic analyzer capture to get the actual delay on the 68K side pavone@736: gen->m68k->current_cycle += 8 * MCLKS_PER_68K; pavone@667: pavone@660: location &= 0x7FFF; pavone@660: if (context->mem_pointers[1]) { pavone@660: return context->mem_pointers[1][location ^ 1]; pavone@660: } pavone@604: uint32_t address = context->bank_reg << 15 | location; pavone@616: if (address >= 0xC00000 && address < 0xE00000) { pavone@616: return z80_vdp_port_read(location & 0xFF, context); pavone@616: } else { pavone@660: fprintf(stderr, "Unhandled read by Z80 from address %X through banked memory area (%X)\n", address, context->bank_reg << 15); pavone@616: } pavone@592: return 0; pavone@592: } pavone@592: pavone@592: void *z80_write_bank(uint32_t location, void * vcontext, uint8_t value) pavone@290: { pavone@592: z80_context * context = vcontext; pavone@672: genesis_context *gen = context->system; pavone@672: if (gen->bus_busy) { pavone@672: context->current_cycle = context->sync_cycle; pavone@672: } pavone@660: //typical delay from bus arbitration pavone@671: context->current_cycle += 3 * MCLKS_PER_Z80; pavone@672: //TODO: add cycle for an access right after a previous one pavone@736: //TODO: Below cycle time is an estimate based on the time between 68K !BG goes low and Z80 !MREQ goes high pavone@736: // Needs a new logic analyzer capture to get the actual delay on the 68K side pavone@736: gen->m68k->current_cycle += 8 * MCLKS_PER_68K; pavone@672: pavone@660: location &= 0x7FFF; pavone@604: uint32_t address = context->bank_reg << 15 | location; pavone@604: if (address >= 0xE00000) { pavone@604: address &= 0xFFFF; pavone@604: ((uint8_t *)ram)[address ^ 1] = value; pavone@616: } else if (address >= 0xC00000) { pavone@616: z80_vdp_port_write(location & 0xFF, context, value); pavone@604: } else { pavone@604: fprintf(stderr, "Unhandled write by Z80 to address %X through banked memory area\n", address); pavone@604: } pavone@604: return context; pavone@604: } pavone@604: pavone@604: void *z80_write_bank_reg(uint32_t location, void * vcontext, uint8_t value) pavone@604: { pavone@604: z80_context * context = vcontext; pavone@667: pavone@604: context->bank_reg = (context->bank_reg >> 1 | value << 8) & 0x1FF; pavone@777: if (context->bank_reg < 0x100) { pavone@660: genesis_context *gen = context->system; pavone@660: context->mem_pointers[1] = get_native_pointer(context->bank_reg << 15, (void **)gen->m68k->mem_pointers, &gen->m68k->options->gen); pavone@604: } else { pavone@604: context->mem_pointers[1] = NULL; pavone@604: } pavone@667: pavone@592: return context; pavone@290: } pavone@290: pavone@483: void set_speed_percent(genesis_context * context, uint32_t percent) pavone@483: { pavone@483: uint32_t old_clock = context->master_clock; pavone@483: context->master_clock = ((uint64_t)context->normal_clock * (uint64_t)percent) / 100; pavone@483: while (context->ym->current_cycle != context->psg->cycles) { pavone@483: sync_sound(context, context->psg->cycles + MCLKS_PER_PSG); pavone@424: } pavone@483: ym_adjust_master_clock(context->ym, context->master_clock); pavone@483: psg_adjust_master_clock(context->psg, context->master_clock); pavone@483: } pavone@483: pavone@767: char * save_filename; pavone@874: genesis_context *genesis; pavone@874: genesis_context *menu_context; pavone@874: genesis_context *game_context; pavone@767: void persist_save() pavone@351: { pavone@767: FILE * f = fopen(save_filename, "wb"); pavone@351: if (!f) { pavone@767: fprintf(stderr, "Failed to open %s file %s for writing\n", genesis->save_type == SAVE_I2C ? "EEPROM" : "SRAM", save_filename); pavone@351: return; pavone@351: } pavone@767: fwrite(genesis->save_storage, 1, genesis->save_size, f); pavone@351: fclose(f); pavone@767: printf("Saved %s to %s\n", genesis->save_type == SAVE_I2C ? "EEPROM" : "SRAM", save_filename); pavone@351: } pavone@351: pavone@874: #ifndef NO_Z80 pavone@874: const memmap_chunk z80_map[] = { pavone@874: { 0x0000, 0x4000, 0x1FFF, 0, MMAP_READ | MMAP_WRITE | MMAP_CODE, z80_ram, NULL, NULL, NULL, NULL }, pavone@874: { 0x8000, 0x10000, 0x7FFF, 0, 0, NULL, NULL, NULL, z80_read_bank, z80_write_bank}, pavone@874: { 0x4000, 0x6000, 0x0003, 0, 0, NULL, NULL, NULL, z80_read_ym, z80_write_ym}, pavone@874: { 0x6000, 0x6100, 0xFFFF, 0, 0, NULL, NULL, NULL, NULL, z80_write_bank_reg}, pavone@874: { 0x7F00, 0x8000, 0x00FF, 0, 0, NULL, NULL, NULL, z80_vdp_port_read, z80_vdp_port_write} pavone@874: }; pavone@874: #endif pavone@874: pavone@874: genesis_context *alloc_init_genesis(rom_info *rom, int fps, uint32_t ym_opts) pavone@351: { pavone@874: genesis_context *gen = calloc(1, sizeof(genesis_context)); pavone@874: gen->master_clock = gen->normal_clock = fps == 60 ? MCLKS_NTSC : MCLKS_PAL; pavone@874: pavone@874: gen->vdp = malloc(sizeof(vdp_context)); pavone@874: init_vdp_context(gen->vdp, version_reg & 0x40); pavone@874: gen->frame_end = vdp_cycles_to_frame_end(gen->vdp); pavone@874: char * config_cycles = tern_find_path(config, "clocks\0max_cycles\0").ptrval; pavone@874: gen->max_cycles = config_cycles ? atoi(config_cycles) : DEFAULT_SYNC_INTERVAL; pavone@874: pavone@874: gen->ym = malloc(sizeof(ym2612_context)); pavone@874: ym_init(gen->ym, render_sample_rate(), gen->master_clock, MCLKS_PER_YM, render_audio_buffer(), ym_opts); pavone@874: pavone@874: gen->psg = malloc(sizeof(psg_context)); pavone@874: psg_init(gen->psg, render_sample_rate(), gen->master_clock, MCLKS_PER_PSG, render_audio_buffer()); pavone@832: pavone@874: gen->z80 = calloc(1, sizeof(z80_context)); pavone@874: #ifndef NO_Z80 pavone@874: z80_options *z_opts = malloc(sizeof(z80_options)); pavone@874: init_z80_opts(z_opts, z80_map, 5, NULL, 0, MCLKS_PER_Z80); pavone@874: init_z80_context(gen->z80, z_opts); pavone@874: z80_assert_reset(gen->z80, 0); pavone@874: #endif pavone@874: pavone@874: gen->z80->system = gen; pavone@874: gen->z80->mem_pointers[0] = z80_ram; pavone@874: gen->z80->mem_pointers[1] = gen->z80->mem_pointers[2] = (uint8_t *)cart; pavone@874: pavone@883: gen->cart = cart; pavone@874: gen->work_ram = ram; pavone@874: gen->zram = z80_ram; pavone@971: setup_io_devices(config, rom, gen); pavone@874: pavone@874: gen->save_type = rom->save_type; pavone@767: gen->save_type = rom->save_type; pavone@767: if (gen->save_type != SAVE_NONE) { pavone@767: gen->save_ram_mask = rom->save_mask; pavone@767: gen->save_size = rom->save_size; pavone@767: gen->save_storage = rom->save_buffer; pavone@769: gen->eeprom_map = rom->eeprom_map; pavone@769: gen->num_eeprom = rom->num_eeprom; pavone@769: if (gen->save_type == SAVE_I2C) { pavone@770: eeprom_init(&gen->eeprom, gen->save_storage, gen->save_size); pavone@769: } pavone@767: } else { pavone@767: gen->save_storage = NULL; pavone@351: } pavone@832: pavone@874: m68k_options *opts = malloc(sizeof(m68k_options)); pavone@874: init_m68k_opts(opts, rom->map, rom->map_chunks, MCLKS_PER_68K); pavone@874: //TODO: make this configurable pavone@874: opts->gen.flags |= M68K_OPT_BROKEN_READ_MODIFY; pavone@874: gen->m68k = init_68k_context(opts); pavone@874: gen->m68k->system = gen; pavone@488: pavone@776: for (int i = 0; i < rom->map_chunks; i++) pavone@776: { pavone@776: if (rom->map[i].flags & MMAP_PTR_IDX) { pavone@874: gen->m68k->mem_pointers[rom->map[i].ptr_index] = rom->map[i].buffer; pavone@776: } pavone@776: } pavone@832: pavone@874: return gen; pavone@874: } pavone@874: pavone@884: void free_genesis(genesis_context *gen) pavone@884: { pavone@884: vdp_free(gen->vdp); pavone@884: m68k_options_free(gen->m68k->options); pavone@884: free(gen->m68k); pavone@884: z80_options_free(gen->z80->options); pavone@884: free(gen->z80); pavone@884: ym_free(gen->ym); pavone@884: psg_free(gen->psg); pavone@884: free(gen->save_storage); pavone@956: free(gen->save_dir); pavone@884: } pavone@884: pavone@874: void start_genesis(genesis_context *gen, char *statefile, uint8_t *debugger) pavone@874: { pavone@874: pavone@424: if (statefile) { pavone@424: uint32_t pc = load_gst(gen, statefile); pavone@424: if (!pc) { pavone@792: fatal_error("Failed to load save state %s\n", statefile); pavone@424: } pavone@451: printf("Loaded %s\n", statefile); pavone@515: if (debugger) { pavone@874: insert_breakpoint(gen->m68k, pc, debugger); pavone@424: } pavone@451: adjust_int_cycle(gen->m68k, gen->vdp); pavone@874: start_68k_context(gen->m68k, pc); pavone@424: } else { pavone@515: if (debugger) { pavone@752: uint32_t address = cart[2] << 16 | cart[3]; pavone@874: insert_breakpoint(gen->m68k, address, debugger); pavone@424: } pavone@874: m68k_reset(gen->m68k); pavone@211: } pavone@211: } pavone@211: pavone@764: char *title; pavone@340: pavone@764: void update_title(char *rom_name) pavone@340: { pavone@764: if (title) { pavone@764: free(title); pavone@764: title = NULL; pavone@340: } pavone@764: title = alloc_concat(rom_name, " - BlastEm"); pavone@874: render_update_caption(title); pavone@340: } pavone@340: pavone@765: void set_region(rom_info *info, uint8_t region) pavone@341: { pavone@765: if (!region) { pavone@765: char * def_region = tern_find_ptr(config, "default_region"); pavone@765: if (def_region && (!info->regions || (info->regions & translate_region_char(toupper(*def_region))))) { pavone@765: region = translate_region_char(toupper(*def_region)); pavone@765: } else { pavone@765: region = info->regions; pavone@765: } pavone@765: } pavone@765: if (region & REGION_E) { pavone@765: version_reg = NO_DISK | EUR; pavone@765: } else if (region & REGION_J) { pavone@765: version_reg = NO_DISK | JAP; pavone@765: } else { pavone@765: version_reg = NO_DISK | USA; pavone@765: } pavone@341: } pavone@341: pavone@956: void setup_saves(char *fname, rom_info *info, genesis_context *context) pavone@955: { pavone@955: char * barename = basename_no_extension(fname); pavone@955: char const * parts[3] = {get_save_dir(), "/", barename}; pavone@955: char *save_dir = alloc_concat_m(3, parts); pavone@955: if (!ensure_dir_exists(save_dir)) { pavone@955: warning("Failed to create save directory %s\n", save_dir); pavone@955: } pavone@955: parts[0] = save_dir; pavone@955: parts[2] = info->save_type == SAVE_I2C ? "save.eeprom" : "save.sram"; pavone@955: free(save_filename); pavone@955: save_filename = alloc_concat_m(3, parts); pavone@955: parts[2] = "quicksave.gst"; pavone@955: free(save_state_path); pavone@955: save_state_path = alloc_concat_m(3, parts); pavone@956: context->save_dir = save_dir; pavone@955: free(barename); pavone@956: if (info->save_type != SAVE_NONE) { pavone@956: FILE * f = fopen(save_filename, "rb"); pavone@956: if (f) { pavone@956: uint32_t read = fread(context->save_storage, 1, info->save_size, f); pavone@956: fclose(f); pavone@956: if (read > 0) { pavone@956: printf("Loaded %s from %s\n", info->save_type == SAVE_I2C ? "EEPROM" : "SRAM", save_filename); pavone@956: } pavone@956: } pavone@956: atexit(persist_save); pavone@956: } pavone@955: } pavone@955: pavone@88: int main(int argc, char ** argv) pavone@88: { pavone@496: set_exe_str(argv[0]); pavone@496: config = load_config(); pavone@184: int width = -1; pavone@184: int height = -1; pavone@184: int debug = 0; pavone@407: int ym_log = 0; pavone@463: int loaded = 0; pavone@469: uint8_t force_version = 0; pavone@469: char * romfname = NULL; pavone@197: FILE *address_log = NULL; pavone@425: char * statefile = NULL; pavone@766: int rom_size; pavone@515: uint8_t * debuggerfun = NULL; pavone@860: uint8_t fullscreen = FULLSCREEN_DEFAULT, use_gl = 1; pavone@885: uint8_t debug_target = 0; pavone@463: for (int i = 1; i < argc; i++) { pavone@184: if (argv[i][0] == '-') { pavone@184: switch(argv[i][1]) { pavone@505: case 'b': pavone@505: i++; pavone@505: if (i >= argc) { pavone@792: fatal_error("-b must be followed by a frame count\n"); pavone@505: } pavone@505: headless = 1; pavone@505: exit_after = atoi(argv[i]); pavone@505: break; pavone@184: case 'd': pavone@515: debuggerfun = (uint8_t *)debugger; pavone@885: //allow debugging the menu pavone@885: if (argv[i][2] == 'm') { pavone@885: debug_target = 1; pavone@885: } pavone@515: break; pavone@515: case 'D': pavone@515: gdb_remote_init(); pavone@515: debuggerfun = (uint8_t *)gdb_debug_enter; pavone@184: break; pavone@338: case 'f': pavone@860: fullscreen = !fullscreen; pavone@338: break; pavone@488: case 'g': pavone@501: use_gl = 0; pavone@488: break; pavone@197: case 'l': pavone@197: address_log = fopen("address.log", "w"); pavone@197: break; pavone@464: case 'v': pavone@792: info_message("blastem %s\n", BLASTEM_VERSION); pavone@464: return 0; pavone@464: break; pavone@265: case 'n': pavone@265: z80_enabled = 0; pavone@265: break; pavone@341: case 'r': pavone@341: i++; pavone@341: if (i >= argc) { pavone@792: fatal_error("-r must be followed by region (J, U or E)\n"); pavone@341: } pavone@765: force_version = translate_region_char(toupper(argv[i][0])); pavone@765: if (!force_version) { pavone@792: fatal_error("'%c' is not a valid region character for the -r option\n", argv[i][0]); pavone@341: } pavone@341: break; pavone@424: case 's': pavone@424: i++; pavone@424: if (i >= argc) { pavone@792: fatal_error("-s must be followed by a savestate filename\n"); pavone@424: } pavone@424: statefile = argv[i]; pavone@424: break; pavone@832: case 't': pavone@832: force_no_terminal(); pavone@832: break; pavone@407: case 'y': pavone@407: ym_log = 1; pavone@407: break; pavone@463: case 'h': pavone@792: info_message( pavone@469: "Usage: blastem [OPTIONS] ROMFILE [WIDTH] [HEIGHT]\n" pavone@463: "Options:\n" pavone@463: " -h Print this help text\n" pavone@463: " -r (J|U|E) Force region to Japan, US or Europe respectively\n" pavone@463: " -f Start in fullscreen mode\n" pavone@501: " -g Disable OpenGL rendering\n" pavone@463: " -s FILE Load a GST format savestate from FILE\n" pavone@463: " -d Enter debugger on startup\n" pavone@463: " -n Disable Z80\n" pavone@501: " -v Display version number and exit\n" pavone@463: " -l Log 68K code addresses (useful for assemblers)\n" pavone@463: " -y Log individual YM-2612 channels to WAVE files\n" pavone@463: ); pavone@463: return 0; pavone@184: default: pavone@792: fatal_error("Unrecognized switch %s\n", argv[i]); pavone@184: } pavone@463: } else if (!loaded) { pavone@767: if (!(rom_size = load_rom(argv[i]))) { pavone@792: fatal_error("Failed to open %s for reading\n", argv[i]); pavone@463: } pavone@469: romfname = argv[i]; pavone@463: loaded = 1; pavone@184: } else if (width < 0) { pavone@184: width = atoi(argv[i]); pavone@184: } else if (height < 0) { pavone@184: height = atoi(argv[i]); pavone@88: } pavone@88: } pavone@874: uint8_t menu = !loaded; pavone@463: if (!loaded) { pavone@874: //load menu pavone@948: romfname = tern_find_path(config, "ui\0rom\0").ptrval; pavone@874: if (!romfname) { pavone@874: romfname = "menu.bin"; pavone@874: } pavone@875: if (romfname[0] == '/') { pavone@875: if (!(rom_size = load_rom(romfname))) { pavone@875: fatal_error("Failed to open UI ROM %s for reading", romfname); pavone@875: } pavone@875: } else { pavone@875: long fsize; pavone@875: cart = (uint16_t *)read_bundled_file(romfname, &fsize); pavone@875: if (!cart) { pavone@875: fatal_error("Failed to open UI ROM %s for reading", romfname); pavone@875: } pavone@875: rom_size = nearest_pow2(fsize); pavone@875: if (rom_size > fsize) { pavone@875: cart = realloc(cart, rom_size); pavone@875: } pavone@875: } pavone@874: //TODO: load relative to executable or from assets depending on platform pavone@872: pavone@861: loaded = 1; pavone@463: } pavone@948: char *m68k_divider = tern_find_path(config, "clocks\0m68k_divider\0").ptrval; pavone@948: if (!m68k_divider) { pavone@948: m68k_divider = "7"; pavone@948: } pavone@948: MCLKS_PER_68K = atoi(m68k_divider); pavone@948: if (!MCLKS_PER_68K) { pavone@948: MCLKS_PER_68K = 7; pavone@948: } pavone@883: ram = malloc(RAM_WORDS * sizeof(uint16_t)); pavone@883: memmap_chunk base_map[] = { pavone@883: {0xE00000, 0x1000000, 0xFFFF, 0, MMAP_READ | MMAP_WRITE | MMAP_CODE, ram, pavone@883: NULL, NULL, NULL, NULL}, pavone@883: {0xC00000, 0xE00000, 0x1FFFFF, 0, 0, NULL, pavone@883: (read_16_fun)vdp_port_read, (write_16_fun)vdp_port_write, pavone@883: (read_8_fun)vdp_port_read_b, (write_8_fun)vdp_port_write_b}, pavone@883: {0xA00000, 0xA12000, 0x1FFFF, 0, 0, NULL, pavone@883: (read_16_fun)io_read_w, (write_16_fun)io_write_w, pavone@883: (read_8_fun)io_read, (write_8_fun)io_write} pavone@883: }; pavone@764: tern_node *rom_db = load_rom_db(); pavone@767: rom_info info = configure_rom(rom_db, cart, rom_size, base_map, sizeof(base_map)/sizeof(base_map[0])); pavone@776: byteswap_rom(rom_size); pavone@765: set_region(&info, force_version); pavone@764: update_title(info.name); pavone@433: int def_width = 0; pavone@766: char *config_width = tern_find_path(config, "video\0width\0").ptrval; pavone@433: if (config_width) { pavone@433: def_width = atoi(config_width); pavone@433: } pavone@433: if (!def_width) { pavone@433: def_width = 640; pavone@433: } pavone@433: width = width < 320 ? def_width : width; pavone@184: height = height < 240 ? (width/320) * 240 : height; pavone@354: uint32_t fps = 60; pavone@342: if (version_reg & 0x40) { pavone@354: fps = 50; pavone@354: } pavone@354: if (!headless) { pavone@719: render_init(width, height, title, fps, fullscreen); pavone@342: } pavone@874: pavone@874: genesis = alloc_init_genesis(&info, fps, (ym_log && !menu) ? YM_OPT_WAVE_LOG : 0); pavone@956: setup_saves(romfname, &info, genesis); pavone@874: if (menu) { pavone@874: menu_context = genesis; pavone@874: } else { pavone@874: genesis->m68k->options->address_log = address_log; pavone@874: game_context = genesis; pavone@874: } pavone@488: pavone@884: set_keybindings(genesis->ports); pavone@885: start_genesis(genesis, menu ? NULL : statefile, menu == debug_target ? debuggerfun : NULL); pavone@883: for(;;) pavone@883: { pavone@949: if (genesis->should_exit) { pavone@949: break; pavone@949: } pavone@883: if (menu && menu_context->next_rom) { pavone@884: if (game_context) { pavone@884: if (game_context->save_type != SAVE_NONE) { pavone@940: genesis = game_context; pavone@884: persist_save(); pavone@940: genesis = menu_context; pavone@884: } pavone@884: free(game_context->cart); pavone@884: base_map[0].buffer = ram = game_context->work_ram; pavone@884: } else { pavone@884: base_map[0].buffer = ram = malloc(RAM_WORDS * sizeof(uint16_t)); pavone@884: } pavone@884: memset(ram, 0, RAM_WORDS * sizeof(uint16_t)); pavone@883: if (!(rom_size = load_rom(menu_context->next_rom))) { pavone@883: fatal_error("Failed to open %s for reading\n", menu_context->next_rom); pavone@883: } pavone@883: info = configure_rom(rom_db, cart, rom_size, base_map, sizeof(base_map)/sizeof(base_map[0])); pavone@883: byteswap_rom(rom_size); pavone@883: set_region(&info, force_version); pavone@883: update_title(info.name); pavone@883: if (!game_context) { pavone@883: //start a new arena and save old one in suspended genesis context pavone@883: genesis->arena = start_new_arena(); pavone@883: } else { pavone@884: genesis->arena = set_current_arena(game_context->arena); pavone@884: mark_all_free(); pavone@884: free_genesis(game_context); pavone@883: } pavone@884: //allocate new genesis context pavone@884: game_context = alloc_init_genesis(&info, fps, ym_log ? YM_OPT_WAVE_LOG : 0); pavone@957: menu_context->next_context = game_context; pavone@957: game_context->next_context = menu_context; pavone@956: setup_saves(menu_context->next_rom, &info, game_context); pavone@883: free(menu_context->next_rom); pavone@883: menu_context->next_rom = NULL; pavone@883: menu = 0; pavone@883: genesis = game_context; pavone@883: genesis->m68k->options->address_log = address_log; pavone@884: map_all_bindings(genesis->ports); pavone@885: start_genesis(genesis, statefile, menu == debug_target ? debuggerfun : NULL); pavone@949: } else if (menu && game_context) { pavone@883: genesis->arena = set_current_arena(game_context->arena); pavone@883: genesis = game_context; pavone@883: cart = genesis->cart; pavone@883: ram = genesis->work_ram; pavone@883: menu = 0; pavone@884: map_all_bindings(genesis->ports); pavone@883: resume_68k(genesis->m68k); pavone@883: } else if (!menu && menu_context) { pavone@883: genesis->arena = set_current_arena(menu_context->arena); pavone@883: genesis = menu_context; pavone@883: cart = genesis->cart; pavone@883: ram = genesis->work_ram; pavone@883: menu = 1; pavone@884: map_all_bindings(genesis->ports); pavone@883: resume_68k(genesis->m68k); pavone@883: } else { pavone@883: break; pavone@874: } pavone@874: } pavone@874: pavone@88: return 0; pavone@88: }