yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
command_context.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <iostream>
6
7#include "absl/flags/declare.h"
8#include "absl/flags/flag.h"
9#include "absl/strings/ascii.h"
10#include "absl/strings/match.h"
11#include "absl/strings/str_format.h"
12#include "absl/strings/str_join.h"
14#include "cli/util/hex_util.h"
15#include "core/project.h"
18
20
21ABSL_DECLARE_FLAG(std::string, rom);
22ABSL_DECLARE_FLAG(bool, mock_rom);
23
24namespace yaze {
25namespace cli {
26namespace resources {
27
28namespace {
29
30bool ProjectUsesCustomObjects(const project::YazeProject& project) {
31 return !project.custom_objects_folder.empty() ||
32 !project.custom_object_files.empty();
33}
34
35} // namespace
36
37// ============================================================================
38// CommandContext Implementation
39// ============================================================================
40
41CommandContext::CommandContext(const Config& config) : config_(config) {}
42
46
48 if (initialized_) {
49 return absl::OkStatus();
50 }
51
52 // Determine ROM source
53 std::string rom_path = config_.rom_path.has_value()
55 : absl::GetFlag(FLAGS_rom);
56
57 if (config_.external_rom_context != nullptr &&
60 } else if (config_.use_mock_rom) {
62 if (!status.ok()) {
63 return status;
64 }
66 } else if (!rom_path.empty()) {
67 auto status = rom_storage_.LoadFromFile(rom_path);
68 if (!status.ok()) {
69 return absl::FailedPreconditionError(absl::StrFormat(
70 "Failed to load ROM from '%s': %s", rom_path, status.message()));
71 }
73 }
74
75 if (active_rom_ == nullptr) {
76 return absl::FailedPreconditionError(
77 "No ROM loaded. Use --rom=<path> or --mock-rom for testing.");
78 }
79
80 auto project_status = ApplyProjectRuntimeContext();
81 if (!project_status.ok()) {
82 return project_status;
83 }
84
85 // Auto-load symbols if available
86 std::string symbols_path =
87 config_.symbols_path.has_value() ? *config_.symbols_path : "";
88 if (symbols_path.empty() && !rom_path.empty()) {
89 // Try ROM name with .mlb or .sym
90 std::string base = rom_path;
91 size_t last_dot = base.find_last_of('.');
92 if (last_dot != std::string::npos) {
93 base = base.substr(0, last_dot);
94 }
95
96 // Try common extensions
97 std::vector<std::string> exts = {".mlb", ".sfc.mlb", ".sym", ".cpu.sym",
98 "sourcemap.json"};
99 for (const auto& ext : exts) {
100 std::string path = base + ext;
101 if (std::ifstream(path).good()) {
102 auto sym_status = symbol_provider_.LoadSymbolFile(path);
103 if (sym_status.ok() && symbol_provider_.HasSymbols()) {
104 symbols_path = path;
105 break;
106 }
107 }
108 }
109 } else if (!symbols_path.empty()) {
110 auto sym_status = symbol_provider_.LoadSymbolFile(symbols_path);
111 if (!sym_status.ok() && config_.verbose) {
112 std::cerr << "Warning: Failed to load symbols from " << symbols_path
113 << ": " << sym_status.message() << std::endl;
114 }
115 }
116
117 initialized_ = true;
118 return absl::OkStatus();
119}
120
121absl::StatusOr<Rom*> CommandContext::GetRom() {
122 if (!initialized_) {
123 auto status = Initialize();
124 if (!status.ok()) {
125 return status;
126 }
127 }
128
129 if (active_rom_ == nullptr) {
130 return absl::FailedPreconditionError("ROM not loaded");
131 }
132
133 return active_rom_;
134}
135
142
144 if (!rom->resource_label()) {
145 return absl::FailedPreconditionError("ROM has no resource label manager");
146 }
147
148 if (!rom->resource_label()->labels_loaded_ ||
149 rom->resource_label()->labels_.empty()) {
150 project::YazeProject project;
151 project.use_embedded_labels = true;
152 auto labels_status = project.InitializeEmbeddedLabels(
154 if (labels_status.ok()) {
155 rom->resource_label()->labels_ = project.resource_labels;
156 rom->resource_label()->labels_loaded_ = true;
157 } else {
158 return labels_status;
159 }
160 }
161
162 return absl::OkStatus();
163}
164
167 return absl::OkStatus();
168 }
169 if (!config_.project_context_path.has_value()) {
170 return absl::OkStatus();
171 }
172 if (config_.project_context_path->empty()) {
173 return absl::InvalidArgumentError("--project-context cannot be empty");
174 }
175
176 project::YazeProject project;
177 auto open_status = project.Open(*config_.project_context_path);
178 if (!open_status.ok()) {
179 return absl::FailedPreconditionError(
180 absl::StrFormat("Failed to open project context '%s': %s",
181 *config_.project_context_path, open_status.message()));
182 }
183
184 // Keep editor parity by auto-enabling custom objects when project config
185 // defines custom object data but stale feature flags are still disabled.
186 if (ProjectUsesCustomObjects(project) &&
188 project.feature_flags.kEnableCustomObjects = true;
189 if (config_.verbose) {
190 std::cerr
191 << "Warning: project context has custom object data but "
192 "kEnableCustomObjects was disabled. Enabling for this command.\n";
193 }
194 }
195
196 loaded_project_ = std::move(project);
200
201 auto& active_project = loaded_project_.value();
202 core::FeatureFlags::get() = active_project.feature_flags;
204
205 if (!active_project.custom_object_files.empty()) {
207 active_project.custom_object_files);
208 } else {
210 }
211
212 if (!active_project.custom_objects_folder.empty()) {
214 active_project.GetAbsolutePath(active_project.custom_objects_folder));
215 } else {
216 // Avoid inheriting a stale base path from a previous singleton state.
218 }
219
221 return absl::OkStatus();
222}
223
244
245// ============================================================================
246// ArgumentParser Implementation
247// ============================================================================
248
249ArgumentParser::ArgumentParser(const std::vector<std::string>& args)
250 : args_(args) {}
251
252std::optional<std::string> ArgumentParser::FindArgValue(
253 const std::string& name) const {
254 std::string flag = "--" + name;
255 std::string equals_form = flag + "=";
256
257 for (size_t i = 0; i < args_.size(); ++i) {
258 const std::string& arg = args_[i];
259
260 // Check for --name=value form
261 if (absl::StartsWith(arg, equals_form)) {
262 return arg.substr(equals_form.length());
263 }
264
265 // Check for --name value form
266 if (arg == flag && i + 1 < args_.size()) {
267 return args_[i + 1];
268 }
269 }
270
271 return std::nullopt;
272}
273
274std::optional<std::string> ArgumentParser::GetString(
275 const std::string& name) const {
276 return FindArgValue(name);
277}
278
279absl::StatusOr<int> ArgumentParser::GetInt(const std::string& name) const {
280 auto value = FindArgValue(name);
281 if (!value.has_value()) {
282 return absl::NotFoundError(
283 absl::StrFormat("Argument '--%s' not found", name));
284 }
285
286 // Try hex first (with 0x prefix)
287 if (absl::StartsWith(*value, "0x") || absl::StartsWith(*value, "0X")) {
288 int result;
289 if (ParseHexString(value->substr(2), &result)) {
290 return result;
291 }
292 return absl::InvalidArgumentError(
293 absl::StrFormat("Invalid hex integer for '--%s': %s", name, *value));
294 }
295
296 // Try decimal
297 int result;
298 if (absl::SimpleAtoi(*value, &result)) {
299 return result;
300 }
301
302 return absl::InvalidArgumentError(
303 absl::StrFormat("Invalid integer for '--%s': %s", name, *value));
304}
305
306absl::StatusOr<int> ArgumentParser::GetHex(const std::string& name) const {
307 auto value = FindArgValue(name);
308 if (!value.has_value()) {
309 return absl::NotFoundError(
310 absl::StrFormat("Argument '--%s' not found", name));
311 }
312
313 // Strip 0x prefix if present
314 std::string hex_str = *value;
315 if (absl::StartsWith(hex_str, "0x") || absl::StartsWith(hex_str, "0X")) {
316 hex_str = hex_str.substr(2);
317 }
318
319 int result;
320 if (ParseHexString(hex_str, &result)) {
321 return result;
322 }
323
324 return absl::InvalidArgumentError(
325 absl::StrFormat("Invalid hex value for '--%s': %s", name, *value));
326}
327
328bool ArgumentParser::HasFlag(const std::string& name) const {
329 std::string flag = "--" + name;
330 for (const auto& arg : args_) {
331 if (arg == flag) {
332 return true;
333 }
334 }
335 return false;
336}
337
338std::vector<std::string> ArgumentParser::GetPositional() const {
339 std::vector<std::string> positional;
340 for (size_t i = 0; i < args_.size(); ++i) {
341 const std::string& arg = args_[i];
342 if (!absl::StartsWith(arg, "--")) {
343 positional.push_back(arg);
344 } else if (arg.find('=') == std::string::npos && i + 1 < args_.size()) {
345 // Skip the next argument as it's the value for this flag
346 ++i;
347 }
348 }
349 return positional;
350}
351
353 const std::vector<std::string>& required) const {
354 std::vector<std::string> missing;
355 for (const auto& arg : required) {
356 if (!FindArgValue(arg).has_value()) {
357 missing.push_back("--" + arg);
358 }
359 }
360
361 if (!missing.empty()) {
362 return absl::InvalidArgumentError(absl::StrFormat(
363 "Missing required arguments: %s", absl::StrJoin(missing, ", ")));
364 }
365
366 return absl::OkStatus();
367}
368
369// ============================================================================
370// OutputFormatter Implementation
371// ============================================================================
372
373absl::StatusOr<OutputFormatter> OutputFormatter::FromString(
374 const std::string& format) {
375 std::string lower = absl::AsciiStrToLower(format);
376 if (lower == "json") {
378 } else if (lower == "text") {
380 } else {
381 return absl::InvalidArgumentError(absl::StrFormat(
382 "Unknown format: %s (expected 'json' or 'text')", format));
383 }
384}
385
386void OutputFormatter::BeginObject(const std::string& title) {
387 if (IsJson()) {
388 if (!first_field_) {
389 buffer_ += ",\n";
390 }
391 AddIndent();
392 // Only output a key if we are inside another object (indent > 0) and not in an array
393 if (!title.empty() && !in_array_ && indent_level_ > 0) {
394 buffer_ += absl::StrFormat("\"%s\": {\n", EscapeJson(title));
395 } else {
396 buffer_ += "{\n";
397 }
399 first_field_ = true;
400 } else if (IsText() && !title.empty()) {
401 buffer_ += absl::StrFormat("=== %s ===\n", title);
402 }
403}
404
406 if (IsJson()) {
408 if (first_field_) {
409 if (!buffer_.empty() && buffer_.back() == '\n') {
410 buffer_.pop_back();
411 }
412 buffer_ += "}";
413 return;
414 }
415 buffer_ += "\n";
416 AddIndent();
417 buffer_ += "}";
418 }
419}
420
421void OutputFormatter::AddField(const std::string& key,
422 const std::string& value) {
423 if (IsJson()) {
424 if (!first_field_) {
425 buffer_ += ",\n";
426 }
427 first_field_ = false;
428 AddIndent();
429 buffer_ +=
430 absl::StrFormat("\"%s\": \"%s\"", EscapeJson(key), EscapeJson(value));
431 } else {
432 buffer_ += absl::StrFormat(" %-20s : %s\n", key, value);
433 }
434}
435
436void OutputFormatter::AddField(const std::string& key, const char* value) {
437 AddField(key, value != nullptr ? std::string(value) : std::string());
438}
439
440void OutputFormatter::AddField(const std::string& key, int value) {
441 if (IsJson()) {
442 if (!first_field_) {
443 buffer_ += ",\n";
444 }
445 first_field_ = false;
446 AddIndent();
447 buffer_ += absl::StrFormat("\"%s\": %d", EscapeJson(key), value);
448 } else {
449 buffer_ += absl::StrFormat(" %-20s : %d\n", key, value);
450 }
451}
452
453void OutputFormatter::AddField(const std::string& key, uint64_t value) {
454 if (IsJson()) {
455 if (!first_field_) {
456 buffer_ += ",\n";
457 }
458 first_field_ = false;
459 AddIndent();
460 buffer_ += absl::StrFormat("\"%s\": %llu", EscapeJson(key), value);
461 } else {
462 buffer_ += absl::StrFormat(" %-20s : %llu\n", key, value);
463 }
464}
465
466void OutputFormatter::AddField(const std::string& key, bool value) {
467 if (IsJson()) {
468 if (!first_field_) {
469 buffer_ += ",\n";
470 }
471 first_field_ = false;
472 AddIndent();
473 buffer_ += absl::StrFormat("\"%s\": %s", EscapeJson(key),
474 value ? "true" : "false");
475 } else {
476 buffer_ += absl::StrFormat(" %-20s : %s\n", key, value ? "yes" : "no");
477 }
478}
479
480void OutputFormatter::AddHexField(const std::string& key, uint64_t value,
481 int width) {
482 if (IsJson()) {
483 if (!first_field_) {
484 buffer_ += ",\n";
485 }
486 first_field_ = false;
487 AddIndent();
488 buffer_ +=
489 absl::StrFormat("\"%s\": \"0x%0*X\"", EscapeJson(key), width, value);
490 } else {
491 buffer_ += absl::StrFormat(" %-20s : 0x%0*X\n", key, width, value);
492 }
493}
494
495void OutputFormatter::BeginArray(const std::string& key) {
496 in_array_ = true;
498
499 if (IsJson()) {
500 if (!first_field_) {
501 buffer_ += ",\n";
502 }
503 first_field_ = false;
504 AddIndent();
505 buffer_ += absl::StrFormat("\"%s\": [\n", EscapeJson(key));
507 first_field_ = true; // Reset for array elements
508 } else {
509 buffer_ += absl::StrFormat(" %s:\n", key);
510 }
511}
512
514 in_array_ = false;
515
516 if (IsJson()) {
518 if (array_item_count_ == 0) {
519 if (!buffer_.empty() && buffer_.back() == '\n') {
520 buffer_.pop_back();
521 }
522 buffer_ += "]";
523 } else {
524 buffer_ += "\n";
525 AddIndent();
526 buffer_ += "]";
527 }
529 false; // Array itself was a field, so next field needs a comma
530 }
531}
532
533void OutputFormatter::AddArrayItem(const std::string& item) {
534 if (IsJson()) {
535 if (array_item_count_ > 0) {
536 buffer_ += ",\n";
537 }
538 AddIndent();
539 buffer_ += absl::StrFormat("\"%s\"", EscapeJson(item));
540 } else {
541 buffer_ += absl::StrFormat(" - %s\n", item);
542 }
544}
545
546std::string OutputFormatter::GetOutput() const {
547 return buffer_;
548}
549
551 std::cout << buffer_;
552 if (IsJson()) {
553 std::cout << "\n";
554 }
555}
556
558 for (int i = 0; i < indent_level_; ++i) {
559 buffer_ += " ";
560 }
561}
562
563std::string OutputFormatter::EscapeJson(const std::string& str) const {
564 std::string result;
565 result.reserve(str.size() + 10);
566
567 for (char c : str) {
568 switch (c) {
569 case '"':
570 result += "\\\"";
571 break;
572 case '\\':
573 result += "\\\\";
574 break;
575 case '\b':
576 result += "\\b";
577 break;
578 case '\f':
579 result += "\\f";
580 break;
581 case '\n':
582 result += "\\n";
583 break;
584 case '\r':
585 result += "\\r";
586 break;
587 case '\t':
588 result += "\\t";
589 break;
590 default:
591 if (c < 0x20) {
592 result += absl::StrFormat("\\u%04x", static_cast<int>(c));
593 } else {
594 result += c;
595 }
596 }
597 }
598
599 return result;
600}
601
602} // namespace resources
603} // namespace cli
604} // 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
project::ResourceLabelManager * resource_label()
Definition rom.h:150
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:155
bool is_loaded() const
Definition rom.h:132
std::optional< std::string > FindArgValue(const std::string &name) const
std::vector< std::string > args_
ArgumentParser(const std::vector< std::string > &args)
std::vector< std::string > GetPositional() const
Get all remaining positional arguments.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
bool HasFlag(const std::string &name) const
Check if a flag is present.
absl::Status RequireArgs(const std::vector< std::string > &required) const
Validate that required arguments are present.
absl::StatusOr< int > GetHex(const std::string &name) const
Parse a hex integer argument.
absl::StatusOr< int > GetInt(const std::string &name) const
Parse an integer argument (supports hex with 0x prefix)
std::optional< project::YazeProject > loaded_project_
absl::StatusOr< Rom * > GetRom()
Get the ROM instance (loads if not already loaded)
absl::Status EnsureLabelsLoaded(Rom *rom)
Ensure resource labels are loaded.
emu::debug::SymbolProvider * GetSymbolProvider()
Get the SymbolProvider instance.
std::optional< core::FeatureFlags::Flags > previous_feature_flags_
std::optional< zelda3::CustomObjectManager::State > previous_custom_object_state_
emu::debug::SymbolProvider symbol_provider_
absl::Status Initialize()
Initialize the context and load ROM if needed.
void BeginArray(const std::string &key)
Begin an array.
std::string GetOutput() const
Get the formatted output.
static absl::StatusOr< OutputFormatter > FromString(const std::string &format)
Create formatter from string ("json" or "text")
void AddArrayItem(const std::string &item)
Add an item to current array.
void BeginObject(const std::string &title="")
Start a JSON object or text section.
void EndObject()
End a JSON object or text section.
std::string EscapeJson(const std::string &str) const
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
bool IsJson() const
Check if using JSON format.
bool IsText() const
Check if using text format.
void AddHexField(const std::string &key, uint64_t value, int width=2)
Add a hex-formatted field.
void Print() const
Print the formatted output to stdout.
static Flags & get()
Definition features.h:118
Provider for symbol (label) resolution in disassembly.
bool HasSymbols() const
Check if any symbols are loaded.
absl::Status LoadSymbolFile(const std::string &path, SymbolFormat format=SymbolFormat::kAuto)
Load symbols from a .sym file (various formats)
void RestoreState(const State &state)
void SetObjectFileMap(const std::unordered_map< int, std::vector< std::string > > &map)
static CustomObjectManager & Get()
void Initialize(const std::string &custom_objects_folder)
static DrawRoutineRegistry & Get()
ABSL_DECLARE_FLAG(std::string, rom)
bool ParseHexString(absl::string_view str, int *out)
Definition hex_util.h:17
absl::Status InitializeMockRom(Rom &rom)
Initialize a mock ROM for testing without requiring an actual ROM file.
Definition mock_rom.cc:16
Configuration for command context.
std::optional< std::string > project_context_path
std::optional< std::string > symbols_path
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:365
Modern project structure with comprehensive settings consolidation.
Definition project.h:120
std::unordered_map< int, std::vector< std::string > > custom_object_files
Definition project.h:145
std::string custom_objects_folder
Definition project.h:140
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:153
absl::Status InitializeEmbeddedLabels(const std::unordered_map< std::string, std::unordered_map< std::string, std::string > > &labels)
Definition project.cc:1981
absl::Status Open(const std::string &project_path)
Definition project.cc:295
core::FeatureFlags::Flags feature_flags
Definition project.h:148
static std::unordered_map< std::string, std::unordered_map< std::string, std::string > > ToResourceLabels()
Convert all labels to a structured map for project embedding.