6#include "absl/strings/str_format.h"
7#include "absl/strings/str_split.h"
22 auto address_str = parser.
GetString(
"address").value();
23 auto length = parser.
GetInt(
"length").value_or(16);
24 std::string data_format =
"both";
25 if (
auto fmt = parser.
GetString(
"data-format"); fmt.has_value()) {
27 }
else if (
auto fmt = parser.
GetString(
"format"); fmt.has_value()) {
28 if (*fmt ==
"hex" || *fmt ==
"ascii" || *fmt ==
"both" ||
35 if (!ParseHexString(address_str, &address)) {
36 return absl::InvalidArgumentError(
"Invalid hex address format");
39 return absl::InvalidArgumentError(
"Length must be greater than 0");
41 if (address +
static_cast<uint32_t
>(length) > rom->
size()) {
42 return absl::OutOfRangeError(absl::StrFormat(
43 "Read beyond ROM: 0x%X+%d > %zu", address, length, rom->
size()));
46 const uint8_t* data = rom->
data() + address;
49 formatter.
AddField(
"length", length);
51 bool include_hex = (data_format ==
"hex" || data_format ==
"both");
52 bool include_ascii = (data_format ==
"ascii" || data_format ==
"both");
56 for (
int i = 0; i < length; ++i) {
57 absl::StrAppendFormat(&hex_data,
"%02X", data[i]);
61 formatter.
AddField(
"data", hex_data);
65 std::string ascii_data;
66 ascii_data.reserve(length);
67 for (
int i = 0; i < length; ++i) {
68 char c =
static_cast<char>(data[i]);
69 ascii_data += (std::isprint(
static_cast<unsigned char>(c)) ? c :
'.');
71 formatter.
AddField(
"ascii", ascii_data);
74 formatter.
AddField(
"format", data_format);
75 return absl::OkStatus();
81 auto address_str = parser.
GetString(
"address").value();
82 auto data_str = parser.
GetString(
"data").value();
85 if (!ParseHexString(address_str, &address)) {
86 return absl::InvalidArgumentError(
"Invalid hex address format");
89 std::vector<std::string> byte_strs = absl::StrSplit(data_str,
' ');
90 std::vector<uint8_t> bytes;
91 bytes.reserve(byte_strs.size());
93 for (
const auto& byte_str : byte_strs) {
97 if (!ParseHexString(byte_str, &value)) {
98 return absl::InvalidArgumentError(
99 absl::StrFormat(
"Invalid byte '%s'", byte_str));
101 if (value < 0 || value > 0xFF) {
102 return absl::InvalidArgumentError(
103 absl::StrFormat(
"Byte out of range: %s", byte_str));
105 bytes.push_back(
static_cast<uint8_t
>(value));
109 return absl::InvalidArgumentError(
"No data bytes provided");
112 if (address + bytes.size() > rom->
size()) {
113 return absl::OutOfRangeError(
114 absl::StrFormat(
"Write beyond ROM: 0x%X+%zu > %zu", address,
115 bytes.size(), rom->
size()));
118 for (
size_t i = 0; i < bytes.size(); ++i) {
122 std::string hex_data;
123 for (
size_t i = 0; i < bytes.size(); ++i) {
124 absl::StrAppendFormat(&hex_data,
"%02X", bytes[i]);
125 if (i < bytes.size() - 1)
130 formatter.
AddField(
"bytes_written",
static_cast<int>(bytes.size()));
131 formatter.
AddField(
"data", hex_data);
132 return absl::OkStatus();
139 return absl::FailedPreconditionError(
"ROM must be loaded");
143 formatter.
AddField(
"size", absl::StrFormat(
"0x%X", rom->
size()));
144 formatter.
AddField(
"size_bytes",
static_cast<int>(rom->
size()));
148 formatter.
AddField(
"copier_header_size",
static_cast<int>(header_size));
149 formatter.
AddField(
"copier_header_detection",
152 absl::StrFormat(
"0x%X", rom->
size() + header_size));
155 const auto& layout = profile.lyra_layout;
156 const char* format_name =
"legacy";
157 if (profile.layout_kind ==
159 format_name =
"older_tail_ea";
160 }
else if (profile.layout_kind ==
162 format_name =
"lyra_descriptor";
164 formatter.
AddField(
"overworld_format", format_name);
166 "overworld_format_valid",
168 formatter.
AddField(
"overworld_format_revision", layout.revision);
169 const int area_capacity =
171 ? profile.editable_map_count
173 const int active_areas =
175 ? profile.editable_map_count
176 : layout.active_count;
177 formatter.
AddField(
"overworld_area_capacity", area_capacity);
178 formatter.
AddField(
"overworld_active_areas", active_areas);
179 formatter.
AddField(
"overworld_write_supported", layout.write_supported);
180 if (!layout.error.empty()) {
181 formatter.
AddField(
"overworld_format_error", layout.error);
184 return absl::OkStatus();
191 return absl::FailedPreconditionError(
"ROM must be loaded");
195 std::vector<std::string> validation_results;
199 validation_results.push_back(
"checksum: PASSED");
201 validation_results.push_back(
"checksum: FAILED");
206 if (rom->
title() ==
"THE LEGEND OF ZELDA") {
207 validation_results.push_back(
"header: PASSED");
209 validation_results.push_back(
210 "header: FAILED (Invalid title: " + rom->
title() +
")");
216 const bool layout_valid =
218 all_ok = all_ok && layout_valid;
219 validation_results.push_back(layout_valid ?
"lyra_descriptor: PASSED"
220 :
"lyra_descriptor: FAILED (" +
224 formatter.
AddField(
"validation_passed", all_ok);
225 std::string results_str;
226 for (
const auto& result : validation_results) {
227 if (!results_str.empty())
229 results_str += result;
231 formatter.
AddField(
"results", results_str);
234 ? absl::FailedPreconditionError(
235 "Invalid Lyra overworld ROM layout: " + layout.error)
242 auto rom_a_opt = parser.
GetString(
"rom_a");
243 auto rom_b_opt = parser.
GetString(
"rom_b");
245 if (!rom_a_opt.has_value()) {
246 return absl::InvalidArgumentError(
"Missing required argument: rom_a");
248 if (!rom_b_opt.has_value()) {
249 return absl::InvalidArgumentError(
"Missing required argument: rom_b");
252 std::string rom_a_path = rom_a_opt.value();
253 std::string rom_b_path = rom_b_opt.value();
257 if (!status_a.ok()) {
263 if (!status_b.ok()) {
268 formatter.
AddField(
"size_match",
false);
269 formatter.
AddField(
"size_a",
static_cast<int>(rom_a.
size()));
270 formatter.
AddField(
"size_b",
static_cast<int>(rom_b.
size()));
271 return absl::OkStatus();
275 std::vector<std::string> diff_details;
277 for (
size_t i = 0; i < rom_a.
size(); ++i) {
280 if (differences <= 10) {
281 diff_details.push_back(absl::StrFormat(
"0x%08X: 0x%02X vs 0x%02X", i,
288 formatter.
AddField(
"identical", differences == 0);
289 formatter.
AddField(
"differences_count", differences);
290 if (!diff_details.empty()) {
291 std::string diff_str;
292 for (
const auto& diff : diff_details) {
293 if (!diff_str.empty())
297 formatter.
AddField(
"differences", diff_str);
300 return absl::OkStatus();
306 auto rom_opt = parser.
GetString(
"rom_file");
307 auto golden_opt = parser.
GetString(
"golden_file");
309 if (!rom_opt.has_value()) {
310 return absl::InvalidArgumentError(
"Missing required argument: rom_file");
312 if (!golden_opt.has_value()) {
313 return absl::InvalidArgumentError(
"Missing required argument: golden_file");
316 std::string rom_path = rom_opt.value();
317 std::string golden_path = golden_opt.value();
325 std::ofstream file(golden_path, std::ios::binary);
326 if (!file.is_open()) {
327 return absl::NotFoundError(
"Could not open file for writing: " +
331 file.write(
reinterpret_cast<const char*
>(source_rom.
vector().data()),
334 formatter.
AddField(
"status",
"success");
335 formatter.
AddField(
"golden_file", golden_path);
336 formatter.
AddField(
"source_file", rom_path);
337 formatter.
AddField(
"size",
static_cast<int>(source_rom.
size()));
339 return absl::OkStatus();
345 auto address_str = parser.
GetString(
"address").value();
346 auto max_offset = parser.
GetInt(
"max-offset").value_or(0x100);
348 uint32_t address = 0;
349 if (!ParseHexString(address_str, &address)) {
350 return absl::InvalidArgumentError(
"Invalid hex address format");
354 formatter.
AddField(
"status",
"no_symbols_loaded");
356 return absl::OkStatus();
360 if (!symbols.empty()) {
361 formatter.
AddField(
"status",
"success");
362 formatter.
AddField(
"match_type",
"exact");
363 if (symbols.size() == 1) {
365 if (!symbols[0].file.empty()) {
366 formatter.
AddField(
"file", symbols[0].file);
367 formatter.
AddField(
"line", symbols[0].line);
371 for (
const auto& sym : symbols) {
380 uint32_t offset = address - nearest->address;
381 if (offset <=
static_cast<uint32_t
>(max_offset)) {
382 formatter.
AddField(
"status",
"success");
383 formatter.
AddField(
"match_type",
"nearest");
384 formatter.
AddField(
"name", nearest->name);
385 formatter.
AddHexField(
"symbol_address", nearest->address, 6);
388 absl::StrFormat(
"%s+$%X", nearest->name, offset));
390 formatter.
AddField(
"status",
"not_found");
391 formatter.
AddField(
"message",
"Nearest symbol too far away");
394 formatter.
AddField(
"status",
"not_found");
398 formatter.
AddHexField(
"requested_address", address, 6);
399 return absl::OkStatus();
408 return absl::FailedPreconditionError(
409 "No symbols loaded. Provide a ROM with .mlb/.sym file.");
414 formatter.
AddField(
"status",
"success");
415 formatter.
AddField(
"name", symbol->name);
416 formatter.
AddHexField(
"address", symbol->address, 6);
417 if (!symbol->file.empty()) {
418 formatter.
AddField(
"file", symbol->file);
419 formatter.
AddField(
"line", symbol->line);
424 if (!matches.empty()) {
425 formatter.
AddField(
"status",
"success");
426 formatter.
AddField(
"match_type",
"partial");
428 for (
const auto& m : matches) {
436 formatter.
AddField(
"status",
"not_found");
440 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
CopierHeaderReason copier_header_reason() const
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
absl::Status WriteByte(int addr, uint8_t value)
rom::LyraLayoutInspection InspectLyraLayout() const
const auto & vector() const
const std::vector< uint8_t > & copier_header() const
bool has_copier_header() const
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
absl::StatusOr< int > GetInt(const std::string &name) const
Parse an integer argument (supports hex with 0x prefix)
emu::debug::SymbolProvider * symbol_provider_
bool HasSymbols() const
Check if any symbols are loaded.
std::vector< Symbol > GetSymbolsAtAddress(uint32_t address) const
Get all symbols at an address (there may be multiple)
std::optional< Symbol > GetNearestSymbol(uint32_t address) const
Get nearest symbol at or before an address.
std::optional< Symbol > FindSymbol(const std::string &name) const
Find symbol by name.
std::vector< Symbol > FindSymbolsMatching(const std::string &pattern) const
Find symbols matching a pattern (supports wildcards)
bool ParseHexString(absl::string_view str, uint64_t *out)
OverworldRomProfile DetectOverworldRomProfile(const Rom &rom)
const char * CopierHeaderReasonName(CopierHeaderReason reason)
#define RETURN_IF_ERROR(expr)
LyraLayoutPresence presence