yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
message.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <iostream>
5#include <string>
6#include <vector>
7
8#include "absl/flags/declare.h"
9#include "absl/flags/flag.h"
10#include "absl/status/status.h"
11#include "absl/status/statusor.h"
12#include "absl/strings/ascii.h"
13#include "absl/strings/match.h"
14#include "absl/strings/numbers.h"
15#include "absl/strings/str_format.h"
17#include "rom/rom.h"
18#include "util/macro.h"
19
20ABSL_DECLARE_FLAG(std::string, rom);
21
22namespace yaze {
23namespace cli {
24namespace message {
25
26namespace {
27
28absl::StatusOr<Rom> LoadRomFromFlag() {
29 std::string rom_path = absl::GetFlag(FLAGS_rom);
30 if (rom_path.empty()) {
31 return absl::FailedPreconditionError(
32 "No ROM loaded. Use --rom=<path> to specify ROM file.");
33 }
34
35 Rom rom;
36 auto status = rom.LoadFromFile(rom_path);
37 if (!status.ok()) {
38 return absl::FailedPreconditionError(absl::StrFormat(
39 "Failed to load ROM from '%s': %s", rom_path, status.message()));
40 }
41
42 return rom;
43}
44
45std::vector<editor::MessageData> LoadMessages(Rom* rom) {
46 // Fix: Cast away constness for ReadAllTextData, which expects uint8_t*
47 return editor::ReadAllTextData(const_cast<uint8_t*>(rom->data()),
49}
50
51} // namespace
52
53absl::Status HandleMessageListCommand(const std::vector<std::string>& arg_vec,
54 Rom* rom_context) {
55 std::string format = "json";
56 int start_id = 0;
57 int end_id = -1; // -1 means all
58
59 for (size_t i = 0; i < arg_vec.size(); ++i) {
60 const std::string& token = arg_vec[i];
61 if (token == "--format") {
62 if (i + 1 >= arg_vec.size()) {
63 return absl::InvalidArgumentError("--format requires a value.");
64 }
65 format = absl::AsciiStrToLower(arg_vec[++i]);
66 } else if (absl::StartsWith(token, "--format=")) {
67 format = absl::AsciiStrToLower(token.substr(9));
68 } else if (token == "--range") {
69 if (i + 1 >= arg_vec.size()) {
70 return absl::InvalidArgumentError(
71 "--range requires a value (start-end).");
72 }
73 std::string range = arg_vec[++i];
74 size_t dash_pos = range.find('-');
75 if (dash_pos == std::string::npos) {
76 return absl::InvalidArgumentError(
77 "--range format must be start-end (e.g. 0-100)");
78 }
79 if (!absl::SimpleAtoi(range.substr(0, dash_pos), &start_id) ||
80 !absl::SimpleAtoi(range.substr(dash_pos + 1), &end_id)) {
81 return absl::InvalidArgumentError("Invalid range format");
82 }
83 } else if (absl::StartsWith(token, "--range=")) {
84 std::string range = token.substr(8);
85 size_t dash_pos = range.find('-');
86 if (dash_pos == std::string::npos) {
87 return absl::InvalidArgumentError(
88 "--range format must be start-end (e.g. 0-100)");
89 }
90 if (!absl::SimpleAtoi(range.substr(0, dash_pos), &start_id) ||
91 !absl::SimpleAtoi(range.substr(dash_pos + 1), &end_id)) {
92 return absl::InvalidArgumentError("Invalid range format");
93 }
94 }
95 }
96
97 if (format != "json" && format != "text") {
98 return absl::InvalidArgumentError("--format must be either json or text");
99 }
100
101 Rom rom_storage;
102 Rom* rom = nullptr;
103 if (rom_context != nullptr && rom_context->is_loaded()) {
104 rom = rom_context;
105 } else {
106 auto rom_or = LoadRomFromFlag();
107 if (!rom_or.ok()) {
108 return rom_or.status();
109 }
110 rom_storage = std::move(rom_or.value());
111 rom = &rom_storage;
112 }
113
114 auto messages = LoadMessages(rom);
115
116 if (end_id < 0) {
117 end_id = static_cast<int>(messages.size()) - 1;
118 }
119
120 start_id =
121 std::max(0, std::min(start_id, static_cast<int>(messages.size()) - 1));
122 end_id = std::max(start_id,
123 std::min(end_id, static_cast<int>(messages.size()) - 1));
124
125 if (format == "json") {
126 std::cout << "{\n";
127 std::cout << absl::StrFormat(" \"total_messages\": %zu,\n",
128 messages.size());
129 std::cout << absl::StrFormat(" \"range\": [%d, %d],\n", start_id, end_id);
130 std::cout << " \"messages\": [\n";
131
132 bool first = true;
133 for (int i = start_id; i <= end_id; ++i) {
134 const auto& msg = messages[i];
135 if (!first)
136 std::cout << ",\n";
137 std::cout << " {\n";
138 std::cout << absl::StrFormat(" \"id\": %d,\n", msg.ID);
139 std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n",
140 msg.Address);
141
142 // Escape quotes in the text
143 std::string escaped_text = msg.ContentsParsed;
144 size_t pos = 0;
145 while ((pos = escaped_text.find('"', pos)) != std::string::npos) {
146 escaped_text.insert(pos, "\\");
147 pos += 2;
148 }
149 std::cout << absl::StrFormat(" \"text\": \"%s\"\n", escaped_text);
150 std::cout << " }";
151 first = false;
152 }
153 std::cout << "\n ]\n";
154 std::cout << "}\n";
155 } else {
156 std::cout << absl::StrFormat("📝 Messages %d-%d (Total: %zu)\n", start_id,
157 end_id, messages.size());
158 std::cout << std::string(60, '=') << "\n";
159 for (int i = start_id; i <= end_id; ++i) {
160 const auto& msg = messages[i];
161 std::cout << absl::StrFormat("[%03d] @ 0x%06X\n", msg.ID, msg.Address);
162 std::cout << " " << msg.ContentsParsed << "\n";
163 std::cout << std::string(60, '-') << "\n";
164 }
165 }
166
167 return absl::OkStatus();
168}
169
170absl::Status HandleMessageReadCommand(const std::vector<std::string>& arg_vec,
171 Rom* rom_context) {
172 int message_id = -1;
173 std::string format = "json";
174
175 for (size_t i = 0; i < arg_vec.size(); ++i) {
176 const std::string& token = arg_vec[i];
177 if (token == "--id") {
178 if (i + 1 >= arg_vec.size()) {
179 return absl::InvalidArgumentError("--id requires a value.");
180 }
181 if (!absl::SimpleAtoi(arg_vec[++i], &message_id)) {
182 return absl::InvalidArgumentError("Invalid message ID format.");
183 }
184 } else if (absl::StartsWith(token, "--id=")) {
185 if (!absl::SimpleAtoi(token.substr(5), &message_id)) {
186 return absl::InvalidArgumentError("Invalid message ID format.");
187 }
188 } else if (token == "--format") {
189 if (i + 1 >= arg_vec.size()) {
190 return absl::InvalidArgumentError("--format requires a value.");
191 }
192 format = absl::AsciiStrToLower(arg_vec[++i]);
193 } else if (absl::StartsWith(token, "--format=")) {
194 format = absl::AsciiStrToLower(token.substr(9));
195 }
196 }
197
198 if (message_id < 0) {
199 return absl::InvalidArgumentError(
200 "Usage: message-read --id <message_id> [--format <json|text>]");
201 }
202
203 if (format != "json" && format != "text") {
204 return absl::InvalidArgumentError("--format must be either json or text");
205 }
206
207 Rom rom_storage;
208 Rom* rom = nullptr;
209 if (rom_context != nullptr && rom_context->is_loaded()) {
210 rom = rom_context;
211 } else {
212 auto rom_or = LoadRomFromFlag();
213 if (!rom_or.ok()) {
214 return rom_or.status();
215 }
216 rom_storage = std::move(rom_or.value());
217 rom = &rom_storage;
218 }
219
220 auto messages = LoadMessages(rom);
221
222 if (message_id >= static_cast<int>(messages.size())) {
223 return absl::NotFoundError(absl::StrFormat(
224 "Message ID %d not found (max: %d)", message_id, messages.size() - 1));
225 }
226
227 const auto& msg = messages[message_id];
228
229 if (format == "json") {
230 std::cout << "{\n";
231 std::cout << absl::StrFormat(" \"id\": %d,\n", msg.ID);
232 std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n", msg.Address);
233
234 // Escape quotes
235 std::string escaped_text = msg.ContentsParsed;
236 size_t pos = 0;
237 while ((pos = escaped_text.find('"', pos)) != std::string::npos) {
238 escaped_text.insert(pos, "\\");
239 pos += 2;
240 }
241 std::cout << absl::StrFormat(" \"text\": \"%s\",\n", escaped_text);
242 std::cout << absl::StrFormat(" \"length\": %zu\n", msg.Data.size());
243 std::cout << "}\n";
244 } else {
245 std::cout << absl::StrFormat("📝 Message #%d\n", msg.ID);
246 std::cout << absl::StrFormat("Address: 0x%06X\n", msg.Address);
247 std::cout << absl::StrFormat("Length: %zu bytes\n", msg.Data.size());
248 std::cout << std::string(60, '-') << "\n";
249 std::cout << msg.ContentsParsed << "\n";
250 }
251
252 return absl::OkStatus();
253}
254
255absl::Status HandleMessageSearchCommand(const std::vector<std::string>& arg_vec,
256 Rom* rom_context) {
257 std::string query;
258 std::string format = "json";
259
260 for (size_t i = 0; i < arg_vec.size(); ++i) {
261 const std::string& token = arg_vec[i];
262 if (token == "--query") {
263 if (i + 1 >= arg_vec.size()) {
264 return absl::InvalidArgumentError("--query requires a value.");
265 }
266 query = arg_vec[++i];
267 } else if (absl::StartsWith(token, "--query=")) {
268 query = token.substr(8);
269 } else if (token == "--format") {
270 if (i + 1 >= arg_vec.size()) {
271 return absl::InvalidArgumentError("--format requires a value.");
272 }
273 format = absl::AsciiStrToLower(arg_vec[++i]);
274 } else if (absl::StartsWith(token, "--format=")) {
275 format = absl::AsciiStrToLower(token.substr(9));
276 }
277 }
278
279 if (query.empty()) {
280 return absl::InvalidArgumentError(
281 "Usage: message-search --query <text> [--format <json|text>]");
282 }
283
284 if (format != "json" && format != "text") {
285 return absl::InvalidArgumentError("--format must be either json or text");
286 }
287
288 Rom rom_storage;
289 Rom* rom = nullptr;
290 if (rom_context != nullptr && rom_context->is_loaded()) {
291 rom = rom_context;
292 } else {
293 auto rom_or = LoadRomFromFlag();
294 if (!rom_or.ok()) {
295 return rom_or.status();
296 }
297 rom_storage = std::move(rom_or.value());
298 rom = &rom_storage;
299 }
300
301 auto messages = LoadMessages(rom);
302 std::string lowered_query = absl::AsciiStrToLower(query);
303
304 std::vector<int> matches;
305 for (const auto& msg : messages) {
306 std::string lowered_text = absl::AsciiStrToLower(msg.ContentsParsed);
307 if (lowered_text.find(lowered_query) != std::string::npos) {
308 matches.push_back(msg.ID);
309 }
310 }
311
312 if (format == "json") {
313 std::cout << "{\n";
314 std::cout << absl::StrFormat(" \"query\": \"%s\",\n", query);
315 std::cout << absl::StrFormat(" \"match_count\": %zu,\n", matches.size());
316 std::cout << " \"matches\": [\n";
317
318 for (size_t i = 0; i < matches.size(); ++i) {
319 const auto& msg = messages[matches[i]];
320 if (i > 0)
321 std::cout << ",\n";
322
323 std::string escaped_text = msg.ContentsParsed;
324 size_t pos = 0;
325 while ((pos = escaped_text.find('"', pos)) != std::string::npos) {
326 escaped_text.insert(pos, "\\");
327 pos += 2;
328 }
329
330 std::cout << " {\n";
331 std::cout << absl::StrFormat(" \"id\": %d,\n", msg.ID);
332 std::cout << absl::StrFormat(" \"address\": \"0x%06X\",\n",
333 msg.Address);
334 std::cout << absl::StrFormat(" \"text\": \"%s\"\n", escaped_text);
335 std::cout << " }";
336 }
337 std::cout << "\n ]\n";
338 std::cout << "}\n";
339 } else {
340 std::cout << absl::StrFormat("🔍 Search: \"%s\" → %zu match(es)\n", query,
341 matches.size());
342 std::cout << std::string(60, '=') << "\n";
343
344 for (int match_id : matches) {
345 const auto& msg = messages[match_id];
346 std::cout << absl::StrFormat("[%03d] @ 0x%06X\n", msg.ID, msg.Address);
347 std::cout << " " << msg.ContentsParsed << "\n";
348 std::cout << std::string(60, '-') << "\n";
349 }
350 }
351
352 return absl::OkStatus();
353}
354
355absl::Status HandleMessageStatsCommand(const std::vector<std::string>& arg_vec,
356 Rom* rom_context) {
357 std::string format = "json";
358
359 for (size_t i = 0; i < arg_vec.size(); ++i) {
360 const std::string& token = arg_vec[i];
361 if (token == "--format") {
362 if (i + 1 >= arg_vec.size()) {
363 return absl::InvalidArgumentError("--format requires a value.");
364 }
365 format = absl::AsciiStrToLower(arg_vec[++i]);
366 } else if (absl::StartsWith(token, "--format=")) {
367 format = absl::AsciiStrToLower(token.substr(9));
368 }
369 }
370
371 if (format != "json" && format != "text") {
372 return absl::InvalidArgumentError("--format must be either json or text");
373 }
374
375 Rom rom_storage;
376 Rom* rom = nullptr;
377 if (rom_context != nullptr && rom_context->is_loaded()) {
378 rom = rom_context;
379 } else {
380 auto rom_or = LoadRomFromFlag();
381 if (!rom_or.ok()) {
382 return rom_or.status();
383 }
384 rom_storage = std::move(rom_or.value());
385 rom = &rom_storage;
386 }
387
388 auto messages = LoadMessages(rom);
389
390 size_t total_bytes = 0;
391 size_t max_length = 0;
392 size_t min_length = SIZE_MAX;
393
394 for (const auto& msg : messages) {
395 size_t len = msg.Data.size();
396 total_bytes += len;
397 max_length = std::max(max_length, len);
398 min_length = std::min(min_length, len);
399 }
400
401 double avg_length = messages.empty()
402 ? 0.0
403 : static_cast<double>(total_bytes) / messages.size();
404
405 if (format == "json") {
406 std::cout << "{\n";
407 std::cout << absl::StrFormat(" \"total_messages\": %zu,\n",
408 messages.size());
409 std::cout << absl::StrFormat(" \"total_bytes\": %zu,\n", total_bytes);
410 std::cout << absl::StrFormat(" \"average_length\": %.2f,\n", avg_length);
411 std::cout << absl::StrFormat(" \"min_length\": %zu,\n", min_length);
412 std::cout << absl::StrFormat(" \"max_length\": %zu\n", max_length);
413 std::cout << "}\n";
414 } else {
415 std::cout << "📊 Message Statistics\n";
416 std::cout << std::string(40, '=') << "\n";
417 std::cout << absl::StrFormat("Total Messages: %zu\n", messages.size());
418 std::cout << absl::StrFormat("Total Bytes: %zu\n", total_bytes);
419 std::cout << absl::StrFormat("Average Length: %.2f bytes\n", avg_length);
420 std::cout << absl::StrFormat("Min Length: %zu bytes\n", min_length);
421 std::cout << absl::StrFormat("Max Length: %zu bytes\n", max_length);
422 }
423
424 return absl::OkStatus();
425}
426
427} // namespace message
428} // namespace cli
429} // 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:24
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:74
auto data() const
Definition rom.h:135
bool is_loaded() const
Definition rom.h:128
ABSL_DECLARE_FLAG(std::string, rom)
std::vector< editor::MessageData > LoadMessages(Rom *rom)
Definition message.cc:45
absl::Status HandleMessageSearchCommand(const std::vector< std::string > &arg_vec, Rom *rom_context)
Search for messages containing specific text.
Definition message.cc:255
absl::Status HandleMessageReadCommand(const std::vector< std::string > &arg_vec, Rom *rom_context)
Read a specific message by ID.
Definition message.cc:170
absl::Status HandleMessageListCommand(const std::vector< std::string > &arg_vec, Rom *rom_context)
List all messages in the ROM.
Definition message.cc:53
absl::Status HandleMessageStatsCommand(const std::vector< std::string > &arg_vec, Rom *rom_context)
Get message statistics and overview.
Definition message.cc:355
std::vector< MessageData > ReadAllTextData(uint8_t *rom, int pos)
constexpr int kTextData