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