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