# HG changeset patch # User Michael Pavone # Date 1628180973 25200 # Node ID 638eb2d256967fe19d0376d80c4c8fa33ab0bb5f # Parent 0d5f88e53dca5aac531bb2a251520fbf5d197f86# Parent a61b47d5489ee0c910aba9742e35d692b598e141 Merge from default diff -r 0d5f88e53dca -r 638eb2d25696 Makefile --- a/Makefile Sun May 10 00:16:00 2020 -0700 +++ b/Makefile Thu Aug 05 09:29:33 2021 -0700 @@ -101,7 +101,8 @@ ifeq ($(OS),Darwin) SDL_INCLUDE_PATH:=Frameworks/SDL2.framework/Headers -LDFLAGS+= -FFrameworks -framework SDL2 -framework OpenGL -framework AppKit +CFLAGS+= -mmacosx-version-min=10.10 +LDFLAGS+= -FFrameworks -framework SDL2 -framework OpenGL -framework AppKit -mmacosx-version-min=10.10 FIXUP:=install_name_tool -change @rpath/SDL2.framework/Versions/A/SDL2 @executable_path/Frameworks/SDL2.framework/Versions/A/SDL2 else SDL_INCLUDE_PATH:=sdl/include @@ -222,7 +223,7 @@ LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.o xband.o realtec.o \ i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \ - $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o rom.db.o + $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o rom.db.o gen_player.o $(LIBZOBJS) ifdef NONUKLEAR CFLAGS+= -DDISABLE_NUKLEAR @@ -302,7 +303,7 @@ ar rcs libemu68k.a $(M68KOBJS) $(TRANSOBJS) trans : trans.o serialize.o $(M68KOBJS) $(TRANSOBJS) util.o - $(CC) -o trans trans.o $(M68KOBJS) $(TRANSOBJS) util.o $(OPT) + $(CC) -o $@ $^ $(OPT) transz80 : transz80.o $(Z80OBJS) $(TRANSOBJS) $(CC) -o transz80 transz80.o $(Z80OBJS) $(TRANSOBJS) @@ -371,6 +372,9 @@ %.bin : %.s68 vasmm68k_mot -Fbin -m68000 -no-opt -spaces -o $@ -L $@.list $< +%.md : %.s68 + vasmm68k_mot -Fbin -m68000 -no-opt -spaces -o $@ -L $@.list $< + %.bin : %.sz8 vasmz80_mot -Fbin -spaces -o $@ $< res.o : blastem.rc @@ -383,6 +387,7 @@ font.tiles : font.png menu.bin : font_interlace_variable.tiles arrow.tiles cursor.tiles button.tiles font.tiles +tmss.md : font.tiles clean : rm -rf $(ALL) trans ztestrun ztestgen *.o nuklear_ui/*.o zlib/*.o diff -r 0d5f88e53dca -r 638eb2d25696 backend.c --- a/backend.c Sun May 10 00:16:00 2020 -0700 +++ b/backend.c Thu Aug 05 09:29:33 2021 -0700 @@ -58,13 +58,23 @@ if (size_sum) { *size_sum = 0; } + uint32_t minsize; + if (flags == MMAP_CODE) { + minsize = 1 << (opts->ram_flags_shift + 3); + } else { + minsize = 0; + } address &= opts->address_mask; for (memmap_chunk const *cur = opts->memmap, *end = opts->memmap + opts->memmap_chunks; cur != end; cur++) { if (address >= cur->start && address < cur->end) { return cur; } else if (size_sum && (cur->flags & flags) == flags) { - *size_sum += chunk_size(opts, cur); + uint32_t size = chunk_size(opts, cur); + if (size < minsize) { + size = minsize; + } + *size_sum += size; } } return NULL; @@ -271,13 +281,15 @@ uint32_t ram_size(cpu_options *opts) { uint32_t size = 0; + uint32_t minsize = 1 << (opts->ram_flags_shift + 3); for (int i = 0; i < opts->memmap_chunks; i++) { if (opts->memmap[i].flags & MMAP_CODE) { - if (opts->memmap[i].mask == opts->address_mask) { - size += opts->memmap[i].end - opts->memmap[i].start; + uint32_t cursize = chunk_size(opts, opts->memmap + i); + if (cursize < minsize) { + size += minsize; } else { - size += opts->memmap[i].mask + 1; + size += cursize; } } } diff -r 0d5f88e53dca -r 638eb2d25696 backend_x86.c --- a/backend_x86.c Sun May 10 00:16:00 2020 -0700 +++ b/backend_x86.c Thu Aug 05 09:29:33 2021 -0700 @@ -299,10 +299,16 @@ retn(code); } if (memmap[chunk].flags & MMAP_CODE) { + uint32_t added_offset; if (memmap[chunk].mask == opts->address_mask) { - ram_flags_off += (memmap[chunk].end - memmap[chunk].start) / (1 << opts->ram_flags_shift) / 8; ; + added_offset = (memmap[chunk].end - memmap[chunk].start) / (1 << opts->ram_flags_shift) / 8; } else { - ram_flags_off += (memmap[chunk].mask + 1) / (1 << opts->ram_flags_shift) / 8;; + added_offset = (memmap[chunk].mask + 1) / (1 << opts->ram_flags_shift) / 8; + } + if (added_offset) { + ram_flags_off += added_offset; + } else { + ram_flags_off += 1; } } if (lb_jcc) { diff -r 0d5f88e53dca -r 638eb2d25696 blastem.c --- a/blastem.c Sun May 10 00:16:00 2020 -0700 +++ b/blastem.c Thu Aug 05 09:29:33 2021 -0700 @@ -281,12 +281,23 @@ return save_dir; } +const char *get_save_fname(uint8_t save_type) +{ + switch(save_type) + { + case SAVE_I2C: return "save.eeprom"; + case SAVE_NOR: return "save.nor"; + case SAVE_HBPT: return "save.hbpt"; + default: return "save.sram"; + } +} + void setup_saves(system_media *media, system_header *context) { static uint8_t persist_save_registered; rom_info *info = &context->info; char *save_dir = get_save_dir(info->is_save_lock_on ? media->chain : media); - char const *parts[] = {save_dir, PATH_SEP, info->save_type == SAVE_I2C ? "save.eeprom" : info->save_type == SAVE_NOR ? "save.nor" : "save.sram"}; + char const *parts[] = {save_dir, PATH_SEP, get_save_fname(info->save_type)}; free(save_filename); save_filename = alloc_concat_m(3, parts); if (info->is_save_lock_on) { diff -r 0d5f88e53dca -r 638eb2d25696 build_release --- a/build_release Sun May 10 00:16:00 2020 -0700 +++ b/build_release Thu Aug 05 09:29:33 2021 -0700 @@ -32,7 +32,7 @@ make PORTABLE=1 clean all SDLDLLPATH=sdl/i686-w64-mingw32/bin fi -make menu.bin +make menu.bin tmss.md if [ $OS = "Windows" -o $OS = "Win64" ]; then binaries="dis.exe zdis.exe vgmplay.exe blastem.exe $SDLDLLPATH/SDL2.dll" verstr=`sed -E -n 's/^[^B]+BLASTEM_VERSION "([^"]+)"/blastem \1/p' blastem.c` @@ -47,7 +47,7 @@ verstr=`./blastem -v` txt="" fi -binaries="$binaries menu.bin" +binaries="$binaries menu.bin tmss.md" ver=`echo $verstr | awk '/blastem/ { gsub(/\r/, "", $2); print $2 }'` if [ $OS = "Windows" ]; then suffix='-win32' diff -r 0d5f88e53dca -r 638eb2d25696 config.c --- a/config.c Sun May 10 00:16:00 2020 -0700 +++ b/config.c Thu Aug 05 09:29:33 2021 -0700 @@ -298,6 +298,21 @@ persist_config_at(config, config, "blastem.cfg"); } +void delete_custom_config_at(char *fname) +{ + char *confpath = path_append(get_exe_dir(), fname); + delete_file(confpath); + free(confpath); + confpath = path_append(get_config_dir(), fname); + delete_file(confpath); + free(confpath); +} + +void delete_custom_config(void) +{ + delete_custom_config_at("blastem.cfg"); +} + char **get_extension_list(tern_node *config, uint32_t *num_exts_out) { char *ext_filter = strdup(tern_find_path_default(config, "ui\0extensions\0", (tern_val){.ptrval = "bin gen md smd sms gg"}, TVAL_PTR).ptrval); diff -r 0d5f88e53dca -r 638eb2d25696 config.h --- a/config.h Sun May 10 00:16:00 2020 -0700 +++ b/config.h Thu Aug 05 09:29:33 2021 -0700 @@ -16,6 +16,8 @@ uint8_t serialize_config_file(tern_node *config, char *path); void persist_config_at(tern_node *app_config, tern_node *to_save, char *fname); void persist_config(tern_node *config); +void delete_custom_config_at(char *fname); +void delete_custom_config(void); char **get_extension_list(tern_node *config, uint32_t *num_exts_out); uint32_t get_lowpass_cutoff(tern_node *config); tern_node *get_systems_config(void); diff -r 0d5f88e53dca -r 638eb2d25696 controller_info.c --- a/controller_info.c Sun May 10 00:16:00 2020 -0700 +++ b/controller_info.c Thu Aug 05 09:29:33 2021 -0700 @@ -62,7 +62,10 @@ static const char *variant_names[] = { "normal", "6b bumpers", - "6b right" + "6b right", + "3button", + "6button", + "8button" }; static void load_ctype_config(void) @@ -221,6 +224,15 @@ #endif } +void delete_controller_info(void) +{ + delete_custom_config_at("controller_types.cfg"); + loaded = 0; + tern_free(info_config); + info_config = NULL; + render_reset_mappings(); +} + char const *labels_xbox[] = { "A", "B", "X", "Y", "Back", NULL, "Start", "Click", "Click", "White", "Black", "LT", "RT" }; @@ -242,6 +254,12 @@ static char const *labels_genesis[] = { "A", "B", "X", "Y", NULL, NULL, "Start", NULL, NULL, "Z", "C", NULL, "Mode" }; +static char const *labels_genesis_3button[] = { + "A", "B", NULL, NULL, NULL, NULL, "Start", NULL, NULL, NULL, "C", NULL, "Mode" +}; +static char const *labels_genesis_8button[] = { + "A", "B", "X", "Y", "Mode", NULL, "Start", NULL, NULL, "Z", "C", "L", "R" +}; static char const *labels_saturn[] = { "A", "B", "X", "Y", NULL, NULL, "Start", NULL, NULL, "Z", "C", "LT", "RT" }; @@ -266,7 +284,13 @@ } } else { if (info->subtype == SUBTYPE_GENESIS) { - return labels_genesis; + if (info->variant == VARIANT_8BUTTON) { + return labels_genesis_8button; + } else if (info->variant == VARIANT_3BUTTON) { + return labels_genesis_3button; + } else { + return labels_genesis; + } } else { return labels_saturn; } diff -r 0d5f88e53dca -r 638eb2d25696 controller_info.h --- a/controller_info.h Sun May 10 00:16:00 2020 -0700 +++ b/controller_info.h Thu Aug 05 09:29:33 2021 -0700 @@ -30,6 +30,8 @@ VARIANT_NORMAL, VARIANT_6B_BUMPERS, //C and Z positions are RB and LB respectively VARIANT_6B_RIGHT, //C and Z positions are RT and RB respectively + VARIANT_3BUTTON, //3-button Gen/MD controller + VARIANT_8BUTTON, //Modern 8-button Gen/MD style controller (retro-bit, 8bitdo M30, etc.) VARIANT_NUM }; @@ -45,6 +47,7 @@ const char *get_axis_label(controller_info *info, int axis); void save_controller_info(int joystick, controller_info *info); void save_controller_mapping(int joystick, char *mapping_string); +void delete_controller_info(void); void controller_add_mappings(void); char *make_controller_type_key(controller_info *info); char *make_human_readable_type_name(controller_info *info); diff -r 0d5f88e53dca -r 638eb2d25696 cpu_dsl.py --- a/cpu_dsl.py Sun May 10 00:16:00 2020 -0700 +++ b/cpu_dsl.py Thu Aug 05 09:29:33 2021 -0700 @@ -899,7 +899,7 @@ else: param = parent.resolveLocal(param) or param if param in fieldVals: - param = fieldVals[index] + param = fieldVals[param] prog.meta[self.params[0]] = param elif self.op == 'dis': #TODO: Disassembler diff -r 0d5f88e53dca -r 638eb2d25696 event_log.c --- a/event_log.c Sun May 10 00:16:00 2020 -0700 +++ b/event_log.c Thu Aug 05 09:29:33 2021 -0700 @@ -10,6 +10,8 @@ #include #endif +#include +#include #include #include "event_log.h" #include "util.h" diff -r 0d5f88e53dca -r 638eb2d25696 gen_player.c --- a/gen_player.c Sun May 10 00:16:00 2020 -0700 +++ b/gen_player.c Thu Aug 05 09:29:33 2021 -0700 @@ -1,7 +1,14 @@ +#include #include "gen_player.h" #include "event_log.h" #include "render.h" +#define MCLKS_NTSC 53693175 +#define MCLKS_PAL 53203395 +#define MCLKS_PER_YM 7 +#define MCLKS_PER_Z80 15 +#define MCLKS_PER_PSG (MCLKS_PER_Z80*16) + #ifdef IS_LIB #define MAX_SOUND_CYCLES (MCLKS_PER_YM*NUM_OPERATORS*6*4) #else @@ -103,7 +110,9 @@ { gen_player *player = (gen_player *)sys; if (player->reader.socket) { +#ifndef IS_LIB render_create_thread(&player->thread, "player", thread_main, player); +#endif } else { run(player); } @@ -121,12 +130,6 @@ reader_send_gamepad_event(&player->reader, gamepad_num, button, 0); } -#define MCLKS_NTSC 53693175 -#define MCLKS_PAL 53203395 -#define MCLKS_PER_YM 7 -#define MCLKS_PER_Z80 15 -#define MCLKS_PER_PSG (MCLKS_PER_Z80*16) - static void config_common(gen_player *player) { uint8_t vid_std = load_int8(&player->reader.buffer); diff -r 0d5f88e53dca -r 638eb2d25696 gen_player.h --- a/gen_player.h Sun May 10 00:16:00 2020 -0700 +++ b/gen_player.h Thu Aug 05 09:29:33 2021 -0700 @@ -14,7 +14,9 @@ vdp_context *vdp; ym2612_context *ym; psg_context *psg; +#ifndef IS_LIB render_thread thread; +#endif event_reader reader; } gen_player; diff -r 0d5f88e53dca -r 638eb2d25696 gen_x86.c --- a/gen_x86.c Sun May 10 00:16:00 2020 -0700 +++ b/gen_x86.c Thu Aug 05 09:29:33 2021 -0700 @@ -130,7 +130,7 @@ X86_R13, X86_R14, X86_R15 -} x86_regs_enc; +}; char * x86_reg_names[] = { #ifdef X86_64 diff -r 0d5f88e53dca -r 638eb2d25696 gen_x86.h --- a/gen_x86.h Sun May 10 00:16:00 2020 -0700 +++ b/gen_x86.h Thu Aug 05 09:29:33 2021 -0700 @@ -30,7 +30,7 @@ R13, R14, R15 -} x86_regs; +}; enum { CC_O = 0, @@ -51,14 +51,14 @@ CC_GE, CC_LE, CC_G -} x86_cc; +}; enum { SZ_B = 0, SZ_W, SZ_D, SZ_Q -} x86_size; +}; #ifdef X86_64 #define SZ_PTR SZ_Q @@ -85,7 +85,7 @@ MODE_REG_DIRECT = 0xC0, //"phony" mode MODE_IMMED = 0xFF -} x86_modes; +}; void rol_ir(code_info *code, uint8_t val, uint8_t dst, uint8_t size); void ror_ir(code_info *code, uint8_t val, uint8_t dst, uint8_t size); diff -r 0d5f88e53dca -r 638eb2d25696 genesis.c --- a/genesis.c Sun May 10 00:16:00 2020 -0700 +++ b/genesis.c Thu Aug 05 09:29:33 2021 -0700 @@ -104,6 +104,16 @@ 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); + } + cart_serialize(&gen->header, buf); } } @@ -173,7 +183,16 @@ gen->z80_bank_reg = load_int16(buf) & 0x1FF; } +static void tmss_deserialize(deserialize_buffer *buf, void *vgen) +{ + genesis_context *gen = vgen; + gen->tmss = load_int8(buf); + load_buffer16(buf, gen->tmss_lock, 2); +} + static void adjust_int_cycle(m68k_context * context, vdp_context * v_context); +static void check_tmss_lock(genesis_context *gen); +static void toggle_tmss_rom(genesis_context *gen); void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen) { register_section_handler(buf, (section_handler){.fun = m68k_deserialize, .data = gen->m68k}, SECTION_68000); @@ -188,10 +207,26 @@ 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); + uint8_t tmss_old = gen->tmss; + gen->tmss = 0xFF; while (buf->cur_pos < buf->size) { load_section(buf); } + if (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 + gen->tmss_lock[0] = 0x5345; + gen->tmss_lock[1] = 0x4741; + gen->tmss = 1; + } + if (gen->tmss != tmss_old) { + toggle_tmss_rom(gen); + } + check_tmss_lock(gen); + } update_z80_bank_pointer(gen); adjust_int_cycle(gen->m68k, gen->vdp); free(buf->handlers); @@ -242,13 +277,14 @@ context->sync_cycle = context->current_cycle + gen->max_cycles; } context->int_cycle = CYCLE_NEVER; - if ((context->status & 0x7) < 6) { + uint8_t mask = context->status & 0x7; + if (mask < 6) { uint32_t next_vint = vdp_next_vint(v_context); if (next_vint != CYCLE_NEVER) { context->int_cycle = next_vint; context->int_num = 6; } - if ((context->status & 0x7) < 4) { + if (mask < 4) { uint32_t next_hint = vdp_next_hint(v_context); if (next_hint != CYCLE_NEVER) { next_hint = next_hint < context->current_cycle ? context->current_cycle : next_hint; @@ -258,6 +294,21 @@ } } + if (mask < 2 && (v_context->regs[REG_MODE_3] & BIT_EINT_EN)) { + 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); + uint32_t next_eint = next_eint_port0 < next_eint_port1 + ? (next_eint_port0 < next_eint_port2 ? next_eint_port0 : next_eint_port2) + : (next_eint_port1 < next_eint_port2 ? next_eint_port1 : next_eint_port2); + if (next_eint != CYCLE_NEVER) { + next_eint = next_eint < context->current_cycle ? context->current_cycle : next_eint; + if (next_eint < context->int_cycle) { + context->int_cycle = next_eint; + context->int_num = 2; + } + } + } } } if (context->int_cycle > context->current_cycle && context->int_pending == INT_PENDING_SR_CHANGE) { @@ -274,7 +325,7 @@ } context->target_cycle = context->int_cycle < context->sync_cycle ? context->int_cycle : context->sync_cycle; - if (context->should_return) { + if (context->should_return || gen->header.enter_debugger) { context->target_cycle = context->current_cycle; } else if (context->target_cycle < context->current_cycle) { //Changes to SR can result in an interrupt cycle that's in the past @@ -389,6 +440,9 @@ sync_z80(z_context, mclks); sync_sound(gen, mclks); vdp_run_context(v_context, mclks); + io_run(gen->io.ports, mclks); + io_run(gen->io.ports + 1, mclks); + io_run(gen->io.ports + 2, mclks); if (mclks >= gen->reset_cycle) { gen->reset_requested = 1; context->should_return = 1; @@ -451,7 +505,11 @@ #ifndef NEW_CORE if (gen->header.enter_debugger) { gen->header.enter_debugger = 0; - debugger(context, address); + if (gen->header.debugger_type == DEBUGGER_NATIVE) { + debugger(context, address); + } else { + gdb_debug_enter(context, address); + } } #endif #ifdef NEW_CORE @@ -512,6 +570,10 @@ if (vdp_port & 0x2700E0) { fatal_error("machine freeze due to write to address %X\n", 0xC00000 | vdp_port); } + genesis_context * gen = context->system; + if (!gen->vdp_unlocked) { + fatal_error("machine freeze due to VDP write to %X without TMSS unlock\n", 0xC00000 | vdp_port); + } vdp_port &= 0x1F; //printf("vdp_port write: %X, value: %X, cycle: %d\n", vdp_port, value, context->current_cycle); #ifdef REFRESH_EMULATION @@ -524,7 +586,6 @@ } #endif sync_components(context, 0); - genesis_context * gen = context->system; vdp_context *v_context = gen->vdp; uint32_t before_cycle = v_context->cycles; if (vdp_port < 0x10) { @@ -601,7 +662,7 @@ vdp_test_port_write(gen->vdp, value); } #ifdef REFRESH_EMULATION - last_sync_cycle -= 4; + last_sync_cycle -= 4 * MCLKS_PER_68K; //refresh may have happened while we were waiting on the VDP, //so advance refresh_counter but don't add any delays if (vdp_port >= 4 && vdp_port < 8 && v_context->cycles != before_cycle) { @@ -653,6 +714,10 @@ if (vdp_port & 0x2700E0) { fatal_error("machine freeze due to read from address %X\n", 0xC00000 | vdp_port); } + genesis_context *gen = context->system; + if (!gen->vdp_unlocked) { + fatal_error("machine freeze due to VDP read from %X without TMSS unlock\n", 0xC00000 | vdp_port); + } vdp_port &= 0x1F; uint16_t value; #ifdef REFRESH_EMULATION @@ -664,27 +729,15 @@ last_sync_cycle = context->current_cycle; } #endif - genesis_context *gen = context->system; + sync_components(context, 0); vdp_context * v_context = gen->vdp; + uint32_t before_cycle = v_context->cycles; if (vdp_port < 0x10) { if (vdp_port < 4) { - sync_components(context, 0); - uint32_t before_cycle = v_context->cycles; value = vdp_data_port_read(v_context); - if (v_context->cycles != before_cycle) { - //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); - context->current_cycle = v_context->cycles; - //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->z80, v_context->cycles); - gen->bus_busy = 0; - } } else if(vdp_port < 8) { - vdp_run_context(v_context, context->current_cycle); value = vdp_control_port_read(v_context); } else { - vdp_run_context(v_context, context->current_cycle); value = vdp_hv_counter_read(v_context); //printf("HV Counter: %X at cycle %d\n", value, v_context->cycles); } @@ -693,8 +746,17 @@ } else { value = get_open_bus_value(&gen->header); } + if (v_context->cycles != before_cycle) { + //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); + context->current_cycle = v_context->cycles; + //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->z80, v_context->cycles); + gen->bus_busy = 0; + } #ifdef REFRESH_EMULATION - last_sync_cycle -= 4; + last_sync_cycle -= 4 * MCLKS_PER_68K; //refresh may have happened while we were waiting on the VDP, //so advance refresh_counter but don't add any delays refresh_counter += (context->current_cycle - last_sync_cycle); @@ -755,6 +817,13 @@ static m68k_context * io_write(uint32_t location, m68k_context * context, uint8_t value) { genesis_context * gen = context->system; +#ifdef REFRESH_EMULATION + //do refresh check here so we can avoid adding a penalty for a refresh that happens during an IO area access + refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - last_sync_cycle; + context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL)); + refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL); + last_sync_cycle = context->current_cycle - 4*MCLKS_PER_68K; +#endif if (location < 0x10000) { //Access to Z80 memory incurs a one 68K cycle wait state context->current_cycle += MCLKS_PER_68K; @@ -808,7 +877,7 @@ io_control_write(gen->io.ports+2, value, context->current_cycle); break; case 0x7: - gen->io.ports[0].serial_out = value; + io_tx_write(gen->io.ports, value, context->current_cycle); break; case 0x8: case 0xB: @@ -816,19 +885,20 @@ //serial input port is not writeable break; case 0x9: + io_sctrl_write(gen->io.ports, value, context->current_cycle); gen->io.ports[0].serial_ctrl = value; break; case 0xA: - gen->io.ports[1].serial_out = value; + io_tx_write(gen->io.ports + 1, value, context->current_cycle); break; case 0xC: - gen->io.ports[1].serial_ctrl = value; + io_sctrl_write(gen->io.ports + 1, value, context->current_cycle); break; case 0xD: - gen->io.ports[2].serial_out = value; + io_tx_write(gen->io.ports + 2, value, context->current_cycle); break; case 0xF: - gen->io.ports[2].serial_ctrl = value; + io_sctrl_write(gen->io.ports + 2, value, context->current_cycle); break; } } else { @@ -879,6 +949,11 @@ } } } +#ifdef REFRESH_EMULATION + //no refresh delays during IO access + refresh_counter += context->current_cycle - last_sync_cycle; + refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL); +#endif return context; } @@ -902,6 +977,13 @@ { uint8_t value; genesis_context *gen = context->system; +#ifdef REFRESH_EMULATION + //do refresh check here so we can avoid adding a penalty for a refresh that happens during an IO area access + refresh_counter += context->current_cycle - 4*MCLKS_PER_68K - last_sync_cycle; + context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL)); + refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL); + last_sync_cycle = context->current_cycle - 4*MCLKS_PER_68K; +#endif if (location < 0x10000) { //Access to Z80 memory incurs a one 68K cycle wait state context->current_cycle += MCLKS_PER_68K; @@ -952,28 +1034,28 @@ value = gen->io.ports[0].serial_out; break; case 0x8: - value = gen->io.ports[0].serial_in; + value = io_rx_read(gen->io.ports, context->current_cycle); break; case 0x9: - value = gen->io.ports[0].serial_ctrl; + value = io_sctrl_read(gen->io.ports, context->current_cycle); break; case 0xA: value = gen->io.ports[1].serial_out; break; case 0xB: - value = gen->io.ports[1].serial_in; + value = io_rx_read(gen->io.ports + 1, context->current_cycle); break; case 0xC: - value = gen->io.ports[1].serial_ctrl; + value = io_sctrl_read(gen->io.ports, context->current_cycle); break; case 0xD: value = gen->io.ports[2].serial_out; break; case 0xE: - value = gen->io.ports[2].serial_in; + value = io_rx_read(gen->io.ports + 1, context->current_cycle); break; case 0xF: - value = gen->io.ports[2].serial_ctrl; + value = io_sctrl_read(gen->io.ports, context->current_cycle); break; default: value = get_open_bus_value(&gen->header) >> 8; @@ -997,6 +1079,11 @@ } } } +#ifdef REFRESH_EMULATION + //no refresh delays during IO access + refresh_counter += context->current_cycle - last_sync_cycle; + refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL); +#endif return value; } @@ -1109,7 +1196,7 @@ { m68k_context *context = vcontext; genesis_context *gen = context->system; - if ((location >= 0xA13000 && location < 0xA13100) || (location >= 0xA12000 && location < 0xA12100)) { + if (location < 0x800000 || (location >= 0xA13000 && location < 0xA13100) || (location >= 0xA12000 && location < 0xA12100)) { //Only called if the cart/exp doesn't have a more specific handler for this region return get_open_bus_value(&gen->header); } else if (location == 0xA14000 || location == 0xA14002) { @@ -1142,6 +1229,23 @@ } } +static void check_tmss_lock(genesis_context *gen) +{ + gen->vdp_unlocked = gen->tmss_lock[0] == 0x5345 && gen->tmss_lock[1] == 0x4741; +} + +static void toggle_tmss_rom(genesis_context *gen) +{ + m68k_context *context = gen->m68k; + for (int i = 0; i < NUM_MEM_AREAS; i++) + { + uint16_t *tmp = context->mem_pointers[i]; + context->mem_pointers[i] = gen->tmss_pointers[i]; + gen->tmss_pointers[i] = tmp; + } + m68k_invalidate_code_range(context, 0, 0x400000); +} + static void *unused_write(uint32_t location, void *vcontext, uint16_t value) { m68k_context *context = vcontext; @@ -1149,9 +1253,16 @@ uint8_t has_tmss = gen->version_reg & 0xF; if (has_tmss && (location == 0xA14000 || location == 0xA14002)) { gen->tmss_lock[location >> 1 & 1] = value; + check_tmss_lock(gen); } else if (has_tmss && location == 0xA14100) { - //TODO: implement TMSS control register - } else if (location < 0xA12000 || location >= 0xA13100 || (location >= 0xA12100 && location < 0xA13000)) { + value &= 1; + if (gen->tmss != value) { + gen->tmss = value; + toggle_tmss_rom(gen); + } + } else if (location < 0x800000 || (location >= 0xA13000 && location < 0xA13100) || (location >= 0xA12000 && location < 0xA12100)) { + //these writes are ignored when no relevant hardware is present + } else { fatal_error("Machine freeze due to unmapped write to %X\n", location); } return vcontext; @@ -1171,9 +1282,18 @@ gen->tmss_lock[offset] &= 0xFF; gen->tmss_lock[offset] |= value << 8; } + check_tmss_lock(gen); } else if (has_tmss && (location == 0xA14100 || location == 0xA14101)) { - //TODO: implement TMSS control register - } else if (location < 0xA12000 || location >= 0xA13100 || (location >= 0xA12100 && location < 0xA13000)) { + if (location & 1) { + value &= 1; + if (gen->tmss != value) { + gen->tmss = value; + toggle_tmss_rom(gen); + } + } + } else if (location < 0x800000 || (location >= 0xA13000 && location < 0xA13100) || (location >= 0xA12000 && location < 0xA12100)) { + //these writes are ignored when no relevant hardware is present + } else { fatal_error("Machine freeze due to unmapped byte write to %X\n", location); } return vcontext; @@ -1528,6 +1648,136 @@ gen->header.vgm_logging = 0; } +static void *tmss_rom_write_16(uint32_t address, void *context, uint16_t value) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss) { + return gen->tmss_write_16(address, context, value); + } + + return context; +} + +static void *tmss_rom_write_8(uint32_t address, void *context, uint8_t value) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss) { + return gen->tmss_write_8(address, context, value); + } + + return context; +} + +static uint16_t tmss_rom_read_16(uint32_t address, void *context) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss) { + return gen->tmss_read_16(address, context); + } + return ((uint16_t *)gen->tmss_buffer)[address >> 1]; +} + +static uint8_t tmss_rom_read_8(uint32_t address, void *context) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss) { + return gen->tmss_read_8(address, context); + } +#ifdef BLASTEM_BIG_ENDIAN + return gen->tmss_buffer[address]; +#else + return gen->tmss_buffer[address ^ 1]; +#endif +} + +static void *tmss_word_write_16(uint32_t address, void *context, uint16_t value) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss) { + address += gen->tmss_write_offset; + uint16_t *dest = get_native_pointer(address, (void **)m68k->mem_pointers, &m68k->options->gen); + *dest = value; + m68k_handle_code_write(address, m68k); + } + + return context; +} + +static void *tmss_word_write_8(uint32_t address, void *context, uint8_t value) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss) { + address += gen->tmss_write_offset; + uint8_t *dest = get_native_pointer(address & ~1, (void **)m68k->mem_pointers, &m68k->options->gen); +#ifdef BLASTEM_BIG_ENDIAN + dest[address & 1] = value; +#else + dest[address & 1 ^ 1] = value; +#endif + m68k_handle_code_write(address & ~1, m68k); + } + + return context; +} + +static void *tmss_odd_write_16(uint32_t address, void *context, uint16_t value) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss) { + memmap_chunk const *chunk = find_map_chunk(address + gen->tmss_write_offset, &m68k->options->gen, 0, NULL); + address >>= 1; + uint8_t *base = (uint8_t *)m68k->mem_pointers[chunk->ptr_index]; + base[address] = value; + } + return context; +} + +static void *tmss_odd_write_8(uint32_t address, void *context, uint8_t value) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss && (address & 1)) { + memmap_chunk const *chunk = find_map_chunk(address + gen->tmss_write_offset, &m68k->options->gen, 0, NULL); + address >>= 1; + uint8_t *base = (uint8_t *)m68k->mem_pointers[chunk->ptr_index]; + base[address] = value; + } + return context; +} + +static void *tmss_even_write_16(uint32_t address, void *context, uint16_t value) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss) { + memmap_chunk const *chunk = find_map_chunk(address + gen->tmss_write_offset, &m68k->options->gen, 0, NULL); + address >>= 1; + uint8_t *base = (uint8_t *)m68k->mem_pointers[chunk->ptr_index]; + base[address] = value >> 8; + } + return context; +} + +static void *tmss_even_write_8(uint32_t address, void *context, uint8_t value) +{ + m68k_context *m68k = context; + genesis_context *gen = m68k->system; + if (gen->tmss && !(address & 1)) { + memmap_chunk const *chunk = find_map_chunk(address + gen->tmss_write_offset, &m68k->options->gen, 0, NULL); + address >>= 1; + uint8_t *base = (uint8_t *)m68k->mem_pointers[chunk->ptr_index]; + base[address] = value; + } + return context; +} + genesis_context *alloc_init_genesis(rom_info *rom, void *main_rom, void *lock_on, uint32_t system_opts, uint8_t force_region) { static memmap_chunk z80_map[] = { @@ -1569,6 +1819,8 @@ uint8_t tmss = !strcmp(tern_find_ptr_default(model, "tmss", "off"), "on"); if (tmss) { gen->version_reg |= 1; + } else { + gen->vdp_unlocked = 1; } uint8_t max_vsram = !strcmp(tern_find_ptr_default(model, "vsram", "40"), "64"); @@ -1664,13 +1916,145 @@ gen->save_storage = NULL; } + gen->mapper_start_index = rom->mapper_start_index; + //This must happen before we generate memory access functions in init_m68k_opts + uint8_t next_ptr_index = 0; + uint32_t tmss_min_alloc = 16 * 1024; for (int i = 0; i < rom->map_chunks; i++) { if (rom->map[i].start == 0xE00000) { rom->map[i].buffer = gen->work_ram; - break; + if (!tmss) { + break; + } + } + if (rom->map[i].flags & MMAP_PTR_IDX && rom->map[i].ptr_index >= next_ptr_index) { + next_ptr_index = rom->map[i].ptr_index + 1; + } + if (rom->map[i].start < 0x400000 && rom->map[i].read_16 != unused_read) { + uint32_t highest_offset = (rom->map[i].end & rom->map[i].mask) + 1; + if (highest_offset > tmss_min_alloc) { + tmss_min_alloc = highest_offset; + } + } + } + if (tmss) { + char *tmss_path = tern_find_path_default(config, "system\0tmss_path\0", (tern_val){.ptrval = "tmss.md"}, TVAL_PTR).ptrval; + uint8_t *buffer = malloc(tmss_min_alloc); + uint32_t tmss_size; + if (is_absolute_path(tmss_path)) { + FILE *f = fopen(tmss_path, "rb"); + if (!f) { + fatal_error("Configured to use a model with TMSS, but failed to load the TMSS ROM from %s\n", tmss_path); + } + tmss_size = fread(buffer, 1, tmss_min_alloc, f); + fclose(f); + } else { + char *tmp = read_bundled_file(tmss_path, &tmss_size); + if (!tmp) { + fatal_error("Configured to use a model with TMSS, but failed to load the TMSS ROM from %s\n", tmss_path); + } + memcpy(buffer, tmp, tmss_size); + free(tmp); + } + for (uint32_t padded = nearest_pow2(tmss_size); tmss_size < padded; tmss_size++) + { + buffer[tmss_size] = 0xFF; + } +#ifndef BLASTEM_BIG_ENDIAN + byteswap_rom(tmss_size, (uint16_t *)buffer); +#endif + //mirror TMSS ROM until we fill up to tmss_min_alloc + for (uint32_t dst = tmss_size; dst < tmss_min_alloc; dst += tmss_size) + { + memcpy(buffer + dst, buffer, dst + tmss_size > tmss_min_alloc ? tmss_min_alloc - dst : tmss_size); } + //modify mappings for ROM space to point to the TMSS ROM and fixup flags to allow switching back and forth + //WARNING: This code makes some pretty big assumptions about the kinds of map chunks it will encounter + for (int i = 0; i < rom->map_chunks; i++) + { + if (rom->map[i].start < 0x400000 && rom->map[i].read_16 != unused_read) { + if (rom->map[i].flags == MMAP_READ) { + //Normal ROM + rom->map[i].flags |= MMAP_PTR_IDX | MMAP_CODE; + rom->map[i].ptr_index = next_ptr_index++; + if (rom->map[i].ptr_index >= NUM_MEM_AREAS) { + fatal_error("Too many memmap chunks with MMAP_PTR_IDX after TMSS remap\n"); + } + gen->tmss_pointers[rom->map[i].ptr_index] = rom->map[i].buffer; + rom->map[i].buffer = buffer + (rom->map[i].start & ~rom->map[i].mask & (tmss_size - 1)); + } else if (rom->map[i].flags & MMAP_PTR_IDX) { + //Sega mapper page or multi-game mapper + gen->tmss_pointers[rom->map[i].ptr_index] = rom->map[i].buffer; + rom->map[i].buffer = buffer + (rom->map[i].start & ~rom->map[i].mask & (tmss_size - 1)); + if (rom->map[i].write_16) { + if (!gen->tmss_write_16) { + gen->tmss_write_16 = rom->map[i].write_16; + gen->tmss_write_8 = rom->map[i].write_8; + rom->map[i].write_16 = tmss_rom_write_16; + rom->map[i].write_8 = tmss_rom_write_8; + } else if (gen->tmss_write_16 == rom->map[i].write_16) { + rom->map[i].write_16 = tmss_rom_write_16; + rom->map[i].write_8 = tmss_rom_write_8; + } else { + warning("Chunk starting at %X has a write function, but we've already stored a different one for TMSS remap\n", rom->map[i].start); + } + } + } else if ((rom->map[i].flags & (MMAP_READ | MMAP_WRITE)) == (MMAP_READ | MMAP_WRITE)) { + //RAM or SRAM + rom->map[i].flags |= MMAP_PTR_IDX; + rom->map[i].ptr_index = next_ptr_index++; + gen->tmss_pointers[rom->map[i].ptr_index] = rom->map[i].buffer; + rom->map[i].buffer = buffer + (rom->map[i].start & ~rom->map[i].mask & (tmss_size - 1)); + if (!gen->tmss_write_offset || gen->tmss_write_offset == rom->map[i].start) { + gen->tmss_write_offset = rom->map[i].start; + rom->map[i].flags &= ~MMAP_WRITE; + if (rom->map[i].flags & MMAP_ONLY_ODD) { + rom->map[i].write_16 = tmss_odd_write_16; + rom->map[i].write_8 = tmss_odd_write_8; + } else if (rom->map[i].flags & MMAP_ONLY_EVEN) { + rom->map[i].write_16 = tmss_even_write_16; + rom->map[i].write_8 = tmss_even_write_8; + } else { + rom->map[i].write_16 = tmss_word_write_16; + rom->map[i].write_8 = tmss_word_write_8; + } + } else { + warning("Could not remap writes for chunk starting at %X for TMSS because write_offset is %X\n", rom->map[i].start, gen->tmss_write_offset); + } + } else if (rom->map[i].flags & MMAP_READ_CODE) { + //NOR flash + rom->map[i].flags |= MMAP_PTR_IDX; + rom->map[i].ptr_index = next_ptr_index++; + if (rom->map[i].ptr_index >= NUM_MEM_AREAS) { + fatal_error("Too many memmap chunks with MMAP_PTR_IDX after TMSS remap\n"); + } + gen->tmss_pointers[rom->map[i].ptr_index] = rom->map[i].buffer; + rom->map[i].buffer = buffer + (rom->map[i].start & ~rom->map[i].mask & (tmss_size - 1)); + if (!gen->tmss_write_16) { + gen->tmss_write_16 = rom->map[i].write_16; + gen->tmss_write_8 = rom->map[i].write_8; + gen->tmss_read_16 = rom->map[i].read_16; + gen->tmss_read_8 = rom->map[i].read_8; + rom->map[i].write_16 = tmss_rom_write_16; + rom->map[i].write_8 = tmss_rom_write_8; + rom->map[i].read_16 = tmss_rom_read_16; + rom->map[i].read_8 = tmss_rom_read_8; + } else if (gen->tmss_write_16 == rom->map[i].write_16) { + rom->map[i].write_16 = tmss_rom_write_16; + rom->map[i].write_8 = tmss_rom_write_8; + rom->map[i].read_16 = tmss_rom_read_16; + rom->map[i].read_8 = tmss_rom_read_8; + } else { + warning("Chunk starting at %X has a write function, but we've already stored a different one for TMSS remap\n", rom->map[i].start); + } + } else { + warning("Didn't remap chunk starting at %X for TMSS because it has flags %X\n", rom->map[i].start, rom->map[i].flags); + } + } + } + gen->tmss_buffer = buffer; } m68k_options *opts = malloc(sizeof(m68k_options)); diff -r 0d5f88e53dca -r 638eb2d25696 genesis.h --- a/genesis.h Sun May 10 00:16:00 2020 -0700 +++ b/genesis.h Thu Aug 05 09:29:33 2021 -0700 @@ -39,6 +39,12 @@ uint8_t *save_storage; void *mapper_temp; eeprom_map *eeprom_map; + write_16_fun tmss_write_16; + write_8_fun tmss_write_8; + read_16_fun tmss_read_16; + read_8_fun tmss_read_8; + uint16_t *tmss_pointers[NUM_MEM_AREAS]; + uint8_t *tmss_buffer; uint8_t *serialize_tmp; size_t serialize_size; uint32_t num_eeprom; @@ -54,6 +60,7 @@ uint32_t last_frame; uint32_t last_flush_cycle; uint32_t soft_flush_cycles; + uint32_t tmss_write_offset; uint8_t bank_regs[8]; uint16_t z80_bank_reg; uint16_t tmss_lock[2]; @@ -65,6 +72,7 @@ uint8_t bus_busy; uint8_t reset_requested; uint8_t tmss; + uint8_t vdp_unlocked; eeprom_state eeprom; nor_state nor; }; diff -r 0d5f88e53dca -r 638eb2d25696 images/genesis_6b.png Binary file images/genesis_6b.png has changed diff -r 0d5f88e53dca -r 638eb2d25696 images/wiiu.png Binary file images/wiiu.png has changed diff -r 0d5f88e53dca -r 638eb2d25696 io.c --- a/io.c Sun May 10 00:16:00 2020 -0700 +++ b/io.c Thu Aug 05 09:29:33 2021 -0700 @@ -39,7 +39,9 @@ "EA 4-way Play cable A", "EA 4-way Play cable B", "Sega Parallel Transfer Board", - "Generic Device" + "Generic Device", + "Generic Serial", + "Heartbeat Personal Trainer" }; #define GAMEPAD_TH0 0 @@ -58,6 +60,13 @@ IO_READ }; +enum { + HBPT_NEED_INIT, + HBPT_IDLE, + HBPT_CMD_PAYLOAD, + HBPT_REPLY +}; + typedef struct { uint8_t states[2], value; } gp_button_def; @@ -86,6 +95,9 @@ if (port->device_type < IO_MOUSE && port->device.pad.gamepad_num == gamepad_num) { return port; } + if (port->device_type == IO_HEARTBEAT_TRAINER && port->device.heartbeat_trainer.device_num == gamepad_num) { + return port; + } } return NULL; } @@ -210,8 +222,20 @@ return find_keyboard(io) != NULL; } +static void set_serial_clock(io_port *port) +{ + switch(port->serial_ctrl >> 6) + { + case 0: port->serial_divider = 11186; break; //4800 bps + case 1: port->serial_divider = 22372; break; //2400 bps + case 2: port->serial_divider = 44744; break; //1200 bps + case 3: port->serial_divider = 178976; break; //300 bps + } +} + void process_device(char * device_type, io_port * port) { + set_serial_clock(port); //assuming that the io_port struct has been zeroed if this is the first time this has been called if (!device_type) { @@ -235,6 +259,10 @@ port->device_type = IO_GAMEPAD6; } port->device.pad.gamepad_num = device_type[gamepad_len+2] - '0'; + } else if(startswith(device_type, "heartbeat_trainer.")) { + port->device_type = IO_HEARTBEAT_TRAINER; + port->device.heartbeat_trainer.nv_memory = NULL; + port->device.heartbeat_trainer.device_num = device_type[strlen("heartbeat_trainer.")] - '0'; } else if(startswith(device_type, "mouse")) { if (port->device_type != IO_MOUSE) { port->device_type = IO_MOUSE; @@ -272,6 +300,12 @@ port->device.stream.data_fd = -1; port->device.stream.listen_fd = -1; } + } else if(!strcmp(device_type, "serial")) { + if (port->device_type != IO_GENERIC_SERIAL) { + port->device_type = IO_GENERIC_SERIAL; + port->device.stream.data_fd = -1; + port->device.stream.listen_fd = -1; + } } } @@ -354,7 +388,7 @@ } } } - } else if (ports[i].device_type == IO_GENERIC && ports[i].device.stream.data_fd == -1) { + } else if (ports[i].device_type == IO_GENERIC || ports[i].device_type == IO_GENERIC_SERIAL && ports[i].device.stream.data_fd == -1) { char *sock_name = tern_find_path(config, "io\0socket\0", TVAL_PTR).ptrval; if (!sock_name) { @@ -392,6 +426,30 @@ #endif if (ports[i].device_type == IO_GAMEPAD3 || ports[i].device_type == IO_GAMEPAD6 || ports[i].device_type == IO_GAMEPAD2) { debug_message("IO port %s connected to gamepad #%d with type '%s'\n", io_name(i), ports[i].device.pad.gamepad_num, device_type_names[ports[i].device_type]); + } else if (ports[i].device_type == IO_HEARTBEAT_TRAINER) { + debug_message("IO port %s connected to Heartbeat Personal Trainer #%d\n", io_name(i), ports[i].device.heartbeat_trainer.device_num); + if (rom->save_type == SAVE_HBPT) { + ports[i].device.heartbeat_trainer.nv_memory = rom->save_buffer; + uint32_t page_size = 16; + for (; page_size < 128; page_size *= 2) + { + if (rom->save_size / page_size < 256) { + break; + } + } + ports[i].device.heartbeat_trainer.nv_page_size = page_size; + uint32_t num_pages = rom->save_size / page_size; + ports[i].device.heartbeat_trainer.nv_pages = num_pages < 256 ? num_pages : 255; + } else { + ports[i].device.heartbeat_trainer.nv_page_size = 16; + ports[i].device.heartbeat_trainer.nv_pages = 32; + size_t bufsize = + ports[i].device.heartbeat_trainer.nv_page_size * ports[i].device.heartbeat_trainer.nv_pages + + 5 + 8; + ports[i].device.heartbeat_trainer.nv_memory = malloc(bufsize); + memset(ports[i].device.heartbeat_trainer.nv_memory, 0xFF, bufsize); + } + ports[i].device.heartbeat_trainer.state = HBPT_NEED_INIT; } else { debug_message("IO port %s connected to device '%s'\n", io_name(i), device_type_names[ports[i].device_type]); } @@ -427,7 +485,6 @@ } } -uint32_t last_poll_cycle; void io_adjust_cycles(io_port * port, uint32_t current_cycle, uint32_t deduction) { /*uint8_t control = pad->control | 0x80; @@ -459,25 +516,80 @@ } } } - if (last_poll_cycle >= deduction) { - last_poll_cycle -= deduction; + if (port->transmit_end >= deduction) { + port->transmit_end -= deduction; } else { - last_poll_cycle = 0; + port->transmit_end = 0; + } + if (port->receive_end >= deduction) { + port->receive_end -= deduction; + } else { + port->receive_end = 0; + } + if (port->last_poll_cycle >= deduction) { + port->last_poll_cycle -= deduction; + } else { + port->last_poll_cycle = 0; } } #ifndef _WIN32 -static void wait_for_connection(io_port * port) +static void wait_for_connection(io_port *port) { if (port->device.stream.data_fd == -1) { - debug_message("Waiting for socket connection..."); + debug_message("Waiting for socket connection...\n"); port->device.stream.data_fd = accept(port->device.stream.listen_fd, NULL, NULL); fcntl(port->device.stream.data_fd, F_SETFL, O_NONBLOCK | O_RDWR); } } -static void service_pipe(io_port * port) +static void poll_for_connection(io_port *port) +{ + if (port->device.stream.data_fd == -1) + { + fcntl(port->device.stream.listen_fd, F_SETFL, O_NONBLOCK | O_RDWR); + port->device.stream.data_fd = accept(port->device.stream.listen_fd, NULL, NULL); + fcntl(port->device.stream.listen_fd, F_SETFL, O_RDWR); + if (port->device.stream.data_fd != -1) { + fcntl(port->device.stream.data_fd, F_SETFL, O_NONBLOCK | O_RDWR); + } + } +} + +static void write_serial_byte(io_port *port) +{ + fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR); + for (int sent = 0; sent != sizeof(port->serial_transmitting);) + { + sent = send(port->device.stream.data_fd, &port->serial_transmitting, sizeof(port->serial_transmitting), 0); + if (sent < 0) { + close(port->device.stream.data_fd); + port->device.stream.data_fd = -1; + wait_for_connection(port); + fcntl(port->device.stream.data_fd, F_SETFL, O_RDWR); + } + } + fcntl(port->device.stream.data_fd, F_SETFL, O_NONBLOCK | O_RDWR); +} + +static void read_serial_byte(io_port *port) +{ + poll_for_connection(port); + if (port->device.stream.data_fd == -1) { + return; + } + int read = recv(port->device.stream.data_fd, &port->serial_receiving, sizeof(port->serial_receiving), 0); + if (read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + close(port->device.stream.data_fd); + port->device.stream.data_fd = -1; + } + if (read > 0) { + port->receive_end = port->serial_cycle + 10 * port->serial_divider; + } +} + +static void service_pipe(io_port *port) { uint8_t value; int numRead = read(port->device.stream.data_fd, &value, sizeof(value)); @@ -568,6 +680,257 @@ } #endif +enum { + HBPT_UNKNOWN1 = 1, + HBPT_POLL, + HBPT_READ_PAGE = 5, + HBPT_WRITE_PAGE, + HBPT_READ_RTC, + HBPT_SET_RTC, + HBPT_GET_STATUS, + HBPT_ERASE_NVMEM, + HBPT_NVMEM_PARAMS, + HBPT_INIT +}; + +static void start_reply(io_port *port, uint8_t bytes, const uint8_t *src) +{ + port->device.heartbeat_trainer.remaining_bytes = bytes; + port->device.heartbeat_trainer.state = HBPT_REPLY; + port->device.heartbeat_trainer.cur_buffer = (uint8_t *)src; +} + +static void simple_reply(io_port *port, uint8_t value) +{ + port->device.heartbeat_trainer.param = value; + start_reply(port, 1, &port->device.heartbeat_trainer.param); +} + +static void expect_payload(io_port *port, uint8_t bytes, uint8_t *dst) +{ + port->device.heartbeat_trainer.remaining_bytes = bytes; + port->device.heartbeat_trainer.state = HBPT_CMD_PAYLOAD; + port->device.heartbeat_trainer.cur_buffer = dst; +} + +void hbpt_check_init(io_port *port) +{ + if (port->device.heartbeat_trainer.state == HBPT_NEED_INIT) { + port->device.heartbeat_trainer.rtc_base_timestamp = 0; + for (int i = 0; i < 8; i ++) + { + port->device.heartbeat_trainer.rtc_base_timestamp <<= 8; + port->device.heartbeat_trainer.rtc_base_timestamp |= port->device.heartbeat_trainer.nv_memory[i]; + } + memcpy(port->device.heartbeat_trainer.rtc_base, port->device.heartbeat_trainer.nv_memory + 8, 5); + if (port->device.heartbeat_trainer.rtc_base_timestamp == UINT64_MAX) { + //uninitialized save, set the appropriate status bit + port->device.heartbeat_trainer.status |= 1; + } + port->device.heartbeat_trainer.bpm = 60; + port->device.heartbeat_trainer.state = HBPT_IDLE; + } +} + +void hbpt_check_send_reply(io_port *port) +{ + if (port->device.heartbeat_trainer.state == HBPT_REPLY && !port->receive_end) { + port->serial_receiving = *(port->device.heartbeat_trainer.cur_buffer++); + port->receive_end = port->serial_cycle + 10 * port->serial_divider; + if (!--port->device.heartbeat_trainer.remaining_bytes) { + port->device.heartbeat_trainer.state = HBPT_IDLE; + } + } +} + +uint8_t is_leap_year(uint16_t year) +{ + if (year & 3) { + return 0; + } + if (year % 100) { + return 1; + } + if (year % 400) { + return 0; + } + return 1; +} + +uint8_t days_in_month(uint8_t month, uint16_t year) +{ + static uint8_t days_per_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if (month == 2 && is_leap_year(year)) { + return 29; + } + if (month > 12 || !month) { + return 30; + } + return days_per_month[month-1]; +} + +void hbpt_write_byte(io_port *port) +{ + hbpt_check_init(port); + uint8_t reply; + switch (port->device.heartbeat_trainer.state) + { + case HBPT_IDLE: + port->device.heartbeat_trainer.cmd = port->serial_transmitting; + switch (port->device.heartbeat_trainer.cmd) + { + case HBPT_UNKNOWN1: + start_reply(port, 11, NULL); + break; + case HBPT_POLL: + start_reply(port, 3, &port->device.heartbeat_trainer.bpm); + if (port->serial_cycle - port->last_poll_cycle > MIN_POLL_INTERVAL) { + process_events(); + port->last_poll_cycle = port->serial_cycle; + } + port->device.heartbeat_trainer.buttons = (port->input[GAMEPAD_TH0] << 2 & 0xC0) | (port->input[GAMEPAD_TH1] & 0x1F); + if (port->device.heartbeat_trainer.cadence && port->input[GAMEPAD_TH1] & 0x20) { + port->device.heartbeat_trainer.cadence--; + printf("Cadence: %d\n", port->device.heartbeat_trainer.cadence); + } else if (port->device.heartbeat_trainer.cadence < 255 && port->input[GAMEPAD_EXTRA] & 1) { + port->device.heartbeat_trainer.cadence++; + printf("Cadence: %d\n", port->device.heartbeat_trainer.cadence); + } + if (port->device.heartbeat_trainer.bpm && port->input[GAMEPAD_EXTRA] & 4) { + port->device.heartbeat_trainer.bpm--; + printf("Heart Rate: %d\n", port->device.heartbeat_trainer.bpm); + } else if (port->device.heartbeat_trainer.bpm < 255 && port->input[GAMEPAD_EXTRA] & 2) { + port->device.heartbeat_trainer.bpm++; + printf("Heart Rate: %d\n", port->device.heartbeat_trainer.bpm); + } + + break; + case HBPT_READ_PAGE: + case HBPT_WRITE_PAGE: + //strictly speaking for the write case, we want 1 + page size here + //but the rest of the payload goes to a different destination + expect_payload(port, 1, &port->device.heartbeat_trainer.param); + break; + case HBPT_READ_RTC: { + uint8_t *rtc = port->device.heartbeat_trainer.rtc_base; + start_reply(port, 5, rtc); + uint64_t now = time(NULL); + uint64_t delta = (now - port->device.heartbeat_trainer.rtc_base_timestamp + 30) / 60; + rtc[4] += delta % 60; + if (rtc[4] > 59) { + rtc[4] -= 60; + rtc[3]++; + } + delta /= 60; + if (delta) { + rtc[3] += delta % 24; + delta /= 24; + if (rtc[3] > 23) { + rtc[3] -= 24; + delta++; + } + if (delta) { + uint16_t year = rtc[0] < 81 ? 2000 + rtc[0] : 1900 + rtc[0]; + uint8_t days_cur_month = days_in_month(rtc[1], year); + while (delta + rtc[2] > days_cur_month) { + delta -= days_cur_month + 1 - rtc[2]; + rtc[2] = 1; + if (++rtc[1] == 13) { + rtc[1] = 1; + year++; + } + days_cur_month = days_in_month(rtc[1], year); + } + rtc[1] += delta; + rtc[0] = year % 100; + } + } + printf("RTC %02d-%02d-%02d %02d:%02d\n", rtc[0], rtc[1], rtc[2], rtc[3], rtc[4]); + port->device.heartbeat_trainer.rtc_base_timestamp = now; + break; + } + case HBPT_SET_RTC: + port->device.heartbeat_trainer.rtc_base_timestamp = time(NULL); + expect_payload(port, 5, port->device.heartbeat_trainer.rtc_base); + break; + case HBPT_GET_STATUS: + simple_reply(port, port->device.heartbeat_trainer.status); + break; + case HBPT_ERASE_NVMEM: + expect_payload(port, 1, &port->device.heartbeat_trainer.param); + break; + case HBPT_NVMEM_PARAMS: + start_reply(port, 2, &port->device.heartbeat_trainer.nv_page_size); + break; + case HBPT_INIT: + expect_payload(port, 19, NULL); + break; + default: + // it's unclear what these commands do as they are unused by Outback Joey + // just return 0 to indicate failure + simple_reply(port, 0); + } + break; + case HBPT_CMD_PAYLOAD: + if (port->device.heartbeat_trainer.cur_buffer) { + *(port->device.heartbeat_trainer.cur_buffer++) = port->serial_transmitting; + } + if (!--port->device.heartbeat_trainer.remaining_bytes) { + switch (port->device.heartbeat_trainer.cmd) + { + case HBPT_READ_PAGE: + case HBPT_WRITE_PAGE: + if ( + port->device.heartbeat_trainer.cmd == HBPT_WRITE_PAGE + && port->device.heartbeat_trainer.cur_buffer != &port->device.heartbeat_trainer.param + 1) { + simple_reply(port, 1); + break; + } + port->device.heartbeat_trainer.remaining_bytes = port->device.heartbeat_trainer.nv_page_size; + port->device.heartbeat_trainer.cur_buffer = + port->device.heartbeat_trainer.param < port->device.heartbeat_trainer.nv_pages + ? port->device.heartbeat_trainer.nv_memory + 5 + 8 + + port->device.heartbeat_trainer.param * port->device.heartbeat_trainer.nv_page_size + : NULL; + if (port->device.heartbeat_trainer.cmd == HBPT_WRITE_PAGE) { + return; + } + port->device.heartbeat_trainer.state = HBPT_REPLY; + break; + case HBPT_SET_RTC: + //save RTC base values back to nv memory area so it's saved to disk on exit + for (int i = 0; i < 8; i++) + { + port->device.heartbeat_trainer.nv_memory[i] = port->device.heartbeat_trainer.rtc_base_timestamp >> (56 - i*8); + } + memcpy(port->device.heartbeat_trainer.nv_memory + 8, port->device.heartbeat_trainer.rtc_base, 5); + simple_reply(port, 1); + break; + case HBPT_ERASE_NVMEM: + memset( + port->device.heartbeat_trainer.nv_memory + 5 + 8, + port->device.heartbeat_trainer.param, + port->device.heartbeat_trainer.nv_pages * port->device.heartbeat_trainer.nv_page_size + ); + simple_reply(port, 1); + break; + case HBPT_INIT: { + static const char reply[] = "(C) HEARTBEAT CORP"; + start_reply(port, strlen(reply), reply); + break; + } + } + } + } + hbpt_check_send_reply(port); +} + +void hbpt_read_byte(io_port *port) +{ + hbpt_check_init(port); + hbpt_check_send_reply(port); +} + const int mouse_delays[] = {112*7, 120*7, 96*7, 132*7, 104*7, 96*7, 112*7, 96*7}; enum { @@ -576,6 +939,67 @@ KB_WRITE }; +enum { + SCTRL_BIT_TX_FULL = 1, + SCTRL_BIT_RX_READY = 2, + SCTRL_BIT_RX_ERROR = 4, + SCTRL_BIT_RX_INTEN = 8, + SCTRL_BIT_TX_ENABLE = 0x10, + SCTRL_BIT_RX_ENABLE = 0x20 +}; + +void io_run(io_port *port, uint32_t current_cycle) +{ + uint32_t new_serial_cycle = ((current_cycle - port->serial_cycle) / port->serial_divider) * port->serial_divider + port->serial_cycle; + if (port->transmit_end && port->transmit_end <= new_serial_cycle) { + port->transmit_end = 0; + + if (port->serial_ctrl & SCTRL_BIT_TX_ENABLE) { + switch (port->device_type) + { + case IO_HEARTBEAT_TRAINER: + hbpt_write_byte(port); + break; +#ifndef _WIN32 + case IO_GENERIC_SERIAL: + write_serial_byte(port); + break; +#endif + //TODO: think about how serial mode might interact with non-serial peripherals + } + } + } + if (!port->transmit_end && new_serial_cycle != port->serial_cycle && (port->serial_ctrl & SCTRL_BIT_TX_FULL)) { + //there's a transmit byte pending and no byte is currently being sent + port->serial_transmitting = port->serial_out; + port->serial_ctrl &= ~SCTRL_BIT_TX_FULL; + //1 start bit, 8 data bits and 1 stop bit + port->transmit_end = new_serial_cycle + 10 * port->serial_divider; + } + port->serial_cycle = new_serial_cycle; + if (port->serial_ctrl && SCTRL_BIT_RX_ENABLE) { + if (port->receive_end && new_serial_cycle >= port->receive_end) { + port->serial_in = port->serial_receiving; + port->serial_ctrl |= SCTRL_BIT_RX_READY; + port->receive_end = 0; + } + if (!port->receive_end) { + switch(port->device_type) + { + case IO_HEARTBEAT_TRAINER: + hbpt_read_byte(port); + break; +#ifndef _WIN32 + case IO_GENERIC_SERIAL: + read_serial_byte(port); + break; +#endif + //TODO: think about how serial mode might interact with non-serial peripherals + } + } + } +} + void io_control_write(io_port *port, uint8_t value, uint32_t current_cycle) { uint8_t changes = value ^ port->control; @@ -723,6 +1147,20 @@ } +void io_tx_write(io_port *port, uint8_t value, uint32_t current_cycle) +{ + io_run(port, current_cycle); + port->serial_out = value; + port->serial_ctrl |= SCTRL_BIT_TX_FULL; +} + +void io_sctrl_write(io_port *port, uint8_t value, uint32_t current_cycle) +{ + io_run(port, current_cycle); + port->serial_ctrl = (port->serial_ctrl & 0x7) | (value & 0xF8); + set_serial_clock(port); +} + uint8_t get_scancode_bytes(io_port *port) { if (port->device.keyboard.read_pos == 0xFF) { @@ -766,9 +1204,9 @@ uint8_t th = output & 0x40; uint8_t input; uint8_t device_driven; - if (current_cycle - last_poll_cycle > MIN_POLL_INTERVAL) { + if (current_cycle - port->last_poll_cycle > MIN_POLL_INTERVAL) { process_events(); - last_poll_cycle = current_cycle; + port->last_poll_cycle = current_cycle; } switch (port->device_type) { @@ -1049,6 +1487,36 @@ return value; } +uint8_t io_rx_read(io_port * port, uint32_t current_cycle) +{ + io_run(port, current_cycle); + port->serial_ctrl &= ~SCTRL_BIT_RX_READY; + return port->serial_in; +} + +uint8_t io_sctrl_read(io_port *port, uint32_t current_cycle) +{ + io_run(port, current_cycle); + return port->serial_ctrl; +} + +uint32_t io_next_interrupt(io_port *port, uint32_t current_cycle) +{ + if (!(port->control & 0x80)) { + return CYCLE_NEVER; + } + if (port->serial_ctrl & SCTRL_BIT_RX_INTEN) { + if (port->serial_ctrl & SCTRL_BIT_RX_READY) { + return current_cycle; + } + if ((port->serial_ctrl & SCTRL_BIT_RX_ENABLE) && port->receive_end) { + return port->receive_end; + } + } + //TODO: handle external interrupts from TH transitions + return CYCLE_NEVER; +} + void io_serialize(io_port *port, serialize_buffer *buf) { save_int8(buf, port->output); @@ -1080,7 +1548,21 @@ save_int8(buf, port->device.keyboard.cmd); } break; + case IO_HEARTBEAT_TRAINER: + save_int8(buf, port->device.heartbeat_trainer.bpm); + save_int8(buf, port->device.heartbeat_trainer.cadence); + save_int8(buf, port->device.heartbeat_trainer.param); + save_int8(buf, port->device.heartbeat_trainer.state); + save_int8(buf, port->device.heartbeat_trainer.status); + save_int8(buf, port->device.heartbeat_trainer.cmd); + save_int8(buf, port->device.heartbeat_trainer.remaining_bytes); + break; } + save_int32(buf, port->serial_cycle); + save_int32(buf, port->transmit_end); + save_int32(buf, port->receive_end); + save_int8(buf, port->serial_transmitting); + save_int8(buf, port->serial_receiving); } void io_deserialize(deserialize_buffer *buf, void *vport) @@ -1091,7 +1573,9 @@ port->serial_out = load_int8(buf); port->serial_in = load_int8(buf); port->serial_ctrl = load_int8(buf); + set_serial_clock(port); uint8_t device_type = load_int8(buf); + load_buffer32(buf, port->slow_rise_start, 8); if (device_type != port->device_type) { warning("Loaded save state has a different device type from the current configuration"); return; @@ -1118,5 +1602,21 @@ port->device.keyboard.cmd = load_int8(buf); } break; + case IO_HEARTBEAT_TRAINER: + port->device.heartbeat_trainer.bpm = load_int8(buf); + port->device.heartbeat_trainer.cadence = load_int8(buf); + port->device.heartbeat_trainer.param = load_int8(buf); + port->device.heartbeat_trainer.state = load_int8(buf); + port->device.heartbeat_trainer.status = load_int8(buf); + port->device.heartbeat_trainer.cmd = load_int8(buf); + port->device.heartbeat_trainer.remaining_bytes = load_int8(buf); + break; + } + if (buf->cur_pos < buf->size) { + port->serial_cycle = load_int32(buf); + port->transmit_end = load_int32(buf); + port->receive_end = load_int32(buf); + port->serial_transmitting = load_int8(buf); + port->serial_receiving = load_int8(buf); } } diff -r 0d5f88e53dca -r 638eb2d25696 io.h --- a/io.h Sun May 10 00:16:00 2020 -0700 +++ b/io.h Thu Aug 05 09:29:33 2021 -0700 @@ -24,7 +24,9 @@ IO_EA_MULTI_A, IO_EA_MULTI_B, IO_SEGA_PARALLEL, - IO_GENERIC + IO_GENERIC, + IO_GENERIC_SERIAL, + IO_HEARTBEAT_TRAINER }; typedef struct { @@ -57,13 +59,37 @@ uint8_t mode; uint8_t cmd; } keyboard; + struct { + uint8_t *nv_memory; + uint8_t *cur_buffer; + uint64_t rtc_base_timestamp; + uint8_t rtc_base[5]; + uint8_t bpm; + uint8_t cadence; + uint8_t buttons; + uint8_t nv_page_size; + uint8_t nv_pages; + uint8_t param; + uint8_t state; + uint8_t status; + uint8_t device_num; + uint8_t cmd; + uint8_t remaining_bytes; + } heartbeat_trainer; } device; uint8_t output; uint8_t control; uint8_t input[3]; uint32_t slow_rise_start[8]; + uint32_t serial_cycle; + uint32_t serial_divider; + uint32_t last_poll_cycle; + uint32_t transmit_end; + uint32_t receive_end; uint8_t serial_out; + uint8_t serial_transmitting; uint8_t serial_in; + uint8_t serial_receiving; uint8_t serial_ctrl; uint8_t device_type; } io_port; @@ -106,9 +132,15 @@ void setup_io_devices(tern_node * config, rom_info *rom, sega_io *io); void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction); +void io_run(io_port *port, uint32_t current_cycle); void io_control_write(io_port *port, uint8_t value, uint32_t current_cycle); void io_data_write(io_port * pad, uint8_t value, uint32_t current_cycle); +void io_tx_write(io_port *port, uint8_t value, uint32_t current_cycle); +void io_sctrl_write(io_port *port, uint8_t value, uint32_t current_cycle); uint8_t io_data_read(io_port * pad, uint32_t current_cycle); +uint8_t io_rx_read(io_port * port, uint32_t current_cycle); +uint8_t io_sctrl_read(io_port *port, uint32_t current_cycle); +uint32_t io_next_interrupt(io_port *port, uint32_t current_cycle); void io_serialize(io_port *port, serialize_buffer *buf); void io_deserialize(deserialize_buffer *buf, void *vport); diff -r 0d5f88e53dca -r 638eb2d25696 libblastem.c --- a/libblastem.c Sun May 10 00:16:00 2020 -0700 +++ b/libblastem.c Thu Aug 05 09:29:33 2021 -0700 @@ -517,10 +517,22 @@ { } +void render_set_external_sync(uint8_t ext_sync_on) +{ +} + void bindings_set_mouse_mode(uint8_t mode) { } +void bindings_release_capture(void) +{ +} + +void bindings_reacquire_capture(void) +{ +} + extern const char rom_db_data[]; char *read_bundled_file(char *name, uint32_t *sizeret) { diff -r 0d5f88e53dca -r 638eb2d25696 m68k.cpu --- a/m68k.cpu Sun May 10 00:16:00 2020 -0700 +++ b/m68k.cpu Thu Aug 05 09:29:33 2021 -0700 @@ -861,6 +861,114 @@ end m68k_save_dst Z m68k_prefetch + +1110CCC0ZZ001RRR lsri + invalid Z 3 + switch C + case 0 + meta shift 8 + default + meta shift C + end + lsr dregs.R shift dregs.R Z + update_flags XNZV0C + add shift shift shift + switch Z + case 2 + add 4 shift shift + default + add 2 shift shift + end + cycles shift + #TODO: should this happen before or after the majority of the shift? + m68k_prefetch + +1110CCC0ZZ101RRR lsr_dn + invalid Z 3 + local shift 8 + and dregs.C 63 shift + lsr dregs.R shift dregs.R Z + update_flags XNZV0C + add shift shift shift + switch Z + case 2 + add 4 shift shift + default + add 2 shift shift + end + cycles shift + #TODO: should this happen before or after the majority of the shift? + m68k_prefetch + +1110001011MMMRRR lsr_ea + invalid M 0 + invalid M 1 + invalid M 7 R 2 + invalid M 7 R 3 + invalid M 7 R 4 + invalid M 7 R 5 + invalid M 7 R 6 + invalid M 7 R 7 + + m68k_fetch_dst_ea M R 0 + lsr dst 1 dst + update_flags XNZV0C + m68k_save_dst 0 + m68k_prefetch + +1110CCC1ZZ001RRR lsli + invalid Z 3 + switch C + case 0 + meta shift 8 + default + meta shift C + end + lsl dregs.R shift dregs.R Z + update_flags XNZV0C + add shift shift shift + switch Z + case 2 + add 4 shift shift + default + add 2 shift shift + end + cycles shift + #TODO: should this happen before or after the majority of the shift? + m68k_prefetch + +1110CCC1ZZ101RRR lsl_dn + invalid Z 3 + local shift 8 + and dregs.C 63 shift + lsl dregs.R shift dregs.R Z + update_flags XNZV0C + add shift shift shift + switch Z + case 2 + add 4 shift shift + default + add 2 shift shift + end + cycles shift + #TODO: should this happen before or after the majority of the shift? + m68k_prefetch + +1110001111MMMRRR lsl_ea + invalid M 0 + invalid M 1 + invalid M 7 R 2 + invalid M 7 R 3 + invalid M 7 R 4 + invalid M 7 R 5 + invalid M 7 R 6 + invalid M 7 R 7 + + m68k_fetch_dst_ea M R 0 + lsl dst 1 dst + update_flags XNZV0C + m68k_save_dst 0 + m68k_prefetch 00ZZRRRMMMEEESSS move invalid Z 0 diff -r 0d5f88e53dca -r 638eb2d25696 m68k_core.h --- a/m68k_core.h Sun May 10 00:16:00 2020 -0700 +++ b/m68k_core.h Thu Aug 05 09:29:33 2021 -0700 @@ -12,7 +12,7 @@ //#include "68kinst.h" struct m68kinst; -#define NUM_MEM_AREAS 8 +#define NUM_MEM_AREAS 10 #define NATIVE_MAP_CHUNKS (64*1024) #define NATIVE_CHUNK_SIZE ((16 * 1024 * 1024 / NATIVE_MAP_CHUNKS)) #define MAX_NATIVE_SIZE 255 diff -r 0d5f88e53dca -r 638eb2d25696 m68k_core_x86.c --- a/m68k_core_x86.c Sun May 10 00:16:00 2020 -0700 +++ b/m68k_core_x86.c Thu Aug 05 09:29:33 2021 -0700 @@ -421,7 +421,11 @@ push_r(code, opts->gen.scratch1); } dec_amount = inst->extra.size == OPSIZE_WORD ? 2 : (inst->extra.size == OPSIZE_LONG ? 4 : (op->params.regs.pri == 7 ? 2 :1)); - if (!dst) { + if (!dst || ( + inst->op != M68K_MOVE && inst->op != M68K_MOVEM + && inst->op != M68K_SUBX && inst->op != M68K_ADDX + && inst->op != M68K_ABCD && inst->op != M68K_SBCD + )) { cycles(&opts->gen, PREDEC_PENALTY); } subi_areg(opts, dec_amount, op->params.regs.pri); @@ -874,7 +878,7 @@ code_ptr end_off = code->cur+1; jmp(code, code->cur+2); *true_off = code->cur - (true_off+1); - cycles(&opts->gen, 6); + cycles(&opts->gen, inst->dst.addr_mode == MODE_REG ? 6 : 4); if (dst_op.mode == MODE_REG_DIRECT) { mov_ir(code, 0xFF, dst_op.base, SZ_B); } else { @@ -1190,8 +1194,6 @@ void translate_m68k_reset(m68k_options *opts, m68kinst *inst) { code_info *code = &opts->gen.code; - //RESET instructions take a long time to give peripherals time to reset themselves - cycles(&opts->gen, 132); mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, reset_handler), opts->gen.scratch1, SZ_PTR); cmp_ir(code, 0, opts->gen.scratch1, SZ_PTR); code_ptr no_reset_handler = code->cur + 1; @@ -1201,6 +1203,8 @@ mov_rr(code, RAX, opts->gen.context_reg, SZ_PTR); call(code, opts->gen.load_context); *no_reset_handler = code->cur - (no_reset_handler + 1); + //RESET instructions take a long time to give peripherals time to reset themselves + cycles(&opts->gen, 132); } void op_ir(code_info *code, m68kinst *inst, int32_t val, uint8_t dst, uint8_t size) @@ -1309,14 +1313,17 @@ uint32_t numcycles; if ((inst->op == M68K_ADDX || inst->op == M68K_SUBX) && inst->src.addr_mode != MODE_REG) { - numcycles = 6; + numcycles = 4; } else if (size == OPSIZE_LONG) { if (inst->op == M68K_CMP) { - numcycles = 6; - } else if (inst->op == M68K_AND && inst->variant == VAR_IMMEDIATE) { + numcycles = inst->src.addr_mode > MODE_AREG && inst->dst.addr_mode > MODE_AREG ? 4 : 6; + } else if (inst->op == M68K_AND && inst->variant == VAR_IMMEDIATE && inst->dst.addr_mode == MODE_REG) { numcycles = 6; - } else if (inst->dst.addr_mode <= MODE_AREG) { + } else if (inst->dst.addr_mode == MODE_REG) { numcycles = inst->src.addr_mode <= MODE_AREG || inst->src.addr_mode == MODE_IMMEDIATE ? 8 : 6; + } else if (inst->dst.addr_mode == MODE_AREG) { + numcycles = numcycles = inst->src.addr_mode <= MODE_AREG || inst->src.addr_mode == MODE_IMMEDIATE + || inst->extra.size == OPSIZE_WORD ? 8 : 6; } else { numcycles = 4; } @@ -1493,8 +1500,9 @@ //destination is in memory so we need to preserve scratch2 for the write at the end push_r(code, opts->gen.scratch2); } - //MC68000 User's Manual suggests NBCD hides the 2 cycle penalty during the write cycle somehow - cycles(&opts->gen, inst->op == M68K_NBCD && inst->dst.addr_mode != MODE_REG_DIRECT ? BUS : BUS + 2); + + //reg to reg takes 6 cycles, mem to mem is 4 cycles + all the operand fetch/writing (including 2 cycle predec penalty for first operand) + cycles(&opts->gen, inst->dst.addr_mode != MODE_REG ? BUS : BUS + 2); uint8_t other_reg; //WARNING: This may need adjustment if register assignments change if (opts->gen.scratch2 > RBX) { @@ -2068,7 +2076,7 @@ void translate_m68k_negx(m68k_options *opts, m68kinst *inst, host_ea *src_op, host_ea *dst_op) { code_info *code = &opts->gen.code; - cycles(&opts->gen, BUS); + cycles(&opts->gen, inst->extra.size == OPSIZE_LONG && inst->dst.addr_mode == MODE_REG ? BUS+2 : BUS); if (dst_op->mode == MODE_REG_DIRECT) { if (dst_op->base == opts->gen.scratch1) { push_r(code, opts->gen.scratch2); @@ -2134,6 +2142,7 @@ } update_flags(opts, init_flags); } else { + cycles(&opts->gen, inst->extra.size == OPSIZE_LONG ? 8 : 6); if (src_op->mode == MODE_REG_DIRECT) { if (src_op->base != opts->gen.scratch1) { mov_rr(code, src_op->base, opts->gen.scratch1, SZ_B); @@ -2441,7 +2450,7 @@ void translate_m68k_move_from_sr(m68k_options *opts, m68kinst *inst, host_ea *src_op, host_ea *dst_op) { code_info *code = &opts->gen.code; - cycles(&opts->gen, inst->dst.addr_mode == MODE_REG_DIRECT ? BUS+2 : BUS); + cycles(&opts->gen, inst->dst.addr_mode == MODE_REG ? BUS+2 : BUS); call(code, opts->get_sr); if (dst_op->mode == MODE_REG_DIRECT) { mov_rr(code, opts->gen.scratch1, dst_op->base, SZ_W); diff -r 0d5f88e53dca -r 638eb2d25696 megawifi.c --- a/megawifi.c Sun May 10 00:16:00 2020 -0700 +++ b/megawifi.c Thu Aug 05 09:29:33 2021 -0700 @@ -21,7 +21,7 @@ #include "net.h" #include "util.h" -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) # if BYTE_ORDER == LITTLE_ENDIAN #define htobe64(val) ((((uint64_t)htonl((val)&0xFFFFFFFF))<<32) | htonl((val)>>32)) # else diff -r 0d5f88e53dca -r 638eb2d25696 nuklear_ui/blastem_nuklear.c --- a/nuklear_ui/blastem_nuklear.c Sun May 10 00:16:00 2020 -0700 +++ b/nuklear_ui/blastem_nuklear.c Thu Aug 05 09:29:33 2021 -0700 @@ -31,7 +31,8 @@ struct nk_image ui; } ui_image; -static ui_image **ui_images, *controller_360, *controller_ps4, *controller_ps4_6b; +static ui_image **ui_images, *controller_360, *controller_ps4, + *controller_ps4_6b, *controller_wiiu, *controller_gen_6b; static uint32_t num_ui_images, ui_image_storage; typedef void (*view_fun)(struct nk_context *); @@ -88,6 +89,15 @@ if (entries) { sort_dir_list(entries, num_entries); } + if (!num_entries) { + //get_dir_list can fail if the user doesn't have permission + //for the current folder, make sure they can still navigate up + free_dir_list(entries, num_entries); + entries = calloc(1, sizeof(dir_entry)); + entries[0].name = strdup(".."); + entries[0].is_dir = 1; + num_entries = 1; + } } if (!got_ext_list) { ext_list = get_extension_list(config, &num_exts); @@ -98,7 +108,8 @@ if (nk_begin(context, "Load ROM", nk_rect(0, 0, width, height), 0)) { nk_layout_row_static(context, height - context->style.font->height * 3, width - 60, 1); int32_t old_selected = selected_entry; - if (nk_group_begin(context, "Select ROM", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) { + char *title = alloc_concat("Select ROM: ", current_path); + if (nk_group_begin(context, title, NK_WINDOW_BORDER | NK_WINDOW_TITLE)) { nk_layout_row_static(context, context->style.font->height - 2, width-100, 1); for (int32_t i = 0; i < num_entries; i++) { @@ -118,6 +129,7 @@ } nk_group_end(context); } + free(title); nk_layout_row_static(context, context->style.font->height * 1.75, width > 600 ? 300 : width / 2, 2); if (nk_button_label(context, "Back")) { pop_view(); @@ -977,10 +989,16 @@ static ui_image *select_best_image(controller_info *info) { - if (info->variant != VARIANT_NORMAL) { - return controller_ps4_6b; + if (info->variant != VARIANT_NORMAL || info->type == TYPE_SEGA) { + if (info->type == TYPE_PSX) { + return controller_ps4_6b; + } else { + return controller_gen_6b; + } } else if (info->type == TYPE_PSX) { return controller_ps4; + } else if (info->type == TYPE_NINTENDO) { + return controller_wiiu; } else { return controller_360; } @@ -1243,14 +1261,14 @@ )) { if (current_button <= SDL_CONTROLLER_BUTTON_B || axis_moved != button_a_axis) { start_mapping(); + if (current_button >= SDL_CONTROLLER_BUTTON_DPAD_UP) { + mapping_string[mapping_pos++] = axis_value >= 0 ? '+' : '-'; + } mapping_string[mapping_pos++] = 'a'; if (axis_moved > 9) { mapping_string[mapping_pos++] = '0' + axis_moved / 10; } mapping_string[mapping_pos++] = '0' + axis_moved % 10; - if (current_button >= SDL_CONTROLLER_BUTTON_DPAD_UP) { - mapping_string[mapping_pos++] = axis_value >= 0 ? '+' : '-'; - } last_axis = axis_moved; last_axis_value = axis_value; } @@ -1330,26 +1348,41 @@ nk_label(context, "Select the layout that", NK_TEXT_CENTERED); nk_label(context, "best matches your controller", NK_TEXT_CENTERED); nk_label(context, "", NK_TEXT_CENTERED); - if (nk_button_label(context, "4 face buttons")) { - selected_controller_info.variant = VARIANT_NORMAL; - selected = 1; - } - char buffer[512]; - snprintf(buffer, sizeof(buffer), "6 face buttons including %s and %s", - get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), - get_axis_label(&selected_controller_info, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) - ); - if (nk_button_label(context, buffer)) { - selected_controller_info.variant = VARIANT_6B_RIGHT; - selected = 1; - } - snprintf(buffer, sizeof(buffer), "6 face buttons including %s and %s", - get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_LEFTSHOULDER), - get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) - ); - if (nk_button_label(context, buffer)) { - selected_controller_info.variant = VARIANT_6B_BUMPERS; - selected = 1; + if (selected_controller_info.subtype == SUBTYPE_GENESIS) { + if (nk_button_label(context, "3 button")) { + selected_controller_info.variant = VARIANT_3BUTTON; + selected = 1; + } + if (nk_button_label(context, "Standard 6 button")) { + selected_controller_info.variant = VARIANT_6B_BUMPERS; + selected = 1; + } + if (nk_button_label(context, "6 button with 2 shoulder buttons")) { + selected_controller_info.variant = VARIANT_8BUTTON; + selected = 1; + } + } else { + if (nk_button_label(context, "4 face buttons")) { + selected_controller_info.variant = VARIANT_NORMAL; + selected = 1; + } + char buffer[512]; + snprintf(buffer, sizeof(buffer), "6 face buttons including %s and %s", + get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), + get_axis_label(&selected_controller_info, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) + ); + if (nk_button_label(context, buffer)) { + selected_controller_info.variant = VARIANT_6B_RIGHT; + selected = 1; + } + snprintf(buffer, sizeof(buffer), "6 face buttons including %s and %s", + get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_LEFTSHOULDER), + get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) + ); + if (nk_button_label(context, buffer)) { + selected_controller_info.variant = VARIANT_6B_BUMPERS; + selected = 1; + } } nk_end(context); } @@ -1380,7 +1413,22 @@ selected_controller_info.type = type_id; selected_controller_info.subtype = first_subtype_id + i; pop_view(); - push_view(view_controller_variant); + if (selected_controller_info.subtype == SUBTYPE_SATURN) { + selected_controller_info.variant = VARIANT_6B_BUMPERS; + save_controller_info(selected_controller, &selected_controller_info); + if (initial_controller_config) { + SDL_GameController *controller = render_get_controller(selected_controller); + if (controller) { + push_view(view_controller_bindings); + controller_binding_changed = 0; + SDL_GameControllerClose(controller); + } else { + show_mapping_view(); + } + } + } else { + push_view(view_controller_variant); + } } } nk_group_end(context); @@ -1960,17 +2008,19 @@ uint32_t desired_width = context->style.font->height * 10; if (nk_begin(context, "System Settings", nk_rect(0, 0, width, height), 0)) { nk_layout_row_static(context, context->style.font->height, desired_width, 2); + + selected_model = settings_dropdown_ex(context, "Model", model_opts, model_names, num_models, selected_model, "system\0model\0"); + selected_io_1 = settings_dropdown_ex(context, "IO Port 1 Device", io_opts_1, device_type_names, num_io, selected_io_1, "io\0devices\0""1\0"); + selected_io_2 = settings_dropdown_ex(context, "IO Port 2 Device", io_opts_2, device_type_names, num_io, selected_io_2, "io\0devices\0""2\0"); + selected_region = settings_dropdown_ex(context, "Default Region", region_codes, regions, num_regions, selected_region, "system\0default_region\0"); selected_sync = settings_dropdown(context, "Sync Source", sync_opts, num_sync_opts, selected_sync, "system\0sync_source\0"); settings_int_property(context, "68000 Clock Divider", "", "clocks\0m68k_divider\0", 7, 1, 53); + selected_format = settings_dropdown(context, "Save State Format", formats, num_formats, selected_format, "ui\0state_format\0"); + selected_init = settings_dropdown(context, "Initial RAM Value", ram_inits, num_inits, selected_init, "system\0ram_init\0"); settings_toggle(context, "Remember ROM Path", "ui\0remember_path\0", 1); settings_toggle(context, "Save config with EXE", "ui\0config_in_exe_dir\0", 0); settings_string(context, "Game Save Path", "ui\0save_path\0", "$USERDATA/blastem/$ROMNAME"); - selected_region = settings_dropdown_ex(context, "Default Region", region_codes, regions, num_regions, selected_region, "system\0default_region\0"); - selected_model = settings_dropdown_ex(context, "Model", model_opts, model_names, num_models, selected_model, "system\0model\0"); - selected_format = settings_dropdown(context, "Save State Format", formats, num_formats, selected_format, "ui\0state_format\0"); - selected_init = settings_dropdown(context, "Initial RAM Value", ram_inits, num_inits, selected_init, "system\0ram_init\0"); - selected_io_1 = settings_dropdown_ex(context, "IO Port 1 Device", io_opts_1, device_type_names, num_io, selected_io_1, "io\0devices\0""1\0"); - selected_io_2 = settings_dropdown_ex(context, "IO Port 2 Device", io_opts_2, device_type_names, num_io, selected_io_2, "io\0devices\0""2\0"); + if (nk_button_label(context, "Back")) { pop_view(); } @@ -1978,6 +2028,29 @@ } } +void view_confirm_reset(struct nk_context *context) +{ + if (nk_begin(context, "Reset Confirm", nk_rect(0, 0, render_width(), render_height()), 0)) { + uint32_t desired_width = context->style.font->height * 20; + nk_layout_row_static(context, context->style.font->height, desired_width, 1); + nk_label(context, "This will reset all settings and controller", NK_TEXT_LEFT); + nk_label(context, "mappings back to the defaults.", NK_TEXT_LEFT); + nk_label(context, "Are you sure you want to proceed?", NK_TEXT_LEFT); + nk_layout_row_static(context, context->style.font->height * 1.5, desired_width / 2, 2); + if (nk_button_label(context, "Maybe not")) { + pop_view(); + } + if (nk_button_label(context, "Yep, delete it all")) { + delete_custom_config(); + config = load_config(); + delete_controller_info(); + config_dirty = 1; + pop_view(); + } + nk_end(context); + } +} + void view_back(struct nk_context *context) { pop_view(); @@ -1993,6 +2066,7 @@ {"Video", view_video_settings}, {"Audio", view_audio_settings}, {"System", view_system_settings}, + {"Reset to Defaults", view_confirm_reset}, {"Back", view_back} }; @@ -2310,6 +2384,8 @@ controller_360 = load_ui_image("images/360.png"); controller_ps4 = load_ui_image("images/ps4.png"); controller_ps4_6b = load_ui_image("images/ps4_6b.png"); + controller_wiiu = load_ui_image("images/wiiu.png"); + controller_gen_6b = load_ui_image("images/genesis_6b.png"); texture_init(); diff -r 0d5f88e53dca -r 638eb2d25696 nuklear_ui/nuklear.h --- a/nuklear_ui/nuklear.h Sun May 10 00:16:00 2020 -0700 +++ b/nuklear_ui/nuklear.h Thu Aug 05 09:29:33 2021 -0700 @@ -11380,10 +11380,13 @@ NK_ASSERT(alloc); if (!image_memory || !width || !height || !config_list || !count) return nk_false; + int pixel_area_estimate = 0; for (config_iter = config_list; config_iter; config_iter = config_iter->next) { range_count = nk_range_count(config_iter->range); total_range_count += range_count; - total_glyph_count += nk_range_glyph_count(config_iter->range, range_count); + int glyphs = nk_range_glyph_count(config_iter->range, range_count); + total_glyph_count += glyphs; + pixel_area_estimate += glyphs * config_iter->size * config_iter->size; } /* setup font baker from temporary memory */ @@ -11394,7 +11397,13 @@ } *height = 0; - *width = (total_glyph_count > 1000) ? 1024 : 512; + int width_estimate = sqrt(pixel_area_estimate) + 0.5; + *width = 128; + while (*width < width_estimate) + { + *width *= 2; + } + //*width = (total_glyph_count > 1000) ? 1024 : 512; nk_tt_PackBegin(&baker->spc, 0, (int)*width, (int)max_height, 0, 1, alloc); { int input_i = 0; diff -r 0d5f88e53dca -r 638eb2d25696 nuklear_ui/nuklear_sdl_gles2.h --- a/nuklear_ui/nuklear_sdl_gles2.h Sun May 10 00:16:00 2020 -0700 +++ b/nuklear_ui/nuklear_sdl_gles2.h Thu Aug 05 09:29:33 2021 -0700 @@ -168,6 +168,10 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + printf("glTexImage2D failed with error %d\n", err); + } } NK_API void diff -r 0d5f88e53dca -r 638eb2d25696 png.c --- a/png.c Sun May 10 00:16:00 2020 -0700 +++ b/png.c Thu Aug 05 09:29:33 2021 -0700 @@ -204,7 +204,7 @@ static uint32_t pixel_gray(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) { - uint8_t value = filter(*cur, *last, bpp, x); + uint8_t value = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; @@ -214,17 +214,17 @@ static uint32_t pixel_true(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) { - uint8_t red = filter(*cur, *last, bpp, x); + uint8_t red = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; } - uint8_t green = filter(*cur, *last, bpp, x); + uint8_t green = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; } - uint8_t blue = filter(*cur, *last, bpp, x); + uint8_t blue = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; @@ -234,12 +234,12 @@ static uint32_t pixel_gray_alpha(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) { - uint8_t value = filter(*cur, *last, bpp, x); + uint8_t value = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; } - uint8_t alpha = filter(*cur, *last, bpp, x); + uint8_t alpha = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; @@ -249,22 +249,22 @@ static uint32_t pixel_true_alpha(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) { - uint8_t red = filter(*cur, *last, bpp, x); + uint8_t red = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; } - uint8_t green = filter(*cur, *last, bpp, x); + uint8_t green = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; } - uint8_t blue = filter(*cur, *last, bpp, x); + uint8_t blue = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; } - uint8_t alpha = filter(*cur, *last, bpp, x); + uint8_t alpha = **cur = filter(*cur, *last, bpp, x); (*cur)++; if (*last) { (*last)++; @@ -354,6 +354,7 @@ } memcpy(idat_buf + idat_size, buffer + cur, chunk_size); idat_size += chunk_size; + idat_needs_free = 1; } else { idat_buf = buffer + cur; idat_size = chunk_size; diff -r 0d5f88e53dca -r 638eb2d25696 render.h --- a/render.h Sun May 10 00:16:00 2020 -0700 +++ b/render.h Thu Aug 05 09:29:33 2021 -0700 @@ -6,6 +6,8 @@ #ifndef RENDER_H_ #define RENDER_H_ +#include + #ifndef IS_LIB #ifdef USE_FBDEV #include "special_keys_evdev.h" @@ -138,7 +140,10 @@ void render_video_loop(void); uint8_t render_should_release_on_exit(void); void render_set_external_sync(uint8_t ext_sync_on); +void render_reset_mappings(void); +#ifndef IS_LIB uint8_t render_create_thread(render_thread *thread, const char *name, render_thread_fun fun, void *data); +#endif #endif //RENDER_H_ diff -r 0d5f88e53dca -r 638eb2d25696 render_sdl.c --- a/render_sdl.c Sun May 10 00:16:00 2020 -0700 +++ b/render_sdl.c Thu Aug 05 09:29:33 2021 -0700 @@ -171,8 +171,14 @@ void render_audio_created(audio_source *source) { - if (render_is_audio_sync()) { - SDL_PauseAudio(0); + if (sync_src == SYNC_AUDIO) { + //SDL_PauseAudio acquires the audio device lock, which is held while the callback runs + //since our callback can itself be stuck waiting on the audio_ready condition variable + //calling SDL_PauseAudio(0) again for audio sources after the first can deadlock + //fortunately SDL_GetAudioStatus does not acquire the lock so is safe to call here + if (SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) { + SDL_PauseAudio(0); + } } if (current_system && sync_src == SYNC_AUDIO_THREAD) { system_request_exit(current_system, 0); @@ -194,8 +200,14 @@ void render_source_resumed(audio_source *src) { - if (render_is_audio_sync()) { - SDL_PauseAudio(0); + if (sync_src == SYNC_AUDIO) { + //SDL_PauseAudio acquires the audio device lock, which is held while the callback runs + //since our callback can itself be stuck waiting on the audio_ready condition variable + //calling SDL_PauseAudio(0) again for audio sources after the first can deadlock + //fortunately SDL_GetAudioStatus does not acquire the lock so is safe to call here + if (SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) { + SDL_PauseAudio(0); + } } if (current_system && sync_src == SYNC_AUDIO_THREAD) { system_request_exit(current_system, 0); @@ -394,7 +406,7 @@ } else { tex_width = tex_height = 512; } - printf("Using %dx%d textures\n", tex_width, tex_height); + debug_message("Using %dx%d textures\n", tex_width, tex_height); for (int i = 0; i < 3; i++) { glBindTexture(GL_TEXTURE_2D, textures[i]); @@ -1191,6 +1203,19 @@ atexit(render_quit); } + +void render_reset_mappings(void) +{ + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); + uint32_t db_size; + char *db_data = read_bundled_file("gamecontrollerdb.txt", &db_size); + if (db_data) { + int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1); + free(db_data); + debug_message("Added %d game controller mappings from gamecontrollerdb.txt\n", added); + } +} static int in_toggle; void render_config_updated(void) @@ -1706,6 +1731,7 @@ if (sync_src != SYNC_AUDIO_THREAD && sync_src != SYNC_EXTERNAL) { return; } + SDL_PauseAudio(0); SDL_LockMutex(frame_mutex); for(;;) { diff -r 0d5f88e53dca -r 638eb2d25696 rom.db --- a/rom.db Sun May 10 00:16:00 2020 -0700 +++ b/rom.db Thu Aug 05 09:29:33 2021 -0700 @@ -1377,3 +1377,61 @@ } } } +NETO-001 { + name Sonic Delta + map { + 0 { + device ROM + last 7FFFF + } + 80000 { + device Sega mapper + last 3FFFFF + offset 80000 + } + } +} +5e5ca20b39122c86b8f662bd8014af674f9dbed7 { + name Rock Heaven + map { + 0 { + device ROM + last 3FFFFF + } + 500008 { + device fixed + value 5082 + last 500009 + } + } +} +68366449fd1497538741b5a1949e342202d48948 { + name Rock World + map { + 0 { + device ROM + last 3FFFFF + } + 500008 { + device fixed + value 4000 + last 500009 + } + 500208 { + device fixed + value A000 + last 500209 + } + + } +} +T-122026 { + name Outback Joey + HeartbeatTrainer { + size 512 + } + device_overrides { + 1 heartbeat_trainer.1 + 2 gamepad3.2 + } +} diff -r 0d5f88e53dca -r 638eb2d25696 romdb.c --- a/romdb.c Sun May 10 00:16:00 2020 -0700 +++ b/romdb.c Thu Aug 05 09:29:33 2021 -0700 @@ -32,6 +32,8 @@ return "EEPROM"; } else if(save_type == SAVE_NOR) { return "NOR Flash"; + } else if(save_type == SAVE_HBPT) { + return "Heartbeat Personal Trainer"; } return "SRAM"; } @@ -364,6 +366,8 @@ info->map[1].flags |= MMAP_ONLY_ODD; } else if (info->save_type == RAM_FLAG_EVEN) { info->map[1].flags |= MMAP_ONLY_EVEN; + } else { + info->map[1].flags |= MMAP_CODE; } info->map[1].buffer = info->save_buffer; } else { @@ -717,6 +721,8 @@ map->flags |= MMAP_ONLY_ODD; } else if(state->info->save_type == RAM_FLAG_EVEN) { map->flags |= MMAP_ONLY_EVEN; + } else { + map->flags |= MMAP_CODE; } map->mask = calc_mask(state->info->save_size, start, end); } else if (!strcmp(dtype, "RAM")) { @@ -986,6 +992,19 @@ info.port1_override = tern_find_ptr(device_overrides, "1"); info.port2_override = tern_find_ptr(device_overrides, "2"); info.ext_override = tern_find_ptr(device_overrides, "ext"); + if ( + info.save_type == SAVE_NONE + && ( + (info.port1_override && startswith(info.port1_override, "heartbeat_trainer.")) + || (info.port2_override && startswith(info.port2_override, "heartbeat_trainer.")) + || (info.ext_override && startswith(info.ext_override, "heartbeat_trainer.")) + ) + ) { + info.save_type = SAVE_HBPT; + info.save_size = atoi(tern_find_path_default(entry, "HeartbeatTrainer\0size\0", (tern_val){.ptrval="512"}, TVAL_PTR).ptrval); + info.save_buffer = calloc(info.save_size + 5 + 8, 1); + memset(info.save_buffer, 0xFF, info.save_size); + } } else { info.port1_override = info.port2_override = info.ext_override = NULL; } diff -r 0d5f88e53dca -r 638eb2d25696 romdb.h --- a/romdb.h Sun May 10 00:16:00 2020 -0700 +++ b/romdb.h Thu Aug 05 09:29:33 2021 -0700 @@ -11,6 +11,7 @@ #define RAM_FLAG_MASK RAM_FLAG_ODD #define SAVE_I2C 0x01 #define SAVE_NOR 0x02 +#define SAVE_HBPT 0x03 #define SAVE_NONE 0xFF #include "tern.h" diff -r 0d5f88e53dca -r 638eb2d25696 serialize.h --- a/serialize.h Sun May 10 00:16:00 2020 -0700 +++ b/serialize.h Thu Aug 05 09:29:33 2021 -0700 @@ -42,7 +42,8 @@ SECTION_SOUND_RAM, SECTION_MAPPER, SECTION_EEPROM, - SECTION_CART_RAM + SECTION_CART_RAM, + SECTION_TMSS }; void init_serialize(serialize_buffer *buf); diff -r 0d5f88e53dca -r 638eb2d25696 tern.c --- a/tern.c Sun May 10 00:16:00 2020 -0700 +++ b/tern.c Thu Aug 05 09:29:33 2021 -0700 @@ -305,12 +305,11 @@ void tern_free(tern_node *head) { - if (head->left) { - tern_free(head->left); + if (!head) { + return; } - if (head->right) { - tern_free(head->right); - } + tern_free(head->left); + tern_free(head->right); if (head->el) { tern_free(head->straight.next); } diff -r 0d5f88e53dca -r 638eb2d25696 tmss.s68 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tmss.s68 Thu Aug 05 09:29:33 2021 -0700 @@ -0,0 +1,253 @@ + dc.l $0, start + dc.l empty_handler + dc.l empty_handler + ;$10 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$20 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$30 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$40 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$50 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$60 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$70 + dc.l empty_handler + dc.l empty_handler + dc.l int_6 + dc.l empty_handler + ;$80 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$90 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$A0 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$B0 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$C0 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$D0 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$E0 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + ;$F0 + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.l empty_handler + dc.b "SEGA IS COOL " + dc.b "(C)M.Pavone 2021" + dc.b "TRAIN MEMES STAN" + dc.b "D SILENTLY " + dc.b " " + dc.b "TRIUMPHANT MAMMA" + dc.b "LS SALUTE SOCIAL" + dc.b "ITES " + dc.b "MP 20210227-01",0,0 + dc.b "J " + dc.l $0 + dc.l romend-1 + dc.l $FF0000 + dc.l $FFFFFF + dc.b " " + dc.b " " + dc.b " " + dc.b " " + dc.b "JUE " + +frame_counter equ $FFFF8000 +ram_code equ $FFFF8002 + +empty_handler: + rte +start: + lea $A14000, a3 + move.l #'SEGA', (a3) + lea $C00000, a0 + lea $C00004, a1 + move.w #$8104, (a1) ;Mode 5, everything turned off + move.w #$8004, (a1) + move.w #$8220, (a1) ;Scroll a table $8000 + move.w #$8404, (a1) ;Scroll b table $8000 + move.w #$8560, (a1) ;SAT table $C000 + move.w #$8700, (a1) ;backdrop color 0 + move.w #$8A01, (a1) ;Set HINT counter + move.w #$8B00, (a1) ;full screen scroll + move.w #$8C81, (a1) ;40 cell mode, no interlace + move.w #$8D00, (a1) ;hscroll table at 0 + move.w #$8F02, (a1) ;autoinc 2 + move.w #$9011, (a1) ;64x64 scroll size + move.l #$C0000000, (a1) + move.w #$000, (a0) + move.w #$EEE, (a0) + + ;clear scroll table + move.l #$40000000, (a1) + move.l #0, (a0) + + ;load tiles + move.l #$44000000, (a1) + lea font, a2 + move.w #((fontend-font)/4 - 1), d0 +tloop: + move.l (a2)+, (a0) + dbra d0, tloop + + ;clear name table + move.l #$40000002, (a1) + moveq #32, d0 + move.w #(64*64-1), d1 +ploop: + move.w d0, (a0) + dbra d1, ploop + move.l #$45960002, d7 + move.l d7, (a1) + move.l #$800000, d6 + + lea ram_code_src(pc), a6 + lea ram_code.w, a5 + moveq #(font-ram_code_src)/2-1, d0 +copy: + move.w (a6)+, (a5)+ + dbra d0, copy + lea $101(a3), a4 + lea $100.w, a5 + move.l #'SEGA', d5 + move.l #' SEG', d4 + moveq #0, d0 + moveq #1, d2 + move.w #180, d3 + btst #6, $A10001 + beq .not_pal + move.w #150, d3 +.not_pal: + jmp ram_code.w + +ram_code_src: + move.b d2, (a4) + cmp.l (a5), d5 + beq.s is_good + cmp.l (a5), d4 + bne.s is_bad +is_good: + move.b d0, (a4) + lea good(pc), a6 + bsr.s print_string + + add.l d6, d7 + move.l d7, (a1) + bsr.s print_string + + add.l d6, d7 + move.l d7, (a1) + bsr.s print_string + + move.w #$8164, (a1) + move #$2500, SR +wait: + cmp.w frame_counter.w, d3 + bne.s wait + move #$2700, SR + move.b d2, (a4) + move.l $0.w, a7 + move.l $4.w, a6 + move.w #$8104, (a1) + move.l d0, (a3) + jmp (a6) + +is_bad: + move.b d0, (a4) + lea bad(pc), a6 + bsr.s print_string + + add.l d6, d7 + move.l d7, (a1) + bsr.s print_string + + add.l d6, d7 + move.l d7, (a1) + bsr.s print_string + + move.w #$8144, (a1) +forever: + bra.s forever + + +int_6: + addq.w #1, frame_counter.w + rte + +;Prints a null terminated string +;a6 - pointer to string +;a0 - VDP data port +;d0 - base tile attribute +; +;Clobbers: d1.w +print_string: +.loop + moveq #0, d1 + move.b (a6)+, d1 + beq .end + add.w d0, d1 + move.w d1, (a0) + bra .loop +.end + rts + +good: + dc.b " BLASTEM THINKS", 0 + dc.b " THAT THIS CART", 0 + dc.b " TASTES DELICIOUS!", 0 + +bad: + dc.b " *sniff* *sniff*", 0 + dc.b " something doesn't", 0 + dc.b " smell right...", 0 + + align 1 +font: + incbin font.tiles +fontend + +romend diff -r 0d5f88e53dca -r 638eb2d25696 trans.c --- a/trans.c Sun May 10 00:16:00 2020 -0700 +++ b/trans.c Thu Aug 05 09:29:33 2021 -0700 @@ -4,7 +4,11 @@ BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. */ #include "68kinst.h" +#ifdef NEW_CORE +#include "m68k.h" +#else #include "m68k_core.h" +#endif #include "mem.h" #include #include @@ -19,6 +23,7 @@ { } +#ifndef NEW_CORE uint64_t total_cycles; m68k_context * sync_components(m68k_context * context, uint32_t address) @@ -32,12 +37,17 @@ } return context; } +#endif m68k_context *reset_handler(m68k_context *context) { m68k_print_regs(context); +#ifdef NEW_CORE + printf("cycles: %d\n", context->cycles); +#else total_cycles += context->current_cycle; printf("%ld cycles\n", total_cycles); +#endif exit(0); //unreachable return context; @@ -75,16 +85,21 @@ memmap[1].flags = MMAP_READ | MMAP_WRITE | MMAP_CODE; memmap[1].buffer = malloc(64 * 1024); memset(memmap[1].buffer, 0, 64 * 1024); - init_m68k_opts(&opts, memmap, 2, 7); + init_m68k_opts(&opts, memmap, 2, 1); m68k_context * context = init_68k_context(&opts, reset_handler); context->mem_pointers[0] = memmap[0].buffer; context->mem_pointers[1] = memmap[1].buffer; - context->target_cycle = context->sync_cycle = 0x80000000; - /* - uint32_t address; - address = filebuf[2] << 16 | filebuf[3]; - translate_m68k_stream(address, context);*/ +#ifdef NEW_CORE + context->cycles = 40; +#else + context->current_cycle = 40; + context->target_cycle = context->sync_cycle = 8000; +#endif m68k_reset(context); +#ifdef NEW_CORE + m68k_execute(context, 8000); + puts("hit cycle limit"); +#endif return 0; } diff -r 0d5f88e53dca -r 638eb2d25696 util.c --- a/util.c Sun May 10 00:16:00 2020 -0700 +++ b/util.c Thu Aug 05 09:29:33 2021 -0700 @@ -534,6 +534,11 @@ output_enabled = 0; } +uint8_t is_stdout_enabled(void) +{ + return output_enabled; +} + #ifdef _WIN32 #define WINVER 0x501 #include diff -r 0d5f88e53dca -r 638eb2d25696 util.h --- a/util.h Sun May 10 00:16:00 2020 -0700 +++ b/util.h Thu Aug 05 09:29:33 2021 -0700 @@ -88,6 +88,8 @@ void debug_message(char *format, ...); //Disables output of info and debug messages to stdout void disable_stdout_messages(void); +//Returns stdout disable status +uint8_t is_stdout_enabled(void); //Deletes a file, returns true on success, false on failure uint8_t delete_file(char *path); //Initializes the socket library on platforms that need it diff -r 0d5f88e53dca -r 638eb2d25696 vdp.c --- a/vdp.c Sun May 10 00:16:00 2020 -0700 +++ b/vdp.c Thu Aug 05 09:29:33 2021 -0700 @@ -10,6 +10,7 @@ #include "render.h" #include "util.h" #include "event_log.h" +#include "terminal.h" #define NTSC_INACTIVE_START 224 #define PAL_INACTIVE_START 240 @@ -253,6 +254,15 @@ void vdp_free(vdp_context *context) { + if (headless) { + free(context->fb); + } + for (int i = 0; i < VDP_NUM_DEBUG_TYPES; i++) + { + if (context->enabled_debuggers & (1 << i)) { + vdp_toggle_debug_view(context, i); + } + } free(context); } @@ -1144,8 +1154,12 @@ context->v_offset = (line) & v_offset_mask; context->flags |= FLAG_WINDOW; return; + } else if (column == right_col) { + context->flags |= FLAG_WINDOW_EDGE; + context->flags &= ~FLAG_WINDOW; + } else { + context->flags &= ~(FLAG_WINDOW_EDGE|FLAG_WINDOW); } - context->flags &= ~FLAG_WINDOW; } //TODO: Verify behavior for 0x20 case uint16_t vscroll = 0xFF | (context->regs[REG_SCROLL] & 0x30) << 4; @@ -1159,7 +1173,7 @@ vscroll >>= vscroll_shift; //TODO: Verify the behavior for a setting of 2 static const uint16_t hscroll_masks[] = {0x1F, 0x3F, 0x1F, 0x7F}; - static const uint16_t v_shifts[] = {6, 7, 0, 8}; + static const uint16_t v_shifts[] = {6, 7, 16, 8}; uint16_t hscroll_mask = hscroll_masks[context->regs[REG_SCROLL] & 0x3]; uint16_t v_shift = v_shifts[context->regs[REG_SCROLL] & 0x3]; uint16_t hscroll, offset; @@ -1338,7 +1352,7 @@ return (sh_pixel){.index = pixel, .intensity = intensity}; } -static void render_normal(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off) +static void render_normal(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, uint8_t *buf_a, int plane_a_off, int plane_a_mask, int plane_b_off) { uint8_t *sprite_buf = context->linebuf + col * 8; if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) { @@ -1352,7 +1366,7 @@ for (int i = 0; i < 8; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i) { uint8_t sprite, plane_a, plane_b; - plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK]; + plane_a = buf_a[plane_a_off & plane_a_mask]; plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; *(dst++) = composite_normal(context, debug_dst, *sprite_buf, plane_a, plane_b, context->regs[REG_BG_COLOR]) & 0x3F; debug_dst++; @@ -1361,7 +1375,7 @@ for (int i = 0; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i) { uint8_t sprite, plane_a, plane_b; - plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK]; + plane_a = buf_a[plane_a_off & plane_a_mask]; plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; *(dst++) = composite_normal(context, debug_dst, *sprite_buf, plane_a, plane_b, context->regs[REG_BG_COLOR]) & 0x3F; debug_dst++; @@ -1369,7 +1383,7 @@ } } -static void render_highlight(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off) +static void render_highlight(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, uint8_t *buf_a, int plane_a_off, int plane_a_mask, int plane_b_off) { int start = 0; if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) { @@ -1383,7 +1397,7 @@ for (int i = start; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i) { uint8_t sprite, plane_a, plane_b; - plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK]; + plane_a = buf_a[plane_a_off & plane_a_mask]; plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; sprite = *sprite_buf; sh_pixel pixel = composite_highlight(context, debug_dst, sprite, plane_a, plane_b, context->regs[REG_BG_COLOR]); @@ -1400,7 +1414,7 @@ } } -static void render_testreg(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off, uint8_t output_disabled, uint8_t test_layer) +static void render_testreg(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, uint8_t *buf_a, int plane_a_off, int plane_a_mask, int plane_b_off, uint8_t output_disabled, uint8_t test_layer) { if (output_disabled) { switch (test_layer) @@ -1424,7 +1438,7 @@ case 2: for (int i = 0; i < 16; i++) { - *(dst++) = context->tmp_buf_a[(plane_a_off++) & SCROLL_BUFFER_MASK] & 0x3F; + *(dst++) = buf_a[(plane_a_off++) & plane_a_mask] & 0x3F; *(debug_dst++) = DBG_SRC_A; } break; @@ -1454,7 +1468,7 @@ } break; case 2: - pixel &= context->tmp_buf_a[(plane_a_off + i) & SCROLL_BUFFER_MASK]; + pixel &= buf_a[(plane_a_off + i) & plane_a_mask]; if (pixel) { src = DBG_SRC_A; } @@ -1478,7 +1492,7 @@ for (int i = start; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i) { uint8_t sprite, plane_a, plane_b; - plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK]; + plane_a = buf_a[plane_a_off & plane_a_mask]; plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; sprite = *sprite_buf; uint8_t pixel = composite_normal(context, debug_dst, sprite, plane_a, plane_b, 0x3F) & 0x3F; @@ -1509,7 +1523,7 @@ } } -static void render_testreg_highlight(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off, uint8_t output_disabled, uint8_t test_layer) +static void render_testreg_highlight(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, uint8_t *buf_a, int plane_a_off, int plane_a_mask, int plane_b_off, uint8_t output_disabled, uint8_t test_layer) { int start = 0; uint8_t *sprite_buf = context->linebuf + col * 8; @@ -1528,7 +1542,7 @@ } break; case 2: - pixel &= context->tmp_buf_a[(plane_a_off + i) & SCROLL_BUFFER_MASK]; + pixel &= buf_a[(plane_a_off + i) & plane_a_mask]; if (pixel) { src = DBG_SRC_A | DBG_SHADOW; } @@ -1552,7 +1566,7 @@ for (int i = start; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i) { uint8_t sprite, plane_a, plane_b; - plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK]; + plane_a = buf_a[plane_a_off & plane_a_mask]; plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; sprite = *sprite_buf; sh_pixel pixel = composite_highlight(context, debug_dst, sprite, plane_a, plane_b, 0x3F); @@ -1626,27 +1640,40 @@ uint8_t a_src, src; + uint8_t *buf_a; + int plane_a_mask; if (context->flags & FLAG_WINDOW) { plane_a_off = context->buf_a_off; + buf_a = context->tmp_buf_a; a_src = DBG_SRC_W; + plane_a_mask = SCROLL_BUFFER_MASK; } else { - plane_a_off = context->buf_a_off - context->hscroll_a_fine; + if (context->flags & FLAG_WINDOW_EDGE) { + buf_a = context->tmp_buf_a + context->buf_a_off; + plane_a_mask = 15; + plane_a_off = -context->hscroll_a_fine; + } else { + plane_a_off = context->buf_a_off - context->hscroll_a_fine; + plane_a_mask = SCROLL_BUFFER_MASK; + buf_a = context->tmp_buf_a; + } a_src = DBG_SRC_A; } + plane_a_off &= plane_a_mask; plane_b_off = context->buf_b_off - context->hscroll_b_fine; //printf("A | tmp_buf offset: %d\n", 8 - (context->hscroll_a & 0x7)); if (context->regs[REG_MODE_4] & BIT_HILIGHT) { if (output_disabled || test_layer) { - render_testreg_highlight(context, col, dst, debug_dst, plane_a_off, plane_b_off, output_disabled, test_layer); + render_testreg_highlight(context, col, dst, debug_dst, buf_a, plane_a_off, plane_a_mask, plane_b_off, output_disabled, test_layer); } else { - render_highlight(context, col, dst, debug_dst, plane_a_off, plane_b_off); + render_highlight(context, col, dst, debug_dst, buf_a, plane_a_off, plane_a_mask, plane_b_off); } } else { if (output_disabled || test_layer) { - render_testreg(context, col, dst, debug_dst, plane_a_off, plane_b_off, output_disabled, test_layer); + render_testreg(context, col, dst, debug_dst, buf_a, plane_a_off, plane_a_mask, plane_b_off, output_disabled, test_layer); } else { - render_normal(context, col, dst, debug_dst, plane_a_off, plane_b_off); + render_normal(context, col, dst, debug_dst, buf_a, plane_a_off, plane_a_mask, plane_b_off); } } dst += 16; @@ -3368,6 +3395,8 @@ //technically the second hcounter check should be different for H40, but this is probably close enough for now if (context->state == ACTIVE && context->vcounter == context->inactive_start && (context->hslot >= (is_h40 ? 167 : 135) || context->hslot < 133)) { context->state = INACTIVE; + context->cur_slot = MAX_SPRITES_LINE-1; + context->sprite_x_offset = 0; } } @@ -3750,13 +3779,15 @@ } } else { uint8_t mode_5 = context->regs[REG_MODE_2] & BIT_MODE_5; - //contrary to what's in Charles MacDonald's doc, it seems top 2 address bits are cleared - //needed for the Mona in 344 Bytes demo context->address_latch = (context->address_latch & 0x1C000) | (value & 0x3FFF); context->cd_latch = (context->cd_latch & 0x3C) | (value >> 14); if ((value & 0xC000) == 0x8000) { //Register write uint8_t reg = (value >> 8) & 0x1F; + // The fact that this is needed seems to pour some cold water on my theory + // about how the address latch actually works. Needs more search to definitively confirm + context->address = (context->address & 0x1C000) | (value & 0x3FFF); + context->cd = (context->cd & 0x3C) | (value >> 14); if (reg < (mode_5 ? VDP_REGS : 0xB)) { //printf("register %d set to %X\n", reg, value & 0xFF); if (reg == REG_MODE_1 && (value & BIT_HVC_LATCH) && !(context->regs[reg] & BIT_HVC_LATCH)) { @@ -3780,6 +3811,43 @@ if (reg == REG_MODE_1 || reg == REG_MODE_2 || reg == REG_MODE_4) { update_video_params(context); } + } else if (reg == REG_KMOD_CTRL) { + if (!(value & 0xFF)) { + context->system->enter_debugger = 1; + } + } else if (reg == REG_KMOD_MSG) { + char c = value; + if (c) { + context->kmod_buffer_length++; + if ((context->kmod_buffer_length + 1) > context->kmod_buffer_storage) { + context->kmod_buffer_storage = context->kmod_buffer_length ? 128 : context->kmod_buffer_length * 2; + context->kmod_msg_buffer = realloc(context->kmod_msg_buffer, context->kmod_buffer_storage); + } + context->kmod_msg_buffer[context->kmod_buffer_length - 1] = c; + } else if (context->kmod_buffer_length) { + context->kmod_msg_buffer[context->kmod_buffer_length] = 0; + if (is_stdout_enabled()) { + init_terminal(); + printf("KDEBUG MESSAGE: %s\n", context->kmod_msg_buffer); + } else { + // GDB remote debugging is enabled, use stderr instead + fprintf(stderr, "KDEBUG MESSAGE: %s\n", context->kmod_msg_buffer); + } + context->kmod_buffer_length = 0; + } + } else if (reg == REG_KMOD_TIMER) { + if (!(value & 0x80)) { + if (is_stdout_enabled()) { + init_terminal(); + printf("KDEBUG TIMER: %d\n", (context->cycles - context->timer_start_cycle) / 7); + } else { + // GDB remote debugging is enabled, use stderr instead + fprintf(stderr, "KDEBUG TIMER: %d\n", (context->cycles - context->timer_start_cycle) / 7); + } + } + if (value & 0xC0) { + context->timer_start_cycle = context->cycles; + } } } else if (mode_5) { context->flags |= FLAG_PENDING; @@ -3787,6 +3855,7 @@ //context->flags &= ~FLAG_READ_FETCHED; //context->flags2 &= ~FLAG2_READ_PENDING; } else { + clear_pending(context); context->flags &= ~FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_READ_PENDING; } @@ -3950,6 +4019,20 @@ } if (context->cd & 1) { warning("Read from VDP data port while writes are configured, CPU is now frozen. VDP Address: %X, CD: %X\n", context->address, context->cd); + context->system->enter_debugger = 1; + return context->prefetch; + } + switch (context->cd) + { + case VRAM_READ: + case VSRAM_READ: + case CRAM_READ: + case VRAM_READ8: + break; + default: + warning("Read from VDP data port with invalid source, CPU is now frozen. VDP Address: %X, CD: %X\n", context->address, context->cd); + context->system->enter_debugger = 1; + return context->prefetch; } while (!(context->flags & FLAG_READ_FETCHED)) { vdp_run_context_full(context, context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20)); diff -r 0d5f88e53dca -r 638eb2d25696 vdp.h --- a/vdp.h Sun May 10 00:16:00 2020 -0700 +++ b/vdp.h Thu Aug 05 09:29:33 2021 -0700 @@ -54,7 +54,7 @@ #define FLAG_PENDING 0x10 #define FLAG_READ_FETCHED 0x20 #define FLAG_DMA_RUN 0x40 -#define FLAG_DMA_PROG 0x80 +#define FLAG_WINDOW_EDGE 0x80 #define FLAG2_VINT_PENDING 0x01 #define FLAG2_HINT_PENDING 0x02 @@ -91,8 +91,11 @@ REG_DMALEN_H, REG_DMASRC_L, REG_DMASRC_M, - REG_DMASRC_H -} vdp_regs; + REG_DMASRC_H, + REG_KMOD_CTRL=29, + REG_KMOD_MSG, + REG_KMOD_TIMER +}; //Mode reg 1 #define BIT_VSCRL_LOCK 0x80 @@ -115,7 +118,7 @@ #define BIT_SPRITE_SZ 0x02 //Mode reg 3 -#define BIT_EINT_EN 0x10 +#define BIT_EINT_EN 0x08 #define BIT_VSCROLL 0x04 //Mode reg 4 @@ -168,6 +171,10 @@ uint32_t *fb; uint8_t *done_composite; uint32_t *debug_fbs[VDP_NUM_DEBUG_TYPES]; + char *kmod_msg_buffer; + uint32_t kmod_buffer_storage; + uint32_t kmod_buffer_length; + uint32_t timer_start_cycle; uint32_t output_pitch; uint32_t debug_fb_pitch[VDP_NUM_DEBUG_TYPES]; fifo_entry fifo[FIFO_SIZE]; diff -r 0d5f88e53dca -r 638eb2d25696 vgm.c --- a/vgm.c Sun May 10 00:16:00 2020 -0700 +++ b/vgm.c Thu Aug 05 09:29:33 2021 -0700 @@ -60,22 +60,33 @@ } } +#include "util.h" static void add_wait(vgm_writer *writer, uint32_t cycle) { - uint64_t delta = cycle - writer->last_cycle; - delta *= (uint64_t)44100; - delta /= (uint64_t)writer->master_clock; + if (cycle < writer->last_cycle) { + //This can happen when a YM-2612 write happens immediately after a PSG write + //due to the relatively low granularity of the PSG's internal clock + //given that VGM only has a granularity of 44.1 kHz ignoring this is harmless + return; + } + uint64_t last_sample = (uint64_t)writer->last_cycle * (uint64_t)44100; + last_sample /= (uint64_t)writer->master_clock; + uint64_t sample = ((uint64_t)cycle + (uint64_t)writer->extra_delta) * (uint64_t)44100; + sample /= (uint64_t)writer->master_clock; + uint32_t delta = sample - last_sample; - uint32_t mclks_per_sample = writer->master_clock / 44100; - writer->last_cycle += delta * mclks_per_sample; + writer->last_cycle = cycle; + writer->extra_delta = 0; writer->header.num_samples += delta; wait_commands(writer, delta); } +static uint8_t last_cmd; void vgm_sn76489_write(vgm_writer *writer, uint32_t cycle, uint8_t value) { add_wait(writer, cycle); uint8_t cmd[2] = {CMD_PSG, value}; + last_cmd = CMD_PSG; fwrite(cmd, 1, sizeof(cmd), writer->f); } @@ -88,6 +99,7 @@ { add_wait(writer, cycle); uint8_t cmd[3] = {CMD_YM2612_0, reg, value}; + last_cmd = CMD_YM2612_0; fwrite(cmd, 1, sizeof(cmd), writer->f); } @@ -95,12 +107,14 @@ { add_wait(writer, cycle); uint8_t cmd[3] = {CMD_YM2612_1, reg, value}; + last_cmd = CMD_YM2612_1; fwrite(cmd, 1, sizeof(cmd), writer->f); } void vgm_adjust_cycles(vgm_writer *writer, uint32_t deduction) { if (deduction > writer->last_cycle) { + writer->extra_delta += deduction - writer->last_cycle; writer->last_cycle = 0; } else { writer->last_cycle -= deduction; @@ -109,6 +123,8 @@ void vgm_close(vgm_writer *writer) { + uint8_t cmd = 0x66; + fwrite(&cmd, 1, sizeof(cmd), writer->f); writer->header.eof_offset = ftell(writer->f) - offsetof(vgm_header, eof_offset); fseek(writer->f, SEEK_SET, 0); fwrite(&writer->header, sizeof(writer->header), 1, writer->f); diff -r 0d5f88e53dca -r 638eb2d25696 vgm.h --- a/vgm.h Sun May 10 00:16:00 2020 -0700 +++ b/vgm.h Thu Aug 05 09:29:33 2021 -0700 @@ -79,6 +79,7 @@ FILE *f; uint32_t master_clock; uint32_t last_cycle; + uint32_t extra_delta; } vgm_writer; vgm_writer *vgm_write_open(char *filename, uint32_t rate, uint32_t clock, uint32_t cycle); diff -r 0d5f88e53dca -r 638eb2d25696 vgmplay.c --- a/vgmplay.c Sun May 10 00:16:00 2020 -0700 +++ b/vgmplay.c Thu Aug 05 09:29:33 2021 -0700 @@ -25,6 +25,10 @@ system_header *current_system; +void system_request_exit(system_header *system, uint8_t force_release) +{ +} + void handle_keydown(int keycode) { } diff -r 0d5f88e53dca -r 638eb2d25696 ym2612.c --- a/ym2612.c Sun May 10 00:16:00 2020 -0700 +++ b/ym2612.c Thu Aug 05 09:29:33 2021 -0700 @@ -179,6 +179,11 @@ for (int i = 0; i < NUM_CHANNELS; i++) { context->channels[i].lr = 0xC0; context->channels[i].logfile = savedlogs[i]; + if (i < 3) { + context->part1_regs[REG_LR_AMS_PMS - YM_PART1_START + i] = 0xC0; + } else { + context->part2_regs[REG_LR_AMS_PMS - YM_PART2_START + i - 3] = 0xC0; + } } context->write_cycle = CYCLE_NEVER; for (int i = 0; i < NUM_OPERATORS; i++) { @@ -402,6 +407,9 @@ context->timer_b = context->timer_b_load; } } + } else if (context->timer_control & BIT_TIMERB_LOAD) { + context->timer_control &= ~BIT_TIMERB_LOAD; + context->timer_b = context->timer_b_load; } context->sub_timer_b += 0x10; //Update LFO