diff --git a/examples/common/CMakeLists.txt b/examples/common/CMakeLists.txt index 5817661..63f4445 100644 --- a/examples/common/CMakeLists.txt +++ b/examples/common/CMakeLists.txt @@ -36,7 +36,8 @@ fips_begin_lib(common) fs.c fs.h gfx.c gfx.h keybuf.c keybuf.h - prof.c prof.h) + prof.c prof.h + webapi.c webapi.h) sokol_shader(shaders.glsl ${slang}) if (FIPS_OSX) fips_files(sokol.m) @@ -65,4 +66,8 @@ fips_end_lib() # a separate library with just keybuf (for the ASCII emulators) fips_begin_lib(keybuf) fips_files(keybuf.c keybuf.h) +fips_end_lib() + +fips_begin_lib(webapi) + fips_files(webapi.c webapi.h) fips_end_lib() \ No newline at end of file diff --git a/examples/common/common.h b/examples/common/common.h index 71b18d1..552629f 100644 --- a/examples/common/common.h +++ b/examples/common/common.h @@ -11,4 +11,5 @@ #include "fs.h" #include "gfx.h" #include "keybuf.h" +#include "webapi.h" #include // isupper, islower, toupper, tolower diff --git a/examples/common/fs.c b/examples/common/fs.c index a784711..2222484 100644 --- a/examples/common/fs.c +++ b/examples/common/fs.c @@ -469,7 +469,6 @@ EM_JS(void, fs_js_load_snapshot, (const char* system_name_cstr, int snapshot_ind const db_name = 'chips'; const db_store_name = 'store'; const system_name = UTF8ToString(system_name_cstr); - console.log('fs_js_load_snapshot: called with', system_name, snapshot_index); let open_request; try { open_request = window.indexedDB.open(db_name, 1); @@ -501,7 +500,6 @@ EM_JS(void, fs_js_load_snapshot, (const char* system_name_cstr, int snapshot_ind HEAPU8.set(get_request.result, ptr); _fs_emsc_load_snapshot_callback(context, ptr, num_bytes); } else { - console.log('fs_js_load_snapshot:', key, 'does not exist'); _fs_emsc_load_snapshot_callback(context, 0, 0); } }; diff --git a/examples/common/gfx.c b/examples/common/gfx.c index 0f5a95b..508dc6b 100644 --- a/examples/common/gfx.c +++ b/examples/common/gfx.c @@ -16,6 +16,7 @@ typedef struct { bool valid; + bool disable_speaker_icon; gfx_border_t border; struct { sg_image img; // framebuffer texture, RGBA8 or R8 if paletted @@ -151,6 +152,11 @@ void gfx_flash_error(void) { state.flash_error_count = 20; } +void gfx_disable_speaker_icon(void) { + assert(state.valid); + state.disable_speaker_icon = true; +} + sg_image gfx_create_icon_texture(const uint8_t* packed_pixels, int width, int height, int stride) { const size_t pixel_data_size = width * height * sizeof(uint32_t); uint32_t* pixels = malloc(pixel_data_size); @@ -256,6 +262,7 @@ void gfx_init(const gfx_desc_t* desc) { }); state.valid = true; + state.disable_speaker_icon = desc->disable_speaker_icon; state.border = desc->border; state.display.portrait = desc->display_info.portrait; state.draw_extra_cb = desc->draw_extra_cb; @@ -413,7 +420,7 @@ void gfx_draw(chips_display_info_t display_info) { } // if audio is off, draw speaker icon via sokol-gl - if (saudio_suspended()) { + if (!state.disable_speaker_icon && saudio_suspended()) { const float x0 = display.width - (float)state.icon.dim.width - 10.0f; const float x1 = x0 + (float)state.icon.dim.width; const float y0 = 10.0f; diff --git a/examples/common/gfx.h b/examples/common/gfx.h index aa58203..3746ca6 100644 --- a/examples/common/gfx.h +++ b/examples/common/gfx.h @@ -21,6 +21,7 @@ typedef struct { } gfx_border_t; typedef struct { + bool disable_speaker_icon; gfx_border_t border; chips_display_info_t display_info; chips_dim_t pixel_aspect; // optional pixel aspect ratio, default is 1:1 @@ -32,6 +33,7 @@ void gfx_draw(chips_display_info_t display_info); void gfx_shutdown(void); void gfx_flash_success(void); void gfx_flash_error(void); +void gfx_disable_speaker_icon(void); sg_image gfx_create_icon_texture(const uint8_t* packed_pixels, int width, int height, int stride); #ifdef __cplusplus diff --git a/examples/common/shell.html b/examples/common/shell.html index 1ac1d53..6983155 100644 --- a/examples/common/shell.html +++ b/examples/common/shell.html @@ -47,12 +47,8 @@ canvas.addEventListener("webglcontextlost", function(e) { alert('FIXME: WebGL context lost, please reload the page'); e.preventDefault(); }, false); return canvas; })(), - setStatus: function(text) { - console.log("status: " + text); - }, - monitorRunDependencies: function(left) { - console.log("monitor run deps: " + left); - } + setStatus: function(text) { }, + monitorRunDependencies: function(left) { }, }; window.onerror = function() { console.log("onerror: " + event); diff --git a/examples/common/webapi.c b/examples/common/webapi.c new file mode 100644 index 0000000..737c8dc --- /dev/null +++ b/examples/common/webapi.c @@ -0,0 +1,223 @@ +#include "webapi.h" +#include +#include +#if defined(__EMSCRIPTEN__) +#include +#endif +#include "gfx.h" + +static struct { + bool dbg_connect_requested; +} before_init_state; + +static struct { + bool inited; + webapi_interface_t funcs; +} state; + +void webapi_init(const webapi_desc_t* desc) { + assert(desc); + state.inited = true; + state.funcs = desc->funcs; + if (before_init_state.dbg_connect_requested && state.funcs.dbg_connect) { + state.funcs.dbg_connect(); + } +} + +#if defined(__EMSCRIPTEN__) + +EM_JS(void, webapi_js_event_stopped, (int stop_reason, uint16_t addr), { + console.log("webapi_js_event_stopped()"); + if (Module["webapi_onStopped"]) { + Module["webapi_onStopped"](stop_reason, addr); + } else { + console.log("no Module.webapi_onStopped function"); + } +}); + +EM_JS(void, webapi_js_event_continued, (), { + console.log("webapi_js_event_continued()"); + if (Module["webapi_onContinued"]) { + Module["webapi_onContinued"](); + } else { + console.log("no Module.webapi_onContinued function"); + } +}); + +EM_JS(void, webapi_js_event_reboot, (), { + console.log("webapi_js_event_reboot()"); + if (Module["webapi_onReboot"]) { + Module["webapi_onReboot"](); + } else { + console.log("no Module.webapi_onReboot function"); + } +}); + +EM_JS(void, webapi_js_event_reset, (), { + console.log("webapi_js_event_reset()"); + if (Module["webapi_onReset"]) { + Module["webapi_onReset"](); + } else { + console.log("no Module.webapi_onReset function"); + } +}); + +EMSCRIPTEN_KEEPALIVE void webapi_dbg_connect(void) { + if (state.inited) { + if (state.funcs.dbg_connect) { + state.funcs.dbg_connect(); + } + } else { + before_init_state.dbg_connect_requested = true; + } +} + +EMSCRIPTEN_KEEPALIVE void webapi_dbg_disconnect(void) { + if (state.inited && state.funcs.dbg_disconnect) { + state.funcs.dbg_disconnect(); + } +} + +EMSCRIPTEN_KEEPALIVE void* webapi_alloc(int size) { + return malloc((size_t)size); +} + +EMSCRIPTEN_KEEPALIVE void webapi_free(void* ptr) { + if (ptr) { + free(ptr); + } +} + +EMSCRIPTEN_KEEPALIVE void webapi_boot(void) { + if (state.inited && state.funcs.boot) { + state.funcs.boot(); + } +} + +EMSCRIPTEN_KEEPALIVE void webapi_reset(void) { + if (state.inited && state.funcs.reset) { + state.funcs.reset(); + } +} + +EMSCRIPTEN_KEEPALIVE bool webapi_ready(void) { + if (state.inited && state.funcs.ready) { + return state.funcs.ready(); + } else { + return false; + } +} + +EMSCRIPTEN_KEEPALIVE bool webapi_load(void* ptr, int size) { + if (state.inited && state.funcs.load && ptr && ((size_t)size > sizeof(webapi_fileheader_t))) { + const webapi_fileheader_t* hdr = (webapi_fileheader_t*)ptr; + if ((hdr->magic[0] != 'C') || (hdr->magic[1] != 'H') || (hdr->magic[2] != 'I') || (hdr->magic[3] != 'P')) { + return false; + } + return state.funcs.load((chips_range_t){ .ptr = ptr, .size = (size_t)size }); + } + return false; +} + +EMSCRIPTEN_KEEPALIVE void webapi_dbg_add_breakpoint(uint16_t addr) { + if (state.inited && state.funcs.dbg_add_breakpoint) { + state.funcs.dbg_add_breakpoint(addr); + } +} + +EMSCRIPTEN_KEEPALIVE void webapi_dbg_remove_breakpoint(uint16_t addr) { + if (state.inited && state.funcs.dbg_remove_breakpoint) { + state.funcs.dbg_remove_breakpoint(addr); + } +} + +EMSCRIPTEN_KEEPALIVE void webapi_dbg_break(void) { + if (state.inited && state.funcs.dbg_break) { + state.funcs.dbg_break(); + } +} + +EMSCRIPTEN_KEEPALIVE void webapi_dbg_continue(void) { + if (state.inited && state.funcs.dbg_continue) { + state.funcs.dbg_continue(); + } +} + +EMSCRIPTEN_KEEPALIVE void webapi_dbg_step_next(void) { + if (state.inited && state.funcs.dbg_step_next) { + state.funcs.dbg_step_next(); + } +} + +EMSCRIPTEN_KEEPALIVE void webapi_dbg_step_into(void) { + if (state.inited && state.funcs.dbg_step_into) { + state.funcs.dbg_step_into(); + } +} + +// return emulator state as JSON-formatted string pointer into WASM heap +EMSCRIPTEN_KEEPALIVE uint16_t* webapi_dbg_cpu_state(void) { + static webapi_cpu_state_t res; + if (state.inited && state.funcs.dbg_cpu_state) { + res = state.funcs.dbg_cpu_state(); + } else { + memset(&res, 0, sizeof(res)); + } + return &res.items[0]; +} + +// request a disassembly, returns ptr to heap-allocated array of 'num_lines' webapi_dasm_line_t structs which must be freed with webapi_free() +// NOTE: may return 0! +EMSCRIPTEN_KEEPALIVE webapi_dasm_line_t* webapi_dbg_request_disassembly(uint16_t addr, int offset_lines, int num_lines) { + if (num_lines <= 0) { + return 0; + } + if (state.inited && state.funcs.dbg_request_disassembly) { + webapi_dasm_line_t* out_lines = calloc((size_t)num_lines, sizeof(webapi_dasm_line_t)); + state.funcs.dbg_request_disassembly(addr, offset_lines, num_lines, out_lines); + return out_lines; + } else { + return 0; + } +} + +// reads a memory chunk, returns heap-allocated buffer which must be freed with webapi_free() +// NOTE: may return 0! +EMSCRIPTEN_KEEPALIVE uint8_t* webapi_dbg_read_memory(uint16_t addr, int num_bytes) { + if (state.inited && state.funcs.dbg_read_memory) { + uint8_t* ptr = calloc((size_t)num_bytes, 1); + state.funcs.dbg_read_memory(addr, num_bytes, ptr); + return ptr; + } else { + return 0; + } +} + +#endif // __EMSCRIPTEN__ + +// stop_reason is UI_DBG_STOP_REASON_xxx +void webapi_event_stopped(int stop_reason, uint16_t addr) { + #if defined(__EMSCRIPTEN__) + webapi_js_event_stopped(stop_reason, addr); + #else + (void)stop_reason; (void)addr; + #endif +} + +void webapi_event_continued(void) { + #if defined(__EMSCRIPTEN__) + webapi_js_event_continued(); + #endif +} + +void webapi_event_reboot(void) { + #if defined(__EMSCRIPTEN__) + webapi_js_event_reboot(); + #endif +} + +void webapi_event_reset(void) { + #if defined(__EMSCRIPTEN__) + webapi_js_event_reset(); + #endif +} \ No newline at end of file diff --git a/examples/common/webapi.h b/examples/common/webapi.h new file mode 100644 index 0000000..ffc04c3 --- /dev/null +++ b/examples/common/webapi.h @@ -0,0 +1,102 @@ +#pragma once +#include +#include +#include "chips/chips_common.h" + +#define WEBAPI_STOPREASON_UNKNOWN (0) +#define WEBAPI_STOPREASON_BREAK (1) +#define WEBAPI_STOPREASON_BREAKPOINT (2) +#define WEBAPI_STOPREASON_STEP (3) +#define WEBAPI_STOPREASON_ENTRY (4) +#define WEBAPI_STOPREASON_EXIT (5) + +#define WEBAPI_CPUTYPE_INVALID (0) +#define WEBAPI_CPUTYPE_Z80 (1) +#define WEBAPI_CPUTYPE_6502 (2) + +#define WEBAPI_CPUSTATE_TYPE (0) + +#define WEBAPI_CPUSTATE_Z80_AF (1) +#define WEBAPI_CPUSTATE_Z80_BC (2) +#define WEBAPI_CPUSTATE_Z80_DE (3) +#define WEBAPI_CPUSTATE_Z80_HL (4) +#define WEBAPI_CPUSTATE_Z80_IX (5) +#define WEBAPI_CPUSTATE_Z80_IY (6) +#define WEBAPI_CPUSTATE_Z80_SP (7) +#define WEBAPI_CPUSTATE_Z80_PC (8) +#define WEBAPI_CPUSTATE_Z80_AF2 (9) +#define WEBAPI_CPUSTATE_Z80_BC2 (10) +#define WEBAPI_CPUSTATE_Z80_DE2 (11) +#define WEBAPI_CPUSTATE_Z80_HL2 (12) +#define WEBAPI_CPUSTATE_Z80_IM (13) +#define WEBAPI_CPUSTATE_Z80_IR (14) +#define WEBAPI_CPUSTATE_Z80_IFF (15) // bit0: iff1, bit2: iff2 + +#define WEBAPI_CPUSTATE_6502_A (1) +#define WEBAPI_CPUSTATE_6502_X (2) +#define WEBAPI_CPUSTATE_6502_Y (3) +#define WEBAPI_CPUSTATE_6502_S (4) +#define WEBAPI_CPUSTATE_6502_P (5) +#define WEBAPI_CPUSTATE_6502_PC (6) + +#define WEBAPI_CPUSTATE_MAX (16) + +typedef struct webapi_cpu_state_t { + uint16_t items[WEBAPI_CPUSTATE_MAX]; +} webapi_cpu_state_t; + +#define WEBAPI_DASM_LINE_MAX_BYTES (8) +#define WEBAPI_DASM_LINE_MAX_CHARS (32) + +typedef struct { + uint16_t addr; + uint8_t num_bytes; + uint8_t num_chars; + uint8_t bytes[WEBAPI_DASM_LINE_MAX_BYTES]; + char chars[WEBAPI_DASM_LINE_MAX_CHARS]; +} webapi_dasm_line_t; + +// the webapi_load() function takes a byte blob which is a container +// format for emulator-specific quickload fileformats with an additional +// 16-byte header with meta-information +typedef struct { + uint8_t magic[4]; // 'CHIP' + uint8_t type[4]; // 'KCC ', 'PRG ', etc... + uint8_t start_addr_lo; // execution starts here + uint8_t start_addr_hi; + uint8_t flags; + uint8_t reserved[5]; + uint8_t payload[]; +} webapi_fileheader_t; + +#define WEBAPI_FILEHEADER_FLAG_START (1<<0) // if set, start automatically +#define WEBAPI_FILEHEADER_FLAG_STOPONENTRY (1<<1) // if set, stop execution on entry + +typedef struct { + void (*boot)(void); + void (*reset)(void); + bool (*ready)(void); + bool (*load)(chips_range_t data); // data starts with a webapi_fileheader_t + void (*dbg_connect)(void); + void (*dbg_disconnect)(void); + void (*dbg_add_breakpoint)(uint16_t addr); + void (*dbg_remove_breakpoint)(uint16_t addr); + void (*dbg_break)(void); + void (*dbg_continue)(void); + void (*dbg_step_next)(void); + void (*dbg_step_into)(void); + webapi_cpu_state_t (*dbg_cpu_state)(void); + void (*dbg_request_disassembly)(uint16_t addr, int offset_lines, int num_lines, webapi_dasm_line_t* dst_lines); + void (*dbg_read_memory)(uint16_t addr, int num_bytes, uint8_t* dst_ptr); +} webapi_interface_t; + +typedef struct { + webapi_interface_t funcs; +} webapi_desc_t; + +void webapi_init(const webapi_desc_t* desc); +// stop_reason: WEBAPI_STOPREASON_xxx +void webapi_event_stopped(int stop_reason, uint16_t addr); +void webapi_event_continued(void); +void webapi_event_reboot(void); +void webapi_event_reset(void); \ No newline at end of file diff --git a/examples/sokol/atom.c b/examples/sokol/atom.c index 28015e4..0ee3d22 100644 --- a/examples/sokol/atom.c +++ b/examples/sokol/atom.c @@ -104,6 +104,7 @@ void app_init(void) { atom_desc_t desc = atom_desc(joy_type); atom_init(&state.atom, &desc); gfx_init(&(gfx_desc_t) { + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif diff --git a/examples/sokol/bombjack.c b/examples/sokol/bombjack.c index 42e0355..7bbae0b 100644 --- a/examples/sokol/bombjack.c +++ b/examples/sokol/bombjack.c @@ -91,6 +91,7 @@ static void app_init(void) { #endif }); gfx_init(&(gfx_desc_t) { + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif @@ -276,7 +277,7 @@ static void ui_load_snapshots_from_storage(void) { #endif sapp_desc sokol_main(int argc, char* argv[]) { - (void)argc; (void)argv; + sargs_setup(&(sargs_desc){ .argc=argc, .argv=argv }); const chips_display_info_t info = bombjack_display_info(0); return (sapp_desc) { .init_cb = app_init, diff --git a/examples/sokol/c64.c b/examples/sokol/c64.c index 1527a24..407579d 100644 --- a/examples/sokol/c64.c +++ b/examples/sokol/c64.c @@ -34,6 +34,7 @@ #include "ui/ui_snapshot.h" #include "ui/ui_c64.h" #endif +#include typedef struct { uint32_t version; @@ -47,6 +48,10 @@ static struct { double emu_time_ms; #ifdef CHIPS_USE_UI ui_c64_t ui; + struct { + uint32_t entry_addr; + uint32_t exit_addr; + } dbg; c64_snapshot_t snapshots[UI_SNAPSHOT_MAX_SLOTS]; #endif } state; @@ -57,6 +62,25 @@ static void ui_boot_cb(c64_t* sys); static void ui_save_snapshot(size_t slot_index); static bool ui_load_snapshot(size_t slot_index); static void ui_load_snapshots_from_storage(void); +static void web_boot(void); +static void web_reset(void); +static bool web_ready(void); +static bool web_load(chips_range_t data); +static void web_dbg_connect(void); +static void web_dbg_disconnect(void); +static void web_dbg_add_breakpoint(uint16_t addr); +static void web_dbg_remove_breakpoint(uint16_t addr); +static void web_dbg_break(void); +static void web_dbg_continue(void); +static void web_dbg_step_next(void); +static void web_dbg_step_into(void); +static void web_dbg_on_stopped(int stop_reason, uint16_t addr); +static void web_dbg_on_continued(void); +static void web_dbg_on_reboot(void); +static void web_dbg_on_reset(void); +static webapi_cpu_state_t web_dbg_cpu_state(void); +static void web_dbg_request_disassemly(uint16_t addr, int offset_lines, int num_lines, webapi_dasm_line_t* result); +static void web_dbg_read_memory(uint16_t addr, int num_bytes, uint8_t* dst_ptr); #define BORDER_TOP (24) #else #define BORDER_TOP (8) @@ -64,6 +88,7 @@ static void ui_load_snapshots_from_storage(void); #define BORDER_LEFT (8) #define BORDER_RIGHT (8) #define BORDER_BOTTOM (16) +#define LOAD_DELAY_FRAMES (180) // audio-streaming callback static void push_audio(const float* samples, int num_samples, void* user_data) { @@ -101,11 +126,9 @@ void app_init(void) { if (sargs_exists("joystick")) { if (sargs_equals("joystick", "digital_1")) { joy_type = C64_JOYSTICKTYPE_DIGITAL_1; - } - else if (sargs_equals("joystick", "digital_2")) { + } else if (sargs_equals("joystick", "digital_2")) { joy_type = C64_JOYSTICKTYPE_DIGITAL_2; - } - else if (sargs_equals("joystick", "digital_12")) { + } else if (sargs_equals("joystick", "digital_12")) { joy_type = C64_JOYSTICKTYPE_DIGITAL_12; } } @@ -114,6 +137,7 @@ void app_init(void) { c64_desc_t desc = c64_desc(joy_type, c1530_enabled, c1541_enabled); c64_init(&state.c64, &desc); gfx_init(&(gfx_desc_t){ + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif @@ -142,6 +166,12 @@ void app_init(void) { .update_cb = ui_update_texture, .destroy_cb = ui_destroy_texture, }, + .dbg_debug = { + .reboot_cb = web_dbg_on_reboot, + .reset_cb = web_dbg_on_reset, + .stopped_cb = web_dbg_on_stopped, + .continued_cb = web_dbg_on_continued, + }, .snapshot = { .load_cb = ui_load_snapshot, .save_cb = ui_save_snapshot, @@ -159,6 +189,26 @@ void app_init(void) { } }); ui_load_snapshots_from_storage(); + // important: initialize webapi after ui + webapi_init(&(webapi_desc_t){ + .funcs = { + .boot = web_boot, + .reset = web_reset, + .ready = web_ready, + .load = web_load, + .dbg_connect = web_dbg_connect, + .dbg_disconnect = web_dbg_disconnect, + .dbg_add_breakpoint = web_dbg_add_breakpoint, + .dbg_remove_breakpoint = web_dbg_remove_breakpoint, + .dbg_break = web_dbg_break, + .dbg_continue = web_dbg_continue, + .dbg_step_next = web_dbg_step_next, + .dbg_step_into = web_dbg_step_into, + .dbg_cpu_state = web_dbg_cpu_state, + .dbg_request_disassembly = web_dbg_request_disassemly, + .dbg_read_memory = web_dbg_read_memory, + } + }); #endif bool delay_input = false; if (sargs_exists("file")) { @@ -210,8 +260,7 @@ void app_input(const sapp_event* event) { // need to invert case (unshifted is upper caps, shifted is lower caps if (isupper(c)) { c = tolower(c); - } - else if (islower(c)) { + } else if (islower(c)) { c = toupper(c); } c64_key_down(&state.c64, c); @@ -242,8 +291,7 @@ void app_input(const sapp_event* event) { if (c) { if (event->type == SAPP_EVENTTYPE_KEY_DOWN) { c64_key_down(&state.c64, c); - } - else { + } else { c64_key_up(&state.c64, c); } } @@ -278,17 +326,15 @@ static void send_keybuf_input(void) { static void handle_file_loading(void) { fs_dowork(); - const uint32_t load_delay_frames = 180; + const uint32_t load_delay_frames = LOAD_DELAY_FRAMES; if (fs_success(FS_SLOT_IMAGE) && clock_frame_count_60hz() > load_delay_frames) { bool load_success = false; if (fs_ext(FS_SLOT_IMAGE, "txt") || fs_ext(FS_SLOT_IMAGE, "bas")) { load_success = true; keybuf_put((const char*)fs_data(FS_SLOT_IMAGE).ptr); - } - else if (fs_ext(FS_SLOT_IMAGE, "tap")) { + } else if (fs_ext(FS_SLOT_IMAGE, "tap")) { load_success = c64_insert_tape(&state.c64, fs_data(FS_SLOT_IMAGE)); - } - else if (fs_ext(FS_SLOT_IMAGE, "bin") || fs_ext(FS_SLOT_IMAGE, "prg") || fs_ext(FS_SLOT_IMAGE, "")) { + } else if (fs_ext(FS_SLOT_IMAGE, "bin") || fs_ext(FS_SLOT_IMAGE, "prg") || fs_ext(FS_SLOT_IMAGE, "")) { load_success = c64_quickload(&state.c64, fs_data(FS_SLOT_IMAGE)); } if (load_success) { @@ -301,16 +347,13 @@ static void handle_file_loading(void) { if (!sargs_exists("debug")) { if (sargs_exists("input")) { keybuf_put(sargs_value("input")); - } - else if (fs_ext(FS_SLOT_IMAGE, "tap")) { - keybuf_put("LOAD\n"); - } - else if (fs_ext(FS_SLOT_IMAGE, "prg")) { - keybuf_put("RUN\n"); + } else if (fs_ext(FS_SLOT_IMAGE, "tap")) { + c64_basic_load(&state.c64); + } else if (fs_ext(FS_SLOT_IMAGE, "prg")) { + c64_basic_run(&state.c64); } } - } - else { + } else { gfx_flash_error(); } fs_reset(FS_SLOT_IMAGE); @@ -332,7 +375,9 @@ static void draw_status_bar(void) { static void ui_draw_cb(void) { ui_c64_draw(&state.ui); } + static void ui_boot_cb(c64_t* sys) { + clock_init(); c64_desc_t desc = c64_desc(sys->joystick_type, sys->c1530.valid, sys->c1541.valid); c64_init(sys, &desc); } @@ -385,6 +430,158 @@ static void ui_load_snapshots_from_storage(void) { fs_start_load_snapshot(FS_SLOT_SNAPSHOTS, "c64", snapshot_slot, ui_fetch_snapshot_callback); } } + +// webapi wrappers +static void web_boot(void) { + clock_init(); + c64_desc_t desc = c64_desc(state.c64.joystick_type, state.c64.c1530.valid, state.c64.c1541.valid); + c64_init(&state.c64, &desc); + ui_dbg_reboot(&state.ui.dbg); +} + +static void web_reset(void) { + c64_reset(&state.c64); + ui_dbg_reset(&state.ui.dbg); +} + +static void web_dbg_connect(void) { + gfx_disable_speaker_icon(); + state.dbg.entry_addr = 0xFFFFFFFF; + state.dbg.exit_addr = 0xFFFFFFFF; + ui_dbg_external_debugger_connected(&state.ui.dbg); +} + +static void web_dbg_disconnect(void) { + state.dbg.entry_addr = 0xFFFFFFFF; + state.dbg.exit_addr = 0xFFFFFFFF; + ui_dbg_external_debugger_disconnected(&state.ui.dbg); +} + +static bool web_ready(void) { + return clock_frame_count_60hz() > LOAD_DELAY_FRAMES; +} + +static bool web_load(chips_range_t data) { + if (data.size <= sizeof(webapi_fileheader_t)) { + return false; + } + const webapi_fileheader_t* hdr = (webapi_fileheader_t*)data.ptr; + if ((hdr->type[0] != 'P') || (hdr->type[1] != 'R') || (hdr->type[2] != 'G') || (hdr->type[3] != ' ')) { + return false; + } + const uint16_t start_addr = (hdr->start_addr_hi<<8) | hdr->start_addr_lo; + const chips_range_t prg = { .ptr = (void*)&hdr->payload, .size = data.size - sizeof(webapi_fileheader_t) }; + bool loaded = c64_quickload(&state.c64, prg); + if (loaded) { + state.dbg.entry_addr = start_addr; + state.dbg.exit_addr = c64_syscall_return_addr(); + ui_dbg_add_breakpoint(&state.ui.dbg, state.dbg.entry_addr); + ui_dbg_add_breakpoint(&state.ui.dbg, state.dbg.exit_addr); + // if debugger is stopped, unstuck it + if (ui_dbg_stopped(&state.ui.dbg)) { + ui_dbg_continue(&state.ui.dbg, false); + } + // execute a SYS start_addr + c64_basic_syscall(&state.c64, start_addr); + } + return loaded; +} + +static void web_dbg_add_breakpoint(uint16_t addr) { + ui_dbg_add_breakpoint(&state.ui.dbg, addr); +} + +static void web_dbg_remove_breakpoint(uint16_t addr) { + ui_dbg_remove_breakpoint(&state.ui.dbg, addr); +} + +static void web_dbg_break(void) { + ui_dbg_break(&state.ui.dbg); +} + +static void web_dbg_continue(void) { + ui_dbg_continue(&state.ui.dbg, false); +} + +static void web_dbg_step_next(void) { + ui_dbg_step_next(&state.ui.dbg); +} + +static void web_dbg_step_into(void) { + ui_dbg_step_into(&state.ui.dbg); +} + +static void web_dbg_on_stopped(int stop_reason, uint16_t addr) { + // stopping on the entry or exit breakpoints always + // overrides the incoming stop_reason + int webapi_stop_reason = WEBAPI_STOPREASON_UNKNOWN; + if (state.dbg.entry_addr == state.c64.cpu.PC) { + webapi_stop_reason = WEBAPI_STOPREASON_ENTRY; + } else if (state.dbg.exit_addr == state.c64.cpu.PC) { + webapi_stop_reason = WEBAPI_STOPREASON_EXIT; + } else if (stop_reason == UI_DBG_STOP_REASON_BREAK) { + webapi_stop_reason = WEBAPI_STOPREASON_BREAK; + } else if (stop_reason == UI_DBG_STOP_REASON_STEP) { + webapi_stop_reason = WEBAPI_STOPREASON_STEP; + } else if (stop_reason == UI_DBG_STOP_REASON_BREAKPOINT) { + webapi_stop_reason = WEBAPI_STOPREASON_BREAKPOINT; + } + webapi_event_stopped(webapi_stop_reason, addr); +} + +static void web_dbg_on_continued(void) { + webapi_event_continued(); +} + +static void web_dbg_on_reboot(void) { + webapi_event_reboot(); +} + +static void web_dbg_on_reset(void) { + webapi_event_reset(); +} + +static webapi_cpu_state_t web_dbg_cpu_state(void) { + const m6502_t* cpu = &state.c64.cpu; + return (webapi_cpu_state_t){ + .items = { + [WEBAPI_CPUSTATE_TYPE] = WEBAPI_CPUTYPE_6502, + [WEBAPI_CPUSTATE_6502_A] = cpu->A, + [WEBAPI_CPUSTATE_6502_X] = cpu->X, + [WEBAPI_CPUSTATE_6502_Y] = cpu->Y, + [WEBAPI_CPUSTATE_6502_S] = cpu->S, + [WEBAPI_CPUSTATE_6502_P] = cpu->P, + [WEBAPI_CPUSTATE_6502_PC] = cpu->PC, + } + }; +} + +static void web_dbg_request_disassemly(uint16_t addr, int offset_lines, int num_lines, webapi_dasm_line_t* result) { + assert(num_lines > 0); + ui_dbg_dasm_line_t* lines = calloc((size_t)num_lines, sizeof(ui_dbg_dasm_line_t)); + ui_dbg_disassemble(&state.ui.dbg, &(ui_dbg_dasm_request_t){ + .addr = addr, + .num_lines = num_lines, + .offset_lines = offset_lines, + .out_lines = lines, + }); + for (int line_idx = 0; line_idx < num_lines; line_idx++) { + const ui_dbg_dasm_line_t* src = &lines[line_idx]; + webapi_dasm_line_t* dst = &result[line_idx]; + dst->addr = src->addr; + dst->num_bytes = (src->num_bytes <= WEBAPI_DASM_LINE_MAX_BYTES) ? src->num_bytes : WEBAPI_DASM_LINE_MAX_BYTES; + dst->num_chars = (src->num_chars <= WEBAPI_DASM_LINE_MAX_CHARS) ? src->num_chars : WEBAPI_DASM_LINE_MAX_CHARS; + memcpy(dst->bytes, src->bytes, dst->num_bytes); + memcpy(dst->chars, src->chars, dst->num_chars); + } + free(lines); +} + +static void web_dbg_read_memory(uint16_t addr, int num_bytes, uint8_t* dst_ptr) { + for (int i = 0; i < num_bytes; i++) { + *dst_ptr++ = mem_rd(&state.c64.mem_cpu, addr++); + } +} #endif sapp_desc sokol_main(int argc, char* argv[]) { diff --git a/examples/sokol/cpc.c b/examples/sokol/cpc.c index b40214b..7105a6f 100644 --- a/examples/sokol/cpc.c +++ b/examples/sokol/cpc.c @@ -39,6 +39,7 @@ #include "ui/ui_snapshot.h" #include "ui/ui_cpc.h" #endif +#include // calloc,free typedef struct { uint32_t version; @@ -52,23 +53,48 @@ static struct { double emu_time_ms; #if defined(CHIPS_USE_UI) ui_cpc_t ui; + struct { + uint32_t entry_addr; + uint32_t exit_addr; + bool entered; + } dbg; cpc_snapshot_t snapshots[UI_SNAPSHOT_MAX_SLOTS]; #endif } state; #ifdef CHIPS_USE_UI +#define BORDER_TOP (24) static void ui_draw_cb(void); static void ui_boot_cb(cpc_t* sys, cpc_type_t type); static void ui_save_snapshot(size_t slot_index); static bool ui_load_snapshot(size_t slot_index); static void ui_load_snapshots_from_storage(void); -#define BORDER_TOP (24) +static void web_boot(void); +static void web_reset(void); +static bool web_ready(void); +static bool web_load(chips_range_t data); +static void web_dbg_connect(void); +static void web_dbg_disconnect(void); +static void web_dbg_add_breakpoint(uint16_t addr); +static void web_dbg_remove_breakpoint(uint16_t addr); +static void web_dbg_break(void); +static void web_dbg_continue(void); +static void web_dbg_step_next(void); +static void web_dbg_step_into(void); +static void web_dbg_on_stopped(int stop_reason, uint16_t addr); +static void web_dbg_on_continued(void); +static void web_dbg_on_reboot(void); +static void web_dbg_on_reset(void); +static webapi_cpu_state_t web_dbg_cpu_state(void); +static void web_dbg_request_disassemly(uint16_t addr, int offset_lines, int num_lines, webapi_dasm_line_t* result); +static void web_dbg_read_memory(uint16_t addr, int num_bytes, uint8_t* dst_ptr); #else #define BORDER_TOP (8) #endif #define BORDER_LEFT (8) #define BORDER_RIGHT (8) #define BORDER_BOTTOM (32) +#define LOAD_DELAY_FRAMES (120) // audio-streaming callback static void push_audio(const float* samples, int num_samples, void* user_data) { @@ -111,8 +137,7 @@ void app_init(void) { if (sargs_exists("type")) { if (sargs_equals("type", "cpc464")) { type = CPC_TYPE_464; - } - else if (sargs_equals("type", "kccompact")) { + } else if (sargs_equals("type", "kccompact")) { type = CPC_TYPE_KCCOMPACT; } } @@ -123,6 +148,7 @@ void app_init(void) { cpc_desc_t desc = cpc_desc(type, joy_type); cpc_init(&state.cpc, &desc); gfx_init(&(gfx_desc_t){ + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif @@ -155,6 +181,12 @@ void app_init(void) { .update_cb = ui_update_texture, .destroy_cb = ui_destroy_texture, }, + .dbg_debug = { + .reboot_cb = web_dbg_on_reboot, + .reset_cb = web_dbg_on_reset, + .stopped_cb = web_dbg_on_stopped, + .continued_cb = web_dbg_on_continued, + }, .snapshot = { .load_cb = ui_load_snapshot, .save_cb = ui_save_snapshot, @@ -172,6 +204,26 @@ void app_init(void) { } }); ui_load_snapshots_from_storage(); + // important: initialize webapi after ui + webapi_init(&(webapi_desc_t){ + .funcs = { + .boot = web_boot, + .reset = web_reset, + .ready = web_ready, + .load = web_load, + .dbg_connect = web_dbg_connect, + .dbg_disconnect = web_dbg_disconnect, + .dbg_add_breakpoint = web_dbg_add_breakpoint, + .dbg_remove_breakpoint = web_dbg_remove_breakpoint, + .dbg_break = web_dbg_break, + .dbg_continue = web_dbg_continue, + .dbg_step_next = web_dbg_step_next, + .dbg_step_into = web_dbg_step_into, + .dbg_cpu_state = web_dbg_cpu_state, + .dbg_request_disassembly = web_dbg_request_disassemly, + .dbg_read_memory = web_dbg_read_memory, + } + }); #endif bool delay_input = false; @@ -258,8 +310,7 @@ void app_input(const sapp_event* event) { shift_c = c; } cpc_key_down(&state.cpc, shift ? shift_c : c); - } - else { + } else { // see: https://github.com/floooh/chips-test/issues/20 cpc_key_up(&state.cpc, c); if (shift_c) { @@ -295,7 +346,7 @@ static void send_keybuf_input(void) { static void handle_file_loading(void) { fs_dowork(); - const uint32_t load_delay_frames = 120; + const uint32_t load_delay_frames = LOAD_DELAY_FRAMES; if (fs_success(FS_SLOT_IMAGE) && ((clock_frame_count_60hz() > load_delay_frames) || fs_ext(FS_SLOT_IMAGE, "sna"))) { bool load_success = false; if (fs_ext(FS_SLOT_IMAGE, "txt") || fs_ext(FS_SLOT_IMAGE, "bas")) { @@ -309,9 +360,8 @@ static void handle_file_loading(void) { */ else if (fs_ext(FS_SLOT_IMAGE, "dsk")) { load_success = cpc_insert_disc(&state.cpc, fs_data(FS_SLOT_IMAGE)); - } - else if (fs_ext(FS_SLOT_IMAGE, "sna") || fs_ext(FS_SLOT_IMAGE, "bin")) { - load_success = cpc_quickload(&state.cpc, fs_data(FS_SLOT_IMAGE)); + } else if (fs_ext(FS_SLOT_IMAGE, "sna") || fs_ext(FS_SLOT_IMAGE, "bin")) { + load_success = cpc_quickload(&state.cpc, fs_data(FS_SLOT_IMAGE), true); } if (load_success) { if (clock_frame_count_60hz() > (load_delay_frames + 10)) { @@ -320,8 +370,7 @@ static void handle_file_loading(void) { if (sargs_exists("input")) { keybuf_put(sargs_value("input")); } - } - else { + } else { gfx_flash_error(); } fs_reset(FS_SLOT_IMAGE); @@ -386,7 +435,9 @@ static void draw_status_bar(void) { static void ui_draw_cb(void) { ui_cpc_draw(&state.ui); } + static void ui_boot_cb(cpc_t* sys, cpc_type_t type) { + clock_init(); cpc_desc_t desc = cpc_desc(type, sys->joystick_type); cpc_init(sys, &desc); } @@ -439,6 +490,175 @@ static void ui_load_snapshots_from_storage(void) { fs_start_load_snapshot(FS_SLOT_SNAPSHOTS, "cpc", snapshot_slot, ui_fetch_snapshot_callback); } } + +// webapi wrappers +static void web_boot(void) { + clock_init(); + cpc_desc_t desc = cpc_desc(state.cpc.type, state.cpc.joystick_type); + cpc_init(&state.cpc, &desc); + ui_dbg_reboot(&state.ui.dbg); +} + +static void web_reset(void) { + cpc_reset(&state.cpc); + ui_dbg_reset(&state.ui.dbg); +} + +static void web_dbg_connect(void) { + gfx_disable_speaker_icon(); + state.dbg.entry_addr = 0xFFFFFFFF; + state.dbg.exit_addr = 0xFFFFFFFF; + state.dbg.entered = false; + ui_dbg_external_debugger_connected(&state.ui.dbg); +} + +static void web_dbg_disconnect(void) { + state.dbg.entry_addr = 0xFFFFFFFF; + state.dbg.exit_addr = 0xFFFFFFFF; + state.dbg.entered = false; + ui_dbg_external_debugger_disconnected(&state.ui.dbg); +} + +static bool web_ready(void) { + return clock_frame_count_60hz() > LOAD_DELAY_FRAMES; +} + +static bool web_load(chips_range_t data) { + if (data.size <= sizeof(webapi_fileheader_t)) { + return false; + } + const webapi_fileheader_t* hdr = (webapi_fileheader_t*)data.ptr; + if ((hdr->type[0] != 'B') || (hdr->type[1] != 'I') || (hdr->type[2] != 'N') || (hdr->type[3] != ' ')) { + return false; + } + const bool start = 0 != (hdr->flags & WEBAPI_FILEHEADER_FLAG_START); + const chips_range_t bin = { .ptr = (void*)&hdr->payload, .size = data.size - sizeof(webapi_fileheader_t) }; + bool loaded = cpc_quickload(&state.cpc, bin, start); + if (loaded) { + state.dbg.entry_addr = cpc_quickload_exec_addr(bin); + state.dbg.exit_addr = cpc_quickload_return_addr(&state.cpc); + ui_dbg_add_breakpoint(&state.ui.dbg, state.dbg.entry_addr); + ui_dbg_add_breakpoint(&state.ui.dbg, state.dbg.exit_addr); + // if debugger is stopped, unstuck it + if (ui_dbg_stopped(&state.ui.dbg)) { + ui_dbg_continue(&state.ui.dbg, false); + } + } + return loaded; +} + +static void web_dbg_add_breakpoint(uint16_t addr) { + ui_dbg_add_breakpoint(&state.ui.dbg, addr); +} + +static void web_dbg_remove_breakpoint(uint16_t addr) { + ui_dbg_remove_breakpoint(&state.ui.dbg, addr); +} + +static void web_dbg_break(void) { + ui_dbg_break(&state.ui.dbg); +} + +static void web_dbg_continue(void) { + ui_dbg_continue(&state.ui.dbg, false); +} + +static void web_dbg_step_next(void) { + ui_dbg_step_next(&state.ui.dbg); +} + +static void web_dbg_step_into(void) { + ui_dbg_step_into(&state.ui.dbg); +} + +static void web_dbg_on_stopped(int stop_reason, uint16_t addr) { + // stopping on the entry or exit breakpoints always + // overrides the incoming stop_reason + int webapi_stop_reason = WEBAPI_STOPREASON_UNKNOWN; + if ((state.dbg.entry_addr + 1) == state.cpc.cpu.pc) { + webapi_stop_reason = WEBAPI_STOPREASON_ENTRY; + state.dbg.entered = true; + } else if ((state.dbg.exit_addr + 1) == state.cpc.cpu.pc) { + // NOTE: the exit address will be hit multiple times before + // the entry address is actually reached, ignore the exit breakpoint + // until entry happens + if (!state.dbg.entered) { + ui_dbg_continue(&state.ui.dbg, false); + return; + } + webapi_stop_reason = WEBAPI_STOPREASON_EXIT; + } else if (stop_reason == UI_DBG_STOP_REASON_BREAK) { + webapi_stop_reason = WEBAPI_STOPREASON_BREAK; + } else if (stop_reason == UI_DBG_STOP_REASON_STEP) { + webapi_stop_reason = WEBAPI_STOPREASON_STEP; + } else if (stop_reason == UI_DBG_STOP_REASON_BREAKPOINT) { + webapi_stop_reason = WEBAPI_STOPREASON_BREAKPOINT; + } + webapi_event_stopped(webapi_stop_reason, addr); +} + +static void web_dbg_on_continued(void) { + webapi_event_continued(); +} + +static void web_dbg_on_reboot(void) { + webapi_event_reboot(); +} + +static void web_dbg_on_reset(void) { + webapi_event_reset(); +} + +static webapi_cpu_state_t web_dbg_cpu_state(void) { + const z80_t* cpu = &state.cpc.cpu; + return (webapi_cpu_state_t){ + .items = { + [WEBAPI_CPUSTATE_TYPE] = WEBAPI_CPUTYPE_Z80, + [WEBAPI_CPUSTATE_Z80_AF] = cpu->af, + [WEBAPI_CPUSTATE_Z80_BC] = cpu->bc, + [WEBAPI_CPUSTATE_Z80_DE] = cpu->de, + [WEBAPI_CPUSTATE_Z80_HL] = cpu->hl, + [WEBAPI_CPUSTATE_Z80_IX] = cpu->ix, + [WEBAPI_CPUSTATE_Z80_IY] = cpu->iy, + [WEBAPI_CPUSTATE_Z80_SP] = cpu->sp, + [WEBAPI_CPUSTATE_Z80_PC] = cpu->pc, + [WEBAPI_CPUSTATE_Z80_AF2] = cpu->af2, + [WEBAPI_CPUSTATE_Z80_BC2] = cpu->bc2, + [WEBAPI_CPUSTATE_Z80_DE2] = cpu->de2, + [WEBAPI_CPUSTATE_Z80_HL2] = cpu->hl2, + [WEBAPI_CPUSTATE_Z80_IM] = cpu->im, + [WEBAPI_CPUSTATE_Z80_IR] = cpu->ir, + [WEBAPI_CPUSTATE_Z80_IFF] = (cpu->iff1 ? 1 : 0) | (cpu->iff2 ? 2 : 0), + } + }; +} + +static void web_dbg_request_disassemly(uint16_t addr, int offset_lines, int num_lines, webapi_dasm_line_t* result) { + assert(num_lines > 0); + ui_dbg_dasm_line_t* lines = calloc((size_t)num_lines, sizeof(ui_dbg_dasm_line_t)); + ui_dbg_disassemble(&state.ui.dbg, &(ui_dbg_dasm_request_t){ + .addr = addr, + .num_lines = num_lines, + .offset_lines = offset_lines, + .out_lines = lines, + }); + for (int line_idx = 0; line_idx < num_lines; line_idx++) { + const ui_dbg_dasm_line_t* src = &lines[line_idx]; + webapi_dasm_line_t* dst = &result[line_idx]; + dst->addr = src->addr; + dst->num_bytes = (src->num_bytes <= WEBAPI_DASM_LINE_MAX_BYTES) ? src->num_bytes : WEBAPI_DASM_LINE_MAX_BYTES; + dst->num_chars = (src->num_chars <= WEBAPI_DASM_LINE_MAX_CHARS) ? src->num_chars : WEBAPI_DASM_LINE_MAX_CHARS; + memcpy(dst->bytes, src->bytes, dst->num_bytes); + memcpy(dst->chars, src->chars, dst->num_chars); + } + free(lines); +} + +static void web_dbg_read_memory(uint16_t addr, int num_bytes, uint8_t* dst_ptr) { + for (int i = 0; i < num_bytes; i++) { + *dst_ptr++ = mem_rd(&state.cpc.mem, addr++); + } +} #endif sapp_desc sokol_main(int argc, char* argv[]) { diff --git a/examples/sokol/kc85.c b/examples/sokol/kc85.c index 15534c9..af8ebe7 100644 --- a/examples/sokol/kc85.c +++ b/examples/sokol/kc85.c @@ -31,6 +31,7 @@ #include "ui/ui_snapshot.h" #include "ui/ui_kc85.h" #endif +#include typedef struct { uint32_t version; @@ -45,6 +46,10 @@ static struct { kc85_module_type_t delay_insert_module; // module to insert after ROM module image has been loaded #ifdef CHIPS_USE_UI ui_kc85_t ui; + struct { + uint32_t entry_addr; + uint32_t exit_addr; + } dbg; kc85_snapshot_t snapshots[UI_SNAPSHOT_MAX_SLOTS]; #endif } state; @@ -56,6 +61,25 @@ static void ui_boot_cb(kc85_t* sys); static void ui_save_snapshot(size_t slot_index); static bool ui_load_snapshot(size_t slot_index); static void ui_load_snapshots_from_storage(void); +static void web_boot(void); +static void web_reset(void); +static bool web_ready(void); +static bool web_load(chips_range_t data); +static void web_dbg_connect(void); +static void web_dbg_disconnect(void); +static void web_dbg_add_breakpoint(uint16_t addr); +static void web_dbg_remove_breakpoint(uint16_t addr); +static void web_dbg_break(void); +static void web_dbg_continue(void); +static void web_dbg_step_next(void); +static void web_dbg_step_into(void); +static void web_dbg_on_stopped(int stop_reason, uint16_t addr); +static void web_dbg_on_continued(void); +static void web_dbg_on_reboot(void); +static void web_dbg_on_reset(void); +static webapi_cpu_state_t web_dbg_cpu_state(void); +static void web_dbg_request_disassemly(uint16_t addr, int offset_lines, int num_lines, webapi_dasm_line_t* result); +static void web_dbg_read_memory(uint16_t addr, int num_bytes, uint8_t* dst_ptr); #else #define BORDER_TOP (8) #endif @@ -133,6 +157,7 @@ kc85_desc_t kc85_desc(void) { void app_init(void) { gfx_init(&(gfx_desc_t) { + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif @@ -163,6 +188,12 @@ void app_init(void) { .update_cb = ui_update_texture, .destroy_cb = ui_destroy_texture, }, + .dbg_debug = { + .reboot_cb = web_dbg_on_reboot, + .reset_cb = web_dbg_on_reset, + .stopped_cb = web_dbg_on_stopped, + .continued_cb = web_dbg_on_continued, + }, .snapshot = { .load_cb = ui_load_snapshot, .save_cb = ui_save_snapshot, @@ -180,6 +211,26 @@ void app_init(void) { } }); ui_load_snapshots_from_storage(); + // important: initialize webapi after ui + webapi_init(&(webapi_desc_t){ + .funcs = { + .boot = web_boot, + .reset = web_reset, + .ready = web_ready, + .load = web_load, + .dbg_connect = web_dbg_connect, + .dbg_disconnect = web_dbg_disconnect, + .dbg_add_breakpoint = web_dbg_add_breakpoint, + .dbg_remove_breakpoint = web_dbg_remove_breakpoint, + .dbg_break = web_dbg_break, + .dbg_continue = web_dbg_continue, + .dbg_step_next = web_dbg_step_next, + .dbg_step_into = web_dbg_step_into, + .dbg_cpu_state = web_dbg_cpu_state, + .dbg_request_disassembly = web_dbg_request_disassemly, + .dbg_read_memory = web_dbg_read_memory, + } + }); #endif bool delay_input = false; @@ -359,7 +410,7 @@ static void handle_file_loading(void) { keybuf_put((const char*)file_data.ptr); } else { - load_success = kc85_quickload(&state.kc85, file_data); + load_success = kc85_quickload(&state.kc85, file_data, true); } if (load_success) { if (clock_frame_count_60hz() > (load_delay_frames + 10)) { @@ -424,6 +475,7 @@ static void ui_draw_cb(void) { } static void ui_boot_cb(kc85_t* sys) { + clock_init(); kc85_desc_t desc = kc85_desc(); kc85_init(sys, &desc); } @@ -476,6 +528,165 @@ static void ui_load_snapshots_from_storage(void) { fs_start_load_snapshot(FS_SLOT_SNAPSHOTS, KC85_SYSTEM_NAME, snapshot_slot, ui_fetch_snapshot_callback); } } + +// webapi wrappers +static void web_boot(void) { + clock_init(); + kc85_desc_t desc = kc85_desc(); + kc85_init(&state.kc85, &desc); + ui_dbg_reboot(&state.ui.dbg); +} + +static void web_reset(void) { + kc85_reset(&state.kc85); + ui_dbg_reset(&state.ui.dbg); +} + +static void web_dbg_connect(void) { + gfx_disable_speaker_icon(); + state.dbg.entry_addr = 0xFFFFFFFF; + state.dbg.exit_addr = 0xFFFFFFFF; + ui_dbg_external_debugger_connected(&state.ui.dbg); +} + +static void web_dbg_disconnect(void) { + state.dbg.entry_addr = 0xFFFFFFFF; + state.dbg.exit_addr = 0xFFFFFFFF; + ui_dbg_external_debugger_disconnected(&state.ui.dbg); +} + +static bool web_ready(void) { + return clock_frame_count_60hz() > LOAD_DELAY_FRAMES; +} + +static bool web_load(chips_range_t data) { + if (data.size <= sizeof(webapi_fileheader_t)) { + return false; + } + const webapi_fileheader_t* hdr = (webapi_fileheader_t*)data.ptr; + if ((hdr->type[0] != 'K') || (hdr->type[1] != 'C') || (hdr->type[2] != 'C') || (hdr->type[3] != ' ')) { + return false; + } + const bool start = 0 != (hdr->flags & WEBAPI_FILEHEADER_FLAG_START); + const chips_range_t kcc = { .ptr = (void*)&hdr->payload, .size = data.size - sizeof(webapi_fileheader_t) }; + bool loaded = kc85_quickload(&state.kc85, kcc, start); + if (loaded) { + state.dbg.entry_addr = kc85_kcc_exec_addr(kcc); + state.dbg.exit_addr = kc85_quickload_return_addr(); + ui_dbg_add_breakpoint(&state.ui.dbg, state.dbg.entry_addr); + ui_dbg_add_breakpoint(&state.ui.dbg, state.dbg.exit_addr); + // if debugger is stopped, unstuck it + if (ui_dbg_stopped(&state.ui.dbg)) { + ui_dbg_continue(&state.ui.dbg, false); + } + } + return loaded; +} + +static void web_dbg_add_breakpoint(uint16_t addr) { + ui_dbg_add_breakpoint(&state.ui.dbg, addr); +} + +static void web_dbg_remove_breakpoint(uint16_t addr) { + ui_dbg_remove_breakpoint(&state.ui.dbg, addr); +} + +static void web_dbg_break(void) { + ui_dbg_break(&state.ui.dbg); +} + +static void web_dbg_continue(void) { + ui_dbg_continue(&state.ui.dbg, false); +} + +static void web_dbg_step_next(void) { + ui_dbg_step_next(&state.ui.dbg); +} + +static void web_dbg_step_into(void) { + ui_dbg_step_into(&state.ui.dbg); +} + +static void web_dbg_on_stopped(int stop_reason, uint16_t addr) { + // stopping on the entry or exit breakpoints always + // overrides the incoming stop_reason + int webapi_stop_reason = WEBAPI_STOPREASON_UNKNOWN; + if ((state.dbg.entry_addr + 1) == state.kc85.cpu.pc) { + webapi_stop_reason = WEBAPI_STOPREASON_ENTRY; + } else if ((state.dbg.exit_addr + 1) == state.kc85.cpu.pc) { + webapi_stop_reason = WEBAPI_STOPREASON_EXIT; + } else if (stop_reason == UI_DBG_STOP_REASON_BREAK) { + webapi_stop_reason = WEBAPI_STOPREASON_BREAK; + } else if (stop_reason == UI_DBG_STOP_REASON_STEP) { + webapi_stop_reason = WEBAPI_STOPREASON_STEP; + } else if (stop_reason == UI_DBG_STOP_REASON_BREAKPOINT) { + webapi_stop_reason = WEBAPI_STOPREASON_BREAKPOINT; + } + webapi_event_stopped(webapi_stop_reason, addr); +} + +static void web_dbg_on_continued(void) { + webapi_event_continued(); +} + +static void web_dbg_on_reboot(void) { + webapi_event_reboot(); +} + +static void web_dbg_on_reset(void) { + webapi_event_reset(); +} + +static webapi_cpu_state_t web_dbg_cpu_state(void) { + const z80_t* cpu = &state.kc85.cpu; + return (webapi_cpu_state_t){ + .items = { + [WEBAPI_CPUSTATE_TYPE] = WEBAPI_CPUTYPE_Z80, + [WEBAPI_CPUSTATE_Z80_AF] = cpu->af, + [WEBAPI_CPUSTATE_Z80_BC] = cpu->bc, + [WEBAPI_CPUSTATE_Z80_DE] = cpu->de, + [WEBAPI_CPUSTATE_Z80_HL] = cpu->hl, + [WEBAPI_CPUSTATE_Z80_IX] = cpu->ix, + [WEBAPI_CPUSTATE_Z80_IY] = cpu->iy, + [WEBAPI_CPUSTATE_Z80_SP] = cpu->sp, + [WEBAPI_CPUSTATE_Z80_PC] = cpu->pc, + [WEBAPI_CPUSTATE_Z80_AF2] = cpu->af2, + [WEBAPI_CPUSTATE_Z80_BC2] = cpu->bc2, + [WEBAPI_CPUSTATE_Z80_DE2] = cpu->de2, + [WEBAPI_CPUSTATE_Z80_HL2] = cpu->hl2, + [WEBAPI_CPUSTATE_Z80_IM] = cpu->im, + [WEBAPI_CPUSTATE_Z80_IR] = cpu->ir, + [WEBAPI_CPUSTATE_Z80_IFF] = (cpu->iff1 ? 1 : 0) | (cpu->iff2 ? 2 : 0), + } + }; +} + +static void web_dbg_request_disassemly(uint16_t addr, int offset_lines, int num_lines, webapi_dasm_line_t* result) { + assert(num_lines > 0); + ui_dbg_dasm_line_t* lines = calloc((size_t)num_lines, sizeof(ui_dbg_dasm_line_t)); + ui_dbg_disassemble(&state.ui.dbg, &(ui_dbg_dasm_request_t){ + .addr = addr, + .num_lines = num_lines, + .offset_lines = offset_lines, + .out_lines = lines, + }); + for (int line_idx = 0; line_idx < num_lines; line_idx++) { + const ui_dbg_dasm_line_t* src = &lines[line_idx]; + webapi_dasm_line_t* dst = &result[line_idx]; + dst->addr = src->addr; + dst->num_bytes = (src->num_bytes <= WEBAPI_DASM_LINE_MAX_BYTES) ? src->num_bytes : WEBAPI_DASM_LINE_MAX_BYTES; + dst->num_chars = (src->num_chars <= WEBAPI_DASM_LINE_MAX_CHARS) ? src->num_chars : WEBAPI_DASM_LINE_MAX_CHARS; + memcpy(dst->bytes, src->bytes, dst->num_bytes); + memcpy(dst->chars, src->chars, dst->num_chars); + } + free(lines); +} + +static void web_dbg_read_memory(uint16_t addr, int num_bytes, uint8_t* dst_ptr) { + for (int i = 0; i < num_bytes; i++) { + *dst_ptr++ = mem_rd(&state.kc85.mem, addr++); + } +} #endif sapp_desc sokol_main(int argc, char* argv[]) { diff --git a/examples/sokol/pacman.c b/examples/sokol/pacman.c index 738a961..7e93579 100644 --- a/examples/sokol/pacman.c +++ b/examples/sokol/pacman.c @@ -87,6 +87,7 @@ static void app_init(void) { #endif }); gfx_init(&(gfx_desc_t) { + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif @@ -264,7 +265,7 @@ static void ui_load_snapshots_from_storage(void) { #endif sapp_desc sokol_main(int argc, char* argv[]) { - (void)argc; (void)argv; + sargs_setup(&(sargs_desc) { .argc = argc, .argv = argv }); const chips_display_info_t info = namco_display_info(0); return (sapp_desc) { .init_cb = app_init, diff --git a/examples/sokol/pengo.c b/examples/sokol/pengo.c index 4a6d223..7b07a55 100644 --- a/examples/sokol/pengo.c +++ b/examples/sokol/pengo.c @@ -91,6 +91,7 @@ static void app_init(void) { #endif }); gfx_init(&(gfx_desc_t) { + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif @@ -270,7 +271,7 @@ static void ui_load_snapshots_from_storage(void) { #endif sapp_desc sokol_main(int argc, char* argv[]) { - (void)argc; (void)argv; + sargs_setup(&(sargs_desc) { .argc = argc, .argv = argv }); const chips_display_info_t info = namco_display_info(0); return (sapp_desc) { .init_cb = app_init, diff --git a/examples/sokol/vic20.c b/examples/sokol/vic20.c index c19e9e1..15c4193 100644 --- a/examples/sokol/vic20.c +++ b/examples/sokol/vic20.c @@ -116,6 +116,7 @@ void app_init(void) { vic20_desc_t desc = vic20_desc(joy_type, mem_config, c1530_enabled); vic20_init(&state.vic20, &desc); gfx_init(&(gfx_desc_t){ + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif diff --git a/examples/sokol/z1013.c b/examples/sokol/z1013.c index 68a71d3..3067686 100644 --- a/examples/sokol/z1013.c +++ b/examples/sokol/z1013.c @@ -76,6 +76,7 @@ z1013_desc_t z1013_desc(z1013_type_t type) { void app_init(void) { gfx_init(&(gfx_desc_t){ + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif diff --git a/examples/sokol/z9001.c b/examples/sokol/z9001.c index 1aea384..18bb7c7 100644 --- a/examples/sokol/z9001.c +++ b/examples/sokol/z9001.c @@ -103,6 +103,7 @@ z9001_desc_t z9001_desc(z9001_type_t type) { void app_init(void) { gfx_init(&(gfx_desc_t) { + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif diff --git a/examples/sokol/zx.c b/examples/sokol/zx.c index 8723d1b..56798fa 100644 --- a/examples/sokol/zx.c +++ b/examples/sokol/zx.c @@ -90,6 +90,7 @@ zx_desc_t zx_desc(zx_type_t type, zx_joystick_type_t joy_type) { void app_init(void) { gfx_init(&(gfx_desc_t){ + .disable_speaker_icon = sargs_exists("disable-speaker-icon"), #ifdef CHIPS_USE_UI .draw_extra_cb = ui_draw, #endif diff --git a/fips-files/configs/wasm-vscode-debug.yml b/fips-files/configs/wasm-vscode-debug.yml new file mode 100644 index 0000000..71d6b24 --- /dev/null +++ b/fips-files/configs/wasm-vscode-debug.yml @@ -0,0 +1,11 @@ +--- +platform: emscripten +generator: Ninja +build_tool: vscode +build_type: Debug +cmake-toolchain: emscripten.toolchain.cmake +defines: + FIPS_EMSCRIPTEN_USE_WASM: ON + FIPS_EMSCRIPTEN_USE_WEBGL2: ON + FIPS_EMSCRIPTEN_USE_EMMALLOC: ON + FIPS_EMSCRIPTEN_RELATIVE_SHELL_HTML: "examples/common/shell.html"