# HG changeset patch # User Michael Pavone # Date 1587235373 25200 # Node ID 374a5ae694e815b87900db6fccaa0db7b634b667 # Parent 13abdc98379e29c2164cb1ae05f859a850d47473# Parent 0f135b214927332f11dbf1d753fcf1e761e328e4 Merge from default diff -r 13abdc98379e -r 374a5ae694e8 Makefile --- a/Makefile Thu Apr 18 22:06:47 2019 -0700 +++ b/Makefile Sat Apr 18 11:42:53 2020 -0700 @@ -116,7 +116,7 @@ LDFLAGS:=-lm else CFLAGS:=$(shell pkg-config --cflags-only-I $(LIBS)) $(CFLAGS) -LDFLAGS:=-lm $(shell pkg-config --libs $(LIBS)) $(GLES_LIB) +LDFLAGS:=-lm $(shell pkg-config --libs $(LIBS)) ifdef USE_FBDEV LDFLAGS+= -pthread endif @@ -179,26 +179,26 @@ endif endif -ifdef USE_NATIVE TRANSOBJS=gen.o backend.o $(MEM) arena.o tern.o -M68KOBJS=68kinst.o m68k_core.o +M68KOBJS=68kinst.o + +ifdef NEW_CORE +Z80OBJS=z80.o z80inst.o +M68KOBJS+= m68k_core.o musashi/m68kops.o musashi/m68kcpu.o +CFLAGS+= -DNEW_CORE +else Z80OBJS=z80inst.o z80_to_x86.o ifeq ($(CPU),x86_64) -M68KOBJS+= m68k_core_x86.o +M68KOBJS+= m68k_core.o m68k_core_x86.o TRANSOBJS+= gen_x86.o backend_x86.o else ifeq ($(CPU),i686) -M68KOBJS+= m68k_core_x86.o +M68KOBJS+= m68k_core.o m68k_core_x86.o TRANSOBJS+= gen_x86.o backend_x86.o endif endif -CFLAGS+= -DUSE_NATIVE -else -Z80OBJS=z80.o z80inst.o -TRANSOBJS=backend.o tern.o -M68KOBJS=68kinst.o m68k_core.o musashi/m68kops.o musashi/m68kcpu.o endif -AUDIOOBJS=ym2612.o psg.o wave.o +AUDIOOBJS=ym2612.o psg.o wave.o vgm.o render_audio.o CONFIGOBJS=config.o tern.o util.o paths.o NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o RENDEROBJS=ppm.o controller_info.o @@ -308,7 +308,7 @@ ztestgen : ztestgen.o z80inst.o $(CC) -ggdb -o ztestgen ztestgen.o z80inst.o -stateview$(EXE) : stateview.o vdp.o $(RENDEROBJS) serialize.o $(CONFIGOBJS) gst.o +stateview$(EXE) : stateview.o vdp.o $(RENDEROBJS) serialize.o $(CONFIGOBJS) gst.o render_audio.o $(CC) -o $@ $^ $(LDFLAGS) $(FIXUP) ./$@ @@ -343,6 +343,9 @@ vos_prog_info : vos_prog_info.o vos_program_module.o $(CC) -o vos_prog_info vos_prog_info.o vos_program_module.o +m68k.c : m68k.cpu cpu_dsl.py + ./cpu_dsl.py -d call $< > $@ + %.c : %.cpu cpu_dsl.py ./cpu_dsl.py -d goto $< > $@ diff -r 13abdc98379e -r 374a5ae694e8 backend.c --- a/backend.c Thu Apr 18 22:06:47 2019 -0700 +++ b/backend.c Sat Apr 18 11:42:53 2020 -0700 @@ -6,7 +6,7 @@ #include "backend.h" #include -#ifdef USE_NATIVE +#ifndef NEW_CORE deferred_addr * defer_address(deferred_addr * old_head, uint32_t address, uint8_t *dest) { deferred_addr * new_head = malloc(sizeof(deferred_addr)); diff -r 13abdc98379e -r 374a5ae694e8 backend.h --- a/backend.h Thu Apr 18 22:06:47 2019 -0700 +++ b/backend.h Sat Apr 18 11:42:53 2020 -0700 @@ -8,7 +8,7 @@ #include #include -#ifdef USE_NATIVE +#ifndef NEW_CORE #include "gen.h" #else typedef uint8_t * code_ptr; @@ -54,14 +54,14 @@ typedef struct { uint32_t flags; -#ifdef USE_NATIVE +#ifndef NEW_CORE native_map_slot *native_code_map; deferred_addr *deferred; code_info code; uint8_t **ram_inst_sizes; #endif memmap_chunk const *memmap; -#ifdef USE_NATIVE +#ifndef NEW_CORE code_ptr save_context; code_ptr load_context; code_ptr handle_cycle_limit; @@ -76,7 +76,7 @@ uint32_t max_address; uint32_t bus_cycles; uint32_t clock_divider; -#ifdef USE_NATIVE +#ifndef NEW_CORE uint32_t move_pc_off; uint32_t move_pc_size; int32_t mem_ptr_off; @@ -85,7 +85,7 @@ #endif uint8_t address_size; uint8_t byte_swap; -#ifdef USE_NATIVE +#ifndef NEW_CORE int8_t context_reg; int8_t cycles; int8_t limit; @@ -95,7 +95,7 @@ #endif } cpu_options; -#ifdef USE_NATIVE +#ifndef NEW_CORE typedef uint8_t * (*native_addr_func)(void * context, uint32_t address); deferred_addr * defer_address(deferred_addr * old_head, uint32_t address, uint8_t *dest); diff -r 13abdc98379e -r 374a5ae694e8 bindings.c --- a/bindings.c Thu Apr 18 22:06:47 2019 -0700 +++ b/bindings.c Sat Apr 18 11:42:53 2020 -0700 @@ -36,6 +36,7 @@ UI_RELOAD, UI_SMS_PAUSE, UI_SCREENSHOT, + UI_VGM_LOG, UI_EXIT, UI_PLANE_DEBUG, UI_VRAM_DEBUG, @@ -258,6 +259,39 @@ #define localtime_r(a,b) localtime(a) #endif +char *get_content_config_path(char *config_path, char *config_template, char *default_name) +{ + char *base = tern_find_path(config, config_path, TVAL_PTR).ptrval; + if (!base) { + base = "$HOME"; + } + const system_media *media = current_media(); + tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir()); + vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir()); + vars = tern_insert_ptr(vars, "USERDATA", (char *)get_userdata_dir()); + vars = tern_insert_ptr(vars, "ROMNAME", media->name); + vars = tern_insert_ptr(vars, "ROMDIR", media->dir); + base = replace_vars(base, vars, 1); + tern_free(vars); + ensure_dir_exists(base); + time_t now = time(NULL); + struct tm local_store; + char fname_part[256]; + char *template = tern_find_path(config, config_template, TVAL_PTR).ptrval; + if (template) { + vars = tern_insert_ptr(NULL, "ROMNAME", media->name); + template = replace_vars(template, vars, 0); + } else { + template = strdup(default_name); + } + strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store)); + char const *parts[] = {base, PATH_SEP, fname_part}; + char *path = alloc_concat_m(3, parts); + free(base); + free(template); + return path; +} + void handle_binding_up(keybinding * binding) { uint8_t allow_content_binds = content_binds_enabled && current_system; @@ -352,31 +386,23 @@ current_system->gamepad_down(current_system, GAMEPAD_MAIN_UNIT, MAIN_UNIT_PAUSE); } break; - case UI_SCREENSHOT: { + case UI_SCREENSHOT: if (allow_content_binds) { - char *screenshot_base = tern_find_path(config, "ui\0screenshot_path\0", TVAL_PTR).ptrval; - if (!screenshot_base) { - screenshot_base = "$HOME"; - } - tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir()); - vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir()); - screenshot_base = replace_vars(screenshot_base, vars, 1); - tern_free(vars); - time_t now = time(NULL); - struct tm local_store; - char fname_part[256]; - char *template = tern_find_path(config, "ui\0screenshot_template\0", TVAL_PTR).ptrval; - if (!template) { - template = "blastem_%c.ppm"; - } - strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store)); - char const *parts[] = {screenshot_base, PATH_SEP, fname_part}; - char *path = alloc_concat_m(3, parts); - free(screenshot_base); + char *path = get_content_config_path("ui\0screenshot_path\0", "ui\0screenshot_template\0", "blastem_%c.ppm"); render_save_screenshot(path); } break; - } + case UI_VGM_LOG: + if (allow_content_binds && current_system->start_vgm_log) { + if (current_system->vgm_logging) { + current_system->stop_vgm_log(current_system); + } else { + char *path = get_content_config_path("ui\0vgm_path\0", "ui\0vgm_template\0", "blastem_%c.vgm"); + current_system->start_vgm_log(current_system, path); + free(path); + } + } + break; case UI_EXIT: #ifndef DISABLE_NUKLEAR if (is_nuklear_active()) { @@ -625,6 +651,8 @@ *subtype_a = UI_SMS_PAUSE; } else if (!strcmp(target + 3, "screenshot")) { *subtype_a = UI_SCREENSHOT; + } else if (!strcmp(target + 3, "vgm_log")) { + *subtype_a = UI_VGM_LOG; } else if(!strcmp(target + 3, "exit")) { *subtype_a = UI_EXIT; } else if (!strcmp(target + 3, "plane_debug")) { diff -r 13abdc98379e -r 374a5ae694e8 blastcpm.c --- a/blastcpm.c Thu Apr 18 22:06:47 2019 -0700 +++ b/blastcpm.c Sat Apr 18 11:42:53 2020 -0700 @@ -5,7 +5,7 @@ #include #include -#ifdef USE_NATIVE +#ifndef NEW_CORE #include "z80_to_x86.h" #else #include "z80.h" @@ -19,7 +19,7 @@ #define OS_RESET 0xE403 int headless = 1; -#ifdef USE_NATIVE +#ifndef NEW_CORE void z80_next_int_pulse(z80_context * context) { context->int_pulse_start = context->int_pulse_end = CYCLE_NEVER; @@ -68,7 +68,7 @@ { time_t duration = time(NULL) - start; z80_context *z80 = context; -#ifdef USE_NATIVE +#ifndef NEW_CORE total_cycles += context->current_cycle; #else total_cycles += z80->cycles; @@ -124,7 +124,7 @@ for(;;) { z80_run(context, 1000000); -#ifdef USE_NATIVE +#ifndef NEW_CORE total_cycles += context->current_cycle; context->current_cycle = 0; #else diff -r 13abdc98379e -r 374a5ae694e8 blastem.c --- a/blastem.c Thu Apr 18 22:06:47 2019 -0700 +++ b/blastem.c Sat Apr 18 11:42:53 2020 -0700 @@ -11,7 +11,7 @@ #include "system.h" #include "68kinst.h" #include "m68k_core.h" -#ifdef USE_NATIVE +#ifndef NEW_CORE #include "z80_to_x86.h" #else #include "z80.h" @@ -267,6 +267,7 @@ savedir_template = "$USERDATA/blastem/$ROMNAME"; } tern_node *vars = tern_insert_ptr(NULL, "ROMNAME", media->name); + vars = tern_insert_ptr(vars, "ROMDIR", media->dir); vars = tern_insert_ptr(vars, "HOME", get_home_dir()); vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir()); vars = tern_insert_ptr(vars, "USERDATA", (char *)get_userdata_dir()); @@ -343,6 +344,11 @@ } static system_media cart, lock_on; +const system_media *current_media(void) +{ + return &cart; +} + void reload_media(void) { if (!current_system) { @@ -382,7 +388,7 @@ { if (game_system) { game_system->persist_save(game_system); -#ifdef USE_NATIVE +#ifndef NEW_CORE //swap to game context arena and mark all allocated pages in it free if (current_system == menu_system) { current_system->arena = set_current_arena(game_system->arena); @@ -390,7 +396,7 @@ mark_all_free(); #endif game_system->free_context(game_system); -#ifdef USE_NATIVE +#ifndef NEW_CORE } else if(current_system) { //start a new arena and save old one in suspended system context current_system->arena = start_new_arena(); @@ -461,7 +467,7 @@ debug_target = 1; } break; -#ifdef USE_NATIVE +#ifndef NEW_CORE case 'D': gdb_remote_init(); dtype = DEBUGGER_GDB; @@ -692,7 +698,7 @@ current_system->enter_debugger = start_in_debugger && menu == debug_target; current_system->start_context(current_system, statefile); } else if (menu && game_system) { -#ifdef USE_NATIVE +#ifndef NEW_CORE current_system->arena = set_current_arena(game_system->arena); #endif current_system = game_system; @@ -704,7 +710,7 @@ ui_idle_loop(); #endif } else { -#ifdef USE_NATIVE +#ifndef NEW_CORE current_system->arena = set_current_arena(menu_system->arena); #endif current_system = menu_system; diff -r 13abdc98379e -r 374a5ae694e8 blastem.h --- a/blastem.h Thu Apr 18 22:06:47 2019 -0700 +++ b/blastem.h Sat Apr 18 11:42:53 2020 -0700 @@ -19,5 +19,6 @@ void lockon_media(char *lock_on_path); void init_system_with_media(const char *path, system_type force_stype); void apply_updated_config(void); +const system_media *current_media(void); #endif //BLASTEM_H_ diff -r 13abdc98379e -r 374a5ae694e8 build_release --- a/build_release Thu Apr 18 22:06:47 2019 -0700 +++ b/build_release Sat Apr 18 11:42:53 2020 -0700 @@ -62,7 +62,7 @@ echo $dir rm -rf "$dir" mkdir "$dir" -cp -r $binaries shaders images default.cfg rom.db gamecontrollerdb.txt "$dir" +cp -r $binaries shaders images default.cfg rom.db gamecontrollerdb.txt systems.cfg "$dir" for file in README COPYING CHANGELOG; do cp "$file" "$dir"/"$file$txt" done diff -r 13abdc98379e -r 374a5ae694e8 config.c --- a/config.c Thu Apr 18 22:06:47 2019 -0700 +++ b/config.c Sat Apr 18 11:42:53 2020 -0700 @@ -6,6 +6,7 @@ #include "tern.h" #include "util.h" #include "paths.h" +#include "config.h" #include #include #include @@ -225,7 +226,7 @@ return ret; } -tern_node *load_overrideable_config(char *name, char *bundled_name) +tern_node *load_overrideable_config(char *name, char *bundled_name, uint8_t *used_config_dir) { char const *confdir = get_config_dir(); char *confpath = NULL; @@ -233,58 +234,60 @@ if (confdir) { confpath = path_append(confdir, name); ret = parse_config_file(confpath); - if (ret) { - free(confpath); - return ret; + } + free(confpath); + if (used_config_dir) { + *used_config_dir = ret != NULL; + } + + if (!ret) { + ret = parse_bundled_config(name); + if (!ret) { + ret = parse_bundled_config(bundled_name); } } - ret = parse_bundled_config(bundled_name); - if (ret) { - free(confpath); - return ret; - } - return NULL; + return ret; } +static uint8_t app_config_in_config_dir; tern_node *load_config() { - char const *confdir = get_config_dir(); - char *confpath = NULL; - tern_node *ret = load_overrideable_config("blastem.cfg", "default.cfg"); - if (confdir) { - confpath = path_append(confdir, "blastem.cfg"); - ret = parse_config_file(confpath); - if (ret) { - free(confpath); - return ret; + tern_node *ret = load_overrideable_config("blastem.cfg", "default.cfg", &app_config_in_config_dir); + + if (!ret) { + if (get_config_dir()) { + fatal_error("Failed to find a config file at %s or in the blastem executable directory\n", get_config_dir()); + } else { + fatal_error("Failed to find a config file in the BlastEm executable directory and the config directory path could not be determined\n"); } } - - ret = parse_bundled_config("default.cfg"); - if (ret) { - free(confpath); - return ret; - } - - if (get_config_dir()) { - fatal_error("Failed to find a config file at %s or in the blastem executable directory\n", get_config_dir()); - } else { - fatal_error("Failed to find a config file in the BlastEm executable directory and the config directory path could not be determined\n"); - } - //this will never get reached, but the compiler doesn't know that. Let's make it happy - return NULL; + return ret; } -void persist_config_at(tern_node *config, char *fname) +void persist_config_at(tern_node *app_config, tern_node *to_save, char *fname) { - char const *confdir = get_config_dir(); - if (!confdir) { - fatal_error("Failed to locate config file directory\n"); + char*use_exe_dir = tern_find_path_default(app_config, "ui\0config_in_exe_dir\0", (tern_val){.ptrval = "off"}, TVAL_PTR).ptrval; + char *confpath; + if (!strcmp(use_exe_dir, "on")) { + confpath = path_append(get_exe_dir(), fname); + if (app_config == to_save && app_config_in_config_dir) { + //user switched to "portable" configs this session and there is an + //existing config file in the user-specific config directory + //delete it so we don't end up loading it next time + char *oldpath = path_append(get_config_dir(), fname); + delete_file(oldpath); + free(oldpath); + } + } else { + char const *confdir = get_config_dir(); + if (!confdir) { + fatal_error("Failed to locate config file directory\n"); + } + ensure_dir_exists(confdir); + confpath = path_append(confdir, fname); } - ensure_dir_exists(confdir); - char *confpath = path_append(confdir, fname); - if (!serialize_config_file(config, confpath)) { + if (!serialize_config_file(to_save, confpath)) { fatal_error("Failed to write config to %s\n", confpath); } free(confpath); @@ -292,7 +295,7 @@ void persist_config(tern_node *config) { - persist_config_at(config, "blastem.cfg"); + persist_config_at(config, config, "blastem.cfg"); } char **get_extension_list(tern_node *config, uint32_t *num_exts_out) @@ -320,3 +323,18 @@ char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval; return lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : DEFAULT_LOWPASS_CUTOFF; } + +tern_node *get_systems_config(void) +{ + static tern_node *systems; + if (!systems) { + systems = parse_bundled_config("systems.cfg"); + } + return systems; +} + +tern_node *get_model(tern_node *config, system_type stype) +{ + char *model = tern_find_path_default(config, "system\0model\0", (tern_val){.ptrval = "md1va3"}, TVAL_PTR).ptrval; + return tern_find_node(get_systems_config(), model); +} diff -r 13abdc98379e -r 374a5ae694e8 config.h --- a/config.h Thu Apr 18 22:06:47 2019 -0700 +++ b/config.h Sat Apr 18 11:42:53 2020 -0700 @@ -6,17 +6,20 @@ #ifndef CONFIG_H_ #define CONFIG_H_ #include "tern.h" +#include "system.h" tern_node *parse_config_file(char *config_path); tern_node *parse_bundled_config(char *config_name); -tern_node *load_overrideable_config(char *name, char *bundled_name); +tern_node *load_overrideable_config(char *name, char *bundled_name, uint8_t *used_config_dir); tern_node *load_config(); char *serialize_config(tern_node *config, uint32_t *size_out); uint8_t serialize_config_file(tern_node *config, char *path); -void persist_config_at(tern_node *config, char *fname); +void persist_config_at(tern_node *app_config, tern_node *to_save, char *fname); void persist_config(tern_node *config); 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); +tern_node *get_model(tern_node *config, system_type stype); #endif //CONFIG_H_ diff -r 13abdc98379e -r 374a5ae694e8 controller_info.c --- a/controller_info.c Thu Apr 18 22:06:47 2019 -0700 +++ b/controller_info.c Sat Apr 18 11:42:53 2020 -0700 @@ -6,6 +6,8 @@ #include "controller_info.h" #include "config.h" #include "util.h" +#include "blastem.h" +#include "bindings.h" typedef struct { char const *name; @@ -66,7 +68,7 @@ static void load_ctype_config(void) { if (!loaded) { - info_config = load_overrideable_config("controller_types.cfg", "controller_types.cfg"); + info_config = load_overrideable_config("controller_types.cfg", "controller_types.cfg", NULL); loaded = 1; } } @@ -194,10 +196,11 @@ char guid_string[33]; SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string)); tern_node *existing = tern_find_node(info_config, guid_string); - existing = tern_insert_ptr(existing, "subtype", (void *)subtype_names[info->subtype]); - existing = tern_insert_ptr(existing, "variant", (void *)variant_names[info->variant]); + existing = tern_insert_ptr(existing, "subtype", strdup(subtype_names[info->subtype])); + existing = tern_insert_ptr(existing, "variant", strdup(variant_names[info->variant])); info_config = tern_insert_node(info_config, guid_string, existing); - persist_config_at(info_config, "controller_types.cfg"); + persist_config_at(config, info_config, "controller_types.cfg"); + handle_joy_added(joystick); #endif } @@ -209,7 +212,12 @@ tern_node *existing = tern_find_node(info_config, guid_string); existing = tern_insert_ptr(existing, "mapping", mapping_string); info_config = tern_insert_node(info_config, guid_string, existing); - persist_config_at(info_config, "controller_types.cfg"); + persist_config_at(config, info_config, "controller_types.cfg"); + const char *parts[] = {guid_string, ",", mapping_string}; + char * full = alloc_concat_m(3, parts); + SDL_GameControllerAddMapping(full); + free(full); + handle_joy_added(joystick); #endif } diff -r 13abdc98379e -r 374a5ae694e8 cpu_dsl.py --- a/cpu_dsl.py Thu Apr 18 22:06:47 2019 -0700 +++ b/cpu_dsl.py Sat Apr 18 11:42:53 2020 -0700 @@ -48,6 +48,7 @@ self.regValues = {} self.varyingBits = 0 self.invalidFieldValues = {} + self.invalidCombos = [] self.newLocals = [] for field in fields: self.varyingBits += fields[field][1] @@ -58,9 +59,17 @@ size = int(op.params[1]) self.locals[name] = size elif op.op == 'invalid': - name = op.params[0] - value = int(op.params[1]) - self.invalidFieldValues.setdefault(name, set()).add(value) + if len(op.params) < 3: + name = op.params[0] + value = int(op.params[1]) + self.invalidFieldValues.setdefault(name, set()).add(value) + else: + vmap = {} + for i in range(0, len(op.params), 2): + name = op.params[i] + value = int(op.params[i+1]) + vmap[name] = value + self.invalidCombos.append(vmap) else: self.implementation.append(op) @@ -89,12 +98,29 @@ for i in range(0, 1 << self.varyingBits): iword = self.value doIt = True + combos = [] + for combo in self.invalidCombos: + combos.append(dict(combo)) for field in self.fields: shift,bits = self.fields[field] val = i & ((1 << bits) - 1) if field in self.invalidFieldValues and val in self.invalidFieldValues[field]: doIt = False break + nextcombos = [] + for combo in combos: + if field in combo: + if combo[field] == val: + del combo[field] + if not combo: + doIt = False + break + else: + continue + nextcombos.append(combo) + combos = nextcombos + if not doIt: + break i >>= bits iword |= val << shift if doIt: @@ -132,7 +158,7 @@ self.processOps(prog, fieldVals, output, otype, self.implementation) if prog.dispatch == 'call': - begin = '\nvoid ' + self.generateName(value) + '(' + prog.context_type + ' *context)\n{' + begin = '\nvoid ' + self.generateName(value) + '(' + prog.context_type + ' *context, uint32_t target_cycle)\n{' elif prog.dispatch == 'goto': begin = '\n' + self.generateName(value) + ': {' else: @@ -143,6 +169,8 @@ output.append(prog.flags.disperseFlags(prog, otype)) for var in self.newLocals: begin += '\n\tuint{sz}_t {name};'.format(sz=self.locals[var], name=var) + for size in prog.temp: + begin += '\n\tuint{sz}_t gen_tmp{sz}__;'.format(sz=size) prog.popScope() if prog.dispatch == 'goto': output += prog.nextInstruction(otype) @@ -237,6 +265,20 @@ else: a = params[0] b = params[1] + needsSizeAdjust = False + if len(params) > 3: + size = params[3] + if size == 0: + size = 8 + elif size == 1: + size = 16 + else: + size = 32 + prog.lastSize = size + destSize = prog.paramSize(rawParams[2]) + if destSize > size: + needsSizeAdjust = True + prog.sizeAdjust = size needsCarry = needsOflow = needsHalf = False if flagUpdates: for flag in flagUpdates: @@ -248,20 +290,35 @@ elif calc == 'overflow': needsOflow = True decl = '' - if needsCarry or needsOflow or needsHalf: - size = prog.paramSize(rawParams[2]) + if needsCarry or needsOflow or needsHalf or (flagUpdates and needsSizeAdjust): + if len(params) <= 3: + size = prog.paramSize(rawParams[2]) if needsCarry and op != 'lsr': size *= 2 decl,name = prog.getTemp(size) dst = prog.carryFlowDst = name prog.lastA = a prog.lastB = b + if size == 64: + a = '((uint64_t){a})'.format(a=a) + b = '((uint64_t){b})'.format(b=b) prog.lastBFlow = b if op == '-' else '(~{b})'.format(b=b) + elif needsSizeAdjust: + decl,name = prog.getTemp(size) + dst = params[2] + return '{decl}\n\t{tmp} = ({a} & {mask}) {op} ({b} & {mask});\n\t{dst} = ({dst} & ~{mask}) | {tmp};'.format( + decl = decl, tmp = name, a = a, b = b, op = op, dst = dst, mask = ((1 << size) - 1) + ) else: dst = params[2] - return decl + '\n\t{dst} = {a} {op} {b};'.format( - dst = dst, a = a, b = b, op = op - ) + if needsSizeAdjust: + return decl + '\n\t{dst} = ({a} & {mask}) {op} ({b} & {mask});'.format( + dst = dst, a = a, b = b, op = op, mask = (1 << prog.sizeAdjust) - 1 + ) + else: + return decl + '\n\t{dst} = {a} {op} {b};'.format( + dst = dst, a = a, b = b, op = op + ) self.impls['c'] = _impl self.outOp = (2,) return self @@ -269,6 +326,20 @@ def _impl(prog, params, rawParams, flagUpdates): dst = params[1] decl = '' + needsSizeAdjust = False + if len(params) > 2: + size = params[2] + if size == 0: + size = 8 + elif size == 1: + size = 16 + else: + size = 32 + prog.lastSize = size + destSize = prog.paramSize(rawParams[1]) + if destSize > size: + needsSizeAdjust = True + prog.sizeAdjust = size if op == '-': if flagUpdates: for flag in flagUpdates: @@ -279,7 +350,7 @@ needsHalf = True elif calc == 'overflow': needsOflow = True - if needsCarry or needsOflow or needsHalf: + if needsCarry or needsOflow or needsHalf or (flagUpdates and needsSizeAdjust): size = prog.paramSize(rawParams[1]) if needsCarry: size *= 2 @@ -288,9 +359,14 @@ prog.lastA = 0 prog.lastB = params[0] prog.lastBFlow = params[0] - return decl + '\n\t{dst} = {op}{a};'.format( - dst = dst, a = params[0], op = op - ) + if needsSizeAdjust: + return decl + '\n\t{dst} = ({dst} & ~{mask}) | (({op}{a}) & {mask});'.format( + dst = dst, a = params[0], op = op, mask = (1 << prog.sizeAdjust) - 1 + ) + else: + return decl + '\n\t{dst} = {op}{a};'.format( + dst = dst, a = params[0], op = op + ) self.impls['c'] = _impl self.outOp = (1,) return self @@ -336,7 +412,7 @@ else: table = params[1] if prog.dispatch == 'call': - return '\n\timpl_{tbl}[{op}](context);'.format(tbl = table, op = params[0]) + return '\n\timpl_{tbl}[{op}](context, target_cycle);'.format(tbl = table, op = params[0]) elif prog.dispatch == 'goto': return '\n\tgoto *impl_{tbl}[{op}];'.format(tbl = table, op = params[0]) else: @@ -358,25 +434,25 @@ if calc == 'bit' or calc == 'sign' or calc == 'carry' or calc == 'half' or calc == 'overflow': myRes = lastDst if calc == 'sign': - resultBit = prog.paramSize(prog.lastDst) - 1 + resultBit = prog.getLastSize() - 1 elif calc == 'carry': if prog.lastOp.op in ('asr', 'lsr'): resultBit = 0 myRes = prog.lastA else: - resultBit = prog.paramSize(prog.lastDst) + resultBit = prog.getLastSize() if prog.lastOp.op == 'ror': resultBit -= 1 elif calc == 'half': - resultBit = prog.paramSize(prog.lastDst) - 4 + resultBit = prog.getLastSize() - 4 myRes = '({a} ^ {b} ^ {res})'.format(a = prog.lastA, b = prog.lastB, res = lastDst) elif calc == 'overflow': - resultBit = prog.paramSize(prog.lastDst) - 1 + resultBit = prog.getLastSize() - 1 myRes = '((({a} ^ {b})) & ({a} ^ {res}))'.format(a = prog.lastA, b = prog.lastBFlow, res = lastDst) else: #Note: offsetting this by the operation size - 8 makes sense for the Z80 #but might not for other CPUs with this kind of fixed bit flag behavior - resultBit = int(resultBit) + prog.paramSize(prog.lastDst) - 8 + resultBit = int(resultBit) + prog.getLastSize() - 8 if type(storage) is tuple: reg,storageBit = storage if storageBit == resultBit: @@ -401,7 +477,7 @@ output.append('\n\t{reg} = {res} & {mask}U;'.format(reg=reg, res=myRes, mask = 1 << resultBit)) elif calc == 'zero': if prog.carryFlowDst: - realSize = prog.paramSize(prog.lastDst) + realSize = prog.getLastSize() if realSize != prog.paramSize(prog.carryFlowDst): lastDst = '({res} & {mask})'.format(res=lastDst, mask = (1 << realSize) - 1) if type(storage) is tuple: @@ -417,7 +493,7 @@ )) elif calc == 'parity': parity = storage - paritySize = prog.paramSize(prog.lastDst) + paritySize = prog.getLastSize() if prog.carryFlowDst: parityDst = paritySrc = prog.carryFlowDst else: @@ -441,7 +517,13 @@ )) if prog.carryFlowDst: if prog.lastOp.op != 'cmp': - output.append('\n\t{dst} = {tmpdst};'.format(dst = prog.resolveParam(prog.lastDst, prog.currentScope, {}), tmpdst = prog.carryFlowDst)) + if prog.sizeAdjust: + output.append('\n\t{dst} = ({dst} & ~{mask}) | ({tmpdst} & {mask});'.format( + dst = prog.resolveParam(prog.lastDst, prog.currentScope, {}), tmpdst = prog.carryFlowDst, mask = ((1 << prog.sizeAdjust) - 1) + )) + prog.sizeAdjust = None + else: + output.append('\n\t{dst} = {tmpdst};'.format(dst = prog.resolveParam(prog.lastDst, prog.currentScope, {}), tmpdst = prog.carryFlowDst)) prog.carryFlowDst = None if parity: if paritySize > 8: @@ -500,6 +582,17 @@ if not scope.resolveLocal(tmpvar): scope.addLocal(tmpvar, size) prog.lastDst = rawParams[1] + if len(params) > 2: + size = params[2] + if size == 0: + size = 8 + elif size == 1: + size = 16 + else: + size = 32 + prog.lastSize = size + else: + prog.lastSize = None return '\n\t{var} = {b} - {a};'.format(var = tmpvar, a = params[0], b = params[1]) def _asrCImpl(prog, params, rawParams, flagUpdates): @@ -523,22 +616,23 @@ def _sext(size, src): if size == 16: - return src | 0xFF00 if src & 0x80 else src + return src | 0xFF00 if src & 0x80 else src & 0x7F else: - return src | 0xFFFF0000 if src & 0x8000 else src + return src | 0xFFFF0000 if src & 0x8000 else src & 0x7FFF def _sextCImpl(prog, params, rawParms): if params[0] == 16: - fmt = '\n\t{dst} = {src} & 0x80 ? {src} | 0xFF00 : {src};' + fmt = '\n\t{dst} = {src} & 0x80 ? {src} | 0xFF00 : {src} & 0x7F;' else: - fmt = '\n\t{dst} = {src} & 0x8000 ? {src} | 0xFFFF0000 : {src};' + fmt = '\n\t{dst} = {src} & 0x8000 ? {src} | 0xFFFF0000 : {src} & 0x7FFF;' return fmt.format(src=params[1], dst=params[2]) def _getCarryCheck(prog): carryFlag = None - for flag in prog.flags.flagCalc: + for flag in prog.flags.flagOrder: if prog.flags.flagCalc[flag] == 'carry': carryFlag = flag + break if carryFlag is None: raise Exception('adc requires a defined carry flag') carryStorage = prog.flags.getStorage(carryFlag) @@ -550,6 +644,20 @@ return prog.resolveReg(carryStorage, None, (), False) def _adcCImpl(prog, params, rawParams, flagUpdates): + needsSizeAdjust = False + if len(params) > 3: + size = params[3] + if size == 0: + size = 8 + elif size == 1: + size = 16 + else: + size = 32 + prog.lastSize = size + destSize = prog.paramSize(rawParams[2]) + if destSize > size: + needsSizeAdjust = True + prog.sizeAdjust = size needsCarry = needsOflow = needsHalf = False if flagUpdates: for flag in flagUpdates: @@ -562,8 +670,10 @@ needsOflow = True decl = '' carryCheck = _getCarryCheck(prog) - if needsCarry or needsOflow or needsHalf: - size = prog.paramSize(rawParams[2]) + vals = '1 : 0' + if needsCarry or needsOflow or needsHalf or (flagUpdates and needsSizeAdjust): + if len(params) <= 3: + size = prog.paramSize(rawParams[2]) if needsCarry: size *= 2 decl,name = prog.getTemp(size) @@ -571,13 +681,37 @@ prog.lastA = params[0] prog.lastB = params[1] prog.lastBFlow = '(~{b})'.format(b=params[1]) + if size == 64: + params[0] = '((uint64_t){a})'.format(a=params[0]) + params[1] = '((uint64_t){b})'.format(b=params[1]) + vals = '((uint64_t)1) : ((uint64_t)0)' + elif needsSizeAdjust: + decl,name = prog.getTemp(size) + dst = params[2] + return '{decl}\n\t{tmp} = ({a} & {mask}) + ({b} & {mask}) + ({check} ? 1 : 0);\n\t{dst} = ({dst} & ~{mask}) | {tmp};'.format( + decl = decl, tmp = name, a = a, b = b, op = op, dst = dst, mask = ((1 << size) - 1), check = carryCheck + ) else: dst = params[2] - return decl + '\n\t{dst} = {a} + {b} + ({check} ? 1 : 0);'.format(dst = dst, - a = params[0], b = params[1], check = carryCheck + return decl + '\n\t{dst} = {a} + {b} + ({check} ? {vals});'.format(dst = dst, + a = params[0], b = params[1], check = carryCheck, vals = vals ) def _sbcCImpl(prog, params, rawParams, flagUpdates): + needsSizeAdjust = False + if len(params) > 3: + size = params[3] + if size == 0: + size = 8 + elif size == 1: + size = 16 + else: + size = 32 + prog.lastSize = size + destSize = prog.paramSize(rawParams[2]) + if destSize > size: + needsSizeAdjust = True + prog.sizeAdjust = size needsCarry = needsOflow = needsHalf = False if flagUpdates: for flag in flagUpdates: @@ -590,7 +724,8 @@ needsOflow = True decl = '' carryCheck = _getCarryCheck(prog) - if needsCarry or needsOflow or needsHalf: + vals = '1 : 0' + if needsCarry or needsOflow or needsHalf or (flagUpdates and needsSizeAdjust): size = prog.paramSize(rawParams[2]) if needsCarry: size *= 2 @@ -599,10 +734,20 @@ prog.lastA = params[1] prog.lastB = params[0] prog.lastBFlow = params[0] + if size == 64: + params[1] = '((uint64_t){a})'.format(a=params[1]) + params[0] = '((uint64_t){b})'.format(b=params[0]) + vals = '((uint64_t)1) : ((uint64_t)0)' + elif needsSizeAdjust: + decl,name = prog.getTemp(size) + dst = params[2] + return '{decl}\n\t{tmp} = ({b} & {mask}) - ({a} & {mask}) - ({check} ? 1 : 0);\n\t{dst} = ({dst} & ~{mask}) | {tmp};'.format( + decl = decl, tmp = name, a = params[0], b = params[1], op = op, dst = dst, mask = ((1 << size) - 1), check = carryCheck + ) else: dst = params[2] - return decl + '\n\t{dst} = {b} - {a} - ({check} ? 1 : 0);'.format(dst = dst, - a = params[0], b = params[1], check=_getCarryCheck(prog) + return decl + '\n\t{dst} = {b} - {a} - ({check} ? {vals});'.format(dst = dst, + a = params[0], b = params[1], check=_getCarryCheck(prog), vals = vals ) def _rolCImpl(prog, params, rawParams, flagUpdates): @@ -697,6 +842,9 @@ 'ocall': Op().addImplementation('c', None, lambda prog, params: '\n\t{pre}{fun}({args});'.format( pre = prog.prefix, fun = params[0], args = ', '.join(['context'] + [str(p) for p in params[1:]]) )), + 'pcall': Op().addImplementation('c', None, lambda prog, params: '\n\t(({typ}){fun})({args});'.format( + typ = params[1], fun = params[0], args = ', '.join([str(p) for p in params[2:]]) + )), 'cycles': Op().addImplementation('c', None, lambda prog, params: '\n\tcontext->cycles += context->opts->gen.clock_divider * {0};'.format( params[0] @@ -704,12 +852,12 @@ ), 'addsize': Op( lambda a, b: b + (2 * a if a else 1) - ).addImplementation('c', 2, lambda prog, params: '\n\t{dst} = {val} + {sz} ? {sz} * 2 : 1;'.format( + ).addImplementation('c', 2, lambda prog, params: '\n\t{dst} = {val} + ({sz} ? {sz} * 2 : 1);'.format( dst = params[2], sz = params[0], val = params[1] )), 'decsize': Op( lambda a, b: b - (2 * a if a else 1) - ).addImplementation('c', 2, lambda prog, params: '\n\t{dst} = {val} - {sz} ? {sz} * 2 : 1;'.format( + ).addImplementation('c', 2, lambda prog, params: '\n\t{dst} = {val} - ({sz} ? {sz} * 2 : 1);'.format( dst = params[2], sz = params[0], val = params[1] )), 'xchg': Op().addImplementation('c', (0,1), _xchgCImpl), @@ -729,8 +877,8 @@ allParamsConst = flagUpdates is None and not prog.conditional opDef = _opMap.get(self.op) for param in self.params: - allowConst = (self.op in prog.subroutines or len(procParams) != len(self.params) - 1) and param in parent.regValues isDst = (not opDef is None) and len(procParams) in opDef.outOp + allowConst = (self.op in prog.subroutines or not isDst) and param in parent.regValues if isDst and self.op == 'xchg': #xchg uses its regs as both source and destination #we need to resolve as both so that disperse/coalesce flag stuff gets done @@ -794,6 +942,10 @@ else: if param in fieldVals: param = fieldVals[param] + else: + maybeLocal = parent.resolveLocal(param) + if maybeLocal and maybeLocal in parent.regValues: + param = parent.regValues[maybeLocal] procParams.append(param) prog.subroutines[self.op].inline(prog, procParams, output, otype, parent) else: @@ -872,7 +1024,7 @@ output.append('\n\tswitch(' + param + ')') output.append('\n\t{') for case in self.cases: - temp = prog.temp.copy() + #temp = prog.temp.copy() self.current_locals = self.case_locals[case] self.regValues = dict(self.parent.regValues) output.append('\n\tcase {0}U: '.format(case) + '{') @@ -881,16 +1033,16 @@ self.processOps(prog, fieldVals, output, otype, self.cases[case]) output.append('\n\tbreak;') output.append('\n\t}') - prog.temp = temp + #prog.temp = temp if self.default: - temp = prog.temp.copy() + #temp = prog.temp.copy() self.current_locals = self.default_locals self.regValues = dict(self.parent.regValues) output.append('\n\tdefault: {') for local in self.default_locals: output.append('\n\tuint{0}_t {1};'.format(self.default_locals[local], local)) self.processOps(prog, fieldVals, output, otype, self.default) - prog.temp = temp + #prog.temp = temp output.append('\n\t}') prog.conditional = oldCond prog.popScope() @@ -915,10 +1067,15 @@ raise Exception(">=U not implemented in the general case yet") def _eqCImpl(prog, parent, fieldVals, output): - return '\n\tif (!{a}) {'.format(a=prog.resolveParam(prog.lastDst, None, {})) + if prog.lastOp.op == 'cmp': + output.pop() + params = [prog.resolveParam(p, parent, fieldVals) for p in prog.lastOp.params] + return '\n\tif ({a} == {b}) '.format(a=params[1], b = params[0]) + '{' + else: + return '\n\tif (!{a}) {{'.format(a=prog.resolveParam(prog.lastDst, None, {})) def _neqCImpl(prog, parent, fieldVals, output): - return '\n\tif ({a}) {'.format(a=prog.resolveParam(prog.lastDst, None, {})) + return '\n\tif ({a}) {{'.format(a=prog.resolveParam(prog.lastDst, None, {})) _ifCmpImpl = { 'c': { @@ -986,21 +1143,21 @@ def generate(self, prog, parent, fieldVals, output, otype, flagUpdates): self.regValues = parent.regValues - try: + if self.cond in prog.booleans: self._genConstParam(prog.checkBool(self.cond), prog, fieldVals, output, otype) - except Exception: + else: if self.cond in _ifCmpImpl[otype]: oldCond = prog.conditional prog.conditional = True - temp = prog.temp.copy() + #temp = prog.temp.copy() output.append(_ifCmpImpl[otype][self.cond](prog, parent, fieldVals, output)) self._genTrueBody(prog, fieldVals, output, otype) - prog.temp = temp + #prog.temp = temp if self.elseBody: - temp = prog.temp.copy() + #temp = prog.temp.copy() output.append('\n\t} else {') self._genFalseBody(prog, fieldVals, output, otype) - prog.temp = temp + #prog.temp = temp output.append('\n\t}') prog.conditional = oldCond else: @@ -1008,17 +1165,17 @@ if type(cond) is int: self._genConstParam(cond, prog, fieldVals, output, otype) else: - temp = prog.temp.copy() + #temp = prog.temp.copy() output.append('\n\tif ({cond}) '.format(cond=cond) + '{') oldCond = prog.conditional prog.conditional = True self._genTrueBody(prog, fieldVals, output, otype) - prog.temp = temp + #prog.temp = temp if self.elseBody: - temp = prog.temp.copy() + #temp = prog.temp.copy() output.append('\n\t} else {') self._genFalseBody(prog, fieldVals, output, otype) - prog.temp = temp + #prog.temp = temp output.append('\n\t}') prog.conditional = oldCond @@ -1129,6 +1286,7 @@ self.flagBits = {} self.flagCalc = {} self.flagStorage = {} + self.flagOrder = [] self.flagReg = None self.storageToFlags = {} self.maxBit = -1 @@ -1153,6 +1311,7 @@ self.flagStorage[flag] = storage storage,_,storebit = storage.partition('.') self.storageToFlags.setdefault(storage, []).append((storebit, flag)) + self.flagOrder.append(flag) return self def getStorage(self, flag): @@ -1312,8 +1471,10 @@ self.lastA = None self.lastB = None self.lastBFlow = None + self.sizeAdjust = None self.conditional = False self.declares = [] + self.lastSize = None def __str__(self): pieces = [] @@ -1411,11 +1572,11 @@ for include in self.includes: body.append('#include "{0}"\n'.format(include)) if self.dispatch == 'call': - body.append('\nstatic void unimplemented({pre}context *context)'.format(pre = self.prefix)) + body.append('\nstatic void unimplemented({pre}context *context, uint32_t target_cycle)'.format(pre = self.prefix)) body.append('\n{') body.append('\n\tfatal_error("Unimplemented instruction\\n");') body.append('\n}\n') - body.append('\ntypedef void (*impl_fun)({pre}context *context);'.format(pre=self.prefix)) + body.append('\ntypedef void (*impl_fun)({pre}context *context, uint32_t target_cycle);'.format(pre=self.prefix)) for table in self.extra_tables: body.append('\nstatic impl_fun impl_{name}[{sz}];'.format(name = table, sz=(1 << self.opsize))) body.append('\nstatic impl_fun impl_main[{sz}];'.format(sz=(1 << self.opsize))) @@ -1432,7 +1593,12 @@ pieces.append('\n\t{sync}(context, target_cycle);'.format(sync=self.sync_cycle)) pieces.append('\n\twhile (context->cycles < target_cycle)') pieces.append('\n\t{') - #TODO: Handle interrupts in call dispatch mode + if self.interrupt in self.subroutines: + pieces.append('\n\t\tif (context->cycles >= context->sync_cycle) {') + self.meta = {} + self.temp = {} + self.subroutines[self.interrupt].inline(self, [], pieces, otype, None) + pieces.append('\n\t\t}') self.meta = {} self.temp = {} self.subroutines[self.body].inline(self, [], pieces, otype, None) @@ -1455,7 +1621,7 @@ if size in self.temp: return ('', self.temp[size]) self.temp[size] = 'gen_tmp{sz}__'.format(sz=size); - return ('\n\tuint{sz}_t gen_tmp{sz}__;'.format(sz=size), self.temp[size]) + return ('', self.temp[size]) def resolveParam(self, param, parent, fieldVals, allowConstant=True, isdst=False): keepGoing = True @@ -1477,6 +1643,7 @@ if maybeLocal: if isdst: self.lastDst = param + self.lastSize = None return maybeLocal if param in fieldVals: param = fieldVals[param] @@ -1487,8 +1654,11 @@ keepGoing = True elif self.isReg(param): return self.resolveReg(param, parent, fieldVals, isdst) + elif param in self.regs.pointers: + return 'context->' + param if isdst: self.lastDst = param + self.lastSize = None return param def isReg(self, name): @@ -1551,6 +1721,11 @@ return self.regs.regs[name] return 32 + def getLastSize(self): + if self.lastSize: + return self.lastSize + return self.paramSize(self.lastDst) + def pushScope(self, scope): self.scopes.append(scope) self.currentScope = scope diff -r 13abdc98379e -r 374a5ae694e8 debug.c --- a/debug.c Thu Apr 18 22:06:47 2019 -0700 +++ b/debug.c Sat Apr 18 11:42:53 2020 -0700 @@ -11,7 +11,7 @@ #include "terminal.h" #include "z80inst.h" -#ifndef USE_NATIVE +#ifdef NEW_CORE #define Z80_OPTS opts #else #define Z80_OPTS options @@ -105,7 +105,7 @@ return m68k_read_word(address, context) << 16 | m68k_read_word(address + 2, context); } -void debugger_print(m68k_context *context, char format_char, char *param) +void debugger_print(m68k_context *context, char format_char, char *param, uint32_t address) { uint32_t value; char format[8]; @@ -141,7 +141,7 @@ value &= 0xFF; } } - } else if (param[0] == 'S' && param[1] == 'R') { + } else if (param[0] == 's' && param[1] == 'r') { value = (context->status << 8); for (int flag = 0; flag < 5; flag++) { value |= context->flags[flag] << (4-flag); @@ -151,6 +151,8 @@ } else if(param[0] == 'f') { genesis_context *gen = context->system; value = gen->vdp->frame; + } else if (param[0] == 'p' && param[1] == 'c') { + value = address; } else if ((param[0] == '0' && param[1] == 'x') || param[0] == '$') { char *after; uint32_t p_addr = strtol(param+(param[0] == '0' ? 2 : 1), &after, 16); @@ -197,7 +199,7 @@ } switch (param[0]) { -#ifdef USE_NATIVE +#ifndef NEW_CORE case 'a': if (param[1] == 'f') { if(param[2] == '\'') { @@ -348,22 +350,7 @@ case '0': if (param[1] == 'x') { uint16_t p_addr = strtol(param+2, NULL, 16); - if (p_addr < 0x4000) { - value = system->zram[p_addr & 0x1FFF]; - } else if(p_addr >= 0x8000) { - uint32_t v_addr = system->z80_bank_reg << 15; - v_addr += p_addr & 0x7FFF; - if (v_addr < 0x400000) { - value = system->cart[v_addr/2]; - } else if(v_addr > 0xE00000) { - value = system->work_ram[(v_addr & 0xFFFF)/2]; - } - if (v_addr & 1) { - value &= 0xFF; - } else { - value >>= 8; - } - } + value = read_byte(p_addr, (void **)context->mem_pointers, &context->options->gen, context); } break; } @@ -496,7 +483,7 @@ if (inst.addr_mode == Z80_IMMED) { after = inst.immed; } else if (inst.ea_reg == Z80_HL) { -#ifdef USE_NATIVE +#ifndef NEW_CORE after = context->regs[Z80_H] << 8 | context->regs[Z80_L]; } else if (inst.ea_reg == Z80_IX) { after = context->regs[Z80_IXH] << 8 | context->regs[Z80_IXL]; @@ -567,12 +554,15 @@ } break; } + case '?': + print_z80_help(); + break; default: if ( !context->Z80_OPTS->gen.debug_cmd_handler || !context->Z80_OPTS->gen.debug_cmd_handler(&system->header, input_buf) ) { - fprintf(stderr, "Unrecognized debugger command %s\n", input_buf); + fprintf(stderr, "Unrecognized debugger command %s\nUse '?' for help.\n", input_buf); } break; } @@ -585,7 +575,7 @@ static uint32_t branch_t; static uint32_t branch_f; -int run_debugger_command(m68k_context *context, char *input_buf, m68kinst inst, uint32_t after) +int run_debugger_command(m68k_context *context, uint32_t address, char *input_buf, m68kinst inst, uint32_t after) { char * param; char format_char; @@ -703,7 +693,7 @@ fputs("display command requires a parameter\n", stderr); break; } - debugger_print(context, format_char, param); + debugger_print(context, format_char, param, address); add_display(&displays, &disp_index, format_char, param); } else { param = find_param(input_buf); @@ -734,11 +724,13 @@ } } param = find_param(input_buf); - if (!param) { - fputs("p command requires a parameter\n", stderr); - break; + if (param) { + debugger_print(context, format_char, param, address); + } else { + m68k_disasm(&inst, input_buf); + printf("%X: %s\n", address, input_buf); } - debugger_print(context, format_char, param); + break; case 'n': if (inst.op == M68K_RTS) { @@ -802,10 +794,12 @@ param = find_param(input_buf); if (!param) { fputs("Missing destination parameter for set\n", stderr); + return 1; } char *val = find_param(param); if (!val) { fputs("Missing value parameter for set\n", stderr); + return 1; } long int_val; int reg_num; @@ -846,6 +840,9 @@ fprintf(stderr, "Invalid destinatino %s\n", param); } break; + } else if (input_buf[1] == 'r') { + system->header.soft_reset(&system->header); + return 0; } else { if (inst.op == M68K_RTS) { after = m68k_read_long(context->aregs[7], context); @@ -940,17 +937,62 @@ break; } #endif + case '?': + print_m68k_help(); + break; case 'q': puts("Quitting"); exit(0); break; default: - fprintf(stderr, "Unrecognized debugger command %s\n", input_buf); + fprintf(stderr, "Unrecognized debugger command %s\nUse '?' for help.\n", input_buf); break; } return 1; } +void print_m68k_help() +{ + printf("M68k Debugger Commands\n"); + printf(" b ADDRESS - Set a breakpoint at ADDRESS\n"); + printf(" d BREAKPOINT - Delete a 68K breakpoint\n"); + printf(" co BREAKPOINT - Run a list of debugger commands each time\n"); + printf(" BREAKPOINT is hit\n"); + printf(" a ADDRESS - Advance to address\n"); + printf(" n - Advance to next instruction\n"); + printf(" o - Advance to next instruction ignoring branches to\n"); + printf(" lower addresses (good for breaking out of loops)\n"); + printf(" s - Advance to next instruction (follows bsr/jsr)\n"); + printf(" se REG|ADDRESS VALUE - Set value\n"); + printf(" sr - Soft reset\n"); + printf(" c - Continue\n"); + printf(" bt - Print a backtrace\n"); + printf(" p[/(x|X|d|c)] VALUE - Print a register or memory location\n"); + printf(" di[/(x|X|d|c)] VALUE - Print a register or memory location each time\n"); + printf(" a breakpoint is hit\n"); + printf(" vs - Print VDP sprite list\n"); + printf(" vr - Print VDP register info\n"); + printf(" yc [CHANNEL NUM] - Print YM-2612 channel info\n"); + printf(" yt - Print YM-2612 timer info\n"); + printf(" zb ADDRESS - Set a Z80 breakpoint\n"); + printf(" zp[/(x|X|d|c)] VALUE - Display a Z80 value\n"); + printf(" ? - Display help\n"); + printf(" q - Quit BlastEm\n"); +} + +void print_z80_help() +{ + printf("Z80 Debugger Commands\n"); + printf(" b ADDRESS - Set a breakpoint at ADDRESS\n"); + printf(" de BREAKPOINT - Delete a Z80 breakpoint\n"); + printf(" a ADDRESS - Advance to address\n"); + printf(" n - Advance to next instruction\n"); + printf(" c - Continue\n"); + printf(" p[/(x|X|d|c)] VALUE - Print a register or memory location\n"); + printf(" di[/(x|X|d|c)] VALUE - Print a register or memory location each time\n"); + printf(" a breakpoint is hit\n"); + printf(" q - Quit BlastEm\n"); +} void debugger(m68k_context * context, uint32_t address) { @@ -1000,7 +1042,7 @@ char *cmd = commands; strip_nl(cmd); commands += strlen(cmd) + 1; - debugging = run_debugger_command(context, cmd, inst, after); + debugging = run_debugger_command(context, address, cmd, inst, after); } free(copy); } @@ -1013,7 +1055,7 @@ remove_breakpoint(context, address); } for (disp_def * cur = displays; cur; cur = cur->next) { - debugger_print(context, cur->format_char, cur->param); + debugger_print(context, cur->format_char, cur->param, address); } m68k_disasm(&inst, input_buf); printf("%X: %s\n", address, input_buf); @@ -1053,7 +1095,7 @@ } else { strcpy(input_buf, last_cmd); } - debugging = run_debugger_command(context, input_buf, inst, after); + debugging = run_debugger_command(context, address, input_buf, inst, after); } return; } diff -r 13abdc98379e -r 374a5ae694e8 debug.h --- a/debug.h Thu Apr 18 22:06:47 2019 -0700 +++ b/debug.h Sat Apr 18 11:42:53 2020 -0700 @@ -3,7 +3,7 @@ #include #include "m68k_core.h" -#ifdef USE_NATIVE +#ifndef NEW_CORE #include "z80_to_x86.h" #else #include "z80.h" @@ -29,5 +29,7 @@ void remove_display(disp_def ** head, uint32_t index); void debugger(m68k_context * context, uint32_t address); z80_context * zdebugger(z80_context * context, uint16_t address); +void print_m68k_help(); +void print_z80_help(); #endif //DEBUG_H_ diff -r 13abdc98379e -r 374a5ae694e8 default.cfg --- a/default.cfg Thu Apr 18 22:06:47 2019 -0700 +++ b/default.cfg Sat Apr 18 11:42:53 2020 -0700 @@ -22,6 +22,7 @@ v ui.vram_debug c ui.cram_debug n ui.compositing_debug + m ui.vgm_log esc ui.exit ` ui.save_state 0 ui.set_speed.0 @@ -364,6 +365,10 @@ screenshot_path $HOME #see strftime for the format specifiers valid in screenshot_template screenshot_template blastem_%Y%m%d_%H%M%S.png + #path for storing VGM recordings, accepts the same variables as initial_path + vgm_path $HOME + #see strftime for the format specifiers valid in vgm_template + vgm_template blastem_%Y%m%d_%H%M%S.vgm #path template for saving SRAM, EEPROM and savestates #accepts special variables $HOME, $EXEDIR, $USERDATA, $ROMNAME save_path $USERDATA/blastem/$ROMNAME @@ -385,6 +390,8 @@ #MegaWiFi allows ROMs to make connections to the internet #so it should only be enabled for ROMs you trust megawifi off + #Model of the emulated Gen/MD system, see systems.cfg for a list of options + model md1va3 } diff -r 13abdc98379e -r 374a5ae694e8 dis.c --- a/dis.c Thu Apr 18 22:06:47 2019 -0700 +++ b/dis.c Sat Apr 18 11:42:53 2020 -0700 @@ -292,7 +292,7 @@ encoded = NULL; address = def->address; if (!is_visited(address)) { - encoded = filebuf + (address - address_off)/2; + encoded = filebuf + ((address & 0xFFFFFF) - address_off)/2; } tmpd = def; def = def->next; @@ -302,7 +302,7 @@ break; } for(;;) { - if (address > address_end || address < address_off) { + if ((address & 0xFFFFFF) > address_end || address < address_off) { break; } visit(address); diff -r 13abdc98379e -r 374a5ae694e8 gdb_remote.c --- a/gdb_remote.c Thu Apr 18 22:06:47 2019 -0700 +++ b/gdb_remote.c Sat Apr 18 11:42:53 2020 -0700 @@ -170,7 +170,7 @@ if (address >= 0xA00000 && address < 0xA04000) { gen->zram[address & 0x1FFF] = value; genesis_context * gen = context->system; -#if !defined(NO_Z80) && defined(USE_NATIVE) +#if !defined(NO_Z80) && !defined(NEW_CORE) z80_handle_code_write(address & 0x1FFF, gen->z80); #endif return; diff -r 13abdc98379e -r 374a5ae694e8 genesis.c --- a/genesis.c Thu Apr 18 22:06:47 2019 -0700 +++ b/genesis.c Sat Apr 18 11:42:53 2020 -0700 @@ -18,6 +18,7 @@ #include "saves.h" #include "bindings.h" #include "jcart.h" +#include "config.h" #define MCLKS_NTSC 53693175 #define MCLKS_PAL 53203395 @@ -34,12 +35,12 @@ #define LINES_PAL 313 #ifdef IS_LIB -#define MAX_SOUND_CYCLES 1000 +#define MAX_SOUND_CYCLES (MCLKS_PER_YM*NUM_OPERATORS*6*4) #else #define MAX_SOUND_CYCLES 100000 #endif -#ifndef USE_NATIVE +#ifdef NEW_CORE #define Z80_CYCLE cycles #define Z80_OPTS opts #define z80_handle_code_write(...) @@ -199,7 +200,7 @@ deserialize_buffer buffer; init_deserialize(&buffer, data, size); genesis_deserialize(&buffer, gen); -#ifdef USE_NATIVE +#ifndef NEW_CORE //HACK: Fix this once PC/IR is represented in a better way in 68K core gen->m68k->resume_pc = get_native_address_trans(gen->m68k, gen->m68k->last_prefetch_address); #endif @@ -220,7 +221,7 @@ static uint16_t get_open_bus_value(system_header *system) { genesis_context *genesis = (genesis_context *)system; -#ifdef USE_NATIVE +#ifndef NEW_CORE return read_dma_value(genesis->m68k->last_prefetch_address/2); #else m68000_base_device *device = (m68000_base_device *)genesis->m68k; @@ -305,7 +306,7 @@ static void z80_next_int_pulse(z80_context * z_context) { genesis_context * gen = z_context->system; -#ifndef USE_NATIVE +#ifdef NEW_CORE z_context->int_cycle = vdp_next_vint_z80(gen->vdp); z_context->int_end_cycle = z_context->int_cycle + Z80_INT_PULSE_MCLKS; z_context->int_value = 0xFF; @@ -321,7 +322,7 @@ { #ifndef NO_Z80 if (z80_enabled) { -#ifndef USE_NATIVE +#ifdef NEW_CORE if (z_context->int_cycle == 0xFFFFFFFFU) { z80_next_int_pulse(z_context); } @@ -373,12 +374,12 @@ z80_context * z_context = gen->z80; #ifdef REFRESH_EMULATION if (context->current_cycle != last_sync_cycle) { - //lame estimation of refresh cycle delay - refresh_counter += context->current_cycle - last_sync_cycle; - if (!gen->bus_busy) { - context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL)); - } - refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL); + //lame estimation of refresh cycle delay + refresh_counter += context->current_cycle - last_sync_cycle; + if (!gen->bus_busy) { + context->current_cycle += REFRESH_DELAY * MCLKS_PER_68K * (refresh_counter / (MCLKS_PER_68K * REFRESH_INTERVAL)); + } + refresh_counter = refresh_counter % (MCLKS_PER_68K * REFRESH_INTERVAL); } #endif @@ -412,11 +413,11 @@ } context->current_cycle -= deduction; z80_adjust_cycles(z_context, deduction); - gen->ym->current_cycle -= deduction; + ym_adjust_cycles(gen->ym, deduction); + if (gen->ym->vgm) { + vgm_adjust_cycles(gen->ym->vgm, deduction); + } gen->psg->cycles -= deduction; - if (gen->ym->write_cycle != CYCLE_NEVER) { - gen->ym->write_cycle = gen->ym->write_cycle >= deduction ? gen->ym->write_cycle - deduction : 0; - } if (gen->reset_cycle != CYCLE_NEVER) { gen->reset_cycle -= deduction; } @@ -438,18 +439,20 @@ context->target_cycle = gen->reset_cycle; } if (address) { -#ifdef USE_NATIVE +#ifndef NEW_CORE if (gen->header.enter_debugger) { gen->header.enter_debugger = 0; debugger(context, address); } - if (gen->header.save_state && (z_context->pc || !z_context->native_pc || z_context->reset || !z_context->busreq)) { +#endif +#ifdef NEW_CORE + if (gen->header.save_state) { #else - if (gen->header.save_state) { + if (gen->header.save_state && (z_context->pc || !z_context->native_pc || z_context->reset || !z_context->busreq)) { #endif uint8_t slot = gen->header.save_state - 1; gen->header.save_state = 0; -#ifdef USE_NATIVE +#ifndef NEW_CORE if (z_context->native_pc && !z_context->reset) { //advance Z80 core to the start of an instruction while (!z_context->pc) @@ -459,7 +462,7 @@ } #endif char *save_path = slot == SERIALIZE_SLOT ? NULL : get_slot_name(&gen->header, slot, use_native_states ? "state" : "gst"); -#ifdef USE_NATIVE +#ifndef NEW_CORE if (use_native_states || slot == SERIALIZE_SLOT) { #endif serialize_buffer state; @@ -474,7 +477,7 @@ save_to_file(&state, save_path); free(state.data); } -#ifdef USE_NATIVE +#ifndef NEW_CORE } else { save_gst(gen, save_path, address); } @@ -501,10 +504,10 @@ #ifdef REFRESH_EMULATION //do refresh check here so we can avoid adding a penalty for a refresh that happens during a VDP access if (context->current_cycle - 4*MCLKS_PER_68K > last_sync_cycle) { - 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; + 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; } #endif sync_components(context, 0); @@ -648,32 +651,34 @@ last_sync_cycle = context->current_cycle; } #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) { 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); } } else if (vdp_port < 0x18){ fatal_error("Illegal read from PSG port %X\n", vdp_port); } else { - value = vdp_test_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; + value = get_open_bus_value(&gen->header); } #ifdef REFRESH_EMULATION last_sync_cycle -= 4; @@ -768,9 +773,8 @@ } } } else { - location &= 0x1FFF; - if (location < 0x100) { - switch(location/2) + if (location < 0x10100) { + switch(location >> 1 & 0xFF) { case 0x1: io_data_write(gen->io.ports, value, context->current_cycle); @@ -815,7 +819,8 @@ break; } } else { - if (location == 0x1100) { + uint32_t masked = location & 0xFFF00; + if (masked == 0x11100) { if (value & 1) { dputs("bus requesting Z80"); if (z80_enabled) { @@ -840,7 +845,7 @@ gen->z80->busack = 0; } } - } else if (location == 0x1200) { + } else if (masked == 0x11200) { sync_z80(gen->z80, context->current_cycle); if (value & 1) { if (z80_enabled) { @@ -856,6 +861,8 @@ } ym_reset(gen->ym); } + } else if (masked != 0x11300 && masked != 0x11000) { + fatal_error("Machine freeze due to unmapped write to address %X\n", location | 0xA00000); } } } @@ -891,17 +898,20 @@ value = gen->zram[location & 0x1FFF]; } else if (location < 0x6000) { sync_sound(gen, context->current_cycle); - value = ym_read_status(gen->ym); + value = ym_read_status(gen->ym, context->current_cycle, location); + } else if (location < 0x7F00) { + value = 0xFF; } else { + fatal_error("Machine freeze due to read of Z80 VDP memory window by 68K: %X\n", location | 0xA00000); value = 0xFF; } } else { - value = 0xFF; + uint16_t word = get_open_bus_value(&gen->header); + value = location & 1 ? word : word >> 8; } } else { - location &= 0x1FFF; - if (location < 0x100) { - switch(location/2) + if (location < 0x10100) { + switch(location >> 1 & 0xFF) { case 0x0: //version bits should be 0 for now since we're not emulating TMSS @@ -953,18 +963,24 @@ value = gen->io.ports[2].serial_ctrl; break; default: - value = 0xFF; + value = get_open_bus_value(&gen->header) >> 8; } } else { - if (location == 0x1100) { + uint32_t masked = location & 0xFFF00; + if (masked == 0x11100) { value = z80_enabled ? !z80_get_busack(gen->z80, context->current_cycle) : !gen->z80->busack; value |= (get_open_bus_value(&gen->header) >> 8) & 0xFE; dprintf("Byte read of BUSREQ returned %d @ %d (reset: %d)\n", value, context->current_cycle, gen->z80->reset); - } else if (location == 0x1200) { + } else if (masked == 0x11200) { value = !gen->z80->reset; + } else if (masked == 0x11300 || masked == 0x11000) { + //A11300 is apparently completely unused + //A11000 is the memory control register which I am assuming is write only + value = get_open_bus_value(&gen->header) >> 8; } else { + location |= 0xA00000; + fatal_error("Machine freeze due to read of unmapped IO location %X\n", location); value = 0xFF; - printf("Byte read of unknown IO location: %X\n", location); } } } @@ -1004,18 +1020,16 @@ z80_context * context = vcontext; genesis_context * gen = context->system; sync_sound(gen, context->Z80_CYCLE); - return ym_read_status(gen->ym); + return ym_read_status(gen->ym, context->Z80_CYCLE, location); } static uint8_t z80_read_bank(uint32_t location, void * vcontext) { z80_context * context = vcontext; genesis_context *gen = context->system; - if (gen->bus_busy) { context->Z80_CYCLE = gen->m68k->current_cycle; } - //typical delay from bus arbitration context->Z80_CYCLE += 3 * MCLKS_PER_Z80; //TODO: add cycle for an access right after a previous one @@ -1030,6 +1044,10 @@ uint32_t address = gen->z80_bank_reg << 15 | location; if (address >= 0xC00000 && address < 0xE00000) { return z80_vdp_port_read(location & 0xFF, context); + } else if (address >= 0xA10000 && address <= 0xA10001) { + //Apparently version reg can be read through Z80 banked area + //TODO: Check rest of IO region addresses + return gen->version_reg; } else { fprintf(stderr, "Unhandled read by Z80 from address %X through banked memory area (%X)\n", address, gen->z80_bank_reg << 15); } @@ -1074,6 +1092,80 @@ return context; } +static uint16_t unused_read(uint32_t location, void *vcontext) +{ + m68k_context *context = vcontext; + genesis_context *gen = context->system; + if ((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) { + if (gen->version_reg & 0xF) { + return gen->tmss_lock[location >> 1 & 1]; + } else { + fatal_error("Machine freeze due to read from TMSS lock when TMSS is not present %X\n", location); + return 0xFFFF; + } + } else if (location == 0xA14100) { + if (gen->version_reg & 0xF) { + return get_open_bus_value(&gen->header); + } else { + fatal_error("Machine freeze due to read from TMSS control when TMSS is not present %X\n", location); + return 0xFFFF; + } + } else { + fatal_error("Machine freeze due to unmapped read from %X\n", location); + return 0xFFFF; + } +} + +static uint8_t unused_read_b(uint32_t location, void *vcontext) +{ + uint16_t v = unused_read(location & 0xFFFFFE, vcontext); + if (location & 1) { + return v; + } else { + return v >> 8; + } +} + +static void *unused_write(uint32_t location, void *vcontext, uint16_t value) +{ + m68k_context *context = vcontext; + genesis_context *gen = context->system; + uint8_t has_tmss = gen->version_reg & 0xF; + if (has_tmss && (location == 0xA14000 || location == 0xA14002)) { + gen->tmss_lock[location >> 1 & 1] = value; + } else if (has_tmss && location == 0xA14100) { + //TODO: implement TMSS control register + } else if (location < 0xA12000 || location >= 0xA13100 || (location >= 0xA12100 && location < 0xA13000)) { + fatal_error("Machine freeze due to unmapped write to %X\n", location); + } + return vcontext; +} + +static void *unused_write_b(uint32_t location, void *vcontext, uint8_t value) +{ + m68k_context *context = vcontext; + genesis_context *gen = context->system; + uint8_t has_tmss = gen->version_reg & 0xF; + if (has_tmss && location >= 0xA14000 && location <= 0xA14003) { + uint32_t offset = location >> 1 & 1; + if (location & 1) { + gen->tmss_lock[offset] &= 0xFF00; + gen->tmss_lock[offset] |= value; + } else { + gen->tmss_lock[offset] &= 0xFF; + gen->tmss_lock[offset] |= value << 8; + } + } else if (has_tmss && (location == 0xA14100 || location == 0xA14101)) { + //TODO: implement TMSS control register + } else if (location < 0xA12000 || location >= 0xA13100 || (location >= 0xA12100 && location < 0xA13000)) { + fatal_error("Machine freeze due to unmapped byte write to %X\n", location); + } + return vcontext; +} + static void set_speed_percent(system_header * system, uint32_t percent) { genesis_context *context = (genesis_context *)system; @@ -1132,19 +1224,19 @@ if (load_from_file(&state, statepath)) { genesis_deserialize(&state, gen); free(state.data); -#ifdef USE_NATIVE +#ifndef NEW_CORE //HACK pc = gen->m68k->last_prefetch_address; #endif ret = 1; } else { -#ifdef USE_NATIVE +#ifndef NEW_CORE strcpy(statepath + strlen(statepath)-strlen("state"), "gst"); pc = load_gst(gen, statepath); ret = pc != 0; #endif } -#ifdef USE_NATIVE +#ifndef NEW_CORE if (ret) { gen->m68k->resume_pc = get_native_address_trans(gen->m68k, pc); } @@ -1191,12 +1283,12 @@ if (load_from_file(&state, statefile)) { genesis_deserialize(&state, gen); free(state.data); -#ifdef USE_NATIVE +#ifndef NEW_CORE //HACK pc = gen->m68k->last_prefetch_address; #endif } else { -#ifdef USE_NATIVE +#ifndef NEW_CORE pc = load_gst(gen, statefile); if (!pc) { fatal_error("Failed to load save state %s\n", statefile); @@ -1204,7 +1296,7 @@ #endif } printf("Loaded %s\n", statefile); -#ifdef USE_NATIVE +#ifndef NEW_CORE if (gen->header.enter_debugger) { gen->header.enter_debugger = 0; insert_breakpoint(gen->m68k, pc, gen->header.debugger_type == DEBUGGER_NATIVE ? debugger : gdb_debug_enter); @@ -1213,7 +1305,7 @@ adjust_int_cycle(gen->m68k, gen->vdp); start_68k_context(gen->m68k, pc); } else { -#ifdef USE_NATIVE +#ifndef NEW_CORE if (gen->header.enter_debugger) { gen->header.enter_debugger = 0; uint32_t address = gen->cart[2] << 16 | gen->cart[3]; @@ -1264,7 +1356,13 @@ fprintf(stderr, "Failed to open %s file %s for writing\n", save_type_name(gen->save_type), save_filename); return; } + if (gen->save_type == RAM_FLAG_BOTH) { + byteswap_rom(gen->save_size, (uint16_t *)gen->save_storage); + } fwrite(gen->save_storage, 1, gen->save_size, f); + if (gen->save_type == RAM_FLAG_BOTH) { + byteswap_rom(gen->save_size, (uint16_t *)gen->save_storage); + } fclose(f); printf("Saved %s to %s\n", save_type_name(gen->save_type), save_filename); } @@ -1277,6 +1375,9 @@ uint32_t read = fread(gen->save_storage, 1, gen->save_size, f); fclose(f); if (read > 0) { + if (gen->save_type == RAM_FLAG_BOTH) { + byteswap_rom(gen->save_size, (uint16_t *)gen->save_storage); + } printf("Loaded %s from %s\n", save_type_name(gen->save_type), save_filename); } } @@ -1387,6 +1488,30 @@ set_audio_config(gen); } +static void start_vgm_log(system_header *system, char *filename) +{ + genesis_context *gen = (genesis_context *)system; + vgm_writer *vgm = vgm_write_open(filename, gen->version_reg & HZ50 ? 50 : 60, gen->master_clock, gen->m68k->current_cycle); + if (vgm) { + printf("Started logging VGM to %s\n", filename); + sync_sound(gen, vgm->last_cycle); + ym_vgm_log(gen->ym, gen->master_clock, vgm); + psg_vgm_log(gen->psg, gen->master_clock, vgm); + gen->header.vgm_logging = 1; + } else { + printf("Failed to start logging to %s\n", filename); + } +} + +static void stop_vgm_log(system_header *system) +{ + puts("Stopped VGM log"); + genesis_context *gen = (genesis_context *)system; + vgm_close(gen->ym->vgm); + gen->ym->vgm = gen->psg->vgm = NULL; + gen->header.vgm_logging = 0; +} + 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[] = { @@ -1419,11 +1544,19 @@ gen->header.config_updated = config_updated; gen->header.serialize = serialize; gen->header.deserialize = deserialize; + gen->header.start_vgm_log = start_vgm_log; + gen->header.stop_vgm_log = stop_vgm_log; gen->header.type = SYSTEM_GENESIS; gen->header.info = *rom; set_region(gen, rom, force_region); + tern_node *model = get_model(config, SYSTEM_GENESIS); + uint8_t tmss = !strcmp(tern_find_ptr_default(model, "tmss", "off"), "on"); + if (tmss) { + gen->version_reg |= 1; + } - gen->vdp = init_vdp_context(gen->version_reg & 0x40); + uint8_t max_vsram = !strcmp(tern_find_ptr_default(model, "vsram", "40"), "64"); + gen->vdp = init_vdp_context(gen->version_reg & 0x40, max_vsram); gen->vdp->system = &gen->header; gen->frame_end = vdp_cycles_to_frame_end(gen->vdp); char * config_cycles = tern_find_path(config, "clocks\0max_cycles\0", TVAL_PTR).ptrval; @@ -1446,7 +1579,7 @@ z80_options *z_opts = malloc(sizeof(z80_options)); init_z80_opts(z_opts, z80_map, 5, NULL, 0, MCLKS_PER_Z80, 0xFFFF); gen->z80 = init_z80_context(z_opts); -#ifdef USE_NATIVE +#ifndef NEW_CORE gen->z80->next_int_pulse = z80_next_int_pulse; #endif z80_assert_reset(gen->z80, 0); @@ -1484,7 +1617,7 @@ { write_cram_internal(gen->vdp, i, rand()); } - for (int i = 0; i < VSRAM_SIZE; i++) + for (int i = 0; i < gen->vdp->vsram_size; i++) { gen->vdp->vsram[i] = rand(); } @@ -1521,8 +1654,9 @@ m68k_options *opts = malloc(sizeof(m68k_options)); init_m68k_opts(opts, rom->map, rom->map_chunks, MCLKS_PER_68K); - //TODO: make this configurable - opts->gen.flags |= M68K_OPT_BROKEN_READ_MODIFY; + if (!strcmp(tern_find_ptr_default(model, "tas", "broken"), "broken")) { + opts->gen.flags |= M68K_OPT_BROKEN_READ_MODIFY; + } gen->m68k = init_68k_context(opts, NULL); gen->m68k->system = gen; opts->address_log = (system_opts & OPT_ADDRESS_LOG) ? fopen("address.log", "w") : NULL; @@ -1556,7 +1690,10 @@ (read_8_fun)vdp_port_read_b, (write_8_fun)vdp_port_write_b}, {0xA00000, 0xA12000, 0x1FFFF, 0, 0, 0, NULL, (read_16_fun)io_read_w, (write_16_fun)io_write_w, - (read_8_fun)io_read, (write_8_fun)io_write} + (read_8_fun)io_read, (write_8_fun)io_write}, + {0x000000, 0xFFFFFF, 0xFFFFFF, 0, 0, 0, NULL, + (read_16_fun)unused_read, (write_16_fun)unused_write, + (read_8_fun)unused_read_b, (write_8_fun)unused_write_b} }; static tern_node *rom_db; if (!rom_db) { diff -r 13abdc98379e -r 374a5ae694e8 genesis.h --- a/genesis.h Thu Apr 18 22:06:47 2019 -0700 +++ b/genesis.h Sat Apr 18 11:42:53 2020 -0700 @@ -9,7 +9,7 @@ #include #include "system.h" #include "m68k_core.h" -#ifdef USE_NATIVE +#ifndef NEW_CORE #include "z80_to_x86.h" #else #include "z80.h" @@ -53,6 +53,7 @@ uint32_t reset_cycle; uint8_t bank_regs[8]; uint16_t z80_bank_reg; + uint16_t tmss_lock[2]; uint16_t mapper_start_index; uint8_t mapper_type; uint8_t save_type; @@ -60,6 +61,7 @@ uint8_t version_reg; uint8_t bus_busy; uint8_t reset_requested; + uint8_t tmss; eeprom_state eeprom; nor_state nor; }; diff -r 13abdc98379e -r 374a5ae694e8 gentests.py --- a/gentests.py Thu Apr 18 22:06:47 2019 -0700 +++ b/gentests.py Sat Apr 18 11:42:53 2020 -0700 @@ -136,7 +136,10 @@ num = already.get('label', 0)+1 already['label'] = num if (already[str(self.index)] + self.disp) & 1: - self.disp += 1 + if self.disp > 0: + self.disp -= 1 + else: + self.disp += 1 address = 'lbl_' + str(num) + ' + 2 + ' + str(self.disp) + ' + ' + str(index) else: if self.base == self.index: diff -r 13abdc98379e -r 374a5ae694e8 gst.c --- a/gst.c Thu Apr 18 22:06:47 2019 -0700 +++ b/gst.c Sat Apr 18 11:42:53 2020 -0700 @@ -244,11 +244,11 @@ uint16_t value; write_cram_internal(context, i, (tmp_buf[i*2+1] << 8) | tmp_buf[i*2]); } - if (fread(tmp_buf, 2, VSRAM_SIZE, state_file) != VSRAM_SIZE) { + if (fread(tmp_buf, 2, MIN_VSRAM_SIZE, state_file) != MIN_VSRAM_SIZE) { fputs("Failed to read VSRAM from savestate\n", stderr); return 0; } - for (int i = 0; i < VSRAM_SIZE; i++) { + for (int i = 0; i < MIN_VSRAM_SIZE; i++) { context->vsram[i] = (tmp_buf[i*2+1] << 8) | tmp_buf[i*2]; } fseek(state_file, GST_VDP_MEM, SEEK_SET); @@ -280,12 +280,12 @@ fputs("Error writing CRAM to savestate\n", stderr); return 0; } - for (int i = 0; i < VSRAM_SIZE; i++) + for (int i = 0; i < MIN_VSRAM_SIZE; i++) { tmp_buf[i*2] = context->vsram[i]; tmp_buf[i*2+1] = context->vsram[i] >> 8; } - if (fwrite(tmp_buf, 2, VSRAM_SIZE, outfile) != VSRAM_SIZE) { + if (fwrite(tmp_buf, 2, MIN_VSRAM_SIZE, outfile) != MIN_VSRAM_SIZE) { fputs("Error writing VSRAM to savestate\n", stderr); return 0; } diff -r 13abdc98379e -r 374a5ae694e8 libblastem.c --- a/libblastem.c Thu Apr 18 22:06:47 2019 -0700 +++ b/libblastem.c Sat Apr 18 11:42:53 2020 -0700 @@ -6,11 +6,40 @@ #include "vdp.h" #include "render.h" #include "io.h" +#include "genesis.h" +#include "sms.h" static retro_environment_t retro_environment; RETRO_API void retro_set_environment(retro_environment_t re) { retro_environment = re; +# define input_descriptor_macro(pad_num) \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "A" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "B" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Y" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "X" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "Z" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "C" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Mode" }, \ + { pad_num, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, \ + + static const struct retro_input_descriptor desc[] = { + input_descriptor_macro(0) + input_descriptor_macro(1) + input_descriptor_macro(2) + input_descriptor_macro(3) + input_descriptor_macro(4) + input_descriptor_macro(5) + input_descriptor_macro(6) + input_descriptor_macro(7) + { 0 }, + }; + + re(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, (void *)desc); } static retro_video_refresh_t retro_video_refresh; @@ -19,14 +48,14 @@ retro_video_refresh = rvf; } -static retro_audio_sample_t retro_audio_sample; RETRO_API void retro_set_audio_sample(retro_audio_sample_t ras) { - retro_audio_sample = ras; } +static retro_audio_sample_batch_t retro_audio_sample_batch; RETRO_API void retro_set_audio_sample_batch(retro_audio_sample_batch_t rasb) { + retro_audio_sample_batch = rasb; } static retro_input_poll_t retro_input_poll; @@ -52,6 +81,7 @@ RETRO_API void retro_init(void) { + render_audio_initialized(RENDER_AUDIO_S16, 53693175 / (7 * 6 * 4), 2, 4, sizeof(int16_t)); } RETRO_API void retro_deinit(void) @@ -77,16 +107,46 @@ static vid_std video_standard; static uint32_t last_width, last_height; +static uint32_t overscan_top, overscan_bot, overscan_left, overscan_right; +static void update_overscan(void) +{ + uint8_t overscan; + retro_environment(RETRO_ENVIRONMENT_GET_OVERSCAN, &overscan); + if (overscan) { + overscan_top = overscan_bot = overscan_left = overscan_right = 0; + } else { + if (video_standard == VID_NTSC) { + overscan_top = 11; + overscan_bot = 8; + overscan_left = 13; + overscan_right = 14; + } else { + overscan_top = 30; + overscan_bot = 24; + overscan_left = 13; + overscan_right = 14; + } + } +} + +static int32_t sample_rate; RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info) { + update_overscan(); last_width = LINEBUF_SIZE; - info->geometry.base_width = info->geometry.max_width = LINEBUF_SIZE; - info->geometry.base_height = video_standard == VID_NTSC ? 243 : 294; + info->geometry.base_width = info->geometry.max_width = LINEBUF_SIZE - (overscan_left + overscan_right); + info->geometry.base_height = (video_standard == VID_NTSC ? 243 : 294) - (overscan_top + overscan_bot); last_height = info->geometry.base_height; info->geometry.max_height = info->geometry.base_height * 2; info->geometry.aspect_ratio = 0; - info->timing.fps = video_standard == VID_NTSC ? 60 : 50; - info->timing.sample_rate = 53267; //approximate sample rate of YM2612 + double master_clock = video_standard == VID_NTSC ? 53693175 : 53203395; + double lines = video_standard == VID_NTSC ? 262 : 313; + info->timing.fps = master_clock / (3420.0 * lines); + info->timing.sample_rate = master_clock / (7 * 6 * 24); //sample rate of YM2612 + sample_rate = info->timing.sample_rate; + render_audio_initialized(RENDER_AUDIO_S16, info->timing.sample_rate, 2, 4, sizeof(int16_t)); + //force adjustment of resampling parameters since target sample rate may have changed slightly + current_system->set_speed_percent(current_system, 100); } RETRO_API void retro_set_controller_port_device(unsigned port, unsigned device) @@ -164,6 +224,7 @@ } /* Loads a game. */ +static system_type stype; RETRO_API bool retro_load_game(const struct retro_game_info *game) { serialize_size_cache = 0; @@ -175,10 +236,12 @@ media.buffer = malloc(nearest_pow2(game->size)); memcpy(media.buffer, game->data, game->size); media.size = game->size; - current_system = alloc_config_system(detect_system_type(&media), &media, 0, 0); + stype = detect_system_type(&media); + current_system = alloc_config_system(stype, &media, 0, 0); unsigned format = RETRO_PIXEL_FORMAT_XRGB8888; retro_environment(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &format); + return current_system != NULL; } @@ -211,11 +274,57 @@ /* Gets region of memory. */ RETRO_API void *retro_get_memory_data(unsigned id) { + switch (id) { + case RETRO_MEMORY_SYSTEM_RAM: + switch (stype) { + case SYSTEM_GENESIS: { + genesis_context *gen = (genesis_context *)current_system; + return (uint8_t *)gen->work_ram; + } +#ifndef NO_Z80 + case SYSTEM_SMS: { + sms_context *sms = (sms_context *)current_system; + return sms->ram; + } +#endif + } + break; + case RETRO_MEMORY_SAVE_RAM: + if (stype == SYSTEM_GENESIS) { + genesis_context *gen = (genesis_context *)current_system; + if (gen->save_type != SAVE_NONE) + return gen->save_storage; + } + break; + default: + break; + } return NULL; } RETRO_API size_t retro_get_memory_size(unsigned id) { + switch (id) { + case RETRO_MEMORY_SYSTEM_RAM: + switch (stype) { + case SYSTEM_GENESIS: + return RAM_WORDS * sizeof(uint16_t); +#ifndef NO_Z80 + case SYSTEM_SMS: + return SMS_RAM_SIZE; +#endif + } + break; + case RETRO_MEMORY_SAVE_RAM: + if (stype == SYSTEM_GENESIS) { + genesis_context *gen = (genesis_context *)current_system; + if (gen->save_type != SAVE_NONE) + return gen->save_size; + } + break; + default: + break; + } return 0; } @@ -254,7 +363,8 @@ void render_framebuffer_updated(uint8_t which, int width) { - unsigned height = video_standard == VID_NTSC ? 243 : 294; + unsigned height = (video_standard == VID_NTSC ? 243 : 294) - (overscan_top + overscan_bot); + width -= (overscan_left + overscan_right); unsigned base_height = height; if (which != last_fb) { height *= 2; @@ -270,7 +380,7 @@ last_width = width; last_height = height; } - retro_video_refresh(fb, width, height, LINEBUF_SIZE * sizeof(uint32_t)); + retro_video_refresh(fb + overscan_left + LINEBUF_SIZE * overscan_top, width, height, LINEBUF_SIZE * sizeof(uint32_t)); current_system->request_exit(current_system); } @@ -289,6 +399,16 @@ return 1; } +uint32_t render_overscan_top() +{ + return overscan_top; +} + +uint32_t render_overscan_bot() +{ + return overscan_bot; +} + void process_events() { static int16_t prev_state[2][RETRO_DEVICE_ID_JOYPAD_L2]; @@ -326,77 +446,70 @@ { } -struct audio_source { - int32_t freq; - int32_t left_accum; - int32_t right_accum; - int32_t num_samples; -}; +uint8_t render_is_audio_sync(void) +{ + //whether this is true depends on the libretro frontend implementation + //but the sync to audio path works better here + return 1; +} -static audio_source *audio_sources[8]; -static uint8_t num_audio_sources; -audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels) +void render_buffer_consumed(audio_source *src) { - audio_sources[num_audio_sources] = calloc(1, sizeof(audio_source)); - audio_sources[num_audio_sources]->freq = master_clock / sample_divider; - return audio_sources[num_audio_sources++]; } -void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider) +void *render_new_audio_opaque(void) +{ + return NULL; +} + +void render_free_audio_opaque(void *opaque) +{ +} + +void render_lock_audio(void) +{ +} + +void render_unlock_audio() { } -void render_audio_source_gaindb(audio_source *src, float gain) +uint32_t render_min_buffered(void) { - //TODO: Implement this once I hook up a core option for individual FM/PSG gain + //not actually used in the sync to audio path + return 4; } -static void check_put_sample(void) +uint32_t render_audio_syncs_per_sec(void) { - for (int i = 0; i < num_audio_sources; i++) - { - int32_t min_samples = audio_sources[i]->freq / 53267; - if (audio_sources[i]->num_samples < min_samples) { - return; - } - } - int16_t left = 0, right = 0; - for (int i = 0; i < num_audio_sources; i++) - { - left += audio_sources[i]->left_accum / audio_sources[i]->num_samples; - right += audio_sources[i]->right_accum / audio_sources[i]->num_samples; - audio_sources[i]->left_accum = audio_sources[i]->right_accum = audio_sources[i]->num_samples = 0; - } - retro_audio_sample(left, right); + return 0; +} + +void render_audio_created(audio_source *src) +{ } -void render_put_mono_sample(audio_source *src, int16_t value) +void render_do_audio_ready(audio_source *src) { - src->left_accum += value; - src->right_accum += value; - src->num_samples++; - check_put_sample(); -} -void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right) -{ - src->left_accum += left; - src->right_accum += right; - src->num_samples++; - check_put_sample(); + int16_t *tmp = src->front; + src->front = src->back; + src->back = tmp; + src->front_populated = 1; + src->buffer_pos = 0; + if (all_sources_ready()) { + int16_t buffer[8]; + int min_remaining_out; + mix_and_convert((uint8_t *)buffer, sizeof(buffer), &min_remaining_out); + retro_audio_sample_batch(buffer, sizeof(buffer)/(2*sizeof(*buffer))); + } } -void render_free_source(audio_source *src) +void render_source_paused(audio_source *src, uint8_t remaining_sources) { - int index; - for (index = 0; index < num_audio_sources; index++) - { - if (audio_sources[index] == src) { - break; - } - } - num_audio_sources--; - audio_sources[index] = audio_sources[num_audio_sources]; - free(src); +} + +void render_source_resumed(audio_source *src) +{ } void bindings_set_mouse_mode(uint8_t mode) diff -r 13abdc98379e -r 374a5ae694e8 m68k.cpu --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/m68k.cpu Sat Apr 18 11:42:53 2020 -0700 @@ -0,0 +1,770 @@ +info + prefix m68k_ + opcode_size 16 + body m68k_run_op + header m68k.h + interrupt m68k_interrupt + include m68k_util.c + sync_cycle m68k_sync_cycle + +declare + typedef m68k_context *(*m68k_reset_handler)(m68k_context *context); + void init_m68k_opts(m68k_options *opts, memmap_chunk * memmap, uint32_t num_chunks, uint32_t clock_divider); + m68k_context *init_68k_context(m68k_options * opts, m68k_reset_handler reset_handler); + void m68k_reset(m68k_context *context); + void m68k_print_regs(m68k_context *context); + +regs + dregs 32 d0 d1 d2 d3 d4 d5 d6 d7 + aregs 32 a0 a1 a2 a3 a4 a5 a6 a7 + pc 32 + other_sp 32 + scratch1 32 + scratch2 32 + int_cycle 32 + prefetch 16 + int_priority 8 + int_num 8 + int_pending 8 + int_pending_num 8 + int_ack 8 + status 8 + ccr 8 + xflag 8 + nflag 8 + zflag 8 + vflag 8 + cflag 8 + reset_handler ptrvoid + mem_pointers ptrvoid 8 + +flags + register ccr + X 4 carry xflag + N 3 sign nflag + Z 2 zero zflag + V 1 overflow vflag + C 0 carry cflag + +m68k_prefetch + if dynarec + + ccall m68k_read16_noinc context pc + mov result prefetch + + end + + if interp + + mov pc scratch1 + ocall read_16 + mov scratch1 prefetch + + end + + add 2 pc pc + +check_user_mode_swap_ssp_usp + local tmp 8 + and 0x20 status tmp + if tmp + else + + xchg other_sp a7 + + end + +m68k_get_sr + lsl status 8 scratch1 + or ccr scratch1 scratch1 + +m68k_write32_lowfirst + arg value 32 + add 2 scratch2 scratch2 + mov value scratch1 + ocall write_16 + + sub 2 scratch2 scratch2 + lsr value 16 scratch1 + ocall write_16 + +m68k_write32 + arg value 32 + local tmp 32 + mov value tmp + lsr value 16 scratch1 + ocall write_16 + + add 2 scratch2 scratch2 + mov tmp scratch1 + ocall write_16 + +m68k_read32 + local tmp 32 + add 2 scratch1 tmp + ocall read_16 + xchg scratch1 tmp + ocall read_16 + lsl tmp 16 tmp + or tmp scratch1 scratch1 + +m68k_interrupt + cmp int_cycle cycles + if >=U + + #INT_PENDING_NONE + cmp 255 int_pending + if = + + mov int_priority int_pending + mov int_num int_pending_num + + else + + #INT_PENDING_SR_CHANGE + cmp 254 int_pending + if = + + mov int_priority int_pending + mov int_num int_pending_num + + else + + check_user_mode_swap_ssp_usp + + cycles 6 + #save status reg + sub 6 a7 a7 + m68k_get_sr + mov a7 scratch2 + ocall write_16 + + #update status register + and 0x78 status status + or int_priority status status + or 0x20 status status + + #Interrupt ack cycle + mov int_pending int_ack + if int_pending_num + cycles 4 + else + #TODO: do the whole E clock variable latency nonsense + cycles 13 + add 24 int_pending int_pending_num + end + + #save pc + add 2 a7 scratch2 + m68k_write32_lowfirst pc + + lsl int_pending_num 2 scratch1 + m68k_read32 + mov scratch1 pc + update_sync + end + +m68k_run_op + dispatch prefetch + +m68k_mem_src + arg address 32 + arg size 16 + arg isdst 8 + mov address scratch1 + if isdst + mov address scratch2 + meta ismem 1 + end + switch size + + case 0 + ocall read_8 + + case 1 + ocall read_16 + + case 2 + m68k_read32 + + end + meta op scratch1 + +m68k_write_size + arg size 16 + arg lowfirst 8 + switch size + case 0 + ocall write_8 + + case 1 + ocall write_16 + + case 2 + if lowfirst + m68k_write32_lowfirst scratch1 + else + m68k_write32 scratch1 + end + end + +m68k_index_word + m68k_prefetch + local disp 32 + and prefetch 255 disp + sext 16 disp disp + sext 32 disp disp + local index 16 + lsr prefetch 12 index + local isareg 16 + and index 8 isareg + and index 7 index + local islong 16 + and prefetch 2048 islong + + switch isareg + case 0 + switch islong + case 0 + sext 32 dregs.index scratch1 + case 2048 + mov dregs.index scratch1 + end + case 8 + switch islong + case 0 + sext 32 aregs.index scratch1 + case 2048 + mov aregs.index scratch1 + end + end + add disp scratch1 scratch1 + +m68k_fetch_op_ea + arg mode 16 + arg reg 16 + arg Z 16 + arg isdst 8 + switch mode + + case 0 + #data reg direct + meta op dregs.reg + if isdst + meta ismem 0 + end + + case 1 + #address reg direct + meta op aregs.reg + if isdst + meta ismem 0 + end + + case 2 + #address reg indirect + m68k_mem_src aregs.reg Z isdst + + case 3 + #postincrement + m68k_mem_src aregs.reg Z isdst + switch reg + case 7 + if Z + addsize Z aregs.reg aregs.reg + else + addsize 1 aregs.reg aregs.reg + end + default + addsize Z aregs.reg aregs.reg + end + + case 4 + #predecrement + switch reg + case 7 + if Z + decsize Z aregs.reg aregs.reg + else + decsize 1 aregs.reg aregs.reg + end + default + decsize Z aregs.reg aregs.reg + end + cycles 2 + m68k_mem_src aregs.reg Z isdst + + case 5 + #displacement + m68k_prefetch + sext 32 prefetch scratch1 + add scratch1 aregs.reg scratch1 + m68k_mem_src scratch1 Z isdst + + case 6 + #indexed + m68k_index_word + add aregs.reg scratch1 scratch1 + + m68k_mem_src scratch1 Z isdst + case 7 + #pc-relative and absolute modes + + switch reg + case 0 + #absolute short + m68k_prefetch + sext 32 prefetch scratch1 + m68k_mem_src scratch1 Z isdst + + case 1 + #absolute long + local address 32 + m68k_prefetch + lsl prefetch 16 address + m68k_prefetch + or prefetch address scratch1 + m68k_mem_src scratch1 Z isdst + + case 2 + #pc displaceent + m68k_prefetch + sext 32 prefetch scratch1 + add scratch1 pc scratch1 + sub 2 scratch1 scratch1 + m68k_mem_src scratch1 Z isdst + + case 3 + #pc indexed + m68k_index_word + add pc scratch1 scratch1 + sub 2 scratch1 scratch1 + m68k_mem_src scratch1 Z isdst + + case 4 + #immediate + switch Z + case 2 + local tmp32 32 + m68k_prefetch + lsl prefetch 16 tmp32 + m68k_prefetch + or prefetch tmp32 scratch1 + + default + m68k_prefetch + mov prefetch scratch1 + end + meta op scratch1 + + end + + end + +m68k_fetch_src_ea + arg mode 16 + arg reg 16 + arg Z 16 + m68k_fetch_op_ea mode reg Z 0 + meta src op + switch mode + case 0 + meta src_is_mem 0 + case 1 + meta src_is_mem 0 + default + meta src_is_mem 1 + end + +m68k_fetch_dst_ea + arg mode 16 + arg reg 16 + arg Z 16 + m68k_fetch_op_ea mode reg Z 1 + meta dst op + +m68k_save_dst + arg Z 16 + if ismem + m68k_write_size Z 0 + end + +1101DDD0ZZMMMRRR add_ea_dn + invalid M 7 R 5 + invalid M 7 R 6 + invalid M 7 R 7 + invalid Z 3 + m68k_fetch_src_ea M R Z + + add src dregs.D dregs.D Z + update_flags XNZVC + m68k_prefetch + +1101DDD1ZZMMMRRR add_dn_ea + 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 + invalid Z 3 + m68k_fetch_dst_ea M R Z + + add dregs.D dst dst Z + update_flags XNZVC + m68k_save_dst Z + m68k_prefetch + +1101AAAZ11MMMRRR adda + invalid M 7 R 5 + invalid M 7 R 6 + invalid M 7 R 7 + local size 16 + local ext_src 32 + if Z + mov 2 size + else + mov 1 size + end + m68k_fetch_src_ea M R size + switch size + case 1 + sext 32 src ext_src + meta src ext_src + end + + add src aregs.A aregs.A + m68k_prefetch + +00000110ZZMMMRRR addi + local immed 32 + invalid Z 3 + 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 + #fetch immediate operand + m68k_prefetch + switch Z + case 2 + lsl prefetch 16 immed + m68k_prefetch + or prefetch immed immed + default + mov prefetch immed + end + #fetch dst EA + m68k_fetch_dst_ea M R Z + + add immed dst dst Z + update_flags XNZVC + m68k_save_dst Z + m68k_prefetch + +0101III0ZZMMMRRR addq + invalid Z 3 + 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 + local src 32 + switch I + case 0 + mov 8 src + default + mov I src + end + + m68k_fetch_dst_ea M R Z + switch M + case 1 + add src dst dst Z + default + add src dst dst Z + update_flags XNZVC + end + m68k_save_dst Z + m68k_prefetch + +1101DDD1ZZ000SSS addx_dy_dx + invalid Z 3 + adc dregs.S dregs.D dregs.D Z + update_flags XNVC + switch Z + case 0 + local tmp8 8 + mov dregs.D tmp8 + if tmp8 + update_flags Z0 + end + case 1 + local tmp16 16 + mov dregs.D tmp16 + if tmp16 + update_flags Z0 + end + case 2 + if dregs.D + update_flags Z0 + end + end + m68k_prefetch + +1101DDD1ZZ001SSS addx_ay_ax + invalid Z 3 + if Z + decsize Z aregs.S aregs.S + else + switch S + case 7 + sub 2 aregs.S aregs.S + default + decsize Z aregs.S aregs.S + end + end + mov aregs.S scratch1 + switch Z + case 0 + ocall read_8 + case 1 + ocall read_16 + case 2 + m68k_read32 + end + mov scratch1 scratch2 + if Z + decsize Z aregs.D aregs.D + else + switch D + case 7 + sub 2 aregs.D aregs.D + default + decsize Z aregs.D aregs.D + end + end + mov aregs.D scratch1 + switch Z + case 0 + ocall read_8 + case 1 + ocall read_16 + case 2 + m68k_read32 + end + adc scratch2 scratch1 scratch1 Z + update_flags XNVC + switch Z + case 0 + local tmp8 8 + mov dregs.D tmp8 + if tmp8 + update_flags Z0 + end + case 1 + local tmp16 16 + mov dregs.D tmp16 + if tmp16 + update_flags Z0 + end + case 2 + if dregs.D + update_flags Z0 + end + end + mov aregs.D scratch2 + m68k_write_size Z 0 + m68k_prefetch + + +00ZZRRRMMMEEESSS move + invalid Z 0 + invalid M 1 + invalid M 7 #not actually invalid, but will be handled separately due to DSL limitations + invalid E 7 S 5 + invalid E 7 S 6 + invalid E 7 S 7 + local size 8 + local memsrc 32 + #move uses a different size format than most instructions + switch Z + case 1 + mov 0 size + case 2 + mov 2 size + case 3 + mov 1 size + end + m68k_fetch_src_ea E S size + + if src_is_mem + #avoid clobbering src if we need scratch1 + mov src memsrc + meta src memsrc + end + + cmp 0 src size + update_flags NZV0C0 + + switch M + case 0 + mov src dregs.R size + + case 2 + mov aregs.R scratch2 + mov src scratch1 + m68k_write_size size 0 + + case 3 + mov aregs.R scratch2 + mov src scratch1 + switch R + case 7 + if size + addsize size aregs.R aregs.R + else + addsize 1 aregs.R aregs.R + end + default + addsize size aregs.R aregs.R + end + m68k_write_size size 0 + + case 4 + mov src scratch1 + switch R + case 7 + if size + decsize size aregs.R aregs.R + else + decsize 1 aregs.R aregs.R + end + default + decsize size aregs.R aregs.R + end + mov aregs.R scratch2 + m68k_write_size size 1 + + case 5 + m68k_prefetch + sext 32 prefetch scratch2 + add aregs.R scratch2 scratch2 + mov src scratch1 + m68k_write_size size 0 + + case 6 + m68k_index_word + add aregs.R scratch1 scratch2 + mov src scratch1 + m68k_write_size size 0 + end + m68k_prefetch + + +00ZZ00M111EEESSS move_abs + invalid E 7 S 5 + invalid E 7 S 6 + invalid E 7 S 7 + invalid Z 0 + local size 8 + local memsrc 32 + #move uses a different size format than most instructions + switch Z + case 1 + mov 0 size + case 2 + mov 2 size + case 3 + mov 1 size + end + m68k_fetch_src_ea E S size + + if src_is_mem + #avoid clobbering src if we need scratch1 + mov src memsrc + meta src memsrc + end + + cmp 0 src size + update_flags NZV0C0 + + switch M + case 0 + m68k_prefetch + sext 32 prefetch scratch2 + + case 1 + m68k_prefetch + lsl prefetch 16 scratch2 + m68k_prefetch + or prefetch scratch2 scratch2 + end + mov src scratch1 + m68k_write_size size 0 + m68k_prefetch + +00ZZRRR001EEESSS movea + local size 8 + invalid Z 0 + invalid Z 1 + invalid E 7 S 5 + invalid E 7 S 6 + invalid E 7 S 7 + switch Z + case 2 + mov 2 size + case 3 + mov 1 size + end + m68k_fetch_src_ea E S size + switch Z + case 2 + mov src aregs.R + case 3 + sext 32 src aregs.R + end + m68k_prefetch + +0100010011MMMRRR move_to_ccr + invalid M 1 + invalid M 7 R 5 + invalid M 7 R 6 + invalid M 7 R 7 + m68k_fetch_src_ea M R 1 + mov scratch1 ccr + m68k_prefetch + +0100011011MMMRRR move_to_sr + invalid M 1 + invalid M 7 R 5 + invalid M 7 R 6 + invalid M 7 R 7 + m68k_fetch_src_ea M R 1 + mov scratch1 ccr + lsr scratch1 8 status + update_sync + m68k_prefetch + +0100000011MMMRRR move_from_sr + 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 1 + lsl status 8 scratch1 + or ccr scratch1 scratch1 + mov scratch1 dst + m68k_save_dst 1 + m68k_prefetch + +0100111001110000 reset + cycles 124 + if reset_handler + pcall reset_handler m68k_reset_handler context + end diff -r 13abdc98379e -r 374a5ae694e8 m68k_core.c --- a/m68k_core.c Thu Apr 18 22:06:47 2019 -0700 +++ b/m68k_core.c Sat Apr 18 11:42:53 2020 -0700 @@ -4,17 +4,17 @@ 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 "m68k_core.h" -#ifdef USE_NATIVE +#ifndef NEW_CORE #include "m68k_internal.h" #endif #include "68kinst.h" #include "backend.h" -#ifdef USE_NATIVE +#ifndef NEW_CORE #include "gen.h" #endif #include "util.h" #include "serialize.h" -#ifndef USE_NATIVE +#ifdef NEW_CORE #include "musashi/m68kcpu.h" #endif #include @@ -23,7 +23,7 @@ #include char disasm_buf[1024]; -#ifdef USE_NATIVE +#ifndef NEW_CORE int8_t native_reg(m68k_op_info * op, m68k_options * opts) { if (op->addr_mode == MODE_REG) { @@ -61,7 +61,7 @@ printf("a%d: %X\n", i, context->aregs[i]); } } -#ifdef USE_NATIVE +#ifndef NEW_CORE void m68k_read_size(m68k_options *opts, uint8_t size) { switch (size) @@ -779,7 +779,7 @@ || (inst->op == M68K_BCC && inst->extra.cond == COND_TRUE); } -#ifdef USE_NATIVE +#ifndef NEW_CORE static void m68k_handle_deferred(m68k_context * context) { m68k_options * opts = context->options; @@ -792,7 +792,7 @@ uint16_t m68k_get_ir(m68k_context *context) { -#ifdef USE_NATIVE +#ifndef NEW_CORE uint32_t inst_addr = get_instruction_start(context->options, context->last_prefetch_address-2); uint16_t *native_addr = get_native_pointer(inst_addr, (void **)context->mem_pointers, &context->options->gen); if (native_addr) { @@ -830,7 +830,7 @@ .handler = bp_handler, .address = address }; -#ifdef USE_NATIVE +#ifndef NEW_CORE m68k_breakpoint_patch(context, address, bp_handler, NULL); #endif } @@ -850,7 +850,7 @@ return context; } -#ifdef USE_NATIVE +#ifndef NEW_CORE typedef enum { RAW_FUNC = 1, BINARY_ARITH, @@ -1175,7 +1175,7 @@ break; } } -#ifdef USE_NATIVE +#ifndef NEW_CORE code_ptr native = get_native_address(context->options, address); if (!native) { return; @@ -1192,7 +1192,7 @@ void start_68k_context(m68k_context * context, uint32_t address) { m68k_options * options = context->options; -#ifdef USE_NATIVE +#ifndef NEW_CORE code_ptr addr = get_native_address_trans(context, address); options->start_context(addr, context); #else @@ -1210,7 +1210,7 @@ void resume_68k(m68k_context *context) { -#ifdef USE_NATIVE +#ifndef NEW_CORE code_ptr addr = context->resume_pc; context->resume_pc = NULL; m68k_options * options = context->options; @@ -1223,7 +1223,7 @@ void m68k_reset(m68k_context * context) { -#ifdef USE_NATIVE +#ifndef NEW_CORE //TODO: Actually execute the M68K reset vector rather than simulating some of its behavior uint16_t *reset_vec = get_native_pointer(0, (void **)context->mem_pointers, &context->options->gen); context->aregs[7] = reset_vec[0] << 16 | reset_vec[1]; @@ -1237,7 +1237,7 @@ void m68k_options_free(m68k_options *opts) { -#ifdef USE_NATIVE +#ifndef NEW_CORE for (uint32_t address = 0; address < opts->gen.address_mask; address += NATIVE_CHUNK_SIZE) { uint32_t chunk = address / NATIVE_CHUNK_SIZE; @@ -1257,7 +1257,7 @@ free(opts); } -#ifndef USE_NATIVE +#ifdef NEW_CORE void init_m68k_opts(m68k_options * opts, memmap_chunk * memmap, uint32_t num_chunks, uint32_t clock_divider) { memset(opts, 0, sizeof(*opts)); @@ -1273,7 +1273,7 @@ m68k_context * init_68k_context(m68k_options * opts, m68k_reset_handler reset_handler) { -#ifdef USE_NATIVE +#ifndef NEW_CORE m68k_context * context = calloc(1, sizeof(m68k_context) + ram_size(&opts->gen) / (1 << opts->gen.ram_flags_shift) / 8); context->options = opts; #else @@ -1301,7 +1301,7 @@ { save_int32(buf, context->aregs[i]); } -#ifdef USE_NATIVE +#ifndef NEW_CORE save_int32(buf, pc); uint16_t sr = context->status << 3; for (int flag = 4; flag >= 0; flag--) { @@ -1332,7 +1332,7 @@ { context->aregs[i] = load_int32(buf); } -#ifdef USE_NATIVE +#ifndef NEW_CORE //hack until both PC and IR registers are represented properly context->last_prefetch_address = load_int32(buf); uint16_t sr = load_int16(buf); @@ -1354,7 +1354,7 @@ context->trace_pending = load_int8(buf); } -#ifndef USE_NATIVE +#ifdef NEW_CORE void m68k_invalidate_code_range(m68k_context *context, uint32_t start, uint32_t end) { m68000_base_device *device = (m68000_base_device *)context; diff -r 13abdc98379e -r 374a5ae694e8 m68k_core.h --- a/m68k_core.h Thu Apr 18 22:06:47 2019 -0700 +++ b/m68k_core.h Sat Apr 18 11:42:53 2020 -0700 @@ -36,13 +36,13 @@ typedef struct { cpu_options gen; -#ifdef USE_NATIVE +#ifndef NEW_CORE int8_t dregs[8]; int8_t aregs[8]; int8_t flag_regs[5]; #endif FILE *address_log; -#ifdef USE_NATIVE +#ifndef NEW_CORE code_ptr read_16; code_ptr write_16; code_ptr read_8; @@ -119,7 +119,7 @@ uint16_t m68k_get_ir(m68k_context *context); void m68k_print_regs(m68k_context * context); void m68k_invalidate_code_range(m68k_context *context, uint32_t start, uint32_t end); -#ifdef USE_NATIVE +#ifndef NEW_CORE m68k_context * m68k_handle_code_write(uint32_t address, m68k_context * context); #else #define m68k_handle_code_write(A, M) diff -r 13abdc98379e -r 374a5ae694e8 nuklear_ui/blastem_nuklear.c --- a/nuklear_ui/blastem_nuklear.c Thu Apr 18 22:06:47 2019 -0700 +++ b/nuklear_ui/blastem_nuklear.c Sat Apr 18 11:42:53 2020 -0700 @@ -50,12 +50,14 @@ } previous_views[num_prev++] = current_view; current_view = new_view; + context->input.selected_widget = 0; } static void pop_view() { if (num_prev) { current_view = previous_views[--num_prev]; + context->input.selected_widget = 0; } } @@ -439,11 +441,11 @@ }; const char *general_binds[] = { "ui.exit", "ui.save_state", "ui.toggle_fullscreen", "ui.soft_reset", "ui.reload", - "ui.screenshot", "ui.sms_pause", "ui.toggle_keyboard_cpatured", "ui.release_mouse" + "ui.screenshot", "ui.vgm_log", "ui.sms_pause", "ui.toggle_keyboard_cpatured", "ui.release_mouse" }; const char *general_names[] = { "Show Menu", "Quick Save", "Toggle Fullscreen", "Soft Reset", "Reload Media", - "Internal Screenshot", "SMS Pause", "Capture Keyboard", "Release Mouse" + "Internal Screenshot", "Toggle VGM Log", "SMS Pause", "Capture Keyboard", "Release Mouse" }; const char *speed_binds[] = { "ui.next_speed", "ui.prev_speed", @@ -590,6 +592,7 @@ conf_names = tern_insert_ptr(conf_names, "ui.vdp_debug_pal", "VDP Debug Palette"); conf_names = tern_insert_ptr(conf_names, "ui.enter_debugger", "Enter CPU Debugger"); conf_names = tern_insert_ptr(conf_names, "ui.screenshot", "Take Screenshot"); + conf_names = tern_insert_ptr(conf_names, "ui.vgm_log", "Toggle VGM Log"); conf_names = tern_insert_ptr(conf_names, "ui.exit", "Show Menu"); conf_names = tern_insert_ptr(conf_names, "ui.save_state", "Quick Save"); conf_names = tern_insert_ptr(conf_names, "ui.set_speed.0", "Set Speed 0"); @@ -1173,6 +1176,7 @@ mapping_string[mapping_pos++] = ':'; } +static uint8_t initial_controller_config; #define QUIET_FRAMES 9 static void view_controller_mappings(struct nk_context *context) { @@ -1275,8 +1279,10 @@ save_controller_mapping(selected_controller, mapping_string); free(mapping_string); pop_view(); - push_view(view_controller_bindings); - controller_binding_changed = 0; + if (initial_controller_config) { + push_view(view_controller_bindings); + controller_binding_changed = 0; + } added_mapping = 0; } else if (get_axis_label(&selected_controller_info, current_axis)) { added_mapping = 0; @@ -1290,6 +1296,31 @@ } } +static void show_mapping_view(void) +{ + current_button = SDL_CONTROLLER_BUTTON_A; + button_pressed = -1; + last_button = -1; + last_hat = -1; + axis_moved = -1; + last_axis = -1; + last_axis_value = 0; + SDL_Joystick *joy = render_get_joystick(selected_controller); + const char *name = SDL_JoystickName(joy); + size_t namesz = strlen(name); + mapping_string = malloc(512 + namesz); + for (mapping_pos = 0; mapping_pos < namesz; mapping_pos++) + { + char c = name[mapping_pos]; + if (c == ',' || c == '\n' || c == '\r') { + c = ' '; + } + mapping_string[mapping_pos] = c; + } + + push_view(view_controller_mappings); +} + static void view_controller_variant(struct nk_context *context) { uint8_t selected = 0; @@ -1325,33 +1356,15 @@ if (selected) { save_controller_info(selected_controller, &selected_controller_info); pop_view(); - SDL_GameController *controller = render_get_controller(selected_controller); - if (controller) { - push_view(view_controller_bindings); - controller_binding_changed = 0; - SDL_GameControllerClose(controller); - } else { - current_button = SDL_CONTROLLER_BUTTON_A; - button_pressed = -1; - last_button = -1; - last_hat = -1; - axis_moved = -1; - last_axis = -1; - last_axis_value = 0; - SDL_Joystick *joy = render_get_joystick(selected_controller); - const char *name = SDL_JoystickName(joy); - size_t namesz = strlen(name); - mapping_string = malloc(512 + namesz); - for (mapping_pos = 0; mapping_pos < namesz; mapping_pos++) - { - char c = name[mapping_pos]; - if (c == ',' || c == '\n' || c == '\r') { - c = ' '; - } - mapping_string[mapping_pos] = c; + 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(); } - - push_view(view_controller_mappings); } } } @@ -1396,36 +1409,85 @@ void view_controllers(struct nk_context *context) { if (nk_begin(context, "Controllers", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) { - int height = (render_width() - 2*context->style.font->height) / MAX_JOYSTICKS; + int height = (render_height() - 2*context->style.font->height) / 5; + int inner_height = height - context->style.window.spacing.y; + const struct nk_user_font *font = context->style.font; + int bindings_width = font->width(font->userdata, font->height, "Bindings", strlen("Bindings")) + context->style.button.padding.x * 2; + int remap_width = font->width(font->userdata, font->height, "Remap", strlen("Remap")) + context->style.button.padding.x * 2; + int change_type_width = font->width(font->userdata, font->height, "Change Type", strlen("Change Type")) + context->style.button.padding.x * 2; + int total = bindings_width + remap_width + change_type_width; + float bindings_ratio = (float)bindings_width / total; + float remap_ratio = (float)remap_width / total; + float change_type_ratio = (float)change_type_width / total; + + + uint8_t found_controller = 0; for (int i = 0; i < MAX_JOYSTICKS; i++) { SDL_Joystick *joy = render_get_joystick(i); if (joy) { + found_controller = 1; controller_info info = get_controller_info(i); ui_image *controller_image = select_best_image(&info); - int image_width = height * controller_image->width / controller_image->height; - nk_layout_row_begin(context, NK_STATIC, height, 2); - nk_layout_row_push(context, image_width); + int image_width = inner_height * controller_image->width / controller_image->height; + nk_layout_space_begin(context, NK_STATIC, height, INT_MAX); + nk_layout_space_push(context, nk_rect(context->style.font->height / 2, 0, image_width, inner_height)); if (info.type == TYPE_UNKNOWN || info.type == TYPE_GENERIC_MAPPING) { nk_label(context, "?", NK_TEXT_CENTERED); } else { nk_image(context, controller_image->ui); } - nk_layout_row_push(context, render_width() - image_width - 2 * context->style.font->height); - if (nk_button_label(context, info.name)) { - selected_controller = i; - selected_controller_info = info; - if (info.type == TYPE_UNKNOWN || info.type == TYPE_GENERIC_MAPPING) { + int button_start = image_width + context->style.font->height; + int button_area_width = render_width() - image_width - 2 * context->style.font->height; + + nk_layout_space_push(context, nk_rect(button_start, 0, button_area_width, inner_height/2)); + nk_label(context, info.name, NK_TEXT_CENTERED); + const struct nk_user_font *font = context->style.font; + if (info.type == TYPE_UNKNOWN || info.type == TYPE_GENERIC_MAPPING) { + int button_width = font->width(font->userdata, font->height, "Configure", strlen("Configure")); + nk_layout_space_push(context, nk_rect(button_start, height/2, button_width, inner_height/2)); + if (nk_button_label(context, "Configure")) { + selected_controller = i; + selected_controller_info = info; + initial_controller_config = 1; push_view(view_controller_type); - } else { + } + } else { + button_area_width -= 2 * context->style.window.spacing.x; + bindings_width = bindings_ratio * button_area_width; + nk_layout_space_push(context, nk_rect(button_start, height/2, bindings_width, inner_height/2)); + if (nk_button_label(context, "Bindings")) { + selected_controller = i; + selected_controller_info = info; push_view(view_controller_bindings); controller_binding_changed = 0; } - + button_start += bindings_width + context->style.window.spacing.x; + remap_width = remap_ratio * button_area_width; + nk_layout_space_push(context, nk_rect(button_start, height/2, remap_width, inner_height/2)); + if (nk_button_label(context, "Remap")) { + selected_controller = i; + selected_controller_info = info; + initial_controller_config = 0; + show_mapping_view(); + } + button_start += remap_width + context->style.window.spacing.x; + change_type_width = change_type_ratio * button_area_width; + nk_layout_space_push(context, nk_rect(button_start, height/2, change_type_width, inner_height/2)); + if (nk_button_label(context, "Change Type")) { + selected_controller = i; + selected_controller_info = info; + initial_controller_config = 0; + push_view(view_controller_type); + } } - nk_layout_row_end(context); + //nk_layout_row_end(context); } } + if (!found_controller) { + nk_layout_row_static(context, context->style.font->height, render_width() - 2 * context->style.font->height, 1); + nk_label(context, "No controllers detected", NK_TEXT_CENTERED); + } nk_layout_row_static(context, context->style.font->height, (render_width() - 2 * context->style.font->height) / 2, 2); nk_label(context, "", NK_TEXT_LEFT); if (nk_button_label(context, "Back")) { @@ -1466,6 +1528,24 @@ } } +void settings_string(struct nk_context *context, char *label, char *path, char *def) +{ + nk_label(context, label, NK_TEXT_LEFT); + char *curstr = tern_find_path_default(config, path, (tern_val){.ptrval = def}, TVAL_PTR).ptrval; + uint32_t len = strlen(curstr); + uint32_t buffer_len = len > 100 ? len + 1 : 101; + char *buffer = malloc(buffer_len); + memcpy(buffer, curstr, len); + memset(buffer+len, 0, buffer_len-len); + nk_edit_string(context, NK_EDIT_SIMPLE, buffer, &len, buffer_len-1, nk_filter_default); + buffer[len] = 0; + if (strcmp(buffer, curstr)) { + config_dirty = 1; + config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR); + } + free(buffer); +} + void settings_int_property(struct nk_context *context, char *label, char *name, char *path, int def, int min, int max) { char *curstr = tern_find_path(config, path, TVAL_PTR).ptrval; @@ -1524,7 +1604,7 @@ if (!dupe) { if (num_progs == prog_storage) { prog_storage = prog_storage ? prog_storage*2 : 4; - progs = realloc(progs, sizeof(progs) * prog_storage); + progs = realloc(progs, sizeof(*progs) * prog_storage); } progs[num_progs].vertex = NULL; progs[num_progs++].fragment = strdup(entries[i].name); @@ -1576,6 +1656,7 @@ shader_dir = path_append(get_exe_dir(), "shaders"); #endif entries = get_dir_list(shader_dir, &num_entries); + free(shader_dir); progs = get_shader_progs(entries, num_entries, progs, &num_progs, &prog_storage); *num_out = num_progs; return progs; @@ -1751,6 +1832,50 @@ nk_end(context); } } +typedef struct { + const char **models; + const char **names; + uint32_t num_models; + uint32_t storage; +} model_foreach_state; +void model_iter(char *key, tern_val val, uint8_t valtype, void *data) +{ + if (valtype != TVAL_NODE) { + return; + } + model_foreach_state *state = data; + if (state->num_models == state->storage) { + state->storage *= 2; + state->models = realloc(state->models, state->storage * sizeof(char *)); + state->names = realloc(state->names, state->storage * sizeof(char *)); + } + char *def = strdup(key); + state->models[state->num_models] = def; + state->names[state->num_models++] = tern_find_ptr_default(val.ptrval, "name", def); +} + +typedef struct { + const char **models; + const char **names; +} models; + +models get_models(uint32_t *num_out) +{ + tern_node *systems = get_systems_config(); + model_foreach_state state = { + .models = calloc(4, sizeof(char *)), + .names = calloc(4, sizeof(char *)), + .num_models = 0, + .storage = 4 + }; + tern_foreach(systems, model_iter, &state); + *num_out = state.num_models; + return (models){ + .models = state.models, + .names = state.names + }; +} + void view_system_settings(struct nk_context *context) { const char *sync_opts[] = { @@ -1773,12 +1898,25 @@ if (selected_region < 0) { selected_region = find_match(region_codes, num_regions, "system\0default_region\0", "U"); } + static const char **model_opts; + static const char **model_names; + static uint32_t num_models; + if (!model_opts) { + models m = get_models(&num_models); + model_opts = m.models; + model_names = m.names; + } + static int32_t selected_model = -1; + if (selected_model < 0) { + selected_model = find_match(model_opts, num_models, "system\0model\0", "md1va3"); + } + const char *formats[] = { "native", "gst" }; const uint32_t num_formats = sizeof(formats)/sizeof(*formats); - int32_t selected_format = -1; + static int32_t selected_format = -1; if (selected_format < 0) { selected_format = find_match(formats, num_formats, "ui\0state_format\0", "native"); } @@ -1825,7 +1963,10 @@ 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); 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"); @@ -1919,6 +2060,7 @@ void ui_idle_loop(void) { + render_enable_gamepad_events(1); const uint32_t MIN_UI_DELAY = 15; static uint32_t last; while (current_view != view_play) @@ -1935,6 +2077,7 @@ persist_config(config); config_dirty = 0; } + render_enable_gamepad_events(0); } static void handle_event(SDL_Event *event) { @@ -2054,6 +2197,11 @@ .r = 255, .g = 128, .b = 0, .a = 255 }; context->style.checkbox.cursor_hover = context->style.checkbox.cursor_normal; + context->style.property.inc_button.text_hover = (struct nk_color){ + .r = 255, .g = 128, .b = 0, .a = 255 + }; + context->style.property.dec_button.text_hover = context->style.property.inc_button.text_hover; + context->style.combo.button.text_hover = context->style.property.inc_button.text_hover; } static void context_created(void) @@ -2071,6 +2219,7 @@ context->style.window.background = nk_rgba(0, 0, 0, 128); context->style.window.fixed_background = nk_style_item_color(nk_rgba(0, 0, 0, 128)); current_view = view_pause; + context->input.selected_widget = 0; current_system->request_exit(current_system); } else if (current_system && !set_binding) { clear_view_stack(); @@ -2082,6 +2231,7 @@ { set_content_binding_state(1); current_view = view_play; + context->input.selected_widget = 0; } static uint8_t active; diff -r 13abdc98379e -r 374a5ae694e8 nuklear_ui/nuklear.h --- a/nuklear_ui/nuklear.h Thu Apr 18 22:06:47 2019 -0700 +++ b/nuklear_ui/nuklear.h Sat Apr 18 11:42:53 2020 -0700 @@ -1845,7 +1845,8 @@ NK_WIDGET_STATE_HOVERED = NK_WIDGET_STATE_HOVER|NK_WIDGET_STATE_MODIFIED, /* widget is being hovered */ NK_WIDGET_STATE_ACTIVE = NK_WIDGET_STATE_ACTIVED|NK_WIDGET_STATE_MODIFIED /* widget is currently activated */ }; -NK_API enum nk_widget_layout_states nk_widget(struct nk_rect*, const struct nk_context*); +NK_API enum nk_widget_layout_states nk_widget(struct nk_rect*, struct nk_context*); +NK_API enum nk_widget_layout_states nk_keynav_widget(struct nk_rect *, struct nk_context *); NK_API enum nk_widget_layout_states nk_widget_fitting(struct nk_rect*, struct nk_context*, struct nk_vec2); NK_API struct nk_rect nk_widget_bounds(struct nk_context*); NK_API struct nk_vec2 nk_widget_position(struct nk_context*); @@ -3192,6 +3193,8 @@ struct nk_input { struct nk_keyboard keyboard; struct nk_mouse mouse; + int widget_counter; + int selected_widget; }; NK_API int nk_input_has_mouse_click(const struct nk_input*, enum nk_buttons); @@ -12568,6 +12571,7 @@ in->mouse.delta.y = 0; for (i = 0; i < NK_KEY_MAX; i++) in->keyboard.keys[i].clicked = 0; + in->widget_counter = -1; } NK_API void @@ -12692,9 +12696,11 @@ const struct nk_mouse_button *btn; if (!i) return nk_false; btn = &i->mouse.buttons[id]; - if (!NK_INBOX(btn->clicked_pos.x,btn->clicked_pos.y,b.x,b.y,b.w,b.h)) - return nk_false; - return nk_true; + if (NK_INBOX(btn->clicked_pos.x,btn->clicked_pos.y,b.x,b.y,b.w,b.h)) + return nk_true; + if (i->selected_widget == i->widget_counter && i->keyboard.keys[NK_KEY_ENTER].clicked) + return nk_true; + return nk_false; } NK_API int @@ -12742,7 +12748,7 @@ nk_input_is_mouse_hovering_rect(const struct nk_input *i, struct nk_rect rect) { if (!i) return nk_false; - return NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); + return i->selected_widget == i->widget_counter || NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); } NK_API int @@ -12764,7 +12770,9 @@ nk_input_is_mouse_down(const struct nk_input *i, enum nk_buttons id) { if (!i) return nk_false; - return i->mouse.buttons[id].down; + return i->mouse.buttons[id].down || ( + id == NK_BUTTON_LEFT && i->widget_counter == i->selected_widget && i->keyboard.keys[NK_KEY_ENTER].down + ); } NK_API int @@ -12775,6 +12783,11 @@ b = &i->mouse.buttons[id]; if (b->down && b->clicked) return nk_true; + if ( + id == NK_BUTTON_LEFT && i->widget_counter == i->selected_widget + && i->keyboard.keys[NK_KEY_ENTER].down && i->keyboard.keys[NK_KEY_ENTER].clicked + ) + return nk_true; return nk_false; } @@ -12782,7 +12795,14 @@ nk_input_is_mouse_released(const struct nk_input *i, enum nk_buttons id) { if (!i) return nk_false; - return (!i->mouse.buttons[id].down && i->mouse.buttons[id].clicked); + if (!i->mouse.buttons[id].down && i->mouse.buttons[id].clicked) + return nk_true; + if ( + id == NK_BUTTON_LEFT && i->widget_counter == i->selected_widget + && !i->keyboard.keys[NK_KEY_ENTER].down && i->keyboard.keys[NK_KEY_ENTER].clicked + ) + return nk_true; + return nk_false; } NK_API int @@ -14610,7 +14630,7 @@ NK_INTERN int nk_do_selectable(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, const char *str, int len, nk_flags align, int *value, - const struct nk_style_selectable *style, const struct nk_input *in, + const struct nk_style_selectable *style, struct nk_input *in, const struct nk_user_font *font) { int old_value; @@ -14636,6 +14656,12 @@ /* update button */ if (nk_button_behavior(state, touch, in, NK_BUTTON_DEFAULT)) *value = !(*value); + + if (!old_value && !(*value) && in && in->selected_widget == in->widget_counter) { + *value = 1; + } else if (!old_value && *value && in) { + in->selected_widget = in->widget_counter; + } /* draw selectable */ if (style->draw_begin) style->draw_begin(out, style->userdata); @@ -16161,8 +16187,18 @@ nk_draw_property(out, style, &property, &label, *ws, name, name_len, font); if (style->draw_end) style->draw_end(out, style->userdata); - /* execute right button */ - if (nk_do_button_symbol(ws, out, left, style->sym_left, behavior, &style->dec_button, in, font)) { + int selected = in && in->selected_widget == in->widget_counter; + int enter_clicked; + if (selected) { + //prevent left/right buttons from activating when enter is pressed + enter_clicked = in->keyboard.keys[NK_KEY_ENTER].clicked; + in->keyboard.keys[NK_KEY_ENTER].clicked = 0; + } + /* execute left button */ + if ( + nk_do_button_symbol(ws, out, left, style->sym_left, behavior, &style->dec_button, in, font) + || (selected && in->keyboard.keys[NK_KEY_LEFT].clicked && in->keyboard.keys[NK_KEY_LEFT].down) + ) { switch (variant->kind) { default: break; case NK_PROPERTY_INT: @@ -16173,8 +16209,11 @@ variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d - variant->step.d, variant->max_value.d); break; } } - /* execute left button */ - if (nk_do_button_symbol(ws, out, right, style->sym_right, behavior, &style->inc_button, in, font)) { + /* execute right button */ + if ( + nk_do_button_symbol(ws, out, right, style->sym_right, behavior, &style->inc_button, in, font) + || (selected && in->keyboard.keys[NK_KEY_RIGHT].clicked && in->keyboard.keys[NK_KEY_RIGHT].down) + ) { switch (variant->kind) { default: break; case NK_PROPERTY_INT: @@ -16185,6 +16224,9 @@ variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d + variant->step.d, variant->max_value.d); break; } } + if (selected) { + in->keyboard.keys[NK_KEY_ENTER].clicked = enter_clicked; + } if (old != NK_PROPERTY_EDIT && (*state == NK_PROPERTY_EDIT)) { /* property has been activated so setup buffer */ NK_MEMCPY(buffer, dst, (nk_size)*length); @@ -17259,6 +17301,7 @@ #ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT nk_draw_list_init(&ctx->draw_list); #endif + ctx->input.widget_counter = -1; } #ifdef NK_INCLUDE_DEFAULT_ALLOCATOR @@ -20238,7 +20281,7 @@ } NK_API enum nk_widget_layout_states -nk_widget(struct nk_rect *bounds, const struct nk_context *ctx) +nk_widget_gen(struct nk_rect *bounds, struct nk_context *ctx, nk_byte is_keynav) { struct nk_rect c, v; struct nk_window *win; @@ -20277,13 +20320,51 @@ c.y = (float)((int)c.y); c.w = (float)((int)c.w); c.h = (float)((int)c.h); + int newly_selected = nk_false; + if (is_keynav) { + ctx->input.widget_counter++; + if ( + ctx->input.selected_widget == (ctx->input.widget_counter + 1) && + ctx->input.keyboard.keys[NK_KEY_UP].clicked && ctx->input.keyboard.keys[NK_KEY_UP].down + ) { + ctx->input.selected_widget--; + newly_selected = nk_true; + } else if ( + ctx->input.selected_widget == (ctx->input.widget_counter - 1) && + ctx->input.keyboard.keys[NK_KEY_DOWN].clicked && ctx->input.keyboard.keys[NK_KEY_DOWN].down + ) { + ctx->input.keyboard.keys[NK_KEY_DOWN].clicked = 0; + ctx->input.selected_widget++; + newly_selected = nk_true; + } + } nk_unify(&v, &c, bounds->x, bounds->y, bounds->x + bounds->w, bounds->y + bounds->h); + if (is_keynav && newly_selected) { + //ensure widget is fully on-screen if it was newly selected via a keyboard action + if ((bounds->y + bounds->h) > (c.y + c.h)) { + *layout->offset_y += bounds->y + bounds->h - (c.y + c.h); + } else if(c.y > bounds->y){ + *layout->offset_y -= c.y - bounds->y; + } + } if (!NK_INTERSECT(c.x, c.y, c.w, c.h, bounds->x, bounds->y, bounds->w, bounds->h)) return NK_WIDGET_INVALID; - if (!NK_INBOX(in->mouse.pos.x, in->mouse.pos.y, v.x, v.y, v.w, v.h)) - return NK_WIDGET_ROM; - return NK_WIDGET_VALID; + if ((is_keynav && ctx->input.selected_widget == ctx->input.widget_counter ) || NK_INBOX(in->mouse.pos.x, in->mouse.pos.y, v.x, v.y, v.w, v.h)) + return NK_WIDGET_VALID; + return NK_WIDGET_ROM; +} + +NK_API enum nk_widget_layout_states +nk_widget(struct nk_rect *bounds, struct nk_context *ctx) +{ + return nk_widget_gen(bounds, ctx, 0); +} + +NK_API enum nk_widget_layout_states +nk_keynav_widget(struct nk_rect *bounds, struct nk_context *ctx) +{ + return nk_widget_gen(bounds, ctx, 1); } NK_API enum nk_widget_layout_states @@ -20306,7 +20387,7 @@ win = ctx->current; style = &ctx->style; layout = win->layout; - state = nk_widget(bounds, ctx); + state = nk_keynav_widget(bounds, ctx); panel_padding = nk_panel_get_padding(style, layout->type); if (layout->row.index == 1) { @@ -20627,13 +20708,17 @@ win = ctx->current; layout = win->layout; - state = nk_widget(&bounds, ctx); + state = nk_keynav_widget(&bounds, ctx); if (!state) return 0; in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; - return nk_do_button_text(&ctx->last_widget_state, &win->buffer, bounds, + int ret = nk_do_button_text(&ctx->last_widget_state, &win->buffer, bounds, title, len, style->text_alignment, ctx->button_behavior, style, in, ctx->style.font); + if (ctx->last_widget_state & NK_WIDGET_STATE_ENTERED) { + ctx->input.selected_widget = ctx->input.widget_counter; + } + return ret; } NK_API int @@ -20673,7 +20758,7 @@ win = ctx->current; layout = win->layout; - state = nk_widget(&bounds, ctx); + state = nk_keynav_widget(&bounds, ctx); if (!state) return 0; in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; @@ -20684,6 +20769,9 @@ ret = nk_do_button(&ctx->last_widget_state, &win->buffer, bounds, &button, in, ctx->button_behavior, &content); nk_draw_button(&win->buffer, &bounds, ctx->last_widget_state, &button); + if (ctx->last_widget_state & NK_WIDGET_STATE_ENTERED) { + ctx->input.selected_widget = ctx->input.widget_counter; + } return ret; } @@ -20706,11 +20794,15 @@ win = ctx->current; layout = win->layout; - state = nk_widget(&bounds, ctx); + state = nk_keynav_widget(&bounds, ctx); if (!state) return 0; in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; - return nk_do_button_symbol(&ctx->last_widget_state, &win->buffer, bounds, + int ret = nk_do_button_symbol(&ctx->last_widget_state, &win->buffer, bounds, symbol, ctx->button_behavior, style, in, ctx->style.font); + if (ctx->last_widget_state & NK_WIDGET_STATE_ENTERED) { + ctx->input.selected_widget = ctx->input.widget_counter; + } + return ret; } NK_API int @@ -20741,7 +20833,7 @@ win = ctx->current; layout = win->layout; - state = nk_widget(&bounds, ctx); + state = nk_keynav_widget(&bounds, ctx); if (!state) return 0; in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; return nk_do_button_image(&ctx->last_widget_state, &win->buffer, bounds, @@ -20777,7 +20869,7 @@ win = ctx->current; layout = win->layout; - state = nk_widget(&bounds, ctx); + state = nk_keynav_widget(&bounds, ctx); if (!state) return 0; in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; return nk_do_button_text_symbol(&ctx->last_widget_state, &win->buffer, bounds, @@ -20824,7 +20916,7 @@ win = ctx->current; layout = win->layout; - state = nk_widget(&bounds, ctx); + state = nk_keynav_widget(&bounds, ctx); if (!state) return 0; in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; return nk_do_button_text_image(&ctx->last_widget_state, &win->buffer, @@ -20858,7 +20950,7 @@ { struct nk_window *win; struct nk_panel *layout; - const struct nk_input *in; + struct nk_input *in; const struct nk_style *style; enum nk_widget_layout_states state; @@ -20875,7 +20967,7 @@ layout = win->layout; style = &ctx->style; - state = nk_widget(&bounds, ctx); + state = nk_keynav_widget(&bounds, ctx); if (!state) return 0; in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; return nk_do_selectable(&ctx->last_widget_state, &win->buffer, bounds, @@ -20960,7 +21052,7 @@ style = &ctx->style; layout = win->layout; - state = nk_widget(&bounds, ctx); + state = nk_keynav_widget(&bounds, ctx); if (!state) return active; in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; nk_do_toggle(&ctx->last_widget_state, &win->buffer, bounds, &active, @@ -21435,7 +21527,7 @@ win = ctx->current; layout = win->layout; style = &ctx->style; - s = nk_widget(&bounds, ctx); + s = nk_keynav_widget(&bounds, ctx); if (!s) return; /* calculate hash from name */ @@ -21485,6 +21577,7 @@ ctx->input.mouse.grab = nk_true; ctx->input.mouse.grabbed = nk_true; } + ctx->input.selected_widget = ctx->input.widget_counter; } /* check if previously active property is now inactive */ if (*state == NK_PROPERTY_DEFAULT && old_state != NK_PROPERTY_DEFAULT) { @@ -22695,8 +22788,11 @@ body.y = (panel->at_y + panel->footer_height + panel->border + padding.y + panel->row.height); body.h = (panel->bounds.y + panel->bounds.h) - body.y; } + int selected = ctx->input.selected_widget; + ctx->input.selected_widget = -1; {int pressed = nk_input_is_mouse_pressed(&ctx->input, NK_BUTTON_LEFT); int in_body = nk_input_is_mouse_hovering_rect(&ctx->input, body); + ctx->input.selected_widget = selected; if (pressed && in_body) popup->flags |= NK_WINDOW_HIDDEN; } @@ -22769,7 +22865,7 @@ win = ctx->current; style = &ctx->style; - s = nk_widget(&header, ctx); + s = nk_keynav_widget(&header, ctx); if (s == NK_WIDGET_INVALID) return 0; @@ -23330,11 +23426,21 @@ size.y = NK_MIN(size.y, (float)max_height); if (nk_combo_begin_label(ctx, items[selected], size)) { nk_layout_row_dynamic(ctx, (float)item_height, 1); + int main_item_widget = ctx->input.widget_counter; for (i = 0; i < count; ++i) { - if (nk_combo_item_label(ctx, items[i], NK_TEXT_LEFT)) + if (nk_combo_item_label(ctx, items[i], NK_TEXT_LEFT)) { selected = i; + ctx->input.selected_widget = main_item_widget; + //prevent below code from advancing selected widget + main_item_widget--; + } } nk_combo_end(ctx); + if (ctx->input.selected_widget <= main_item_widget) { + ctx->input.selected_widget = main_item_widget + 1; + } else if (ctx->input.selected_widget > main_item_widget + count) { + ctx->input.selected_widget = main_item_widget + count; + } } return selected; } diff -r 13abdc98379e -r 374a5ae694e8 nuklear_ui/nuklear_sdl_gles2.h --- a/nuklear_ui/nuklear_sdl_gles2.h Thu Apr 18 22:06:47 2019 -0700 +++ b/nuklear_ui/nuklear_sdl_gles2.h Sat Apr 18 11:42:53 2020 -0700 @@ -414,6 +414,30 @@ else nk_input_key(ctx, NK_KEY_RIGHT, down); } else return 0; return 1; + } else if (evt->type == SDL_CONTROLLERBUTTONDOWN || evt->type == SDL_CONTROLLERBUTTONUP) { + int down = evt->type == SDL_CONTROLLERBUTTONDOWN; + if (evt->cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP) { + nk_input_key(ctx, NK_KEY_UP, down); + } else if (evt->cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) { + nk_input_key(ctx, NK_KEY_DOWN, down); + } else if (evt->cbutton.button == SDL_CONTROLLER_BUTTON_A || evt->cbutton.button == SDL_CONTROLLER_BUTTON_START) { + nk_input_key(ctx, NK_KEY_ENTER, down); + } + } else if (evt->type == SDL_CONTROLLERAXISMOTION) { + if (evt->caxis.axis == SDL_CONTROLLER_AXIS_LEFTY || evt->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY) { + int down = abs(evt->caxis.value) > 2000; + if (evt->caxis.value >= 0) { + if (ctx->input.keyboard.keys[NK_KEY_UP].down) { + nk_input_key(ctx, NK_KEY_UP, 0); + } + nk_input_key(ctx, NK_KEY_DOWN, down); + } else { + if (ctx->input.keyboard.keys[NK_KEY_DOWN].down) { + nk_input_key(ctx, NK_KEY_DOWN, 0); + } + nk_input_key(ctx, NK_KEY_UP, down); + } + } } else if (evt->type == SDL_MOUSEBUTTONDOWN || evt->type == SDL_MOUSEBUTTONUP) { /* mouse button */ int down = evt->type == SDL_MOUSEBUTTONDOWN; diff -r 13abdc98379e -r 374a5ae694e8 paths.c --- a/paths.c Thu Apr 18 22:06:47 2019 -0700 +++ b/paths.c Sat Apr 18 11:42:53 2020 -0700 @@ -59,7 +59,7 @@ void get_initial_browse_path(char **dst) { - *dst = NULL; + char *base = NULL; char *remember_path = tern_find_path(config, "ui\0remember_path\0", TVAL_PTR).ptrval; if (!remember_path || !strcmp("on", remember_path)) { char *pathfname = alloc_concat(get_userdata_dir(), PATH_SEP "blastem" PATH_SEP "sticky_path"); @@ -67,13 +67,13 @@ if (f) { long pathsize = file_size(f); if (pathsize > 0) { - *dst = malloc(pathsize + 1); - if (fread(*dst, 1, pathsize, f) != pathsize) { + base = malloc(pathsize + 1); + if (fread(base, 1, pathsize, f) != pathsize) { warning("Error restoring saved file browser path"); - free(*dst); - *dst = NULL; + free(base); + base = NULL; } else { - (*dst)[pathsize] = 0; + base[pathsize] = 0; } } fclose(f); @@ -84,19 +84,20 @@ current_path = dst; } } - if (!*dst) { - *dst = tern_find_path(config, "ui\0initial_path\0", TVAL_PTR).ptrval; + if (!base) { + base = tern_find_path(config, "ui\0initial_path\0", TVAL_PTR).ptrval; } - if (!*dst){ + if (!base){ #ifdef __ANDROID__ - *dst = get_external_storage_path(); + base = get_external_storage_path(); #else - *dst = "$HOME"; + base = "$HOME"; #endif } tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir()); vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir()); - *dst = replace_vars(*dst, vars, 1); + *dst = replace_vars(base, vars, 1); + free(base); tern_free(vars); } diff -r 13abdc98379e -r 374a5ae694e8 psg.c --- a/psg.c Thu Apr 18 22:06:47 2019 -0700 +++ b/psg.c Sat Apr 18 11:42:53 2020 -0700 @@ -4,7 +4,6 @@ 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 "psg.h" -#include "render.h" #include "blastem.h" #include #include @@ -33,6 +32,9 @@ void psg_write(psg_context * context, uint8_t value) { + if (context->vgm) { + vgm_sn76489_write(context->vgm, context->cycles, value); + } if (value & 0x80) { context->latch = value & 0x70; uint8_t channel = value >> 5 & 0x3; @@ -123,6 +125,30 @@ } } +void psg_vgm_log(psg_context *context, uint32_t master_clock, vgm_writer *vgm) +{ + vgm_sn76489_init(vgm, 16 * master_clock / context->clock_inc, 9, 16, 0); + context->vgm = vgm; + for (int chan = 0; chan < 4; chan++) + { + uint8_t base = chan << 5 | 0x80; + vgm_sn76489_write(context->vgm, context->cycles, context->volume[chan] | base | 0x10); + if (chan == 3) { + if (context->noise_use_tone) { + vgm_sn76489_write(context->vgm, context->cycles, 3 | base); + } else { + //0x10 = 0 + //0x20 = 1 + //0x40 = 2 + vgm_sn76489_write(context->vgm, context->cycles, context->counter_load[chan] >> 5 | base); + } + } else { + vgm_sn76489_write(context->vgm, context->cycles, (context->counter_load[chan] & 0xF) | base); + vgm_sn76489_write(context->vgm, context->cycles, context->counter_load[chan] >> 4 & 0x3F); + } + } +} + void psg_serialize(psg_context *context, serialize_buffer *buf) { save_int16(buf, context->lsfr); diff -r 13abdc98379e -r 374a5ae694e8 psg.h --- a/psg.h Thu Apr 18 22:06:47 2019 -0700 +++ b/psg.h Sat Apr 18 11:42:53 2020 -0700 @@ -8,10 +8,12 @@ #include #include "serialize.h" -#include "render.h" +#include "render_audio.h" +#include "vgm.h" typedef struct { audio_source *audio; + vgm_writer *vgm; uint32_t clock_inc; uint32_t cycles; uint16_t lsfr; @@ -31,6 +33,7 @@ void psg_adjust_master_clock(psg_context * context, uint32_t master_clock); void psg_write(psg_context * context, uint8_t value); void psg_run(psg_context * context, uint32_t cycles); +void psg_vgm_log(psg_context *context, uint32_t master_clock, vgm_writer *vgm); void psg_serialize(psg_context *context, serialize_buffer *buf); void psg_deserialize(deserialize_buffer *buf, void *vcontext); diff -r 13abdc98379e -r 374a5ae694e8 render.h --- a/render.h Thu Apr 18 22:06:47 2019 -0700 +++ b/render.h Sat Apr 18 11:42:53 2020 -0700 @@ -92,7 +92,6 @@ #define RENDER_NOT_MAPPED -2 #define RENDER_NOT_PLUGGED_IN -3 -typedef struct audio_source audio_source; typedef void (*drop_handler)(const char *filename); typedef void (*window_close_handler)(uint8_t which); typedef void (*ui_render_fun)(void); @@ -109,9 +108,7 @@ void render_set_video_standard(vid_std std); void render_toggle_fullscreen(); void render_update_caption(char *title); -void render_wait_quit(vdp_context * context); -uint32_t render_audio_buffer(); -uint32_t render_sample_rate(); +void render_wait_quit(void); void process_events(); int render_width(); int render_height(); @@ -129,18 +126,11 @@ uint32_t render_emulated_width(); uint32_t render_emulated_height(); uint32_t render_overscan_top(); +uint32_t render_overscan_bot(); uint32_t render_overscan_left(); uint32_t render_elapsed_ms(void); void render_sleep_ms(uint32_t delay); uint8_t render_has_gl(void); -audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels); -void render_audio_source_gaindb(audio_source *src, float gain); -void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider); -void render_put_mono_sample(audio_source *src, int16_t value); -void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right); -void render_pause_source(audio_source *src); -void render_resume_source(audio_source *src); -void render_free_source(audio_source *src); void render_config_updated(void); void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create); void render_set_ui_render_fun(ui_render_fun); diff -r 13abdc98379e -r 374a5ae694e8 render_audio.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/render_audio.c Sat Apr 18 11:42:53 2020 -0700 @@ -0,0 +1,385 @@ +#include +#include +#include +#include +#include "render_audio.h" +#include "util.h" +#include "config.h" +#include "blastem.h" + +static uint8_t output_channels; +static uint32_t buffer_samples, sample_rate; + +static audio_source *audio_sources[8]; +static audio_source *inactive_audio_sources[8]; +static uint8_t num_audio_sources; +static uint8_t num_inactive_audio_sources; + +static float overall_gain_mult, *mix_buf; +static int sample_size; + +typedef void (*conv_func)(float *samples, void *vstream, int sample_count); + +static void convert_null(float *samples, void *vstream, int sample_count) +{ + memset(vstream, 0, sample_count * sample_size); +} + +static void convert_s16(float *samples, void *vstream, int sample_count) +{ + int16_t *stream = vstream; + for (int16_t *end = stream + sample_count; stream < end; stream++, samples++) + { + float sample = *samples; + int16_t out_sample; + if (sample >= 1.0f) { + out_sample = 0x7FFF; + } else if (sample <= -1.0f) { + out_sample = -0x8000; + } else { + out_sample = sample * 0x7FFF; + } + *stream = out_sample; + } +} + +static void clamp_f32(float *samples, void *vstream, int sample_count) +{ + for (; sample_count > 0; sample_count--, samples++) + { + float sample = *samples; + if (sample > 1.0f) { + sample = 1.0f; + } else if (sample < -1.0f) { + sample = -1.0f; + } + *samples = sample; + } +} + +static int32_t mix_f32(audio_source *audio, float *stream, int samples) +{ + float *end = stream + samples; + int16_t *src = audio->front; + uint32_t i = audio->read_start; + uint32_t i_end = audio->read_end; + float *cur = stream; + float gain_mult = audio->gain_mult * overall_gain_mult; + size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; + if (audio->num_channels == 1) { + while (cur < end && i != i_end) + { + *cur += gain_mult * ((float)src[i]) / 0x7FFF; + cur += first_add; + *cur += gain_mult * ((float)src[i++]) / 0x7FFF; + cur += second_add; + i &= audio->mask; + } + } else { + while(cur < end && i != i_end) + { + *cur += gain_mult * ((float)src[i++]) / 0x7FFF; + cur += first_add; + *cur += gain_mult * ((float)src[i++]) / 0x7FFF; + cur += second_add; + i &= audio->mask; + } + } + if (!render_is_audio_sync()) { + audio->read_start = i; + } + if (cur != end) { + debug_message("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); + return (cur-end)/2; + } else { + return ((i_end - i) & audio->mask) / audio->num_channels; + } +} + +static conv_func convert; + + +int mix_and_convert(unsigned char *byte_stream, int len, int *min_remaining_out) +{ + int samples = len / sample_size; + float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream; + memset(mix_dest, 0, samples * sizeof(float)); + int min_buffered = INT_MAX; + int min_remaining_buffer = INT_MAX; + for (uint8_t i = 0; i < num_audio_sources; i++) + { + int buffered = mix_f32(audio_sources[i], mix_dest, samples); + int remaining = (audio_sources[i]->mask + 1) / audio_sources[i]->num_channels - buffered; + min_buffered = buffered < min_buffered ? buffered : min_buffered; + min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer; + audio_sources[i]->front_populated = 0; + render_buffer_consumed(audio_sources[i]); + } + convert(mix_dest, byte_stream, samples); + if (min_remaining_out) { + *min_remaining_out = min_remaining_buffer; + } + return min_buffered; +} + +uint8_t all_sources_ready(void) +{ + uint8_t num_populated = 0; + num_populated = 0; + for (uint8_t i = 0; i < num_audio_sources; i++) + { + if (audio_sources[i]->front_populated) { + num_populated++; + } + } + return num_populated == num_audio_sources; +} + +#define BUFFER_INC_RES 0x40000000UL + +void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider) +{ + src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider; +} + +void render_audio_adjust_speed(float adjust_ratio) +{ + for (uint8_t i = 0; i < num_audio_sources; i++) + { + audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5; + } +} + +audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels) +{ + audio_source *ret = NULL; + uint32_t alloc_size = render_is_audio_sync() ? channels * buffer_samples : nearest_pow2(render_min_buffered() * 4 * channels); + render_lock_audio(); + if (num_audio_sources < 8) { + ret = calloc(1, sizeof(audio_source)); + ret->back = malloc(alloc_size * sizeof(int16_t)); + ret->front = render_is_audio_sync() ? malloc(alloc_size * sizeof(int16_t)) : ret->back; + ret->front_populated = 0; + ret->opaque = render_new_audio_opaque(); + ret->num_channels = channels; + audio_sources[num_audio_sources++] = ret; + } + render_unlock_audio(); + if (!ret) { + fatal_error("Too many audio sources!"); + } else { + render_audio_adjust_clock(ret, master_clock, sample_divider); + double lowpass_cutoff = get_lowpass_cutoff(config); + double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); + ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider)); + double alpha = ret->dt / (ret->dt + rc); + ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); + ret->buffer_pos = 0; + ret->buffer_fraction = 0; + ret->last_left = ret->last_right = 0; + ret->read_start = 0; + ret->read_end = render_is_audio_sync() ? buffer_samples * channels : 0; + ret->mask = render_is_audio_sync() ? 0xFFFFFFFF : alloc_size-1; + ret->gain_mult = 1.0f; + } + render_audio_created(ret); + + return ret; +} + + +static float db_to_mult(float gain) +{ + return powf(10.0f, gain/20.0f); +} + +void render_audio_source_gaindb(audio_source *src, float gain) +{ + src->gain_mult = db_to_mult(gain); +} + +void render_pause_source(audio_source *src) +{ + uint8_t found = 0, remaining_sources; + render_lock_audio(); + for (uint8_t i = 0; i < num_audio_sources; i++) + { + if (audio_sources[i] == src) { + audio_sources[i] = audio_sources[--num_audio_sources]; + found = 1; + remaining_sources = num_audio_sources; + break; + } + } + + render_unlock_audio(); + if (found) { + render_source_paused(src, remaining_sources); + } + inactive_audio_sources[num_inactive_audio_sources++] = src; +} + +void render_resume_source(audio_source *src) +{ + render_lock_audio(); + if (num_audio_sources < 8) { + audio_sources[num_audio_sources++] = src; + } + render_unlock_audio(); + for (uint8_t i = 0; i < num_inactive_audio_sources; i++) + { + if (inactive_audio_sources[i] == src) { + inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources]; + } + } + render_source_resumed(src); +} + +void render_free_source(audio_source *src) +{ + uint8_t found = 0; + for (uint8_t i = 0; i < num_inactive_audio_sources; i++) + { + if (inactive_audio_sources[i] == src) { + inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources]; + found = 1; + break; + } + } + if (!found) { + render_pause_source(src); + num_inactive_audio_sources--; + } + + free(src->front); + if (render_is_audio_sync()) { + free(src->back); + render_free_audio_opaque(src->opaque); + } + free(src); +} + +static int16_t lowpass_sample(audio_source *src, int16_t last, int16_t current) +{ + int32_t tmp = current * src->lowpass_alpha + last * (0x10000 - src->lowpass_alpha); + current = tmp >> 16; + return current; +} + +static void interp_sample(audio_source *src, int16_t last, int16_t current) +{ + int64_t tmp = last * ((src->buffer_fraction << 16) / src->buffer_inc); + tmp += current * (0x10000 - ((src->buffer_fraction << 16) / src->buffer_inc)); + src->back[src->buffer_pos++] = tmp >> 16; +} + +static uint32_t sync_samples; +void render_put_mono_sample(audio_source *src, int16_t value) +{ + value = lowpass_sample(src, src->last_left, value); + src->buffer_fraction += src->buffer_inc; + uint32_t base = render_is_audio_sync() ? 0 : src->read_end; + while (src->buffer_fraction > BUFFER_INC_RES) + { + src->buffer_fraction -= BUFFER_INC_RES; + interp_sample(src, src->last_left, value); + + if (((src->buffer_pos - base) & src->mask) >= sync_samples) { + render_do_audio_ready(src); + } + src->buffer_pos &= src->mask; + } + src->last_left = value; +} + +void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right) +{ + left = lowpass_sample(src, src->last_left, left); + right = lowpass_sample(src, src->last_right, right); + src->buffer_fraction += src->buffer_inc; + uint32_t base = render_is_audio_sync() ? 0 : src->read_end; + while (src->buffer_fraction > BUFFER_INC_RES) + { + src->buffer_fraction -= BUFFER_INC_RES; + + interp_sample(src, src->last_left, left); + interp_sample(src, src->last_right, right); + + if (((src->buffer_pos - base) & src->mask)/2 >= sync_samples) { + render_do_audio_ready(src); + } + src->buffer_pos &= src->mask; + } + src->last_left = left; + src->last_right = right; +} + +static void update_source(audio_source *src, double rc, uint8_t sync_changed) +{ + double alpha = src->dt / (src->dt + rc); + int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha); + src->lowpass_alpha = lowpass_alpha; + if (sync_changed) { + uint32_t alloc_size = render_is_audio_sync() ? src->num_channels * buffer_samples : nearest_pow2(render_min_buffered() * 4 * src->num_channels); + src->back = realloc(src->back, alloc_size * sizeof(int16_t)); + if (render_is_audio_sync()) { + src->front = malloc(alloc_size * sizeof(int16_t)); + } else { + free(src->front); + src->front = src->back; + } + src->mask = render_is_audio_sync() ? 0xFFFFFFFF : alloc_size-1; + src->read_start = 0; + src->read_end = render_is_audio_sync() ? buffer_samples * src->num_channels : 0; + src->buffer_pos = 0; + } +} + +uint8_t old_audio_sync; +void render_audio_initialized(render_audio_format format, uint32_t rate, uint8_t channels, uint32_t buffer_size, int sample_size_in) +{ + sample_rate = rate; + output_channels = channels; + buffer_samples = buffer_size; + sample_size = sample_size_in; + if (mix_buf) { + free(mix_buf); + mix_buf = NULL; + } + switch(format) + { + case RENDER_AUDIO_S16: + convert = convert_s16; + mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); + break; + case RENDER_AUDIO_FLOAT: + convert = clamp_f32; + break; + case RENDER_AUDIO_UNKNOWN: + convert = convert_null; + mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); + break; + } + uint32_t syncs = render_audio_syncs_per_sec(); + if (syncs) { + sync_samples = rate / syncs; + } else { + sync_samples = buffer_samples; + } + char * gain_str = tern_find_path(config, "audio\0gain\0", TVAL_PTR).ptrval; + overall_gain_mult = db_to_mult(gain_str ? atof(gain_str) : 0.0f); + uint8_t sync_changed = old_audio_sync != render_is_audio_sync(); + old_audio_sync = render_is_audio_sync(); + double lowpass_cutoff = get_lowpass_cutoff(config); + double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); + render_lock_audio(); + for (uint8_t i = 0; i < num_audio_sources; i++) + { + update_source(audio_sources[i], rc, sync_changed); + } + render_unlock_audio(); + for (uint8_t i = 0; i < num_inactive_audio_sources; i++) + { + update_source(inactive_audio_sources[i], rc, sync_changed); + } +} \ No newline at end of file diff -r 13abdc98379e -r 374a5ae694e8 render_audio.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/render_audio.h Sat Apr 18 11:42:53 2020 -0700 @@ -0,0 +1,57 @@ +#ifndef RENDER_AUDIO_H_ +#define RENDER_AUDIO_H_ + +#include +typedef enum { + RENDER_AUDIO_S16, + RENDER_AUDIO_FLOAT, + RENDER_AUDIO_UNKNOWN +} render_audio_format; + +typedef struct { + void *opaque; + int16_t *front; + int16_t *back; + double dt; + uint64_t buffer_fraction; + uint64_t buffer_inc; + float gain_mult; + uint32_t buffer_pos; + uint32_t read_start; + uint32_t read_end; + uint32_t lowpass_alpha; + uint32_t mask; + int16_t last_left; + int16_t last_right; + uint8_t num_channels; + uint8_t front_populated; +} audio_source; + +//public interface +audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels); +void render_audio_source_gaindb(audio_source *src, float gain); +void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider); +void render_put_mono_sample(audio_source *src, int16_t value); +void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right); +void render_pause_source(audio_source *src); +void render_resume_source(audio_source *src); +void render_free_source(audio_source *src); +//interface for render backends +void render_audio_initialized(render_audio_format format, uint32_t rate, uint8_t channels, uint32_t buffer_size, int sample_size); +int mix_and_convert(unsigned char *byte_stream, int len, int *min_remaining_out); +uint8_t all_sources_ready(void); +void render_audio_adjust_speed(float adjust_ratio); +//to be implemented by render backend +uint8_t render_is_audio_sync(void); +void render_buffer_consumed(audio_source *src); +void *render_new_audio_opaque(void); +void render_free_audio_opaque(void *opaque); +void render_lock_audio(void); +void render_unlock_audio(void); +uint32_t render_min_buffered(void); +uint32_t render_audio_syncs_per_sec(void); +void render_audio_created(audio_source *src); +void render_do_audio_ready(audio_source *src); +void render_source_paused(audio_source *src, uint8_t remaining_sources); +void render_source_resumed(audio_source *src); +#endif //RENDER_AUDIO_H_ diff -r 13abdc98379e -r 374a5ae694e8 render_sdl.c --- a/render_sdl.c Thu Apr 18 22:06:47 2019 -0700 +++ b/render_sdl.c Sat Apr 18 11:42:53 2020 -0700 @@ -46,151 +46,41 @@ static uint32_t last_frame = 0; -static uint8_t output_channels; -static uint32_t buffer_samples, sample_rate; -static uint32_t missing_count; - static SDL_mutex * audio_mutex; static SDL_cond * audio_ready; static uint8_t quitting = 0; -struct audio_source { - SDL_cond *cond; - int16_t *front; - int16_t *back; - double dt; - uint64_t buffer_fraction; - uint64_t buffer_inc; - float gain_mult; - uint32_t buffer_pos; - uint32_t read_start; - uint32_t read_end; - uint32_t lowpass_alpha; - uint32_t mask; - int16_t last_left; - int16_t last_right; - uint8_t num_channels; - uint8_t front_populated; -}; - -static audio_source *audio_sources[8]; -static audio_source *inactive_audio_sources[8]; -static uint8_t num_audio_sources; -static uint8_t num_inactive_audio_sources; static uint8_t sync_to_audio; static uint32_t min_buffered; -static float overall_gain_mult, *mix_buf; -static int sample_size; -typedef void (*conv_func)(float *samples, void *vstream, int sample_count); - -static void convert_null(float *samples, void *vstream, int sample_count) -{ - memset(vstream, 0, sample_count * sample_size); -} - -static void convert_s16(float *samples, void *vstream, int sample_count) +uint32_t render_min_buffered(void) { - int16_t *stream = vstream; - for (int16_t *end = stream + sample_count; stream < end; stream++, samples++) - { - float sample = *samples; - int16_t out_sample; - if (sample >= 1.0f) { - out_sample = 0x7FFF; - } else if (sample <= -1.0f) { - out_sample = -0x8000; - } else { - out_sample = sample * 0x7FFF; - } - *stream = out_sample; - } + return min_buffered; } -static void clamp_f32(float *samples, void *vstream, int sample_count) +uint8_t render_is_audio_sync(void) { - for (; sample_count > 0; sample_count--, samples++) - { - float sample = *samples; - if (sample > 1.0f) { - sample = 1.0f; - } else if (sample < -1.0f) { - sample = -1.0f; - } - *samples = sample; - } + return sync_to_audio; } -static int32_t mix_f32(audio_source *audio, float *stream, int samples) +void render_buffer_consumed(audio_source *src) { - float *end = stream + samples; - int16_t *src = audio->front; - uint32_t i = audio->read_start; - uint32_t i_end = audio->read_end; - float *cur = stream; - float gain_mult = audio->gain_mult * overall_gain_mult; - size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; - if (audio->num_channels == 1) { - while (cur < end && i != i_end) - { - *cur += gain_mult * ((float)src[i]) / 0x7FFF; - cur += first_add; - *cur += gain_mult * ((float)src[i++]) / 0x7FFF; - cur += second_add; - i &= audio->mask; - } - } else { - while(cur < end && i != i_end) - { - *cur += gain_mult * ((float)src[i++]) / 0x7FFF; - cur += first_add; - *cur += gain_mult * ((float)src[i++]) / 0x7FFF; - cur += second_add; - i &= audio->mask; - } - } - if (!sync_to_audio) { - audio->read_start = i; - } - if (cur != end) { - debug_message("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); - return (cur-end)/2; - } else { - return ((i_end - i) & audio->mask) / audio->num_channels; - } + SDL_CondSignal(src->opaque); } -static conv_func convert; - static void audio_callback(void * userdata, uint8_t *byte_stream, int len) { - uint8_t num_populated; SDL_LockMutex(audio_mutex); + uint8_t all_ready; do { - num_populated = 0; - for (uint8_t i = 0; i < num_audio_sources; i++) - { - if (audio_sources[i]->front_populated) { - num_populated++; - } - } - if (!quitting && num_populated < num_audio_sources) { - fflush(stdout); + all_ready = all_sources_ready(); + if (!quitting && !all_ready) { SDL_CondWait(audio_ready, audio_mutex); } - } while(!quitting && num_populated < num_audio_sources); - int samples = len / sample_size; - float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream; - memset(mix_dest, 0, samples * sizeof(float)); + } while(!quitting && !all_ready); if (!quitting) { - for (uint8_t i = 0; i < num_audio_sources; i++) - { - mix_f32(audio_sources[i], mix_dest, samples); - audio_sources[i]->front_populated = 0; - SDL_CondSignal(audio_sources[i]->cond); - } + mix_and_convert(byte_stream, len, NULL); } - convert(mix_dest, byte_stream, samples); SDL_UnlockMutex(audio_mutex); } @@ -208,23 +98,10 @@ //underflow last frame, but main thread hasn't gotten a chance to call SDL_PauseAudio yet return; } - cur_min_buffered = 0x7FFFFFFF; - min_remaining_buffer = 0xFFFFFFFF; - float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream; - int samples = len / sample_size; - memset(mix_dest, 0, samples * sizeof(float)); - for (uint8_t i = 0; i < num_audio_sources; i++) - { - - int32_t buffered = mix_f32(audio_sources[i], mix_dest, samples); - cur_min_buffered = buffered < cur_min_buffered ? buffered : cur_min_buffered; - uint32_t remaining = (audio_sources[i]->mask + 1)/audio_sources[i]->num_channels - buffered; - min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer; - } - convert(mix_dest, byte_stream, samples); + cur_min_buffered = mix_and_convert(byte_stream, len, &min_remaining_buffer); } -static void lock_audio() +void render_lock_audio() { if (sync_to_audio) { SDL_LockMutex(audio_mutex); @@ -233,7 +110,7 @@ } } -static void unlock_audio() +void render_unlock_audio() { if (sync_to_audio) { SDL_UnlockMutex(audio_mutex); @@ -249,127 +126,55 @@ SDL_CondSignal(audio_ready); SDL_UnlockMutex(audio_mutex); SDL_CloseAudio(); + /* + FIXME: move this to render_audio.c if (mix_buf) { free(mix_buf); mix_buf = NULL; } + */ } -#define BUFFER_INC_RES 0x40000000UL - -void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider) +void *render_new_audio_opaque(void) { - src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider; + return SDL_CreateCond(); } -audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels) +void render_free_audio_opaque(void *opaque) { - audio_source *ret = NULL; - uint32_t alloc_size = sync_to_audio ? channels * buffer_samples : nearest_pow2(min_buffered * 4 * channels); - lock_audio(); - if (num_audio_sources < 8) { - ret = malloc(sizeof(audio_source)); - ret->back = malloc(alloc_size * sizeof(int16_t)); - ret->front = sync_to_audio ? malloc(alloc_size * sizeof(int16_t)) : ret->back; - ret->front_populated = 0; - ret->cond = SDL_CreateCond(); - ret->num_channels = channels; - audio_sources[num_audio_sources++] = ret; - } - unlock_audio(); - if (!ret) { - fatal_error("Too many audio sources!"); - } else { - render_audio_adjust_clock(ret, master_clock, sample_divider); - double lowpass_cutoff = get_lowpass_cutoff(config); - double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); - ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider)); - double alpha = ret->dt / (ret->dt + rc); - ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); - ret->buffer_pos = 0; - ret->buffer_fraction = 0; - ret->last_left = ret->last_right = 0; - ret->read_start = 0; - ret->read_end = sync_to_audio ? buffer_samples * channels : 0; - ret->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1; - ret->gain_mult = 1.0f; - } + SDL_DestroyCond(opaque); +} + +void render_audio_created(audio_source *source) +{ if (sync_to_audio && SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) { SDL_PauseAudio(0); } - return ret; -} - -static float db_to_mult(float gain) -{ - return powf(10.0f, gain/20.0f); -} - -void render_audio_source_gaindb(audio_source *src, float gain) -{ - src->gain_mult = db_to_mult(gain); } -void render_pause_source(audio_source *src) +void render_source_paused(audio_source *src, uint8_t remaining_sources) { - uint8_t need_pause = 0; - lock_audio(); - for (uint8_t i = 0; i < num_audio_sources; i++) - { - if (audio_sources[i] == src) { - audio_sources[i] = audio_sources[--num_audio_sources]; - if (sync_to_audio) { - SDL_CondSignal(audio_ready); - } - break; - } - } - if (!num_audio_sources) { - need_pause = 1; - } - unlock_audio(); - if (need_pause) { - SDL_PauseAudio(1); + if (sync_to_audio) { + SDL_CondSignal(audio_ready); } - inactive_audio_sources[num_inactive_audio_sources++] = src; + if (!remaining_sources) { + SDL_PauseAudio(0); + } } -void render_resume_source(audio_source *src) +void render_source_resumed(audio_source *src) { - lock_audio(); - if (num_audio_sources < 8) { - audio_sources[num_audio_sources++] = src; - } - unlock_audio(); - for (uint8_t i = 0; i < num_inactive_audio_sources; i++) - { - if (inactive_audio_sources[i] == src) { - inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources]; - } - } if (sync_to_audio) { SDL_PauseAudio(0); } } -void render_free_source(audio_source *src) -{ - render_pause_source(src); - - free(src->front); - if (sync_to_audio) { - free(src->back); - SDL_DestroyCond(src->cond); - } - free(src); -} -static uint32_t sync_samples; -static void do_audio_ready(audio_source *src) +void render_do_audio_ready(audio_source *src) { if (sync_to_audio) { SDL_LockMutex(audio_mutex); while (src->front_populated) { - SDL_CondWait(src->cond, audio_mutex); + SDL_CondWait(src->opaque, audio_mutex); } int16_t *tmp = src->front; src->front = src->back; @@ -390,62 +195,9 @@ } } -static int16_t lowpass_sample(audio_source *src, int16_t last, int16_t current) -{ - int32_t tmp = current * src->lowpass_alpha + last * (0x10000 - src->lowpass_alpha); - current = tmp >> 16; - return current; -} - -static void interp_sample(audio_source *src, int16_t last, int16_t current) -{ - int64_t tmp = last * ((src->buffer_fraction << 16) / src->buffer_inc); - tmp += current * (0x10000 - ((src->buffer_fraction << 16) / src->buffer_inc)); - src->back[src->buffer_pos++] = tmp >> 16; -} - -void render_put_mono_sample(audio_source *src, int16_t value) -{ - value = lowpass_sample(src, src->last_left, value); - src->buffer_fraction += src->buffer_inc; - uint32_t base = sync_to_audio ? 0 : src->read_end; - while (src->buffer_fraction > BUFFER_INC_RES) - { - src->buffer_fraction -= BUFFER_INC_RES; - interp_sample(src, src->last_left, value); - - if (((src->buffer_pos - base) & src->mask) >= sync_samples) { - do_audio_ready(src); - } - src->buffer_pos &= src->mask; - } - src->last_left = value; -} - -void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right) -{ - left = lowpass_sample(src, src->last_left, left); - right = lowpass_sample(src, src->last_right, right); - src->buffer_fraction += src->buffer_inc; - uint32_t base = sync_to_audio ? 0 : src->read_end; - while (src->buffer_fraction > BUFFER_INC_RES) - { - src->buffer_fraction -= BUFFER_INC_RES; - - interp_sample(src, src->last_left, left); - interp_sample(src, src->last_right, right); - - if (((src->buffer_pos - base) & src->mask)/2 >= sync_samples) { - do_audio_ready(src); - } - src->buffer_pos &= src->mask; - } - src->last_left = left; - src->last_right = right; -} - static SDL_Joystick * joysticks[MAX_JOYSTICKS]; static int joystick_sdl_index[MAX_JOYSTICKS]; +static uint8_t joystick_index_locked[MAX_JOYSTICKS]; int render_width() { @@ -635,12 +387,11 @@ if (texture_init) { return; } - sdl_textures= malloc(sizeof(SDL_Texture *) * 2); - num_textures = 2; + sdl_textures= calloc(sizeof(SDL_Texture *), 3); + num_textures = 3; texture_init = 1; #ifndef DISABLE_OPENGL if (render_gl) { - sdl_textures[0] = sdl_textures[1] = NULL; gl_setup(); } else { #endif @@ -889,6 +640,16 @@ return -1; } +static int lowest_unlocked_joystick_index(void) +{ + for (int i = 0; i < MAX_JOYSTICKS; i++) { + if (!joystick_index_locked[i]) { + return i; + } + } + return -1; +} + SDL_Joystick *render_get_joystick(int index) { if (index >= MAX_JOYSTICKS) { @@ -910,12 +671,29 @@ SDL_GameController *render_get_controller(int index) { - if (index >= MAX_JOYSTICKS) { + if (index >= MAX_JOYSTICKS || !joysticks[index]) { return NULL; } return SDL_GameControllerOpen(joystick_sdl_index[index]); } +static uint8_t gc_events_enabled; +static SDL_GameController *controllers[MAX_JOYSTICKS]; +void render_enable_gamepad_events(uint8_t enabled) +{ + if (enabled != gc_events_enabled) { + gc_events_enabled = enabled; + for (int i = 0; i < MAX_JOYSTICKS; i++) { + if (enabled) { + controllers[i] = render_get_controller(i); + } else if (controllers[i]) { + SDL_GameControllerClose(controllers[i]); + controllers[i] = NULL; + } + } + } +} + static uint32_t overscan_top[NUM_VID_STD] = {2, 21}; static uint32_t overscan_bot[NUM_VID_STD] = {1, 17}; static uint32_t overscan_left[NUM_VID_STD] = {13, 13}; @@ -923,6 +701,30 @@ static vid_std video_standard = VID_NTSC; static uint8_t need_ui_fb_resize; +int lock_joystick_index(int joystick, int desired_index) +{ + if (desired_index < 0) { + desired_index = lowest_unlocked_joystick_index(); + if (desired_index < 0 || desired_index >= joystick) { + return joystick; + } + } + SDL_Joystick *tmp_joy = joysticks[joystick]; + int tmp_index = joystick_sdl_index[joystick]; + joysticks[joystick] = joysticks[desired_index]; + joystick_sdl_index[joystick] = joystick_sdl_index[desired_index]; + joystick_index_locked[joystick] = joystick_sdl_index[desired_index]; + joysticks[desired_index] = tmp_joy; + joystick_sdl_index[desired_index] = tmp_index; + joystick_index_locked[desired_index] = 1; + //update bindings as the controllers being swapped may have different mappings + handle_joy_added(desired_index); + if (joysticks[joystick]) { + handle_joy_added(joystick); + } + return desired_index; +} + static int32_t handle_event(SDL_Event *event) { if (custom_event_handler) { @@ -939,13 +741,13 @@ handle_joydown(find_joystick_index(event->jbutton.which), event->jbutton.button); break; case SDL_JOYBUTTONUP: - handle_joyup(find_joystick_index(event->jbutton.which), event->jbutton.button); + handle_joyup(lock_joystick_index(find_joystick_index(event->jbutton.which), -1), event->jbutton.button); break; case SDL_JOYHATMOTION: - handle_joy_dpad(find_joystick_index(event->jhat.which), event->jhat.hat, event->jhat.value); + handle_joy_dpad(lock_joystick_index(find_joystick_index(event->jhat.which), -1), event->jhat.hat, event->jhat.value); break; case SDL_JOYAXISMOTION: - handle_joy_axis(find_joystick_index(event->jaxis.which), event->jaxis.axis, event->jaxis.value); + handle_joy_axis(lock_joystick_index(find_joystick_index(event->jaxis.which), -1), event->jaxis.axis, event->jaxis.value); break; case SDL_JOYDEVICEADDED: if (event->jdevice.which < MAX_JOYSTICKS) { @@ -953,6 +755,10 @@ if (index >= 0) { SDL_Joystick * joy = joysticks[index] = SDL_JoystickOpen(event->jdevice.which); joystick_sdl_index[index] = event->jdevice.which; + joystick_index_locked[index] = 0; + if (gc_events_enabled) { + controllers[index] = SDL_GameControllerOpen(event->jdevice.which); + } if (joy) { debug_message("Joystick %d added: %s\n", index, SDL_JoystickName(joy)); debug_message("\tNum Axes: %d\n\tNum Buttons: %d\n\tNum Hats: %d\n", SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumHats(joy)); @@ -966,6 +772,10 @@ if (index >= 0) { SDL_JoystickClose(joysticks[index]); joysticks[index] = NULL; + if (controllers[index]) { + SDL_GameControllerClose(controllers[index]); + controllers[index] = NULL; + } debug_message("Joystick %d removed\n", index); } else { debug_message("Failed to find removed joystick with instance ID: %d\n", index); @@ -1053,6 +863,7 @@ static int source_frame_count; static int frame_repeat[60]; +static uint32_t sample_rate; static void init_audio() { SDL_AudioSpec desired, actual; @@ -1077,27 +888,20 @@ if (SDL_OpenAudio(&desired, &actual) < 0) { fatal_error("Unable to open SDL audio: %s\n", SDL_GetError()); } - buffer_samples = actual.samples; sample_rate = actual.freq; - output_channels = actual.channels; debug_message("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples); - sample_size = SDL_AUDIO_BITSIZE(actual.format) / 8; + render_audio_format format = RENDER_AUDIO_UNKNOWN; if (actual.format == AUDIO_S16SYS) { debug_message("signed 16-bit int format\n"); - convert = convert_s16; - mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); + format = RENDER_AUDIO_S16; } else if (actual.format == AUDIO_F32SYS) { debug_message("32-bit float format\n"); - convert = clamp_f32; - mix_buf = NULL; + format = RENDER_AUDIO_FLOAT; } else { debug_message("unsupported format %X\n", actual.format); warning("Unsupported audio sample format: %X\n", actual.format); - convert = convert_null; - mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); } - char * gain_str = tern_find_path(config, "audio\0gain\0", TVAL_PTR).ptrval; - overall_gain_mult = db_to_mult(gain_str ? atof(gain_str) : 0.0f); + render_audio_initialized(format, actual.freq, actual.channels, actual.samples, SDL_AUDIO_BITSIZE(actual.format) / 8); } void window_setup(void) @@ -1292,26 +1096,6 @@ } #include static int in_toggle; -static void update_source(audio_source *src, double rc, uint8_t sync_changed) -{ - double alpha = src->dt / (src->dt + rc); - int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha); - src->lowpass_alpha = lowpass_alpha; - if (sync_changed) { - uint32_t alloc_size = sync_to_audio ? src->num_channels * buffer_samples : nearest_pow2(min_buffered * 4 * src->num_channels); - src->back = realloc(src->back, alloc_size * sizeof(int16_t)); - if (sync_to_audio) { - src->front = malloc(alloc_size * sizeof(int16_t)); - } else { - free(src->front); - src->front = src->back; - } - src->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1; - src->read_start = 0; - src->read_end = sync_to_audio ? buffer_samples * src->num_channels : 0; - src->buffer_pos = 0; - } -} void render_config_updated(void) { @@ -1378,18 +1162,6 @@ init_audio(); render_set_video_standard(video_standard); - double lowpass_cutoff = get_lowpass_cutoff(config); - double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); - lock_audio(); - for (uint8_t i = 0; i < num_audio_sources; i++) - { - update_source(audio_sources[i], rc, old_sync_to_audio != sync_to_audio); - } - unlock_audio(); - for (uint8_t i = 0; i < num_inactive_audio_sources; i++) - { - update_source(inactive_audio_sources[i], rc, old_sync_to_audio != sync_to_audio); - } drain_events(); in_toggle = 0; if (!was_paused) { @@ -1402,6 +1174,12 @@ return main_window; } +uint32_t render_audio_syncs_per_sec(void) +{ + //sync samples with audio thread approximately every 8 lines when doing sync to video + return sync_to_audio ? 0 : source_hz * (video_standard == VID_PAL ? 313 : 262) / 8; +} + void render_set_video_standard(vid_std std) { video_standard = std; @@ -1431,8 +1209,6 @@ } source_frame = 0; source_frame_count = frame_repeat[0]; - //sync samples with audio thread approximately every 8 lines - sync_samples = sync_to_audio ? buffer_samples : 8 * sample_rate / (source_hz * (std == VID_PAL ? 313 : 262)); max_repeat++; min_buffered = (((float)max_repeat * (float)sample_rate/(float)source_hz)/* / (float)buffer_samples*/);// + 0.9999; //min_buffered *= buffer_samples; @@ -1521,13 +1297,6 @@ return texture_buf; } else { #endif - if (which == FRAMEBUFFER_UI && which >= num_textures) { - sdl_textures = realloc(sdl_textures, sizeof(*sdl_textures) * (FRAMEBUFFER_UI + 1)); - for (; num_textures <= FRAMEBUFFER_UI; num_textures++) - { - sdl_textures[num_textures] = NULL; - } - } if (which == FRAMEBUFFER_UI && !sdl_textures[which]) { sdl_textures[which] = SDL_CreateTexture(main_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, main_width, main_height); } @@ -1593,7 +1362,7 @@ #ifndef DISABLE_ZLIB ext = path_extension(screenshot_path); #endif - info_message("Saving screenshot to %s\n", screenshot_path); + debug_message("Saving screenshot to %s\n", screenshot_path); } else { warning("Failed to open screenshot file %s for writing\n", screenshot_path); } @@ -1748,10 +1517,8 @@ } if (adjust_ratio != 0.0f) { average_change = 0; - for (uint8_t i = 0; i < num_audio_sources; i++) - { - audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5; - } + render_audio_adjust_speed(adjust_ratio); + } while (source_frame_count > 0) { @@ -1849,7 +1616,12 @@ return overscan_top[video_standard]; } -void render_wait_quit(vdp_context * context) +uint32_t render_overscan_bot() +{ + return overscan_bot[video_standard]; +} + +void render_wait_quit(void) { SDL_Event event; while(SDL_WaitEvent(&event)) { @@ -2017,16 +1789,6 @@ need_ui_fb_resize = 1; } -uint32_t render_audio_buffer() -{ - return buffer_samples; -} - -uint32_t render_sample_rate() -{ - return sample_rate; -} - void render_errorbox(char *title, char *message) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, message, NULL); diff -r 13abdc98379e -r 374a5ae694e8 render_sdl.h --- a/render_sdl.h Thu Apr 18 22:06:47 2019 -0700 +++ b/render_sdl.h Sat Apr 18 11:42:53 2020 -0700 @@ -11,5 +11,6 @@ SDL_GameController *render_get_controller(int index); int render_lookup_button(char *name); int render_lookup_axis(char *name); +void render_enable_gamepad_events(uint8_t enabled); #endif //RENDER_SDL_H_ diff -r 13abdc98379e -r 374a5ae694e8 romdb.c --- a/romdb.c Thu Apr 18 22:06:47 2019 -0700 +++ b/romdb.c Sat Apr 18 11:42:53 2020 -0700 @@ -77,6 +77,7 @@ switch(gen->mapper_type) { case MAPPER_SEGA: + case MAPPER_SEGA_SRAM: sega_mapper_serialize(gen, buf); break; case MAPPER_REALTEC: @@ -96,13 +97,14 @@ { genesis_context *gen = vcontext; uint8_t mapper_type = load_int8(buf); - if (mapper_type != gen->mapper_type) { - warning("Mapper type mismatch, skipping load of mapper state"); + if (mapper_type != gen->mapper_type && (mapper_type != MAPPER_SEGA || gen->mapper_type != MAPPER_SEGA_SRAM)) { + warning("Mapper type mismatch, skipping load of mapper state\n"); return; } switch(gen->mapper_type) { case MAPPER_SEGA: + case MAPPER_SEGA_SRAM: sega_mapper_deserialize(buf, gen); break; case MAPPER_REALTEC: @@ -242,7 +244,7 @@ save_size /= 2; } info->save_size = save_size; - info->save_buffer = malloc(save_size); + info->save_buffer = calloc(save_size, 1); return ram_start; } @@ -254,6 +256,7 @@ } else if (rom_end > nearest_pow2(size)) { rom_end = nearest_pow2(size); } + info->save_type = SAVE_NONE; if (size >= 0x80000 && !memcmp("SEGA SSF", rom + 0x100, 8)) { info->mapper_start_index = 0; info->mapper_type = MAPPER_SEGA; @@ -310,7 +313,7 @@ info->map[0].read_16 = nor_flash_read_w; info->map[0].read_8 = nor_flash_read_b; info->map[0].flags = MMAP_READ_CODE | MMAP_CODE; - info->map[0].buffer = info->save_buffer = malloc(info->save_size); + info->map[0].buffer = info->save_buffer = calloc(info->save_size, 1); uint32_t init_size = size < info->save_size ? size : info->save_size; memcpy(info->save_buffer, rom, init_size); byteswap_rom(info->save_size, (uint16_t *)info->save_buffer); @@ -365,7 +368,7 @@ info->map[1].buffer = info->save_buffer; } else { //Assume the standard Sega mapper - info->mapper_type = MAPPER_SEGA; + info->mapper_type = MAPPER_SEGA_SRAM; info->map[0].end = 0x200000; info->map[0].mask = 0xFFFFFF; info->map[0].flags = MMAP_READ; @@ -381,8 +384,13 @@ info->map[1].write_16 = (write_16_fun)write_sram_area_w;//these will be called all writes to the area info->map[1].write_8 = (write_8_fun)write_sram_area_b; info->map[1].buffer = rom + 0x200000; - + + //Last entry in the base map is a catch all one that needs to be + //after all the other entries + memmap_chunk *unused = info->map + info->map_chunks - 2; memmap_chunk *last = info->map + info->map_chunks - 1; + *last = *unused; + last = unused; memset(last, 0, sizeof(memmap_chunk)); last->start = 0xA13000; last->end = 0xA13100; @@ -489,8 +497,7 @@ fatal_error("SRAM size %s is invalid\n", size); } state->info->save_mask = nearest_pow2(state->info->save_size)-1; - state->info->save_buffer = malloc(state->info->save_size); - memset(state->info->save_buffer, 0, state->info->save_size); + state->info->save_buffer = calloc(state->info->save_size, 1); char *bus = tern_find_path(state->root, "SRAM\0bus\0", TVAL_PTR).ptrval; if (!strcmp(bus, "odd")) { state->info->save_type = RAM_FLAG_ODD; @@ -567,6 +574,9 @@ if (!strcmp(init, "ROM")) { uint32_t init_size = state->rom_size > state->info->save_size ? state->info->save_size : state->rom_size; memcpy(state->info->save_buffer, state->rom, init_size); + if (init_size < state->info->save_size) { + memset(state->info->save_buffer + init_size, 0xFF, state->info->save_size - init_size); + } if (state->info->save_bus == RAM_FLAG_BOTH) { byteswap_rom(state->info->save_size, (uint16_t *)state->info->save_buffer); } @@ -712,7 +722,7 @@ if (!size || size > map->end - map->start) { size = map->end - map->start; } - map->buffer = malloc(size); + map->buffer = calloc(size, 1); map->mask = calc_mask(size, start, end); map->flags = MMAP_READ | MMAP_WRITE; char *bus = tern_find_ptr_default(node, "bus", "both"); diff -r 13abdc98379e -r 374a5ae694e8 romdb.h --- a/romdb.h Thu Apr 18 22:06:47 2019 -0700 +++ b/romdb.h Sat Apr 18 11:42:53 2020 -0700 @@ -43,6 +43,7 @@ enum { MAPPER_NONE, MAPPER_SEGA, + MAPPER_SEGA_SRAM, MAPPER_REALTEC, MAPPER_XBAND, MAPPER_MULTI_GAME, diff -r 13abdc98379e -r 374a5ae694e8 sega_mapper.c --- a/sega_mapper.c Thu Apr 18 22:06:47 2019 -0700 +++ b/sega_mapper.c Sat Apr 18 11:42:53 2020 -0700 @@ -116,7 +116,7 @@ context->mem_pointers[gen->mapper_start_index + i] = gen->cart + 0x40000*gen->bank_regs[i]; } } - } else { + } else if (gen->mapper_type == MAPPER_SEGA) { void *new_ptr = gen->cart + 0x40000*value; if (context->mem_pointers[gen->mapper_start_index + address] != new_ptr) { context->mem_pointers[gen->mapper_start_index + address] = new_ptr; diff -r 13abdc98379e -r 374a5ae694e8 shaders/sharp.f.glsl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/shaders/sharp.f.glsl Sat Apr 18 11:42:53 2020 -0700 @@ -0,0 +1,31 @@ + +uniform sampler2D textures[2]; + +varying mediump vec2 texcoord; + +void main() +{ + mediump float x0 = (floor(texcoord.x * 512.0 - 0.25) + 0.5)/512.0; + mediump float x1 = (floor(texcoord.x * 512.0 + 0.25) + 0.5)/512.0; + mediump float y0 = (floor(texcoord.y * 512.0 + 0.25) + 0.5)/512.0; + mediump float y1 = (floor(texcoord.y * 512.0 - 0.25) + 0.5)/512.0; + + + mediump vec2 modifiedCoord0 = vec2(texcoord.x, (floor(texcoord.y * 512.0 + 0.25) + 0.5)/512.0); + mediump vec2 modifiedCoord1 = vec2(texcoord.x, (floor(texcoord.y * 512.0 - 0.25) + 0.5)/512.0); + mediump float ymix = (sin(texcoord.y * 1024.0 * 3.14159265359) + 1.0) * 0.5; + mediump float xmix = (sin(texcoord.x * 1024.0 * 3.14159265359) + 1.0) * 0.5; + gl_FragColor = mix( + mix( + texture2D(textures[1], vec2(x0, y1)), + texture2D(textures[0], vec2(x0, y0)), + ymix + ), + mix( + texture2D(textures[1], vec2(x1, y1)), + texture2D(textures[0], vec2(x1, y0)), + ymix + ), + xmix + ); +} diff -r 13abdc98379e -r 374a5ae694e8 sms.c --- a/sms.c Thu Apr 18 22:06:47 2019 -0700 +++ b/sms.c Sat Apr 18 11:42:53 2020 -0700 @@ -9,7 +9,7 @@ #include "saves.h" #include "bindings.h" -#ifndef USE_NATIVE +#ifdef NEW_CORE #define Z80_CYCLE cycles #define Z80_OPTS opts #define z80_handle_code_write(...) @@ -70,7 +70,7 @@ { uint32_t vint = vdp_next_vint(sms->vdp); uint32_t hint = vdp_next_hint(sms->vdp); -#ifndef USE_NATIVE +#ifdef NEW_CORE sms->z80->int_cycle = vint < hint ? vint : hint; z80_sync_cycle(sms->z80, sms->z80->sync_cycle); #else @@ -357,7 +357,7 @@ sms_context *sms = (sms_context *)system; char *statepath = get_slot_name(system, slot, "state"); uint8_t ret; -#ifdef USE_NATIVE +#ifndef NEW_CORE if (!sms->z80->native_pc) { ret = get_modification_time(statepath) != 0; if (ret) { @@ -381,7 +381,7 @@ render_set_video_standard(VID_NTSC); while (!sms->should_return) { -#ifdef USE_NATIVE +#ifndef NEW_CORE if (system->delayed_load_slot) { load_state(system, system->delayed_load_slot - 1); system->delayed_load_slot = 0; @@ -408,7 +408,7 @@ vdp_run_context(sms->vdp, target_cycle); psg_run(sms->psg, target_cycle); -#ifdef USE_NATIVE +#ifndef NEW_CORE if (system->save_state) { while (!sms->z80->pc) { //advance Z80 to an instruction boundary @@ -460,7 +460,7 @@ load_state_path(sms, statefile); } -#ifdef USE_NATIVE +#ifndef NEW_CORE if (system->enter_debugger) { system->enter_debugger = 0; zinsert_breakpoint(sms->z80, sms->z80->pc, (uint8_t *)zdebugger); @@ -474,7 +474,7 @@ { sms_context *sms = (sms_context *)system; z80_assert_reset(sms->z80, sms->z80->Z80_CYCLE); -#ifdef USE_NATIVE +#ifndef NEW_CORE sms->z80->target_cycle = sms->z80->sync_cycle = sms->z80->current_cycle; #endif } @@ -498,7 +498,7 @@ { sms_context *sms = (sms_context *)system; sms->should_return = 1; -#ifdef USE_NATIVE +#ifndef NEW_CORE sms->z80->target_cycle = sms->z80->sync_cycle = sms->z80->Z80_CYCLE; #endif } @@ -633,7 +633,7 @@ set_gain_config(sms); - sms->vdp = init_vdp_context(0); + sms->vdp = init_vdp_context(0, 0); sms->vdp->system = &sms->header; sms->header.info.save_type = SAVE_NONE; diff -r 13abdc98379e -r 374a5ae694e8 sms.h --- a/sms.h Thu Apr 18 22:06:47 2019 -0700 +++ b/sms.h Sat Apr 18 11:42:53 2020 -0700 @@ -4,7 +4,7 @@ #include "system.h" #include "vdp.h" #include "psg.h" -#ifdef USE_NATIVE +#ifndef NEW_CORE #include "z80_to_x86.h" #else #include "z80.h" diff -r 13abdc98379e -r 374a5ae694e8 stateview.c --- a/stateview.c Thu Apr 18 22:06:47 2019 -0700 +++ b/stateview.c Sat Apr 18 11:42:53 2020 -0700 @@ -113,7 +113,7 @@ height = height < 240 ? (width/320) * 240 : height; render_init(width, height, "GST State Viewer", 0); - vdp_context *context = init_vdp_context(0); + vdp_context *context = init_vdp_context(0, 0); vdp_load_gst(context, state_file); vdp_run_to_vblank(context); vdp_print_sprite_table(context); @@ -122,6 +122,6 @@ puts("Forcing display on"); vdp_control_port_write(context, 0x8000 | REG_MODE_2 << 8 | context->regs[REG_MODE_2] | DISPLAY_ENABLE); } - render_wait_quit(context); + render_wait_quit(); return 0; } diff -r 13abdc98379e -r 374a5ae694e8 system.c --- a/system.c Thu Apr 18 22:06:47 2019 -0700 +++ b/system.c Sat Apr 18 11:42:53 2020 -0700 @@ -39,7 +39,7 @@ //More certain checks failed, look for a valid 68K reset vector if (media->size >= 8) { char *rom = media->buffer; - uint32_t reset = rom[4] << 24 | rom[5] << 16 | rom[6] << 8 | rom[7]; + uint32_t reset = rom[5] << 16 | rom[6] << 8 | rom[7]; if (!(reset & 1) && reset < media->size) { //we have a valid looking reset vector, assume it's a Genesis ROM return SYSTEM_GENESIS; diff -r 13abdc98379e -r 374a5ae694e8 system.h --- a/system.h Thu Apr 18 22:06:47 2019 -0700 +++ b/system.h Sat Apr 18 11:42:53 2020 -0700 @@ -58,6 +58,8 @@ system_fun config_updated; system_ptrszt_fun_rptr8 serialize; system_ptr8_sizet_fun deserialize; + system_str_fun start_vgm_log; + system_fun stop_vgm_log; rom_info info; arena *arena; char *next_rom; @@ -67,6 +69,7 @@ uint8_t save_state; uint8_t delayed_load_slot; uint8_t has_keyboard; + uint8_t vgm_logging; debugger_type debugger_type; system_type type; }; diff -r 13abdc98379e -r 374a5ae694e8 systems.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/systems.cfg Sat Apr 18 11:42:53 2020 -0700 @@ -0,0 +1,80 @@ +md1va0 { + name Model 1 VA0 + vram 64 + vsram 40 + zram 8 + tas broken + z80_open_bus float + fm discrete 2612 + tmss off +} +md1va3 { + name Model 1 VA3 + vram 64 + vsram 40 + zram 8 + tas broken + z80_open_bus FF + fm discrete 2612 + tmss off +} +md1va6 { + name Model 1 VA6 + vram 64 + vsram 40 + zram 8 + tas broken + z80_open_bus FF + fm discrete 2612 + tmss on +} +md2va1 { + name Model 2 VA1 + vram 64 + vsram 40 + zram 8 + tas broken + z80_open_bus FF + fm integrated 3834 + tmss on +} +md2va2 { + name Model 2 VA2 + vram 64 + vsram 40 + zram 8 + tas broken + z80_open_bus FF + fm discrete 2612 + tmss on +} +md3va1 { + name Model 3 VA1 + vram 64 + vsram 64 + zram 8 + tas broken + z80_open_bus FF + fm integrated 3834 + tmss on +} +md3va2 { + name Model 3 VA2 + vram 64 + vsram 64 + zram 8 + tas works + z80_open_bus FF + fm integrated 3834 + tmss on +} +teradrive { + name Teradrive + vram 128 + vsram 40 + zram 16 + tas broken + z80_open_bus FF + fm discrete 3834 + tmss off +} \ No newline at end of file diff -r 13abdc98379e -r 374a5ae694e8 trans.c diff -r 13abdc98379e -r 374a5ae694e8 util.c --- a/util.c Thu Apr 18 22:06:47 2019 -0700 +++ b/util.c Sat Apr 18 11:42:53 2020 -0700 @@ -80,7 +80,7 @@ for (char *cur = base; *cur; ++cur) { if (in_var) { - if (!(*cur == '_' || isalnum(*cur))) { + if (!isalnum(*cur)) { positions[num_vars].end = cur-base; if (positions[num_vars].end - positions[num_vars].start > max_var_len) { max_var_len = positions[num_vars].end - positions[num_vars].start; @@ -854,6 +854,16 @@ qsort(list, num_entries, sizeof(dir_entry), sort_dir_alpha); } +uint8_t delete_file(char *path) +{ +#ifdef _WIN32 + //TODO: Call Unicode version and prepend special string to remove max path limitation + return 0 != DeleteFileA(path); +#else + return 0 == unlink(path); +#endif +} + #ifdef __ANDROID__ #include @@ -943,7 +953,7 @@ fclose(f); return ret; } -#endif +#endif //ISLIB #ifdef _WIN32 char const *get_userdata_dir() @@ -1000,8 +1010,6 @@ } -#endif - +#endif //_WIN32 +#endif //__ANDROID__ - -#endif diff -r 13abdc98379e -r 374a5ae694e8 util.h --- a/util.h Thu Apr 18 22:06:47 2019 -0700 +++ b/util.h Sat Apr 18 11:42:53 2020 -0700 @@ -88,5 +88,7 @@ void debug_message(char *format, ...); //Disables output of info and debug messages to stdout void disable_stdout_messages(void); +//Deletes a file, returns true on success, false on failure +uint8_t delete_file(char *path); #endif //UTIL_H_ diff -r 13abdc98379e -r 374a5ae694e8 vdp.c --- a/vdp.c Thu Apr 18 22:06:47 2019 -0700 +++ b/vdp.c Sat Apr 18 11:42:53 2020 -0700 @@ -70,28 +70,36 @@ {127, 0, 127} //Sprites }; +static uint32_t calc_crop(uint32_t crop, uint32_t border) +{ + return crop >= border ? 0 : border - crop; +} + static void update_video_params(vdp_context *context) { + uint32_t top_crop = render_overscan_top(); + uint32_t bot_crop = render_overscan_bot(); + uint32_t border_top; if (context->regs[REG_MODE_2] & BIT_MODE_5) { if (context->regs[REG_MODE_2] & BIT_PAL) { if (context->flags2 & FLAG2_REGION_PAL) { context->inactive_start = PAL_INACTIVE_START; - context->border_top = BORDER_TOP_V30_PAL; - context->border_bot = BORDER_BOT_V30_PAL; + border_top = BORDER_TOP_V30_PAL; + context->border_bot = calc_crop(bot_crop, BORDER_BOT_V30_PAL); } else { //the behavior here is rather weird and needs more investigation context->inactive_start = 0xF0; - context->border_top = 1; - context->border_bot = 3; + border_top = 1; + context->border_bot = calc_crop(bot_crop, 3); } } else { context->inactive_start = NTSC_INACTIVE_START; if (context->flags2 & FLAG2_REGION_PAL) { - context->border_top = BORDER_TOP_V28_PAL; - context->border_bot = BORDER_BOT_V28_PAL; + border_top = BORDER_TOP_V28_PAL; + context->border_bot = calc_crop(bot_crop, BORDER_BOT_V28_PAL); } else { - context->border_top = BORDER_TOP_V28; - context->border_bot = BORDER_TOP_V28; + border_top = BORDER_TOP_V28; + context->border_bot = calc_crop(bot_crop, BORDER_BOT_V28); } } if (context->regs[REG_MODE_4] & BIT_H40) { @@ -112,11 +120,11 @@ } else { context->inactive_start = MODE4_INACTIVE_START; if (context->flags2 & FLAG2_REGION_PAL) { - context->border_top = BORDER_TOP_V24_PAL; - context->border_bot = BORDER_BOT_V24_PAL; + border_top = BORDER_TOP_V24_PAL; + context->border_bot = calc_crop(bot_crop, BORDER_BOT_V24_PAL); } else { - context->border_top = BORDER_TOP_V24; - context->border_bot = BORDER_BOT_V24; + border_top = BORDER_TOP_V24; + context->border_bot = calc_crop(bot_crop, BORDER_BOT_V24); } if (!(context->regs[REG_MODE_1] & BIT_MODE_4)){ context->state = INACTIVE; @@ -130,24 +138,27 @@ } } } + context->border_top = calc_crop(top_crop, border_top); + context->top_offset = border_top - context->border_top; } static uint8_t color_map_init_done; -vdp_context *init_vdp_context(uint8_t region_pal) +vdp_context *init_vdp_context(uint8_t region_pal, uint8_t has_max_vsram) { vdp_context *context = calloc(1, sizeof(vdp_context) + VRAM_SIZE); if (headless) { - context->output = malloc(LINEBUF_SIZE * sizeof(uint32_t)); - context->output_pitch = 0; + context->fb = malloc(512 * LINEBUF_SIZE * sizeof(uint32_t)); + context->output_pitch = LINEBUF_SIZE * sizeof(uint32_t); } else { context->cur_buffer = FRAMEBUFFER_ODD; context->fb = render_get_framebuffer(FRAMEBUFFER_ODD, &context->output_pitch); } - context->sprite_draws = MAX_DRAWS; + context->sprite_draws = MAX_SPRITES_LINE; context->fifo_write = 0; context->fifo_read = -1; context->regs[REG_HINT] = context->hint_counter = 0xFF; + context->vsram_size = has_max_vsram ? MAX_VSRAM_SIZE : MIN_VSRAM_SIZE; if (!color_map_init_done) { uint8_t b,g,r; @@ -235,9 +246,7 @@ context->flags2 |= FLAG2_REGION_PAL; } update_video_params(context); - if (!headless) { - context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * context->border_top); - } + context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * context->border_top); return context; } @@ -269,40 +278,86 @@ static void render_sprite_cells(vdp_context * context) { + if (context->cur_slot > MAX_SPRITES_LINE) { + context->cur_slot--; + return; + } + if (context->cur_slot < 0) { + return; + } sprite_draw * d = context->sprite_draw_list + context->cur_slot; - context->serial_address = d->address; - if (context->cur_slot >= context->sprite_draws) { - - uint16_t dir; - int16_t x; - if (d->h_flip) { - x = d->x_pos + 7; - dir = -1; - } else { - x = d->x_pos; - dir = 1; - } - //printf("Draw Slot %d of %d, Rendering sprite cell from %X to x: %d\n", context->cur_slot, context->sprite_draws, d->address, x); - context->cur_slot--; - for (uint16_t address = d->address; address != ((d->address+4) & 0xFFFF); address++) { - if (x >= 0 && x < 320) { - if (!(context->linebuf[x] & 0xF)) { - context->linebuf[x] = (context->vdpmem[address] >> 4) | d->pal_priority; - } else if (context->vdpmem[address] >> 4) { - context->flags2 |= FLAG2_SPRITE_COLLIDE; + uint16_t address = d->address; + address += context->sprite_x_offset * d->height * 4; + context->serial_address = address; + uint16_t dir; + int16_t x; + if (d->h_flip) { + x = d->x_pos + 7 + 8 * (d->width - context->sprite_x_offset - 1); + dir = -1; + } else { + x = d->x_pos + context->sprite_x_offset * 8; + dir = 1; + } + if (d->x_pos) { + context->flags |= FLAG_CAN_MASK; + if (!(context->flags & FLAG_MASKED)) { + x -= 128; + //printf("Draw Slot %d of %d, Rendering sprite cell from %X to x: %d\n", context->cur_slot, context->sprite_draws, d->address, x); + uint8_t collide = 0; + if (x >= 8 && x < 312) { + //sprite is fully visible + for (; address != ((context->serial_address+4) & 0xFFFF); address++) { + uint8_t pixel = context->vdpmem[address] >> 4; + if (!(context->linebuf[x] & 0xF)) { + context->linebuf[x] = pixel | d->pal_priority; + } else { + collide |= pixel; + } + x += dir; + pixel = context->vdpmem[address] & 0xF; + if (!(context->linebuf[x] & 0xF)) { + context->linebuf[x] = pixel | d->pal_priority; + } else { + collide |= pixel; + } + x += dir; + } + } else if (x > -8 && x < 327) { + //sprite is partially visible + for (; address != ((context->serial_address+4) & 0xFFFF); address++) { + if (x >= 0 && x < 320) { + uint8_t pixel = context->vdpmem[address] >> 4; + if (!(context->linebuf[x] & 0xF)) { + context->linebuf[x] = pixel | d->pal_priority; + } else { + collide |= pixel; + } + } + x += dir; + if (x >= 0 && x < 320) { + uint8_t pixel = context->vdpmem[address] & 0xF; + if (!(context->linebuf[x] & 0xF)) { + context->linebuf[x] = pixel | d->pal_priority; + } else { + collide |= pixel; + } + } + x += dir; } } - x += dir; - if (x >= 0 && x < 320) { - if (!(context->linebuf[x] & 0xF)) { - context->linebuf[x] = (context->vdpmem[address] & 0xF) | d->pal_priority; - } else if (context->vdpmem[address] & 0xF) { - context->flags2 |= FLAG2_SPRITE_COLLIDE; - } + if (collide) { + context->flags2 |= FLAG2_SPRITE_COLLIDE; } - x += dir; } - } else { + } else if (context->flags & FLAG_CAN_MASK) { + context->flags |= FLAG_MASKED; + context->flags &= ~FLAG_CAN_MASK; + } + + context->sprite_x_offset++; + if (context->sprite_x_offset == d->width) { + d->x_pos = 0; + context->sprite_x_offset = 0; context->cur_slot--; } } @@ -530,6 +585,9 @@ (context->flags & FLAG_PENDING) ? "word" : (context->flags2 & FLAG2_BYTE_PENDING) ? "byte" : "none", context->vcounter, context->hslot*2, (context->flags2 & FLAG2_VINT_PENDING) ? "true" : "false", (context->flags2 & FLAG2_HINT_PENDING) ? "true" : "false", vdp_control_port_read(context)); + printf("\nDebug Register: %X | Output disabled: %s, Force Layer: %d\n", context->test_port, + (context->test_port & TEST_BIT_DISABLE) ? "true" : "false", context->test_port >> 7 & 3 + ); //restore flags as calling vdp_control_port_read can change them context->flags = old_flags; context->flags2 = old_flags2; @@ -695,47 +753,13 @@ } else { address = ((tileinfo & 0x7FF) << 5) + row * 4; } - int16_t x = ((context->vdpmem[att_addr+ 2] & 0x3) << 8 | context->vdpmem[att_addr + 3]) & 0x1FF; - if (x) { - context->flags |= FLAG_CAN_MASK; - } else if(context->flags & (FLAG_CAN_MASK | FLAG_DOT_OFLOW)) { - context->flags |= FLAG_MASKED; - } - - context->flags &= ~FLAG_DOT_OFLOW; - int16_t i; - if (context->flags & FLAG_MASKED) { - for (i=0; i < width && context->sprite_draws; i++) { - --context->sprite_draws; - context->sprite_draw_list[context->sprite_draws].x_pos = -128; - context->sprite_draw_list[context->sprite_draws].address = address + i * height * 4; - } - } else { - x -= 128; - int16_t base_x = x; - int16_t dir; - if (tileinfo & MAP_BIT_H_FLIP) { - x += (width-1) * 8; - dir = -8; - } else { - dir = 8; - } - //printf("Sprite %d | x: %d, y: %d, width: %d, height: %d, pal_priority: %X, row: %d, tile addr: %X\n", context->sprite_info_list[context->cur_slot].index, x, context->sprite_info_list[context->cur_slot].y, width, height, pal_priority, row, address); - for (i=0; i < width && context->sprite_draws; i++, x += dir) { - --context->sprite_draws; - context->sprite_draw_list[context->sprite_draws].address = address + i * height * 4; - context->sprite_draw_list[context->sprite_draws].x_pos = x; - context->sprite_draw_list[context->sprite_draws].pal_priority = pal_priority; - context->sprite_draw_list[context->sprite_draws].h_flip = (tileinfo & MAP_BIT_H_FLIP) ? 1 : 0; - } - } - //Used to be i < width - //TODO: Confirm this is the right condition on hardware - if (!context->sprite_draws) { - context->flags |= FLAG_DOT_OFLOW; - } - } else { - context->flags |= FLAG_DOT_OFLOW; + context->sprite_draws--; + context->sprite_draw_list[context->sprite_draws].x_pos = ((context->vdpmem[att_addr+ 2] & 0x3) << 8 | context->vdpmem[att_addr + 3]) & 0x1FF; + context->sprite_draw_list[context->sprite_draws].address = address; + context->sprite_draw_list[context->sprite_draws].pal_priority = pal_priority; + context->sprite_draw_list[context->sprite_draws].h_flip = (tileinfo & MAP_BIT_H_FLIP) ? 1 : 0; + context->sprite_draw_list[context->sprite_draws].width = width; + context->sprite_draw_list[context->sprite_draws].height = height; } } context->cur_slot++; @@ -790,7 +814,7 @@ } write_cram_internal(context, addr, value); - if (context->hslot >= BG_START_SLOT && ( + if (context->output && context->hslot >= BG_START_SLOT && ( context->vcounter < context->inactive_start + context->border_bot || context->vcounter > 0x200 - context->border_top )) { @@ -915,7 +939,7 @@ break; } case VSRAM_WRITE: - if (((start->address/2) & 63) < VSRAM_SIZE) { + if (((start->address/2) & 63) < context->vsram_size) { //printf("VSRAM Write: %X to %X @ frame: %d, vcounter: %d, hslot: %d, cycle: %d\n", start->value, start->address, context->frame, context->vcounter, context->hslot, context->cycles); if (start->partial == 3) { if (start->address & 1) { @@ -952,7 +976,7 @@ context->flags |= FLAG_READ_FETCHED; } - } else if (!(context->cd & 1) && !(context->flags & (FLAG_READ_FETCHED|FLAG_PENDING))) { + } else if (!(context->cd & 1) && !(context->flags & FLAG_READ_FETCHED)) { switch(context->cd & 0xF) { case VRAM_READ: @@ -989,7 +1013,7 @@ break; case VSRAM_READ: { uint16_t address = (context->address /2) & 63; - if (address >= VSRAM_SIZE) { + if (address >= context->vsram_size) { address = 0; } context->prefetch = context->vsram[address] & VSRAM_BITS; @@ -1125,32 +1149,15 @@ context->v_offset = vscroll & v_offset_mask; //printf("%s | line %d, vsram: %d, vscroll: %d, v_offset: %d\n",(vsram_off ? "B" : "A"), line, context->vsram[context->regs[REG_MODE_3] & 0x4 ? column : 0], vscroll, context->v_offset); vscroll >>= vscroll_shift; - uint16_t hscroll_mask; - uint16_t v_mul; - switch(context->regs[REG_SCROLL] & 0x3) - { - case 0: - hscroll_mask = 0x1F; - v_mul = 64; - break; - case 0x1: - hscroll_mask = 0x3F; - v_mul = 128; - break; - case 0x2: - //TODO: Verify this behavior - hscroll_mask = 0x1F; - v_mul = 0; - break; - case 0x3: - hscroll_mask = 0x7F; - v_mul = 256; - break; - } + //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}; + 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; for (int i = 0; i < 2; i++) { hscroll = (column - 2 + i - ((hscroll_val/8) & 0xFFFE)) & hscroll_mask; - offset = address + ((vscroll * v_mul + hscroll*2) & 0x1FFF); + offset = address + (((vscroll << v_shift) + hscroll*2) & 0x1FFF); //printf("%s | line: %d, col: %d, x: %d, hs_mask %X, scr reg: %X, tbl addr: %X\n", (vsram_off ? "B" : "A"), line, (column-2+i), hscroll, hscroll_mask, context->regs[REG_SCROLL], offset); uint16_t col_val = (context->vdpmem[offset] << 8) | context->vdpmem[offset+1]; if (i) { @@ -1208,28 +1215,25 @@ } uint8_t pal_priority = (col >> 9) & 0x70; uint32_t bits = *((uint32_t *)(&context->vdpmem[address])); + tmp_buf += offset; if (col & MAP_BIT_H_FLIP) { uint32_t shift = 28; for (int i = 0; i < 4; i++) { uint8_t right = pal_priority | ((bits >> shift) & 0xF); shift -= 4; - tmp_buf[offset++] = pal_priority | ((bits >> shift) & 0xF); + *(tmp_buf++) = pal_priority | ((bits >> shift) & 0xF); shift -= 4; - offset &= SCROLL_BUFFER_MASK; - tmp_buf[offset++] = right; - offset &= SCROLL_BUFFER_MASK; + *(tmp_buf++) = right; } } else { for (int i = 0; i < 4; i++) { uint8_t right = pal_priority | (bits & 0xF); bits >>= 4; - tmp_buf[offset++] = pal_priority | (bits & 0xF); - offset &= SCROLL_BUFFER_MASK; + *(tmp_buf++) = pal_priority | (bits & 0xF); bits >>= 4; - tmp_buf[offset++] = right; - offset &= SCROLL_BUFFER_MASK; + *(tmp_buf++) = right; } } } @@ -1328,23 +1332,32 @@ 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) { - int start = 0; + uint8_t *sprite_buf = context->linebuf + col * 8; if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) { memset(dst, 0, 8); memset(debug_dst, DBG_SRC_BG, 8); dst += 8; debug_dst += 8; - start = 8; - } - uint8_t *sprite_buf = context->linebuf + col * 8 + start; - 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_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; - sprite = *sprite_buf; - *(dst++) = composite_normal(context, debug_dst, sprite, plane_a, plane_b, context->regs[REG_BG_COLOR]) & 0x3F; - debug_dst++; + sprite_buf += 8; + plane_a_off += 8; + plane_b_off += 8; + 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_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++; + } + } else { + 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_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++; + } } } @@ -1609,10 +1622,10 @@ plane_a_off = context->buf_a_off; a_src = DBG_SRC_W; } else { - plane_a_off = context->buf_a_off - (context->hscroll_a & 0xF); + plane_a_off = context->buf_a_off - context->hscroll_a_fine; a_src = DBG_SRC_A; } - plane_b_off = context->buf_b_off - (context->hscroll_b & 0xF); + 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) { @@ -1642,13 +1655,14 @@ case 1: memset(dst, 0, BORDER_LEFT); memset(debug_dst, DBG_SRC_BG, BORDER_LEFT); + dst += BORDER_LEFT; break; case 2: { //plane A //TODO: Deal with Window layer int i; i = 0; - uint8_t buf_off = context->buf_a_off - (context->hscroll_a & 0xF) + (16 - BORDER_LEFT); + uint8_t buf_off = context->buf_a_off - context->hscroll_a_fine + (16 - BORDER_LEFT); //uint8_t *src = context->tmp_buf_a + ((context->buf_a_off + (i ? 0 : (16 - BORDER_LEFT) - (context->hscroll_a & 0xF))) & SCROLL_BUFFER_MASK); for (; i < BORDER_LEFT; buf_off++, i++, dst++, debug_dst++) { @@ -1661,7 +1675,7 @@ //plane B int i; i = 0; - uint8_t buf_off = context->buf_b_off - (context->hscroll_b & 0xF) + (16 - BORDER_LEFT); + uint8_t buf_off = context->buf_b_off - context->hscroll_b_fine + (16 - BORDER_LEFT); //uint8_t *src = context->tmp_buf_b + ((context->buf_b_off + (i ? 0 : (16 - BORDER_LEFT) - (context->hscroll_b & 0xF))) & SCROLL_BUFFER_MASK); for (; i < BORDER_LEFT; buf_off++, i++, dst++, debug_dst++) { @@ -1674,8 +1688,8 @@ } else { memset(dst, pixel, BORDER_LEFT); memset(debug_dst, DBG_SRC_BG, BORDER_LEFT); + dst += BORDER_LEFT; } - dst += BORDER_LEFT; } context->done_composite = dst; context->buf_a_off = (context->buf_a_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_MASK; @@ -1725,7 +1739,7 @@ uint8_t *dst = context->compositebuf + col * 8 + BORDER_LEFT; uint8_t *debug_dst = context->layer_debug_buf + col * 8 + BORDER_LEFT; if (context->state == PREPARING) { - memset(dst, 0, 8); + memset(dst, 0x10 + (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET, 8); memset(debug_dst, DBG_SRC_BG, 8); context->done_composite = dst + 8; return; @@ -1752,8 +1766,8 @@ } context->done_composite = dst; } else { - memset(dst, 0, 8); - memset(dst, DBG_SRC_BG, 8); + memset(dst, 0x10 + (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET, 8); + memset(debug_dst, DBG_SRC_BG, 8); context->done_composite = dst + 8; } } @@ -1809,11 +1823,21 @@ } if (context->enabled_debuggers & (1 << VDP_DEBUG_CRAM)) { uint32_t *fb = context->debug_fbs[VDP_DEBUG_CRAM] + context->debug_fb_pitch[VDP_DEBUG_CRAM] * line / sizeof(uint32_t); - for (int i = 0; i < 64; i++) - { - for (int x = 0; x < 8; x++) + if (context->regs[REG_MODE_2] & BIT_MODE_5) { + for (int i = 0; i < 64; i++) { - *(fb++) = context->colors[i]; + for (int x = 0; x < 8; x++) + { + *(fb++) = context->colors[i]; + } + } + } else { + for (int i = MODE4_OFFSET; i < MODE4_OFFSET+32; i++) + { + for (int x = 0; x < 16; x++) + { + *(fb++) = context->colors[i]; + } } } } @@ -1988,28 +2012,54 @@ uint32_t starting_line = 512 - 32*4; uint32_t *line = context->debug_fbs[VDP_DEBUG_CRAM] + context->debug_fb_pitch[VDP_DEBUG_CRAM] * starting_line / sizeof(uint32_t); - for (int pal = 0; pal < 4; pal ++) - { - uint32_t *cur; - for (int y = 0; y < 31; y++) + if (context->regs[REG_MODE_2] & BIT_MODE_5) { + for (int pal = 0; pal < 4; pal ++) { + uint32_t *cur; + for (int y = 0; y < 31; y++) + { + cur = line; + for (int offset = 0; offset < 16; offset++) + { + for (int x = 0; x < 31; x++) + { + *(cur++) = context->colors[pal * 16 + offset]; + } + *(cur++) = 0xFF000000; + } + line += context->debug_fb_pitch[VDP_DEBUG_CRAM] / sizeof(uint32_t); + } cur = line; - for (int offset = 0; offset < 16; offset++) + for (int x = 0; x < 512; x++) { - for (int x = 0; x < 31; x++) - { - *(cur++) = context->colors[pal * 16 + offset]; - } *(cur++) = 0xFF000000; } line += context->debug_fb_pitch[VDP_DEBUG_CRAM] / sizeof(uint32_t); } - cur = line; - for (int x = 0; x < 512; x++) + } else { + for (int pal = 0; pal < 2; pal ++) { - *(cur++) = 0xFF000000; + uint32_t *cur; + for (int y = 0; y < 31; y++) + { + cur = line; + for (int offset = MODE4_OFFSET; offset < MODE4_OFFSET + 16; offset++) + { + for (int x = 0; x < 31; x++) + { + *(cur++) = context->colors[pal * 16 + offset]; + } + *(cur++) = 0xFF000000; + } + line += context->debug_fb_pitch[VDP_DEBUG_CRAM] / sizeof(uint32_t); + } + cur = line; + for (int x = 0; x < 512; x++) + { + *(cur++) = 0xFF000000; + } + line += context->debug_fb_pitch[VDP_DEBUG_CRAM] / sizeof(uint32_t); } - line += context->debug_fb_pitch[VDP_DEBUG_CRAM] / sizeof(uint32_t); } render_framebuffer_updated(context->debug_fb_indices[VDP_DEBUG_CRAM], 512); context->debug_fbs[VDP_DEBUG_CRAM] = render_get_framebuffer(context->debug_fb_indices[VDP_DEBUG_CRAM], &context->debug_fb_pitch[VDP_DEBUG_CRAM]); @@ -2022,9 +2072,10 @@ void vdp_force_update_framebuffer(vdp_context *context) { - uint16_t lines_max = (context->flags2 & FLAG2_REGION_PAL) - ? 240 + BORDER_TOP_V30_PAL + BORDER_BOT_V30_PAL - : 224 + BORDER_TOP_V28 + BORDER_BOT_V28; + if (!context->fb) { + return; + } + uint16_t lines_max = context->inactive_start + context->border_bot + context->border_top; uint16_t to_fill = lines_max - context->output_lines; memset( @@ -2039,69 +2090,85 @@ static void advance_output_line(vdp_context *context) { - if (headless) { - if (context->vcounter == context->inactive_start) { - context->frame++; - } - context->vcounter &= 0x1FF; - } else { - uint16_t lines_max = (context->flags2 & FLAG2_REGION_PAL) - ? 240 + BORDER_TOP_V30_PAL + BORDER_BOT_V30_PAL - : 224 + BORDER_TOP_V28 + BORDER_BOT_V28; - - if (context->output_lines == lines_max) { + //This function is kind of gross because of the need to deal with vertical border busting via mode changes + uint16_t lines_max = context->inactive_start + context->border_bot + context->border_top; + uint32_t output_line = context->vcounter; + if (!(context->regs[REG_MODE_2] & BIT_MODE_5)) { + //vcounter increment occurs much later in Mode 4 + output_line++; + } + + if (context->output_lines >= lines_max || (!context->pushed_frame && output_line == context->inactive_start + context->border_top)) { + //we've either filled up a full frame or we're at the bottom of screen in the current defined mode + border crop + if (!headless) { render_framebuffer_updated(context->cur_buffer, context->h40_lines > (context->inactive_start + context->border_top) / 2 ? LINEBUF_SIZE : (256+HORIZ_BORDER)); - context->cur_buffer = context->flags2 & FLAG2_EVEN_FIELD ? FRAMEBUFFER_EVEN : FRAMEBUFFER_ODD; - context->fb = render_get_framebuffer(context->cur_buffer, &context->output_pitch); - vdp_update_per_frame_debug(context); - context->h40_lines = 0; - context->frame++; - context->output_lines = 0; + uint8_t is_even = context->flags2 & FLAG2_EVEN_FIELD; + if (context->vcounter <= context->inactive_start && (context->regs[REG_MODE_4] & BIT_INTERLACE)) { + is_even = !is_even; + } + context->cur_buffer = is_even ? FRAMEBUFFER_EVEN : FRAMEBUFFER_ODD; + context->pushed_frame = 1; + context->fb = NULL; } - uint32_t output_line = context->vcounter; - if (!(context->regs[REG_MODE_2] & BIT_MODE_5)) { - //vcounter increment occurs much later in Mode 4 - output_line++; - } - if (output_line < context->inactive_start + context->border_bot && context->output_lines > 0) { + vdp_update_per_frame_debug(context); + context->h40_lines = 0; + context->frame++; + context->output_lines = 0; + } + + if (output_line < context->inactive_start + context->border_bot) { + if (context->output_lines) { output_line = context->output_lines++;//context->border_top + context->vcounter; - } else if (output_line >= 0x200 - context->border_top) { - if (output_line == 0x200 - context->border_top) { - //We're at the top of the display, force context->output_lines to be zero to avoid - //potential screen rolling if the mode is changed at an inopportune time - context->output_lines = 0; - } - output_line = context->output_lines++;//context->vcounter - (0x200 - context->border_top); + } else if (!output_line && !context->border_top) { + //top border is completely cropped so we won't hit the case below + output_line = 0; + context->output_lines = 1; + context->pushed_frame = 0; } else { - context->output = NULL; + context->output_lines = output_line + 1; + } + } else if (output_line >= 0x200 - context->border_top) { + if (output_line == 0x200 - context->border_top) { + //We're at the top of the display, force context->output_lines to be zero to avoid + //potential screen rolling if the mode is changed at an inopportune time + context->output_lines = 0; + context->pushed_frame = 0; } - context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * output_line); + output_line = context->output_lines++;//context->vcounter - (0x200 - context->border_top); + } else { + context->output = NULL; + return; + } + if (!context->fb) { + context->fb = render_get_framebuffer(context->cur_buffer, &context->output_pitch); + } + output_line += context->top_offset; + context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * output_line); #ifdef DEBUG_FB_FILL - for (int i = 0; i < LINEBUF_SIZE; i++) - { - context->output[i] = 0xFFFF00FF; - } + for (int i = 0; i < LINEBUF_SIZE; i++) + { + context->output[i] = 0xFFFF00FF; + } #endif - if (context->output && (context->regs[REG_MODE_4] & BIT_H40)) { - context->h40_lines++; - } + if (context->output && (context->regs[REG_MODE_4] & BIT_H40)) { + context->h40_lines++; } } void vdp_release_framebuffer(vdp_context *context) { - render_framebuffer_updated(context->cur_buffer, context->h40_lines > (context->inactive_start + context->border_top) / 2 ? LINEBUF_SIZE : (256+HORIZ_BORDER)); - context->output = context->fb = NULL; + if (context->fb) { + render_framebuffer_updated(context->cur_buffer, context->h40_lines > (context->inactive_start + context->border_top) / 2 ? LINEBUF_SIZE : (256+HORIZ_BORDER)); + context->output = context->fb = NULL; + } } void vdp_reacquire_framebuffer(vdp_context *context) { - context->fb = render_get_framebuffer(context->cur_buffer, &context->output_pitch); - uint16_t lines_max = (context->flags2 & FLAG2_REGION_PAL) - ? 240 + BORDER_TOP_V30_PAL + BORDER_BOT_V30_PAL - : 224 + BORDER_TOP_V28 + BORDER_BOT_V28; + uint16_t lines_max = context->inactive_start + context->border_bot + context->border_top; if (context->output_lines <= lines_max && context->output_lines > 0) { - context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * (context->output_lines - 1)); + context->fb = render_get_framebuffer(context->cur_buffer, &context->output_pitch); + context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * (context->output_lines - 1 + context->top_offset)); } else { context->output = NULL; } @@ -2138,7 +2205,7 @@ //TODO: Deal with Window layer int i; i = 0; - uint8_t buf_off = context->buf_a_off - (context->hscroll_a & 0xF); + uint8_t buf_off = context->buf_a_off - context->hscroll_a_fine; //uint8_t *src = context->tmp_buf_a + ((context->buf_a_off + (i ? 0 : (16 - BORDER_LEFT) - (context->hscroll_a & 0xF))) & SCROLL_BUFFER_MASK); for (; i < BORDER_RIGHT; buf_off++, i++, dst++) { @@ -2218,6 +2285,30 @@ }\ }\ } + +//BG_START_SLOT => dst = 0, src = border +//BG_START_SLOT + 13/2=6, dst = 6, src = border + comp + 13 +#define OUTPUT_PIXEL_MODE4(slot) if ((slot) >= BG_START_SLOT) {\ + uint8_t *src = context->compositebuf + ((slot) - BG_START_SLOT) *2;\ + uint32_t *dst = context->output + ((slot) - BG_START_SLOT) *2;\ + if ((slot) - BG_START_SLOT < BORDER_LEFT/2) {\ + *(dst++) = context->colors[bgindex];\ + *(dst++) = context->colors[bgindex];\ + } else if ((slot) - BG_START_SLOT < (BORDER_LEFT+256)/2){\ + if ((slot) - BG_START_SLOT == BORDER_LEFT/2) {\ + *(dst++) = context->colors[bgindex];\ + src++;\ + } else {\ + *(dst++) = context->colors[*(src++)];\ + }\ + *(dst++) = context->colors[*(src++)];\ + } else if ((slot) - BG_START_SLOT <= (HORIZ_BORDER+256)/2) {\ + *(dst++) = context->colors[bgindex];\ + if ((slot) - BG_START_SLOT < (HORIZ_BORDER+256)/2) {\ + *(dst++) = context->colors[bgindex];\ + }\ + }\ + } #define COLUMN_RENDER_BLOCK(column, startcyc) \ case startcyc:\ @@ -2291,11 +2382,11 @@ #define COLUMN_RENDER_BLOCK_MODE4(column, startcyc) \ case startcyc:\ - OUTPUT_PIXEL(startcyc)\ + OUTPUT_PIXEL_MODE4(startcyc)\ read_map_mode4(column, context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+1)&0xFF):\ - OUTPUT_PIXEL((startcyc+1)&0xFF)\ + OUTPUT_PIXEL_MODE4((startcyc+1)&0xFF)\ if (column & 3) {\ scan_sprite_table_mode4(context);\ } else {\ @@ -2303,11 +2394,11 @@ }\ CHECK_LIMIT\ case ((startcyc+2)&0xFF):\ - OUTPUT_PIXEL((startcyc+2)&0xFF)\ + OUTPUT_PIXEL_MODE4((startcyc+2)&0xFF)\ fetch_map_mode4(column, context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+3)&0xFF):\ - OUTPUT_PIXEL((startcyc+3)&0xFF)\ + OUTPUT_PIXEL_MODE4((startcyc+3)&0xFF)\ render_map_mode4(context->vcounter, column, context);\ CHECK_LIMIT @@ -2424,31 +2515,225 @@ #define SPRITE_RENDER_H32_MODE4(slot) \ case slot:\ - OUTPUT_PIXEL_H32(slot)\ + OUTPUT_PIXEL_MODE4(slot)\ read_sprite_x_mode4(context);\ MODE4_CHECK_SLOT_LINE(slot)\ case CALC_SLOT(slot, 1):\ - OUTPUT_PIXEL(CALC_SLOT(slot, 1))\ + OUTPUT_PIXEL_MODE4(CALC_SLOT(slot, 1))\ read_sprite_x_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot,1))\ case CALC_SLOT(slot, 2):\ - OUTPUT_PIXEL(CALC_SLOT(slot, 2))\ + OUTPUT_PIXEL_MODE4(CALC_SLOT(slot, 2))\ fetch_sprite_cells_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot, 2))\ case CALC_SLOT(slot, 3):\ - OUTPUT_PIXEL(CALC_SLOT(slot, 3))\ + OUTPUT_PIXEL_MODE4(CALC_SLOT(slot, 3))\ render_sprite_cells_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot, 3))\ case CALC_SLOT(slot, 4):\ - OUTPUT_PIXEL(CALC_SLOT(slot, 4))\ + OUTPUT_PIXEL_MODE4(CALC_SLOT(slot, 4))\ fetch_sprite_cells_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot, 4))\ case CALC_SLOT(slot, 5):\ - OUTPUT_PIXEL(CALC_SLOT(slot, 5))\ + OUTPUT_PIXEL_MODE4(CALC_SLOT(slot, 5))\ render_sprite_cells_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot, 5)) static uint32_t dummy_buffer[LINEBUF_SIZE]; +static void vdp_h40_line(vdp_context * context) +{ + uint16_t address; + uint32_t mask; + uint32_t const slot_cycles = MCLKS_SLOT_H40; + uint8_t bgindex = context->regs[REG_BG_COLOR] & 0x3F; + uint8_t test_layer = context->test_port >> 7 & 3; + + //165 + if (!(context->regs[REG_MODE_3] & BIT_VSCROLL)) { + //TODO: Develop some tests on hardware to see when vscroll latch actually happens for full plane mode + //See note in vdp_h32 for why this was originally moved out of read_map_scroll + //Skitchin' has a similar problem, but uses H40 mode. It seems to be able to hit the extern slot at 232 + //pretty consistently + context->vscroll_latch[0] = context->vsram[0]; + context->vscroll_latch[1] = context->vsram[1]; + } + render_sprite_cells(context); + //166 + render_sprite_cells(context); + //167 + context->sprite_index = 0x80; + context->slot_counter = 0; + render_border_garbage( + context, + context->sprite_draw_list[context->cur_slot].address, + context->tmp_buf_b, context->buf_b_off, + context->col_1 + ); + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + //168 + render_border_garbage( + context, + context->sprite_draw_list[context->cur_slot].address, + context->tmp_buf_b, + context->buf_b_off + 8, + context->col_2 + ); + //Do palette lookup for end of previous line + uint8_t *src = context->compositebuf + (LINE_CHANGE_H40 - BG_START_SLOT) *2; + uint32_t *dst = context->output + (LINE_CHANGE_H40 - BG_START_SLOT) *2; + if (test_layer) { + for (int i = 0; i < LINEBUF_SIZE - (LINE_CHANGE_H40 - BG_START_SLOT) * 2; i++) + { + *(dst++) = context->colors[*(src++)]; + } + } else { + for (int i = 0; i < LINEBUF_SIZE - (LINE_CHANGE_H40 - BG_START_SLOT) * 2; i++) + { + if (*src & 0x3F) { + *(dst++) = context->colors[*(src++)]; + } else { + *(dst++) = context->colors[(*(src++) & 0xC0) | bgindex]; + } + } + } + advance_output_line(context); + //168-242 (inclusive) + for (int i = 0; i < 28; i++) + { + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + } + //243 + render_border_garbage( + context, + context->sprite_draw_list[context->cur_slot].address, + context->tmp_buf_a, + context->buf_a_off, + context->col_1 + ); + //244 + address = (context->regs[REG_HSCROLL] & 0x3F) << 10; + mask = 0; + if (context->regs[REG_MODE_3] & 0x2) { + mask |= 0xF8; + } + if (context->regs[REG_MODE_3] & 0x1) { + mask |= 0x7; + } + render_border_garbage(context, address, context->tmp_buf_a, context->buf_a_off+8, context->col_2); + address += (context->vcounter & mask) * 4; + context->hscroll_a = context->vdpmem[address] << 8 | context->vdpmem[address+1]; + context->hscroll_a_fine = context->hscroll_a & 0xF; + context->hscroll_b = context->vdpmem[address+2] << 8 | context->vdpmem[address+3]; + context->hscroll_b_fine = context->hscroll_b & 0xF; + //printf("%d: HScroll A: %d, HScroll B: %d\n", context->vcounter, context->hscroll_a, context->hscroll_b); + //243-246 inclusive + for (int i = 0; i < 3; i++) + { + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + } + //247 + render_border_garbage( + context, + context->sprite_draw_list[context->cur_slot].address, + context->tmp_buf_b, + context->buf_b_off, + context->col_1 + ); + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + //248 + render_border_garbage( + context, + context->sprite_draw_list[context->cur_slot].address, + context->tmp_buf_b, + context->buf_b_off + 8, + context->col_2 + ); + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + context->buf_a_off = (context->buf_a_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_MASK; + context->buf_b_off = (context->buf_b_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_MASK; + //250 + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + //251 + scan_sprite_table(context->vcounter, context);//Just a guess + //252 + scan_sprite_table(context->vcounter, context);//Just a guess + //254 + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + //255 + if (context->cur_slot >= 0 && context->sprite_draw_list[context->cur_slot].x_pos) { + context->flags |= FLAG_DOT_OFLOW; + } + scan_sprite_table(context->vcounter, context); + //0 + scan_sprite_table(context->vcounter, context);//Just a guess + //seems like the sprite table scan fills a shift register + //values are FIFO, but unused slots precede used slots + //so we set cur_slot to slot_counter and let it wrap around to + //the beginning of the list + context->cur_slot = context->slot_counter; + context->sprite_x_offset = 0; + context->sprite_draws = MAX_SPRITES_LINE; + //background planes and layer compositing + for (int col = 0; col < 42; col+=2) + { + read_map_scroll_a(col, context->vcounter, context); + render_map_1(context); + render_map_2(context); + read_map_scroll_b(col, context->vcounter, context); + render_map_3(context); + render_map_output(context->vcounter, col, context); + } + //sprite rendering phase 2 + for (int i = 0; i < MAX_SPRITES_LINE; i++) + { + read_sprite_x(context->vcounter, context); + } + //163 + context->cur_slot = MAX_SPRITES_LINE-1; + memset(context->linebuf, 0, LINEBUF_SIZE); + render_border_garbage( + context, + context->sprite_draw_list[context->cur_slot].address, + context->tmp_buf_a, context->buf_a_off, + context->col_1 + ); + context->flags &= ~FLAG_MASKED; + render_sprite_cells(context); + //164 + render_border_garbage( + context, + context->sprite_draw_list[context->cur_slot].address, + context->tmp_buf_a, context->buf_a_off + 8, + context->col_2 + ); + render_sprite_cells(context); + context->cycles += MCLKS_LINE; + vdp_advance_line(context); + src = context->compositebuf; + dst = context->output; + if (test_layer) { + for (int i = 0; i < (LINE_CHANGE_H40 - BG_START_SLOT) * 2; i++) + { + *(dst++) = context->colors[*(src++)]; + } + } else { + for (int i = 0; i < (LINE_CHANGE_H40 - BG_START_SLOT) * 2; i++) + { + if (*src & 0x3F) { + *(dst++) = context->colors[*(src++)]; + } else { + *(dst++) = context->colors[(*(src++) & 0xC0) | bgindex]; + } + } + } +} static void vdp_h40(vdp_context * context, uint32_t target_cycles) { uint16_t address; @@ -2466,6 +2751,13 @@ for (;;) { case 165: + //only consider doing a line at a time if the FIFO is empty, there are no pending reads and there is no DMA running + if (context->fifo_read == -1 && !(context->flags & FLAG_DMA_RUN) && ((context->cd & 1) || (context->flags & FLAG_READ_FETCHED))) { + while (target_cycles - context->cycles >= MCLKS_LINE && context->state != PREPARING && context->vcounter != context->inactive_start) { + vdp_h40_line(context); + } + CHECK_ONLY + } OUTPUT_PIXEL(165) if (!(context->regs[REG_MODE_3] & BIT_VSCROLL)) { //TODO: Develop some tests on hardware to see when vscroll latch actually happens for full plane mode @@ -2553,7 +2845,9 @@ render_border_garbage(context, address, context->tmp_buf_a, context->buf_a_off+8, context->col_2); address += (context->vcounter & mask) * 4; context->hscroll_a = context->vdpmem[address] << 8 | context->vdpmem[address+1]; + context->hscroll_a_fine = context->hscroll_a & 0xF; context->hscroll_b = context->vdpmem[address+2] << 8 | context->vdpmem[address+3]; + context->hscroll_b_fine = context->hscroll_b & 0xF; //printf("%d: HScroll A: %d, HScroll B: %d\n", context->vcounter, context->hscroll_a, context->hscroll_b); if (context->flags & FLAG_DMA_RUN) { run_dma_src(context, -1); } context->hslot++; @@ -2581,6 +2875,9 @@ CHECK_LIMIT SPRITE_RENDER_H40(254) case 255: + if (context->cur_slot >= 0 && context->sprite_draw_list[context->cur_slot].x_pos) { + context->flags |= FLAG_DOT_OFLOW; + } render_map_3(context); scan_sprite_table(context->vcounter, context);//Just a guess CHECK_LIMIT @@ -2592,8 +2889,8 @@ //so we set cur_slot to slot_counter and let it wrap around to //the beginning of the list context->cur_slot = context->slot_counter; - context->sprite_draws = MAX_DRAWS; - context->flags &= (~FLAG_CAN_MASK & ~FLAG_MASKED); + context->sprite_x_offset = 0; + context->sprite_draws = MAX_SPRITES_LINE; CHECK_LIMIT COLUMN_RENDER_BLOCK(2, 1) COLUMN_RENDER_BLOCK(4, 9) @@ -2626,7 +2923,7 @@ //sprite render to line buffer starts case 163: OUTPUT_PIXEL(163) - context->cur_slot = MAX_DRAWS-1; + context->cur_slot = MAX_SPRITES_LINE-1; memset(context->linebuf, 0, LINEBUF_SIZE); render_border_garbage( context, @@ -2634,6 +2931,7 @@ context->tmp_buf_a, context->buf_a_off, context->col_1 ); + context->flags &= ~FLAG_MASKED; render_sprite_cells(context); CHECK_LIMIT case 164: @@ -2769,7 +3067,9 @@ render_border_garbage(context, address, context->tmp_buf_a, context->buf_a_off+8, context->col_2); address += (context->vcounter & mask) * 4; context->hscroll_a = context->vdpmem[address] << 8 | context->vdpmem[address+1]; + context->hscroll_a_fine = context->hscroll_a & 0xF; context->hscroll_b = context->vdpmem[address+2] << 8 | context->vdpmem[address+3]; + context->hscroll_b_fine = context->hscroll_b & 0xF; //printf("%d: HScroll A: %d, HScroll B: %d\n", context->vcounter, context->hscroll_a, context->hscroll_b); CHECK_LIMIT //provides "garbage" for border when plane A selected SPRITE_RENDER_H32(245) @@ -2782,6 +3082,9 @@ CHECK_LIMIT SPRITE_RENDER_H32(250) case 251: + if (context->cur_slot >= 0 && context->sprite_draw_list[context->cur_slot].x_pos) { + context->flags |= FLAG_DOT_OFLOW; + } render_map_1(context); scan_sprite_table(context->vcounter, context);//Just a guess CHECK_LIMIT @@ -2807,8 +3110,8 @@ //filled rather than the number of available slots //context->slot_counter = MAX_SPRITES_LINE - context->slot_counter; context->cur_slot = context->slot_counter; - context->sprite_draws = MAX_DRAWS_H32; - context->flags &= (~FLAG_CAN_MASK & ~FLAG_MASKED); + context->sprite_x_offset = 0; + context->sprite_draws = MAX_SPRITES_LINE_H32; CHECK_LIMIT COLUMN_RENDER_BLOCK(2, 1) COLUMN_RENDER_BLOCK(4, 9) @@ -2838,7 +3141,7 @@ //sprite render to line buffer starts case 131: OUTPUT_PIXEL(131) - context->cur_slot = MAX_DRAWS_H32-1; + context->cur_slot = MAX_SPRITES_LINE_H32-1; memset(context->linebuf, 0, LINEBUF_SIZE); render_border_garbage( context, @@ -2846,6 +3149,7 @@ context->tmp_buf_a, context->buf_a_off, context->col_1 ); + context->flags &= ~FLAG_MASKED; render_sprite_cells(context); CHECK_LIMIT case 132: @@ -2982,19 +3286,19 @@ COLUMN_RENDER_BLOCK_MODE4(30, 125) COLUMN_RENDER_BLOCK_MODE4(31, 129) case 133: - OUTPUT_PIXEL(133) + OUTPUT_PIXEL_MODE4(133) external_slot(context); CHECK_LIMIT case 134: - OUTPUT_PIXEL(134) + OUTPUT_PIXEL_MODE4(134) external_slot(context); CHECK_LIMIT case 135: - OUTPUT_PIXEL(135) + OUTPUT_PIXEL_MODE4(135) external_slot(context); CHECK_LIMIT case 136: { - OUTPUT_PIXEL(136) + OUTPUT_PIXEL_MODE4(136) external_slot(context); //set things up for sprite rendering in the next slot memset(context->linebuf, 0, LINEBUF_SIZE); @@ -3028,11 +3332,11 @@ uint8_t *src = NULL; if (test_layer == 2) { //plane A - src_off += context->buf_a_off + context->hscroll_a; + src_off += context->buf_a_off - (context->hscroll_a & 0xF); src = context->tmp_buf_a; } else if (test_layer == 3){ //plane B - src_off += context->buf_b_off + context->hscroll_b; + src_off += context->buf_b_off - (context->hscroll_b & 0xF); src = context->tmp_buf_b; } else { //sprite layer @@ -3064,7 +3368,6 @@ uint8_t buf_clear_slot, index_reset_slot, bg_end_slot, vint_slot, line_change, jump_start, jump_dest, latch_slot; uint8_t index_reset_value, max_draws, max_sprites; uint16_t vint_line, active_line; - uint32_t bg_color; if (mode_5) { if (is_h40) { @@ -3072,7 +3375,7 @@ buf_clear_slot = 163; index_reset_slot = 167; bg_end_slot = BG_START_SLOT + LINEBUF_SIZE/2; - max_draws = MAX_DRAWS-1; + max_draws = MAX_SPRITES_LINE-1; max_sprites = MAX_SPRITES_LINE; index_reset_value = 0x80; vint_slot = VINT_SLOT_H40; @@ -3081,7 +3384,7 @@ jump_dest = 229; } else { bg_end_slot = BG_START_SLOT + (256+HORIZ_BORDER)/2; - max_draws = MAX_DRAWS_H32-1; + max_draws = MAX_SPRITES_LINE_H32-1; max_sprites = MAX_SPRITES_LINE_H32; buf_clear_slot = 128; index_reset_slot = 132; @@ -3108,7 +3411,6 @@ vint_line = context->inactive_start + 1; vint_slot = VINT_SLOT_MODE4; line_change = LINE_CHANGE_MODE4; - bg_color = render_map_color(0, 0, 0); jump_start = 147; jump_dest = 233; if (context->regs[REG_MODE_1] & BIT_MODE_4) { @@ -3156,6 +3458,8 @@ break; case 0: render_border_garbage(context, context->serial_address, context->tmp_buf_b, context->buf_b_off+8, context->col_2); + break; + case 1: inactive_test_output(context, is_h40, test_layer); break; } @@ -3189,12 +3493,15 @@ if (dst) { uint8_t bg_index; + uint32_t bg_color; if (mode_5) { bg_index = context->regs[REG_BG_COLOR] & 0x3F; bg_color = context->colors[bg_index]; } else if (context->regs[REG_MODE_1] & BIT_MODE_4) { bg_index = 0x10 + (context->regs[REG_BG_COLOR] & 0xF); bg_color = context->colors[MODE4_OFFSET + bg_index]; + } else { + bg_color = render_map_color(0, 0, 0); } if (context->done_composite) { uint8_t pixel = context->compositebuf[dst-context->output]; @@ -3384,6 +3691,19 @@ return hv; } +static void clear_pending(vdp_context *context) +{ + context->flags &= ~FLAG_PENDING; + context->address = context->address_latch; + //It seems like the DMA enable bit doesn't so much enable DMA so much + //as it enables changing CD5 from control port writes + if (context->regs[REG_MODE_2] & BIT_DMA_ENABLE) { + context->cd = context->cd_latch; + } else { + context->cd = (context->cd & 0x20) | (context->cd_latch & 0x1F); + } +} + int vdp_control_port_write(vdp_context * context, uint16_t value) { //printf("control port write: %X at %d\n", value, context->cycles); @@ -3391,12 +3711,9 @@ return -1; } if (context->flags & FLAG_PENDING) { - context->address = (context->address & 0x3FFF) | (value << 14 & 0x1C000); - //It seems like the DMA enable bit doesn't so much enable DMA so much - //as it enables changing CD5 from control port writes - uint8_t preserve = (context->regs[REG_MODE_2] & BIT_DMA_ENABLE) ? 0x3 : 0x23; - context->cd = (context->cd & preserve) | ((value >> 2) & ~preserve & 0xFF); - context->flags &= ~FLAG_PENDING; + context->address_latch = (context->address_latch & 0x3FFF) | (value << 14 & 0x1C000); + context->cd_latch = (context->cd_latch & 0x3) | ((value >> 2) & ~0x3 & 0xFF); + clear_pending(context); //Should these be taken care of here or after the first write? context->flags &= ~FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_READ_PENDING; @@ -3425,8 +3742,10 @@ } } else { uint8_t mode_5 = context->regs[REG_MODE_2] & BIT_MODE_5; - context->address = (context->address &0xC000) | (value & 0x3FFF); - context->cd = (context->cd & 0x3C) | (value >> 14); + //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; @@ -3488,7 +3807,7 @@ return -1; } if (context->flags & FLAG_PENDING) { - context->flags &= ~FLAG_PENDING; + clear_pending(context); //Should these be cleared here? context->flags &= ~FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_READ_PENDING; @@ -3523,7 +3842,7 @@ void vdp_data_port_write_pbc(vdp_context * context, uint8_t value) { if (context->flags & FLAG_PENDING) { - context->flags &= ~FLAG_PENDING; + clear_pending(context); //Should these be cleared here? context->flags &= ~FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_READ_PENDING; @@ -3562,7 +3881,9 @@ uint16_t vdp_control_port_read(vdp_context * context) { - context->flags &= ~FLAG_PENDING; + if (context->flags & FLAG_PENDING) { + clear_pending(context); + } context->flags2 &= ~FLAG2_BYTE_PENDING; //Bits 15-10 are not fixed like Charles MacDonald's doc suggests, but instead open bus values that reflect 68K prefetch uint16_t value = context->system->get_open_bus_value(context->system) & 0xFC00; @@ -3612,7 +3933,7 @@ uint16_t vdp_data_port_read(vdp_context * context) { if (context->flags & FLAG_PENDING) { - context->flags &= ~FLAG_PENDING; + clear_pending(context); //Should these be cleared here? context->flags &= ~FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_READ_PENDING; @@ -3629,19 +3950,16 @@ uint8_t vdp_data_port_read_pbc(vdp_context * context) { - context->flags &= ~(FLAG_PENDING | FLAG_READ_FETCHED); + if (context->flags & FLAG_PENDING) { + clear_pending(context); + } + context->flags &= ~FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_BYTE_PENDING; context->cd = VRAM_READ8; return context->prefetch; } -uint16_t vdp_test_port_read(vdp_context * context) -{ - //TODO: Find out what actually gets returned here - return context->test_port; -} - void vdp_adjust_cycles(vdp_context * context, uint32_t deduction) { context->cycles -= deduction; @@ -3934,12 +4252,14 @@ } } +#define VDP_STATE_VERSION 3 void vdp_serialize(vdp_context *context, serialize_buffer *buf) { + save_int8(buf, VDP_STATE_VERSION); save_int8(buf, VRAM_SIZE / 1024);//VRAM size in KB, needed for future proofing save_buffer8(buf, context->vdpmem, VRAM_SIZE); save_buffer16(buf, context->cram, CRAM_SIZE); - save_buffer16(buf, context->vsram, VSRAM_SIZE); + save_buffer16(buf, context->vsram, MAX_VSRAM_SIZE); save_buffer8(buf, context->sat_cache, SAT_CACHE_SIZE); for (int i = 0; i <= REG_DMASRC_H; i++) { @@ -3990,13 +4310,15 @@ save_int8(buf, context->sprite_draws); save_int8(buf, context->slot_counter); save_int8(buf, context->cur_slot); - for (int i = 0; i < MAX_DRAWS; i++) + for (int i = 0; i < MAX_SPRITES_LINE; i++) { sprite_draw *draw = context->sprite_draw_list + i; save_int16(buf, draw->address); save_int16(buf, draw->x_pos); save_int8(buf, draw->pal_priority); save_int8(buf, draw->h_flip); + save_int8(buf, draw->width); + save_int8(buf, draw->height); } for (int i = 0; i < MAX_SPRITES_LINE; i++) { @@ -4010,12 +4332,24 @@ save_int32(buf, context->cycles); save_int32(buf, context->pending_vint_start); save_int32(buf, context->pending_hint_start); + save_int32(buf, context->address_latch); + save_int8(buf, context->cd_latch); } void vdp_deserialize(deserialize_buffer *buf, void *vcontext) { vdp_context *context = vcontext; - uint8_t vramk = load_int8(buf); + uint8_t version = load_int8(buf); + uint8_t vramk; + if (version == 64) { + vramk = version; + version = 0; + } else { + vramk = load_int8(buf); + } + if (version > VDP_STATE_VERSION) { + warning("Save state has VDP version %d, but this build only understands versions %d and lower", version, VDP_STATE_VERSION); + } load_buffer8(buf, context->vdpmem, (vramk * 1024) <= VRAM_SIZE ? vramk * 1024 : VRAM_SIZE); if ((vramk * 1024) > VRAM_SIZE) { buf->cur_pos += (vramk * 1024) - VRAM_SIZE; @@ -4025,7 +4359,7 @@ { update_color_map(context, i, context->cram[i]); } - load_buffer16(buf, context->vsram, VSRAM_SIZE); + load_buffer16(buf, context->vsram, version > 1 ? MAX_VSRAM_SIZE : MIN_VSRAM_SIZE); load_buffer8(buf, context->sat_cache, SAT_CACHE_SIZE); for (int i = 0; i <= REG_DMASRC_H; i++) { @@ -4077,13 +4411,50 @@ context->sprite_draws = load_int8(buf); context->slot_counter = load_int8(buf); context->cur_slot = load_int8(buf); - for (int i = 0; i < MAX_DRAWS; i++) - { - sprite_draw *draw = context->sprite_draw_list + i; - draw->address = load_int16(buf); - draw->x_pos = load_int16(buf); - draw->pal_priority = load_int8(buf); - draw->h_flip = load_int8(buf); + if (version == 0) { + int cur_draw = 0; + for (int i = 0; i < MAX_SPRITES_LINE * 2; i++) + { + if (cur_draw < MAX_SPRITES_LINE) { + sprite_draw *last = cur_draw ? context->sprite_draw_list + cur_draw - 1 : NULL; + sprite_draw *draw = context->sprite_draw_list + cur_draw++; + draw->address = load_int16(buf); + draw->x_pos = load_int16(buf); + draw->pal_priority = load_int8(buf); + draw->h_flip = load_int8(buf); + draw->width = 1; + draw->height = 8; + + if (last && last->width < 4 && last->h_flip == draw->h_flip && last->pal_priority == draw->pal_priority) { + int adjust_x = draw->x_pos + draw->h_flip ? -8 : 8; + int height = draw->address - last->address /4; + if (last->x_pos == adjust_x && ( + (last->width > 1 && height == last->height) || + (last->width == 1 && (height == 8 || height == 16 || height == 24 || height == 32)) + )) { + //current draw appears to be part of the same sprite as the last one, combine it + cur_draw--; + last->width++; + } + } + } else { + load_int16(buf); + load_int16(buf); + load_int8(buf); + load_int8(buf); + } + } + } else { + for (int i = 0; i < MAX_SPRITES_LINE; i++) + { + sprite_draw *draw = context->sprite_draw_list + i; + draw->address = load_int16(buf); + draw->x_pos = load_int16(buf); + draw->pal_priority = load_int8(buf); + draw->h_flip = load_int8(buf); + draw->width = load_int8(buf); + draw->height = load_int8(buf); + } } for (int i = 0; i < MAX_SPRITES_LINE; i++) { @@ -4097,6 +4468,13 @@ context->cycles = load_int32(buf); context->pending_vint_start = load_int32(buf); context->pending_hint_start = load_int32(buf); + if (version > 2) { + context->address_latch = load_int32(buf); + context->cd_latch = load_int8(buf); + } else { + context->address_latch = context->address; + context->cd_latch = context->cd; + } update_video_params(context); } diff -r 13abdc98379e -r 374a5ae694e8 vdp.h --- a/vdp.h Thu Apr 18 22:06:47 2019 -0700 +++ b/vdp.h Sat Apr 18 11:42:53 2020 -0700 @@ -16,7 +16,8 @@ #define SHADOW_OFFSET CRAM_SIZE #define HIGHLIGHT_OFFSET (SHADOW_OFFSET+CRAM_SIZE) #define MODE4_OFFSET (HIGHLIGHT_OFFSET+CRAM_SIZE) -#define VSRAM_SIZE 40 +#define MIN_VSRAM_SIZE 40 +#define MAX_VSRAM_SIZE 64 #define VRAM_SIZE (64*1024) #define BORDER_LEFT 13 #define BORDER_RIGHT 14 @@ -24,8 +25,6 @@ #define LINEBUF_SIZE (320+HORIZ_BORDER) //H40 + full border #define SCROLL_BUFFER_SIZE 32 #define BORDER_BOTTOM 13 //TODO: Replace with actual value -#define MAX_DRAWS 40 -#define MAX_DRAWS_H32 32 #define MAX_DRAWS_H32_MODE4 8 #define MAX_SPRITES_LINE 20 #define MAX_SPRITES_LINE_H32 16 @@ -133,6 +132,8 @@ int16_t x_pos; uint8_t pal_priority; uint8_t h_flip; + uint8_t width; + uint8_t height; } sprite_draw; typedef struct { @@ -173,29 +174,35 @@ int32_t fifo_write; int32_t fifo_read; uint32_t address; + uint32_t address_latch; uint32_t serial_address; uint32_t colors[CRAM_SIZE*4]; uint32_t debugcolors[1 << (3 + 1 + 1 + 1)];//3 bits for source, 1 bit for priority, 1 bit for shadow, 1 bit for hilight uint16_t cram[CRAM_SIZE]; uint32_t frame; + uint32_t vsram_size; uint8_t cd; + uint8_t cd_latch; uint8_t flags; uint8_t regs[VDP_REGS]; //cycle count in MCLKs uint32_t cycles; uint32_t pending_vint_start; uint32_t pending_hint_start; - uint16_t vsram[VSRAM_SIZE]; + uint32_t top_offset; + uint16_t vsram[MAX_VSRAM_SIZE]; uint16_t vscroll_latch[2]; uint16_t vcounter; uint16_t inactive_start; uint16_t border_top; uint16_t border_bot; uint16_t hscroll_a; + uint16_t hscroll_a_fine; uint16_t hscroll_b; + uint16_t hscroll_b_fine; uint16_t h40_lines; uint16_t output_lines; - sprite_draw sprite_draw_list[MAX_DRAWS]; + sprite_draw sprite_draw_list[MAX_SPRITES_LINE]; sprite_info sprite_info_list[MAX_SPRITES_LINE]; uint8_t sat_cache[SAT_CACHE_SIZE]; uint16_t col_1; @@ -212,6 +219,7 @@ uint8_t sprite_draws; int8_t slot_counter; int8_t cur_slot; + uint8_t sprite_x_offset; uint8_t max_sprites_frame; uint8_t max_sprites_line; uint8_t fetch_tmp[2]; @@ -229,12 +237,13 @@ uint8_t enabled_debuggers; uint8_t debug_fb_indices[VDP_NUM_DEBUG_TYPES]; uint8_t debug_modes[VDP_NUM_DEBUG_TYPES]; + uint8_t pushed_frame; uint8_t vdpmem[]; } vdp_context; -vdp_context *init_vdp_context(uint8_t region_pal); +vdp_context *init_vdp_context(uint8_t region_pal, uint8_t has_max_vsram); void vdp_free(vdp_context *context); void vdp_run_context_full(vdp_context * context, uint32_t target_cycles); void vdp_run_context(vdp_context * context, uint32_t target_cycles); @@ -254,7 +263,6 @@ uint8_t vdp_data_port_read_pbc(vdp_context * context); void vdp_latch_hv(vdp_context *context); uint16_t vdp_hv_counter_read(vdp_context * context); -uint16_t vdp_test_port_read(vdp_context * context); void vdp_adjust_cycles(vdp_context * context, uint32_t deduction); uint32_t vdp_next_hint(vdp_context * context); uint32_t vdp_next_vint(vdp_context * context); diff -r 13abdc98379e -r 374a5ae694e8 vgm.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vgm.c Sat Apr 18 11:42:53 2020 -0700 @@ -0,0 +1,117 @@ +#include +#include +#include +#include "vgm.h" + +vgm_writer *vgm_write_open(char *filename, uint32_t rate, uint32_t clock, uint32_t cycle) +{ + FILE *f = fopen(filename, "wb"); + if (!f) { + return NULL; + } + vgm_writer *writer = calloc(sizeof(vgm_writer), 1); + memcpy(writer->header.ident, "Vgm ", 4); + writer->header.version = 0x150; + writer->header.data_offset = sizeof(writer->header) - offsetof(vgm_header, data_offset); + writer->header.rate = rate; + writer->f = f; + if (1 != fwrite(&writer->header, sizeof(writer->header), 1, f)) { + free(writer); + fclose(f); + return NULL; + } + writer->master_clock = clock; + writer->last_cycle = cycle; + + return writer; +} + +void vgm_sn76489_init(vgm_writer *writer, uint32_t clock, uint16_t feedback, uint8_t shift_reg_size, uint8_t flags) +{ + if (flags && writer->header.version < 0x151) { + writer->header.version = 0x151; + } + writer->header.sn76489_clk = clock, + writer->header.sn76489_fb = feedback; + writer->header.sn76489_shift = shift_reg_size; + writer->header.sn76489_flags = flags; +} + +static void wait_commands(vgm_writer *writer, uint32_t delta) +{ + if (!delta) { + return; + } + if (delta <= 0x10) { + fputc(CMD_WAIT_SHORT + (delta - 1), writer->f); + } else if (delta >= 735 && delta <= (735 + 0x10)) { + fputc(CMD_WAIT_60, writer->f); + wait_commands(writer, delta - 735); + } else if (delta >= 882 && delta <= (882 + 0x10)) { + fputc(CMD_WAIT_50, writer->f); + wait_commands(writer, delta - 882); + } else if (delta > 0xFFFF) { + uint8_t cmd[3] = {CMD_WAIT, 0xFF, 0xFF}; + fwrite(cmd, 1, sizeof(cmd), writer->f); + wait_commands(writer, delta - 0xFFFF); + } else { + uint8_t cmd[3] = {CMD_WAIT, delta, delta >> 8}; + fwrite(cmd, 1, sizeof(cmd), writer->f); + } +} + +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; + + uint32_t mclks_per_sample = writer->master_clock / 44100; + writer->last_cycle += delta * mclks_per_sample; + writer->header.num_samples += delta; + wait_commands(writer, delta); +} + +void vgm_sn76489_write(vgm_writer *writer, uint32_t cycle, uint8_t value) +{ + add_wait(writer, cycle); + uint8_t cmd[2] = {CMD_PSG, value}; + fwrite(cmd, 1, sizeof(cmd), writer->f); +} + +void vgm_ym2612_init(vgm_writer *writer, uint32_t clock) +{ + writer->header.ym2612_clk = clock; +} + +void vgm_ym2612_part1_write(vgm_writer *writer, uint32_t cycle, uint8_t reg, uint8_t value) +{ + add_wait(writer, cycle); + uint8_t cmd[3] = {CMD_YM2612_0, reg, value}; + fwrite(cmd, 1, sizeof(cmd), writer->f); +} + +void vgm_ym2612_part2_write(vgm_writer *writer, uint32_t cycle, uint8_t reg, uint8_t value) +{ + add_wait(writer, cycle); + uint8_t cmd[3] = {CMD_YM2612_1, reg, value}; + fwrite(cmd, 1, sizeof(cmd), writer->f); +} + +void vgm_adjust_cycles(vgm_writer *writer, uint32_t deduction) +{ + if (deduction > writer->last_cycle) { + writer->last_cycle = 0; + } else { + writer->last_cycle -= deduction; + } +} + +void vgm_close(vgm_writer *writer) +{ + 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); + fclose(writer->f); + free(writer); +} \ No newline at end of file diff -r 13abdc98379e -r 374a5ae694e8 vgm.h --- a/vgm.h Thu Apr 18 22:06:47 2019 -0700 +++ b/vgm.h Sat Apr 18 11:42:53 2020 -0700 @@ -1,6 +1,9 @@ #ifndef VGM_H_ #define VGM_H_ +#include +#include + #pragma pack(push, 1) typedef struct { char ident[4]; @@ -71,4 +74,20 @@ uint8_t type; } data_block; +typedef struct { + vgm_header header; + FILE *f; + uint32_t master_clock; + uint32_t last_cycle; +} vgm_writer; + +vgm_writer *vgm_write_open(char *filename, uint32_t rate, uint32_t clock, uint32_t cycle); +void vgm_sn76489_init(vgm_writer *writer, uint32_t clock, uint16_t feedback, uint8_t shift_reg_size, uint8_t flags); +void vgm_sn76489_write(vgm_writer *writer, uint32_t cycle, uint8_t value); +void vgm_ym2612_init(vgm_writer *writer, uint32_t clock); +void vgm_ym2612_part1_write(vgm_writer *writer, uint32_t cycle, uint8_t reg, uint8_t value); +void vgm_ym2612_part2_write(vgm_writer *writer, uint32_t cycle, uint8_t reg, uint8_t value); +void vgm_adjust_cycles(vgm_writer *writer, uint32_t deduction); +void vgm_close(vgm_writer *writer); + #endif //VGM_H_ diff -r 13abdc98379e -r 374a5ae694e8 ym2612.c --- a/ym2612.c Thu Apr 18 22:06:47 2019 -0700 +++ b/ym2612.c Sat Apr 18 11:42:53 2020 -0700 @@ -21,9 +21,7 @@ #define dfopen(var, fname, mode) #endif -#define BUSY_CYCLES_ADDRESS 17 -#define BUSY_CYCLES_DATA_LOW 83 -#define BUSY_CYCLES_DATA_HIGH 47 +#define BUSY_CYCLES 32 #define OP_UPDATE_PERIOD 144 #define BIT_TIMERA_ENABLE 0x1 @@ -122,6 +120,27 @@ render_audio_adjust_clock(context->audio, master_clock, context->clock_inc * NUM_OPERATORS); } +void ym_adjust_cycles(ym2612_context *context, uint32_t deduction) +{ + context->current_cycle -= deduction; + if (context->write_cycle != CYCLE_NEVER && context->write_cycle >= deduction) { + context->write_cycle -= deduction; + } else { + context->write_cycle = CYCLE_NEVER; + } + if (context->busy_start != CYCLE_NEVER && context->busy_start >= deduction) { + context->busy_start -= deduction; + } else { + context->busy_start = CYCLE_NEVER; + } + if (context->last_status_cycle != CYCLE_NEVER && context->last_status_cycle >= deduction) { + context->last_status_cycle -= deduction; + } else { + context->last_status = 0; + context->last_status_cycle = CYCLE_NEVER; + } +} + #ifdef __ANDROID__ #define log2(x) (log(x)/log(2)) #endif @@ -173,7 +192,11 @@ dfopen(debug_file, "ym_debug.txt", "w"); memset(context, 0, sizeof(*context)); context->clock_inc = clock_div * 6; + context->busy_cycles = BUSY_CYCLES * context->clock_inc; context->audio = render_audio_source(master_clock, context->clock_inc * NUM_OPERATORS, 2); + //TODO: pick a randomish high initial value and lower it over time + context->invalid_status_decay = 225000 * context->clock_inc; + context->status_address_mask = (options & YM_OPT_3834) ? 0 : 3; //some games seem to expect that the LR flags start out as 1 for (int i = 0; i < NUM_CHANNELS; i++) { @@ -338,128 +361,287 @@ } } +void ym_run_timers(ym2612_context *context) +{ + if (context->timer_control & BIT_TIMERA_ENABLE) { + if (context->timer_a != TIMER_A_MAX) { + context->timer_a++; + if (context->csm_keyon) { + csm_keyoff(context); + } + } else { + if (context->timer_control & BIT_TIMERA_LOAD) { + context->timer_control &= ~BIT_TIMERA_LOAD; + } else if (context->timer_control & BIT_TIMERA_OVEREN) { + context->status |= BIT_STATUS_TIMERA; + } + context->timer_a = context->timer_a_load; + if (!context->csm_keyon && context->ch3_mode == CSM_MODE) { + context->csm_keyon = 0xF0; + uint8_t changes = 0xF0 ^ context->channels[2].keyon;; + for (uint8_t op = 2*4, bit = 0; op < 3*4; op++, bit++) + { + if (changes & keyon_bits[bit]) { + keyon(context->operators + op, context->channels + 2); + } + } + } + } + } + if (!context->sub_timer_b) { + if (context->timer_control & BIT_TIMERB_ENABLE) { + if (context->timer_b != TIMER_B_MAX) { + context->timer_b++; + } else { + if (context->timer_control & BIT_TIMERB_LOAD) { + context->timer_control &= ~BIT_TIMERB_LOAD; + } else if (context->timer_control & BIT_TIMERB_OVEREN) { + context->status |= BIT_STATUS_TIMERB; + } + context->timer_b = context->timer_b_load; + } + } + } + context->sub_timer_b += 0x10; + //Update LFO + if (context->lfo_enable) { + if (context->lfo_counter) { + context->lfo_counter--; + } else { + context->lfo_counter = lfo_timer_values[context->lfo_freq]; + context->lfo_am_step += 2; + context->lfo_am_step &= 0xFE; + uint8_t old_pm_step = context->lfo_pm_step; + context->lfo_pm_step = context->lfo_am_step / 8; + if (context->lfo_pm_step != old_pm_step) { + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + if (context->channels[chan].pms) { + for (int op = chan * 4; op < (chan + 1) * 4; op++) + { + context->operators[op].phase_inc = ym_calc_phase_inc(context, context->operators + op, op); + } + } + } + } + } + } +} + +void ym_run_envelope(ym2612_context *context, ym_channel *channel, ym_operator *operator) +{ + uint32_t env_cyc = context->env_counter; + uint8_t rate; + if (operator->env_phase == PHASE_DECAY && operator->envelope >= operator->sustain_level) { + //operator->envelope = operator->sustain_level; + operator->env_phase = PHASE_SUSTAIN; + } + rate = operator->rates[operator->env_phase]; + if (rate) { + uint8_t ks = channel->keycode >> operator->key_scaling;; + rate = rate*2 + ks; + if (rate > 63) { + rate = 63; + } + } + uint32_t cycle_shift = rate < 0x30 ? ((0x2F - rate) >> 2) : 0; + if (!(env_cyc & ((1 << cycle_shift) - 1))) { + uint32_t update_cycle = env_cyc >> cycle_shift & 0x7; + uint16_t envelope_inc = rate_table[rate * 8 + update_cycle]; + if (operator->env_phase == PHASE_ATTACK) { + //this can probably be optimized to a single shift rather than a multiply + shift + uint16_t old_env = operator->envelope; + operator->envelope += ((~operator->envelope * envelope_inc) >> 4) & 0xFFFFFFFC; + if (operator->envelope > old_env) { + //Handle overflow + operator->envelope = 0; + } + if (!operator->envelope) { + operator->env_phase = PHASE_DECAY; + } + } else { + if (operator->ssg) { + if (operator->envelope < SSG_CENTER) { + envelope_inc *= 4; + } else { + envelope_inc = 0; + } + } + //envelope value is 10-bits, but it will be used as a 4.8 value + operator->envelope += envelope_inc << 2; + //clamp to max attenuation value + if ( + operator->envelope > MAX_ENVELOPE + || (operator->env_phase == PHASE_RELEASE && operator->envelope >= SSG_CENTER) + ) { + operator->envelope = MAX_ENVELOPE; + } + } + } +} + +void ym_run_phase(ym2612_context *context, uint32_t channel, uint32_t op) +{ + if (channel != 5 || !context->dac_enable) { + //printf("updating operator %d of channel %d\n", op, channel); + ym_operator * operator = context->operators + op; + ym_channel * chan = context->channels + channel; + uint16_t phase = operator->phase_counter >> 10 & 0x3FF; + operator->phase_counter += operator->phase_inc;//ym_calc_phase_inc(context, operator, op); + int16_t mod = 0; + if (op & 3) { + if (operator->mod_src[0]) { + mod = *operator->mod_src[0]; + if (operator->mod_src[1]) { + mod += *operator->mod_src[1]; + } + mod >>= YM_MOD_SHIFT; + } + } else { + if (chan->feedback) { + mod = (chan->op1_old + operator->output) >> (10-chan->feedback); + } + } + uint16_t env = operator->envelope; + if (operator->ssg) { + if (env >= SSG_CENTER) { + if (operator->ssg & SSG_ALTERNATE) { + if (operator->env_phase != PHASE_RELEASE && ( + !(operator->ssg & SSG_HOLD) || ((operator->ssg ^ operator->inverted) & SSG_INVERT) == 0 + )) { + operator->inverted ^= SSG_INVERT; + } + } else if (!(operator->ssg & SSG_HOLD)) { + phase = operator->phase_counter = 0; + } + if ( + (operator->env_phase == PHASE_DECAY || operator->env_phase == PHASE_SUSTAIN) + && !(operator->ssg & SSG_HOLD) + ) { + start_envelope(operator, chan); + env = operator->envelope; + } + } + if (operator->inverted) { + env = (SSG_CENTER - env) & MAX_ENVELOPE; + } + } + env += operator->total_level; + if (operator->am) { + uint16_t base_am = (context->lfo_am_step & 0x80 ? context->lfo_am_step : ~context->lfo_am_step) & 0x7E; + if (ams_shift[chan->ams] >= 0) { + env += (base_am >> ams_shift[chan->ams]) & MAX_ENVELOPE; + } else { + env += base_am << (-ams_shift[chan->ams]); + } + } + if (env > MAX_ENVELOPE) { + env = MAX_ENVELOPE; + } + if (first_key_on) { + dfprintf(debug_file, "op %d, base phase: %d, mod: %d, sine: %d, out: %d\n", op, phase, mod, sine_table[(phase+mod) & 0x1FF], pow_table[sine_table[phase & 0x1FF] + env]); + } + //if ((channel != 0 && channel != 4) || chan->algorithm != 5) { + phase += mod; + //} + + int16_t output = pow_table[sine_table[phase & 0x1FF] + env]; + if (phase & 0x200) { + output = -output; + } + if (op % 4 == 0) { + chan->op1_old = operator->output; + } else if (op % 4 == 2) { + chan->op2_old = operator->output; + } + operator->output = output; + //Update the channel output if we've updated all operators + if (op % 4 == 3) { + if (chan->algorithm < 4) { + chan->output = operator->output; + } else if(chan->algorithm == 4) { + chan->output = operator->output + context->operators[channel * 4 + 2].output; + } else { + output = 0; + for (uint32_t op = ((chan->algorithm == 7) ? 0 : 1) + channel*4; op < (channel+1)*4; op++) { + output += context->operators[op].output; + } + chan->output = output; + } + if (first_key_on) { + int16_t value = context->channels[channel].output & 0x3FE0; + if (value & 0x2000) { + value |= 0xC000; + } + } + } + //puts("operator update done"); + } +} + +void ym_output_sample(ym2612_context *context) +{ + int16_t left = 0, right = 0; + for (int i = 0; i < NUM_CHANNELS; i++) { + int16_t value = context->channels[i].output; + if (value > 0x1FE0) { + value = 0x1FE0; + } else if (value < -0x1FF0) { + value = -0x1FF0; + } else { + value &= 0x3FE0; + if (value & 0x2000) { + value |= 0xC000; + } + } + if (value >= 0) { + value += context->zero_offset; + } else { + value -= context->zero_offset; + } + if (context->channels[i].logfile) { + fwrite(&value, sizeof(value), 1, context->channels[i].logfile); + } + if (context->channels[i].lr & 0x80) { + left += (value * context->volume_mult) / context->volume_div; + } else if (context->zero_offset) { + if (value >= 0) { + left += (context->zero_offset * context->volume_mult) / context->volume_div; + } else { + left -= (context->zero_offset * context->volume_mult) / context->volume_div; + } + } + if (context->channels[i].lr & 0x40) { + right += (value * context->volume_mult) / context->volume_div; + } else if (context->zero_offset) { + if (value >= 0) { + right += (context->zero_offset * context->volume_mult) / context->volume_div; + } else { + right -= (context->zero_offset * context->volume_mult) / context->volume_div; + } + } + } + render_put_stereo_sample(context->audio, left, right); +} + void ym_run(ym2612_context * context, uint32_t to_cycle) { + if (context->current_cycle >= to_cycle) { + return; + } //printf("Running YM2612 from cycle %d to cycle %d\n", context->current_cycle, to_cycle); //TODO: Fix channel update order OR remap channels in register write for (; context->current_cycle < to_cycle; context->current_cycle += context->clock_inc) { //Update timers at beginning of 144 cycle period if (!context->current_op) { - if (context->timer_control & BIT_TIMERA_ENABLE) { - if (context->timer_a != TIMER_A_MAX) { - context->timer_a++; - if (context->csm_keyon) { - csm_keyoff(context); - } - } else { - if (context->timer_control & BIT_TIMERA_LOAD) { - context->timer_control &= ~BIT_TIMERA_LOAD; - } else if (context->timer_control & BIT_TIMERA_OVEREN) { - context->status |= BIT_STATUS_TIMERA; - } - context->timer_a = context->timer_a_load; - if (!context->csm_keyon && context->ch3_mode == CSM_MODE) { - context->csm_keyon = 0xF0; - uint8_t changes = 0xF0 ^ context->channels[2].keyon;; - for (uint8_t op = 2*4, bit = 0; op < 3*4; op++, bit++) - { - if (changes & keyon_bits[bit]) { - keyon(context->operators + op, context->channels + 2); - } - } - } - } - } - if (!context->sub_timer_b) { - if (context->timer_control & BIT_TIMERB_ENABLE) { - if (context->timer_b != TIMER_B_MAX) { - context->timer_b++; - } else { - if (context->timer_control & BIT_TIMERB_LOAD) { - context->timer_control &= ~BIT_TIMERB_LOAD; - } else if (context->timer_control & BIT_TIMERB_OVEREN) { - context->status |= BIT_STATUS_TIMERB; - } - context->timer_b = context->timer_b_load; - } - } - } - context->sub_timer_b += 0x10; - //Update LFO - if (context->lfo_enable) { - if (context->lfo_counter) { - context->lfo_counter--; - } else { - context->lfo_counter = lfo_timer_values[context->lfo_freq]; - context->lfo_am_step += 2; - context->lfo_am_step &= 0xFE; - context->lfo_pm_step = context->lfo_am_step / 8; - } - } + ym_run_timers(context); } //Update Envelope Generator if (!(context->current_op % 3)) { - uint32_t env_cyc = context->env_counter; uint32_t op = context->current_env_op; ym_operator * operator = context->operators + op; ym_channel * channel = context->channels + op/4; - uint8_t rate; - if (operator->env_phase == PHASE_DECAY && operator->envelope >= operator->sustain_level) { - //operator->envelope = operator->sustain_level; - operator->env_phase = PHASE_SUSTAIN; - } - rate = operator->rates[operator->env_phase]; - if (rate) { - uint8_t ks = channel->keycode >> operator->key_scaling;; - rate = rate*2 + ks; - if (rate > 63) { - rate = 63; - } - } - uint32_t cycle_shift = rate < 0x30 ? ((0x2F - rate) >> 2) : 0; - if (first_key_on) { - dfprintf(debug_file, "Operator: %d, env rate: %d (2*%d+%d), env_cyc: %d, cycle_shift: %d, env_cyc & ((1 << cycle_shift) - 1): %d\n", op, rate, operator->rates[operator->env_phase], channel->keycode >> operator->key_scaling,env_cyc, cycle_shift, env_cyc & ((1 << cycle_shift) - 1)); - } - if (!(env_cyc & ((1 << cycle_shift) - 1))) { - uint32_t update_cycle = env_cyc >> cycle_shift & 0x7; - uint16_t envelope_inc = rate_table[rate * 8 + update_cycle]; - if (operator->env_phase == PHASE_ATTACK) { - //this can probably be optimized to a single shift rather than a multiply + shift - if (first_key_on) { - dfprintf(debug_file, "Changing op %d envelope %d by %d(%d * %d) in attack phase\n", op, operator->envelope, (~operator->envelope * envelope_inc) >> 4, ~operator->envelope, envelope_inc); - } - uint16_t old_env = operator->envelope; - operator->envelope += ((~operator->envelope * envelope_inc) >> 4) & 0xFFFFFFFC; - if (operator->envelope > old_env) { - //Handle overflow - operator->envelope = 0; - } - if (!operator->envelope) { - operator->env_phase = PHASE_DECAY; - } - } else { - if (first_key_on) { - dfprintf(debug_file, "Changing op %d envelope %d by %d in %s phase\n", op, operator->envelope, envelope_inc, - operator->env_phase == PHASE_SUSTAIN ? "sustain" : (operator->env_phase == PHASE_DECAY ? "decay": "release")); - } - if (operator->ssg) { - if (operator->envelope < SSG_CENTER) { - envelope_inc *= 4; - } else { - envelope_inc = 0; - } - } - //envelope value is 10-bits, but it will be used as a 4.8 value - operator->envelope += envelope_inc << 2; - //clamp to max attenuation value - if ( - operator->envelope > MAX_ENVELOPE - || (operator->env_phase == PHASE_RELEASE && operator->envelope >= SSG_CENTER) - ) { - operator->envelope = MAX_ENVELOPE; - } - } - } + ym_run_envelope(context, channel, operator); context->current_env_op++; if (context->current_env_op == NUM_OPERATORS) { context->current_env_op = 0; @@ -468,156 +650,14 @@ } //Update Phase Generator - uint32_t channel = context->current_op / 4; - if (channel != 5 || !context->dac_enable) { - uint32_t op = context->current_op; - //printf("updating operator %d of channel %d\n", op, channel); - ym_operator * operator = context->operators + op; - ym_channel * chan = context->channels + channel; - uint16_t phase = operator->phase_counter >> 10 & 0x3FF; - operator->phase_counter += ym_calc_phase_inc(context, operator, context->current_op); - int16_t mod = 0; - if (op & 3) { - if (operator->mod_src[0]) { - mod = *operator->mod_src[0]; - if (operator->mod_src[1]) { - mod += *operator->mod_src[1]; - } - mod >>= YM_MOD_SHIFT; - } - } else { - if (chan->feedback) { - mod = (chan->op1_old + operator->output) >> (10-chan->feedback); - } - } - uint16_t env = operator->envelope; - if (operator->ssg) { - if (env >= SSG_CENTER) { - if (operator->ssg & SSG_ALTERNATE) { - if (operator->env_phase != PHASE_RELEASE && ( - !(operator->ssg & SSG_HOLD) || ((operator->ssg ^ operator->inverted) & SSG_INVERT) == 0 - )) { - operator->inverted ^= SSG_INVERT; - } - } else if (!(operator->ssg & SSG_HOLD)) { - phase = operator->phase_counter = 0; - } - if ( - (operator->env_phase == PHASE_DECAY || operator->env_phase == PHASE_SUSTAIN) - && !(operator->ssg & SSG_HOLD) - ) { - start_envelope(operator, chan); - env = operator->envelope; - } - } - if (operator->inverted) { - env = (SSG_CENTER - env) & MAX_ENVELOPE; - } - } - env += operator->total_level; - if (operator->am) { - uint16_t base_am = (context->lfo_am_step & 0x80 ? context->lfo_am_step : ~context->lfo_am_step) & 0x7E; - if (ams_shift[chan->ams] >= 0) { - env += (base_am >> ams_shift[chan->ams]) & MAX_ENVELOPE; - } else { - env += base_am << (-ams_shift[chan->ams]); - } - } - if (env > MAX_ENVELOPE) { - env = MAX_ENVELOPE; - } - if (first_key_on) { - dfprintf(debug_file, "op %d, base phase: %d, mod: %d, sine: %d, out: %d\n", op, phase, mod, sine_table[(phase+mod) & 0x1FF], pow_table[sine_table[phase & 0x1FF] + env]); - } - //if ((channel != 0 && channel != 4) || chan->algorithm != 5) { - phase += mod; - //} - - int16_t output = pow_table[sine_table[phase & 0x1FF] + env]; - if (phase & 0x200) { - output = -output; - } - if (op % 4 == 0) { - chan->op1_old = operator->output; - } else if (op % 4 == 2) { - chan->op2_old = operator->output; - } - operator->output = output; - //Update the channel output if we've updated all operators - if (op % 4 == 3) { - if (chan->algorithm < 4) { - chan->output = operator->output; - } else if(chan->algorithm == 4) { - chan->output = operator->output + context->operators[channel * 4 + 2].output; - } else { - output = 0; - for (uint32_t op = ((chan->algorithm == 7) ? 0 : 1) + channel*4; op < (channel+1)*4; op++) { - output += context->operators[op].output; - } - chan->output = output; - } - if (first_key_on) { - int16_t value = context->channels[channel].output & 0x3FE0; - if (value & 0x2000) { - value |= 0xC000; - } - dfprintf(debug_file, "channel %d output: %d\n", channel, (value * context->volume_mult) / context->volume_div); - } - } - //puts("operator update done"); - } + ym_run_phase(context, context->current_op / 4, context->current_op); context->current_op++; if (context->current_op == NUM_OPERATORS) { context->current_op = 0; - - int16_t left = 0, right = 0; - for (int i = 0; i < NUM_CHANNELS; i++) { - int16_t value = context->channels[i].output; - if (value > 0x1FE0) { - value = 0x1FE0; - } else if (value < -0x1FF0) { - value = -0x1FF0; - } else { - value &= 0x3FE0; - if (value & 0x2000) { - value |= 0xC000; - } - } - if (value >= 0) { - value += context->zero_offset; - } else { - value -= context->zero_offset; - } - if (context->channels[i].logfile) { - fwrite(&value, sizeof(value), 1, context->channels[i].logfile); - } - if (context->channels[i].lr & 0x80) { - left += (value * context->volume_mult) / context->volume_div; - } else if (context->zero_offset) { - if (value >= 0) { - left += (context->zero_offset * context->volume_mult) / context->volume_div; - } else { - left -= (context->zero_offset * context->volume_mult) / context->volume_div; - } - } - if (context->channels[i].lr & 0x40) { - right += (value * context->volume_mult) / context->volume_div; - } else if (context->zero_offset) { - if (value >= 0) { - right += (context->zero_offset * context->volume_mult) / context->volume_div; - } else { - right -= (context->zero_offset * context->volume_mult) / context->volume_div; - } - } - } - render_put_stereo_sample(context->audio, left, right); + ym_output_sample(context); } } - if (context->current_cycle >= context->write_cycle + (context->busy_cycles * context->clock_inc / 6)) { - context->status &= 0x7F; - context->write_cycle = CYCLE_NEVER; - } //printf("Done running YM2612 at cycle %d\n", context->current_cycle, to_cycle); } @@ -626,9 +666,6 @@ //printf("address_write_part1: %X\n", address); context->selected_reg = address; context->selected_part = 0; - context->write_cycle = context->current_cycle; - context->busy_cycles = BUSY_CYCLES_ADDRESS; - context->status |= 0x80; } void ym_address_write_part2(ym2612_context * context, uint8_t address) @@ -636,9 +673,6 @@ //printf("address_write_part2: %X\n", address); context->selected_reg = address; context->selected_part = 1; - context->write_cycle = context->current_cycle; - context->busy_cycles = BUSY_CYCLES_ADDRESS; - context->status |= 0x80; } static uint8_t fnum_to_keycode[] = { @@ -745,8 +779,32 @@ return inc; } +void ym_vgm_log(ym2612_context *context, uint32_t master_clock, vgm_writer *vgm) +{ + vgm_ym2612_init(vgm, 6 * master_clock / context->clock_inc); + context->vgm = vgm; + for (uint8_t reg = YM_PART1_START; reg < YM_REG_END; reg++) { + if ((reg >= REG_DETUNE_MULT && (reg & 3) == 3) || (reg >= 0x2D && reg < REG_DETUNE_MULT) || reg == 0x23 || reg == 0x29) { + //skip invalid registers + continue; + } + vgm_ym2612_part1_write(context->vgm, context->current_cycle, reg, context->part1_regs[reg - YM_PART1_START]); + } + + for (uint8_t reg = YM_PART2_START; reg < YM_REG_END; reg++) { + if ((reg & 3) == 3 || (reg >= REG_FNUM_LOW_CH3 && reg < REG_ALG_FEEDBACK)) { + //skip invalid registers + continue; + } + vgm_ym2612_part2_write(context->vgm, context->current_cycle, reg, context->part2_regs[reg - YM_PART2_START]); + } +} + void ym_data_write(ym2612_context * context, uint8_t value) { + context->write_cycle = context->current_cycle; + context->busy_start = context->current_cycle + context->clock_inc; + if (context->selected_reg >= YM_REG_END) { return; } @@ -754,11 +812,17 @@ if (context->selected_reg < YM_PART2_START) { return; } + if (context->vgm) { + vgm_ym2612_part2_write(context->vgm, context->current_cycle, context->selected_reg, value); + } context->part2_regs[context->selected_reg - YM_PART2_START] = value; } else { if (context->selected_reg < YM_PART1_START) { return; } + if (context->vgm) { + vgm_ym2612_part1_write(context->vgm, context->current_cycle, context->selected_reg, value); + } context->part1_regs[context->selected_reg - YM_PART1_START] = value; } dfprintf(debug_file, "write of %X to reg %X in part %d\n", value, context->selected_reg, context->selected_part+1); @@ -773,7 +837,19 @@ }*/ context->lfo_enable = value & 0x8; if (!context->lfo_enable) { + uint8_t old_pm_step = context->lfo_pm_step; context->lfo_am_step = context->lfo_pm_step = 0; + if (old_pm_step) { + for (int chan = 0; chan < NUM_CHANNELS; chan++) + { + if (context->channels[chan].pms) { + for (int op = chan * 4; op < (chan + 1) * 4; op++) + { + context->operators[op].phase_inc = ym_calc_phase_inc(context, context->operators + op, op); + } + } + } + } } context->lfo_freq = value & 0x7; @@ -809,7 +885,14 @@ if (context->ch3_mode == CSM_MODE && (value & 0xC0) != CSM_MODE && context->csm_keyon) { csm_keyoff(context); } + uint8_t old_mode = context->ch3_mode; context->ch3_mode = value & 0xC0; + if (context->ch3_mode != old_mode) { + for (int op = 2 * 4; op < 3*4; op++) + { + context->operators[op].phase_inc = ym_calc_phase_inc(context, context->operators + op, op); + } + } break; } case REG_KEY_ONOFF: { @@ -861,6 +944,7 @@ case REG_DETUNE_MULT: operator->detune = value >> 4 & 0x7; operator->multiple = value & 0xF; + operator->phase_inc = ym_calc_phase_inc(context, operator, op); break; case REG_TOTAL_LEVEL: operator->total_level = (value & 0x7F) << 5; @@ -907,6 +991,10 @@ context->channels[channel].block = context->channels[channel].block_fnum_latch >> 3 & 0x7; context->channels[channel].fnum = (context->channels[channel].block_fnum_latch & 0x7) << 8 | value; context->channels[channel].keycode = context->channels[channel].block << 2 | fnum_to_keycode[context->channels[channel].fnum >> 7]; + for (int op = channel * 4; op < (channel + 1) * 4; op++) + { + context->operators[op].phase_inc = ym_calc_phase_inc(context, context->operators + op, op); + } break; case REG_BLOCK_FNUM_H:{ context->channels[channel].block_fnum_latch = value; @@ -917,6 +1005,10 @@ context->ch3_supp[channel].block = context->ch3_supp[channel].block_fnum_latch >> 3 & 0x7; context->ch3_supp[channel].fnum = (context->ch3_supp[channel].block_fnum_latch & 0x7) << 8 | value; context->ch3_supp[channel].keycode = context->ch3_supp[channel].block << 2 | fnum_to_keycode[context->ch3_supp[channel].fnum >> 7]; + if (context->ch3_mode) { + int op = 2 * 4 + (channel < 2 ? (channel ^ 1) : channel); + context->operators[op].phase_inc = ym_calc_phase_inc(context, context->operators + op, op); + } } break; case REG_BLOCK_FN_CH3: @@ -1042,24 +1134,44 @@ context->channels[channel].feedback = value >> 3 & 0x7; //printf("Algorithm %d, feedback %d for channel %d\n", value & 0x7, value >> 3 & 0x7, channel); break; - case REG_LR_AMS_PMS: + case REG_LR_AMS_PMS: { + uint8_t old_pms = context->channels[channel].pms; context->channels[channel].pms = (value & 0x7) * 32; context->channels[channel].ams = value >> 4 & 0x3; context->channels[channel].lr = value & 0xC0; + if (old_pms != context->channels[channel].pms) { + for (int op = channel * 4; op < (channel + 1) * 4; op++) + { + context->operators[op].phase_inc = ym_calc_phase_inc(context, context->operators + op, op); + } + } //printf("Write of %X to LR_AMS_PMS reg for channel %d\n", value, channel); break; } + } } } - - context->write_cycle = context->current_cycle; - context->busy_cycles = context->selected_reg < 0xA0 ? BUSY_CYCLES_DATA_LOW : BUSY_CYCLES_DATA_HIGH; - context->status |= 0x80; } -uint8_t ym_read_status(ym2612_context * context) +uint8_t ym_read_status(ym2612_context * context, uint32_t cycle, uint32_t port) { - return context->status; + uint8_t status; + port &= context->status_address_mask; + if (port) { + if (context->last_status_cycle != CYCLE_NEVER && cycle - context->last_status_cycle > context->invalid_status_decay) { + context->last_status = 0; + } + status = context->last_status; + } else { + status = context->status; + if (cycle >= context->busy_start && cycle < context->busy_start + context->busy_cycles) { + status |= 0x80; + } + context->last_status = status; + context->last_status_cycle = cycle; + } + return status; + } void ym_print_channel_info(ym2612_context *context, int channel) @@ -1171,7 +1283,10 @@ save_int8(buf, context->selected_part); save_int32(buf, context->current_cycle); save_int32(buf, context->write_cycle); - save_int32(buf, context->busy_cycles); + save_int32(buf, context->busy_start); + save_int32(buf, context->last_status_cycle); + save_int32(buf, context->invalid_status_decay); + save_int8(buf, context->last_status); } void ym_deserialize(deserialize_buffer *buf, void *vcontext) @@ -1246,5 +1361,13 @@ context->selected_part = load_int8(buf); context->current_cycle = load_int32(buf); context->write_cycle = load_int32(buf); - context->busy_cycles = load_int32(buf); + context->busy_start = load_int32(buf); + if (buf->size > buf->cur_pos) { + context->last_status_cycle = load_int32(buf); + context->invalid_status_decay = load_int32(buf); + context->last_status = load_int8(buf); + } else { + context->last_status = context->status; + context->last_status_cycle = context->write_cycle; + } } diff -r 13abdc98379e -r 374a5ae694e8 ym2612.h --- a/ym2612.h Thu Apr 18 22:06:47 2019 -0700 +++ b/ym2612.h Sat Apr 18 11:42:53 2020 -0700 @@ -9,17 +9,20 @@ #include #include #include "serialize.h" -#include "render.h" +#include "render_audio.h" +#include "vgm.h" #define NUM_PART_REGS (0xB7-0x30) #define NUM_CHANNELS 6 #define NUM_OPERATORS (4*NUM_CHANNELS) #define YM_OPT_WAVE_LOG 1 +#define YM_OPT_3834 2 typedef struct { int16_t *mod_src[2]; uint32_t phase_counter; + uint32_t phase_inc; uint16_t envelope; int16_t output; uint16_t total_level; @@ -66,11 +69,15 @@ typedef struct { audio_source *audio; + vgm_writer *vgm; uint32_t clock_inc; uint32_t current_cycle; - //TODO: Condense the next two fields into one uint32_t write_cycle; + uint32_t busy_start; uint32_t busy_cycles; + uint32_t last_status_cycle; + uint32_t invalid_status_decay; + uint32_t status_address_mask; int32_t volume_mult; int32_t volume_div; ym_operator operators[NUM_OPERATORS]; @@ -96,6 +103,7 @@ uint8_t lfo_pm_step; uint8_t csm_keyon; uint8_t status; + uint8_t last_status; uint8_t selected_reg; uint8_t selected_part; uint8_t part1_regs[YM_PART1_REGS]; @@ -133,11 +141,13 @@ void ym_free(ym2612_context *context); void ym_enable_zero_offset(ym2612_context *context, uint8_t enabled); void ym_adjust_master_clock(ym2612_context * context, uint32_t master_clock); +void ym_adjust_cycles(ym2612_context *context, uint32_t deduction); void ym_run(ym2612_context * context, uint32_t to_cycle); void ym_address_write_part1(ym2612_context * context, uint8_t address); void ym_address_write_part2(ym2612_context * context, uint8_t address); void ym_data_write(ym2612_context * context, uint8_t value); -uint8_t ym_read_status(ym2612_context * context); +void ym_vgm_log(ym2612_context *context, uint32_t master_clock, vgm_writer *vgm); +uint8_t ym_read_status(ym2612_context * context, uint32_t cycle, uint32_t port); uint8_t ym_load_gst(ym2612_context * context, FILE * gstfile); uint8_t ym_save_gst(ym2612_context * context, FILE * gstfile); void ym_print_channel_info(ym2612_context *context, int channel); diff -r 13abdc98379e -r 374a5ae694e8 z80_to_x86.c --- a/z80_to_x86.c Thu Apr 18 22:06:47 2019 -0700 +++ b/z80_to_x86.c Sat Apr 18 11:42:53 2020 -0700 @@ -2327,7 +2327,7 @@ if (inst->addr_mode == Z80_IMMED_INDIRECT) { mov_ir(code, inst->immed, opts->gen.scratch1, SZ_B); } else { - zreg_to_native(opts, Z80_C, opts->gen.scratch2); + zreg_to_native(opts, Z80_C, opts->gen.scratch1); } call(code, opts->read_io); if (inst->addr_mode != Z80_IMMED_INDIRECT) { @@ -3413,6 +3413,9 @@ add_rdispr(code, options->gen.context_reg, offsetof(z80_context, target_cycle), options->gen.cycles, SZ_D); cmp_rdispr(code, options->gen.context_reg, offsetof(z80_context, int_cycle), options->gen.cycles, SZ_D); jcc(code, CC_B, skip_int); + //check that we are not past the end of interrupt pulse + cmp_rrdisp(code, options->gen.cycles, options->gen.context_reg, offsetof(z80_context, int_pulse_end), SZ_D); + jcc(code, CC_B, skip_int); //set limit to the cycle limit mov_rdispr(code, options->gen.context_reg, offsetof(z80_context, sync_cycle), options->gen.scratch2, SZ_D); mov_rrdisp(code, options->gen.scratch2, options->gen.context_reg, offsetof(z80_context, target_cycle), SZ_D); diff -r 13abdc98379e -r 374a5ae694e8 z80inst.c --- a/z80inst.c Thu Apr 18 22:06:47 2019 -0700 +++ b/z80inst.c Sat Apr 18 11:42:53 2020 -0700 @@ -1570,7 +1570,7 @@ uint8_t z80_is_terminal(z80inst * inst) { return inst->op == Z80_RET || inst->op == Z80_RETI || inst->op == Z80_RETN || inst->op == Z80_JP - || inst->op == Z80_JR || inst->op == Z80_HALT || (inst->op == Z80_NOP && inst->immed == 42); + || inst->op == Z80_JR || (inst->op == Z80_NOP && inst->immed == 42); } diff -r 13abdc98379e -r 374a5ae694e8 ztestrun.c --- a/ztestrun.c Thu Apr 18 22:06:47 2019 -0700 +++ b/ztestrun.c Sat Apr 18 11:42:53 2020 -0700 @@ -4,7 +4,7 @@ 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 "z80inst.h" -#ifndef USE_NATIVE +#ifdef NEW_CORE #include "z80.h" #include #else @@ -47,7 +47,7 @@ { 0x0000, 0x100, 0xFF, 0, 0, 0, NULL, NULL, NULL, z80_unmapped_read, z80_unmapped_write} }; -#ifdef USE_NATIVE +#ifndef NEW_CORE void z80_next_int_pulse(z80_context * context) { context->int_pulse_start = context->int_pulse_end = CYCLE_NEVER; @@ -98,7 +98,7 @@ fclose(f); init_z80_opts(&opts, z80_map, 2, port_map, 1, 1, 0xFF); context = init_z80_context(&opts); -#ifndef USE_NATIVE +#ifdef NEW_CORE z80_execute(context, 1000); printf("A: %X\nB: %X\nC: %X\nD: %X\nE: %X\nHL: %X\nIX: %X\nIY: %X\nSP: %X\n\nIM: %d, IFF1: %d, IFF2: %d\n", context->main[7], context->main[0], context->main[1],