yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
api_handlers.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <sstream>
6
7#include <fstream>
8
9#include "absl/status/status.h"
15#include "httplib.h"
16#include "nlohmann/json.hpp"
17#include "rom/rom.h"
18#include "util/log.h"
19
20namespace yaze {
21namespace cli {
22namespace api {
23
24using json = nlohmann::json;
25
26namespace {
27
28constexpr const char* kCorsAllowOrigin = "*";
29constexpr const char* kCorsAllowHeaders = "Content-Type, Authorization, Accept";
30constexpr const char* kCorsAllowMethods = "GET, POST, OPTIONS";
31constexpr const char* kCorsMaxAge = "86400";
32
33bool IsTruthyParam(const std::string& value, bool param_present) {
34 if (value.empty()) {
35 return param_present;
36 }
37 std::string normalized = value;
38 std::transform(
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" ||
42 normalized == "on";
43}
44
45} // namespace
46
47void ApplyCorsHeaders(httplib::Response& res) {
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);
52}
53
54namespace {
55
56void HandleWindowAction(const std::function<bool()>& action,
57 const char* window_state, httplib::Response& res) {
59
60 if (action) {
61 if (action()) {
62 json response;
63 response["status"] = "ok";
64 response["window"] = window_state;
65 res.status = 200;
66 res.set_content(response.dump(), "application/json");
67 return;
68 }
69 res.status = 500;
70 json response;
71 response["status"] = "error";
72 response["message"] = "window action failed";
73 res.set_content(response.dump(), "application/json");
74 return;
75 }
76
77 res.status = 501;
78 json response;
79 response["status"] = "error";
80 response["message"] = "window control unavailable";
81 res.set_content(response.dump(), "application/json");
82}
83
84} // namespace
85
86void HandleHealth(const httplib::Request& req, httplib::Response& res,
87 const BonjourPublisher* bonjour) {
88 (void)req;
89 json j;
90 j["status"] = "ok";
91 j["version"] = "1.0";
92 j["service"] = "yaze-agent-api";
93
94 // Report LAN discovery mechanism availability.
95 if (bonjour && bonjour->IsAvailable()) {
96 j["discovery"] = "bonjour";
97 } else {
98 j["discovery"] = "none";
99 }
100
101 res.status = 200;
102 res.set_content(j.dump(), "application/json");
103 ApplyCorsHeaders(res);
104}
105
106void HandleListModels(const httplib::Request& req, httplib::Response& res) {
107 auto& registry = ModelRegistry::GetInstance();
108 const bool force_refresh =
109 IsTruthyParam(req.get_param_value("refresh"), req.has_param("refresh"));
110 auto models_or = registry.ListAllModels(force_refresh);
111
112 ApplyCorsHeaders(res);
113
114 if (!models_or.ok()) {
115 json j;
116 j["error"] = models_or.status().message();
117 res.status = 500;
118 res.set_content(j.dump(), "application/json");
119 return;
120 }
121
122 json j_models = json::array();
123 for (const auto& info : *models_or) {
124 json j_model;
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);
135 }
136
137 json response;
138 response["models"] = j_models;
139 response["count"] = j_models.size();
140
141 res.status = 200;
142 res.set_content(response.dump(), "application/json");
143}
144
145void HandleGetSymbols(const httplib::Request& req, httplib::Response& res,
147 ApplyCorsHeaders(res);
148
149 std::string format_str = req.get_param_value("format");
150 if (format_str.empty())
151 format_str = "mesen";
152
155 if (format_str == "mesen") {
157 } else if (format_str == "asar") {
159 } else if (format_str == "wla") {
161 } else if (format_str == "bsnes") {
163 } else {
164 json j;
165 j["error"] = "Unsupported symbol format";
166 j["format"] = format_str;
167 j["supported"] = {"mesen", "asar", "wla", "bsnes"};
168 res.status = 400;
169 res.set_content(j.dump(), "application/json");
170 return;
171 }
172
173 if (!symbols) {
174 json j;
175 j["error"] = "Symbol provider not available";
176 res.status = 503;
177 res.set_content(j.dump(), "application/json");
178 return;
179 }
180
181 auto export_or = symbols->ExportSymbols(format);
182 if (export_or.ok()) {
183 const std::string accept = req.has_header("Accept")
184 ? req.get_header_value("Accept")
185 : std::string();
186 const bool wants_json =
187 accept.find("application/json") != std::string::npos;
188 if (wants_json) {
189 json j;
190 j["symbols"] = *export_or;
191 j["format"] = format_str;
192 res.status = 200;
193 res.set_content(j.dump(), "application/json");
194 } else {
195 res.status = 200;
196 res.set_content(*export_or, "text/plain");
197 }
198 } else {
199 json j;
200 j["error"] = export_or.status().message();
201 res.status = 500;
202 res.set_content(j.dump(), "application/json");
203 }
204}
205
206void HandleNavigate(const httplib::Request& req, httplib::Response& res) {
207 ApplyCorsHeaders(res);
208
209 try {
210 json body = json::parse(req.body);
211 uint32_t address = body.value("address", 0);
212 std::string source = body.value("source", "unknown");
213
214 (void)source; // Used in response
215 // Navigate request logged via JSON response
216
217 // TODO: Integrate with yaze's disassembly viewer to jump to address
218 // For now, just acknowledge the request
219 json response;
220 response["status"] = "ok";
221 response["address"] = address;
222 response["message"] = "Navigation request received";
223 res.status = 200;
224 res.set_content(response.dump(), "application/json");
225 } catch (const std::exception& e) {
226 json j;
227 j["error"] = e.what();
228 res.status = 400;
229 res.set_content(j.dump(), "application/json");
230 }
231}
232
233void HandleBreakpointHit(const httplib::Request& req, httplib::Response& res) {
234 ApplyCorsHeaders(res);
235
236 try {
237 json body = json::parse(req.body);
238 uint32_t address = body.value("address", 0);
239 std::string source = body.value("source", "unknown");
240
241 (void)source; // Used in response
242
243 // Log CPU state if provided (for debugging, stored in response)
244 json cpu_info;
245 if (body.contains("cpu_state")) {
246 cpu_info = body["cpu_state"];
247 }
248
249 // TODO: Integrate with RomDebugAgent for analysis
250 json response;
251 response["status"] = "ok";
252 response["address"] = address;
253 res.status = 200;
254 res.set_content(response.dump(), "application/json");
255 } catch (const std::exception& e) {
256 json j;
257 j["error"] = e.what();
258 res.status = 400;
259 res.set_content(j.dump(), "application/json");
260 }
261}
262
263void HandleStateUpdate(const httplib::Request& req, httplib::Response& res) {
264 ApplyCorsHeaders(res);
265
266 try {
267 json body = json::parse(req.body);
268
269 // State update received; store for panel use
270 (void)body; // Will be used when state storage is implemented
271
272 // TODO: Store state for use by MesenDebugPanel and RomDebugAgent
273 json response;
274 response["status"] = "ok";
275 res.status = 200;
276 res.set_content(response.dump(), "application/json");
277 } catch (const std::exception& e) {
278 json j;
279 j["error"] = e.what();
280 res.status = 400;
281 res.set_content(j.dump(), "application/json");
282 }
283}
284
285void HandleWindowShow(const httplib::Request& req, httplib::Response& res,
286 const std::function<bool()>& action) {
287 (void)req;
288 HandleWindowAction(action, "shown", res);
289}
290
291void HandleWindowHide(const httplib::Request& req, httplib::Response& res,
292 const std::function<bool()>& action) {
293 (void)req;
294 HandleWindowAction(action, "hidden", res);
295}
296
297void HandleCorsPreflight(const httplib::Request& req, httplib::Response& res) {
298 (void)req;
299 ApplyCorsHeaders(res);
300 res.status = 204;
301}
302
303// ---------------------------------------------------------------------------
304// Render endpoints
305// ---------------------------------------------------------------------------
306
307namespace {
308
309// Parse "room" query param — accepts decimal or 0x-prefixed hex.
310// Returns false and sets a 400 error response on failure.
311bool ParseRoomParam(const httplib::Request& req, httplib::Response& res,
312 int& room_id) {
313 if (!req.has_param("room")) {
314 json j;
315 j["error"] = "Missing required parameter: room";
316 res.status = 400;
317 res.set_content(j.dump(), "application/json");
318 return false;
319 }
320 const std::string s = req.get_param_value("room");
321 try {
322 room_id = std::stoi(s, nullptr, 0);
323 } catch (...) {
324 json j;
325 j["error"] = "Invalid room parameter";
326 j["value"] = s;
327 res.status = 400;
328 res.set_content(j.dump(), "application/json");
329 return false;
330 }
331 return true;
332}
333
334} // namespace
335
336void HandleRenderDungeon(const httplib::Request& req, httplib::Response& res,
337 yaze::app::service::RenderService* render_service) {
338 ApplyCorsHeaders(res);
339
340 if (!render_service) {
341 json j;
342 j["error"] = "Render service not available";
343 res.status = 503;
344 res.set_content(j.dump(), "application/json");
345 return;
346 }
347
348 int room_id = 0;
349 if (!ParseRoomParam(req, res, room_id))
350 return;
351
352 // Parse overlays — comma-separated: collision, sprites, objects, track,
353 // camera, grid, all.
354 uint32_t overlay_flags = yaze::app::service::RenderOverlay::kNone;
355 if (req.has_param("overlays")) {
356 std::istringstream ss(req.get_param_value("overlays"));
357 std::string tok;
358 while (std::getline(ss, tok, ',')) {
359 tok.erase(0, tok.find_first_not_of(" \t"));
360 if (!tok.empty())
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")
376 }
377 }
378
379 // Parse scale (clamped 0.25–8.0, default 1.0).
380 float scale = 1.0f;
381 if (req.has_param("scale")) {
382 try {
383 scale = std::stof(req.get_param_value("scale"));
384 if (scale < 0.25f)
385 scale = 0.25f;
386 if (scale > 8.0f)
387 scale = 8.0f;
388 } catch (...) {}
389 }
390
392 render_req.room_id = room_id;
393 render_req.overlay_flags = overlay_flags;
394 render_req.scale = scale;
395
396 auto result_or = render_service->RenderDungeonRoom(render_req);
397 if (!result_or.ok()) {
398 const auto& s = result_or.status();
399 json j;
400 j["error"] = std::string(s.message());
401 if (s.code() == absl::StatusCode::kInvalidArgument)
402 res.status = 400;
403 else if (s.code() == absl::StatusCode::kFailedPrecondition)
404 res.status = 503;
405 else
406 res.status = 500;
407 res.set_content(j.dump(), "application/json");
408 return;
409 }
410
411 const auto& result = *result_or;
412 res.status = 200;
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));
418}
419
421 const httplib::Request& req, httplib::Response& res,
422 yaze::app::service::RenderService* render_service) {
423 ApplyCorsHeaders(res);
424
425 if (!render_service) {
426 json j;
427 j["error"] = "Render service not available";
428 res.status = 503;
429 res.set_content(j.dump(), "application/json");
430 return;
431 }
432
433 int room_id = 0;
434 if (!ParseRoomParam(req, res, room_id))
435 return;
436
437 auto meta_or = render_service->GetDungeonRoomMetadata(room_id);
438 if (!meta_or.ok()) {
439 const auto& s = meta_or.status();
440 json j;
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");
444 return;
445 }
446
447 const auto& meta = *meta_or;
448 json j;
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;
462 res.status = 200;
463 res.set_content(j.dump(), "application/json");
464}
465
466// ---------------------------------------------------------------------------
467// Command execution endpoints (Phase 3)
468// ---------------------------------------------------------------------------
469
470void HandleCommandExecute(const httplib::Request& req, httplib::Response& res,
471 yaze::Rom* rom) {
472 ApplyCorsHeaders(res);
473
474 try {
475 json body = json::parse(req.body);
476 std::string command_name = body.value("command", "");
477 if (command_name.empty()) {
478 json j;
479 j["error"] = "Missing 'command' field";
480 res.status = 400;
481 res.set_content(j.dump(), "application/json");
482 return;
483 }
484
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>());
489 }
490 }
491
492 auto& registry = CommandRegistry::Instance();
493 if (!registry.HasCommand(command_name)) {
494 json j;
495 j["error"] = "Unknown command: " + command_name;
496 res.status = 404;
497 res.set_content(j.dump(), "application/json");
498 return;
499 }
500
501 std::string captured_output;
502 auto status = registry.Execute(command_name, args, rom, &captured_output);
503
504 json response;
505 if (status.ok()) {
506 response["status"] = "ok";
507 response["command"] = command_name;
508 // Try to parse output as JSON, fall back to string
509 try {
510 response["result"] = json::parse(captured_output);
511 } catch (...) {
512 response["result"] = captured_output;
513 }
514 res.status = 200;
515 } else {
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;
521 }
522 res.status = 500;
523 }
524 res.set_content(response.dump(), "application/json");
525 } catch (const std::exception& e) {
526 json j;
527 j["error"] = e.what();
528 res.status = 400;
529 res.set_content(j.dump(), "application/json");
530 }
531}
532
533void HandleCommandList(const httplib::Request& req, httplib::Response& res) {
534 (void)req;
535 ApplyCorsHeaders(res);
536
537 auto& registry = CommandRegistry::Instance();
538 json j_commands = json::array();
539
540 for (const auto& category : registry.GetCategories()) {
541 for (const auto& cmd_name : registry.GetCommandsInCategory(category)) {
542 const auto* meta = registry.GetMetadata(cmd_name);
543 if (!meta)
544 continue;
545
546 json j_cmd;
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);
553 }
554 }
555
556 json response;
557 response["commands"] = j_commands;
558 response["count"] = j_commands.size();
559 res.status = 200;
560 res.set_content(response.dump(), "application/json");
561}
562
563// ---------------------------------------------------------------------------
564// Annotation CRUD endpoints (Phase 4)
565// ---------------------------------------------------------------------------
566
567namespace {
568
569std::string ResolveAnnotationsPath(const std::string& project_path) {
570 if (project_path.empty())
571 return "";
572 // Match the iOS AnnotationStore resolution logic
573 return project_path + "/Docs/Dev/Planning/annotations.json";
574}
575
576json LoadAnnotationsFile(const std::string& path) {
577 if (path.empty())
578 return json::object();
579 std::ifstream file(path);
580 if (!file.is_open())
581 return json::object();
582 try {
583 return json::parse(file);
584 } catch (...) {
585 return json::object();
586 }
587}
588
589bool SaveAnnotationsFile(const std::string& path, const json& data) {
590 if (path.empty())
591 return false;
592
593 // Ensure parent directory exists
594 auto last_slash = path.find_last_of('/');
595 if (last_slash != std::string::npos) {
596 std::string dir = path.substr(0, last_slash);
597 // Use mkdir -p equivalent (simple approach)
598 std::string cmd = "mkdir -p '" + dir + "'";
599 (void)system(cmd.c_str());
600 }
601
602 std::ofstream file(path);
603 if (!file.is_open())
604 return false;
605 file << data.dump(2);
606 return file.good();
607}
608
609} // namespace
610
611void HandleAnnotationList(const httplib::Request& req, httplib::Response& res,
612 const std::string& project_path) {
613 ApplyCorsHeaders(res);
614
615 std::string path = ResolveAnnotationsPath(project_path);
616 if (path.empty()) {
617 json j;
618 j["error"] = "No project path configured";
619 res.status = 503;
620 res.set_content(j.dump(), "application/json");
621 return;
622 }
623
624 json file_data = LoadAnnotationsFile(path);
625 json annotations = file_data.value("annotations", json::array());
626
627 // Optional room filter
628 if (req.has_param("room")) {
629 int room_id = 0;
630 try {
631 room_id = std::stoi(req.get_param_value("room"), nullptr, 0);
632 } catch (...) {
633 json j;
634 j["error"] = "Invalid room parameter";
635 res.status = 400;
636 res.set_content(j.dump(), "application/json");
637 return;
638 }
639
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);
644 }
645 }
646 annotations = filtered;
647 }
648
649 json response;
650 response["annotations"] = annotations;
651 res.status = 200;
652 res.set_content(response.dump(), "application/json");
653}
654
655void HandleAnnotationCreate(const httplib::Request& req, httplib::Response& res,
656 const std::string& project_path) {
657 ApplyCorsHeaders(res);
658
659 std::string path = ResolveAnnotationsPath(project_path);
660 if (path.empty()) {
661 json j;
662 j["error"] = "No project path configured";
663 res.status = 503;
664 res.set_content(j.dump(), "application/json");
665 return;
666 }
667
668 try {
669 json new_annotation = json::parse(req.body);
670 json file_data = LoadAnnotationsFile(path);
671
672 if (!file_data.contains("annotations")) {
673 file_data["annotations"] = json::array();
674 }
675 file_data["annotations"].push_back(new_annotation);
676
677 if (SaveAnnotationsFile(path, file_data)) {
678 json response;
679 response["status"] = "ok";
680 response["id"] = new_annotation.value("id", "");
681 res.status = 201;
682 res.set_content(response.dump(), "application/json");
683 } else {
684 json j;
685 j["error"] = "Failed to write annotations file";
686 res.status = 500;
687 res.set_content(j.dump(), "application/json");
688 }
689 } catch (const std::exception& e) {
690 json j;
691 j["error"] = e.what();
692 res.status = 400;
693 res.set_content(j.dump(), "application/json");
694 }
695}
696
697void HandleAnnotationUpdate(const httplib::Request& req, httplib::Response& res,
698 const std::string& project_path) {
699 ApplyCorsHeaders(res);
700
701 std::string path = ResolveAnnotationsPath(project_path);
702 if (path.empty()) {
703 json j;
704 j["error"] = "No project path configured";
705 res.status = 503;
706 res.set_content(j.dump(), "application/json");
707 return;
708 }
709
710 // Extract annotation ID from URL path
711 std::string annotation_id;
712 if (!req.matches.empty() && req.matches.size() > 1) {
713 annotation_id = req.matches[1].str();
714 }
715 if (annotation_id.empty()) {
716 json j;
717 j["error"] = "Missing annotation ID in URL";
718 res.status = 400;
719 res.set_content(j.dump(), "application/json");
720 return;
721 }
722
723 try {
724 json updated = json::parse(req.body);
725 json file_data = LoadAnnotationsFile(path);
726 json& annotations = file_data["annotations"];
727
728 bool found = false;
729 for (auto& ann : annotations) {
730 if (ann.value("id", "") == annotation_id) {
731 // Merge updated fields
732 for (auto it = updated.begin(); it != updated.end(); ++it) {
733 ann[it.key()] = it.value();
734 }
735 found = true;
736 break;
737 }
738 }
739
740 if (!found) {
741 json j;
742 j["error"] = "Annotation not found: " + annotation_id;
743 res.status = 404;
744 res.set_content(j.dump(), "application/json");
745 return;
746 }
747
748 if (SaveAnnotationsFile(path, file_data)) {
749 json response;
750 response["status"] = "ok";
751 response["id"] = annotation_id;
752 res.status = 200;
753 res.set_content(response.dump(), "application/json");
754 } else {
755 json j;
756 j["error"] = "Failed to write annotations file";
757 res.status = 500;
758 res.set_content(j.dump(), "application/json");
759 }
760 } catch (const std::exception& e) {
761 json j;
762 j["error"] = e.what();
763 res.status = 400;
764 res.set_content(j.dump(), "application/json");
765 }
766}
767
768void HandleAnnotationDelete(const httplib::Request& req, httplib::Response& res,
769 const std::string& project_path) {
770 ApplyCorsHeaders(res);
771 (void)req;
772
773 std::string path = ResolveAnnotationsPath(project_path);
774 if (path.empty()) {
775 json j;
776 j["error"] = "No project path configured";
777 res.status = 503;
778 res.set_content(j.dump(), "application/json");
779 return;
780 }
781
782 std::string annotation_id;
783 if (!req.matches.empty() && req.matches.size() > 1) {
784 annotation_id = req.matches[1].str();
785 }
786 if (annotation_id.empty()) {
787 json j;
788 j["error"] = "Missing annotation ID in URL";
789 res.status = 400;
790 res.set_content(j.dump(), "application/json");
791 return;
792 }
793
794 json file_data = LoadAnnotationsFile(path);
795 json& annotations = file_data["annotations"];
796
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);
802 }
803 }
804
805 if (filtered.size() == original_size) {
806 json j;
807 j["error"] = "Annotation not found: " + annotation_id;
808 res.status = 404;
809 res.set_content(j.dump(), "application/json");
810 return;
811 }
812
813 file_data["annotations"] = filtered;
814 if (SaveAnnotationsFile(path, file_data)) {
815 json response;
816 response["status"] = "ok";
817 response["id"] = annotation_id;
818 res.status = 200;
819 res.set_content(response.dump(), "application/json");
820 } else {
821 json j;
822 j["error"] = "Failed to write annotations file";
823 res.status = 500;
824 res.set_content(j.dump(), "application/json");
825 }
826}
827
828} // namespace api
829} // namespace cli
830} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
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.
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)
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)
nlohmann::json json
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.