yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
asar_wrapper.cc
Go to the documentation of this file.
1#include "core/asar_wrapper.h"
2
3#include <chrono>
4#include <cstdlib>
5#include <filesystem>
6#include <fstream>
7#include <iterator>
8#include <iostream>
9#include <sstream>
10
11#include "absl/strings/str_format.h"
12#include "absl/strings/str_join.h"
13
14#ifdef YAZE_ENABLE_ASAR
15#include "asar-dll-bindings/c/asar.h"
16#endif
17
18namespace yaze {
19namespace core {
20
21namespace fs = std::filesystem;
22
23AsarWrapper::AsarWrapper() : initialized_(false), library_enabled_(false) {}
24
26 if (initialized_) {
27 Shutdown();
28 }
29}
30
32 if (initialized_) {
33 return absl::OkStatus();
34 }
35
36#ifdef YAZE_ENABLE_ASAR
37 // Verify API version compatibility
38 int api_version = asar_apiversion();
39 if (api_version < 300) { // Require at least API version 3.0
40 return absl::InternalError(absl::StrFormat(
41 "Asar API version %d is too old (required: 300+)", api_version));
42 }
43
44 library_enabled_ = true;
45 initialized_ = true;
46 return absl::OkStatus();
47#else
48 // Library not available; allow initialization so we can fall back to the
49 // bundled/system CLI.
50 library_enabled_ = false;
51 initialized_ = true;
52 return absl::OkStatus();
53#endif
54}
55
57 if (initialized_) {
58 // Note: Static library doesn't have asar_close()
59 initialized_ = false;
60 library_enabled_ = false;
61 }
62}
63
64std::string AsarWrapper::GetVersion() const {
65#ifdef YAZE_ENABLE_ASAR
67 return "Not initialized";
68 }
69
70 int version = asar_version();
71 int major = version / 10000;
72 int minor = (version / 100) % 100;
73 int patch = version % 100;
74
75 return absl::StrFormat("%d.%d.%d", major, minor, patch);
76#endif
77
78#ifdef YAZE_ASAR_STANDALONE_PATH
79 return "Asar CLI (bundled)";
80#else
81 return "Asar CLI (external)";
82#endif
83}
84
86#ifdef YAZE_ENABLE_ASAR
88 return 0;
89 }
90 return asar_apiversion();
91#endif
92 return 0;
93}
94
95absl::StatusOr<AsarPatchResult> AsarWrapper::ApplyPatch(
96 const std::string& patch_path,
97 std::vector<uint8_t>& rom_data,
98 const std::vector<std::string>& include_paths) {
99 if (!initialized_) {
100 return absl::FailedPreconditionError("Asar not initialized");
101 }
102
103 // Reset previous state
104 Reset();
105
106#ifdef YAZE_ENABLE_ASAR
107 if (library_enabled_) {
108 auto lib_result =
109 ApplyPatchWithLibrary(patch_path, rom_data, include_paths);
110 if (lib_result.ok()) {
111 return lib_result;
112 }
113 // Fall through to CLI fallback
114 last_errors_.push_back(lib_result.status().ToString());
115 }
116#endif
117
118 return ApplyPatchWithBinary(patch_path, rom_data, include_paths);
119}
120
121absl::StatusOr<AsarPatchResult> AsarWrapper::ApplyPatchFromString(
122 const std::string& patch_content,
123 std::vector<uint8_t>& rom_data,
124 const std::string& base_path) {
125 if (!initialized_) {
126 return absl::FailedPreconditionError("Asar not initialized");
127 }
128
129 // Reset previous state
130 Reset();
131
132 // Write patch content to temporary file
133 fs::path temp_patch_path =
134 fs::temp_directory_path() / "yaze_asar_temp.asm";
135
136 std::ofstream temp_patch_file(temp_patch_path);
137 if (!temp_patch_file) {
138 return absl::InternalError("Failed to create temporary patch file");
139 }
140
141 temp_patch_file << patch_content;
142 temp_patch_file.close();
143
144 // Apply patch using temporary file
145 auto patch_result = ApplyPatch(temp_patch_path.string(), rom_data, {base_path});
146
147 // Clean up temporary file
148 std::error_code ec;
149 fs::remove(temp_patch_path, ec);
150
151 return patch_result;
152}
153
154absl::StatusOr<std::vector<AsarSymbol>> AsarWrapper::ExtractSymbols(
155 const std::string& asm_path,
156 const std::vector<std::string>& include_paths) {
157#ifdef YAZE_ENABLE_ASAR
159 return absl::FailedPreconditionError("Asar not initialized");
160 }
161
162 // Reset state before extraction
163 Reset();
164
165 // Create a dummy ROM for symbol extraction
166 std::vector<uint8_t> dummy_rom(1024 * 1024, 0); // 1MB dummy ROM
167
168 auto result = ApplyPatch(asm_path, dummy_rom, include_paths);
169 if (!result.ok()) {
170 return result.status();
171 }
172
173 return result->symbols;
174#else
175 (void)asm_path;
176 (void)include_paths;
177 return absl::UnimplementedError(
178 "ASAR library not enabled - build with YAZE_ENABLE_ASAR=1");
179#endif
180}
181
182std::map<std::string, AsarSymbol> AsarWrapper::GetSymbolTable() const {
183 return symbol_table_;
184}
185
186std::optional<AsarSymbol> AsarWrapper::FindSymbol(const std::string& name) const {
187 auto it = symbol_table_.find(name);
188 if (it != symbol_table_.end()) {
189 return it->second;
190 }
191 return std::nullopt;
192}
193
194std::vector<AsarSymbol> AsarWrapper::GetSymbolsAtAddress(uint32_t address) const {
195 std::vector<AsarSymbol> symbols;
196 for (const auto& [name, symbol] : symbol_table_) {
197 if (symbol.address == address) {
198 symbols.push_back(symbol);
199 }
200 }
201 return symbols;
202}
203
205#ifdef YAZE_ENABLE_ASAR
207 asar_reset();
208 }
209#endif
210 symbol_table_.clear();
211 last_errors_.clear();
212 last_warnings_.clear();
213}
214
216 const std::vector<uint8_t>& original_rom,
217 const std::vector<uint8_t>& modified_rom,
218 const std::string& patch_path) {
219 // This is a complex operation that would require:
220 // 1. Analyzing differences between ROMs
221 // 2. Generating appropriate assembly code
222 // 3. Writing the patch file
223 return absl::UnimplementedError(
224 "Patch creation from ROM differences not yet implemented");
225}
226
227absl::Status AsarWrapper::ValidateAssembly(const std::string& asm_path) {
228#ifdef YAZE_ENABLE_ASAR
229 if (!library_enabled_) {
230 return absl::UnimplementedError(
231 "ASAR library not enabled - build with YAZE_ENABLE_ASAR=1");
232 }
233
234 // Create a dummy ROM for validation
235 std::vector<uint8_t> dummy_rom(1024, 0);
236
237 auto result = ApplyPatch(asm_path, dummy_rom);
238 if (!result.ok()) {
239 return result.status();
240 }
241
242 if (!result->success) {
243 return absl::InvalidArgumentError(absl::StrFormat(
244 "Assembly validation failed: %s",
245 absl::StrJoin(result->errors, "; ")));
246 }
247
248 return absl::OkStatus();
249#else
250 (void)asm_path;
251 return absl::UnimplementedError(
252 "ASAR library not enabled - build with YAZE_ENABLE_ASAR=1");
253#endif
254}
255
257#ifdef YAZE_ENABLE_ASAR
258 last_errors_.clear();
259
260 int error_count = 0;
261 const errordata* errors = asar_geterrors(&error_count);
262
263 for (int i = 0; i < error_count; ++i) {
264 last_errors_.push_back(std::string(errors[i].fullerrdata));
265 }
266#endif
267}
268
270#ifdef YAZE_ENABLE_ASAR
271 last_warnings_.clear();
272
273 int warning_count = 0;
274 const errordata* warnings = asar_getwarnings(&warning_count);
275
276 for (int i = 0; i < warning_count; ++i) {
277 last_warnings_.push_back(std::string(warnings[i].fullerrdata));
278 }
279#endif
280}
281
282absl::StatusOr<AsarPatchResult> AsarWrapper::ApplyPatchWithLibrary(
283 const std::string& patch_path,
284 std::vector<uint8_t>& rom_data,
285 const std::vector<std::string>& /*include_paths*/) {
286#ifdef YAZE_ENABLE_ASAR
288 return absl::FailedPreconditionError("Asar library not initialized");
289 }
290
291 AsarPatchResult result;
292 result.success = false;
293
294 // Prepare ROM data
295 int rom_size = static_cast<int>(rom_data.size());
296 int buffer_size =
297 std::max(rom_size, 16 * 1024 * 1024); // At least 16MB buffer
298
299 // Resize ROM data if needed
300 if (rom_data.size() < static_cast<size_t>(buffer_size)) {
301 rom_data.resize(buffer_size, 0);
302 }
303
304 // Apply the patch
305 bool patch_success = asar_patch(
306 patch_path.c_str(),
307 reinterpret_cast<char*>(rom_data.data()),
308 buffer_size,
309 &rom_size);
310
311 // Process results
314
315 result.errors = last_errors_;
316 result.warnings = last_warnings_;
317 result.success = patch_success && last_errors_.empty();
318
319 if (result.success) {
320 // Resize ROM data to actual size
321 rom_data.resize(rom_size);
322 result.rom_size = rom_size;
323
324 // Extract symbols
326 result.symbols.reserve(symbol_table_.size());
327 for (const auto& [name, symbol] : symbol_table_) {
328 result.symbols.push_back(symbol);
329 }
330
331 result.crc32 = 0; // TODO: CRC32 calculation for library path
332 return result;
333 }
334
335 return absl::InternalError(absl::StrFormat(
336 "Patch failed: %s", absl::StrJoin(last_errors_, "; ")));
337#else
338 (void)patch_path;
339 (void)rom_data;
340 (void)include_paths;
341 return absl::UnimplementedError(
342 "ASAR library not enabled - build with YAZE_ENABLE_ASAR=1");
343#endif
344}
345
346absl::StatusOr<AsarPatchResult> AsarWrapper::ApplyPatchWithBinary(
347 const std::string& patch_path,
348 std::vector<uint8_t>& rom_data,
349 const std::vector<std::string>& include_paths) {
350 last_errors_.clear();
351 last_warnings_.clear();
352
353 auto asar_path_opt = ResolveAsarBinary();
354 if (!asar_path_opt) {
355 return absl::FailedPreconditionError("Asar CLI path could not be resolved");
356 }
357
358 fs::path patch_fs = fs::absolute(patch_path);
359 fs::path patch_dir = patch_fs.parent_path();
360 fs::path patch_name = patch_fs.filename();
361
362 // Write ROM to temporary file
363 auto timestamp = std::chrono::steady_clock::now().time_since_epoch().count();
364 fs::path temp_rom = fs::temp_directory_path() /
365 ("yaze_asar_cli_" + std::to_string(timestamp) + ".sfc");
366 {
367 std::ofstream temp_rom_file(temp_rom, std::ios::binary);
368 if (!temp_rom_file) {
369 return absl::InternalError(
370 "Failed to create temporary ROM file for Asar CLI");
371 }
372 temp_rom_file.write(reinterpret_cast<const char*>(rom_data.data()),
373 static_cast<std::streamsize>(rom_data.size()));
374 if (!temp_rom_file) {
375 return absl::InternalError("Failed to write temporary ROM file");
376 }
377 }
378
379 // Build command
380 std::ostringstream cmd;
381 cmd << "\"" << *asar_path_opt << "\" --no-progress";
382 for (const auto& include_path : include_paths) {
383 cmd << " -I\"" << include_path << "\"";
384 }
385 // Capture stderr
386 cmd << " \"" << patch_name.string() << "\" \"" << temp_rom.string() << "\" 2>&1";
387
388 // Run in patch directory so relative incsrc paths resolve naturally
389 std::error_code ec;
390 fs::path original_cwd = fs::current_path(ec);
391 if (!patch_dir.empty()) {
392 fs::current_path(patch_dir, ec);
393 }
394
395 // Execute using popen to capture output
396 std::array<char, 128> buffer;
397 std::string output;
398 std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.str().c_str(), "r"), pclose);
399 if (!pipe) {
400 fs::remove(temp_rom, ec);
401 if (!patch_dir.empty()) fs::current_path(original_cwd, ec);
402 return absl::InternalError("popen() failed for Asar CLI");
403 }
404 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
405 output += buffer.data();
406 }
407
408 // Note: pclose returns exit status
409 // But we can't rely solely on it if popen failed differently.
410 // Assuming standard behavior.
411
412 if (!patch_dir.empty()) {
413 fs::current_path(original_cwd, ec);
414 }
415
416 // Parse output for errors/warnings
417 // Asar format: "file.asm:line: error: message"
418 std::istringstream output_stream(output);
419 std::string line;
420 while (std::getline(output_stream, line)) {
421 if (line.find("error: ") != std::string::npos || line.find(": error:") != std::string::npos) {
422 last_errors_.push_back(line);
423 } else if (line.find("warning: ") != std::string::npos) {
424 last_warnings_.push_back(line);
425 }
426 }
427
428 if (!last_errors_.empty()) {
429 fs::remove(temp_rom, ec);
430 return absl::InternalError("Asar CLI reported errors during assembly");
431 }
432
433 // Read patched ROM back into memory
434 std::ifstream patched_rom(temp_rom, std::ios::binary);
435 if (!patched_rom) {
436 last_errors_.push_back("Failed to read patched ROM from Asar CLI");
437 fs::remove(temp_rom, ec);
438 return absl::InternalError(last_errors_.back());
439 }
440
441 std::vector<uint8_t> new_data(
442 (std::istreambuf_iterator<char>(patched_rom)),
443 std::istreambuf_iterator<char>());
444 rom_data.swap(new_data);
445 fs::remove(temp_rom, ec);
446
447 AsarPatchResult result;
448 result.success = true;
449 result.rom_size = static_cast<uint32_t>(rom_data.size());
450 result.crc32 = 0; // TODO: CRC32 for CLI path
451 return result;
452}
453
454std::optional<std::string> AsarWrapper::ResolveAsarBinary() const {
455 const char* env_path = std::getenv("YAZE_ASAR_PATH");
456 if (env_path != nullptr) {
457 fs::path env(env_path);
458 std::error_code ec;
459 if (fs::exists(env, ec)) {
460 return env.string();
461 }
462 }
463
464#ifdef YAZE_ASAR_STANDALONE_PATH
465 {
466 fs::path bundled(YAZE_ASAR_STANDALONE_PATH);
467 std::error_code ec;
468 if (!bundled.empty() && fs::exists(bundled, ec)) {
469 return bundled.string();
470 }
471 }
472#endif
473
474 // Fallback to system asar in PATH
475 return std::string("asar");
476}
477
478absl::Status AsarWrapper::LoadSymbolsFromFile(const std::string& symbol_path) {
479 std::ifstream file(symbol_path);
480 if (!file.is_open()) {
481 return absl::InvalidArgumentError(
482 absl::StrFormat("Cannot open symbol file: %s", symbol_path));
483 }
484
485 symbol_table_.clear();
486 std::string line;
487 while (std::getline(file, line)) {
488 // WLA format: bank:addr label
489 // Example: 00:8000 Reset
490
491 // Skip sections or comments
492 if (line.empty() || line[0] == '[' || line[0] == ';') continue;
493
494 size_t colon_pos = line.find(':');
495 if (colon_pos == std::string::npos || colon_pos != 2) continue;
496
497 size_t space_pos = line.find(' ', colon_pos);
498 if (space_pos == std::string::npos) continue;
499
500 try {
501 std::string bank_str = line.substr(0, colon_pos);
502 std::string addr_str = line.substr(colon_pos + 1, space_pos - (colon_pos + 1));
503 std::string name = line.substr(space_pos + 1);
504
505 // Trim name
506 name.erase(0, name.find_first_not_of(" \t\r\n"));
507 name.erase(name.find_last_not_of(" \t\r\n") + 1);
508
509 int bank = std::stoi(bank_str, nullptr, 16);
510 int addr = std::stoi(addr_str, nullptr, 16);
511 uint32_t full_addr = (bank << 16) | addr;
512
513 AsarSymbol symbol;
514 symbol.name = name;
515 symbol.address = full_addr;
516
517 symbol_table_[name] = symbol;
518 } catch (...) {
519 // Parse error, skip line
520 continue;
521 }
522 }
523
524 return absl::OkStatus();
525}
526
528#ifdef YAZE_ENABLE_ASAR
529 symbol_table_.clear();
530
531 // Extract labels using the correct API function
532 int symbol_count = 0;
533 const labeldata* labels = asar_getalllabels(&symbol_count);
534
535 for (int i = 0; i < symbol_count; ++i) {
536 AsarSymbol symbol;
537 symbol.name = std::string(labels[i].name);
538 symbol.address = labels[i].location;
539 symbol.file = ""; // Not available in basic API
540 symbol.line = 0; // Not available in basic API
541 symbol.opcode = ""; // Would need additional processing
542 symbol.comment = "";
543
544 symbol_table_[symbol.name] = symbol;
545 }
546#endif
547}
548
549AsarSymbol AsarWrapper::ConvertAsarSymbol(const void* asar_symbol_data) const {
550 // This would convert from Asar's internal symbol representation
551 // to our AsarSymbol struct. Implementation depends on Asar's API.
552 AsarSymbol symbol;
553 return symbol;
554}
555
556} // namespace core
557} // namespace yaze
absl::StatusOr< AsarPatchResult > ApplyPatchWithBinary(const std::string &patch_path, std::vector< uint8_t > &rom_data, const std::vector< std::string > &include_paths)
absl::StatusOr< AsarPatchResult > ApplyPatchFromString(const std::string &patch_content, std::vector< uint8_t > &rom_data, const std::string &base_path="")
AsarSymbol ConvertAsarSymbol(const void *asar_symbol_data) const
absl::StatusOr< AsarPatchResult > ApplyPatch(const std::string &patch_path, std::vector< uint8_t > &rom_data, const std::vector< std::string > &include_paths={})
absl::Status CreatePatch(const std::vector< uint8_t > &original_rom, const std::vector< uint8_t > &modified_rom, const std::string &patch_path)
std::vector< std::string > last_warnings_
absl::Status ValidateAssembly(const std::string &asm_path)
std::optional< AsarSymbol > FindSymbol(const std::string &name) const
std::string GetVersion() const
std::optional< std::string > ResolveAsarBinary() const
std::map< std::string, AsarSymbol > symbol_table_
absl::Status Initialize()
std::vector< std::string > last_errors_
absl::StatusOr< AsarPatchResult > ApplyPatchWithLibrary(const std::string &patch_path, std::vector< uint8_t > &rom_data, const std::vector< std::string > &include_paths)
absl::Status LoadSymbolsFromFile(const std::string &symbol_path)
absl::StatusOr< std::vector< AsarSymbol > > ExtractSymbols(const std::string &asm_path, const std::vector< std::string > &include_paths={})
std::vector< AsarSymbol > GetSymbolsAtAddress(uint32_t address) const
std::map< std::string, AsarSymbol > GetSymbolTable() const
Asar patch result information.
std::vector< std::string > errors
std::vector< AsarSymbol > symbols
std::vector< std::string > warnings
Symbol information extracted from Asar assembly.