yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
subcommand_routes.cc
Go to the documentation of this file.
2
3#include <cstddef>
4#include <iterator>
5#include <optional>
6
7#include "absl/status/status.h"
8#include "absl/strings/match.h"
9#include "absl/strings/str_cat.h"
10#include "absl/strings/str_join.h"
11
12namespace yaze::cli {
13namespace {
14
16 absl::string_view name;
17 absl::string_view usage;
18};
19
20// `z3ed rom <name>` runs the registered command `rom-<name>`.
22 {"info", "z3ed rom info --rom=<path>"},
23 {"validate", "z3ed rom validate --rom=<path>"},
24 {"read", "z3ed rom read --address=0x1000 --length=16 --rom=<path>"},
25 {"write", "z3ed rom write --address=0x1000 --value=0xFF --rom=<path>"},
26 {"diff", "z3ed rom diff --rom_a=<a> --rom_b=<b>"},
27 {"compare", "z3ed rom compare --rom=<path> --baseline=<path>"},
28 {"doctor", "z3ed rom doctor --rom=<path>"},
29};
30
31enum class DebugBackend { kMesen, kGrpc };
32
33struct DebugRoute {
34 absl::string_view topic;
35 absl::string_view alias = {};
36 // Required second word, e.g. "read" in `debug mem read`.
37 absl::string_view action = {};
38 absl::string_view mesen_command;
39 // Empty when the topic has no gRPC equivalent.
40 absl::string_view grpc_command = {};
41 // Appended to the arguments when routed to Mesen2.
42 absl::string_view mesen_extra_arg = {};
43 absl::string_view usage;
44};
45
46constexpr DebugRoute kDebugRoutes[] = {
47 {.topic = "state",
48 .alias = "gamestate",
49 .mesen_command = "mesen-gamestate",
50 .grpc_command = "emulator-get-state",
51 .usage = "z3ed debug state [--backend=mesen|grpc]"},
52 {.topic = "sprites",
53 .mesen_command = "mesen-sprites",
54 .usage = "z3ed debug sprites [--backend=mesen]"},
55 {.topic = "cpu",
56 .mesen_command = "mesen-cpu",
57 .usage = "z3ed debug cpu [--backend=mesen]"},
58 {.topic = "mem",
59 .alias = "memory",
60 .action = "read",
61 .mesen_command = "mesen-memory-read",
62 .grpc_command = "emulator-read-memory",
63 .usage = "z3ed debug mem read --address=<addr> [--length=<n>]"},
64 {.topic = "mem",
65 .alias = "memory",
66 .action = "write",
67 .mesen_command = "mesen-memory-write",
68 .grpc_command = "emulator-write-memory",
69 .usage = "z3ed debug mem write --address=<addr> --value=<hex>"},
70 {.topic = "disasm",
71 .mesen_command = "mesen-disasm",
72 .usage = "z3ed debug disasm --address=<addr> [--count=<n>]"},
73 {.topic = "trace",
74 .mesen_command = "mesen-trace",
75 .usage = "z3ed debug trace --address=<addr> [--count=<n>]"},
76 {.topic = "breakpoint",
77 .alias = "bp",
78 .mesen_command = "mesen-breakpoint",
79 .usage = "z3ed debug breakpoint --address=<addr>"},
80 {.topic = "control",
81 .mesen_command = "mesen-control",
82 .usage = "z3ed debug control --action=<pause|resume|step|reset>"},
83 {.topic = "reset",
84 .mesen_command = "mesen-control",
85 .grpc_command = "emulator-reset",
86 .mesen_extra_arg = "--action=reset",
87 .usage = "z3ed debug reset [--backend=mesen|grpc]"},
88};
89
90bool MatchesTopic(const DebugRoute& route, absl::string_view word) {
91 return word == route.topic || (!route.alias.empty() && word == route.alias);
92}
93
94struct DebugWords {
95 DebugBackend backend = DebugBackend::kMesen;
96 std::vector<std::string> words;
97};
98
99// Removes --backend / --debug-backend from `args`, wherever they appear.
100absl::StatusOr<DebugWords> ExtractDebugBackend(
101 absl::Span<const std::string> args) {
102 DebugWords result;
103 result.words.reserve(args.size());
104 for (size_t i = 0; i < args.size(); ++i) {
105 const std::string& token = args[i];
106 std::optional<std::string> value;
107 if (absl::StartsWith(token, "--backend=") ||
108 absl::StartsWith(token, "--debug-backend=")) {
109 value = token.substr(token.find('=') + 1);
110 } else if (token == "--backend" || token == "--debug-backend") {
111 if (i + 1 >= args.size()) {
112 return absl::InvalidArgumentError(
113 absl::StrCat(token, " requires a value (mesen or grpc)"));
114 }
115 value = args[++i];
116 } else {
117 result.words.push_back(token);
118 continue;
119 }
120
121 if (*value == "mesen") {
122 result.backend = DebugBackend::kMesen;
123 } else if (*value == "grpc") {
124 result.backend = DebugBackend::kGrpc;
125 } else {
126 return absl::InvalidArgumentError(absl::StrCat(
127 "Unknown debug backend '", *value, "' (expected mesen or grpc)"));
128 }
129 }
130 return result;
131}
132
133} // namespace
134
135absl::StatusOr<CommandInvocation> ResolveRomSubcommand(
136 absl::Span<const std::string> args) {
137 if (args.empty()) {
138 return absl::InvalidArgumentError("rom requires a subcommand");
139 }
140 for (const RomSubcommand& subcommand : kRomSubcommands) {
141 if (args[0] == subcommand.name) {
142 return CommandInvocation{
143 .command = absl::StrCat("rom-", subcommand.name),
144 .args = {args.begin() + 1, args.end()},
145 };
146 }
147 }
148 return absl::InvalidArgumentError(
149 absl::StrCat("Unknown rom subcommand: ", args[0]));
150}
151
152std::vector<absl::string_view> RomSubcommandNames() {
153 std::vector<absl::string_view> names;
154 names.reserve(std::size(kRomSubcommands));
155 for (const RomSubcommand& subcommand : kRomSubcommands) {
156 names.push_back(subcommand.name);
157 }
158 return names;
159}
160
161std::string RomSubcommandUsage() {
162 std::string usage;
163 for (const RomSubcommand& subcommand : kRomSubcommands) {
164 absl::StrAppend(&usage, " ", subcommand.usage, "\n");
165 }
166 return usage;
167}
168
169absl::StatusOr<CommandInvocation> ResolveDebugSubcommand(
170 absl::Span<const std::string> args) {
171 absl::StatusOr<DebugWords> parsed = ExtractDebugBackend(args);
172 if (!parsed.ok()) {
173 return parsed.status();
174 }
175 const DebugBackend backend = parsed->backend;
176 const std::vector<std::string>& words = parsed->words;
177 if (words.empty()) {
178 return absl::InvalidArgumentError("debug requires a subcommand");
179 }
180
181 const std::string& topic = words[0];
182 std::vector<absl::string_view> actions;
183 for (const DebugRoute& route : kDebugRoutes) {
184 if (!MatchesTopic(route, topic)) {
185 continue;
186 }
187 size_t consumed_words = 1;
188 if (!route.action.empty()) {
189 actions.push_back(route.action);
190 if (words.size() < 2 || words[1] != route.action) {
191 continue;
192 }
193 consumed_words = 2;
194 }
195
196 const absl::string_view command = backend == DebugBackend::kGrpc
197 ? route.grpc_command
198 : route.mesen_command;
199 if (command.empty()) {
200 return absl::FailedPreconditionError(absl::StrCat(
201 "debug ", topic, " has no gRPC backend; use --backend=mesen"));
202 }
203
204 CommandInvocation invocation{
205 .command = std::string(command),
206 .args = {words.begin() + consumed_words, words.end()},
207 };
208 if (backend == DebugBackend::kMesen && !route.mesen_extra_arg.empty()) {
209 invocation.args.emplace_back(route.mesen_extra_arg);
210 }
211 return invocation;
212 }
213
214 if (!actions.empty()) {
215 return absl::InvalidArgumentError(absl::StrCat(
216 "debug ", topic, " requires one of: ", absl::StrJoin(actions, ", ")));
217 }
218 return absl::InvalidArgumentError(
219 absl::StrCat("Unknown debug subcommand: ", topic));
220}
221
222std::string DebugSubcommandUsage() {
223 std::string usage;
224 for (const DebugRoute& route : kDebugRoutes) {
225 absl::StrAppend(&usage, " ", route.usage, "\n");
226 }
227 return usage;
228}
229
230} // namespace yaze::cli
bool MatchesTopic(const DebugRoute &route, absl::string_view word)
absl::StatusOr< DebugWords > ExtractDebugBackend(absl::Span< const std::string > args)
absl::StatusOr< CommandInvocation > ResolveRomSubcommand(absl::Span< const std::string > args)
std::string RomSubcommandUsage()
absl::StatusOr< CommandInvocation > ResolveDebugSubcommand(absl::Span< const std::string > args)
std::vector< absl::string_view > RomSubcommandNames()
std::string DebugSubcommandUsage()