9#include "absl/status/status.h"
16#include "nlohmann/json.hpp"
24using json = nlohmann::json;
37 std::string normalized = value;
39 normalized.begin(), normalized.end(), normalized.begin(),
40 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
41 return normalized ==
"1" || normalized ==
"true" || normalized ==
"yes" ||
48 res.set_header(
"Access-Control-Allow-Origin", kCorsAllowOrigin);
49 res.set_header(
"Access-Control-Allow-Headers", kCorsAllowHeaders);
50 res.set_header(
"Access-Control-Allow-Methods", kCorsAllowMethods);
51 res.set_header(
"Access-Control-Max-Age", kCorsMaxAge);
57 const char* window_state, httplib::Response& res) {
63 response[
"status"] =
"ok";
64 response[
"window"] = window_state;
66 res.set_content(response.dump(),
"application/json");
71 response[
"status"] =
"error";
72 response[
"message"] =
"window action failed";
73 res.set_content(response.dump(),
"application/json");
79 response[
"status"] =
"error";
80 response[
"message"] =
"window control unavailable";
81 res.set_content(response.dump(),
"application/json");
86void HandleHealth(
const httplib::Request& req, httplib::Response& res,
92 j[
"service"] =
"yaze-agent-api";
96 j[
"discovery"] =
"bonjour";
98 j[
"discovery"] =
"none";
102 res.set_content(j.dump(),
"application/json");
108 const bool force_refresh =
109 IsTruthyParam(req.get_param_value(
"refresh"), req.has_param(
"refresh"));
110 auto models_or = registry.ListAllModels(force_refresh);
114 if (!models_or.ok()) {
116 j[
"error"] = models_or.status().message();
118 res.set_content(j.dump(),
"application/json");
122 json j_models = json::array();
123 for (
const auto& info : *models_or) {
125 j_model[
"name"] = info.name;
126 j_model[
"display_name"] = info.display_name;
127 j_model[
"provider"] = info.provider;
128 j_model[
"description"] = info.description;
129 j_model[
"family"] = info.family;
130 j_model[
"parameter_size"] = info.parameter_size;
131 j_model[
"quantization"] = info.quantization;
132 j_model[
"size_bytes"] = info.size_bytes;
133 j_model[
"is_local"] = info.is_local;
134 j_models.push_back(j_model);
138 response[
"models"] = j_models;
139 response[
"count"] = j_models.size();
142 res.set_content(response.dump(),
"application/json");
149 std::string format_str = req.get_param_value(
"format");
150 if (format_str.empty())
151 format_str =
"mesen";
155 if (format_str ==
"mesen") {
157 }
else if (format_str ==
"asar") {
159 }
else if (format_str ==
"wla") {
161 }
else if (format_str ==
"bsnes") {
165 j[
"error"] =
"Unsupported symbol format";
166 j[
"format"] = format_str;
167 j[
"supported"] = {
"mesen",
"asar",
"wla",
"bsnes"};
169 res.set_content(j.dump(),
"application/json");
175 j[
"error"] =
"Symbol provider not available";
177 res.set_content(j.dump(),
"application/json");
182 if (export_or.ok()) {
183 const std::string accept = req.has_header(
"Accept")
184 ? req.get_header_value(
"Accept")
186 const bool wants_json =
187 accept.find(
"application/json") != std::string::npos;
190 j[
"symbols"] = *export_or;
191 j[
"format"] = format_str;
193 res.set_content(j.dump(),
"application/json");
196 res.set_content(*export_or,
"text/plain");
200 j[
"error"] = export_or.status().message();
202 res.set_content(j.dump(),
"application/json");
210 json body = json::parse(req.body);
211 uint32_t address = body.value(
"address", 0);
212 std::string source = body.value(
"source",
"unknown");
220 response[
"status"] =
"ok";
221 response[
"address"] = address;
222 response[
"message"] =
"Navigation request received";
224 res.set_content(response.dump(),
"application/json");
225 }
catch (
const std::exception& e) {
227 j[
"error"] = e.what();
229 res.set_content(j.dump(),
"application/json");
237 json body = json::parse(req.body);
238 uint32_t address = body.value(
"address", 0);
239 std::string source = body.value(
"source",
"unknown");
245 if (body.contains(
"cpu_state")) {
246 cpu_info = body[
"cpu_state"];
251 response[
"status"] =
"ok";
252 response[
"address"] = address;
254 res.set_content(response.dump(),
"application/json");
255 }
catch (
const std::exception& e) {
257 j[
"error"] = e.what();
259 res.set_content(j.dump(),
"application/json");
267 json body = json::parse(req.body);
274 response[
"status"] =
"ok";
276 res.set_content(response.dump(),
"application/json");
277 }
catch (
const std::exception& e) {
279 j[
"error"] = e.what();
281 res.set_content(j.dump(),
"application/json");
286 const std::function<
bool()>& action) {
288 HandleWindowAction(action,
"shown", res);
292 const std::function<
bool()>& action) {
294 HandleWindowAction(action,
"hidden", res);
313 if (!req.has_param(
"room")) {
315 j[
"error"] =
"Missing required parameter: room";
317 res.set_content(j.dump(),
"application/json");
320 const std::string s = req.get_param_value(
"room");
322 room_id = std::stoi(s,
nullptr, 0);
325 j[
"error"] =
"Invalid room parameter";
328 res.set_content(j.dump(),
"application/json");
340 if (!render_service) {
342 j[
"error"] =
"Render service not available";
344 res.set_content(j.dump(),
"application/json");
349 if (!ParseRoomParam(req, res, room_id))
355 if (req.has_param(
"overlays")) {
356 std::istringstream ss(req.get_param_value(
"overlays"));
358 while (std::getline(ss, tok,
',')) {
359 tok.erase(0, tok.find_first_not_of(
" \t"));
361 tok.erase(tok.find_last_not_of(
" \t") + 1);
362 if (tok ==
"collision")
364 else if (tok ==
"sprites")
366 else if (tok ==
"objects")
368 else if (tok ==
"track")
370 else if (tok ==
"camera")
372 else if (tok ==
"grid")
374 else if (tok ==
"all")
381 if (req.has_param(
"scale")) {
383 scale = std::stof(req.get_param_value(
"scale"));
394 render_req.
scale = scale;
397 if (!result_or.ok()) {
398 const auto& s = result_or.status();
400 j[
"error"] = std::string(s.message());
401 if (s.code() == absl::StatusCode::kInvalidArgument)
403 else if (s.code() == absl::StatusCode::kFailedPrecondition)
407 res.set_content(j.dump(),
"application/json");
411 const auto& result = *result_or;
413 res.set_content(
reinterpret_cast<const char*
>(result.png_data.data()),
414 result.png_data.size(),
"image/png");
415 res.set_header(
"X-Room-Id", std::to_string(room_id));
416 res.set_header(
"X-Room-Width", std::to_string(result.width));
417 res.set_header(
"X-Room-Height", std::to_string(result.height));
421 const httplib::Request& req, httplib::Response& res,
425 if (!render_service) {
427 j[
"error"] =
"Render service not available";
429 res.set_content(j.dump(),
"application/json");
434 if (!ParseRoomParam(req, res, room_id))
439 const auto& s = meta_or.status();
441 j[
"error"] = std::string(s.message());
442 res.status = (s.code() == absl::StatusCode::kInvalidArgument) ? 400 : 500;
443 res.set_content(j.dump(),
"application/json");
447 const auto& meta = *meta_or;
449 j[
"room_id"] = meta.room_id;
450 j[
"blockset"] = meta.blockset;
451 j[
"spriteset"] = meta.spriteset;
452 j[
"palette"] = meta.palette;
453 j[
"layout_id"] = meta.layout_id;
454 j[
"effect"] = meta.effect;
455 j[
"collision"] = meta.collision;
456 j[
"tag1"] = meta.tag1;
457 j[
"tag2"] = meta.tag2;
458 j[
"message_id"] = meta.message_id;
459 j[
"has_custom_collision"] = meta.has_custom_collision;
460 j[
"object_count"] = meta.object_count;
461 j[
"sprite_count"] = meta.sprite_count;
463 res.set_content(j.dump(),
"application/json");
475 json body = json::parse(req.body);
476 std::string command_name = body.value(
"command",
"");
477 if (command_name.empty()) {
479 j[
"error"] =
"Missing 'command' field";
481 res.set_content(j.dump(),
"application/json");
485 std::vector<std::string> args;
486 if (body.contains(
"args") && body[
"args"].is_array()) {
487 for (
const auto& arg : body[
"args"]) {
488 args.push_back(arg.get<std::string>());
493 if (!registry.HasCommand(command_name)) {
495 j[
"error"] =
"Unknown command: " + command_name;
497 res.set_content(j.dump(),
"application/json");
501 std::string captured_output;
502 auto status = registry.Execute(command_name, args, rom, &captured_output);
506 response[
"status"] =
"ok";
507 response[
"command"] = command_name;
510 response[
"result"] = json::parse(captured_output);
512 response[
"result"] = captured_output;
516 response[
"status"] =
"error";
517 response[
"command"] = command_name;
518 response[
"error"] = std::string(status.message());
519 if (!captured_output.empty()) {
520 response[
"output"] = captured_output;
524 res.set_content(response.dump(),
"application/json");
525 }
catch (
const std::exception& e) {
527 j[
"error"] = e.what();
529 res.set_content(j.dump(),
"application/json");
538 json j_commands = json::array();
540 for (
const auto& category : registry.GetCategories()) {
541 for (
const auto& cmd_name : registry.GetCommandsInCategory(category)) {
542 const auto* meta = registry.GetMetadata(cmd_name);
547 j_cmd[
"name"] = meta->name;
548 j_cmd[
"category"] = meta->category;
549 j_cmd[
"description"] = meta->description;
550 j_cmd[
"usage"] = meta->usage;
551 j_cmd[
"requires_rom"] = meta->requires_rom;
552 j_commands.push_back(j_cmd);
557 response[
"commands"] = j_commands;
558 response[
"count"] = j_commands.size();
560 res.set_content(response.dump(),
"application/json");
570 if (project_path.empty())
573 return project_path +
"/Docs/Dev/Planning/annotations.json";
578 return json::object();
579 std::ifstream file(path);
581 return json::object();
583 return json::parse(file);
585 return json::object();
594 auto last_slash = path.find_last_of(
'/');
595 if (last_slash != std::string::npos) {
596 std::string dir = path.substr(0, last_slash);
598 std::string cmd =
"mkdir -p '" + dir +
"'";
599 (void)system(cmd.c_str());
602 std::ofstream file(path);
605 file << data.dump(2);
612 const std::string& project_path) {
615 std::string path = ResolveAnnotationsPath(project_path);
618 j[
"error"] =
"No project path configured";
620 res.set_content(j.dump(),
"application/json");
624 json file_data = LoadAnnotationsFile(path);
625 json annotations = file_data.value(
"annotations", json::array());
628 if (req.has_param(
"room")) {
631 room_id = std::stoi(req.get_param_value(
"room"),
nullptr, 0);
634 j[
"error"] =
"Invalid room parameter";
636 res.set_content(j.dump(),
"application/json");
640 json filtered = json::array();
641 for (
const auto& ann : annotations) {
642 if (ann.value(
"room_id", -1) == room_id) {
643 filtered.push_back(ann);
646 annotations = filtered;
650 response[
"annotations"] = annotations;
652 res.set_content(response.dump(),
"application/json");
656 const std::string& project_path) {
659 std::string path = ResolveAnnotationsPath(project_path);
662 j[
"error"] =
"No project path configured";
664 res.set_content(j.dump(),
"application/json");
669 json new_annotation = json::parse(req.body);
670 json file_data = LoadAnnotationsFile(path);
672 if (!file_data.contains(
"annotations")) {
673 file_data[
"annotations"] = json::array();
675 file_data[
"annotations"].push_back(new_annotation);
677 if (SaveAnnotationsFile(path, file_data)) {
679 response[
"status"] =
"ok";
680 response[
"id"] = new_annotation.value(
"id",
"");
682 res.set_content(response.dump(),
"application/json");
685 j[
"error"] =
"Failed to write annotations file";
687 res.set_content(j.dump(),
"application/json");
689 }
catch (
const std::exception& e) {
691 j[
"error"] = e.what();
693 res.set_content(j.dump(),
"application/json");
698 const std::string& project_path) {
701 std::string path = ResolveAnnotationsPath(project_path);
704 j[
"error"] =
"No project path configured";
706 res.set_content(j.dump(),
"application/json");
711 std::string annotation_id;
712 if (!req.matches.empty() && req.matches.size() > 1) {
713 annotation_id = req.matches[1].str();
715 if (annotation_id.empty()) {
717 j[
"error"] =
"Missing annotation ID in URL";
719 res.set_content(j.dump(),
"application/json");
724 json updated = json::parse(req.body);
725 json file_data = LoadAnnotationsFile(path);
726 json& annotations = file_data[
"annotations"];
729 for (
auto& ann : annotations) {
730 if (ann.value(
"id",
"") == annotation_id) {
732 for (
auto it = updated.begin(); it != updated.end(); ++it) {
733 ann[it.key()] = it.value();
742 j[
"error"] =
"Annotation not found: " + annotation_id;
744 res.set_content(j.dump(),
"application/json");
748 if (SaveAnnotationsFile(path, file_data)) {
750 response[
"status"] =
"ok";
751 response[
"id"] = annotation_id;
753 res.set_content(response.dump(),
"application/json");
756 j[
"error"] =
"Failed to write annotations file";
758 res.set_content(j.dump(),
"application/json");
760 }
catch (
const std::exception& e) {
762 j[
"error"] = e.what();
764 res.set_content(j.dump(),
"application/json");
769 const std::string& project_path) {
773 std::string path = ResolveAnnotationsPath(project_path);
776 j[
"error"] =
"No project path configured";
778 res.set_content(j.dump(),
"application/json");
782 std::string annotation_id;
783 if (!req.matches.empty() && req.matches.size() > 1) {
784 annotation_id = req.matches[1].str();
786 if (annotation_id.empty()) {
788 j[
"error"] =
"Missing annotation ID in URL";
790 res.set_content(j.dump(),
"application/json");
794 json file_data = LoadAnnotationsFile(path);
795 json& annotations = file_data[
"annotations"];
797 size_t original_size = annotations.size();
798 json filtered = json::array();
799 for (
const auto& ann : annotations) {
800 if (ann.value(
"id",
"") != annotation_id) {
801 filtered.push_back(ann);
805 if (filtered.size() == original_size) {
807 j[
"error"] =
"Annotation not found: " + annotation_id;
809 res.set_content(j.dump(),
"application/json");
813 file_data[
"annotations"] = filtered;
814 if (SaveAnnotationsFile(path, file_data)) {
816 response[
"status"] =
"ok";
817 response[
"id"] = annotation_id;
819 res.set_content(response.dump(),
"application/json");
822 j[
"error"] =
"Failed to write annotations file";
824 res.set_content(j.dump(),
"application/json");
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< RoomMetadata > GetDungeonRoomMetadata(int room_id)
absl::StatusOr< RenderResult > RenderDungeonRoom(const RenderRequest &req)
static CommandRegistry & Instance()
static ModelRegistry & GetInstance()
Provider for symbol (label) resolution in disassembly.
absl::StatusOr< std::string > ExportSymbols(SymbolFormat format) const
Export all symbols to a string in the specified format.
constexpr uint32_t kCollision
constexpr uint32_t kSprites
constexpr uint32_t kCameraQuads
constexpr uint32_t kObjects
constexpr uint32_t kTrack
constexpr const char * kCorsMaxAge
constexpr const char * kCorsAllowOrigin
bool IsTruthyParam(const std::string &value, bool param_present)
bool ParseRoomParam(const httplib::Request &req, httplib::Response &res, int &room_id)
void HandleWindowAction(const std::function< bool()> &action, const char *window_state, httplib::Response &res)
bool SaveAnnotationsFile(const std::string &path, const json &data)
std::string ResolveAnnotationsPath(const std::string &project_path)
constexpr const char * kCorsAllowHeaders
constexpr const char * kCorsAllowMethods
json LoadAnnotationsFile(const std::string &path)
void HandleStateUpdate(const httplib::Request &req, httplib::Response &res)
void ApplyCorsHeaders(httplib::Response &res)
void HandleCorsPreflight(const httplib::Request &req, httplib::Response &res)
void HandleBreakpointHit(const httplib::Request &req, httplib::Response &res)
void HandleListModels(const httplib::Request &req, httplib::Response &res)
void HandleGetSymbols(const httplib::Request &req, httplib::Response &res, yaze::emu::debug::SymbolProvider *symbols)
void HandleNavigate(const httplib::Request &req, httplib::Response &res)
void HandleWindowShow(const httplib::Request &req, httplib::Response &res, const std::function< bool()> &action)
void HandleAnnotationUpdate(const httplib::Request &req, httplib::Response &res, const std::string &project_path)
void HandleRenderDungeonMetadata(const httplib::Request &req, httplib::Response &res, yaze::app::service::RenderService *render_service)
void HandleRenderDungeon(const httplib::Request &req, httplib::Response &res, yaze::app::service::RenderService *render_service)
void HandleCommandExecute(const httplib::Request &req, httplib::Response &res, yaze::Rom *rom)
void HandleAnnotationDelete(const httplib::Request &req, httplib::Response &res, const std::string &project_path)
void HandleAnnotationList(const httplib::Request &req, httplib::Response &res, const std::string &project_path)
void HandleAnnotationCreate(const httplib::Request &req, httplib::Response &res, const std::string &project_path)
void HandleCommandList(const httplib::Request &req, httplib::Response &res)
void HandleWindowHide(const httplib::Request &req, httplib::Response &res, const std::function< bool()> &action)
void HandleHealth(const httplib::Request &req, httplib::Response &res, const BonjourPublisher *bonjour)
SymbolFormat
Supported symbol file formats.