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 <fstream>
5
6#include "absl/strings/str_format.h"
7#include "absl/strings/str_split.h"
8#include "cli/util/hex_util.h"
9#include "rom/rom.h"
10#include "util/macro.h"
12
13namespace yaze {
14namespace cli {
15namespace handlers {
16
18
20 Rom* rom, const resources::ArgumentParser& parser,
21 resources::OutputFormatter& formatter) {
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()) {
26 data_format = *fmt;
27 } else if (auto fmt = parser.GetString("format"); fmt.has_value()) {
28 if (*fmt == "hex" || *fmt == "ascii" || *fmt == "both" ||
29 *fmt == "binary") {
30 data_format = *fmt;
31 }
32 }
33
34 uint32_t address = 0;
35 if (!ParseHexString(address_str, &address)) {
36 return absl::InvalidArgumentError("Invalid hex address format");
37 }
38 if (length <= 0) {
39 return absl::InvalidArgumentError("Length must be greater than 0");
40 }
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()));
44 }
45
46 const uint8_t* data = rom->data() + address;
47
48 formatter.AddHexField("address", address, 6);
49 formatter.AddField("length", length);
50
51 bool include_hex = (data_format == "hex" || data_format == "both");
52 bool include_ascii = (data_format == "ascii" || data_format == "both");
53
54 if (include_hex) {
55 std::string hex_data;
56 for (int i = 0; i < length; ++i) {
57 absl::StrAppendFormat(&hex_data, "%02X", data[i]);
58 if (i < length - 1)
59 hex_data += " ";
60 }
61 formatter.AddField("data", hex_data);
62 }
63
64 if (include_ascii) {
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 : '.');
70 }
71 formatter.AddField("ascii", ascii_data);
72 }
73
74 formatter.AddField("format", data_format);
75 return absl::OkStatus();
76}
77
79 Rom* rom, const resources::ArgumentParser& parser,
80 resources::OutputFormatter& formatter) {
81 auto address_str = parser.GetString("address").value();
82 auto data_str = parser.GetString("data").value();
83
84 uint32_t address = 0;
85 if (!ParseHexString(address_str, &address)) {
86 return absl::InvalidArgumentError("Invalid hex address format");
87 }
88
89 std::vector<std::string> byte_strs = absl::StrSplit(data_str, ' ');
90 std::vector<uint8_t> bytes;
91 bytes.reserve(byte_strs.size());
92
93 for (const auto& byte_str : byte_strs) {
94 if (byte_str.empty())
95 continue;
96 int value = 0;
97 if (!ParseHexString(byte_str, &value)) {
98 return absl::InvalidArgumentError(
99 absl::StrFormat("Invalid byte '%s'", byte_str));
100 }
101 if (value < 0 || value > 0xFF) {
102 return absl::InvalidArgumentError(
103 absl::StrFormat("Byte out of range: %s", byte_str));
104 }
105 bytes.push_back(static_cast<uint8_t>(value));
106 }
107
108 if (bytes.empty()) {
109 return absl::InvalidArgumentError("No data bytes provided");
110 }
111
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()));
116 }
117
118 for (size_t i = 0; i < bytes.size(); ++i) {
119 RETURN_IF_ERROR(rom->WriteByte(address + i, bytes[i]));
120 }
121
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)
126 hex_data += " ";
127 }
128
129 formatter.AddHexField("address", address, 6);
130 formatter.AddField("bytes_written", static_cast<int>(bytes.size()));
131 formatter.AddField("data", hex_data);
132 return absl::OkStatus();
133}
134
136 Rom* rom, const resources::ArgumentParser& parser,
137 resources::OutputFormatter& formatter) {
138 if (!rom || !rom->is_loaded()) {
139 return absl::FailedPreconditionError("ROM must be loaded");
140 }
141
142 formatter.AddField("title", rom->title());
143 formatter.AddField("size", absl::StrFormat("0x%X", rom->size()));
144 formatter.AddField("size_bytes", static_cast<int>(rom->size()));
145 // size/size_bytes exclude a stripped copier header; saves write it back.
146 const size_t header_size = rom->copier_header().size();
147 formatter.AddField("copier_header", rom->has_copier_header());
148 formatter.AddField("copier_header_size", static_cast<int>(header_size));
149 formatter.AddField("copier_header_detection",
151 formatter.AddField("file_size",
152 absl::StrFormat("0x%X", rom->size() + header_size));
153
154 const auto profile = zelda3::DetectOverworldRomProfile(*rom);
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";
163 }
164 formatter.AddField("overworld_format", format_name);
165 formatter.AddField(
166 "overworld_format_valid",
167 layout.presence != yaze::rom::LyraLayoutPresence::kInvalid);
168 formatter.AddField("overworld_format_revision", layout.revision);
169 const int area_capacity =
171 ? profile.editable_map_count
172 : layout.capacity;
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);
182 }
183
184 return absl::OkStatus();
185}
186
188 Rom* rom, const resources::ArgumentParser& parser,
189 resources::OutputFormatter& formatter) {
190 if (!rom || !rom->is_loaded()) {
191 return absl::FailedPreconditionError("ROM must be loaded");
192 }
193
194 bool all_ok = true;
195 std::vector<std::string> validation_results;
196
197 // Basic ROM validation - check if ROM is loaded and has reasonable size
198 if (rom->is_loaded() && rom->size() > 0) {
199 validation_results.push_back("checksum: PASSED");
200 } else {
201 validation_results.push_back("checksum: FAILED");
202 all_ok = false;
203 }
204
205 // Header validation
206 if (rom->title() == "THE LEGEND OF ZELDA") {
207 validation_results.push_back("header: PASSED");
208 } else {
209 validation_results.push_back(
210 "header: FAILED (Invalid title: " + rom->title() + ")");
211 all_ok = false;
212 }
213
214 const auto layout = rom->InspectLyraLayout();
215 if (layout.presence != yaze::rom::LyraLayoutPresence::kAbsent) {
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 (" +
221 layout.error + ")");
222 }
223
224 formatter.AddField("validation_passed", all_ok);
225 std::string results_str;
226 for (const auto& result : validation_results) {
227 if (!results_str.empty())
228 results_str += "; ";
229 results_str += result;
230 }
231 formatter.AddField("results", results_str);
232
233 return layout.presence == yaze::rom::LyraLayoutPresence::kInvalid
234 ? absl::FailedPreconditionError(
235 "Invalid Lyra overworld ROM layout: " + layout.error)
236 : absl::OkStatus();
237}
238
240 Rom* rom, const resources::ArgumentParser& parser,
241 resources::OutputFormatter& formatter) {
242 auto rom_a_opt = parser.GetString("rom_a");
243 auto rom_b_opt = parser.GetString("rom_b");
244
245 if (!rom_a_opt.has_value()) {
246 return absl::InvalidArgumentError("Missing required argument: rom_a");
247 }
248 if (!rom_b_opt.has_value()) {
249 return absl::InvalidArgumentError("Missing required argument: rom_b");
250 }
251
252 std::string rom_a_path = rom_a_opt.value();
253 std::string rom_b_path = rom_b_opt.value();
254
255 Rom rom_a;
256 auto status_a = rom_a.LoadFromFile(rom_a_path);
257 if (!status_a.ok()) {
258 return status_a;
259 }
260
261 Rom rom_b;
262 auto status_b = rom_b.LoadFromFile(rom_b_path);
263 if (!status_b.ok()) {
264 return status_b;
265 }
266
267 if (rom_a.size() != rom_b.size()) {
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();
272 }
273
274 int differences = 0;
275 std::vector<std::string> diff_details;
276
277 for (size_t i = 0; i < rom_a.size(); ++i) {
278 if (rom_a.vector()[i] != rom_b.vector()[i]) {
279 differences++;
280 if (differences <= 10) { // Limit output to first 10 differences
281 diff_details.push_back(absl::StrFormat("0x%08X: 0x%02X vs 0x%02X", i,
282 rom_a.vector()[i],
283 rom_b.vector()[i]));
284 }
285 }
286 }
287
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())
294 diff_str += "; ";
295 diff_str += diff;
296 }
297 formatter.AddField("differences", diff_str);
298 }
299
300 return absl::OkStatus();
301}
302
304 Rom* rom, const resources::ArgumentParser& parser,
305 resources::OutputFormatter& formatter) {
306 auto rom_opt = parser.GetString("rom_file");
307 auto golden_opt = parser.GetString("golden_file");
308
309 if (!rom_opt.has_value()) {
310 return absl::InvalidArgumentError("Missing required argument: rom_file");
311 }
312 if (!golden_opt.has_value()) {
313 return absl::InvalidArgumentError("Missing required argument: golden_file");
314 }
315
316 std::string rom_path = rom_opt.value();
317 std::string golden_path = golden_opt.value();
318
319 Rom source_rom;
320 auto status = source_rom.LoadFromFile(rom_path);
321 if (!status.ok()) {
322 return status;
323 }
324
325 std::ofstream file(golden_path, std::ios::binary);
326 if (!file.is_open()) {
327 return absl::NotFoundError("Could not open file for writing: " +
328 golden_path);
329 }
330
331 file.write(reinterpret_cast<const char*>(source_rom.vector().data()),
332 source_rom.size());
333
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()));
338
339 return absl::OkStatus();
340}
341
343 Rom* rom, const resources::ArgumentParser& parser,
344 resources::OutputFormatter& formatter) {
345 auto address_str = parser.GetString("address").value();
346 auto max_offset = parser.GetInt("max-offset").value_or(0x100);
347
348 uint32_t address = 0;
349 if (!ParseHexString(address_str, &address)) {
350 return absl::InvalidArgumentError("Invalid hex address format");
351 }
352
353 if (symbol_provider_ == nullptr || !symbol_provider_->HasSymbols()) {
354 formatter.AddField("status", "no_symbols_loaded");
355 formatter.AddHexField("address", address, 6);
356 return absl::OkStatus();
357 }
358
359 auto symbols = symbol_provider_->GetSymbolsAtAddress(address);
360 if (!symbols.empty()) {
361 formatter.AddField("status", "success");
362 formatter.AddField("match_type", "exact");
363 if (symbols.size() == 1) {
364 formatter.AddField("name", symbols[0].name);
365 if (!symbols[0].file.empty()) {
366 formatter.AddField("file", symbols[0].file);
367 formatter.AddField("line", symbols[0].line);
368 }
369 } else {
370 formatter.BeginArray("names");
371 for (const auto& sym : symbols) {
372 formatter.AddArrayItem(sym.name);
373 }
374 formatter.EndArray();
375 }
376 formatter.AddHexField("address", address, 6);
377 } else {
378 auto nearest = symbol_provider_->GetNearestSymbol(address);
379 if (nearest) {
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);
386 formatter.AddHexField("offset", offset);
387 formatter.AddField("formatted",
388 absl::StrFormat("%s+$%X", nearest->name, offset));
389 } else {
390 formatter.AddField("status", "not_found");
391 formatter.AddField("message", "Nearest symbol too far away");
392 }
393 } else {
394 formatter.AddField("status", "not_found");
395 }
396 }
397
398 formatter.AddHexField("requested_address", address, 6);
399 return absl::OkStatus();
400}
401
403 Rom* rom, const resources::ArgumentParser& parser,
404 resources::OutputFormatter& formatter) {
405 auto name = parser.GetString("name").value();
406
407 if (symbol_provider_ == nullptr || !symbol_provider_->HasSymbols()) {
408 return absl::FailedPreconditionError(
409 "No symbols loaded. Provide a ROM with .mlb/.sym file.");
410 }
411
412 auto symbol = symbol_provider_->FindSymbol(name);
413 if (symbol) {
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);
420 }
421 } else {
422 // Try wildcard search
424 if (!matches.empty()) {
425 formatter.AddField("status", "success");
426 formatter.AddField("match_type", "partial");
427 formatter.BeginArray("matches");
428 for (const auto& m : matches) {
429 formatter.BeginObject();
430 formatter.AddField("name", m.name);
431 formatter.AddHexField("address", m.address, 6);
432 formatter.EndObject();
433 }
434 formatter.EndArray();
435 } else {
436 formatter.AddField("status", "not_found");
437 }
438 }
439
440 return absl::OkStatus();
441}
442
443} // namespace handlers
444} // namespace cli
445} // 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:49
CopierHeaderReason copier_header_reason() const
Definition rom.h:215
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:491
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:853
rom::LyraLayoutInspection InspectLyraLayout() const
Definition rom.cc:418
const auto & vector() const
Definition rom.h:205
const std::vector< uint8_t > & copier_header() const
Definition rom.h:214
auto data() const
Definition rom.h:201
auto size() const
Definition rom.h:200
bool has_copier_header() const
Definition rom.h:213
bool is_loaded() const
Definition rom.h:187
auto title() const
Definition rom.h:199
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_
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)
bool ParseHexString(absl::string_view str, uint64_t *out)
Definition hex.cc:133
OverworldRomProfile DetectOverworldRomProfile(const Rom &rom)
Definition overworld.h:316
const char * CopierHeaderReasonName(CopierHeaderReason reason)
Definition rom.cc:402
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22