mirror of
https://github.com/kichikuou/system3-sdl2.git
synced 2026-10-12 17:28:22 +03:00
Add command line debugger
This commit is contained in:
@@ -67,6 +67,10 @@ elseif (EMSCRIPTEN)
|
||||
)
|
||||
|
||||
else() # NOT (ANDROID OR EMSCRIPTEN)
|
||||
if (NOT NINTENDO_SWITCH)
|
||||
option(ENABLE_DEBUGGER "Enable built-in debugger" ON)
|
||||
endif()
|
||||
|
||||
add_executable(system3)
|
||||
target_link_libraries(system3 PRIVATE ymfm)
|
||||
|
||||
@@ -176,6 +180,14 @@ target_sources(system3 PRIVATE
|
||||
src/sys/nact_sys3.cpp
|
||||
src/sys/scenario.cpp
|
||||
)
|
||||
if (ENABLE_DEBUGGER)
|
||||
target_compile_definitions(system3 PRIVATE ENABLE_DEBUGGER)
|
||||
target_sources(system3 PRIVATE
|
||||
src/debugger/debug_info.cpp
|
||||
src/debugger/debugger.cpp
|
||||
src/debugger/cli_frontend.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NINTENDO_SWITCH)
|
||||
set(RESOURCE_PATH romfs:/)
|
||||
|
||||
@@ -77,6 +77,23 @@ std::pair<char*, char*> parse_keyval(char *line)
|
||||
return { trim(line), trim(eq + 1) };
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
DebuggerMode parse_debugger_mode(const char *s, int lineno = -1)
|
||||
{
|
||||
if (!strcasecmp(s, "none"))
|
||||
return DebuggerMode::DISABLED;
|
||||
if (!strcasecmp(s, "cli"))
|
||||
return DebuggerMode::CLI;
|
||||
if (!strcasecmp(s, "dap"))
|
||||
return DebuggerMode::DAP;
|
||||
if (lineno == -1)
|
||||
ERROR("Command line: Invalid debugger mode '%s'", s);
|
||||
else
|
||||
ERROR(INIFILENAME ":%d Invalid debugger mode '%s'", lineno, s);
|
||||
return DebuggerMode::DISABLED;
|
||||
}
|
||||
#endif
|
||||
|
||||
TexthookMode parse_texthook_mode(const char *s, int lineno = -1)
|
||||
{
|
||||
if (!strcasecmp(s, "none"))
|
||||
@@ -135,6 +152,10 @@ Config::Config(int argc, char *argv[])
|
||||
title = argv[++i];
|
||||
else if (strcmp(argv[i], "-scanline") == 0)
|
||||
scanline = true;
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
else if (strcmp(argv[i], "-debugger") == 0)
|
||||
debugger_mode = parse_debugger_mode(argv[++i]);
|
||||
#endif
|
||||
else if (strcmp(argv[i], "-texthook") == 0)
|
||||
texthook_mode = parse_texthook_mode(argv[++i]);
|
||||
else if (strcmp(argv[i], "-texthook_suppress") == 0)
|
||||
@@ -197,6 +218,10 @@ void Config::load_ini()
|
||||
title = val;
|
||||
else if (!strcasecmp(key, "scanline"))
|
||||
scanline = to_bool(val, lineno);
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
else if (!strcasecmp(key, "debugger"))
|
||||
debugger_mode = parse_debugger_mode(val, lineno);
|
||||
#endif
|
||||
else if (!strcasecmp(key, "texthook"))
|
||||
texthook_mode = parse_texthook_mode(val, lineno);
|
||||
else if (!strcasecmp(key, "texthook_suppress"))
|
||||
|
||||
@@ -11,6 +11,12 @@ enum class TexthookMode {
|
||||
COPY,
|
||||
};
|
||||
|
||||
enum class DebuggerMode {
|
||||
DISABLED,
|
||||
CLI,
|
||||
DAP,
|
||||
};
|
||||
|
||||
struct Strings {
|
||||
std::string back;
|
||||
std::string next_page;
|
||||
@@ -39,6 +45,7 @@ struct Config {
|
||||
bool no_antialias = false;
|
||||
bool scanline = false;
|
||||
TexthookMode texthook_mode = TexthookMode::NONE;
|
||||
DebuggerMode debugger_mode = DebuggerMode::DISABLED;
|
||||
std::string texthook_suppressions;
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
#include "debugger/frontend.h"
|
||||
#include <stdio.h>
|
||||
#include "debugger/debug_info.h"
|
||||
#include "debugger/debugger.h"
|
||||
#include "encoding.h"
|
||||
#include "nact.h"
|
||||
#ifndef _WIN32
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
namespace debugger {
|
||||
|
||||
namespace {
|
||||
|
||||
const char whitespaces[] = " \t\r\n";
|
||||
const size_t CMD_BUFFER_SIZE = 256;
|
||||
|
||||
#ifndef _WIN32
|
||||
void handle_sigint(int) {
|
||||
g_debugger->set_state(State::STOPPED_INTERRUPT);
|
||||
}
|
||||
|
||||
void set_sigint_handler() {
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = handle_sigint;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
class CliFrontend : public Frontend {
|
||||
public:
|
||||
CliFrontend(Debugger* backend, const DebugInfo& symbols) : Frontend(backend, symbols) {
|
||||
backend->set_state(State::STOPPED_ENTRY);
|
||||
#ifndef _WIN32
|
||||
set_sigint_handler();
|
||||
#endif
|
||||
}
|
||||
|
||||
~CliFrontend() override = default;
|
||||
|
||||
void repl(int bp_no) override {
|
||||
if (backend->get_state() == State::STOPPED_BREAKPOINT && bp_no)
|
||||
printf("Breakpoint %d\n", bp_no);
|
||||
printf("Stopped at %s\n", format_address(g_nact->sco.page(), g_nact->sco.cmd_addr()).c_str());
|
||||
backend->set_state(State::RUNNING);
|
||||
|
||||
char buf[CMD_BUFFER_SIZE];
|
||||
while (printf("dbg> "), fflush(stdout), fgets(buf, CMD_BUFFER_SIZE, stdin)) {
|
||||
// If input line is empty, repeat the last command.
|
||||
if (buf[0] == '\n')
|
||||
strcpy(buf, default_command);
|
||||
else
|
||||
strcpy(default_command, buf);
|
||||
|
||||
char *token = strtok(buf, whitespaces);
|
||||
if (!token)
|
||||
continue;
|
||||
|
||||
const Command *cmd = find_command(token);
|
||||
if (cmd) {
|
||||
if ((this->*(cmd->func))() == EXIT_REPL)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
void on_sleep() override {
|
||||
if (backend->get_state() == State::STOPPED_INTERRUPT)
|
||||
backend->repl(0);
|
||||
}
|
||||
|
||||
void on_palette_change() override {
|
||||
}
|
||||
|
||||
void console_output(int lv, const char *output) override {
|
||||
}
|
||||
|
||||
private:
|
||||
enum CommandResult { CONTINUE_REPL, EXIT_REPL };
|
||||
|
||||
char default_command[CMD_BUFFER_SIZE];
|
||||
|
||||
CommandResult cmd_backtrace();
|
||||
CommandResult cmd_break();
|
||||
CommandResult cmd_continue();
|
||||
CommandResult cmd_delete();
|
||||
CommandResult cmd_help();
|
||||
CommandResult cmd_list();
|
||||
CommandResult cmd_step();
|
||||
CommandResult cmd_finish();
|
||||
CommandResult cmd_next();
|
||||
CommandResult cmd_print();
|
||||
CommandResult cmd_quit();
|
||||
CommandResult cmd_string();
|
||||
|
||||
struct Command {
|
||||
const char *name;
|
||||
const char *alias;
|
||||
const char *description;
|
||||
const char *help;
|
||||
CommandResult (CliFrontend::* func)();
|
||||
};
|
||||
|
||||
static const Command commands[];
|
||||
|
||||
const Command *find_command(const char *str);
|
||||
|
||||
void cmd_print_help(const Command& cmd) {
|
||||
if (cmd.alias)
|
||||
printf("%s, %s -- %s\n", cmd.name, cmd.alias, cmd.description);
|
||||
else
|
||||
printf("%s -- %s\n", cmd.name, cmd.description);
|
||||
}
|
||||
|
||||
std::string format_address(int page, int addr) {
|
||||
char buf[200];
|
||||
const char *src = symbols.page2src(page);
|
||||
int line = symbols.addr2line(page, addr);
|
||||
if (src && line > 0)
|
||||
snprintf(buf, sizeof(buf), "%s:%d", src, line);
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "%d:0x%x", page, addr);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
bool parse_address(const char *str, int *page, int *addr) {
|
||||
// <page>:<address>
|
||||
if (sscanf(str, "%i:%i", page, addr) == 2)
|
||||
return true;
|
||||
|
||||
// <filename>:<linenum>
|
||||
char filename[100];
|
||||
int line_no;
|
||||
if (sscanf(str, "%[^:]:%i", filename, &line_no) == 2) {
|
||||
*page = symbols.src2page(filename);
|
||||
if (*page < 0) {
|
||||
printf("No source file named %s.\n", filename);
|
||||
return false;
|
||||
}
|
||||
*addr = symbols.line2addr(*page, line_no);
|
||||
if (*addr < 0) {
|
||||
printf("No line %d in file %s.\n", line_no, filename);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
*page = g_nact->sco.page();
|
||||
|
||||
// <linenum>
|
||||
char *endptr;
|
||||
line_no = strtol(str, &endptr, 0);
|
||||
if (!*endptr) {
|
||||
*addr = symbols.line2addr(g_nact->sco.page(), line_no);
|
||||
if (*addr < 0) {
|
||||
printf("No line %d in current file.\n", line_no);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
static const char desc_backtrace[] = "Print backtrace of stack frames.";
|
||||
static const char * const help_backtrace = nullptr;
|
||||
CliFrontend::CommandResult CliFrontend::cmd_backtrace() {
|
||||
for (const StackFrame &frame : backend->stack_trace()) {
|
||||
if (frame.src && frame.line > 0) {
|
||||
printf("\t%s:%d\n", frame.src, frame.line);
|
||||
} else {
|
||||
printf("\t%d:%04x\n", frame.page, frame.addr);
|
||||
}
|
||||
}
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
static const char desc_break[] = "Set breakpoint at specified location.";
|
||||
static const char help_break[] =
|
||||
"Syntax: break <filename>:<linenum> [if <condition>]\n"
|
||||
" break <linenum> [if <condition>]\n"
|
||||
" break <page>:<address> [if <condition>]";
|
||||
|
||||
CliFrontend::CommandResult CliFrontend::cmd_break() {
|
||||
char *token = strtok(nullptr, whitespaces);
|
||||
int page, addr;
|
||||
if (!token || !parse_address(token, &page, &addr)) {
|
||||
puts(help_break);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
int bp_no = backend->set_breakpoint(page, addr, false);
|
||||
if (bp_no < 0) {
|
||||
printf("Failed to set breakpoint at %d:%04x: invalid address\n", page, addr);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
printf("Breakpoint %d at %s\n", bp_no, format_address(page, addr).c_str());
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
static const char desc_continue[] = "Continue program execution.";
|
||||
static const char * const help_continue = nullptr;
|
||||
CliFrontend::CommandResult CliFrontend::cmd_continue() {
|
||||
return EXIT_REPL;
|
||||
}
|
||||
|
||||
static const char desc_delete[] = "Delete breakpoints.";
|
||||
static const char help_delete[] =
|
||||
"Syntax: delete <breakpoint_no>...";
|
||||
CliFrontend::CommandResult CliFrontend::cmd_delete() {
|
||||
char *arg = strtok(nullptr, whitespaces);
|
||||
if (!arg) {
|
||||
puts(help_delete);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
while (arg) {
|
||||
char *endptr;
|
||||
int bp_no = strtol(arg, &endptr, 0);
|
||||
if (*endptr)
|
||||
printf("Bad breakpoint number %s\n", arg);
|
||||
else {
|
||||
if (!backend->delete_breakpoint(bp_no))
|
||||
printf("No breakpoint number %d.\n", bp_no);
|
||||
}
|
||||
arg = strtok(nullptr, whitespaces);
|
||||
}
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
static const char desc_list[] = "List specified line.";
|
||||
static const char help_list[] =
|
||||
"Syntax: list\n"
|
||||
" list <linenum>\n"
|
||||
" list <filename>:<linenum>\n"
|
||||
"\n"
|
||||
"With no argument, lists ten lines around current location.";
|
||||
|
||||
CliFrontend::CommandResult CliFrontend::cmd_list() {
|
||||
char *arg = strtok(NULL, whitespaces);
|
||||
int page, addr;
|
||||
if (arg) {
|
||||
if (!parse_address(arg, &page, &addr)) {
|
||||
puts(help_list);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
} else {
|
||||
page = g_nact->sco.page();
|
||||
addr = g_nact->sco.cmd_addr();
|
||||
}
|
||||
|
||||
int line_no = symbols.addr2line(page, addr);
|
||||
if (line_no <= 0) {
|
||||
printf("Cannot determine source location for %x:%x\n", page, addr);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
if ((line_no -= 5) <= 0)
|
||||
line_no = 1;
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
const char *line = symbols.source_line(page, line_no + i);
|
||||
if (!line) {
|
||||
if (i == 0)
|
||||
printf("No source text for %s:%d\n", symbols.page2src(page), line_no);
|
||||
break;
|
||||
}
|
||||
printf("%d\t%s\n", line_no + i, line);
|
||||
}
|
||||
|
||||
// Empty REPL input will print the next 10 lines.
|
||||
snprintf(default_command, CMD_BUFFER_SIZE, "l %s:%d", symbols.page2src(page), line_no + 15);
|
||||
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
static const char desc_step[] = "Step program until it reaches a different source line.";
|
||||
static const char * const help_step = nullptr;
|
||||
CliFrontend::CommandResult CliFrontend::cmd_step() {
|
||||
backend->stepin();
|
||||
return EXIT_REPL;
|
||||
}
|
||||
|
||||
static const char desc_finish[] = "Execute until current function returns.";
|
||||
static const char * const help_finish = nullptr;
|
||||
CliFrontend::CommandResult CliFrontend::cmd_finish() {
|
||||
backend->stepout();
|
||||
return EXIT_REPL;
|
||||
}
|
||||
|
||||
static const char desc_next[] = "Step program, proceeding through subroutine calls.";
|
||||
static const char * const help_next = nullptr;
|
||||
CliFrontend::CommandResult CliFrontend::cmd_next() {
|
||||
backend->next();
|
||||
return EXIT_REPL;
|
||||
}
|
||||
|
||||
static const char desc_print[] = "Print value of variable.";
|
||||
static const char help_print[] =
|
||||
"Syntax: print <variable>";
|
||||
|
||||
CliFrontend::CommandResult CliFrontend::cmd_print() {
|
||||
char *arg = strtok(NULL, whitespaces);
|
||||
if (!arg) {
|
||||
puts(help_print);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
int var = symbols.lookup_variable(arg);
|
||||
if (var < 0) {
|
||||
printf("Unrecognized variable name \"%s\".\n", arg);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
printf("%s = %d\n", arg, g_nact->get_var(var));
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
static const char desc_quit[] = "Exit system3-sdl2.";
|
||||
static const char * const help_quit = nullptr;
|
||||
CliFrontend::CommandResult CliFrontend::cmd_quit() {
|
||||
g_nact->quit(0);
|
||||
return EXIT_REPL;
|
||||
}
|
||||
|
||||
static const char desc_string[] = "Print value of string variable.";
|
||||
static const char help_string[] =
|
||||
"Syntax: string <index>";
|
||||
|
||||
CliFrontend::CommandResult CliFrontend::cmd_string() {
|
||||
char *arg = strtok(NULL, whitespaces);
|
||||
if (!arg) {
|
||||
puts(help_string);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
while (arg) {
|
||||
int no = atoi(arg);
|
||||
if (no < 1 || no > MAX_STRVAR) {
|
||||
printf("Bad string index %s\n", arg);
|
||||
} else {
|
||||
char *utf = g_nact->encoding->toUtf8(g_nact->get_string(no - 1));
|
||||
printf("string[%d] = \"%s\"\n", no, utf); // TODO: escaping
|
||||
free(utf);
|
||||
}
|
||||
arg = strtok(NULL, whitespaces);
|
||||
}
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
static const char desc_help[] = "Print list of commands.";
|
||||
static const char * const help_help = nullptr;
|
||||
|
||||
const CliFrontend::Command CliFrontend::commands[] = {
|
||||
{"backtrace", "bt", desc_backtrace, help_backtrace, &CliFrontend::cmd_backtrace},
|
||||
{"break", "b", desc_break, help_break, &CliFrontend::cmd_break},
|
||||
{"continue", "c", desc_continue, help_continue, &CliFrontend::cmd_continue},
|
||||
{"delete", "d", desc_delete, help_delete, &CliFrontend::cmd_delete},
|
||||
{"help", "h", desc_help, help_help, &CliFrontend::cmd_help},
|
||||
{"list", "l", desc_list, help_list, &CliFrontend::cmd_list},
|
||||
{"step", "s", desc_step, help_step, &CliFrontend::cmd_step},
|
||||
{"finish", nullptr, desc_finish, help_finish, &CliFrontend::cmd_finish},
|
||||
{"next", "n", desc_next, help_next, &CliFrontend::cmd_next},
|
||||
{"print", "p", desc_print, help_print, &CliFrontend::cmd_print},
|
||||
{"quit", "q", desc_quit, help_quit, &CliFrontend::cmd_quit},
|
||||
{"string", "str", desc_string, help_string, &CliFrontend::cmd_string},
|
||||
};
|
||||
|
||||
const CliFrontend::Command *CliFrontend::find_command(const char *str) {
|
||||
for (const Command &cmd : commands) {
|
||||
if (!strcmp(str, cmd.name) || (cmd.alias && !strcmp(str, cmd.alias)))
|
||||
return &cmd;
|
||||
}
|
||||
printf("Unknown command \"%s\". Try \"help\".\n", str);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CliFrontend::CommandResult CliFrontend::cmd_help() {
|
||||
char *token = strtok(nullptr, whitespaces);
|
||||
if (!token) {
|
||||
puts("List of commands:");
|
||||
puts("");
|
||||
for (auto& cmd : commands)
|
||||
cmd_print_help(cmd);
|
||||
puts("");
|
||||
puts("Type \"help\" followed by command name for full documentation.");
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
const Command *cmd = find_command(token);
|
||||
if (cmd) {
|
||||
cmd_print_help(*cmd);
|
||||
if (cmd->help) {
|
||||
puts("");
|
||||
puts(cmd->help);
|
||||
}
|
||||
}
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
// static
|
||||
Frontend* Frontend::create_cli(Debugger* backend, const DebugInfo& symbols) {
|
||||
return new CliFrontend(backend, symbols);
|
||||
}
|
||||
|
||||
} // namespace debugger
|
||||
@@ -0,0 +1,220 @@
|
||||
#include "debugger/debug_info.h"
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "fileio.h"
|
||||
|
||||
namespace debugger {
|
||||
|
||||
namespace {
|
||||
|
||||
inline uint32_t LittleEndian_getDW(uint8_t* buf, int ofs) {
|
||||
return buf[ofs] | (buf[ofs + 1] << 8) | (buf[ofs + 2] << 16) | (buf[ofs + 3] << 24);
|
||||
}
|
||||
|
||||
std::string fget4cc(FILEIO& fio) {
|
||||
char buf[5] = {0};
|
||||
if (!fio.read(buf, 4))
|
||||
buf[0] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool ensure_srcs(std::vector<SrcInfo>& srcs, uint32_t nr_srcs) {
|
||||
if (srcs.empty()) {
|
||||
srcs.resize(nr_srcs);
|
||||
} else if (nr_srcs != srcs.size()) {
|
||||
WARNING("malformed debug information");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_srcs(std::vector<SrcInfo>& srcs, std::vector<uint8_t>& buf) {
|
||||
uint32_t nr_srcs = LittleEndian_getDW(buf.data(), 0);
|
||||
if (!ensure_srcs(srcs, nr_srcs))
|
||||
return false;
|
||||
|
||||
char *p = reinterpret_cast<char*>(buf.data()) + 4;
|
||||
for (uint32_t i = 0; i < nr_srcs; i++) {
|
||||
srcs[i].filename = p;
|
||||
p += strlen(p) + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void store_src_lines(SrcInfo& info, char *src) {
|
||||
while (src) {
|
||||
char *eol = strchr(src, '\n');
|
||||
if (eol) {
|
||||
if (src < eol && eol[-1] == '\r')
|
||||
eol[-1] = '\0';
|
||||
else
|
||||
eol[0] = '\0';
|
||||
eol++;
|
||||
}
|
||||
info.lines.push_back(src);
|
||||
src = eol;
|
||||
}
|
||||
}
|
||||
|
||||
bool load_scnt(std::vector<SrcInfo>& srcs, std::vector<uint8_t>& buf) {
|
||||
uint32_t nr_srcs = LittleEndian_getDW(buf.data(), 0);
|
||||
if (!ensure_srcs(srcs, nr_srcs))
|
||||
return false;
|
||||
|
||||
char *p = reinterpret_cast<char*>(buf.data()) + 4;
|
||||
for (uint32_t i = 0; i < nr_srcs; i++) {
|
||||
size_t len = strlen(p);
|
||||
store_src_lines(srcs[i], p);
|
||||
p += len + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_line(std::vector<SrcInfo>& srcs, std::vector<uint8_t>& buf) {
|
||||
uint32_t nr_srcs = LittleEndian_getDW(buf.data(), 0);
|
||||
if (!ensure_srcs(srcs, nr_srcs))
|
||||
return false;
|
||||
|
||||
int ofs = 4;
|
||||
for (uint32_t i = 0; i < nr_srcs; i++) {
|
||||
SrcInfo& info = srcs[i];
|
||||
uint32_t nr_mappings = LittleEndian_getDW(buf.data(), ofs);
|
||||
ofs += 4;
|
||||
info.mappings.resize(nr_mappings);
|
||||
for (uint32_t i = 0; i < nr_mappings; i++) {
|
||||
info.mappings[i].line = LittleEndian_getDW(buf.data(), ofs);
|
||||
info.mappings[i].addr = LittleEndian_getDW(buf.data(), ofs + 4);
|
||||
ofs += 8;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_vari(std::vector<std::string>& variables, std::vector<uint8_t>& buf) {
|
||||
uint32_t nr_vars = LittleEndian_getDW(buf.data(), 0);
|
||||
char *p = reinterpret_cast<char*>(buf.data()) + 4;
|
||||
for (uint32_t i = 0; i < nr_vars; i++) {
|
||||
variables.push_back(p);
|
||||
p += strlen(p) + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int SrcInfo::line2addr(int line) const {
|
||||
// Find the first mapping with line >= `line`.
|
||||
auto it = std::lower_bound(mappings.begin(), mappings.end(), line,
|
||||
[](const Mapping& mapping, int line) {
|
||||
return mapping.line < line;
|
||||
});
|
||||
return it != mappings.end() ? it->addr : -1;
|
||||
}
|
||||
|
||||
int SrcInfo::addr2line(int addr) const {
|
||||
// Find the last mapping with addr <= `addr`.
|
||||
auto it = std::upper_bound(mappings.begin(), mappings.end(), addr,
|
||||
[](int addr, const Mapping& mapping) {
|
||||
return addr < mapping.addr;
|
||||
});
|
||||
return it != mappings.begin() ? (it - 1)->line : -1;
|
||||
}
|
||||
|
||||
void DebugInfo::load(const char *path) {
|
||||
auto fio = FILEIO::open(path, FILEIO_READ_BINARY);
|
||||
if (!fio) {
|
||||
WARNING("Cannot open %s: %s", path, strerror(errno));
|
||||
return;
|
||||
}
|
||||
if (fget4cc(*fio) != "DSYM") {
|
||||
WARNING("%s: wrong signature", path);
|
||||
return;
|
||||
}
|
||||
int version = fio->getdw();
|
||||
if (version != 0) {
|
||||
WARNING("%s: unsupported debug info version", path);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t nr_sections = fio->getdw();
|
||||
for (uint32_t i = 0; i < nr_sections; i++) {
|
||||
std::string tag = fget4cc(*fio);
|
||||
uint32_t section_size = fio->getdw();
|
||||
std::vector<uint8_t> section_content(section_size - 8);
|
||||
if (!fio->read(section_content.data(), section_content.size())) {
|
||||
WARNING("%s: %s", path, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
if (tag == "SRCS") {
|
||||
if (!load_srcs(srcs, section_content)) return;
|
||||
} else if (tag == "SCNT") {
|
||||
if (!load_scnt(srcs, section_content)) return;
|
||||
} else if (tag == "LINE") {
|
||||
if (!load_line(srcs, section_content)) return;
|
||||
} else if (tag == "VARI") {
|
||||
if (!load_vari(variables, section_content)) return;
|
||||
} else {
|
||||
WARNING("%s: unrecognized section %s", path, tag.c_str());
|
||||
}
|
||||
}
|
||||
if (fio->getc() != EOF) {
|
||||
WARNING("%s: broken debug information structure", path);
|
||||
return;
|
||||
}
|
||||
loaded_ = !srcs.empty() && !variables.empty();
|
||||
}
|
||||
|
||||
int DebugInfo::src2page(const char *fname) const {
|
||||
for (size_t i = 0; i < srcs.size(); i++) {
|
||||
if (!strcasecmp(srcs[i].filename.c_str(), fname))
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *DebugInfo::page2src(int page) const {
|
||||
if (static_cast<size_t>(page) >= srcs.size())
|
||||
return NULL;
|
||||
return srcs[page].filename.c_str();
|
||||
}
|
||||
|
||||
int DebugInfo::line2addr(int page, int line) const {
|
||||
if (static_cast<size_t>(page) >= srcs.size())
|
||||
return -1;
|
||||
return srcs[page].line2addr(line);
|
||||
}
|
||||
|
||||
int DebugInfo::addr2line(int page, int addr) const {
|
||||
if (static_cast<size_t>(page) >= srcs.size())
|
||||
return -1;
|
||||
return srcs[page].addr2line(addr);
|
||||
}
|
||||
|
||||
const char *DebugInfo::source_line(int page, int line) const {
|
||||
if (static_cast<size_t>(page) >= srcs.size())
|
||||
return NULL;
|
||||
const SrcInfo& info = srcs[page];
|
||||
|
||||
line--; // 1-based to 0-based index
|
||||
if (static_cast<size_t>(line) >= info.lines.size())
|
||||
return NULL;
|
||||
return info.lines[line].c_str();
|
||||
}
|
||||
|
||||
const char *DebugInfo::variable_name(int i) const {
|
||||
if (static_cast<size_t>(i) >= variables.size())
|
||||
return NULL;
|
||||
return variables[i].c_str();
|
||||
}
|
||||
|
||||
int DebugInfo::lookup_variable(const char *name) const {
|
||||
for (size_t i = 0; i < variables.size(); i++) {
|
||||
if (variables[i] == name)
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace debugger
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef _DEBUG_INFO_H_
|
||||
#define _DEBUG_INFO_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace debugger {
|
||||
|
||||
struct SrcInfo {
|
||||
struct Mapping {
|
||||
int line;
|
||||
int addr;
|
||||
};
|
||||
std::string filename;
|
||||
std::vector<std::string> lines;
|
||||
std::vector<Mapping> mappings;
|
||||
|
||||
int line2addr(int line) const;
|
||||
int addr2line(int addr) const;
|
||||
};
|
||||
|
||||
class DebugInfo {
|
||||
public:
|
||||
void load(const char *filename);
|
||||
bool loaded() const { return loaded_; }
|
||||
int src2page(const char *fname) const;
|
||||
const char *page2src(int page) const;
|
||||
int line2addr(int page, int line) const;
|
||||
int addr2line(int page, int addr) const;
|
||||
const char *source_line(int page, int line) const;
|
||||
size_t num_variables() const { return variables.size(); }
|
||||
const char *variable_name(int i) const;
|
||||
int lookup_variable(const char *name) const;
|
||||
|
||||
private:
|
||||
bool loaded_ = false;
|
||||
std::vector<SrcInfo> srcs;
|
||||
std::vector<std::string> variables;
|
||||
};
|
||||
|
||||
} // namespace debugger
|
||||
|
||||
#endif // _DEBUG_INFO_H_
|
||||
@@ -0,0 +1,219 @@
|
||||
#include "debugger/debugger.h"
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include "common.h"
|
||||
#include "nact.h"
|
||||
#include "debugger/frontend.h"
|
||||
|
||||
namespace debugger {
|
||||
|
||||
namespace {
|
||||
|
||||
const int INTERNAL_BREAKPOINT_NO = -1;
|
||||
|
||||
struct Breakpoint {
|
||||
int page;
|
||||
int addr;
|
||||
int no;
|
||||
uint8_t restore_op;
|
||||
};
|
||||
|
||||
std::map<std::pair<int, int>, Breakpoint> breakpoints;
|
||||
int next_breakpoint_no = 1;
|
||||
|
||||
// Used in State::STOPPED_STEP and State::STOPPED_NEXT
|
||||
struct {
|
||||
int page;
|
||||
int line;
|
||||
} step_exec_state;
|
||||
|
||||
bool should_continue_step(const DebugInfo& symbols) {
|
||||
if (step_exec_state.line < 0)
|
||||
return false; // line info was not available
|
||||
return step_exec_state.page == g_nact->sco.page()
|
||||
&& step_exec_state.line == symbols.addr2line(step_exec_state.page, g_nact->sco.cmd_addr());
|
||||
}
|
||||
|
||||
int get_retaddr_if_funcall() {
|
||||
int orig_addr = g_nact->sco.current_addr();
|
||||
int retaddr = -1;
|
||||
|
||||
g_nact->sco.jump_to(g_nact->sco.cmd_addr());
|
||||
int c0 = g_nact->sco.getd();
|
||||
if (c0 == BREAKPOINT_INSTRUCTION) {
|
||||
auto bp = breakpoints.find({g_nact->sco.page(), g_nact->sco.cmd_addr()});
|
||||
if (bp == breakpoints.end())
|
||||
sys_error("Illegal BREAKPOINT instruction");
|
||||
c0 = bp->second.restore_op;
|
||||
}
|
||||
switch (c0) {
|
||||
case '%':
|
||||
if (g_nact->cali() != 0)
|
||||
retaddr = g_nact->sco.current_addr();
|
||||
break;
|
||||
case '\\':
|
||||
if (g_nact->sco.getw() != 0)
|
||||
retaddr = g_nact->sco.current_addr();
|
||||
break;
|
||||
}
|
||||
g_nact->sco.jump_to(orig_addr);
|
||||
return retaddr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Debugger::Debugger(const char *symbols_path, DebuggerMode mode) {
|
||||
symbols.load(symbols_path);
|
||||
if (mode == DebuggerMode::DAP) {
|
||||
// frontend = std::make_unique<DapFrontend>(this);
|
||||
} else {
|
||||
frontend = std::unique_ptr<Frontend>(Frontend::create_cli(this, symbols));
|
||||
}
|
||||
}
|
||||
|
||||
Debugger::~Debugger() = default;
|
||||
|
||||
void Debugger::repl(int bp_no) {
|
||||
delete_breakpoint(INTERNAL_BREAKPOINT_NO);
|
||||
|
||||
switch (state) {
|
||||
case State::STOPPED_STEP:
|
||||
if (should_continue_step(symbols))
|
||||
return;
|
||||
break;
|
||||
case State::STOPPED_NEXT:
|
||||
if (should_continue_step(symbols)) {
|
||||
do_next();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
frontend->repl(bp_no);
|
||||
}
|
||||
|
||||
void Debugger::on_sleep() {
|
||||
frontend->on_sleep();
|
||||
}
|
||||
|
||||
void Debugger::on_palette_change() {
|
||||
frontend->on_palette_change();
|
||||
}
|
||||
|
||||
uint8_t Debugger::handle_breakpoint(int page, int addr) {
|
||||
auto bp = breakpoints.find({page, addr});
|
||||
if (bp == breakpoints.end())
|
||||
sys_error("Illegal BREAKPOINT instruction");
|
||||
|
||||
uint8_t restore_op = bp->second.restore_op;
|
||||
|
||||
state = bp->second.no == INTERNAL_BREAKPOINT_NO ? State::STOPPED_NEXT : State::STOPPED_BREAKPOINT;
|
||||
|
||||
repl(bp->second.no); // this may destroy bp
|
||||
return restore_op;
|
||||
}
|
||||
|
||||
bool Debugger::console_vprintf(int lv, const char *format, va_list ap) {
|
||||
return false;
|
||||
}
|
||||
void Debugger::post_command(void *data) {
|
||||
}
|
||||
|
||||
int Debugger::set_breakpoint(int page, int addr, bool is_internal)
|
||||
{
|
||||
int restore_op = g_nact->sco.write_instruction(page, addr, BREAKPOINT_INSTRUCTION);
|
||||
if (restore_op == -1)
|
||||
return -1;
|
||||
if (restore_op == BREAKPOINT_INSTRUCTION) {
|
||||
auto bp = breakpoints.find({page, addr});
|
||||
if (bp != breakpoints.end()) {
|
||||
sys_error("Illegal BREAKPOINT instruction");
|
||||
} else {
|
||||
return bp->second.no;
|
||||
}
|
||||
}
|
||||
int no = is_internal ? INTERNAL_BREAKPOINT_NO : next_breakpoint_no++;
|
||||
breakpoints[{page, addr}] = {page, addr, no, static_cast<uint8_t>(restore_op)};
|
||||
return no;
|
||||
}
|
||||
|
||||
bool Debugger::delete_breakpoint(int no)
|
||||
{
|
||||
for (auto it = breakpoints.begin(); it != breakpoints.end(); ++it) {
|
||||
if (it->second.no == no) {
|
||||
g_nact->sco.write_instruction(it->second.page, it->second.addr, it->second.restore_op);
|
||||
breakpoints.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Debugger::delete_breakpoints_in_page(int page)
|
||||
{
|
||||
auto lower = breakpoints.lower_bound({page, 0});
|
||||
auto upper = breakpoints.upper_bound({page, 0xffff});
|
||||
for (auto it = lower; it != upper; ++it) {
|
||||
g_nact->sco.write_instruction(it->second.page, it->second.addr, it->second.restore_op);
|
||||
}
|
||||
breakpoints.erase(lower, upper);
|
||||
}
|
||||
|
||||
void Debugger::stepin()
|
||||
{
|
||||
step_exec_state.page = g_nact->sco.page();
|
||||
step_exec_state.line = symbols.addr2line(step_exec_state.page, g_nact->sco.cmd_addr());
|
||||
state = State::STOPPED_STEP;
|
||||
}
|
||||
|
||||
void Debugger::stepout()
|
||||
{
|
||||
// Set an internal breakpoint at the return address of current frame.
|
||||
auto& call_stack = g_nact->sco.get_call_stack();
|
||||
if (call_stack.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto& frame = call_stack.back();
|
||||
set_breakpoint(frame.page, frame.addr, true);
|
||||
}
|
||||
|
||||
void Debugger::next()
|
||||
{
|
||||
step_exec_state.page = g_nact->sco.page();
|
||||
step_exec_state.line = symbols.addr2line(step_exec_state.page, g_nact->sco.cmd_addr());
|
||||
state = State::STOPPED_NEXT;
|
||||
do_next();
|
||||
}
|
||||
|
||||
void Debugger::do_next()
|
||||
{
|
||||
assert(state == State::STOPPED_NEXT);
|
||||
|
||||
int retaddr = get_retaddr_if_funcall();
|
||||
if (retaddr >= 0) {
|
||||
set_breakpoint(step_exec_state.page, retaddr, true);
|
||||
state = State::RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<StackFrame> Debugger::stack_trace()
|
||||
{
|
||||
std::vector<StackFrame> frames;
|
||||
const char *src = symbols.page2src(g_nact->sco.page());
|
||||
int line = symbols.addr2line(g_nact->sco.page(), g_nact->sco.cmd_addr());
|
||||
frames.emplace_back(g_nact->sco.page(), g_nact->sco.cmd_addr(), src, line);
|
||||
|
||||
auto& call_stack = g_nact->sco.get_call_stack();
|
||||
std::for_each(call_stack.rbegin(), call_stack.rend(), [&](const auto& frame) {
|
||||
const char* src = symbols.page2src(frame.page);
|
||||
// -1 because the return address is the address of the next command
|
||||
int line = symbols.addr2line(frame.page, frame.addr - 1);
|
||||
frames.emplace_back(frame.page, frame.addr, src, line);
|
||||
});
|
||||
return frames;
|
||||
}
|
||||
|
||||
} // namespace debugger
|
||||
|
||||
std::unique_ptr<debugger::Debugger> g_debugger;
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef _DEBUGGER_H_
|
||||
#define _DEBUGGER_H_
|
||||
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
|
||||
#include <memory>
|
||||
#include <stdarg.h>
|
||||
#include "config.h"
|
||||
#include "debugger/debug_info.h"
|
||||
|
||||
namespace debugger {
|
||||
|
||||
const uint8_t BREAKPOINT_INSTRUCTION = 0x0f;
|
||||
|
||||
class Frontend;
|
||||
|
||||
enum class State {
|
||||
RUNNING,
|
||||
STOPPED_ENTRY,
|
||||
STOPPED_STEP,
|
||||
STOPPED_NEXT,
|
||||
STOPPED_BREAKPOINT,
|
||||
STOPPED_INTERRUPT,
|
||||
STOPPED_EXCEPTION,
|
||||
};
|
||||
|
||||
struct StackFrame {
|
||||
int page;
|
||||
int addr;
|
||||
const char *src;
|
||||
int line;
|
||||
|
||||
StackFrame(int page, int addr, const char *src, int line)
|
||||
: page(page), addr(addr), src(src), line(line) {}
|
||||
};
|
||||
|
||||
class Debugger {
|
||||
public:
|
||||
Debugger(const char *symbols_path, DebuggerMode mode);
|
||||
~Debugger();
|
||||
void repl(int bp_no);
|
||||
|
||||
// API for VM
|
||||
bool trapped() const { return state != State::RUNNING; }
|
||||
void on_sleep();
|
||||
void on_palette_change();
|
||||
uint8_t handle_breakpoint(int page, int addr);
|
||||
bool console_vprintf(int lv, const char *format, va_list ap);
|
||||
void post_command(void *data);
|
||||
|
||||
// API for frontend
|
||||
State get_state() const { return state; }
|
||||
void set_state(State s) { state = s; }
|
||||
int set_breakpoint(int page, int addr, bool is_internal);
|
||||
bool delete_breakpoint(int no);
|
||||
void delete_breakpoints_in_page(int page);
|
||||
void stepin();
|
||||
void stepout();
|
||||
void next();
|
||||
std::vector<StackFrame> stack_trace();
|
||||
|
||||
private:
|
||||
State state = State::RUNNING;
|
||||
DebugInfo symbols;
|
||||
std::unique_ptr<Frontend> frontend;
|
||||
|
||||
void do_next();
|
||||
};
|
||||
|
||||
} // namespace debugger
|
||||
|
||||
extern std::unique_ptr<debugger::Debugger> g_debugger;
|
||||
|
||||
#endif // ENABLE_DEBUGGER
|
||||
|
||||
#endif /* _DEBUGGER_H_ */
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef _DEBUGGER_FRONTEND_H_
|
||||
#define _DEBUGGER_FRONTEND_H_
|
||||
|
||||
namespace debugger {
|
||||
|
||||
class Debugger;
|
||||
class DebugInfo;
|
||||
|
||||
class Frontend {
|
||||
public:
|
||||
static Frontend* create_cli(Debugger* backend, const DebugInfo& symbols);
|
||||
|
||||
Frontend(Debugger* backend, const DebugInfo& symbols) : backend(backend), symbols(symbols) {}
|
||||
virtual ~Frontend() = default;
|
||||
virtual void repl(int bp_no) = 0;
|
||||
virtual void on_sleep() = 0;
|
||||
virtual void on_palette_change() = 0;
|
||||
virtual void console_output(int lv, const char *output) = 0;
|
||||
protected:
|
||||
Debugger* backend;
|
||||
const DebugInfo& symbols;
|
||||
};
|
||||
|
||||
} // namespace debugger
|
||||
|
||||
#endif /* _DEBUGGER_FRONTEND_H_ */
|
||||
@@ -44,6 +44,13 @@ public:
|
||||
int val = getc();
|
||||
return val | (getc() << 8);
|
||||
}
|
||||
uint32_t getdw() {
|
||||
uint32_t val = getc();
|
||||
val |= getc() << 8;
|
||||
val |= getc() << 16;
|
||||
val |= getc() << 24;
|
||||
return val;
|
||||
}
|
||||
void putc(int c) { fputc(c, fp); }
|
||||
void putw(int w) {
|
||||
putc(w & 0xff);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "fileio.h"
|
||||
#include "texthook.h"
|
||||
#include "sys/nact.h"
|
||||
#include "debugger/debugger.h"
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#else
|
||||
@@ -108,6 +109,12 @@ int main(int argc, char *argv[])
|
||||
texthook_set_mode(config.texthook_mode);
|
||||
texthook_set_suppression_list(config.texthook_suppressions.c_str());
|
||||
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
if (config.debugger_mode != DebuggerMode::DISABLED) {
|
||||
g_debugger = std::make_unique<debugger::Debugger>("ADISK.DAT.symbols", config.debugger_mode);
|
||||
}
|
||||
#endif
|
||||
|
||||
int exit_code = 0;
|
||||
while (g_nact) {
|
||||
exit_code = g_nact->mainloop();
|
||||
|
||||
+1
-2
@@ -39,8 +39,7 @@ void MsgSkip::load_from_file()
|
||||
{
|
||||
auto fio = FILEIO::open(MSGSKIP_FILENAME, FILEIO_READ_BINARY | FILEIO_SAVEDATA);
|
||||
if (fio) {
|
||||
uint32 size = fio->getw();
|
||||
size |= fio->getw() << 16;
|
||||
uint32 size = fio->getdw();
|
||||
if (size == BLOOM_FILTER_SIZE)
|
||||
fio->read(bloom, BLOOM_FILTER_SIZE / 8);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "../config.h"
|
||||
#include "../fileio.h"
|
||||
#include "game_id.h"
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
extern SDL_Window* g_window;
|
||||
|
||||
@@ -86,6 +87,12 @@ int NACT::mainloop()
|
||||
|
||||
int sleep_cnt = 0;
|
||||
while(!terminate) {
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
if (g_debugger && g_debugger->trapped()) {
|
||||
sco.update_cmd_addr();
|
||||
g_debugger->repl(0);
|
||||
}
|
||||
#endif
|
||||
execute();
|
||||
// 512コマンド実行毎にSleep(10)
|
||||
if(!(sleep_cnt = (sleep_cnt + 1) & 0x1ff)) {
|
||||
@@ -132,6 +139,14 @@ void NACT::execute()
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
if (cmd == debugger::BREAKPOINT_INSTRUCTION) {
|
||||
if (!g_debugger)
|
||||
sys_error("Illegal BREAKPOINT instruction");
|
||||
cmd = g_debugger->handle_breakpoint(sco.page(), sco.cmd_addr());
|
||||
}
|
||||
#endif
|
||||
|
||||
switch(cmd) {
|
||||
case '!':
|
||||
cmd_calc();
|
||||
@@ -817,6 +832,10 @@ void NACT::wait_after_open_menu()
|
||||
void NACT::sys_sleep(int ms) {
|
||||
pump_events();
|
||||
ags->update_screen();
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
if (g_debugger)
|
||||
g_debugger->on_sleep();
|
||||
#endif
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_sleep(ms);
|
||||
#else
|
||||
|
||||
+9
-5
@@ -80,6 +80,7 @@
|
||||
#define MAX_VERB 128
|
||||
#define MAX_OBJ 256
|
||||
#define MAX_PCM 256
|
||||
#define MAX_STRVAR 10
|
||||
|
||||
class AGS;
|
||||
class MAKO;
|
||||
@@ -97,6 +98,8 @@ public:
|
||||
NACT(const Config& config, const GameId& game_id);
|
||||
virtual ~NACT();
|
||||
|
||||
Scenario sco;
|
||||
|
||||
protected:
|
||||
AGS* ags;
|
||||
MAKO* mako;
|
||||
@@ -105,13 +108,11 @@ protected:
|
||||
// コマンドパーサ
|
||||
void execute();
|
||||
|
||||
Scenario sco;
|
||||
|
||||
// 変数
|
||||
uint16 var[512] = {};
|
||||
uint16 var_stack[30][20] = {};
|
||||
char tvar[10][33] = {};
|
||||
char tvar_stack[30][10][22] = {};
|
||||
char tvar[MAX_STRVAR][33] = {};
|
||||
char tvar_stack[30][MAX_STRVAR][22] = {};
|
||||
int tvar_index = 0;
|
||||
int tvar_maxlen;
|
||||
|
||||
@@ -194,7 +195,6 @@ protected:
|
||||
int pcm[MAX_PCM] = {};
|
||||
|
||||
// 下位関数
|
||||
virtual uint16 cali() = 0;
|
||||
bool is_message(uint8_t c) { return c == ' ' || c & 0x80; }
|
||||
|
||||
uint16 random(uint16 range);
|
||||
@@ -237,6 +237,8 @@ public:
|
||||
void text_wait();
|
||||
void set_skip_menu_state(bool enabled, bool checked);
|
||||
|
||||
virtual uint16 cali() = 0;
|
||||
|
||||
bool mouse_move_enabled = true;
|
||||
const Config& config;
|
||||
const GameId& game_id;
|
||||
@@ -247,6 +249,8 @@ public:
|
||||
void output_console(const char *format, ...);
|
||||
|
||||
int get_scenario_page() const { return sco.page(); }
|
||||
uint16 get_var(int index) const { return var[index]; }
|
||||
const char* get_string(int index) const { return tvar[index]; }
|
||||
|
||||
private:
|
||||
void pump_events();
|
||||
|
||||
+42
-3
@@ -6,17 +6,44 @@
|
||||
|
||||
uint8_t Scenario::fetch_command()
|
||||
{
|
||||
if (addr_ < 2 || static_cast<size_t>(addr_) >= data_.size())
|
||||
if (addr_ < 2 || static_cast<size_t>(addr_) >= data_size_)
|
||||
sys_error("Scenario error: invalid address %d:%04x", page_, addr_);
|
||||
|
||||
// Skip SysEng's "new style" marker
|
||||
if (page_ == 0 && addr_ == 2 && data_[2] == 'R' && data_[3] == 'E' && data_[4] == 'V')
|
||||
addr_ = 5;
|
||||
|
||||
cmd_addr_ = addr_;
|
||||
update_cmd_addr();
|
||||
return getd();
|
||||
}
|
||||
|
||||
std::vector<uint8_t>* Scenario::load_page(int page)
|
||||
{
|
||||
if (static_cast<size_t>(page) < pages_.size() && !pages_[page].empty())
|
||||
return &pages_[page];
|
||||
std::vector<uint8_t> data = adisk.load(page + 1);
|
||||
if (data.empty())
|
||||
return nullptr;
|
||||
if (static_cast<size_t>(page) >= pages_.size())
|
||||
pages_.resize(page + 1);
|
||||
pages_[page] = std::move(data);
|
||||
return &pages_[page];
|
||||
}
|
||||
|
||||
void Scenario::page_jump(int page, int addr)
|
||||
{
|
||||
std::vector<uint8_t>* page_data = load_page(page);
|
||||
if (page_data) {
|
||||
data_ = page_data->data();
|
||||
data_size_ = page_data->size();
|
||||
} else {
|
||||
data_ = nullptr;
|
||||
data_size_ = 0;
|
||||
}
|
||||
page_ = page;
|
||||
cmd_addr_ = addr_ = addr;
|
||||
}
|
||||
|
||||
void Scenario::skip_syseng_string(Encoding *enc, uint8_t terminator)
|
||||
{
|
||||
for (uint8_t c = getd(); c != terminator; c = getd()) {
|
||||
@@ -136,4 +163,16 @@ void Scenario::page_stack_clear()
|
||||
} else {
|
||||
sys_error("Unknown Command: %02x at %d:%04x", cmd, page_, cmd_addr_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Scenario::write_instruction(int page, int addr, uint8_t op)
|
||||
{
|
||||
std::vector<uint8_t>* page_data = load_page(page);
|
||||
if (!page_data)
|
||||
return -1;
|
||||
if (addr < 0 || addr >= static_cast<int>(page_data->size()))
|
||||
return -1;
|
||||
uint8_t old_op = (*page_data)[addr];
|
||||
(*page_data)[addr] = op;
|
||||
return old_op;
|
||||
}
|
||||
|
||||
+7
-6
@@ -11,11 +11,7 @@ class Encoding;
|
||||
class Scenario {
|
||||
public:
|
||||
void open(const char* file_name) { adisk.open(file_name); }
|
||||
void page_jump(int page, int addr) {
|
||||
data_ = adisk.load(page + 1);
|
||||
page_ = page;
|
||||
cmd_addr_ = addr_ = addr;
|
||||
}
|
||||
void page_jump(int page, int addr);
|
||||
int default_addr() { return data_[0] | data_[1] << 8; }
|
||||
|
||||
int page() const { return page_; }
|
||||
@@ -28,6 +24,7 @@ public:
|
||||
void skip(int n) { addr_ += n; }
|
||||
|
||||
uint8_t fetch_command();
|
||||
void update_cmd_addr() { cmd_addr_ = addr_; }
|
||||
|
||||
uint8_t getd() { return data_[addr_++]; }
|
||||
uint16_t getw() {
|
||||
@@ -63,8 +60,12 @@ public:
|
||||
int write_instruction(int page, int addr, uint8_t op);
|
||||
|
||||
private:
|
||||
std::vector<uint8_t>* load_page(int page);
|
||||
|
||||
Dri adisk;
|
||||
std::vector<uint8_t> data_;
|
||||
std::vector<std::vector<uint8_t>> pages_;
|
||||
const uint8_t* data_;
|
||||
size_t data_size_;
|
||||
int page_;
|
||||
int addr_;
|
||||
int cmd_addr_;
|
||||
|
||||
Reference in New Issue
Block a user