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 "util/macro.h"
10
11namespace yaze {
12namespace cli {
13namespace handlers {
14
16
18 Rom* rom, const resources::ArgumentParser& parser,
19 resources::OutputFormatter& formatter) {
20 auto address_str = parser.GetString("address").value();
21 auto length = parser.GetInt("length").value_or(16);
22 std::string data_format = "both";
23 if (auto fmt = parser.GetString("data-format"); fmt.has_value()) {
24 data_format = *fmt;
25 } else if (auto fmt = parser.GetString("format"); fmt.has_value()) {
26 if (*fmt == "hex" || *fmt == "ascii" || *fmt == "both" ||
27 *fmt == "binary") {
28 data_format = *fmt;
29 }
30 }
31
32 uint32_t address = 0;
33 if (!ParseHexString(address_str, &address)) {
34 return absl::InvalidArgumentError("Invalid hex address format");
35 }
36 if (length <= 0) {
37 return absl::InvalidArgumentError("Length must be greater than 0");
38 }
39 if (address + static_cast<uint32_t>(length) > rom->size()) {
40 return absl::OutOfRangeError(absl::StrFormat(
41 "Read beyond ROM: 0x%X+%d > %zu", address, length, rom->size()));
42 }
43
44 const uint8_t* data = rom->data() + address;
45
46 formatter.AddHexField("address", address, 6);
47 formatter.AddField("length", length);
48
49 bool include_hex = (data_format == "hex" || data_format == "both");
50 bool include_ascii = (data_format == "ascii" || data_format == "both");
51
52 if (include_hex) {
53 std::string hex_data;
54 for (int i = 0; i < length; ++i) {
55 absl::StrAppendFormat(&hex_data, "%02X", data[i]);
56 if (i < length - 1)
57 hex_data += " ";
58 }
59 formatter.AddField("data", hex_data);
60 }
61
62 if (include_ascii) {
63 std::string ascii_data;
64 ascii_data.reserve(length);
65 for (int i = 0; i < length; ++i) {
66 char c = static_cast<char>(data[i]);
67 ascii_data += (std::isprint(static_cast<unsigned char>(c)) ? c : '.');
68 }
69 formatter.AddField("ascii", ascii_data);
70 }
71
72 formatter.AddField("format", data_format);
73 return absl::OkStatus();
74}
75
77 Rom* rom, const resources::ArgumentParser& parser,
78 resources::OutputFormatter& formatter) {
79 auto address_str = parser.GetString("address").value();
80 auto data_str = parser.GetString("data").value();
81
82 uint32_t address = 0;
83 if (!ParseHexString(address_str, &address)) {
84 return absl::InvalidArgumentError("Invalid hex address format");
85 }
86
87 std::vector<std::string> byte_strs = absl::StrSplit(data_str, ' ');
88 std::vector<uint8_t> bytes;
89 bytes.reserve(byte_strs.size());
90
91 for (const auto& byte_str : byte_strs) {
92 if (byte_str.empty())
93 continue;
94 int value = 0;
95 if (!ParseHexString(byte_str, &value)) {
96 return absl::InvalidArgumentError(
97 absl::StrFormat("Invalid byte '%s'", byte_str));
98 }
99 if (value < 0 || value > 0xFF) {
100 return absl::InvalidArgumentError(
101 absl::StrFormat("Byte out of range: %s", byte_str));
102 }
103 bytes.push_back(static_cast<uint8_t>(value));
104 }
105
106 if (bytes.empty()) {
107 return absl::InvalidArgumentError("No data bytes provided");
108 }
109
110 if (address + bytes.size() > rom->size()) {
111 return absl::OutOfRangeError(
112 absl::StrFormat("Write beyond ROM: 0x%X+%zu > %zu", address,
113 bytes.size(), rom->size()));
114 }
115
116 for (size_t i = 0; i < bytes.size(); ++i) {
117 rom->WriteByte(address + i, bytes[i]);
118 }
119
120 std::string hex_data;
121 for (size_t i = 0; i < bytes.size(); ++i) {
122 absl::StrAppendFormat(&hex_data, "%02X", bytes[i]);
123 if (i < bytes.size() - 1)
124 hex_data += " ";
125 }
126
127 formatter.AddHexField("address", address, 6);
128 formatter.AddField("bytes_written", static_cast<int>(bytes.size()));
129 formatter.AddField("data", hex_data);
130 return absl::OkStatus();
131}
132
134 Rom* rom, const resources::ArgumentParser& parser,
135 resources::OutputFormatter& formatter) {
136 if (!rom || !rom->is_loaded()) {
137 return absl::FailedPreconditionError("ROM must be loaded");
138 }
139
140 formatter.AddField("title", rom->title());
141 formatter.AddField("size", absl::StrFormat("0x%X", rom->size()));
142 formatter.AddField("size_bytes", static_cast<int>(rom->size()));
143
144 return absl::OkStatus();
145}
146
148 Rom* rom, const resources::ArgumentParser& parser,
149 resources::OutputFormatter& formatter) {
150 if (!rom || !rom->is_loaded()) {
151 return absl::FailedPreconditionError("ROM must be loaded");
152 }
153
154 bool all_ok = true;
155 std::vector<std::string> validation_results;
156
157 // Basic ROM validation - check if ROM is loaded and has reasonable size
158 if (rom->is_loaded() && rom->size() > 0) {
159 validation_results.push_back("checksum: PASSED");
160 } else {
161 validation_results.push_back("checksum: FAILED");
162 all_ok = false;
163 }
164
165 // Header validation
166 if (rom->title() == "THE LEGEND OF ZELDA") {
167 validation_results.push_back("header: PASSED");
168 } else {
169 validation_results.push_back(
170 "header: FAILED (Invalid title: " + rom->title() + ")");
171 all_ok = false;
172 }
173
174 formatter.AddField("validation_passed", all_ok);
175 std::string results_str;
176 for (const auto& result : validation_results) {
177 if (!results_str.empty())
178 results_str += "; ";
179 results_str += result;
180 }
181 formatter.AddField("results", results_str);
182
183 return absl::OkStatus();
184}
185
187 Rom* rom, const resources::ArgumentParser& parser,
188 resources::OutputFormatter& formatter) {
189 auto rom_a_opt = parser.GetString("rom_a");
190 auto rom_b_opt = parser.GetString("rom_b");
191
192 if (!rom_a_opt.has_value()) {
193 return absl::InvalidArgumentError("Missing required argument: rom_a");
194 }
195 if (!rom_b_opt.has_value()) {
196 return absl::InvalidArgumentError("Missing required argument: rom_b");
197 }
198
199 std::string rom_a_path = rom_a_opt.value();
200 std::string rom_b_path = rom_b_opt.value();
201
202 Rom rom_a;
203 auto status_a = rom_a.LoadFromFile(rom_a_path);
204 if (!status_a.ok()) {
205 return status_a;
206 }
207
208 Rom rom_b;
209 auto status_b = rom_b.LoadFromFile(rom_b_path);
210 if (!status_b.ok()) {
211 return status_b;
212 }
213
214 if (rom_a.size() != rom_b.size()) {
215 formatter.AddField("size_match", false);
216 formatter.AddField("size_a", static_cast<int>(rom_a.size()));
217 formatter.AddField("size_b", static_cast<int>(rom_b.size()));
218 return absl::OkStatus();
219 }
220
221 int differences = 0;
222 std::vector<std::string> diff_details;
223
224 for (size_t i = 0; i < rom_a.size(); ++i) {
225 if (rom_a.vector()[i] != rom_b.vector()[i]) {
226 differences++;
227 if (differences <= 10) { // Limit output to first 10 differences
228 diff_details.push_back(absl::StrFormat("0x%08X: 0x%02X vs 0x%02X", i,
229 rom_a.vector()[i],
230 rom_b.vector()[i]));
231 }
232 }
233 }
234
235 formatter.AddField("identical", differences == 0);
236 formatter.AddField("differences_count", differences);
237 if (!diff_details.empty()) {
238 std::string diff_str;
239 for (const auto& diff : diff_details) {
240 if (!diff_str.empty())
241 diff_str += "; ";
242 diff_str += diff;
243 }
244 formatter.AddField("differences", diff_str);
245 }
246
247 return absl::OkStatus();
248}
249
251 Rom* rom, const resources::ArgumentParser& parser,
252 resources::OutputFormatter& formatter) {
253 auto rom_opt = parser.GetString("rom_file");
254 auto golden_opt = parser.GetString("golden_file");
255
256 if (!rom_opt.has_value()) {
257 return absl::InvalidArgumentError("Missing required argument: rom_file");
258 }
259 if (!golden_opt.has_value()) {
260 return absl::InvalidArgumentError("Missing required argument: golden_file");
261 }
262
263 std::string rom_path = rom_opt.value();
264 std::string golden_path = golden_opt.value();
265
266 Rom source_rom;
267 auto status = source_rom.LoadFromFile(rom_path);
268 if (!status.ok()) {
269 return status;
270 }
271
272 std::ofstream file(golden_path, std::ios::binary);
273 if (!file.is_open()) {
274 return absl::NotFoundError("Could not open file for writing: " +
275 golden_path);
276 }
277
278 file.write(reinterpret_cast<const char*>(source_rom.vector().data()),
279 source_rom.size());
280
281 formatter.AddField("status", "success");
282 formatter.AddField("golden_file", golden_path);
283 formatter.AddField("source_file", rom_path);
284 formatter.AddField("size", static_cast<int>(source_rom.size()));
285
286 return absl::OkStatus();
287}
288
290 Rom* rom, const resources::ArgumentParser& parser,
291 resources::OutputFormatter& formatter) {
292 auto address_str = parser.GetString("address").value();
293 auto max_offset = parser.GetInt("max-offset").value_or(0x100);
294
295 uint32_t address = 0;
296 if (!ParseHexString(address_str, &address)) {
297 return absl::InvalidArgumentError("Invalid hex address format");
298 }
299
300 if (symbol_provider_ == nullptr || !symbol_provider_->HasSymbols()) {
301 formatter.AddField("status", "no_symbols_loaded");
302 formatter.AddHexField("address", address, 6);
303 return absl::OkStatus();
304 }
305
306 auto symbols = symbol_provider_->GetSymbolsAtAddress(address);
307 if (!symbols.empty()) {
308 formatter.AddField("status", "success");
309 formatter.AddField("match_type", "exact");
310 if (symbols.size() == 1) {
311 formatter.AddField("name", symbols[0].name);
312 if (!symbols[0].file.empty()) {
313 formatter.AddField("file", symbols[0].file);
314 formatter.AddField("line", symbols[0].line);
315 }
316 } else {
317 formatter.BeginArray("names");
318 for (const auto& sym : symbols) {
319 formatter.AddArrayItem(sym.name);
320 }
321 formatter.EndArray();
322 }
323 formatter.AddHexField("address", address, 6);
324 } else {
325 auto nearest = symbol_provider_->GetNearestSymbol(address);
326 if (nearest) {
327 uint32_t offset = address - nearest->address;
328 if (offset <= static_cast<uint32_t>(max_offset)) {
329 formatter.AddField("status", "success");
330 formatter.AddField("match_type", "nearest");
331 formatter.AddField("name", nearest->name);
332 formatter.AddHexField("symbol_address", nearest->address, 6);
333 formatter.AddHexField("offset", offset);
334 formatter.AddField("formatted", absl::StrFormat("%s+$%X", nearest->name, offset));
335 } else {
336 formatter.AddField("status", "not_found");
337 formatter.AddField("message", "Nearest symbol too far away");
338 }
339 } else {
340 formatter.AddField("status", "not_found");
341 }
342 }
343
344 formatter.AddHexField("requested_address", address, 6);
345 return absl::OkStatus();
346}
347
349 Rom* rom, const resources::ArgumentParser& parser,
350 resources::OutputFormatter& formatter) {
351 auto name = parser.GetString("name").value();
352
353 if (symbol_provider_ == nullptr || !symbol_provider_->HasSymbols()) {
354 return absl::FailedPreconditionError("No symbols loaded. Provide a ROM with .mlb/.sym file.");
355 }
356
357 auto symbol = symbol_provider_->FindSymbol(name);
358 if (symbol) {
359 formatter.AddField("status", "success");
360 formatter.AddField("name", symbol->name);
361 formatter.AddHexField("address", symbol->address, 6);
362 if (!symbol->file.empty()) {
363 formatter.AddField("file", symbol->file);
364 formatter.AddField("line", symbol->line);
365 }
366 } else {
367 // Try wildcard search
369 if (!matches.empty()) {
370 formatter.AddField("status", "success");
371 formatter.AddField("match_type", "partial");
372 formatter.BeginArray("matches");
373 for (const auto& m : matches) {
374 formatter.BeginObject();
375 formatter.AddField("name", m.name);
376 formatter.AddHexField("address", m.address, 6);
377 formatter.EndObject();
378 }
379 formatter.EndArray();
380 } else {
381 formatter.AddField("status", "not_found");
382 }
383 }
384
385 return absl::OkStatus();
386}
387
388} // namespace handlers
389} // namespace cli
390} // 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
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:155
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:476
const auto & vector() const
Definition rom.h:143
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
bool is_loaded() const
Definition rom.h:132
auto title() const
Definition rom.h:137
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, int *out)
Definition hex_util.h:17