mirror of
https://github.com/kichikuou/system3-sdl2.git
synced 2026-09-22 22:58:12 +03:00
Add support for Debug Adapter Protocol
This commit is contained in:
+8
-4
@@ -67,10 +67,6 @@ 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)
|
||||
|
||||
@@ -117,6 +113,13 @@ else() # NOT (ANDROID OR EMSCRIPTEN)
|
||||
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2)
|
||||
pkg_check_modules(SDL2TTF REQUIRED IMPORTED_TARGET SDL2_ttf)
|
||||
target_link_libraries(system3 PRIVATE PkgConfig::SDL2 PkgConfig::SDL2TTF)
|
||||
|
||||
find_package(nlohmann_json 3.2.0)
|
||||
if (nlohmann_json_FOUND)
|
||||
set(ENABLE_DEBUGGER ON)
|
||||
target_link_libraries(system3 PRIVATE nlohmann_json::nlohmann_json)
|
||||
endif()
|
||||
|
||||
pkg_check_modules(RTMIDI IMPORTED_TARGET rtmidi)
|
||||
if (RTMIDI_FOUND)
|
||||
target_compile_definitions(system3 PRIVATE USE_MIDI)
|
||||
@@ -186,6 +189,7 @@ if (ENABLE_DEBUGGER)
|
||||
src/debugger/debug_info.cpp
|
||||
src/debugger/debugger.cpp
|
||||
src/debugger/cli_frontend.cpp
|
||||
src/debugger/dap_frontend.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ inline void strcpy_s(char* dst, size_t n, const char* src)
|
||||
#define strcasecmp _stricmp
|
||||
#endif
|
||||
|
||||
extern uint32_t sdl_custom_event_type;
|
||||
enum CustomEvent {
|
||||
DEBUGGER_COMMAND,
|
||||
};
|
||||
|
||||
// resource.cpp
|
||||
SDL_RWops* open_resource(const char* name, const char* type);
|
||||
SDL_RWops* open_file(const char* name);
|
||||
|
||||
@@ -132,6 +132,8 @@ Config::Config(int argc, char *argv[])
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-gamedir") == 0)
|
||||
++i;
|
||||
else if (strcmp(argv[i], "-version") == 0)
|
||||
print_version = true;
|
||||
else if (strcmp(argv[i], "-noantialias") == 0)
|
||||
no_antialias = true;
|
||||
else if (strcmp(argv[i], "-savedir") == 0)
|
||||
|
||||
@@ -41,6 +41,7 @@ struct Config {
|
||||
std::string playlist;
|
||||
std::string title;
|
||||
int midi_device = -1;
|
||||
bool print_version = false;
|
||||
bool use_fm = false;
|
||||
bool no_antialias = false;
|
||||
bool scanline = false;
|
||||
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
|
||||
~CliFrontend() override = default;
|
||||
|
||||
void init() override {}
|
||||
|
||||
void repl(int bp_no) override {
|
||||
if (backend->get_state() == State::STOPPED_BREAKPOINT && bp_no)
|
||||
printf("Breakpoint %d\n", bp_no);
|
||||
@@ -67,6 +69,9 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_command(void* data) override {}
|
||||
|
||||
void on_sleep() override {
|
||||
if (backend->get_state() == State::STOPPED_INTERRUPT)
|
||||
backend->repl(0);
|
||||
@@ -75,7 +80,8 @@ public:
|
||||
void on_palette_change() override {
|
||||
}
|
||||
|
||||
void console_output(int lv, const char *output) override {
|
||||
bool console_output(const char* format, va_list ap) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,512 @@
|
||||
#define JSON_USE_IMPLICIT_CONVERSIONS 0
|
||||
|
||||
#include "debugger/frontend.h"
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <SDL.h>
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "common.h"
|
||||
#include "encoding.h"
|
||||
#include "nact.h"
|
||||
#include "debugger/debugger.h"
|
||||
using Json = nlohmann::json;
|
||||
|
||||
namespace debugger {
|
||||
|
||||
namespace {
|
||||
|
||||
enum VariablesReference {
|
||||
VREF_GLOBALS = 1,
|
||||
VREF_STRINGS,
|
||||
};
|
||||
|
||||
void post_debugger_command(void *data) {
|
||||
SDL_Event event = {};
|
||||
event.user.type = sdl_custom_event_type;
|
||||
event.user.code = DEBUGGER_COMMAND;
|
||||
event.user.data1 = data;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
int read_command_thread(void*) {
|
||||
int content_length = -1;
|
||||
char header[512];
|
||||
while (fgets(header, sizeof(header), stdin)) {
|
||||
if (sscanf(header, "Content-Length: %d", &content_length) == 1) {
|
||||
continue;
|
||||
} else if ((header[0] == '\r' && header[1] == '\n') || header[0] == '\n') {
|
||||
if (content_length < 0) {
|
||||
fprintf(stderr, "Debug Adapter Protocol error: no Content-Length header\n");
|
||||
continue;
|
||||
}
|
||||
char* buf = (char*)malloc(content_length + 1);
|
||||
fread(buf, content_length, 1, stdin);
|
||||
buf[content_length] = '\0';
|
||||
post_debugger_command(buf);
|
||||
content_length = -1;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown Debug Adapter Protocol header: %s", header);
|
||||
}
|
||||
}
|
||||
post_debugger_command(NULL); // end of messages
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class DapFrontend : public Frontend {
|
||||
public:
|
||||
DapFrontend(Debugger* backend, const DebugInfo& symbols) : Frontend(backend, symbols) {
|
||||
SDL_CreateThread(read_command_thread, "Debugger", NULL);
|
||||
}
|
||||
|
||||
void init() override {
|
||||
while (!initialized && !g_nact->is_terminating()) {
|
||||
g_nact->process_next_event();
|
||||
while (!queue.empty()) {
|
||||
char* msg = queue.front();
|
||||
queue.pop();
|
||||
if (!msg)
|
||||
return;
|
||||
handle_message(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void repl(int bp_no) override {
|
||||
emit_stopped_event();
|
||||
backend->set_state(State::RUNNING);
|
||||
|
||||
bool continue_repl = true;
|
||||
while (continue_repl && !g_nact->is_terminating()) {
|
||||
g_nact->process_next_event();
|
||||
while (!queue.empty()) {
|
||||
char* msg = queue.front();
|
||||
queue.pop();
|
||||
if (!msg)
|
||||
return;
|
||||
continue_repl = handle_message(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_command(void* data) override {
|
||||
queue.push(static_cast<char*>(data));
|
||||
}
|
||||
|
||||
void on_sleep() override {
|
||||
while (!queue.empty()) {
|
||||
char* msg = queue.front();
|
||||
queue.pop();
|
||||
if (!msg)
|
||||
return;
|
||||
handle_message(msg);
|
||||
}
|
||||
if (backend->get_state() == State::STOPPED_INTERRUPT || backend->get_state() == State::STOPPED_EXCEPTION)
|
||||
backend->repl(0);
|
||||
}
|
||||
|
||||
void on_palette_change() override {
|
||||
}
|
||||
|
||||
bool console_output(const char* format, va_list ap) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Json create_source(const char *name) {
|
||||
Json source;
|
||||
source["name"] = name;
|
||||
if (!src_dir.empty())
|
||||
source["path"] = src_dir + "/" + name;
|
||||
source["sourceReference"] = 0;
|
||||
return source;
|
||||
}
|
||||
|
||||
std::string format_string_value(const char* str) {
|
||||
char *utf = g_nact->encoding->toUtf8(str);
|
||||
std::string value = "\"";
|
||||
value += utf;
|
||||
value += "\"";
|
||||
free(utf);
|
||||
return value;
|
||||
}
|
||||
|
||||
void send_json(Json& json) {
|
||||
json["seq"] = ++seq_;
|
||||
std::string str = json.dump();
|
||||
printf("Content-Length: %zu\r\n\r\n%s", str.size(), str.c_str());
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void emit_initialized_event() {
|
||||
Json json = {
|
||||
{"type", "event"},
|
||||
{"event", "initialized"},
|
||||
};
|
||||
send_json(json);
|
||||
}
|
||||
|
||||
void emit_stopped_event() {
|
||||
std::string reason;
|
||||
switch (backend->get_state()) {
|
||||
case State::STOPPED_ENTRY: reason = "entry"; break;
|
||||
case State::STOPPED_STEP: reason = "step"; break;
|
||||
case State::STOPPED_NEXT: reason = "step"; break;
|
||||
case State::STOPPED_BREAKPOINT: reason = "breakpoint"; break;
|
||||
case State::STOPPED_INTERRUPT: reason = "pause"; break;
|
||||
case State::STOPPED_EXCEPTION: reason = "exception"; break;
|
||||
default: reason = "unknown"; break;
|
||||
}
|
||||
Json json = {
|
||||
{"type", "event"},
|
||||
{"event", "stopped"},
|
||||
{"body", {
|
||||
{"reason", reason},
|
||||
{"allThreadsStopped", true},
|
||||
}}
|
||||
};
|
||||
send_json(json);
|
||||
}
|
||||
|
||||
bool handle_message(char* msg) {
|
||||
Json json = Json::parse(msg);
|
||||
auto type = json["type"].get<std::string>();
|
||||
bool continue_repl = true;
|
||||
if (type == "request")
|
||||
continue_repl = handle_request(json);
|
||||
free(msg);
|
||||
return continue_repl;
|
||||
}
|
||||
|
||||
bool handle_request(Json& request) {
|
||||
Json resp;
|
||||
resp["type"] = "response";
|
||||
resp["request_seq"] = request["seq"];
|
||||
resp["command"] = request["command"];
|
||||
auto command = request["command"].get<std::string>();
|
||||
Json& args = request["arguments"];
|
||||
|
||||
bool continue_repl = true;
|
||||
if (command == "initialize") {
|
||||
cmd_initialize(args, resp);
|
||||
} else if (command == "disconnect") {
|
||||
cmd_disconnect(args, resp);
|
||||
continue_repl = false;
|
||||
} else if (command == "launch") {
|
||||
cmd_launch(args, resp);
|
||||
} else if (command == "configurationDone") {
|
||||
cmd_configurationDone(args, resp);
|
||||
} else if (command == "threads") {
|
||||
cmd_threads(args, resp);
|
||||
} else if (command == "scopes") {
|
||||
cmd_scopes(args, resp);
|
||||
} else if (command == "variables") {
|
||||
cmd_variables(args, resp);
|
||||
} else if (command == "setVariable") {
|
||||
cmd_setVariable(args, resp);
|
||||
} else if (command == "stackTrace") {
|
||||
cmd_stackTrace(args, resp);
|
||||
} else if (command == "evaluate") {
|
||||
cmd_evaluate(args, resp);
|
||||
} else if (command == "setBreakpoints") {
|
||||
cmd_setBreakpoints(args, resp);
|
||||
} else if (command == "continue") {
|
||||
cmd_continue(args, resp);
|
||||
continue_repl = false;
|
||||
} else if (command == "pause") {
|
||||
cmd_pause(args, resp);
|
||||
} else if (command == "stepIn") {
|
||||
cmd_stepIn(args, resp);
|
||||
continue_repl = false;
|
||||
} else if (command == "stepOut") {
|
||||
cmd_stepOut(args, resp);
|
||||
continue_repl = false;
|
||||
} else if (command == "next") {
|
||||
cmd_next(args, resp);
|
||||
continue_repl = false;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown command '%s'\n", command.c_str());
|
||||
resp["success"] = false;
|
||||
resp["message"] = "Unknown command " + command;
|
||||
}
|
||||
send_json(resp);
|
||||
return continue_repl;
|
||||
}
|
||||
|
||||
void cmd_initialize(Json& args, Json& resp) {
|
||||
resp["success"] = true;
|
||||
resp["body"] = {
|
||||
{"supportsConfigurationDoneRequest", true},
|
||||
{"supportsEvaluateForHovers", true},
|
||||
{"supportsSetVariable", true},
|
||||
};
|
||||
}
|
||||
|
||||
void cmd_disconnect(Json& args, Json& resp) {
|
||||
resp["success"] = true;
|
||||
g_nact->quit(0);
|
||||
}
|
||||
|
||||
void cmd_launch(Json& args, Json& resp) {
|
||||
if (args["noDebug"].is_boolean() && args["noDebug"].get<bool>()) {
|
||||
resp["success"] = true;
|
||||
initialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!symbols.loaded()) {
|
||||
resp["success"] = false;
|
||||
resp["message"] = "system3: Cannot load debug symbols";
|
||||
return;
|
||||
}
|
||||
|
||||
if (args["srcDir"].is_string())
|
||||
src_dir = args["srcDir"].get<std::string>();
|
||||
if (args["stopOnEntry"].is_boolean() && args["stopOnEntry"].get<bool>())
|
||||
backend->set_state(State::STOPPED_ENTRY);
|
||||
resp["success"] = true;
|
||||
|
||||
emit_initialized_event();
|
||||
}
|
||||
|
||||
void cmd_configurationDone(Json& args, Json& resp) {
|
||||
initialized = true;
|
||||
resp["success"] = true;
|
||||
}
|
||||
|
||||
void cmd_threads(Json& args, Json& resp) {
|
||||
resp["success"] = true;
|
||||
resp["body"] = {
|
||||
{"threads", {
|
||||
{{"id", 1}, {"name", "main thread"}},
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
void cmd_scopes(Json& args, Json& resp) {
|
||||
resp["success"] = true;
|
||||
resp["body"] = {
|
||||
{"scopes", {
|
||||
{
|
||||
{"name", "All Variables"},
|
||||
{"variablesReference", VREF_GLOBALS},
|
||||
{"namedVariables", symbols.num_variables()},
|
||||
{"expensive", false}
|
||||
},
|
||||
{
|
||||
{"name", "Strings"},
|
||||
{"variablesReference", VREF_STRINGS},
|
||||
{"indexedVariables", MAX_STRVAR},
|
||||
{"expensive", false}
|
||||
},
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
void cmd_variables(Json& args, Json& resp) {
|
||||
const Json& start_ = args["start"];
|
||||
const Json& count_ = args["count"];
|
||||
int start = start_.is_number() ? start_.get<int>() : 0;
|
||||
int count = count_.is_number() ? count_.get<int>() : MAX_VAR;
|
||||
|
||||
int var_ref = args["variablesReference"].get<int>();
|
||||
if (var_ref == VREF_GLOBALS) {
|
||||
resp["success"] = true;
|
||||
Json& variables = resp["body"]["variables"];
|
||||
int end = std::min(start + count, static_cast<int>(symbols.num_variables()));
|
||||
for (int i = start; i < end; i++) {
|
||||
Json item = {
|
||||
{"name", symbols.variable_name(i)},
|
||||
{"value", std::to_string(g_nact->get_var(i))},
|
||||
{"variablesReference", 0},
|
||||
};
|
||||
variables.push_back(std::move(item));
|
||||
}
|
||||
} else if (var_ref == VREF_STRINGS) {
|
||||
resp["success"] = true;
|
||||
Json& variables = resp["body"]["variables"];
|
||||
int end = std::min(start + count, MAX_STRVAR);
|
||||
for (int i = start; i < end; i++) {
|
||||
char name[10];
|
||||
snprintf(name, sizeof(name), "[%d]", i + 1);
|
||||
Json item = {
|
||||
{"name", name},
|
||||
{"value", format_string_value(g_nact->get_string(i))},
|
||||
{"variablesReference", 0},
|
||||
};
|
||||
variables.push_back(std::move(item));
|
||||
}
|
||||
} else {
|
||||
resp["success"] = false;
|
||||
resp["message"] = "Invalid variables reference";
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_setVariable(Json& args, Json& resp) {
|
||||
switch (args["variablesReference"].get<int>()) {
|
||||
case VREF_GLOBALS:
|
||||
{
|
||||
int var = symbols.lookup_variable(args["name"].get<std::string>().c_str());
|
||||
if (var < 0) {
|
||||
resp["success"] = false;
|
||||
resp["message"] = "Invalid variable name";
|
||||
return;
|
||||
}
|
||||
int parsed_value;
|
||||
if (sscanf(args["value"].get<std::string>().c_str(), "%i", &parsed_value) != 1) {
|
||||
resp["success"] = false;
|
||||
resp["message"] = "Syntax error";
|
||||
return;
|
||||
}
|
||||
g_nact->set_var(var, parsed_value);
|
||||
resp["success"] = true;
|
||||
resp["body"] = {
|
||||
{"value", std::to_string(g_nact->get_var(var))},
|
||||
};
|
||||
}
|
||||
break;
|
||||
case VREF_STRINGS:
|
||||
{
|
||||
int index;
|
||||
if (sscanf(args["name"].get<std::string>().c_str(), "[%d]", &index) != 1 || index <= 0 || index > MAX_STRVAR) {
|
||||
resp["success"] = false;
|
||||
resp["message"] = "Invalid string index";
|
||||
return;
|
||||
}
|
||||
index--; // 1-based to 0-based
|
||||
|
||||
std::string value = args["value"].get<std::string>();
|
||||
if (value.size() < 2 || value[0] != '"' || value.back() != '"') {
|
||||
resp["success"] = false;
|
||||
resp["message"] = "Syntax error";
|
||||
return;
|
||||
}
|
||||
value = value.substr(1, value.size() - 2);
|
||||
|
||||
char* encoded = g_nact->encoding->fromUtf8(value.c_str());
|
||||
g_nact->set_string(index, encoded);
|
||||
free(encoded);
|
||||
|
||||
resp["success"] = true;
|
||||
resp["body"] = {
|
||||
{"value", format_string_value(g_nact->get_string(index))},
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
resp["success"] = false;
|
||||
resp["message"] = "Invalid variables reference";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_stackTrace(Json& args, Json& resp) {
|
||||
resp["success"] = true;
|
||||
Json& body = resp["body"];
|
||||
Json& stackFrames = body["stackFrames"];
|
||||
int i = 0;
|
||||
for (const StackFrame &frame : backend->stack_trace()) {
|
||||
Json& item = stackFrames[i++];
|
||||
item["id"] = i;
|
||||
item["name"] = frame.src;
|
||||
item["source"] = create_source(frame.src);
|
||||
item["line"] = frame.line;
|
||||
item["column"] = 0;
|
||||
}
|
||||
body["totalFrames"] = i;
|
||||
}
|
||||
|
||||
void cmd_evaluate(Json& args, Json& resp) {
|
||||
int var = symbols.lookup_variable(args["expression"].get<std::string>().c_str());
|
||||
if (var < 0) {
|
||||
resp["success"] = false;
|
||||
resp["message"] = "Invalid expression";
|
||||
return;
|
||||
}
|
||||
resp["success"] = true;
|
||||
resp["body"] = {
|
||||
{"result", std::to_string(g_nact->get_var(var))},
|
||||
{"variablesReference", 0},
|
||||
};
|
||||
}
|
||||
|
||||
void cmd_setBreakpoints(Json& args, Json& resp) {
|
||||
const auto filename = args["source"]["name"].get<std::string>();
|
||||
int page = symbols.src2page(filename.c_str());
|
||||
|
||||
backend->delete_breakpoints_in_page(page);
|
||||
|
||||
resp["success"] = true;
|
||||
Json& body = resp["body"];
|
||||
Json& out_bps = body["breakpoints"];
|
||||
|
||||
for (const Json& srcbp : args["breakpoints"]) {
|
||||
Json& item = out_bps.emplace_back();
|
||||
|
||||
int line = srcbp["line"].get<int>();
|
||||
int addr = symbols.line2addr(page, line);
|
||||
if (page < 0) {
|
||||
item["verified"] = false;
|
||||
item["message"] = "no source file named " + filename;
|
||||
continue;
|
||||
}
|
||||
if (addr < 0) {
|
||||
item["verified"] = false;
|
||||
item["message"] = "no line " + std::to_string(line) + " in file " + filename;
|
||||
continue;
|
||||
}
|
||||
int bp_no = backend->set_breakpoint(page, addr, false);
|
||||
if (bp_no < 0) {
|
||||
char message[256];
|
||||
snprintf(message, sizeof(message), "failed to set breakpoint at %d:0x%x", page, addr);
|
||||
item["verified"] = false;
|
||||
item["message"] = message;
|
||||
continue;
|
||||
}
|
||||
|
||||
line = symbols.addr2line(page, addr);
|
||||
item["id"] = bp_no;
|
||||
item["verified"] = true;
|
||||
item["source"] = create_source(filename.c_str());
|
||||
item["line"] = line;
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_continue(Json& args, Json& resp) {
|
||||
// TODO: sdl_raiseWindow();
|
||||
resp["success"] = true;
|
||||
}
|
||||
|
||||
void cmd_pause(Json& args, Json& resp) {
|
||||
backend->set_state(State::STOPPED_INTERRUPT);
|
||||
resp["success"] = true;
|
||||
}
|
||||
|
||||
void cmd_stepIn(Json& args, Json& resp) {
|
||||
backend->stepin();
|
||||
resp["success"] = true;
|
||||
}
|
||||
|
||||
void cmd_stepOut(Json& args, Json& resp) {
|
||||
backend->stepout();
|
||||
resp["success"] = true;
|
||||
}
|
||||
|
||||
void cmd_next(Json& args, Json& resp) {
|
||||
backend->next();
|
||||
resp["success"] = true;
|
||||
}
|
||||
|
||||
std::queue<char*> queue;
|
||||
bool initialized = false;
|
||||
int seq_ = 0;
|
||||
std::string src_dir;
|
||||
};
|
||||
|
||||
// static
|
||||
Frontend* Frontend::create_dap(Debugger* backend, const DebugInfo& symbols) {
|
||||
return new DapFrontend(backend, symbols);
|
||||
}
|
||||
|
||||
} // namespace debugger
|
||||
@@ -65,7 +65,7 @@ int get_retaddr_if_funcall() {
|
||||
Debugger::Debugger(const char *symbols_path, DebuggerMode mode) {
|
||||
symbols.load(symbols_path);
|
||||
if (mode == DebuggerMode::DAP) {
|
||||
// frontend = std::make_unique<DapFrontend>(this);
|
||||
frontend = std::unique_ptr<Frontend>(Frontend::create_dap(this, symbols));
|
||||
} else {
|
||||
frontend = std::unique_ptr<Frontend>(Frontend::create_cli(this, symbols));
|
||||
}
|
||||
@@ -73,6 +73,10 @@ Debugger::Debugger(const char *symbols_path, DebuggerMode mode) {
|
||||
|
||||
Debugger::~Debugger() = default;
|
||||
|
||||
void Debugger::init() {
|
||||
frontend->init();
|
||||
}
|
||||
|
||||
void Debugger::repl(int bp_no) {
|
||||
delete_breakpoint(INTERNAL_BREAKPOINT_NO);
|
||||
|
||||
@@ -114,10 +118,12 @@ uint8_t Debugger::handle_breakpoint(int page, int addr) {
|
||||
return restore_op;
|
||||
}
|
||||
|
||||
bool Debugger::console_vprintf(int lv, const char *format, va_list ap) {
|
||||
return false;
|
||||
bool Debugger::console_vprintf(const char *format, va_list ap) {
|
||||
return frontend->console_output(format, ap);
|
||||
}
|
||||
|
||||
void Debugger::post_command(void *data) {
|
||||
frontend->on_command(data);
|
||||
}
|
||||
|
||||
int Debugger::set_breakpoint(int page, int addr, bool is_internal)
|
||||
|
||||
@@ -41,11 +41,12 @@ public:
|
||||
void repl(int bp_no);
|
||||
|
||||
// API for VM
|
||||
void init();
|
||||
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);
|
||||
bool console_vprintf(const char *format, va_list ap);
|
||||
void post_command(void *data);
|
||||
|
||||
// API for frontend
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef _DEBUGGER_FRONTEND_H_
|
||||
#define _DEBUGGER_FRONTEND_H_
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
namespace debugger {
|
||||
|
||||
class Debugger;
|
||||
@@ -9,13 +11,16 @@ class DebugInfo;
|
||||
class Frontend {
|
||||
public:
|
||||
static Frontend* create_cli(Debugger* backend, const DebugInfo& symbols);
|
||||
static Frontend* create_dap(Debugger* backend, const DebugInfo& symbols);
|
||||
|
||||
Frontend(Debugger* backend, const DebugInfo& symbols) : backend(backend), symbols(symbols) {}
|
||||
virtual ~Frontend() = default;
|
||||
virtual void init() = 0;
|
||||
virtual void repl(int bp_no) = 0;
|
||||
virtual void on_command(void* data) = 0;
|
||||
virtual void on_sleep() = 0;
|
||||
virtual void on_palette_change() = 0;
|
||||
virtual void console_output(int lv, const char *output) = 0;
|
||||
virtual bool console_output(const char* format, va_list ap) = 0;
|
||||
protected:
|
||||
Debugger* backend;
|
||||
const DebugInfo& symbols;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <stdio.h>
|
||||
#include "nact.h"
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
void NACT::text_dialog()
|
||||
{
|
||||
@@ -15,6 +16,16 @@ void NACT::platform_finalize()
|
||||
|
||||
void NACT::output_console(const char *format, ...)
|
||||
{
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
if (g_debugger) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
bool handled = g_debugger->console_vprintf(format, ap);
|
||||
va_end(ap);
|
||||
if (handled)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(_DEBUG_CONSOLE)
|
||||
va_list ap;
|
||||
|
||||
|
||||
@@ -69,16 +69,23 @@ SDL_Window* create_window(const GameId& game_id)
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t sdl_custom_event_type;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef __SWITCH__
|
||||
romfsInit();
|
||||
#endif
|
||||
Config config(argc, argv);
|
||||
if (config.print_version) {
|
||||
puts(SYSTEM3_VERSION);
|
||||
return 0;
|
||||
}
|
||||
GameId game_id(config);
|
||||
|
||||
g_window = create_window(game_id);
|
||||
g_renderer = SDL_CreateRenderer(g_window, -1, 0);
|
||||
sdl_custom_event_type = SDL_RegisterEvents(1);
|
||||
|
||||
// system3 初期化
|
||||
g_nact.reset(NACT::create(config, game_id));
|
||||
@@ -112,6 +119,7 @@ int main(int argc, char *argv[])
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
if (config.debugger_mode != DebuggerMode::DISABLED) {
|
||||
g_debugger = std::make_unique<debugger::Debugger>("ADISK.DAT.symbols", config.debugger_mode);
|
||||
g_debugger->init();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "nact.h"
|
||||
#include "encoding.h"
|
||||
#include "ags.h"
|
||||
@@ -843,6 +844,23 @@ void NACT::sys_sleep(int ms) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void NACT::set_string(int index, const char* value)
|
||||
{
|
||||
const char *src = value;
|
||||
char *dst = tvar[index];
|
||||
int remaining = sizeof(tvar[0]) - 1;
|
||||
while (*src) {
|
||||
int len = encoding->mblen(*src);
|
||||
if (len > remaining)
|
||||
break;
|
||||
memcpy(dst, src, len);
|
||||
src += len;
|
||||
dst += len;
|
||||
remaining -= len;
|
||||
}
|
||||
*dst = '\0';
|
||||
}
|
||||
|
||||
// WinMainとのインターフェース
|
||||
|
||||
int NACT::get_screen_height()
|
||||
|
||||
+7
-1
@@ -80,6 +80,7 @@
|
||||
#define MAX_VERB 128
|
||||
#define MAX_OBJ 256
|
||||
#define MAX_PCM 256
|
||||
#define MAX_VAR 512
|
||||
#define MAX_STRVAR 10
|
||||
|
||||
class AGS;
|
||||
@@ -109,7 +110,7 @@ protected:
|
||||
void execute();
|
||||
|
||||
// 変数
|
||||
uint16 var[512] = {};
|
||||
uint16 var[MAX_VAR] = {};
|
||||
uint16 var_stack[30][20] = {};
|
||||
char tvar[MAX_STRVAR][33] = {};
|
||||
char tvar_stack[30][MAX_STRVAR][22] = {};
|
||||
@@ -229,6 +230,8 @@ public:
|
||||
int mainloop();
|
||||
void sys_sleep(int ms);
|
||||
void quit(int code);
|
||||
void process_next_event();
|
||||
bool is_terminating() const { return terminate; }
|
||||
|
||||
int get_screen_height();
|
||||
|
||||
@@ -250,10 +253,13 @@ public:
|
||||
|
||||
int get_scenario_page() const { return sco.page(); }
|
||||
uint16 get_var(int index) const { return var[index]; }
|
||||
void set_var(int index, uint16_t value) { var[index] = value; }
|
||||
const char* get_string(int index) const { return tvar[index]; }
|
||||
void set_string(int index, const char* value);
|
||||
|
||||
private:
|
||||
void pump_events();
|
||||
void handle_event(SDL_Event e);
|
||||
bool handle_platform_event(const SDL_Event& e);
|
||||
};
|
||||
|
||||
|
||||
+77
-52
@@ -12,6 +12,7 @@
|
||||
#include "nact.h"
|
||||
#include "ags.h"
|
||||
#include "texthook.h"
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
enum TouchState {
|
||||
TOUCH_NONE,
|
||||
@@ -23,65 +24,89 @@ extern SDL_Window* g_window;
|
||||
static int mousex, mousey;
|
||||
static TouchState touch_state = TOUCH_NONE;
|
||||
|
||||
void NACT::handle_event(SDL_Event e)
|
||||
{
|
||||
if (handle_platform_event(e))
|
||||
return;
|
||||
|
||||
switch (e.type) {
|
||||
case SDL_QUIT:
|
||||
quit(0);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (e.window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
ags->flush_screen(false);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
mousex = e.motion.x * ags->screen_width / ags->window_width;
|
||||
mousey = e.motion.y * ags->screen_height / ags->window_height;
|
||||
break;
|
||||
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
case SDL_FINGERMOTION:
|
||||
mousex = e.tfinger.x * ags->screen_width;
|
||||
mousey = e.tfinger.y * ags->screen_height;
|
||||
switch (SDL_GetNumTouchFingers(e.tfinger.touchId)) {
|
||||
case 0:
|
||||
touch_state = TOUCH_NONE;
|
||||
break;
|
||||
case 1:
|
||||
// A touch outside of the viewport (SDL clamps it to 0.0-1.0) is
|
||||
// a right-click.
|
||||
if (e.tfinger.x == 0.0f || e.tfinger.x == 1.0f ||
|
||||
e.tfinger.y == 0.0f || e.tfinger.y == 1.0f) {
|
||||
touch_state = TOUCH_RBUTTON;
|
||||
} else {
|
||||
touch_state = TOUCH_LBUTTON;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
// Two-finger touch is a right-click.
|
||||
touch_state = TOUCH_RBUTTON;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_APP_DIDENTERFOREGROUND:
|
||||
ags->flush_screen(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
if (e.type == sdl_custom_event_type) {
|
||||
switch (e.user.code) {
|
||||
case DEBUGGER_COMMAND:
|
||||
g_debugger->post_command(e.user.data1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NACT::pump_events()
|
||||
{
|
||||
SDL_Event e;
|
||||
while (SDL_PollEvent(&e)) {
|
||||
if (handle_platform_event(e))
|
||||
continue;
|
||||
|
||||
switch (e.type) {
|
||||
case SDL_QUIT:
|
||||
quit(0);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (e.window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
ags->flush_screen(false);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
mousex = e.motion.x * ags->screen_width / ags->window_width;
|
||||
mousey = e.motion.y * ags->screen_height / ags->window_height;
|
||||
break;
|
||||
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
case SDL_FINGERMOTION:
|
||||
mousex = e.tfinger.x * ags->screen_width;
|
||||
mousey = e.tfinger.y * ags->screen_height;
|
||||
switch (SDL_GetNumTouchFingers(e.tfinger.touchId)) {
|
||||
case 0:
|
||||
touch_state = TOUCH_NONE;
|
||||
break;
|
||||
case 1:
|
||||
// A touch outside of the viewport (SDL clamps it to 0.0-1.0) is
|
||||
// a right-click.
|
||||
if (e.tfinger.x == 0.0f || e.tfinger.x == 1.0f ||
|
||||
e.tfinger.y == 0.0f || e.tfinger.y == 1.0f) {
|
||||
touch_state = TOUCH_RBUTTON;
|
||||
} else {
|
||||
touch_state = TOUCH_LBUTTON;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
// Two-finger touch is a right-click.
|
||||
touch_state = TOUCH_RBUTTON;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_APP_DIDENTERFOREGROUND:
|
||||
ags->flush_screen(false);
|
||||
break;
|
||||
}
|
||||
handle_event(std::move(e));
|
||||
}
|
||||
}
|
||||
|
||||
void NACT::process_next_event()
|
||||
{
|
||||
SDL_Event e;
|
||||
SDL_WaitEvent(&e);
|
||||
handle_event(std::move(e));
|
||||
}
|
||||
|
||||
uint8 NACT::get_key()
|
||||
{
|
||||
uint8 val = 0;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "msgskip.h"
|
||||
#include "texthook.h"
|
||||
#include "resource.h"
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
extern SDL_Window* g_window;
|
||||
|
||||
@@ -150,6 +151,16 @@ void NACT::platform_finalize()
|
||||
|
||||
void NACT::output_console(const char *format, ...)
|
||||
{
|
||||
#ifdef ENABLE_DEBUGGER
|
||||
if (g_debugger) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
bool handled = g_debugger->console_vprintf(format, ap);
|
||||
va_end(ap);
|
||||
if (handled)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(_DEBUG_CONSOLE)
|
||||
va_list ap;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user