Update CMake configuration and CI/CD workflows

- Upgraded CMake minimum version requirement to 3.16 and updated project version to 0.3.0.
- Introduced new CMake presets for build configurations, including default, debug, and release options.
- Added CI/CD workflows for continuous integration and release management, enhancing automated testing and deployment processes.
- Integrated Asar assembler support with new wrapper classes and CLI commands for patching ROMs.
- Implemented comprehensive tests for Asar integration, ensuring robust functionality and error handling.
- Enhanced packaging configuration for cross-platform support, including Windows, macOS, and Linux.
- Updated documentation and added test assets for improved clarity and usability.
This commit is contained in:
scawful
2025-09-25 08:59:59 -04:00
parent a01200dd29
commit 6bdcfe95ec
37 changed files with 4406 additions and 135 deletions

View File

@@ -0,0 +1,293 @@
#include "app/core/asar_wrapper.h"
#include <fstream>
#include <sstream>
#include <iostream>
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
// Include Asar C bindings
#include "asar-dll-bindings/c/asar.h"
namespace yaze {
namespace app {
namespace core {
AsarWrapper::AsarWrapper() : initialized_(false) {}
AsarWrapper::~AsarWrapper() {
if (initialized_) {
Shutdown();
}
}
absl::Status AsarWrapper::Initialize() {
if (initialized_) {
return absl::OkStatus();
}
// Verify API version compatibility
int api_version = asar_apiversion();
if (api_version < 300) { // Require at least API version 3.0
return absl::InternalError(absl::StrFormat(
"Asar API version %d is too old (required: 300+)", api_version));
}
initialized_ = true;
return absl::OkStatus();
}
void AsarWrapper::Shutdown() {
if (initialized_) {
// Note: Static library doesn't have asar_close()
initialized_ = false;
}
}
std::string AsarWrapper::GetVersion() const {
if (!initialized_) {
return "Not initialized";
}
int version = asar_version();
int major = version / 10000;
int minor = (version / 100) % 100;
int patch = version % 100;
return absl::StrFormat("%d.%d.%d", major, minor, patch);
}
int AsarWrapper::GetApiVersion() const {
if (!initialized_) {
return 0;
}
return asar_apiversion();
}
absl::StatusOr<AsarPatchResult> AsarWrapper::ApplyPatch(
const std::string& patch_path,
std::vector<uint8_t>& rom_data,
const std::vector<std::string>& include_paths) {
if (!initialized_) {
return absl::FailedPreconditionError("Asar not initialized");
}
// Reset previous state
Reset();
AsarPatchResult result;
result.success = false;
// Prepare ROM data
int rom_size = static_cast<int>(rom_data.size());
int buffer_size = std::max(rom_size, 16 * 1024 * 1024); // At least 16MB buffer
// Resize ROM data if needed
if (rom_data.size() < buffer_size) {
rom_data.resize(buffer_size, 0);
}
// Apply the patch
bool patch_success = asar_patch(
patch_path.c_str(),
reinterpret_cast<char*>(rom_data.data()),
buffer_size,
&rom_size);
// Process results
ProcessErrors();
ProcessWarnings();
result.errors = last_errors_;
result.warnings = last_warnings_;
result.success = patch_success && last_errors_.empty();
if (result.success) {
// Resize ROM data to actual size
rom_data.resize(rom_size);
result.rom_size = rom_size;
// Extract symbols
ExtractSymbolsFromLastOperation();
result.symbols.reserve(symbol_table_.size());
for (const auto& [name, symbol] : symbol_table_) {
result.symbols.push_back(symbol);
}
// Calculate CRC32 if available
// Note: Asar might provide this, check if function exists
result.crc32 = 0; // TODO: Implement CRC32 calculation
} else {
return absl::InternalError(absl::StrFormat(
"Patch failed: %s", absl::StrJoin(last_errors_, "; ")));
}
return result;
}
absl::StatusOr<AsarPatchResult> AsarWrapper::ApplyPatchFromString(
const std::string& patch_content,
std::vector<uint8_t>& rom_data,
const std::string& base_path) {
// Create temporary file for patch content
std::string temp_path = "/tmp/yaze_temp_patch.asm";
if (!base_path.empty()) {
temp_path = base_path + "/temp_patch.asm";
}
std::ofstream temp_file(temp_path);
if (!temp_file) {
return absl::InternalError("Failed to create temporary patch file");
}
temp_file << patch_content;
temp_file.close();
auto result = ApplyPatch(temp_path, rom_data);
// Clean up temporary file
std::remove(temp_path.c_str());
return result;
}
absl::StatusOr<std::vector<AsarSymbol>> AsarWrapper::ExtractSymbols(
const std::string& asm_path,
const std::vector<std::string>& include_paths) {
if (!initialized_) {
return absl::FailedPreconditionError("Asar not initialized");
}
// Create a dummy ROM for symbol extraction
std::vector<uint8_t> dummy_rom(1024 * 1024, 0); // 1MB dummy ROM
auto result = ApplyPatch(asm_path, dummy_rom, include_paths);
if (!result.ok()) {
return result.status();
}
return result->symbols;
}
std::map<std::string, AsarSymbol> AsarWrapper::GetSymbolTable() const {
return symbol_table_;
}
std::optional<AsarSymbol> AsarWrapper::FindSymbol(const std::string& name) const {
auto it = symbol_table_.find(name);
if (it != symbol_table_.end()) {
return it->second;
}
return std::nullopt;
}
std::vector<AsarSymbol> AsarWrapper::GetSymbolsAtAddress(uint32_t address) const {
std::vector<AsarSymbol> symbols;
for (const auto& [name, symbol] : symbol_table_) {
if (symbol.address == address) {
symbols.push_back(symbol);
}
}
return symbols;
}
void AsarWrapper::Reset() {
if (initialized_) {
asar_reset();
}
symbol_table_.clear();
last_errors_.clear();
last_warnings_.clear();
}
absl::Status AsarWrapper::CreatePatch(
const std::vector<uint8_t>& original_rom,
const std::vector<uint8_t>& modified_rom,
const std::string& patch_path) {
// This is a complex operation that would require:
// 1. Analyzing differences between ROMs
// 2. Generating appropriate assembly code
// 3. Writing the patch file
// For now, return not implemented
return absl::UnimplementedError(
"Patch creation from ROM differences not yet implemented");
}
absl::Status AsarWrapper::ValidateAssembly(const std::string& asm_path) {
// Create a dummy ROM for validation
std::vector<uint8_t> dummy_rom(1024, 0);
auto result = ApplyPatch(asm_path, dummy_rom);
if (!result.ok()) {
return result.status();
}
if (!result->success) {
return absl::InvalidArgumentError(absl::StrFormat(
"Assembly validation failed: %s",
absl::StrJoin(result->errors, "; ")));
}
return absl::OkStatus();
}
void AsarWrapper::ProcessErrors() {
last_errors_.clear();
int error_count = 0;
const errordata* errors = asar_geterrors(&error_count);
for (int i = 0; i < error_count; ++i) {
last_errors_.push_back(std::string(errors[i].fullerrdata));
}
}
void AsarWrapper::ProcessWarnings() {
last_warnings_.clear();
int warning_count = 0;
const errordata* warnings = asar_getwarnings(&warning_count);
for (int i = 0; i < warning_count; ++i) {
last_warnings_.push_back(std::string(warnings[i].fullerrdata));
}
}
void AsarWrapper::ExtractSymbolsFromLastOperation() {
symbol_table_.clear();
// Extract labels using the correct API function
int symbol_count = 0;
const labeldata* labels = asar_getalllabels(&symbol_count);
for (int i = 0; i < symbol_count; ++i) {
AsarSymbol symbol;
symbol.name = std::string(labels[i].name);
symbol.address = labels[i].location;
symbol.file = ""; // Not available in basic API
symbol.line = 0; // Not available in basic API
symbol.opcode = ""; // Would need additional processing
symbol.comment = "";
symbol_table_[symbol.name] = symbol;
}
}
AsarSymbol AsarWrapper::ConvertAsarSymbol(const void* asar_symbol_data) const {
// This would convert from Asar's internal symbol representation
// to our AsarSymbol struct. Implementation depends on Asar's API.
AsarSymbol symbol;
// Placeholder implementation
return symbol;
}
} // namespace core
} // namespace app
} // namespace yaze

