mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-09-28 18:08:07 +03:00
xsystem4.scene: returns a list of scene entities
* all entities have an entityId that can be used in further requests
* SACT2 sprites have a 'sprite' member with a spriteId that can be
used in further requests
xsystem4.renderEntity: renders a scene entity and returns the texture
xsystem4.spriteTexture: returns the stored SACT2 sprite texture
xsystem4.renderParts: renders a parts object and returns the texture
xsystem4.partsTexture: returns the stored parts object texture
Image data in texture objects is RGB8888 encoded in base 64.
889 lines
23 KiB
C
889 lines
23 KiB
C
/* Copyright (C) 2023 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
|
*
|
|
* Based largely on DAP implementation from xsystem35-sdl2
|
|
* Copyright (C) 2021 kichikuou <KichikuouChrome@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <SDL_thread.h>
|
|
#ifdef _WIN32
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#endif
|
|
|
|
#define VM_PRIVATE
|
|
|
|
#include "system4.h"
|
|
#include "system4/string.h"
|
|
#include "system4/utfsjis.h"
|
|
#include "xsystem4.h"
|
|
#include "base64.h"
|
|
#include "cJSON.h"
|
|
#include "debugger.h"
|
|
#include "gfx/gfx.h"
|
|
#include "json.h"
|
|
#include "msgqueue.h"
|
|
#include "parts/parts_internal.h"
|
|
#include "sact.h"
|
|
#include "scene.h"
|
|
#include "vm.h"
|
|
#include "vm/heap.h"
|
|
#include "vm/page.h"
|
|
|
|
#define THREAD_ID 1
|
|
|
|
static enum {
|
|
DAP_UNINITIALIZED,
|
|
DAP_INITIALIZED,
|
|
DAP_RUNNING,
|
|
DAP_STOPPED
|
|
} dap_state = DAP_UNINITIALIZED;
|
|
|
|
static struct msgq *queue;
|
|
|
|
static void json_add_sjis_to_object(cJSON *obj, const char *name, const char *sjis)
|
|
{
|
|
char *utf = sjis2utf(sjis, 0);
|
|
cJSON_AddStringToObject(obj, name, utf);
|
|
free(utf);
|
|
}
|
|
|
|
static void send_json(cJSON *json)
|
|
{
|
|
static int seq = 1;
|
|
|
|
cJSON_AddNumberToObject(json, "seq", seq++);
|
|
char *str = cJSON_PrintUnformatted(json);
|
|
printf("Content-Length: %zu\r\n\r\n%s", strlen(str), str);
|
|
|
|
fflush(stdout);
|
|
free(str);
|
|
cJSON_Delete(json);
|
|
}
|
|
|
|
static void send_response(cJSON *response, bool success)
|
|
{
|
|
cJSON_AddBoolToObject(response, "success", success);
|
|
send_json(response);
|
|
}
|
|
|
|
static void emit_event(const char *name, cJSON *body)
|
|
{
|
|
cJSON *event = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(event, "type", "event");
|
|
cJSON_AddStringToObject(event, "event", name);
|
|
if (body) {
|
|
cJSON_AddItemToObjectCS(event, "body", body);
|
|
}
|
|
send_json(event);
|
|
}
|
|
|
|
static void emit_initialized_event(void)
|
|
{
|
|
emit_event("initialized", NULL);
|
|
}
|
|
|
|
static void emit_terminated_event(void)
|
|
{
|
|
emit_event("terminated", NULL);
|
|
}
|
|
|
|
static void emit_stopped_event(void)
|
|
{
|
|
cJSON *body = cJSON_CreateObject();
|
|
// TODO: specify correct reason
|
|
cJSON_AddStringToObject(body, "reason", "pause");
|
|
emit_event("stopped", body);
|
|
}
|
|
|
|
static void emit_output_event(const char *category, const char *output)
|
|
{
|
|
cJSON *body = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(body, "category", category);
|
|
cJSON_AddStringToObject(body, "output", output);
|
|
emit_event("output", body);
|
|
}
|
|
|
|
static void cmd_initialize(cJSON *args, cJSON *resp)
|
|
{
|
|
cJSON *body;
|
|
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddBoolToObject(body, "supportsConfigurationDoneRequest", true);
|
|
//cJSON_AddBoolToObject(body, "supportsFunctionBreakpoints", true);
|
|
//cJSON_AddBoolToObject(body, "supportsConditionalBreakpoints", true);
|
|
//cJSON_AddBoolToObject(body, "supportsHitConditionalBreakpoints", true);
|
|
cJSON_AddBoolToObject(body, "supportsEvaluateForHovers", true);
|
|
//cJSON_AddBoolToObject(body, "supportsSetVariable", true);
|
|
//cJSON_AddBoolToObject(body, "supportsSetExpression", true);
|
|
//cJSON_AddBoolToObject(body, "supportsTerminateRequest", true);
|
|
cJSON_AddBoolToObject(body, "supportsInstructionBreakpoints", true);
|
|
send_response(resp, true);
|
|
|
|
emit_initialized_event();
|
|
dap_state = DAP_INITIALIZED;
|
|
}
|
|
|
|
static void cmd_launch(cJSON *args, cJSON *resp)
|
|
{
|
|
if (dap_state != DAP_INITIALIZED) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
// TODO: handle noDebug option
|
|
send_response(resp, true);
|
|
dap_state = DAP_RUNNING;
|
|
}
|
|
|
|
static void cmd_configurationDone(cJSON *args, cJSON *resp)
|
|
{
|
|
send_response(resp, true);
|
|
}
|
|
|
|
static void cmd_stackTrace(cJSON *args, cJSON *resp)
|
|
{
|
|
cJSON *body, *stack_frames, *frame;
|
|
// FIXME: we ignore startFrame/levels arguments
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddNumberToObject(body, "totalFrames", call_stack_ptr);
|
|
cJSON_AddItemToObjectCS(body, "stackFrames", stack_frames = cJSON_CreateArray());
|
|
for (int i = 0; i < call_stack_ptr; i++) {
|
|
cJSON_AddItemToArray(stack_frames, frame = cJSON_CreateObject());
|
|
cJSON_AddNumberToObject(frame, "id", i);
|
|
// this *shouldn't* happen, but since we're in the debugger...
|
|
int fno = call_stack[i].fno;
|
|
if (fno < 0 || fno >= ain->nr_functions) {
|
|
cJSON_AddStringToObject(frame, "name", "<invalid function>");
|
|
} else {
|
|
json_add_sjis_to_object(frame, "name", ain->functions[fno].name);
|
|
}
|
|
char ip[9];
|
|
if (i == call_stack_ptr - 1) {
|
|
snprintf(ip, 9, "%x", (unsigned)instr_ptr);
|
|
} else {
|
|
snprintf(ip, 9, "%x", call_stack[i].call_address);
|
|
}
|
|
cJSON_AddStringToObject(frame, "instructionPointerReference", ip);
|
|
cJSON_AddNumberToObject(frame, "line", 0);
|
|
cJSON_AddNumberToObject(frame, "column", 0);
|
|
}
|
|
send_response(resp, true);
|
|
}
|
|
|
|
enum var_ref_type {
|
|
VAR_REF_ARGUMENTS = 0,
|
|
VAR_REF_LOCALS = 1,
|
|
VAR_REF_MEMBERS = 2,
|
|
VAR_REF_GENERIC = 3,
|
|
};
|
|
|
|
static inline unsigned var_ref(unsigned slot, enum var_ref_type type)
|
|
{
|
|
return (slot << 4) | type;
|
|
}
|
|
|
|
static inline unsigned var_ref_slot(unsigned var_ref)
|
|
{
|
|
return var_ref >> 4;
|
|
}
|
|
|
|
static inline enum var_ref_type var_ref_type(unsigned var_ref)
|
|
{
|
|
return var_ref & 0xF;
|
|
}
|
|
|
|
static void cmd_scopes(cJSON *args, cJSON *resp)
|
|
{
|
|
if (!args) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
cJSON *j_id = cJSON_GetObjectItemCaseSensitive(args, "frameId");
|
|
if (!j_id || !cJSON_IsNumber(j_id)) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
int id = j_id->valueint;
|
|
if (id < 0 || id >= call_stack_ptr) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
if (call_stack[id].fno < 0 || call_stack[id].fno >= ain->nr_functions) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
cJSON *body, *scopes, *arg_scope, *loc_scope, *mem_scope;
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddItemToObjectCS(body, "scopes", scopes = cJSON_CreateArray());
|
|
|
|
int l_page = call_stack[id].page_slot;
|
|
if (page_index_valid(l_page) && heap_get_page(l_page)->type == LOCAL_PAGE) {
|
|
int arg_ref = var_ref(l_page, VAR_REF_ARGUMENTS);
|
|
int loc_ref = var_ref(l_page, VAR_REF_LOCALS);
|
|
cJSON_AddItemToArray(scopes, arg_scope = cJSON_CreateObject());
|
|
cJSON_AddStringToObject(arg_scope, "name", "Arguments");
|
|
cJSON_AddStringToObject(arg_scope, "presentationHint", "arguments");
|
|
cJSON_AddNumberToObject(arg_scope, "variablesReference", arg_ref);
|
|
cJSON_AddItemToArray(scopes, loc_scope = cJSON_CreateObject());
|
|
cJSON_AddStringToObject(loc_scope, "name", "Locals");
|
|
cJSON_AddStringToObject(loc_scope, "presentationHint", "locals");
|
|
cJSON_AddNumberToObject(loc_scope, "variablesReference", loc_ref);
|
|
}
|
|
|
|
int s_page = call_stack[id].struct_page;
|
|
if (page_index_valid(s_page) && heap_get_page(s_page)->type == STRUCT_PAGE) {
|
|
int mem_ref = var_ref(s_page, VAR_REF_MEMBERS);
|
|
cJSON_AddItemToArray(scopes, mem_scope = cJSON_CreateObject());
|
|
cJSON_AddStringToObject(mem_scope, "name", "Members");
|
|
cJSON_AddStringToObject(mem_scope, "presentationHint", "locals");
|
|
cJSON_AddNumberToObject(mem_scope, "variablesReference", mem_ref);
|
|
}
|
|
|
|
// TODO: stack values?
|
|
|
|
send_response(resp, true);
|
|
}
|
|
|
|
static bool type_is_structured(struct ain_type *type)
|
|
{
|
|
switch (type->data) {
|
|
case AIN_STRUCT:
|
|
case AIN_REF_STRUCT:
|
|
case AIN_ARRAY_TYPE:
|
|
case AIN_REF_ARRAY_TYPE:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static int make_var_ref(struct ain_type *type, union vm_value value)
|
|
{
|
|
if (type_is_structured(type) && page_index_valid(value.i))
|
|
return var_ref(value.i, VAR_REF_GENERIC);
|
|
return 0;
|
|
}
|
|
|
|
static cJSON *value_to_json(const char *name, struct ain_type *type, union vm_value value)
|
|
{
|
|
char *s_type = ain_strtype_d(ain, type);
|
|
struct string *s_val = dbg_value_to_string(type, value, 0);
|
|
|
|
cJSON *json = cJSON_CreateObject();
|
|
json_add_sjis_to_object(json, "name", name);
|
|
json_add_sjis_to_object(json, "value", s_val->text);
|
|
json_add_sjis_to_object(json, "type", s_type);
|
|
cJSON_AddNumberToObject(json, "variablesReference", make_var_ref(type, value));
|
|
|
|
free_string(s_val);
|
|
free(s_type);
|
|
return json;
|
|
}
|
|
|
|
static cJSON *var_to_json(struct ain_variable *var, union vm_value value)
|
|
{
|
|
return value_to_json(var->name, &var->type, value);
|
|
}
|
|
|
|
static void add_arguments_to_array(cJSON *array, struct page *page)
|
|
{
|
|
struct ain_function *f = &ain->functions[page->index];
|
|
for (int i = 0; i < f->nr_args; i++) {
|
|
cJSON_AddItemToArray(array, var_to_json(&f->vars[i], page->values[i]));
|
|
}
|
|
}
|
|
|
|
static void add_locals_to_array(cJSON *array, struct page *page)
|
|
{
|
|
struct ain_function *f = &ain->functions[page->index];
|
|
for (int i = f->nr_args; i < f->nr_vars; i++) {
|
|
cJSON_AddItemToArray(array, var_to_json(&f->vars[i], page->values[i]));
|
|
}
|
|
}
|
|
|
|
static void add_variables_to_array(cJSON *array, struct page *page)
|
|
{
|
|
struct ain_function *f = &ain->functions[page->index];
|
|
for (int i = 0; i < f->nr_vars; i++) {
|
|
cJSON_AddItemToArray(array, var_to_json(&f->vars[i], page->values[i]));
|
|
}
|
|
}
|
|
|
|
static void add_members_to_array(cJSON *array, struct page *page)
|
|
{
|
|
struct ain_struct *s = &ain->structures[page->index];
|
|
for (int i = 0; i < s->nr_members; i++) {
|
|
cJSON_AddItemToArray(array, var_to_json(&s->members[i], page->values[i]));
|
|
}
|
|
}
|
|
|
|
static void add_globals_to_array(cJSON *array, struct page *page)
|
|
{
|
|
for (int i = 0; i < ain->nr_globals; i++) {
|
|
cJSON_AddItemToArray(array, var_to_json(&ain->globals[i], page->values[i]));
|
|
}
|
|
}
|
|
|
|
static void add_array_elements_to_array(cJSON *array, struct page *page)
|
|
{
|
|
if (!page)
|
|
return;
|
|
|
|
for (int i = 0; i < page->nr_vars; i++) {
|
|
char name[512];
|
|
snprintf(name, 512, "[%d]", i);
|
|
struct ain_type type;
|
|
type.data = variable_type(page, i, &type.struc, &type.rank);
|
|
cJSON_AddItemToArray(array, value_to_json(name, &type, page->values[i]));
|
|
}
|
|
}
|
|
|
|
static void add_page_to_array(cJSON *array, struct page *page)
|
|
{
|
|
switch (page->type) {
|
|
case GLOBAL_PAGE:
|
|
add_globals_to_array(array, page);
|
|
break;
|
|
case LOCAL_PAGE:
|
|
add_variables_to_array(array, page);
|
|
break;
|
|
case STRUCT_PAGE:
|
|
add_members_to_array(array, page);
|
|
break;
|
|
case ARRAY_PAGE:
|
|
add_array_elements_to_array(array, page);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static cJSON *var_ref_to_json(struct page *page, enum var_ref_type type)
|
|
{
|
|
cJSON *vars = cJSON_CreateArray();
|
|
if (type == VAR_REF_ARGUMENTS) {
|
|
add_arguments_to_array(vars, page);
|
|
} else if (type == VAR_REF_LOCALS) {
|
|
add_locals_to_array(vars, page);
|
|
} else if (type == VAR_REF_MEMBERS) {
|
|
add_members_to_array(vars, page);
|
|
} else if (type == VAR_REF_GENERIC) {
|
|
add_page_to_array(vars, page);
|
|
} else {
|
|
cJSON_Delete(vars);
|
|
return NULL;
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
static void cmd_variables(cJSON *args, cJSON *resp)
|
|
{
|
|
if (!args) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
cJSON *j_ref = cJSON_GetObjectItemCaseSensitive(args, "variablesReference");
|
|
if (!j_ref || !cJSON_IsNumber(j_ref)) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
int slot = var_ref_slot(j_ref->valueint);
|
|
enum var_ref_type type = var_ref_type(j_ref->valueint);
|
|
if (!page_index_valid(slot)) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
struct page *page = heap_get_page(slot);
|
|
|
|
// FIXME: filter/start/count are ignored
|
|
|
|
cJSON *vars = var_ref_to_json(page, type);
|
|
if (!vars) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
cJSON *body = cJSON_CreateObject();
|
|
cJSON_AddItemToObjectCS(body, "variables", vars);
|
|
cJSON_AddItemToObjectCS(resp, "body", body);
|
|
send_response(resp, true);
|
|
}
|
|
|
|
static void cmd_setInstructionBreakpoints(cJSON *args, cJSON *resp)
|
|
{
|
|
static uint32_t *old_breakpoints = NULL;
|
|
static int nr_old_breakpoints = 0;
|
|
|
|
uint32_t *addresses = NULL;
|
|
if (!args) {
|
|
goto fail;
|
|
}
|
|
|
|
cJSON *j_bp = cJSON_GetObjectItemCaseSensitive(args, "breakpoints");
|
|
if (!j_bp || !cJSON_IsArray(j_bp)) {
|
|
goto fail;
|
|
}
|
|
|
|
int nr_breakpoints = cJSON_GetArraySize(j_bp);
|
|
addresses = xcalloc(nr_breakpoints, sizeof(uint32_t));
|
|
|
|
int i;
|
|
cJSON *e;
|
|
cJSON_ArrayForEachIndex(i, e, j_bp) {
|
|
if (!cJSON_IsObject(e)) {
|
|
goto fail;
|
|
}
|
|
cJSON *ref = cJSON_GetObjectItemCaseSensitive(e, "instructionReference");
|
|
if (!ref || !cJSON_IsString(ref)) {
|
|
goto fail;
|
|
}
|
|
char *endptr;
|
|
addresses[i] = strtol(ref->valuestring, &endptr, 16);
|
|
if (ref->valuestring[0] == '\0' || *endptr != '\0') {
|
|
goto fail;
|
|
}
|
|
// FIXME: offset is ignored
|
|
// TODO: condition
|
|
// TODO: hitCondition
|
|
}
|
|
|
|
// clear old breakpoints
|
|
for (int i = 0; i < nr_old_breakpoints; i++) {
|
|
dbg_clear_breakpoint(old_breakpoints[i], NULL);
|
|
}
|
|
|
|
cJSON *body, *breakpoints, *bp;
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddItemToObjectCS(body, "breakpoints", breakpoints = cJSON_CreateArray());
|
|
for (int i = 0; i < nr_breakpoints; i++) {
|
|
char s_addr[9];
|
|
snprintf(s_addr, 9, "%x", addresses[i]);
|
|
cJSON_AddItemToArray(breakpoints, bp = cJSON_CreateObject());
|
|
cJSON_AddStringToObject(bp, "instructionReference", s_addr);
|
|
bool verified = dbg_set_address_breakpoint(addresses[i], NULL, NULL);
|
|
cJSON_AddBoolToObject(bp, "verified", verified);
|
|
// TODO: id, message, offset
|
|
}
|
|
|
|
// save new breakpoints list
|
|
free(old_breakpoints);
|
|
old_breakpoints = addresses;
|
|
nr_old_breakpoints = nr_breakpoints;
|
|
|
|
send_response(resp, true);
|
|
return;
|
|
fail:
|
|
free(addresses);
|
|
send_response(resp, false);
|
|
}
|
|
|
|
static void cmd_evaluate(cJSON *args, cJSON *resp)
|
|
{
|
|
if (!args) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
cJSON *j_expr = cJSON_GetObjectItemCaseSensitive(args, "expression");
|
|
if (!j_expr || !cJSON_IsString(j_expr)) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
// TODO: frameId
|
|
|
|
struct ain_type type;
|
|
union vm_value val = dbg_eval_string(j_expr->valuestring, &type);
|
|
struct string *s_val = dbg_value_to_string(&type, val, 0);
|
|
char *s_type = ain_strtype_d(ain, &type);
|
|
|
|
cJSON *body;
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
json_add_sjis_to_object(body, "result", s_val->text);
|
|
json_add_sjis_to_object(body, "type", s_type);
|
|
cJSON_AddNumberToObject(body, "variablesReference", make_var_ref(&type, val));
|
|
// TODO: presentationHint
|
|
|
|
send_response(resp, true);
|
|
}
|
|
|
|
static void cmd_continue(cJSON *args, cJSON *resp)
|
|
{
|
|
send_response(resp, true);
|
|
|
|
dap_state = DAP_RUNNING;
|
|
dbg_continue();
|
|
}
|
|
|
|
static void cmd_pause(cJSON *args, cJSON *resp)
|
|
{
|
|
if (dap_state < DAP_RUNNING) {
|
|
cJSON_AddBoolToObject(resp, "success", false);
|
|
return;
|
|
}
|
|
|
|
cJSON_AddBoolToObject(resp, "success", true);
|
|
send_json(resp);
|
|
|
|
if (dap_state != DAP_STOPPED) {
|
|
dbg_repl();
|
|
}
|
|
}
|
|
|
|
static void cmd_stepIn(cJSON *args, cJSON *resp)
|
|
{
|
|
dbg_set_step_into_breakpoint();
|
|
send_response(resp, true);
|
|
|
|
dap_state = DAP_RUNNING;
|
|
dbg_continue();
|
|
}
|
|
|
|
static void cmd_stepOut(cJSON *args, cJSON *resp)
|
|
{
|
|
dbg_set_finish_breakpoint();
|
|
send_response(resp, true);
|
|
|
|
dap_state = DAP_RUNNING;
|
|
dbg_continue();
|
|
}
|
|
|
|
static void cmd_next(cJSON *args, cJSON *resp)
|
|
{
|
|
dbg_set_step_over_breakpoint();
|
|
send_response(resp, true);
|
|
|
|
dap_state = DAP_RUNNING;
|
|
dbg_continue();
|
|
}
|
|
|
|
static void cmd_threads(cJSON *args, cJSON *resp)
|
|
{
|
|
cJSON *body, *threads, *thread;
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddItemToObjectCS(body, "threads", threads = cJSON_CreateArray());
|
|
cJSON_AddItemToArray(threads, thread = cJSON_CreateObject());
|
|
cJSON_AddNumberToObject(thread, "id", 0);
|
|
cJSON_AddStringToObject(thread, "name", "main_thread");
|
|
send_response(resp, true);
|
|
}
|
|
|
|
static void cmd_setVariable(cJSON *args, cJSON *resp)
|
|
{
|
|
// TODO
|
|
send_response(resp, false);
|
|
}
|
|
|
|
static void cmd_disconnect(cJSON *args, cJSON *resp)
|
|
{
|
|
send_response(resp, true);
|
|
vm_exit(0);
|
|
}
|
|
|
|
// get scene metadata
|
|
static void cmd_xsystem4_scene(cJSON *args, cJSON *resp)
|
|
{
|
|
cJSON *body;
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddItemToObjectCS(body, "entities", scene_to_json(true));
|
|
send_response(resp, true);
|
|
}
|
|
|
|
static bool get_id(cJSON *args, const char *id_name, int *id_out)
|
|
{
|
|
if (!args)
|
|
return false;
|
|
|
|
cJSON *j_id = cJSON_GetObjectItemCaseSensitive(args, id_name);
|
|
if (!j_id || !cJSON_IsNumber(j_id))
|
|
return false;
|
|
|
|
*id_out = j_id->valueint;
|
|
return true;
|
|
}
|
|
|
|
// render a scene entity (using sprite->render())
|
|
static void cmd_xsystem4_renderEntity(cJSON *args, cJSON *resp)
|
|
{
|
|
int id;
|
|
struct sprite *sp;
|
|
|
|
if (!get_id(args, "entityId", &id) || !(sp = scene_get(id))) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
// create output texture
|
|
Texture t;
|
|
gfx_init_texture_rgba(&t, config.view_width, config.view_height, (SDL_Color){0,0,0,0});
|
|
|
|
// render entity to texture
|
|
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, &t, 0, 0, t.w, t.h);
|
|
sp->render(sp);
|
|
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
|
|
|
|
// send response
|
|
cJSON *body;
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddNumberToObject(body, "entityId", id);
|
|
cJSON_AddItemToObjectCS(body, "texture", texture_to_json_with_pixels(&t));
|
|
send_response(resp, true);
|
|
gfx_delete_texture(&t);
|
|
}
|
|
|
|
// get the stored texture data of a sact_sprite
|
|
static void cmd_xsystem4_spriteTexture(cJSON *args, cJSON *resp)
|
|
{
|
|
int id;
|
|
struct sact_sprite *sp;
|
|
|
|
if (!get_id(args, "spriteId", &id) || !(sp = sact_try_get_sprite(id)) || !sp->texture.handle) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
cJSON *body;
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddNumberToObject(body, "spriteId", id);
|
|
cJSON_AddItemToObjectCS(body, "texture", texture_to_json_with_pixels(&sp->texture));
|
|
if (sp->text.texture.handle) {
|
|
cJSON_AddItemToObjectCS(body, "textTexture", texture_to_json_with_pixels(&sp->text.texture));
|
|
}
|
|
send_response(resp, true);
|
|
}
|
|
|
|
static struct parts *get_parts(cJSON *args, int *id_out)
|
|
{
|
|
struct parts *parts;
|
|
|
|
if (!get_id(args, "partsId", id_out) || !(parts = parts_try_get(*id_out))
|
|
|| !parts->states[parts->state].common.texture.handle)
|
|
return NULL;
|
|
return parts;
|
|
}
|
|
|
|
// render a parts object
|
|
static void cmd_xsystem4_renderParts(cJSON *args, cJSON *resp)
|
|
{
|
|
int parts_id;
|
|
struct parts *parts = get_parts(args, &parts_id);
|
|
if (!parts) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
// create output texture
|
|
Texture t;
|
|
gfx_init_texture_rgba(&t, config.view_width, config.view_height, (SDL_Color){0,0,0,0});
|
|
|
|
// render parts to textre
|
|
GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, &t, 0, 0, t.w, t.h);
|
|
parts_render(parts);
|
|
gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo);
|
|
|
|
// send response
|
|
cJSON *body;
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddNumberToObject(body, "partsId", parts_id);
|
|
cJSON_AddItemToObjectCS(body, "texture", texture_to_json_with_pixels(&t));
|
|
send_response(resp, true);
|
|
gfx_delete_texture(&t);
|
|
}
|
|
|
|
// get the stored texture data of a parts object
|
|
static void cmd_xsystem4_partsTexture(cJSON *args, cJSON *resp)
|
|
{
|
|
int parts_id;
|
|
struct parts *parts = get_parts(args, &parts_id);;
|
|
if (!parts) {
|
|
send_response(resp, false);
|
|
return;
|
|
}
|
|
|
|
Texture *t = &parts->states[parts->state].common.texture;
|
|
|
|
cJSON *body;
|
|
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
|
|
cJSON_AddNumberToObject(body, "partsId", parts_id);
|
|
cJSON_AddItemToObjectCS(body, "texture", texture_to_json_with_pixels(t));
|
|
send_response(resp, true);
|
|
}
|
|
|
|
static struct {
|
|
const char *name;
|
|
void (*fun)(cJSON *args, cJSON *resp);
|
|
bool continue_repl;
|
|
} commands[] = {
|
|
{ "initialize", cmd_initialize, true },
|
|
{ "launch", cmd_launch, true },
|
|
{ "configurationDone", cmd_configurationDone, true },
|
|
{ "continue", cmd_continue, false },
|
|
{ "stackTrace", cmd_stackTrace, true },
|
|
{ "stepIn", cmd_stepIn, false },
|
|
{ "stepOut", cmd_stepOut, false },
|
|
{ "next", cmd_next, false },
|
|
{ "pause", cmd_pause, true },
|
|
{ "evaluate", cmd_evaluate, true },
|
|
{ "setInstructionBreakpoints", cmd_setInstructionBreakpoints, true },
|
|
{ "threads", cmd_threads, true },
|
|
{ "scopes", cmd_scopes, true },
|
|
{ "variables", cmd_variables, true },
|
|
{ "setVariable", cmd_setVariable, true },
|
|
{ "disconnect", cmd_disconnect, true },
|
|
// DAP extensions
|
|
{ "xsystem4.scene", cmd_xsystem4_scene, true },
|
|
{ "xsystem4.renderEntity", cmd_xsystem4_renderEntity, true },
|
|
{ "xsystem4.spriteTexture", cmd_xsystem4_spriteTexture, true },
|
|
{ "xsystem4.renderParts", cmd_xsystem4_renderParts, true },
|
|
{ "xsystem4.partsTexture", cmd_xsystem4_partsTexture, true },
|
|
};
|
|
|
|
static bool handle_request(cJSON *request)
|
|
{
|
|
cJSON *resp = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(resp, "type", "response");
|
|
cJSON *request_seq = cJSON_DetachItemFromObjectCaseSensitive(request, "seq");
|
|
cJSON_AddItemToObjectCS(resp, "request_seq", request_seq);
|
|
cJSON *command = cJSON_DetachItemFromObjectCaseSensitive(request, "command");
|
|
cJSON_AddItemToObjectCS(resp, "command", command);
|
|
cJSON *args = cJSON_GetObjectItemCaseSensitive(request, "arguments");
|
|
|
|
if (!cJSON_IsString(command)) {
|
|
WARNING("protocol error: command is not a string");
|
|
cJSON_Delete(resp);
|
|
return true;
|
|
}
|
|
|
|
for (unsigned i = 0; i < sizeof(commands) / sizeof(*commands); i++) {
|
|
if (!strcmp(command->valuestring, commands[i].name)) {
|
|
commands[i].fun(args, resp);
|
|
return commands[i].continue_repl;
|
|
}
|
|
}
|
|
|
|
WARNING("unknown command \"%s\"", command->valuestring);
|
|
return true;
|
|
}
|
|
|
|
static bool handle_message(char *msg)
|
|
{
|
|
cJSON *json = cJSON_Parse(msg);
|
|
cJSON *type = cJSON_GetObjectItemCaseSensitive(json, "type");
|
|
bool continue_repl = true;
|
|
if (cJSON_IsString(type) && !strcmp(type->valuestring, "request"))
|
|
continue_repl = handle_request(json);
|
|
cJSON_Delete(json);
|
|
free(msg);
|
|
return continue_repl;
|
|
}
|
|
|
|
static int read_command_thread(void *data)
|
|
{
|
|
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) {
|
|
WARNING("Debug Adapter Protocol error: no Content-Length header");
|
|
continue;
|
|
}
|
|
char *buf = malloc(content_length);
|
|
if (fread(buf, content_length, 1, stdin) != 1) {
|
|
WARNING("fread(stdin): %s", strerror(errno));
|
|
free(buf);
|
|
continue;
|
|
}
|
|
msgq_enqueue(queue, buf);
|
|
content_length = -1;
|
|
} else {
|
|
WARNING("Unknown Debug Adapter Protocol header: %s", header);
|
|
}
|
|
}
|
|
msgq_enqueue(queue, NULL); // EOF
|
|
return 0;
|
|
}
|
|
|
|
void dbg_dap_init(void)
|
|
{
|
|
atexit(dbg_dap_quit);
|
|
queue = msgq_new();
|
|
|
|
#ifdef _WIN32
|
|
_setmode(_fileno(stdin), _O_BINARY);
|
|
_setmode(_fileno(stdout), _O_BINARY);
|
|
#endif
|
|
|
|
SDL_CreateThread(read_command_thread, "Debugger", NULL);
|
|
|
|
while (dap_state < DAP_RUNNING) {
|
|
char *msg = msgq_dequeue(queue);
|
|
if (!msg)
|
|
break;
|
|
handle_message(msg);
|
|
}
|
|
}
|
|
|
|
void dbg_dap_quit(void)
|
|
{
|
|
emit_terminated_event();
|
|
}
|
|
|
|
void dbg_dap_repl(void)
|
|
{
|
|
emit_stopped_event();
|
|
dap_state = DAP_STOPPED;
|
|
|
|
bool continue_repl = true;
|
|
while (continue_repl) {
|
|
char *msg = msgq_dequeue(queue);
|
|
if (!msg)
|
|
break;
|
|
continue_repl = handle_message(msg);
|
|
}
|
|
}
|
|
|
|
void dbg_dap_handle_messages(void)
|
|
{
|
|
while (!msgq_isempty(queue)) {
|
|
char *msg = msgq_dequeue(queue);
|
|
if (!msg)
|
|
break;
|
|
handle_message(msg);
|
|
}
|
|
}
|
|
|
|
void dbg_dap_log(const char *log, const char *fmt, va_list ap)
|
|
{
|
|
size_t len = max(1024, strlen(fmt) * 2);
|
|
char *buf = xmalloc(len);
|
|
vsnprintf(buf, len, fmt, ap);
|
|
emit_output_event(log, buf);
|
|
free(buf);
|
|
}
|