yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
rom_commands.cc
Go to the documentation of this file.
2
3#include <cctype>
4#include <filesystem>
5#include <fstream>
6
7#include "absl/strings/str_format.h"
8#include "absl/strings/str_split.h"
10#include "cli/util/hex_util.h"
11#include "rom/rom.h"
12#include "util/macro.h"
14
15namespace yaze {
16namespace cli {
17namespace handlers {
18
20
22 Rom* rom, const resources::ArgumentParser& parser,
23 resources::OutputFormatter& formatter) {
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()) {
28 data_format = *fmt;
29 } else if (auto fmt = parser.GetString("format"); fmt.has_value()) {
30 if (*fmt == "hex" || *fmt == "ascii" || *fmt == "both" ||
31 *fmt == "binary") {
32 data_format = *fmt;
33 }
34 }
35
36 uint32_t address = 0;
37 if (!ParseHexString(address_str, &address)) {
38 return absl::InvalidArgumentError("Invalid hex address format");
39 }
40 if (length <= 0) {
41 return absl::InvalidArgumentError("Length must be greater than 0");
42 }
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()));
46 }
47
48 const uint8_t* data = rom->data() + address;
49
50 formatter.AddHexField("address", address, 6);
51 formatter.AddField("length", length);
52
53 bool include_hex = (data_format == "hex" || data_format == "both");
54 bool include_ascii = (data_format == "ascii" || data_format == "both");
55
56 if (include_hex) {
57 std::string hex_data;
58 for (int i = 0; i < length; ++i) {
59 absl::StrAppendFormat(&hex_data, "%02X", data[i]);
60 if (i < length - 1)
61 hex_data += " ";
62 }
63 formatter.AddField("data", hex_data);
64 }
65
66 if (include_ascii) {
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 : '.');
72 }
73 formatter.AddField("ascii", ascii_data);
74 }
75
76 formatter.AddField("format", data_format);
77 return absl::OkStatus();
78}
79
81 Rom* rom, const resources::ArgumentParser& parser,
82 resources::OutputFormatter& formatter) {
83 auto address_str = parser.GetString("address").value();
84 auto data_str = parser.GetString("data").value();
85
86 uint32_t address = 0;
87 if (!ParseHexString(address_str, &address)) {
88 return absl::InvalidArgumentError("Invalid hex address format");
89 }
90
91 std::vector<std::string> byte_strs = absl::StrSplit(data_str, ' ');
92 std::vector<uint8_t> bytes;
93 bytes.reserve(byte_strs.size());
94
95 for (const auto& byte_str : byte_strs) {
96 if (byte_str.empty())
97 continue;
98 int value = 0;
99 if (!ParseHexString(byte_str, &value)) {
100 return absl::InvalidArgumentError(
101 absl::StrFormat("Invalid byte '%s'", byte_str));
102 }
103 if (value < 0 || value > 0xFF) {
104 return absl::InvalidArgumentError(
105 absl::StrFormat("Byte out of range: %s", byte_str));
106 }
107 bytes.push_back(static_cast<uint8_t>(value));
108 }
109
110 return ApplyRawRomWrite(rom, address, bytes, parser, formatter);
111}
112
113namespace {
114
115std::string HexBytes(const std::vector<uint8_t>& bytes) {
116 std::string out;
117 for (size_t i = 0; i < bytes.size(); ++i) {
118 if (i > 0) {
119 out += ' ';
120 }
121 absl::StrAppendFormat(&out, "%02X", bytes[i]);
122 }
123 return out;
124}
125
126} // namespace
127
128absl::Status ApplyRawRomWrite(Rom* rom, uint32_t address,
129 const std::vector<uint8_t>& bytes,
130 const resources::ArgumentParser& parser,
131 resources::OutputFormatter& formatter) {
132 if (rom == nullptr || !rom->is_loaded()) {
133 return absl::FailedPreconditionError("ROM not loaded");
134 }
135 if (bytes.empty()) {
136 return absl::InvalidArgumentError("No data bytes provided");
137 }
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()));
142 }
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") &&
146 IsOracleProjectRomPath(std::filesystem::path(rom->filename()))) {
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.",
150 rom->filename()));
151 }
152
153 std::vector<uint8_t> before(bytes.size());
154 for (size_t i = 0; i < bytes.size(); ++i) {
155 ASSIGN_OR_RETURN(before[i], rom->ReadByte(address + i));
156 }
157 formatter.AddHexField("address", address, 6);
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");
162 if (!do_write) {
163 formatter.AddField("write_status", "dry-run");
164 return absl::OkStatus();
165 }
166
168 for (size_t i = 0; i < bytes.size(); ++i) {
169 RETURN_IF_ERROR(rom->WriteByte(address + i, bytes[i]));
170 }
171 if (mock_rom) {
172 formatter.AddField("save_status", "mock-rom-skipped");
173 return absl::OkStatus();
174 }
175 Rom::SaveSettings save_settings;
176 save_settings.require_backup = true;
177 RETURN_IF_ERROR(rom->SaveToFile(save_settings));
178 formatter.AddField("save_status", "saved");
179
180 Rom reopened;
181 RETURN_IF_ERROR(reopened.LoadFromFile(rom->filename()));
182 if (reopened.vector() != rom->vector()) {
183 return absl::DataLossError(
184 "Saved ROM file differs from the written image; restore the backup");
185 }
186 formatter.AddField("verified_reload", true);
187 return absl::OkStatus();
188}
189
191 Rom* rom, const resources::ArgumentParser& parser,
192 resources::OutputFormatter& formatter) {
193 if (!rom || !rom->is_loaded()) {
194 return absl::FailedPreconditionError("ROM must be loaded");
195 }
196
197 formatter.AddField("title", rom->title());
198 formatter.AddField("size", absl::StrFormat("0x%X", rom->size()));
199 formatter.AddField("size_bytes", static_cast<int>(rom->size()));
200 // size/size_bytes exclude a stripped copier header; saves write it back.
201 const size_t header_size = rom->copier_header().size();
202 formatter.AddField("copier_header", rom->has_copier_header());
203 formatter.AddField("copier_header_size", static_cast<int>(header_size));
204 formatter.AddField("copier_header_detection",
206 formatter.AddField("file_size",
207 absl::StrFormat("0x%X", rom->size() + header_size));
208
209 const auto profile = zelda3::DetectOverworldRomProfile(*rom);
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";
224 }
225 formatter.AddField("overworld_format", format_name);
226 formatter.AddField("zscream_layout",
227 zelda3::zscream::LayoutName(profile.zscream_layout));
228 formatter.AddField("zscream_raw_marker",
229 static_cast<int>(profile.asm_version));
230 formatter.AddField("tail_expansion_layout_compatible",
232 formatter.AddField(
233 "overworld_format_valid",
234 layout.presence != yaze::rom::LyraLayoutPresence::kInvalid);
235 formatter.AddField("overworld_format_revision", layout.revision);
236 const int area_capacity =
238 ? profile.editable_map_count
239 : layout.capacity;
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);
249 }
250
251 return absl::OkStatus();
252}
253
255 Rom* rom, const resources::ArgumentParser& parser,
256 resources::OutputFormatter& formatter) {
257 if (!rom || !rom->is_loaded()) {
258 return absl::FailedPreconditionError("ROM must be loaded");
259 }
260
261 bool all_ok = true;
262 std::vector<std::string> validation_results;
263
264 // Basic ROM validation - check if ROM is loaded and has reasonable size
265 if (rom->is_loaded() && rom->size() > 0) {
266 validation_results.push_back("checksum: PASSED");
267 } else {
268 validation_results.push_back("checksum: FAILED");
269 all_ok = false;
270 }
271
272 // Header validation
273 if (rom->title() == "THE LEGEND OF ZELDA") {
274 validation_results.push_back("header: PASSED");
275 } else {
276 validation_results.push_back(
277 "header: FAILED (Invalid title: " + rom->title() + ")");
278 all_ok = false;
279 }
280
281 const auto layout = rom->InspectLyraLayout();
282 if (layout.presence != yaze::rom::LyraLayoutPresence::kAbsent) {
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 (" +
288 layout.error + ")");
289 }
290
291 formatter.AddField("validation_passed", all_ok);
292 std::string results_str;
293 for (const auto& result : validation_results) {
294 if (!results_str.empty())
295 results_str += "; ";
296 results_str += result;
297 }
298 formatter.AddField("results", results_str);
299
300 return layout.presence == yaze::rom::LyraLayoutPresence::kInvalid
301 ? absl::FailedPreconditionError(
302 "Invalid Lyra overworld ROM layout: " + layout.error)
303 : absl::OkStatus();
304}
305
307 Rom* rom, const resources::ArgumentParser& parser,
308 resources::OutputFormatter& formatter) {
309 auto rom_a_opt = parser.GetString("rom_a");
310 auto rom_b_opt = parser.GetString("rom_b");
311
312 if (!rom_a_opt.has_value()) {
313 return absl::InvalidArgumentError("Missing required argument: rom_a");
314 }
315 if (!rom_b_opt.has_value()) {
316 return absl::InvalidArgumentError("Missing required argument: rom_b");
317 }
318
319 std::string rom_a_path = rom_a_opt.value();
320 std::string rom_b_path = rom_b_opt.value();
321
322 Rom rom_a;
323 auto status_a = rom_a.LoadFromFile(rom_a_path);
324 if (!status_a.ok()) {
325 return status_a;
326 }
327
328 Rom rom_b;
329 auto status_b = rom_b.LoadFromFile(rom_b_path);
330 if (!status_b.ok()) {
331 return status_b;
332 }
333
334 if (rom_a.size() != rom_b.size()) {
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();
339 }
340
341 int differences = 0;
342 std::vector<std::string> diff_details;
343
344 for (size_t i = 0; i < rom_a.size(); ++i) {
345 if (rom_a.vector()[i] != rom_b.vector()[i]) {
346 differences++;
347 if (differences <= 10) { // Limit output to first 10 differences
348 diff_details.push_back(absl::StrFormat("0x%08X: 0x%02X vs 0x%02X", i,
349 rom_a.vector()[i],
350 rom_b.vector()[i]));
351 }
352 }
353 }
354
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())
361 diff_str += "; ";
362 diff_str += diff;
363 }
364 formatter.AddField("differences", diff_str);
365 }
366
367 return absl::OkStatus();
368}
369
371 Rom* rom, const resources::ArgumentParser& parser,
372 resources::OutputFormatter& formatter) {
373 auto rom_opt = parser.GetString("rom_file");
374 auto golden_opt = parser.GetString("golden_file");
375
376 if (!rom_opt.has_value()) {
377 return absl::InvalidArgumentError("Missing required argument: rom_file");
378 }
379 if (!golden_opt.has_value()) {
380 return absl::InvalidArgumentError("Missing required argument: golden_file");
381 }
382
383 std::string rom_path = rom_opt.value();
384 std::string golden_path = golden_opt.value();
385
386 Rom source_rom;
387 auto status = source_rom.LoadFromFile(rom_path);
388 if (!status.ok()) {
389 return status;
390 }
391
392 std::ofstream file(golden_path, std::ios::binary);
393 if (!file.is_open()) {
394 return absl::NotFoundError("Could not open file for writing: " +
395 golden_path);
396 }
397
398 file.write(reinterpret_cast<const char*>(source_rom.vector().data()),
399 source_rom.size());
400
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()));
405
406 return absl::OkStatus();
407}
408
410 Rom* rom, const resources::ArgumentParser& parser,
411 resources::OutputFormatter& formatter) {
412 auto address_str = parser.GetString("address").value();
413 auto max_offset = parser.GetInt("max-offset").value_or(0x100);
414
415 uint32_t address = 0;
416 if (!ParseHexString(address_str, &address)) {
417 return absl::InvalidArgumentError("Invalid hex address format");
418 }
419
420 if (symbol_provider_ == nullptr || !symbol_provider_->HasSymbols()) {
421 formatter.AddField("status", "no_symbols_loaded");
422 formatter.AddHexField("address", address, 6);
423 return absl::OkStatus();
424 }
425
426 auto symbols = symbol_provider_->GetSymbolsAtAddress(address);
427 if (!symbols.empty()) {
428 formatter.AddField("status", "success");
429 formatter.AddField("match_type", "exact");
430 if (symbols.size() == 1) {
431 formatter.AddField("name", symbols[0].name);
432 if (!symbols[0].file.empty()) {
433 formatter.AddField("file", symbols[0].file);
434 formatter.AddField("line", symbols[0].line);
435 }
436 } else {
437 formatter.BeginArray("names");
438 for (const auto& sym : symbols) {
439 formatter.AddArrayItem(sym.name);
440 }
441 formatter.EndArray();
442 }
443 formatter.AddHexField("address", address, 6);
444 } else {
445 auto nearest = symbol_provider_->GetNearestSymbol(address);
446 if (nearest) {
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);
453 formatter.AddHexField("offset", offset);
454 formatter.AddField("formatted",
455 absl::StrFormat("%s+$%X", nearest->name, offset));
456 } else {
457 formatter.AddField("status", "not_found");
458 formatter.AddField("message", "Nearest symbol too far away");
459 }
460 } else {
461 formatter.AddField("status", "not_found");
462 }
463 }
464
465 formatter.AddHexField("requested_address", address, 6);
466 return absl::OkStatus();
467}
468
470 Rom* rom, const resources::ArgumentParser& parser,
471 resources::OutputFormatter& formatter) {
472 auto name = parser.GetString("name").value();
473
474 if (symbol_provider_ == nullptr || !symbol_provider_->HasSymbols()) {
475 return absl::FailedPreconditionError(
476 "No symbols loaded. Provide a ROM with .mlb/.sym file.");
477 }
478
479 auto symbol = symbol_provider_->FindSymbol(name);
480 if (symbol) {
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);
487 }
488 } else {
489 // Try wildcard search
491 if (!matches.empty()) {
492 formatter.AddField("status", "success");
493 formatter.AddField("match_type", "partial");
494 formatter.BeginArray("matches");
495 for (const auto& m : matches) {
496 formatter.BeginObject();
497 formatter.AddField("name", m.name);
498 formatter.AddHexField("address", m.address, 6);
499 formatter.EndObject();
500 }
501 formatter.EndArray();
502 } else {
503 formatter.AddField("status", "not_found");
504 }
505 }
506
507 return absl::OkStatus();
508}
509
510} // namespace handlers
511} // namespace cli
512} // 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:52
CopierHeaderReason copier_header_reason() const
Definition rom.h:227
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:632
absl::Status CheckLayoutWritable() const
Definition rom.cc:432
auto filename() const
Definition rom.h:219
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:1104
rom::LyraLayoutInspection InspectLyraLayout() const
Definition rom.cc:421
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:1036
const auto & vector() const
Definition rom.h:217
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:893
const std::vector< uint8_t > & copier_header() const
Definition rom.h:226
auto data() const
Definition rom.h:213
auto size() const
Definition rom.h:212
bool has_copier_header() const
Definition rom.h:225
bool is_loaded() const
Definition rom.h:199
auto title() const
Definition rom.h:211
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_
Utility for consistent output formatting across commands.
void BeginArray(const std::string &key)
Begin an array.
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.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
void AddHexField(const std::string &key, uint64_t value, int width=2)
Add a hex-formatted field.
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)
Definition macro.h:62
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)
Definition hex.cc:133
bool CanUseTailExpansion(const Rom &rom)
const char * LayoutName(LayoutDialect dialect)
OverworldRomProfile DetectOverworldRomProfile(const Rom &rom)
Definition overworld.h:325
const char * CopierHeaderReasonName(CopierHeaderReason reason)
Definition rom.cc:405
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22