# HG changeset patch # User Michael Pavone # Date 1707013961 28800 # Node ID 65c2e4d990cc4831bf6ce1b7c79c747c923166d8 # Parent 8b948cf23557ef70cda131e545bbc14b06f444bf WIP Pico emulation diff -r 8b948cf23557 -r 65c2e4d990cc blastem.c --- a/blastem.c Thu Jan 25 22:18:46 2024 -0800 +++ b/blastem.c Sat Feb 03 18:32:41 2024 -0800 @@ -609,6 +609,8 @@ stype = force_stype = SYSTEM_SMS; } else if (!strcmp("gen", argv[i])) { stype = force_stype = SYSTEM_GENESIS; + } else if (!strcmp("pico", argv[i])) { + stype = force_stype = SYSTEM_PICO; } else if (!strcmp("jag", argv[i])) { stype = force_stype = SYSTEM_JAGUAR; } else if (!strcmp("media", argv[i])) { @@ -648,8 +650,9 @@ " -h Print this help text\n" " -r (J|U|E) Force region to Japan, US or Europe respectively\n" " -m MACHINE Force emulated machine type to MACHINE. Valid values are:\n" - " sms - Sega Master System/Mark III\n" - " gen - Sega Genesis/Megadrive\n" + " sms - Sega Master System/Mark III\n" + " gen - Sega Genesis/Megadrive\n" + " pico - Sega Pico\n" " media - Media Player\n" " -f Toggles fullscreen mode\n" " -g Disable OpenGL rendering\n" diff -r 8b948cf23557 -r 65c2e4d990cc genesis.c --- a/genesis.c Thu Jan 25 22:18:46 2024 -0800 +++ b/genesis.c Sat Feb 03 18:32:41 2024 -0800 @@ -59,18 +59,22 @@ m68k_serialize(gen->m68k, m68k_pc, buf); end_section(buf); - start_section(buf, SECTION_Z80); - z80_serialize(gen->z80, buf); - end_section(buf); + if (gen->header.type == SYSTEM_GENESIS) { + start_section(buf, SECTION_Z80); + z80_serialize(gen->z80, buf); + end_section(buf); + } } start_section(buf, SECTION_VDP); vdp_serialize(gen->vdp, buf); end_section(buf); - start_section(buf, SECTION_YM2612); - ym_serialize(gen->ym, buf); - end_section(buf); + if (gen->header.type != SYSTEM_PICO) { + start_section(buf, SECTION_YM2612); + ym_serialize(gen->ym, buf); + end_section(buf); + } start_section(buf, SECTION_PSG); psg_serialize(gen->psg, buf); @@ -86,36 +90,40 @@ save_int32(buf, gen->refresh_counter); end_section(buf); - start_section(buf, SECTION_SEGA_IO_1); - io_serialize(gen->io.ports, buf); - end_section(buf); + if (gen->header.type == SYSTEM_GENESIS) { + start_section(buf, SECTION_SEGA_IO_1); + io_serialize(gen->io.ports, buf); + end_section(buf); - start_section(buf, SECTION_SEGA_IO_2); - io_serialize(gen->io.ports + 1, buf); - end_section(buf); + start_section(buf, SECTION_SEGA_IO_2); + io_serialize(gen->io.ports + 1, buf); + end_section(buf); - start_section(buf, SECTION_SEGA_IO_EXT); - io_serialize(gen->io.ports + 2, buf); - end_section(buf); + start_section(buf, SECTION_SEGA_IO_EXT); + io_serialize(gen->io.ports + 2, buf); + end_section(buf); + } start_section(buf, SECTION_MAIN_RAM); save_int8(buf, RAM_WORDS * 2 / 1024); save_buffer16(buf, gen->work_ram, RAM_WORDS); end_section(buf); - start_section(buf, SECTION_SOUND_RAM); - save_int8(buf, Z80_RAM_BYTES / 1024); - save_buffer8(buf, gen->zram, Z80_RAM_BYTES); - end_section(buf); + if (gen->header.type == SYSTEM_GENESIS) { + start_section(buf, SECTION_SOUND_RAM); + save_int8(buf, Z80_RAM_BYTES / 1024); + save_buffer8(buf, gen->zram, Z80_RAM_BYTES); + end_section(buf); - if (gen->version_reg & 0xF) { - //only save TMSS info if it's present - //that will allow a state saved on a model lacking TMSS - //to be loaded on a model that has it - start_section(buf, SECTION_TMSS); - save_int8(buf, gen->tmss); - save_buffer16(buf, gen->tmss_lock, 2); - end_section(buf); + if (gen->version_reg & 0xF) { + //only save TMSS info if it's present + //that will allow a state saved on a model lacking TMSS + //to be loaded on a model that has it + start_section(buf, SECTION_TMSS); + save_int8(buf, gen->tmss); + save_buffer16(buf, gen->tmss_lock, 2); + end_section(buf); + } } cart_serialize(&gen->header, buf); @@ -214,18 +222,22 @@ void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen) { register_section_handler(buf, (section_handler){.fun = m68k_deserialize, .data = gen->m68k}, SECTION_68000); - register_section_handler(buf, (section_handler){.fun = z80_deserialize, .data = gen->z80}, SECTION_Z80); register_section_handler(buf, (section_handler){.fun = vdp_deserialize, .data = gen->vdp}, SECTION_VDP); - register_section_handler(buf, (section_handler){.fun = ym_deserialize, .data = gen->ym}, SECTION_YM2612); register_section_handler(buf, (section_handler){.fun = psg_deserialize, .data = gen->psg}, SECTION_PSG); register_section_handler(buf, (section_handler){.fun = bus_arbiter_deserialize, .data = gen}, SECTION_GEN_BUS_ARBITER); - register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = gen->io.ports}, SECTION_SEGA_IO_1); - register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = gen->io.ports + 1}, SECTION_SEGA_IO_2); - register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = gen->io.ports + 2}, SECTION_SEGA_IO_EXT); register_section_handler(buf, (section_handler){.fun = ram_deserialize, .data = gen}, SECTION_MAIN_RAM); - register_section_handler(buf, (section_handler){.fun = zram_deserialize, .data = gen}, SECTION_SOUND_RAM); register_section_handler(buf, (section_handler){.fun = cart_deserialize, .data = gen}, SECTION_MAPPER); - register_section_handler(buf, (section_handler){.fun = tmss_deserialize, .data = gen}, SECTION_TMSS); + if (gen->header.type == SYSTEM_GENESIS) { + register_section_handler(buf, (section_handler){.fun = z80_deserialize, .data = gen->z80}, SECTION_Z80); + register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = gen->io.ports}, SECTION_SEGA_IO_1); + register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = gen->io.ports + 1}, SECTION_SEGA_IO_2); + register_section_handler(buf, (section_handler){.fun = io_deserialize, .data = gen->io.ports + 2}, SECTION_SEGA_IO_EXT); + register_section_handler(buf, (section_handler){.fun = zram_deserialize, .data = gen}, SECTION_SOUND_RAM); + register_section_handler(buf, (section_handler){.fun = tmss_deserialize, .data = gen}, SECTION_TMSS); + } + if (gen->header.type != SYSTEM_PICO) { + register_section_handler(buf, (section_handler){.fun = ym_deserialize, .data = gen->ym}, SECTION_YM2612); + } if (gen->expansion) { segacd_context *cd = gen->expansion; segacd_register_section_handlers(cd, buf); @@ -236,7 +248,7 @@ { load_section(buf); } - if (gen->version_reg & 0xF) { + if (gen->header.type == SYSTEM_GENESIS && (gen->version_reg & 0xF)) { if (gen->tmss == 0xFF) { //state lacked a TMSS section, assume that the game ROM is mapped in //and that the VDP is unlocked @@ -339,7 +351,7 @@ } } - if (mask < 2 && (v_context->regs[REG_MODE_3] & BIT_EINT_EN)) { + if (mask < 2 && (v_context->regs[REG_MODE_3] & BIT_EINT_EN) && gen->header.type == SYSTEM_GENESIS) { uint32_t next_eint_port0 = io_next_interrupt(gen->io.ports, context->current_cycle); uint32_t next_eint_port1 = io_next_interrupt(gen->io.ports + 1, context->current_cycle); uint32_t next_eint_port2 = io_next_interrupt(gen->io.ports + 2, context->current_cycle); @@ -669,6 +681,131 @@ return context; } +static m68k_context* sync_components_pico(m68k_context * context, uint32_t address) +{ + genesis_context * gen = context->system; + vdp_context * v_context = gen->vdp; + if (gen->bus_busy) { + gen_update_refresh_no_wait(context); + } else { + gen_update_refresh(context); + } + + uint32_t mclks = context->current_cycle; + psg_run(gen->psg, mclks); + vdp_run_context(v_context, mclks); + if (mclks >= gen->reset_cycle) { + gen->reset_requested = 1; + context->should_return = 1; + gen->reset_cycle = CYCLE_NEVER; + } + if (v_context->frame != gen->last_frame) { +#ifndef IS_LIB + if (gen->psg->scope) { + scope_render(gen->psg->scope); + } +#endif + //printf("reached frame end %d | MCLK Cycles: %d, Target: %d, VDP cycles: %d, vcounter: %d, hslot: %d\n", gen->last_frame, mclks, gen->frame_end, v_context->cycles, v_context->vcounter, v_context->hslot); + uint32_t elapsed = v_context->frame - gen->last_frame; + gen->last_frame = v_context->frame; + event_flush(mclks); + gen->last_flush_cycle = mclks; + if (gen->header.enter_debugger_frames) { + if (elapsed >= gen->header.enter_debugger_frames) { + gen->header.enter_debugger_frames = 0; + gen->header.enter_debugger = 1; + } else { + gen->header.enter_debugger_frames -= elapsed; + } + } + + if(exit_after){ + if (elapsed >= exit_after) { + exit(0); + } else { + exit_after -= elapsed; + } + } + if (context->current_cycle > MAX_NO_ADJUST) { + uint32_t deduction = mclks - ADJUST_BUFFER; + vdp_adjust_cycles(v_context, deduction); + if (gen->mapper_type == MAPPER_JCART) { + jcart_adjust_cycles(gen, deduction); + } + context->current_cycle -= deduction; + if (gen->psg->vgm) { + vgm_adjust_cycles(gen->psg->vgm, deduction); + } + gen->psg->cycles -= deduction; + if (gen->reset_cycle != CYCLE_NEVER) { + gen->reset_cycle -= deduction; + } + event_cycle_adjust(mclks, deduction); + if (gen->expansion) { + scd_adjust_cycle(gen->expansion, deduction); + } + gen->last_flush_cycle -= deduction; + } + } else if (mclks - gen->last_flush_cycle > gen->soft_flush_cycles) { + event_soft_flush(mclks); + gen->last_flush_cycle = mclks; + } + gen->frame_end = vdp_cycles_to_frame_end(v_context); + context->sync_cycle = gen->frame_end; + //printf("Set sync cycle to: %d @ %d, vcounter: %d, hslot: %d\n", context->sync_cycle, context->current_cycle, v_context->vcounter, v_context->hslot); + if (!address && (gen->header.enter_debugger || gen->header.save_state)) { + context->sync_cycle = context->current_cycle + 1; + } + adjust_int_cycle(context, v_context); + if (gen->reset_cycle < context->target_cycle) { + context->target_cycle = gen->reset_cycle; + } + if (address) { + if (gen->header.enter_debugger || context->wp_hit) { + if (!context->wp_hit) { + gen->header.enter_debugger = 0; + } +#ifndef IS_LIB + if (gen->header.debugger_type == DEBUGGER_NATIVE) { + debugger(context, address); + } else { + gdb_debug_enter(context, address); + } +#endif + } + if (gen->header.save_state) { + uint8_t slot = gen->header.save_state - 1; + gen->header.save_state = 0; + char *save_path = slot >= SERIALIZE_SLOT ? NULL : get_slot_name(&gen->header, slot, use_native_states ? "state" : "gst"); + if (use_native_states || slot >= SERIALIZE_SLOT) { + serialize_buffer state; + init_serialize(&state); + genesis_serialize(gen, &state, address, slot != EVENTLOG_SLOT); + if (slot == SERIALIZE_SLOT) { + gen->serialize_tmp = state.data; + gen->serialize_size = state.size; + context->sync_cycle = context->current_cycle; + context->should_return = 1; + } else if (slot == EVENTLOG_SLOT) { + event_state(context->current_cycle, &state); + } else { + save_to_file(&state, save_path); + free(state.data); + } + } else { + save_gst(gen, save_path, address); + } + if (slot != SERIALIZE_SLOT) { + debug_message("Saved state to %s\n", save_path); + } + free(save_path); + } else if(gen->header.save_state) { + context->sync_cycle = context->current_cycle + 1; + } + } + return context; +} + static m68k_context *int_ack(m68k_context *context) { genesis_context * gen = context->system; @@ -704,7 +841,11 @@ //do refresh check here so we can avoid adding a penalty for a refresh that happens during a VDP access gen_update_refresh_free_access(context); - sync_components(context, 0); + if (gen->header.type == SYSTEM_PICO) { + sync_components_pico(context, 0); + } else { + sync_components(context, 0); + } vdp_context *v_context = gen->vdp; uint32_t before_cycle = v_context->cycles; uint8_t did_dma = 0; @@ -723,7 +864,11 @@ } context->current_cycle += m68k_cycle_diff; gen->bus_busy = 1; - sync_components(context, 0); + if (gen->header.type == SYSTEM_PICO) { + sync_components_pico(context, 0); + } else { + sync_components(context, 0); + } gen->bus_busy = 0; } } @@ -746,7 +891,11 @@ } context->current_cycle += m68k_cycle_diff; gen->bus_busy = 1; - sync_components(context, 0); + if (gen->header.type == SYSTEM_PICO) { + sync_components_pico(context, 0); + } else { + sync_components(context, 0); + } gen->bus_busy = 0; } } @@ -773,10 +922,12 @@ m68k_cycle_diff += MCLKS_PER_68K; } context->current_cycle += m68k_cycle_diff; - //Lock the Z80 out of the bus until the VDP access is complete - gen->bus_busy = 1; - sync_z80(gen, v_context->cycles); - gen->bus_busy = 0; + if (gen->header.type == SYSTEM_GENESIS) { + //Lock the Z80 out of the bus until the VDP access is complete + gen->bus_busy = 1; + sync_z80(gen, v_context->cycles); + gen->bus_busy = 0; + } } } else if (vdp_port < 0x18) { psg_write(gen->psg, value); @@ -843,7 +994,11 @@ //do refresh check here so we can avoid adding a penalty for a refresh that happens during a VDP access gen_update_refresh_free_access(context); - sync_components(context, 0); + if (gen->header.type == SYSTEM_PICO) { + sync_components_pico(context, 0); + } else { + sync_components(context, 0); + } vdp_context * v_context = gen->vdp; uint32_t before_cycle = context->current_cycle; if (vdp_port < 0x10) { @@ -864,9 +1019,11 @@ //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); //Lock the Z80 out of the bus until the VDP access is complete genesis_context *gen = context->system; - gen->bus_busy = 1; - sync_z80(gen, context->current_cycle); - gen->bus_busy = 0; + if (gen->header.type == SYSTEM_GENESIS) { + gen->bus_busy = 1; + sync_z80(gen, context->current_cycle); + gen->bus_busy = 0; + } } //refresh may have happened while we were waiting on the VDP, @@ -1074,6 +1231,18 @@ } } +static void* pico_io_write(uint32_t location, void *vcontext, uint8_t value) +{ + printf("Pico IO write.b %X - %X\n", location, value); + return vcontext; +} + +static void* pico_io_write_w(uint32_t location, void *vcontext, uint16_t value) +{ + printf("Pico IO write.w %X - %X\n", location, value); + return vcontext; +} + #define FOREIGN 0x80 #define HZ50 0x40 #define USA FOREIGN @@ -1203,6 +1372,46 @@ return value; } +static uint8_t pico_io_read(uint32_t location, void *vcontext) +{ + m68k_context *m68k = vcontext; + genesis_context *gen = m68k->system; + switch(location >> 1 & 0x7F) + { + case 0: + return gen->version_reg; + case 1: + return gen->pico_button_state; + case 2: + return gen->pico_pen_x >> 8; + case 3: + return gen->pico_pen_x; + case 4: + return gen->pico_pen_y >> 8; + case 5: + return gen->pico_pen_y; + case 6: + return gen->pico_page; + case 8: + printf("uPD7759 data read @ %u\n", m68k->current_cycle); + return 0xFF; + case 9: + printf("uPD7759 contro/status read @ %u\n", m68k->current_cycle); + return 0; + default: + printf("Unknown Pico IO read %X @ %u\n", location, m68k->current_cycle); + return 0xFF; + } +} + +static uint16_t pico_io_read_w(uint32_t location, void *vcontext) +{ + m68k_context *m68k = vcontext; + genesis_context *gen = m68k->system; + uint16_t value = pico_io_read(location, vcontext); + return value | (value << 8); +} + static void * z80_write_ym(uint32_t location, void * vcontext, uint8_t value) { z80_context * context = vcontext; @@ -1446,20 +1655,33 @@ region = info->regions; } } - if (region & REGION_E) { - gen->version_reg = NO_DISK | EUR; - } else if (region & REGION_J) { - gen->version_reg = NO_DISK | JAP; + uint8_t is_50hz = 0; + if (gen->header.type == SYSTEM_PICO) { + if (region & REGION_E) { + is_50hz = 1; + gen->version_reg = 0x20; + } else if (region & REGION_J) { + gen->version_reg = 0; + } else { + gen->version_reg = 0x40; + } } else { - gen->version_reg = NO_DISK | USA; + if (region & REGION_E) { + gen->version_reg = NO_DISK | EUR; + is_50hz = 1; + } else if (region & REGION_J) { + gen->version_reg = NO_DISK | JAP; + } else { + gen->version_reg = NO_DISK | USA; + } } - if (region & HZ50) { + if (is_50hz) { gen->normal_clock = MCLKS_PAL; - gen->soft_flush_cycles = MCLKS_LINE * 262 / 3 + 2; + gen->soft_flush_cycles = MCLKS_LINE * 313 / 3 + 2; } else { gen->normal_clock = MCLKS_NTSC; - gen->soft_flush_cycles = MCLKS_LINE * 313 / 3 + 2; + gen->soft_flush_cycles = MCLKS_LINE * 262 / 3 + 2; } gen->master_clock = gen->normal_clock; } @@ -1505,9 +1727,13 @@ if (gen->reset_requested) { gen->reset_requested = 0; gen->m68k->should_return = 0; - z80_assert_reset(gen->z80, gen->m68k->current_cycle); - z80_clear_busreq(gen->z80, gen->m68k->current_cycle); - ym_reset(gen->ym); + if (gen->header.type == SYSTEM_GENESIS) { + z80_assert_reset(gen->z80, gen->m68k->current_cycle); + z80_clear_busreq(gen->z80, gen->m68k->current_cycle); + } + if (gen->header.type != SYSTEM_PICO) { + ym_reset(gen->ym); + } //Is there any sort of VDP reset? m68k_reset(gen->m68k); } @@ -1520,7 +1746,9 @@ if (gen->header.force_release || render_should_release_on_exit()) { bindings_release_capture(); vdp_release_framebuffer(gen->vdp); - render_pause_source(gen->ym->audio); + if (gen->header.type != SYSTEM_PICO) { + render_pause_source(gen->ym->audio); + } render_pause_source(gen->psg->audio); } } @@ -1572,10 +1800,16 @@ genesis_context *gen = (genesis_context *)system; if (gen->header.force_release || render_should_release_on_exit()) { gen->header.force_release = 0; - render_set_video_standard((gen->version_reg & HZ50) ? VID_PAL : VID_NTSC); + if (gen->header.type == SYSTEM_PICO) { + render_set_video_standard((gen->version_reg & 0x60) == 0x20 ? VID_PAL : VID_NTSC); + } else { + render_set_video_standard((gen->version_reg & HZ50) ? VID_PAL : VID_NTSC); + } bindings_reacquire_capture(); vdp_reacquire_framebuffer(gen->vdp); - render_resume_source(gen->ym->audio); + if (gen->header.type != SYSTEM_PICO) { + render_resume_source(gen->ym->audio); + } render_resume_source(gen->psg->audio); } resume_68k(gen->m68k); @@ -1722,10 +1956,14 @@ free(gen->cart); free(gen->m68k); free(gen->work_ram); - z80_options_free(gen->z80->Z80_OPTS); - free(gen->z80); - free(gen->zram); - ym_free(gen->ym); + if (gen->header.type == SYSTEM_GENESIS) { + z80_options_free(gen->z80->Z80_OPTS); + free(gen->z80); + free(gen->zram); + } + if (gen->header.type != SYSTEM_PICO) { + ym_free(gen->ym); + } psg_free(gen->psg); free(gen->header.save_dir); free_rom_info(&gen->header.info); @@ -1755,6 +1993,36 @@ } } +static void gamepad_down_pico(system_header *system, uint8_t gamepad_num, uint8_t button) +{ + genesis_context *gen = (genesis_context *)system; + if (gamepad_num != 1) { + return; + } + //TODO: storyware display + if (button == BUTTON_C) { + gen->pico_page <<= 1; + gen->pico_page |= 1; + gen->pico_page &= 0x3F; + } else if (button == BUTTON_Z) { + gen->pico_page >>= 1; + } else if (button < BUTTON_B) { + gen->pico_button_state &= ~(1 << (button - 1)); + } +} + +static void gamepad_up_pico(system_header *system, uint8_t gamepad_num, uint8_t button) +{ + genesis_context *gen = (genesis_context *)system; + if (gamepad_num != 1) { + return; + } + if (button < BUTTON_B) { + gen->pico_button_state |= 1 << (button - 1); + } + return; +} + static void mouse_down(system_header *system, uint8_t mouse_num, uint8_t button) { genesis_context *gen = (genesis_context *)system; @@ -1767,6 +2035,22 @@ io_mouse_up(&gen->io, mouse_num, button); } +static void mouse_down_pico(system_header *system, uint8_t mouse_num, uint8_t button) +{ + genesis_context *gen = (genesis_context *)system; + if (button == MOUSE_LEFT) { + gen->pico_button_state &= ~0x80; + } +} + +static void mouse_up_pico(system_header *system, uint8_t mouse_num, uint8_t button) +{ + genesis_context *gen = (genesis_context *)system; + if (button == MOUSE_LEFT) { + gen->pico_button_state |= 0x80; + } +} + static void mouse_motion_absolute(system_header *system, uint8_t mouse_num, uint16_t x, uint16_t y) { genesis_context *gen = (genesis_context *)system; @@ -1779,6 +2063,24 @@ io_mouse_motion_relative(&gen->io, mouse_num, x, y); } +static void mouse_motion_absolute_pico(system_header *system, uint8_t mouse_num, uint16_t x, uint16_t y) +{ + genesis_context *gen = (genesis_context *)system; + //TODO: scale properly + //TODO: limit to mouse motion on emulated storyware/drawing area + gen->pico_pen_x = (x >> 1) + 0x3C; + gen->pico_pen_y = y + 0x1FC; +} + +static void mouse_motion_relative_pico(system_header *system, uint8_t mouse_num, int32_t x, int32_t y) +{ + genesis_context *gen = (genesis_context *)system; + //TODO: scale properly + //TODO: limit to mouse motion on emulated storyware/drawing area + gen->pico_pen_x += x; + gen->pico_pen_y += y; +} + static void keyboard_down(system_header *system, uint8_t scancode) { genesis_context *gen = (genesis_context *)system; @@ -1791,16 +2093,30 @@ io_keyboard_up(&gen->io, scancode); } +static void keyboard_down_pico(system_header *system, uint8_t scancode) +{ + genesis_context *gen = (genesis_context *)system; + //TODO: Keyboard Pico emulation +} + +static void keyboard_up_pico(system_header *system, uint8_t scancode) +{ + genesis_context *gen = (genesis_context *)system; + //TODO: Keyboard Pico emulation +} + static void set_audio_config(genesis_context *gen) { char *config_gain; config_gain = tern_find_path(config, "audio\0psg_gain\0", TVAL_PTR).ptrval; render_audio_source_gaindb(gen->psg->audio, config_gain ? atof(config_gain) : 0.0f); - config_gain = tern_find_path(config, "audio\0fm_gain\0", TVAL_PTR).ptrval; - render_audio_source_gaindb(gen->ym->audio, config_gain ? atof(config_gain) : 0.0f); + if (gen->header.type != SYSTEM_PICO) { + config_gain = tern_find_path(config, "audio\0fm_gain\0", TVAL_PTR).ptrval; + render_audio_source_gaindb(gen->ym->audio, config_gain ? atof(config_gain) : 0.0f); - char *config_dac = tern_find_path_default(config, "audio\0fm_dac\0", (tern_val){.ptrval="zero_offset"}, TVAL_PTR).ptrval; - ym_enable_zero_offset(gen->ym, !strcmp(config_dac, "zero_offset")); + char *config_dac = tern_find_path_default(config, "audio\0fm_dac\0", (tern_val){.ptrval="zero_offset"}, TVAL_PTR).ptrval; + ym_enable_zero_offset(gen->ym, !strcmp(config_dac, "zero_offset")); + } if (gen->expansion) { segacd_context *cd = gen->expansion; @@ -1814,10 +2130,14 @@ static void config_updated(system_header *system) { genesis_context *gen = (genesis_context *)system; - setup_io_devices(config, &system->info, &gen->io); + if (gen->header.type == SYSTEM_GENESIS) { + setup_io_devices(config, &system->info, &gen->io); + } set_audio_config(gen); //sample rate may have changed - ym_adjust_master_clock(gen->ym, gen->master_clock); + if (gen->header.type != SYSTEM_PICO) { + ym_adjust_master_clock(gen->ym, gen->master_clock); + } psg_adjust_master_clock(gen->psg, gen->master_clock); if (gen->expansion) { segacd_config_updated(gen->expansion); @@ -1831,7 +2151,9 @@ if (vgm) { printf("Started logging VGM to %s\n", filename); sync_sound(gen, vgm->last_cycle); - ym_vgm_log(gen->ym, gen->normal_clock, vgm); + if (gen->header.type != SYSTEM_PICO) { + ym_vgm_log(gen->ym, gen->normal_clock, vgm); + } psg_vgm_log(gen->psg, gen->normal_clock, vgm); gen->header.vgm_logging = 1; } else { @@ -1844,7 +2166,10 @@ puts("Stopped VGM log"); genesis_context *gen = (genesis_context *)system; vgm_close(gen->ym->vgm); - gen->ym->vgm = gen->psg->vgm = NULL; + if (gen->header.type != SYSTEM_PICO) { + gen->ym->vgm = NULL; + } + gen->psg->vgm = NULL; gen->header.vgm_logging = 0; } @@ -1855,9 +2180,11 @@ if (debug_view < DEBUG_OSCILLOSCOPE) { vdp_toggle_debug_view(gen->vdp, debug_view); } else if (debug_view == DEBUG_OSCILLOSCOPE) { - if (gen->ym->scope) { - oscilloscope *scope = gen->ym->scope; - gen->ym->scope = NULL; + if (gen->psg->scope) { + oscilloscope *scope = gen->psg->scope; + if (gen->header.type != SYSTEM_PICO) { + gen->ym->scope = NULL; + } gen->psg->scope = NULL; if (gen->expansion) { segacd_context *cd = gen->expansion; @@ -1866,7 +2193,9 @@ scope_close(scope); } else { oscilloscope *scope = create_oscilloscope(); - ym_enable_scope(gen->ym, scope, gen->normal_clock); + if (gen->header.type != SYSTEM_PICO) { + ym_enable_scope(gen->ym, scope, gen->normal_clock); + } psg_enable_scope(gen->psg, scope, gen->normal_clock); if (gen->expansion) { segacd_context *cd = gen->expansion; @@ -2466,3 +2795,169 @@ set_audio_config(gen); return gen; } + +static memmap_chunk pico_base_map[] = { + {0xE00000, 0x1000000, 0xFFFF, .flags = MMAP_READ | MMAP_WRITE | MMAP_CODE}, + {0xC00000, 0xE00000, 0x1FFFFF, .read_16 = (read_16_fun)vdp_port_read, .write_16 =(write_16_fun)vdp_port_write, + .read_8 = (read_8_fun)vdp_port_read_b, .write_8 = (write_8_fun)vdp_port_write_b}, + {0x800000, 0x900000, 0xFFFFFF, .read_16 = pico_io_read_w, .write_16 = pico_io_write_w, + .read_8 = pico_io_read, .write_8 = pico_io_write} +}; +const size_t pico_base_chunks = sizeof(pico_base_map)/sizeof(*pico_base_map); + +genesis_context* alloc_config_pico(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t ym_opts, uint8_t force_region) +{ + tern_node *rom_db = get_rom_db(); + rom_info info = configure_rom(rom_db, rom, rom_size, lock_on, lock_on_size, pico_base_map, pico_base_chunks); + rom = info.rom; + rom_size = info.rom_size; +#ifndef BLASTEM_BIG_ENDIAN + byteswap_rom(nearest_pow2(rom_size), rom); + if (lock_on) { + byteswap_rom(nearest_pow2(lock_on_size), lock_on); + } +#endif + char *m68k_divider = tern_find_path(config, "clocks\0m68k_divider\0", TVAL_PTR).ptrval; + if (!m68k_divider) { + m68k_divider = "7"; + } + MCLKS_PER_68K = atoi(m68k_divider); + if (!MCLKS_PER_68K) { + MCLKS_PER_68K = 7; + } + genesis_context *gen = calloc(1, sizeof(genesis_context)); + gen->header.set_speed_percent = set_speed_percent; + gen->header.start_context = start_genesis; + gen->header.resume_context = resume_genesis; + gen->header.load_save = load_save; + gen->header.persist_save = persist_save; + gen->header.load_state = load_state; + gen->header.soft_reset = soft_reset; + gen->header.free_context = free_genesis; + gen->header.get_open_bus_value = get_open_bus_value; + gen->header.request_exit = request_exit; + gen->header.inc_debug_mode = inc_debug_mode; + gen->header.gamepad_down = gamepad_down_pico; + gen->header.gamepad_up = gamepad_up_pico; + gen->header.mouse_down = mouse_down_pico; + gen->header.mouse_up = mouse_up_pico; + gen->header.mouse_motion_absolute = mouse_motion_absolute_pico; + gen->header.mouse_motion_relative = mouse_motion_relative_pico; + gen->header.keyboard_down = keyboard_down_pico; + gen->header.keyboard_up = keyboard_up_pico; + gen->header.config_updated = config_updated; + gen->header.serialize = serialize; + gen->header.deserialize = deserialize; + gen->header.start_vgm_log = start_vgm_log; + gen->header.stop_vgm_log = stop_vgm_log; + gen->header.toggle_debug_view = toggle_debug_view; + gen->header.type = SYSTEM_PICO; + gen->header.info = info; + set_region(gen, rom, force_region); + gen->vdp_unlocked = 1; + gen->pico_button_state = 0xFF; + + gen->vdp = init_vdp_context((gen->version_reg & 0x60) == 0x20, 40, VDP_GENESIS); + gen->vdp->system = &gen->header; + gen->frame_end = vdp_cycles_to_frame_end(gen->vdp); + char * config_cycles = tern_find_path(config, "clocks\0max_cycles\0", TVAL_PTR).ptrval; + gen->max_cycles = config_cycles ? atoi(config_cycles) : DEFAULT_SYNC_INTERVAL; + gen->int_latency_prev1 = MCLKS_PER_68K * 32; + gen->int_latency_prev2 = MCLKS_PER_68K * 16; + + render_set_video_standard((gen->version_reg & 0x60) == 0x20 ? VID_PAL : VID_NTSC); + + gen->psg = calloc(1, sizeof(psg_context)); + psg_init(gen->psg, gen->master_clock, MCLKS_PER_PSG); + gen->work_ram = calloc(2, RAM_WORDS); + if (!strcmp("random", tern_find_path_default(config, "system\0ram_init\0", (tern_val){.ptrval = "zero"}, TVAL_PTR).ptrval)) + { + srand(time(NULL)); + for (int i = 0; i < RAM_WORDS; i++) + { + gen->work_ram[i] = rand(); + } + for (int i = 0; i < VRAM_SIZE; i++) + { + gen->vdp->vdpmem[i] = rand(); + } + for (int i = 0; i < SAT_CACHE_SIZE; i++) + { + gen->vdp->sat_cache[i] = rand(); + } + for (int i = 0; i < CRAM_SIZE; i++) + { + write_cram_internal(gen->vdp, i, rand()); + } + for (int i = 0; i < gen->vdp->vsram_size; i++) + { + gen->vdp->vsram[i] = rand(); + } + } + gen->cart = rom; + gen->lock_on = lock_on; + gen->header.has_keyboard = 0; //TODO: Keyboard Pico emulation + gen->mapper_type = info.mapper_type; + gen->save_type = info.save_type; + if (gen->save_type != SAVE_NONE) { + gen->save_ram_mask = info.save_mask; + gen->save_size = info.save_size; + gen->save_storage = info.save_buffer; + gen->header.info.save_buffer = info.save_buffer = NULL; + gen->eeprom_map = info.eeprom_map; + gen->num_eeprom = info.num_eeprom; + if (gen->save_type == SAVE_I2C) { + eeprom_init(&gen->eeprom, gen->save_storage, gen->save_size); + } else if (gen->save_type == SAVE_NOR) { + memcpy(&gen->nor, info.nor, sizeof(gen->nor)); + //nor_flash_init(&gen->nor, gen->save_storage, gen->save_size, info.save_page_size, info.save_product_id, info.save_bus); + } + } else { + gen->save_storage = NULL; + } + + gen->mapper_start_index = info.mapper_start_index; + //This must happen before we generate memory access functions in init_m68k_opts + for (int i = 0; i < info.map_chunks; i++) + { + if (info.map[i].start == 0xE00000) { + info.map[i].buffer = gen->work_ram; + break; + } + } + + memmap_chunk* map = info.map; + uint32_t map_chunks = info.map_chunks; + info.map = gen->header.info.map = NULL; + + m68k_options *opts = malloc(sizeof(m68k_options)); + init_m68k_opts(opts, map, map_chunks, MCLKS_PER_68K, sync_components_pico, int_ack); + //TODO: Pico model selection + //if (!strcmp(tern_find_ptr_default(model, "tas", "broken"), "broken")) { + opts->gen.flags |= M68K_OPT_BROKEN_READ_MODIFY; + //} + gen->m68k = init_68k_context(opts, NULL); + gen->m68k->system = gen; + opts->address_log = (ym_opts & OPT_ADDRESS_LOG) ? fopen("address.log", "w") : NULL; + + //This must happen after the 68K context has been allocated + for (int i = 0; i < map_chunks; i++) + { + if (map[i].flags & MMAP_PTR_IDX) { + gen->m68k->mem_pointers[map[i].ptr_index] = map[i].buffer; + } + } + + if (gen->mapper_type == MAPPER_SEGA) { + //initialize bank registers + for (int i = 1; i < sizeof(gen->bank_regs); i++) + { + gen->bank_regs[i] = i; + } + } + gen->reset_cycle = CYCLE_NEVER; + + set_audio_config(gen); + bindings_set_mouse_mode(MOUSE_ABSOLUTE); + return gen; +} diff -r 8b948cf23557 -r 65c2e4d990cc genesis.h --- a/genesis.h Thu Jan 25 22:18:46 2024 -0800 +++ b/genesis.h Sat Feb 03 18:32:41 2024 -0800 @@ -65,6 +65,8 @@ uint32_t last_sync_cycle; uint32_t refresh_counter; uint16_t z80_bank_reg; + uint16_t pico_pen_x; + uint16_t pico_pen_y; uint16_t tmss_lock[2]; uint16_t mapper_start_index; uint8_t mapper_type; @@ -72,6 +74,8 @@ uint8_t save_type; sega_io io; uint8_t version_reg; + uint8_t pico_button_state; + uint8_t pico_page; uint8_t bus_busy; uint8_t reset_requested; uint8_t tmss; @@ -86,6 +90,7 @@ genesis_context *alloc_config_genesis(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t system_opts, uint8_t force_region); genesis_context *alloc_config_genesis_cdboot(system_media *media, uint32_t system_opts, uint8_t force_region); +genesis_context* alloc_config_pico(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t ym_opts, uint8_t force_region); void genesis_serialize(genesis_context *gen, serialize_buffer *buf, uint32_t m68k_pc, uint8_t all); void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen); void gen_update_refresh_free_access(m68k_context *context); diff -r 8b948cf23557 -r 65c2e4d990cc system.c --- a/system.c Thu Jan 25 22:18:46 2024 -0800 +++ b/system.c Sat Feb 03 18:32:41 2024 -0800 @@ -14,6 +14,16 @@ system_type detect_system_type(system_media *media) { + static char *pico_names[] = { + "SEGA PICO", "SEGATOYS PICO", "SEGA TOYS PICO", "SAMSUNG PICO", + "SEGA IAC", "IMA IKUNOUJYUKU", "IMA IKUNOJYUKU" + }; + static const int num_pico = sizeof(pico_names)/sizeof(*pico_names); + for (int i = 0; i < num_pico; i++) { + if (safe_cmp(pico_names[i], 0x100, media->buffer, media->size)) { + return SYSTEM_PICO; + } + } if (safe_cmp("SEGA", 0x100, media->buffer, media->size)) { //TODO: support other bootable identifiers if (safe_cmp("SEGADISCSYSTEM", 0, media->buffer, media->size)) { @@ -105,6 +115,8 @@ #endif case SYSTEM_MEDIA_PLAYER: return &(alloc_media_player(media, opts))->header; + case SYSTEM_PICO: + return &(alloc_config_pico(media->buffer, media->size, lock_on, lock_on_size, opts, force_region))->header; default: return NULL; } diff -r 8b948cf23557 -r 65c2e4d990cc system.h --- a/system.h Thu Jan 25 22:18:46 2024 -0800 +++ b/system.h Sat Feb 03 18:32:41 2024 -0800 @@ -17,7 +17,8 @@ SYSTEM_SMS_PLAYER, SYSTEM_JAGUAR, SYSTEM_MEDIA_PLAYER, - SYSTEM_COLECOVISION + SYSTEM_COLECOVISION, + SYSTEM_PICO } system_type; typedef enum {