diff --git a/include/app.h b/include/app.h index f170f16c..188fe53c 100644 --- a/include/app.h +++ b/include/app.h @@ -329,12 +329,13 @@ void app_game_key(struct app *app, enum keys key, bool pressed); void app_game_speed(struct app *app, uint32_t); void app_game_update_backup(struct app *app); void app_game_screenshot(struct app *app); +void app_game_screenshot_path(struct app *app, char const *); void app_game_quicksave(struct app *app, size_t idx); void app_game_quickload(struct app *app, size_t idx); #ifdef WITH_DEBUGGER -void app_game_frame(struct app *app); +void app_game_frame(struct app *app, size_t); void app_game_trace(struct app *app, size_t, void (*)(struct app *)); void app_game_step_in(struct app *app, size_t cnt); void app_game_step_over(struct app *app, size_t cnt); diff --git a/include/common/channel/event.h b/include/common/channel/event.h index 0453be56..aaab8097 100644 --- a/include/common/channel/event.h +++ b/include/common/channel/event.h @@ -93,6 +93,11 @@ struct message_set_watchpoints_list { size_t len; }; +struct message_frame { + struct event_header header; + size_t count; +}; + #endif /* diff --git a/include/dbg/dbg.h b/include/dbg/dbg.h index 90ad267f..7ab0df12 100644 --- a/include/dbg/dbg.h +++ b/include/dbg/dbg.h @@ -134,6 +134,8 @@ enum commands_list { CMD_RESET, CMD_FRAME, CMD_IO, + CMD_KEY, + CMD_SCREENSHOT, }; struct io_bitfield { @@ -196,6 +198,9 @@ void debugger_cmd_help(struct app *, size_t, struct arg const *); /* dbg/cmd/io.c */ void debugger_cmd_io(struct app *, size_t, struct arg const *); +/* dbg/cmd/key.c */ +void debugger_cmd_key(struct app *, size_t, struct arg const *); + /* dbg/cmd/print.c */ void debugger_cmd_print(struct app *, size_t, struct arg const *); void debugger_cmd_print_u8(struct app const *, uint32_t, size_t, size_t); @@ -208,6 +213,9 @@ void debugger_cmd_registers(struct app *, size_t, struct arg const *); /* dbg/cmd/reset.c */ void debugger_cmd_reset(struct app *, size_t, struct arg const *); +/* dbg/cmd/screenshot.c */ +void debugger_cmd_screenshot(struct app *, size_t, struct arg const *); + /* dbg/cmd/step.c */ void debugger_cmd_step_in(struct app *, size_t, struct arg const *); void debugger_cmd_step_over(struct app *, size_t, struct arg const *); diff --git a/include/gba/debugger.h b/include/gba/debugger.h index 67f1e480..4b393f2d 100644 --- a/include/gba/debugger.h +++ b/include/gba/debugger.h @@ -70,6 +70,10 @@ struct debugger { uint32_t next_pc; size_t count; } step; + + struct { + size_t count; + } frame; }; /* gba/debugger.c */ diff --git a/include/gba/gba.h b/include/gba/gba.h index cd231bc6..a55e5f01 100644 --- a/include/gba/gba.h +++ b/include/gba/gba.h @@ -45,6 +45,9 @@ enum keys { KEY_RIGHT, KEY_START, KEY_SELECT, + + KEY_MAX, + KEY_MIN = KEY_A, }; struct shared_data { diff --git a/source/common/game.c b/source/common/game.c index a1c02883..59bdc35c 100644 --- a/source/common/game.c +++ b/source/common/game.c @@ -659,23 +659,15 @@ app_game_update_backup( ** Take a screenshot of the game and writes it to the disk. */ void -app_game_screenshot( - struct app *app +app_game_screenshot_path( + struct app *app, + char const *path ) { - time_t now; - struct tm *now_info; - char filename[256]; int out; - time(&now); - now_info = localtime(&now); - - hs_mkdir("screenshots"); - strftime(filename, sizeof(filename), "screenshots/%Y-%m-%d_%Hh%Mm%Ss.png", now_info); - pthread_mutex_lock(&app->emulation.gba->shared_data.framebuffer.lock); out = stbi_write_png( - filename, + path, GBA_SCREEN_WIDTH, GBA_SCREEN_HEIGHT, 4, @@ -688,19 +680,41 @@ app_game_screenshot( gui_new_notification( app, UI_NOTIFICATION_SUCCESS, - "Screenshot saved in %s.", - filename + "Screenshot saved in \"%s\".", + path ); } else { gui_new_notification( app, UI_NOTIFICATION_ERROR, - "Failed to save screenshot in %s.", - filename + "Failed to save screenshot in \"%s\".", + path ); } } +/* +** Take a screenshot of the game and writes it to the disk. +** +** The file's name depends on the current time. +*/ +void +app_game_screenshot( + struct app *app +) { + time_t now; + struct tm *now_info; + char path[256]; + + time(&now); + now_info = localtime(&now); + + hs_mkdir("screenshots"); + strftime(path, sizeof(path), "screenshots/%Y-%m-%d_%Hh%Mm%Ss.png", now_info); + + app_game_screenshot_path(app, path); +} + void app_game_quicksave( struct app *app, @@ -795,12 +809,14 @@ app_game_quickload( */ void app_game_frame( - struct app *app + struct app *app, + size_t count ) { - struct message event; + struct message_frame event; event.header.kind = MESSAGE_FRAME; event.header.size = sizeof(event); + event.count = count; channel_lock(&app->emulation.gba->channels.messages); channel_push(&app->emulation.gba->channels.messages, &event.header); diff --git a/source/dbg/cmd/frame.c b/source/dbg/cmd/frame.c index 165a9420..148fa25a 100644 --- a/source/dbg/cmd/frame.c +++ b/source/dbg/cmd/frame.c @@ -22,7 +22,20 @@ debugger_cmd_frame( return; } - app_game_frame(app); - debugger_wait_for_emulator(app); - debugger_dump_context_auto(app); + if (argc == 0) { + app_game_frame(app, 1); + debugger_wait_for_emulator(app); + debugger_dump_context_auto(app); + } else if (argc == 1) { + if (debugger_check_arg_type(CMD_FRAME, &argv[0], ARGS_INTEGER)) { + return ; + } + + app_game_frame(app, argv[0].value.i64); + debugger_wait_for_emulator(app); + debugger_dump_context_auto(app); + } else { + printf("Usage: %s\n", g_commands[CMD_FRAME].usage); + return ; + } } diff --git a/source/dbg/cmd/key.c b/source/dbg/cmd/key.c new file mode 100644 index 00000000..25da74cd --- /dev/null +++ b/source/dbg/cmd/key.c @@ -0,0 +1,64 @@ +/******************************************************************************\ +** +** This file is part of the Hades GBA Emulator, and is made available under +** the terms of the GNU General Public License version 2. +** +** Copyright (C) 2021-2023 - The Hades Authors +** +\******************************************************************************/ + +#include "hades.h" +#include "app.h" +#include "dbg/dbg.h" + +static char const * const key_names[] = { + [KEY_A] = "a", + [KEY_B] = "b", + [KEY_L] = "l", + [KEY_R] = "r", + [KEY_UP] = "up", + [KEY_DOWN] = "down", + [KEY_LEFT] = "left", + [KEY_RIGHT] = "right", + [KEY_START] = "start", + [KEY_SELECT] = "select", +}; + +void +debugger_cmd_key( + struct app *app, + size_t argc, + struct arg const *argv +) { + if (!app->debugger.is_started) { + logln(HS_ERROR, "%s%s%s", g_red, "This command cannot be used when no game is running.", g_reset); + return; + } + + if (argc == 2) { + enum keys i; + + if (debugger_check_arg_type(CMD_KEY, &argv[0], ARGS_STRING) + || debugger_check_arg_type(CMD_KEY, &argv[1], ARGS_INTEGER) + ) { + return ; + } + + for (i = KEY_MIN; i < KEY_MAX; ++i) { + if (!strcmp(argv[0].value.s, key_names[i])) { + app_game_key(app, i, (bool)argv[1].value.i64); + printf( + "Key \"%s\" set to %s.\n", + argv[0].value.s, + argv[1].value.i64 ? "true" : "false" + ); + return ; + } + } + + printf("Error: unknown key \"%s\".\n", argv[0].value.s); + } else { + printf("Usage: %s\n", g_commands[CMD_KEY].usage); + return ; + } +} diff --git a/source/dbg/cmd/screenshot.c b/source/dbg/cmd/screenshot.c new file mode 100644 index 00000000..66d33fa3 --- /dev/null +++ b/source/dbg/cmd/screenshot.c @@ -0,0 +1,37 @@ +/******************************************************************************\ +** +** This file is part of the Hades GBA Emulator, and is made available under +** the terms of the GNU General Public License version 2. +** +** Copyright (C) 2021-2023 - The Hades Authors +** +\******************************************************************************/ + +#include "hades.h" +#include "app.h" +#include "dbg/dbg.h" + +void +debugger_cmd_screenshot( + struct app *app, + size_t argc, + struct arg const *argv +) { + if (!app->debugger.is_started) { + logln(HS_ERROR, "%s%s%s", g_red, "This command cannot be used when no game is running.", g_reset); + return; + } + + if (argc == 0) { + app_game_screenshot(app); + } else if (argc == 1) { + if (debugger_check_arg_type(CMD_SCREENSHOT, &argv[0], ARGS_STRING)) { + return ; + } + + app_game_screenshot_path(app, argv[0].value.s); + } else { + printf("Usage: %s\n", g_commands[CMD_SCREENSHOT].usage); + return ; + } +} diff --git a/source/dbg/dbg.c b/source/dbg/dbg.c index 8c7e4a62..0caa830b 100644 --- a/source/dbg/dbg.c +++ b/source/dbg/dbg.c @@ -125,8 +125,8 @@ struct command g_commands[] = { [CMD_FRAME] = { .name = "frame", .alias = "f", - .usage = "frame", - .description = "Run until the end of the current frame.", + .usage = "frame [N=1]", + .description = "Run until N frames are completed.", .func = debugger_cmd_frame, }, [CMD_IO] = { @@ -136,6 +136,20 @@ struct command g_commands[] = { .description = "Print or set the value of an IO register.", .func = debugger_cmd_io, }, + [CMD_KEY] = { + .name = "key", + .alias = NULL, + .usage = "key KEY STATE", + .description = "Set the state of any input key.", + .func = debugger_cmd_key, + }, + [CMD_SCREENSHOT] = { + .name = "screenshot", + .alias = "screen", + .usage = "screenshot [FILE]", + .description = "Store a screenshot of the screen in FILE.", + .func = debugger_cmd_screenshot + }, { .name = NULL, } diff --git a/source/dbg/lang/lexer.c b/source/dbg/lang/lexer.c index 6fb9c64a..33412d2e 100644 --- a/source/dbg/lang/lexer.c +++ b/source/dbg/lang/lexer.c @@ -65,6 +65,7 @@ debugger_lang_lexe( i = 0; while (input[i]) { switch (input[i]) { + case '.': case 'a' ... 'z': case 'A' ... 'Z': { /* Lexe the whole identifier */ @@ -72,12 +73,20 @@ debugger_lang_lexe( size_t j; j = 0; - while (isalnum(input[i + j])) { + while (isalnum(input[i + j]) || input[i + j] == '.' || input[i + j] == '/') { ++j; } - t = token_new(lexer, TOKEN_IDENTIFIER); - t->value.identifier = strndup(input + i, j); + if (j >= strlen("true") && !strncmp(input + i, "true", j)) { + t = token_new(lexer, TOKEN_LITTERAL); + t->value.litteral = 1; + } else if (j >= strlen("false") && !strncmp(input + i, "false", j)) { + t = token_new(lexer, TOKEN_LITTERAL); + t->value.litteral = 0; + } else { + t = token_new(lexer, TOKEN_IDENTIFIER); + t->value.identifier = strndup(input + i, j); + } i += j; break; diff --git a/source/dbg/meson.build b/source/dbg/meson.build index d63d5369..145fc921 100644 --- a/source/dbg/meson.build +++ b/source/dbg/meson.build @@ -17,9 +17,11 @@ libdbg = static_library( 'cmd/frame.c', 'cmd/help.c', 'cmd/io.c', + 'cmd/key.c', 'cmd/print.c', 'cmd/registers.c', 'cmd/reset.c', + 'cmd/screenshot.c', 'cmd/step.c', 'cmd/trace.c', 'cmd/verbose.c', diff --git a/source/gba/debugger.c b/source/gba/debugger.c index 2694ce57..ad930c70 100644 --- a/source/gba/debugger.c +++ b/source/gba/debugger.c @@ -121,8 +121,12 @@ debugger_execute_run_mode( break; }; case GBA_RUN_MODE_FRAME: { - sched_run_for(gba, GBA_CYCLES_PER_PIXEL * GBA_SCREEN_REAL_WIDTH * GBA_SCREEN_HEIGHT); - gba_state_pause(gba); + if (gba->debugger.frame.count) { + --gba->debugger.frame.count; + sched_run_for(gba, GBA_CYCLES_PER_PIXEL * GBA_SCREEN_REAL_WIDTH * GBA_SCREEN_HEIGHT); + } else { + gba_state_pause(gba); + } break; }; case GBA_RUN_MODE_TRACE: { diff --git a/source/gba/gba.c b/source/gba/gba.c index 60305957..d83d5b98 100644 --- a/source/gba/gba.c +++ b/source/gba/gba.c @@ -403,6 +403,7 @@ gba_process_message( case KEY_LEFT: gba->io.keyinput.left = !msg_key->pressed; break; case KEY_START: gba->io.keyinput.start = !msg_key->pressed; break; case KEY_SELECT: gba->io.keyinput.select = !msg_key->pressed; break; + default: break; }; io_scan_keypad_irq(gba); @@ -434,6 +435,11 @@ gba_process_message( }; #ifdef WITH_DEBUGGER case MESSAGE_FRAME: { + struct message_frame const *msg_frame; + + msg_frame = (struct message_frame const *)message; + + gba->debugger.frame.count = msg_frame->count; gba->debugger.run_mode = GBA_RUN_MODE_FRAME; gba_state_run(gba);