7#include "absl/strings/str_format.h"
8#include "absl/strings/str_split.h"
24 auto address_str = parser.
GetString(
"address").value();
25 auto length = parser.
GetInt(
"length").value_or(16);
26 std::string data_format =
"both";
27 if (
auto fmt = parser.
GetString(
"data-format"); fmt.has_value()) {
29 }
else if (
auto fmt = parser.
GetString(
"format"); fmt.has_value()) {
30 if (*fmt ==
"hex" || *fmt ==
"ascii" || *fmt ==
"both" ||
37 if (!ParseHexString(address_str, &address)) {
38 return absl::InvalidArgumentError(
"Invalid hex address format");
41 return absl::InvalidArgumentError(
"Length must be greater than 0");
43 if (address +
static_cast<uint32_t
>(length) > rom->
size()) {
44 return absl::OutOfRangeError(absl::StrFormat(
45 "Read beyond ROM: 0x%X+%d > %zu", address, length, rom->
size()));
48 const uint8_t* data = rom->
data() + address;
51 formatter.
AddField(
"length", length);
53 bool include_hex = (data_format ==
"hex" || data_format ==
"both");
54 bool include_ascii = (data_format ==
"ascii" || data_format ==
"both");
58 for (
int i = 0; i < length; ++i) {
59 absl::StrAppendFormat(&hex_data,
"%02X", data[i]);
63 formatter.
AddField(
"data", hex_data);
67 std::string ascii_data;
68 ascii_data.reserve(length);
69 for (
int i = 0; i < length; ++i) {
70 char c =
static_cast<char>(data[i]);
71 ascii_data += (std::isprint(
static_cast<unsigned char>(c)) ? c :
'.');
73 formatter.
AddField(
"ascii", ascii_data);
76 formatter.
AddField(
"format", data_format);
77 return absl::OkStatus();
83 auto address_str = parser.
GetString(
"address").value();
84 auto data_str = parser.
GetString(
"data").value();
87 if (!ParseHexString(address_str, &address)) {
88 return absl::InvalidArgumentError(
"Invalid hex address format");
91 std::vector<std::string> byte_strs = absl::StrSplit(data_str,
' ');
92 std::vector<uint8_t> bytes;
93 bytes.reserve(byte_strs.size());
95 for (
const auto& byte_str : byte_strs) {
99 if (!ParseHexString(byte_str, &value)) {
100 return absl::InvalidArgumentError(
101 absl::StrFormat(
"Invalid byte '%s'", byte_str));
103 if (value < 0 || value > 0xFF) {
104 return absl::InvalidArgumentError(
105 absl::StrFormat(
"Byte out of range: %s", byte_str));
107 bytes.push_back(
static_cast<uint8_t
>(value));
115std::string
HexBytes(
const std::vector<uint8_t>& bytes) {
117 for (
size_t i = 0; i < bytes.size(); ++i) {
121 absl::StrAppendFormat(&out,
"%02X", bytes[i]);
129 const std::vector<uint8_t>& bytes,
132 if (rom ==
nullptr || !rom->
is_loaded()) {
133 return absl::FailedPreconditionError(
"ROM not loaded");
136 return absl::InvalidArgumentError(
"No data bytes provided");
138 if (address + bytes.size() > rom->
size()) {
139 return absl::OutOfRangeError(
140 absl::StrFormat(
"Write beyond ROM: 0x%X+%zu > %zu", address,
141 bytes.size(), rom->
size()));
143 const bool do_write = parser.
HasFlag(
"write");
144 const bool mock_rom = parser.
HasFlag(
"mock-rom");
145 if (do_write && !mock_rom && !parser.
HasFlag(
"allow-project-rom") &&
147 return absl::PermissionDeniedError(absl::StrFormat(
148 "Refusing --write to %s: it is inside an Oracle of Secrets checkout. "
149 "Write a copy, or pass --allow-project-rom deliberately.",
153 std::vector<uint8_t> before(bytes.size());
154 for (
size_t i = 0; i < bytes.size(); ++i) {
158 formatter.
AddField(
"byte_count",
static_cast<int>(bytes.size()));
159 formatter.
AddField(
"before", HexBytes(before));
160 formatter.
AddField(
"after", HexBytes(bytes));
161 formatter.
AddField(
"mode", do_write ?
"write" :
"dry-run");
163 formatter.
AddField(
"write_status",
"dry-run");
164 return absl::OkStatus();
168 for (
size_t i = 0; i < bytes.size(); ++i) {
172 formatter.
AddField(
"save_status",
"mock-rom-skipped");
173 return absl::OkStatus();
178 formatter.
AddField(
"save_status",
"saved");
183 return absl::DataLossError(
184 "Saved ROM file differs from the written image; restore the backup");
186 formatter.
AddField(
"verified_reload",
true);
187 return absl::OkStatus();
194 return absl::FailedPreconditionError(
"ROM must be loaded");
198 formatter.
AddField(
"size", absl::StrFormat(
"0x%X", rom->
size()));
199 formatter.
AddField(
"size_bytes",
static_cast<int>(rom->
size()));
203 formatter.
AddField(
"copier_header_size",
static_cast<int>(header_size));
204 formatter.
AddField(
"copier_header_detection",
207 absl::StrFormat(
"0x%X", rom->
size() + header_size));
210 const auto& layout = profile.lyra_layout;
211 const char* format_name =
"legacy";
212 if (profile.layout_kind ==
214 format_name =
"older_tail_ea";
215 }
else if (profile.layout_kind ==
217 format_name =
"lyra_descriptor";
218 }
else if (profile.layout_kind ==
220 format_name =
"zscream_per_sheet";
221 }
else if (profile.layout_kind ==
223 format_name =
"unknown_custom";
225 formatter.
AddField(
"overworld_format", format_name);
226 formatter.
AddField(
"zscream_layout",
228 formatter.
AddField(
"zscream_raw_marker",
229 static_cast<int>(profile.asm_version));
230 formatter.
AddField(
"tail_expansion_layout_compatible",
233 "overworld_format_valid",
235 formatter.
AddField(
"overworld_format_revision", layout.revision);
236 const int area_capacity =
238 ? profile.editable_map_count
240 const int active_areas =
242 ? profile.editable_map_count
243 : layout.active_count;
244 formatter.
AddField(
"overworld_area_capacity", area_capacity);
245 formatter.
AddField(
"overworld_active_areas", active_areas);
246 formatter.
AddField(
"overworld_write_supported", layout.write_supported);
247 if (!layout.error.empty()) {
248 formatter.
AddField(
"overworld_format_error", layout.error);
251 return absl::OkStatus();
258 return absl::FailedPreconditionError(
"ROM must be loaded");
262 std::vector<std::string> validation_results;
266 validation_results.push_back(
"checksum: PASSED");
268 validation_results.push_back(
"checksum: FAILED");
273 if (rom->
title() ==
"THE LEGEND OF ZELDA") {
274 validation_results.push_back(
"header: PASSED");
276 validation_results.push_back(
277 "header: FAILED (Invalid title: " + rom->
title() +
")");
283 const bool layout_valid =
285 all_ok = all_ok && layout_valid;
286 validation_results.push_back(layout_valid ?
"lyra_descriptor: PASSED"
287 :
"lyra_descriptor: FAILED (" +
291 formatter.
AddField(
"validation_passed", all_ok);
292 std::string results_str;
293 for (
const auto& result : validation_results) {
294 if (!results_str.empty())
296 results_str += result;
298 formatter.
AddField(
"results", results_str);
301 ? absl::FailedPreconditionError(
302 "Invalid Lyra overworld ROM layout: " + layout.error)
309 auto rom_a_opt = parser.
GetString(
"rom_a");
310 auto rom_b_opt = parser.
GetString(
"rom_b");
312 if (!rom_a_opt.has_value()) {
313 return absl::InvalidArgumentError(
"Missing required argument: rom_a");
315 if (!rom_b_opt.has_value()) {
316 return absl::InvalidArgumentError(
"Missing required argument: rom_b");
319 std::string rom_a_path = rom_a_opt.value();
320 std::string rom_b_path = rom_b_opt.value();
324 if (!status_a.ok()) {
330 if (!status_b.ok()) {
335 formatter.
AddField(
"size_match",
false);
336 formatter.
AddField(
"size_a",
static_cast<int>(rom_a.
size()));
337 formatter.
AddField(
"size_b",
static_cast<int>(rom_b.
size()));
338 return absl::OkStatus();
342 std::vector<std::string> diff_details;
344 for (
size_t i = 0; i < rom_a.
size(); ++i) {
347 if (differences <= 10) {
348 diff_details.push_back(absl::StrFormat(
"0x%08X: 0x%02X vs 0x%02X", i,
355 formatter.
AddField(
"identical", differences == 0);
356 formatter.
AddField(
"differences_count", differences);
357 if (!diff_details.empty()) {
358 std::string diff_str;
359 for (
const auto& diff : diff_details) {
360 if (!diff_str.empty())
364 formatter.
AddField(
"differences", diff_str);
367 return absl::OkStatus();
373 auto rom_opt = parser.
GetString(
"rom_file");
374 auto golden_opt = parser.
GetString(
"golden_file");
376 if (!rom_opt.has_value()) {
377 return absl::InvalidArgumentError(
"Missing required argument: rom_file");
379 if (!golden_opt.has_value()) {
380 return absl::InvalidArgumentError(
"Missing required argument: golden_file");
383 std::string rom_path = rom_opt.value();
384 std::string golden_path = golden_opt.value();
392 std::ofstream file(golden_path, std::ios::binary);
393 if (!file.is_open()) {
394 return absl::NotFoundError(
"Could not open file for writing: " +
398 file.write(
reinterpret_cast<const char*
>(source_rom.
vector().data()),
401 formatter.
AddField(
"status",
"success");
402 formatter.
AddField(
"golden_file", golden_path);
403 formatter.
AddField(
"source_file", rom_path);
404 formatter.
AddField(
"size",
static_cast<int>(source_rom.
size()));
406 return absl::OkStatus();
412 auto address_str = parser.
GetString(
"address").value();
413 auto max_offset = parser.
GetInt(
"max-offset").value_or(0x100);
415 uint32_t address = 0;
416 if (!ParseHexString(address_str, &address)) {
417 return absl::InvalidArgumentError(
"Invalid hex address format");
421 formatter.
AddField(
"status",
"no_symbols_loaded");
423 return absl::OkStatus();
427 if (!symbols.empty()) {
428 formatter.
AddField(
"status",
"success");
429 formatter.
AddField(
"match_type",
"exact");
430 if (symbols.size() == 1) {
432 if (!symbols[0].file.empty()) {
433 formatter.
AddField(
"file", symbols[0].file);
434 formatter.
AddField(
"line", symbols[0].line);
438 for (
const auto& sym : symbols) {
447 uint32_t offset = address - nearest->address;
448 if (offset <=
static_cast<uint32_t
>(max_offset)) {
449 formatter.
AddField(
"status",
"success");
450 formatter.
AddField(
"match_type",
"nearest");
451 formatter.
AddField(
"name", nearest->name);
452 formatter.
AddHexField(
"symbol_address", nearest->address, 6);
455 absl::StrFormat(
"%s+$%X", nearest->name, offset));
457 formatter.
AddField(
"status",
"not_found");
458 formatter.
AddField(
"message",
"Nearest symbol too far away");
461 formatter.
AddField(
"status",
"not_found");
465 formatter.
AddHexField(
"requested_address", address, 6);
466 return absl::OkStatus();
475 return absl::FailedPreconditionError(
476 "No symbols loaded. Provide a ROM with .mlb/.sym file.");
481 formatter.
AddField(
"status",
"success");
482 formatter.
AddField(
"name", symbol->name);
483 formatter.
AddHexField(
"address", symbol->address, 6);
484 if (!symbol->file.empty()) {
485 formatter.
AddField(
"file", symbol->file);
486 formatter.
AddField(
"line", symbol->line);
491 if (!matches.empty()) {
492 formatter.
AddField(
"status",
"success");
493 formatter.
AddField(
"match_type",
"partial");
495 for (
const auto& m : matches) {
503 formatter.
AddField(
"status",
"not_found");
507 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 CheckLayoutWritable() const
absl::Status WriteByte(int addr, uint8_t value)
rom::LyraLayoutInspection InspectLyraLayout() const
absl::StatusOr< uint8_t > ReadByte(int offset) const
const auto & vector() const
absl::Status SaveToFile(const SaveSettings &settings)
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)
bool HasFlag(const std::string &name) const
Check if a flag is present.
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)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::string HexBytes(const std::vector< uint8_t > &bytes)
absl::Status ApplyRawRomWrite(Rom *rom, uint32_t address, const std::vector< uint8_t > &bytes, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter)
Shared body of rom-write and hex-write.
bool IsOracleProjectRomPath(const std::filesystem::path &rom_path)
bool ParseHexString(absl::string_view str, uint64_t *out)
bool CanUseTailExpansion(const Rom &rom)
const char * LayoutName(LayoutDialect dialect)
OverworldRomProfile DetectOverworldRomProfile(const Rom &rom)
const char * CopierHeaderReasonName(CopierHeaderReason reason)
#define RETURN_IF_ERROR(expr)
LyraLayoutPresence presence