212
src/app/core/asar_wrapper.h Normal file
View File

@@ -0,0 +1,212 @@
#ifndef YAZE_APP_CORE_ASAR_WRAPPER_H
#define YAZE_APP_CORE_ASAR_WRAPPER_H
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <optional>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
namespace yaze {
namespace app {
namespace core {
/**
* @brief Symbol information extracted from Asar assembly
*/
struct AsarSymbol {
std::string name; // Symbol name
uint32_t address; // Memory address
std::string opcode; // Associated opcode if available
std::string file; // Source file
int line; // Line number in source
std::string comment; // Optional comment
};
/**
* @brief Asar patch result information
*/
struct AsarPatchResult {
bool success; // Whether patch was successful
std::vector<std::string> errors; // Error messages if any
std::vector<std::string> warnings; // Warning messages
std::vector<AsarSymbol> symbols; // Extracted symbols
uint32_t rom_size; // Final ROM size after patching
uint32_t crc32; // CRC32 checksum of patched ROM
};
/**
* @brief Modern C++ wrapper for Asar 65816 assembler integration
*
* This class provides a high-level interface for:
* - Patching ROMs with assembly code
* - Extracting symbol names and opcodes
* - Cross-platform compatibility (Windows, macOS, Linux)
*/
class AsarWrapper {
public:
AsarWrapper();
~AsarWrapper();
// Disable copy constructor and assignment
AsarWrapper(const AsarWrapper&) = delete;
AsarWrapper& operator=(const AsarWrapper&) = delete;
// Enable move constructor and assignment
AsarWrapper(AsarWrapper&&) = default;
AsarWrapper& operator=(AsarWrapper&&) = default;
/**
* @brief Initialize the Asar library
* @return Status indicating success or failure
*/
absl::Status Initialize();
/**
* @brief Clean up and close the Asar library
*/
void Shutdown();
/**
* @brief Check if Asar is initialized and ready
* @return True if initialized, false otherwise
*/
bool IsInitialized() const { return initialized_; }
/**
* @brief Get Asar version information
* @return Version string
*/
std::string GetVersion() const;
/**
* @brief Get Asar API version
* @return API version number
*/
int GetApiVersion() const;
/**
* @brief Apply an assembly patch to a ROM
* @param patch_path Path to the .asm patch file
* @param rom_data ROM data to patch (will be modified)
* @param include_paths Additional include paths for assembly files
* @return Patch result with status and extracted information
*/
absl::StatusOr<AsarPatchResult> ApplyPatch(
const std::string& patch_path,
std::vector<uint8_t>& rom_data,
const std::vector<std::string>& include_paths = {});
/**
* @brief Apply an assembly patch from string content
* @param patch_content Assembly source code as string
* @param rom_data ROM data to patch (will be modified)
* @param base_path Base path for resolving includes
* @return Patch result with status and extracted information
*/
absl::StatusOr<AsarPatchResult> ApplyPatchFromString(
const std::string& patch_content,
std::vector<uint8_t>& rom_data,
const std::string& base_path = "");
/**
* @brief Extract symbols from an assembly file without patching
* @param asm_path Path to the assembly file
* @param include_paths Additional include paths
* @return Vector of extracted symbols
*/
absl::StatusOr<std::vector<AsarSymbol>> ExtractSymbols(
const std::string& asm_path,
const std::vector<std::string>& include_paths = {});
/**
* @brief Get all available symbols from the last patch operation
* @return Map of symbol names to symbol information
*/
std::map<std::string, AsarSymbol> GetSymbolTable() const;
/**
* @brief Find a symbol by name
* @param name Symbol name to search for
* @return Symbol information if found
*/
std::optional<AsarSymbol> FindSymbol(const std::string& name) const;
/**
* @brief Get symbols at a specific address
* @param address Memory address to search
* @return Vector of symbols at that address
*/
std::vector<AsarSymbol> GetSymbolsAtAddress(uint32_t address) const;
/**
* @brief Reset the Asar state (clear errors, warnings, symbols)
*/
void Reset();
/**
* @brief Get the last error messages
* @return Vector of error strings
*/
std::vector<std::string> GetLastErrors() const { return last_errors_; }
/**
* @brief Get the last warning messages
* @return Vector of warning strings
*/
std::vector<std::string> GetLastWarnings() const { return last_warnings_; }
/**
* @brief Create a patch that can be applied to transform one ROM to another
* @param original_rom Original ROM data
* @param modified_rom Modified ROM data
* @param patch_path Output path for the generated patch
* @return Status indicating success or failure
*/
absl::Status CreatePatch(
const std::vector<uint8_t>& original_rom,
const std::vector<uint8_t>& modified_rom,
const std::string& patch_path);
/**
* @brief Validate an assembly file for syntax errors
* @param asm_path Path to the assembly file
* @return Status indicating validation result
*/
absl::Status ValidateAssembly(const std::string& asm_path);
private:
bool initialized_;
std::map<std::string, AsarSymbol> symbol_table_;
std::vector<std::string> last_errors_;
std::vector<std::string> last_warnings_;
/**
* @brief Process errors from Asar and store them
*/
void ProcessErrors();
/**
* @brief Process warnings from Asar and store them
*/
void ProcessWarnings();
/**
* @brief Extract symbols from the last Asar operation
*/
void ExtractSymbolsFromLastOperation();
/**
* @brief Convert Asar symbol data to AsarSymbol struct
*/
AsarSymbol ConvertAsarSymbol(const void* asar_symbol_data) const;
};
} // namespace core
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_ASAR_WRAPPER_H

View File

@@ -4,6 +4,7 @@ set(
app/emu/emulator.cc
app/core/project.cc
app/core/window.cc
app/core/asar_wrapper.cc
)
if (WIN32 OR MINGW OR UNIX AND NOT APPLE)