Update z3ed CLI tool and project build configuration
- Updated `.clang-tidy` and `.clangd` configurations for improved code quality checks and diagnostics. - Added new submodules for JSON and HTTP libraries to support future features. - Refined README and documentation files to standardize naming conventions and improve clarity. - Introduced a new command palette in the CLI for easier command access and execution. - Implemented various CLI handlers for managing ROM, sprites, palettes, and dungeon functionalities. - Enhanced the TUI components for better user interaction and command execution. - Added AI service integration for generating commands based on user prompts, expanding the CLI's capabilities.
This commit is contained in:
@@ -242,7 +242,7 @@ absl::Status YazeProject::SaveToYazeFormat() {
|
||||
}
|
||||
|
||||
// Write header comment
|
||||
file << "# YAZE Project File\n";
|
||||
file << "# yaze Project File\n";
|
||||
file << "# Format Version: 2.0\n";
|
||||
file << "# Generated by YAZE " << metadata.yaze_version << "\n";
|
||||
file << "# Last Modified: " << metadata.last_modified << "\n\n";
|
||||
@@ -467,7 +467,7 @@ absl::Status YazeProject::RepairProject() {
|
||||
std::filesystem::path abs_labels = GetAbsolutePath(labels_filename);
|
||||
if (!std::filesystem::exists(abs_labels)) {
|
||||
std::ofstream labels_file(abs_labels);
|
||||
labels_file << "# YAZE Resource Labels\n";
|
||||
labels_file << "# yaze Resource Labels\n";
|
||||
labels_file << "# Format: [type] key=value\n\n";
|
||||
labels_file.close();
|
||||
}
|
||||
@@ -755,7 +755,7 @@ bool ResourceLabelManager::SaveLabels() {
|
||||
std::ofstream file(filename_);
|
||||
if (!file.is_open()) return false;
|
||||
|
||||
file << "# YAZE Resource Labels\n";
|
||||
file << "# yaze Resource Labels\n";
|
||||
file << "# Format: [type] followed by key=value pairs\n\n";
|
||||
|
||||
for (const auto& [type, type_labels] : labels_) {
|
||||
|
||||
@@ -116,8 +116,8 @@ absl::Status LoadScr(std::string_view filename, uint8_t input_value,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_data,
|
||||
std::vector<uint8_t>& map_bitmap_data,
|
||||
absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_bitmap_data,
|
||||
std::vector<uint8_t>& map_data,
|
||||
std::vector<uint8_t>& cgx_loaded) {
|
||||
const std::vector<uint16_t> dimensions = {0x000, 0x400, 0x800, 0xC00};
|
||||
uint8_t p = 0;
|
||||
@@ -155,6 +155,34 @@ absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_data,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status SaveCgx(uint8_t bpp, std::string_view filename,
|
||||
const std::vector<uint8_t>& cgx_data,
|
||||
const std::vector<uint8_t>& cgx_header) {
|
||||
std::ofstream file(filename.data(), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return absl::NotFoundError("Could not open file for writing.");
|
||||
}
|
||||
|
||||
CgxHeader header;
|
||||
strncpy(header.file_type, "SCH", 4);
|
||||
std::string bpp_str = std::to_string(bpp) + "BIT";
|
||||
strncpy(header.bit_mode, bpp_str.c_str(), 5);
|
||||
strncpy(header.version_number, "Ver-0.01\n", 9);
|
||||
header.header_size = sizeof(CgxHeader);
|
||||
strncpy(header.hardware_name, "SFC", 4);
|
||||
header.bg_obj_flag = 0;
|
||||
header.color_palette_number = 0;
|
||||
|
||||
file.write(reinterpret_cast<const char*>(&header), sizeof(CgxHeader));
|
||||
file.write(reinterpret_cast<const char*>(cgx_data.data()), cgx_data.size());
|
||||
file.write(reinterpret_cast<const char*>(cgx_header.data()), cgx_header.size());
|
||||
|
||||
file.close();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<SDL_Color> DecodeColFile(const std::string_view filename) {
|
||||
std::vector<SDL_Color> decoded_col;
|
||||
std::ifstream file(filename.data(), std::ios::binary | std::ios::ate);
|
||||
@@ -190,6 +218,23 @@ std::vector<SDL_Color> DecodeColFile(const std::string_view filename) {
|
||||
return decoded_col;
|
||||
}
|
||||
|
||||
absl::Status SaveCol(std::string_view filename, const std::vector<SDL_Color>& palette) {
|
||||
std::ofstream file(filename.data(), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return absl::NotFoundError("Could not open file for writing.");
|
||||
}
|
||||
|
||||
for (const auto& color : palette) {
|
||||
uint16_t snes_color = ((color.b >> 3) << 10) |
|
||||
((color.g >> 3) << 5) |
|
||||
(color.r >> 3);
|
||||
file.write(reinterpret_cast<const char*>(&snes_color), sizeof(snes_color));
|
||||
}
|
||||
|
||||
file.close();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DecodeObjFile(
|
||||
std::string_view filename, std::vector<uint8_t>& obj_data,
|
||||
std::vector<uint8_t> actual_obj_data,
|
||||
|
||||
@@ -73,6 +73,13 @@ absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_bitmap_data,
|
||||
std::vector<uint8_t>& map_data,
|
||||
std::vector<uint8_t>& cgx_loaded);
|
||||
|
||||
/**
|
||||
* @brief Save Cgx file (graphical content)
|
||||
*/
|
||||
absl::Status SaveCgx(uint8_t bpp, std::string_view filename,
|
||||
const std::vector<uint8_t>& cgx_data,
|
||||
const std::vector<uint8_t>& cgx_header);
|
||||
|
||||
/**
|
||||
* @brief Decode color file
|
||||
*/
|
||||
@@ -87,6 +94,11 @@ absl::Status DecodeObjFile(
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> decoded_obj,
|
||||
std::vector<uint8_t>& decoded_extra_obj, int& obj_loaded);
|
||||
|
||||
/**
|
||||
* @brief Save Col file (palette data)
|
||||
*/
|
||||
absl::Status SaveCol(std::string_view filename, const std::vector<SDL_Color>& palette);
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -655,7 +655,7 @@ std::string ThemeManager::SerializeTheme(const EnhancedTheme& theme) const {
|
||||
return std::to_string(r) + "," + std::to_string(g) + "," + std::to_string(b) + "," + std::to_string(a);
|
||||
};
|
||||
|
||||
ss << "# YAZE Theme File\n";
|
||||
ss << "# yaze Theme File\n";
|
||||
ss << "# Generated by YAZE Theme Editor\n";
|
||||
ss << "name=" << theme.name << "\n";
|
||||
ss << "description=" << theme.description << "\n";
|
||||
|
||||
@@ -1,653 +1,39 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/parse.h"
|
||||
#include "absl/flags/usage.h"
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
#include "cli/z3ed.h"
|
||||
#include "cli/modern_cli.h"
|
||||
#include "cli/tui.h"
|
||||
#include "app/core/asar_wrapper.h"
|
||||
#include "app/gfx/arena.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
|
||||
// Global flags
|
||||
ABSL_FLAG(bool, tui, false, "Launch the Text User Interface");
|
||||
ABSL_FLAG(bool, verbose, false, "Enable verbose output");
|
||||
ABSL_FLAG(bool, tui, false, "Launch Text User Interface");
|
||||
ABSL_FLAG(std::string, rom, "", "Path to the ROM file");
|
||||
|
||||
// Command-specific flags
|
||||
ABSL_FLAG(std::string, output, "", "Output file path");
|
||||
ABSL_FLAG(bool, dry_run, false, "Perform a dry run without making changes");
|
||||
ABSL_FLAG(bool, verbose, false, "Enable verbose output");
|
||||
ABSL_FLAG(bool, dry_run, false, "Perform operations without making changes");
|
||||
ABSL_FLAG(bool, backup, true, "Create a backup before modifying files");
|
||||
ABSL_FLAG(std::string, test, "", "Name of the test to run");
|
||||
ABSL_FLAG(bool, show_gui, false, "Show the test engine GUI");
|
||||
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
struct CommandInfo {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string usage;
|
||||
std::function<absl::Status(const std::vector<std::string>&)> handler;
|
||||
};
|
||||
|
||||
class ModernCLI {
|
||||
public:
|
||||
ModernCLI() {
|
||||
SetupCommands();
|
||||
}
|
||||
|
||||
void SetupCommands() {
|
||||
commands_["patch apply-asar"] = {
|
||||
.name = "patch apply-asar",
|
||||
.description = "Apply Asar 65816 assembly patch to ROM",
|
||||
.usage = "z3ed patch apply-asar <patch.asm> [--rom=<rom_file>] [--output=<output_file>]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleAsarCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["patch apply-bps"] = {
|
||||
.name = "patch apply-bps",
|
||||
.description = "Apply BPS patch to ROM",
|
||||
.usage = "z3ed patch apply-bps <patch.bps> [--rom=<rom_file>] [--output=<output_file>]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandlePatchCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["patch extract-symbols"] = {
|
||||
.name = "patch extract-symbols",
|
||||
.description = "Extract symbols from assembly file",
|
||||
.usage = "z3ed patch extract-symbols <patch.asm>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleExtractCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["patch validate"] = {
|
||||
.name = "patch validate",
|
||||
.description = "Validate assembly file syntax",
|
||||
.usage = "z3ed patch validate <patch.asm>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleValidateCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["rom info"] = {
|
||||
.name = "rom info",
|
||||
.description = "Show ROM information",
|
||||
.usage = "z3ed rom info [--rom=<rom_file>]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleInfoCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["dungeon export"] = {
|
||||
.name = "dungeon export",
|
||||
.description = "Export dungeon data to a file",
|
||||
.usage = "z3ed dungeon export <room_id> --format <format>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleDungeonExportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["gfx export-sheet"] = {
|
||||
.name = "gfx export-sheet",
|
||||
.description = "Export a graphics sheet to a file",
|
||||
.usage = "z3ed gfx export-sheet <sheet_id> --to <file>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleGfxExportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["gfx import-sheet"] = {
|
||||
.name = "gfx import-sheet",
|
||||
.description = "Import a graphics sheet from a file",
|
||||
.usage = "z3ed gfx import-sheet <sheet_id> --from <file>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleGfxImportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["palette export"] = {
|
||||
.name = "palette export",
|
||||
.description = "Export a palette to a file",
|
||||
.usage = "z3ed palette export --group <group> --id <id> --format <format>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandlePaletteExportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["palette import"] = {
|
||||
.name = "palette import",
|
||||
.description = "Import a palette from a file",
|
||||
.usage = "z3ed palette import --group <group> --id <id> --from <file>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandlePaletteImportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["util convert"] = {
|
||||
.name = "util convert",
|
||||
.description = "Convert between SNES and PC addresses",
|
||||
.usage = "z3ed util convert <address> [--to-pc|--to-snes]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleConvertCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["test run-assets"] = {
|
||||
.name = "test run-assets",
|
||||
.description = "Run comprehensive asset loading tests on ROM",
|
||||
.usage = "z3ed test run-assets [--rom=<rom_file>]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleTestCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["help"] = {
|
||||
.name = "help",
|
||||
.description = "Show help information",
|
||||
.usage = "z3ed help [command]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleHelpCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["test run-gui"] = {
|
||||
.name = "test run-gui",
|
||||
.description = "Run automated GUI tests",
|
||||
.usage = "z3ed test run-gui --rom=<rom_file> --test=<test_name>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleTestGuiCommand(args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void ShowHelp(const std::string& command = "") {
|
||||
if (!command.empty()) {
|
||||
auto it = commands_.find(command);
|
||||
if (it != commands_.end()) {
|
||||
std::cout << "Command: " << it->second.name << std::endl;
|
||||
std::cout << "Description: " << it->second.description << std::endl;
|
||||
std::cout << "Usage: " << it->second.usage << std::endl;
|
||||
return;
|
||||
} else {
|
||||
std::cout << "Unknown command: " << command << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "z3ed - Yet Another Zelda3 Editor CLI Tool" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "USAGE:" << std::endl;
|
||||
std::cout << " z3ed [--tui] <resource> <action> [arguments]" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "GLOBAL FLAGS:" << std::endl;
|
||||
std::cout << " --tui Launch Text User Interface" << std::endl;
|
||||
std::cout << " --version Show version information" << std::endl;
|
||||
std::cout << " --verbose Enable verbose output" << std::endl;
|
||||
std::cout << " --rom=<file> Specify ROM file to use" << std::endl;
|
||||
std::cout << " --output=<file> Specify output file path" << std::endl;
|
||||
std::cout << " --dry-run Perform operations without making changes" << std::endl;
|
||||
std::cout << " --backup=<bool> Create backup before modifying (default: true)" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "COMMANDS:" << std::endl;
|
||||
|
||||
for (const auto& [name, info] : commands_) {
|
||||
std::cout << absl::StrFormat(" %-25s %s", name, info.description) << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "EXAMPLES:" << std::endl;
|
||||
std::cout << " z3ed --tui # Launch TUI" << std::endl;
|
||||
std::cout << " z3ed patch apply-asar patch.asm --rom=zelda3.sfc # Apply Asar patch" << std::endl;
|
||||
std::cout << " z3ed patch apply-bps changes.bps --rom=zelda3.sfc # Apply BPS patch" << std::endl;
|
||||
std::cout << " z3ed patch extract-symbols patch.asm # Extract symbols" << std::endl;
|
||||
std::cout << " z3ed rom info --rom=zelda3.sfc # Show ROM info" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "For more information on a specific command:" << std::endl;
|
||||
std::cout << " z3ed help <resource> <action>" << std::endl;
|
||||
}
|
||||
|
||||
absl::Status RunCommand(const std::string& command, const std::vector<std::string>& args) {
|
||||
auto it = commands_.find(command);
|
||||
if (it == commands_.end()) {
|
||||
return absl::NotFoundError(absl::StrFormat("Unknown command: %s", command));
|
||||
}
|
||||
|
||||
return it->second.handler(args);
|
||||
}
|
||||
|
||||
#include "cli/handlers/dungeon.h"
|
||||
|
||||
private:
|
||||
std::map<std::string, CommandInfo> commands_;
|
||||
|
||||
absl::Status HandleDungeonExportCommand(const std::vector<std::string>& args) {
|
||||
DungeonExport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status HandleGfxExportCommand(const std::vector<std::string>& args) {
|
||||
GfxExport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status HandleGfxImportCommand(const std::vector<std::string>& args) {
|
||||
GfxImport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status HandlePaletteExportCommand(const std::vector<std::string>& args) {
|
||||
PaletteExport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status HandlePaletteImportCommand(const std::vector<std::string>& args) {
|
||||
PaletteImport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
absl::Status HandleAsarCommand(const std::vector<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError("Asar command requires a patch file");
|
||||
}
|
||||
|
||||
AsarPatch handler;
|
||||
std::vector<std::string> handler_args = args;
|
||||
|
||||
// Add ROM file from flag if not provided as argument
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (args.size() == 1 && !rom_file.empty()) {
|
||||
handler_args.push_back(rom_file);
|
||||
}
|
||||
|
||||
return handler.Run(handler_args);
|
||||
}
|
||||
|
||||
absl::Status HandlePatchCommand(const std::vector<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError("Patch command requires a BPS file");
|
||||
}
|
||||
|
||||
ApplyPatch handler;
|
||||
std::vector<std::string> handler_args = args;
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (args.size() == 1 && !rom_file.empty()) {
|
||||
handler_args.push_back(rom_file);
|
||||
}
|
||||
|
||||
return handler.Run(handler_args);
|
||||
}
|
||||
|
||||
absl::Status HandleExtractCommand(const std::vector<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError("Extract command requires an assembly file");
|
||||
}
|
||||
|
||||
// Use the AsarWrapper to extract symbols
|
||||
yaze::app::core::AsarWrapper wrapper;
|
||||
RETURN_IF_ERROR(wrapper.Initialize());
|
||||
|
||||
auto symbols_result = wrapper.ExtractSymbols(args[0]);
|
||||
if (!symbols_result.ok()) {
|
||||
return symbols_result.status();
|
||||
}
|
||||
|
||||
const auto& symbols = symbols_result.value();
|
||||
std::cout << "🏷️ Extracted " << symbols.size() << " symbols from " << args[0] << ":" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
for (const auto& symbol : symbols) {
|
||||
std::cout << absl::StrFormat(" %-20s @ $%06X", symbol.name, symbol.address) << std::endl;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleValidateCommand(const std::vector<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError("Validate command requires an assembly file");
|
||||
}
|
||||
|
||||
yaze::app::core::AsarWrapper wrapper;
|
||||
RETURN_IF_ERROR(wrapper.Initialize());
|
||||
|
||||
auto status = wrapper.ValidateAssembly(args[0]);
|
||||
if (status.ok()) {
|
||||
std::cout << "✅ Assembly file is valid: " << args[0] << std::endl;
|
||||
} else {
|
||||
std::cout << "❌ Assembly validation failed:" << std::endl;
|
||||
std::cout << " " << status.message() << std::endl;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
absl::Status HandleInfoCommand(const std::vector<std::string>& args) {
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (!args.empty()) {
|
||||
rom_file = args[0];
|
||||
}
|
||||
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file required (use --rom=<file> or provide as argument)");
|
||||
}
|
||||
|
||||
Open handler;
|
||||
return handler.Run({rom_file});
|
||||
}
|
||||
|
||||
absl::Status HandleConvertCommand(const std::vector<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
return absl::InvalidArgumentError("Convert command requires an address");
|
||||
}
|
||||
|
||||
// TODO: Implement address conversion
|
||||
std::cout << "Address conversion not yet implemented" << std::endl;
|
||||
return absl::UnimplementedError("Address conversion functionality");
|
||||
}
|
||||
|
||||
absl::Status HandleTestCommand(const std::vector<std::string>& args) {
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (args.size() > 0 && args[0].find("--rom=") == 0) {
|
||||
rom_file = args[0].substr(6);
|
||||
}
|
||||
|
||||
if (rom_file.empty()) {
|
||||
rom_file = "zelda3.sfc"; // Default ROM file
|
||||
}
|
||||
|
||||
std::cout << "🧪 YAZE Asset Loading Test Suite" << std::endl;
|
||||
std::cout << "ROM: " << rom_file << std::endl;
|
||||
std::cout << "=================================" << std::endl;
|
||||
|
||||
// Initialize SDL for graphics tests
|
||||
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
||||
return absl::InternalError(absl::StrCat("Failed to initialize SDL: ", SDL_GetError()));
|
||||
}
|
||||
|
||||
int tests_passed = 0;
|
||||
int tests_total = 0;
|
||||
|
||||
// Test 1: ROM Loading
|
||||
std::cout << "📁 Testing ROM loading..." << std::flush;
|
||||
tests_total++;
|
||||
Rom test_rom;
|
||||
auto status = test_rom.LoadFromFile(rom_file);
|
||||
if (status.ok()) {
|
||||
std::cout << " ✅ PASSED" << std::endl;
|
||||
tests_passed++;
|
||||
std::cout << " Title: " << test_rom.title() << std::endl;
|
||||
std::cout << " Size: " << test_rom.size() << " bytes" << std::endl;
|
||||
} else {
|
||||
std::cout << " ❌ FAILED: " << status.message() << std::endl;
|
||||
SDL_Quit();
|
||||
return status;
|
||||
}
|
||||
|
||||
// Test 2: Graphics Arena Resource Tracking
|
||||
std::cout << "🎨 Testing graphics arena..." << std::flush;
|
||||
tests_total++;
|
||||
try {
|
||||
auto& arena = gfx::Arena::Get();
|
||||
size_t initial_textures = arena.GetTextureCount();
|
||||
size_t initial_surfaces = arena.GetSurfaceCount();
|
||||
|
||||
std::cout << " ✅ PASSED" << std::endl;
|
||||
std::cout << " Initial textures: " << initial_textures << std::endl;
|
||||
std::cout << " Initial surfaces: " << initial_surfaces << std::endl;
|
||||
tests_passed++;
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << " ❌ FAILED: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
// Test 3: Graphics Data Loading
|
||||
bool test_graphics = true;
|
||||
for (const auto& arg : args) {
|
||||
if (arg == "--no-graphics") test_graphics = false;
|
||||
}
|
||||
|
||||
if (test_graphics) {
|
||||
std::cout << "🖼️ Testing graphics data loading..." << std::flush;
|
||||
tests_total++;
|
||||
try {
|
||||
auto graphics_result = LoadAllGraphicsData(test_rom);
|
||||
if (graphics_result.ok()) {
|
||||
std::cout << " ✅ PASSED" << std::endl;
|
||||
std::cout << " Loaded " << graphics_result.value().size() << " graphics sheets" << std::endl;
|
||||
tests_passed++;
|
||||
} else {
|
||||
std::cout << " ❌ FAILED: " << graphics_result.status().message() << std::endl;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << " ❌ FAILED: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Test 4: Overworld Loading
|
||||
bool test_overworld = true;
|
||||
for (const auto& arg : args) {
|
||||
if (arg == "--no-overworld") test_overworld = false;
|
||||
}
|
||||
|
||||
if (test_overworld) {
|
||||
std::cout << "🗺️ Testing overworld loading..." << std::flush;
|
||||
tests_total++;
|
||||
try {
|
||||
zelda3::Overworld overworld(&test_rom);
|
||||
auto ow_status = overworld.Load(&test_rom);
|
||||
if (ow_status.ok()) {
|
||||
std::cout << " ✅ PASSED" << std::endl;
|
||||
std::cout << " Loaded overworld data successfully" << std::endl;
|
||||
tests_passed++;
|
||||
} else {
|
||||
std::cout << " ❌ FAILED: " << ow_status.message() << std::endl;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << " ❌ FAILED: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Test 5: Arena Shutdown Test
|
||||
std::cout << "🔄 Testing arena shutdown..." << std::flush;
|
||||
tests_total++;
|
||||
try {
|
||||
auto& arena = gfx::Arena::Get();
|
||||
size_t final_textures = arena.GetTextureCount();
|
||||
size_t final_surfaces = arena.GetSurfaceCount();
|
||||
|
||||
// Test the shutdown method (this should not crash)
|
||||
arena.Shutdown();
|
||||
|
||||
std::cout << " ✅ PASSED" << std::endl;
|
||||
std::cout << " Final textures: " << final_textures << std::endl;
|
||||
std::cout << " Final surfaces: " << final_surfaces << std::endl;
|
||||
tests_passed++;
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << " ❌ FAILED: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
SDL_Quit();
|
||||
|
||||
// Summary
|
||||
std::cout << "=================================" << std::endl;
|
||||
std::cout << "📊 Test Results: " << tests_passed << "/" << tests_total << " passed" << std::endl;
|
||||
|
||||
if (tests_passed == tests_total) {
|
||||
std::cout << "🎉 All tests passed!" << std::endl;
|
||||
return absl::OkStatus();
|
||||
} else {
|
||||
std::cout << "❌ Some tests failed." << std::endl;
|
||||
return absl::InternalError("Test failures detected");
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status HandleTestGuiCommand(const std::vector<std::string>& args) {
|
||||
#ifdef _WIN32
|
||||
return absl::UnimplementedError(
|
||||
"GUI test command is not supported on Windows. "
|
||||
"Please run yaze_test.exe directly with --enable-ui-tests flag.");
|
||||
#else
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
std::string test_name = absl::GetFlag(FLAGS_test);
|
||||
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file required (use --rom=<file>)");
|
||||
}
|
||||
|
||||
// Get the path to the current executable
|
||||
char exe_path[1024];
|
||||
#ifdef __APPLE__
|
||||
uint32_t size = sizeof(exe_path);
|
||||
if (_NSGetExecutablePath(exe_path, &size) != 0) {
|
||||
return absl::InternalError("Could not get executable path");
|
||||
}
|
||||
#else
|
||||
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
if (len == -1) {
|
||||
return absl::InternalError("Could not get executable path");
|
||||
}
|
||||
exe_path[len] = '\0';
|
||||
#endif
|
||||
|
||||
std::string exe_dir = std::string(exe_path);
|
||||
exe_dir = exe_dir.substr(0, exe_dir.find_last_of("/"));
|
||||
|
||||
std::string yaze_test_path = exe_dir + "/yaze_test";
|
||||
|
||||
std::vector<std::string> command_args;
|
||||
command_args.push_back(yaze_test_path);
|
||||
command_args.push_back("--enable-ui-tests");
|
||||
command_args.push_back("--rom-path=" + rom_file);
|
||||
if (!test_name.empty()) {
|
||||
command_args.push_back(test_name);
|
||||
}
|
||||
if (absl::GetFlag(FLAGS_show_gui)) {
|
||||
command_args.push_back("--show-gui");
|
||||
}
|
||||
|
||||
std::vector<char*> argv;
|
||||
for (const auto& arg : command_args) {
|
||||
argv.push_back((char*)arg.c_str());
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
return absl::InternalError("Failed to fork process");
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process
|
||||
execv(yaze_test_path.c_str(), argv.data());
|
||||
// If execv returns, it must have failed
|
||||
return absl::InternalError("Failed to execute yaze_test");
|
||||
} else {
|
||||
// Parent process
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFEXITED(status)) {
|
||||
int exit_code = WEXITSTATUS(status);
|
||||
if (exit_code == 0) {
|
||||
return absl::OkStatus();
|
||||
} else {
|
||||
return absl::InternalError(absl::StrFormat("yaze_test exited with code %d", exit_code));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
absl::Status HandleHelpCommand(const std::vector<std::string>& args) {
|
||||
std::string command = args.empty() ? "" : absl::StrJoin(args, " ");
|
||||
ShowHelp(command);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#ifdef _WIN32
|
||||
extern "C" int SDL_main(int argc, char* argv[]) {
|
||||
#else
|
||||
int main(int argc, char* argv[]) {
|
||||
#endif
|
||||
absl::SetProgramUsageMessage(
|
||||
"z3ed - Yet Another Zelda3 Editor CLI Tool\n"
|
||||
"\n"
|
||||
"A command-line tool for editing The Legend of Zelda: A Link to the Past ROMs.\n"
|
||||
"Supports Asar 65816 assembly patching, BPS patches, and ROM analysis.\n"
|
||||
"\n"
|
||||
"Use --tui to launch the interactive text interface, or run commands directly.\n"
|
||||
);
|
||||
// Parse command line flags
|
||||
absl::ParseCommandLine(argc, argv);
|
||||
|
||||
auto args = absl::ParseCommandLine(argc, argv);
|
||||
|
||||
yaze::cli::ModernCLI cli;
|
||||
|
||||
// Handle TUI flag
|
||||
// Check if TUI mode is requested
|
||||
if (absl::GetFlag(FLAGS_tui)) {
|
||||
yaze::cli::ShowMain();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle command line arguments
|
||||
if (args.size() < 2) {
|
||||
cli.ShowHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string command;
|
||||
std::vector<std::string> command_args;
|
||||
|
||||
if (args.size() >= 3) {
|
||||
command = std::string(args[1]) + " " + std::string(args[2]);
|
||||
command_args.assign(args.begin() + 3, args.end());
|
||||
} else {
|
||||
command = args[1];
|
||||
command_args.assign(args.begin() + 2, args.end());
|
||||
}
|
||||
|
||||
auto status = cli.RunCommand(command, command_args);
|
||||
// Run CLI commands
|
||||
yaze::cli::ModernCLI cli;
|
||||
auto status = cli.Run(argc, argv);
|
||||
|
||||
if (!status.ok()) {
|
||||
std::cerr << "Error: " << status.message() << std::endl;
|
||||
|
||||
if (status.code() == absl::StatusCode::kNotFound) {
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "Available commands:" << std::endl;
|
||||
cli.ShowHelp();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
232
src/cli/handlers/agent.cc
Normal file
232
src/cli/handlers/agent.cc
Normal file
@@ -0,0 +1,232 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "cli/modern_cli.h"
|
||||
#include "cli/service/ai_service.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
// Mock AI service is defined in ai_service.h
|
||||
|
||||
namespace {
|
||||
|
||||
absl::Status HandleRunCommand(const std::vector<std::string>& arg_vec, Rom& rom) {
|
||||
if (arg_vec.size() < 2 || arg_vec[0] != "--prompt") {
|
||||
return absl::InvalidArgumentError("Usage: agent run --prompt <prompt>");
|
||||
}
|
||||
std::string prompt = arg_vec[1];
|
||||
|
||||
// Save a temporary copy of the ROM
|
||||
if (rom.is_loaded()) {
|
||||
auto status = rom.SaveToFile({.save_new = true, .filename = "temp_rom.sfc"});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
MockAIService ai_service;
|
||||
auto commands_or = ai_service.GetCommands(prompt);
|
||||
if (!commands_or.ok()) {
|
||||
return commands_or.status();
|
||||
}
|
||||
std::vector<std::string> commands = commands_or.value();
|
||||
|
||||
ModernCLI cli;
|
||||
for (const auto& command : commands) {
|
||||
std::vector<std::string> command_parts;
|
||||
std::string current_part;
|
||||
bool in_quotes = false;
|
||||
for (char c : command) {
|
||||
if (c == '\"') {
|
||||
in_quotes = !in_quotes;
|
||||
} else if (c == ' ' && !in_quotes) {
|
||||
command_parts.push_back(current_part);
|
||||
current_part.clear();
|
||||
} else {
|
||||
current_part += c;
|
||||
}
|
||||
}
|
||||
command_parts.push_back(current_part);
|
||||
|
||||
std::string cmd_name = command_parts[0] + " " + command_parts[1];
|
||||
std::vector<std::string> cmd_args(command_parts.begin() + 2, command_parts.end());
|
||||
|
||||
auto it = cli.commands_.find(cmd_name);
|
||||
if (it != cli.commands_.end()) {
|
||||
auto status = it->second.handler(cmd_args);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandlePlanCommand(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2 || arg_vec[0] != "--prompt") {
|
||||
return absl::InvalidArgumentError("Usage: agent plan --prompt <prompt>");
|
||||
}
|
||||
std::string prompt = arg_vec[1];
|
||||
MockAIService ai_service;
|
||||
auto commands_or = ai_service.GetCommands(prompt);
|
||||
if (!commands_or.ok()) {
|
||||
return commands_or.status();
|
||||
}
|
||||
std::vector<std::string> commands = commands_or.value();
|
||||
|
||||
std::cout << "AI Agent Plan:" << std::endl;
|
||||
for (const auto& command : commands) {
|
||||
std::cout << " - " << command << std::endl;
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleDiffCommand(Rom& rom) {
|
||||
if (rom.is_loaded()) {
|
||||
RomDiff diff_handler;
|
||||
auto status = diff_handler.Run({rom.filename(), "temp_rom.sfc"});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
} else {
|
||||
return absl::AbortedError("No ROM loaded.");
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleTestCommand(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2 || arg_vec[0] != "--test") {
|
||||
return absl::InvalidArgumentError("Usage: agent test --test <test_name>");
|
||||
}
|
||||
std::string test_name = arg_vec[1];
|
||||
|
||||
#ifdef _WIN32
|
||||
return absl::UnimplementedError(
|
||||
"GUI test command is not supported on Windows. "
|
||||
"Please run yaze_test.exe directly with --enable-ui-tests flag.");
|
||||
#else
|
||||
char exe_path[1024];
|
||||
#ifdef __APPLE__
|
||||
uint32_t size = sizeof(exe_path);
|
||||
if (_NSGetExecutablePath(exe_path, &size) != 0) {
|
||||
return absl::InternalError("Could not get executable path");
|
||||
}
|
||||
#else
|
||||
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
if (len == -1) {
|
||||
return absl::InternalError("Could not get executable path");
|
||||
}
|
||||
exe_path[len] = '\0';
|
||||
#endif
|
||||
|
||||
std::string exe_dir = std::string(exe_path);
|
||||
exe_dir = exe_dir.substr(0, exe_dir.find_last_of("/"));
|
||||
|
||||
std::string yaze_test_path = exe_dir + "/yaze_test";
|
||||
|
||||
std::vector<std::string> command_args;
|
||||
command_args.push_back(yaze_test_path);
|
||||
command_args.push_back("--enable-ui-tests");
|
||||
command_args.push_back("--test=" + test_name);
|
||||
|
||||
std::vector<char*> argv;
|
||||
for (const auto& arg : command_args) {
|
||||
argv.push_back((char*)arg.c_str());
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
return absl::InternalError("Failed to fork process");
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process
|
||||
execv(yaze_test_path.c_str(), argv.data());
|
||||
// If execv returns, it must have failed
|
||||
return absl::InternalError("Failed to execute yaze_test");
|
||||
} else {
|
||||
// Parent process
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFEXITED(status)) {
|
||||
int exit_code = WEXITSTATUS(status);
|
||||
if (exit_code == 0) {
|
||||
return absl::OkStatus();
|
||||
} else {
|
||||
return absl::InternalError(absl::StrFormat("yaze_test exited with code %d", exit_code));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
#endif
|
||||
}
|
||||
|
||||
absl::Status HandleLearnCommand() {
|
||||
std::cout << "Agent learn not yet implemented." << std::endl;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleCommitCommand(Rom& rom) {
|
||||
if (rom.is_loaded()) {
|
||||
auto status = rom.SaveToFile({.save_new = false});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
std::cout << "✅ Changes committed successfully." << std::endl;
|
||||
} else {
|
||||
return absl::AbortedError("No ROM loaded.");
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleRevertCommand(Rom& rom) {
|
||||
if (rom.is_loaded()) {
|
||||
auto status = rom.LoadFromFile(rom.filename());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
std::cout << "✅ Changes reverted successfully." << std::endl;
|
||||
} else {
|
||||
return absl::AbortedError("No ROM loaded.");
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
absl::Status Agent::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: agent <run|plan|diff|test|learn|commit|revert> [options]");
|
||||
}
|
||||
|
||||
std::string subcommand = arg_vec[0];
|
||||
std::vector<std::string> subcommand_args(arg_vec.begin() + 1, arg_vec.end());
|
||||
|
||||
if (subcommand == "run") {
|
||||
return HandleRunCommand(subcommand_args, rom_);
|
||||
} else if (subcommand == "plan") {
|
||||
return HandlePlanCommand(subcommand_args);
|
||||
} else if (subcommand == "diff") {
|
||||
return HandleDiffCommand(rom_);
|
||||
} else if (subcommand == "test") {
|
||||
return HandleTestCommand(subcommand_args);
|
||||
} else if (subcommand == "learn") {
|
||||
return HandleLearnCommand();
|
||||
} else if (subcommand == "commit") {
|
||||
return HandleCommitCommand(rom_);
|
||||
} else if (subcommand == "revert") {
|
||||
return HandleRevertCommand(rom_);
|
||||
} else {
|
||||
return absl::InvalidArgumentError("Invalid subcommand for agent command.");
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
18
src/cli/handlers/command_palette.cc
Normal file
18
src/cli/handlers/command_palette.cc
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "cli/tui/command_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
CommandPalette::CommandPalette() {}
|
||||
|
||||
absl::Status CommandPalette::Run(const std::vector<std::string>& arg_vec) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void CommandPalette::RunTUI(ftxui::ScreenInteractive& screen) {
|
||||
// TODO: Implement command palette TUI
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
@@ -1,5 +1,10 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
@@ -10,9 +15,61 @@ absl::Status DungeonExport::Run(const std::vector<std::string>& arg_vec) {
|
||||
}
|
||||
|
||||
int room_id = std::stoi(arg_vec[0]);
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
// TODO: Implement dungeon export logic
|
||||
std::cout << "Dungeon export for room " << room_id << " not yet implemented." << std::endl;
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
zelda3::DungeonEditorSystem dungeon_editor(&rom_);
|
||||
auto room_or = dungeon_editor.GetRoom(room_id);
|
||||
if (!room_or.ok()) {
|
||||
return room_or.status();
|
||||
}
|
||||
zelda3::Room room = room_or.value();
|
||||
|
||||
std::cout << "Room ID: " << room_id << std::endl;
|
||||
std::cout << "Blockset: " << (int)room.blockset << std::endl;
|
||||
std::cout << "Spriteset: " << (int)room.spriteset << std::endl;
|
||||
std::cout << "Palette: " << (int)room.palette << std::endl;
|
||||
std::cout << "Layout: " << (int)room.layout << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonListObjects::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 1) {
|
||||
return absl::InvalidArgumentError("Usage: dungeon list-objects <room_id>");
|
||||
}
|
||||
|
||||
int room_id = std::stoi(arg_vec[0]);
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
zelda3::DungeonEditorSystem dungeon_editor(&rom_);
|
||||
auto room_or = dungeon_editor.GetRoom(room_id);
|
||||
if (!room_or.ok()) {
|
||||
return room_or.status();
|
||||
}
|
||||
zelda3::Room room = room_or.value();
|
||||
room.LoadObjects();
|
||||
|
||||
std::cout << "Objects in Room " << room_id << ":" << std::endl;
|
||||
for (const auto& obj : room.GetTileObjects()) {
|
||||
std::cout << absl::StrFormat(" - ID: 0x%04X, Pos: (%d, %d), Size: 0x%02X, Layer: %d\n",
|
||||
obj.id_, obj.x_, obj.y_, obj.size_, obj.layer_);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/gfx/scad_format.h"
|
||||
#include "app/gfx/arena.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
@@ -8,8 +14,34 @@ absl::Status GfxExport::Run(const std::vector<std::string>& arg_vec) {
|
||||
return absl::InvalidArgumentError("Usage: gfx export-sheet <sheet_id> --to <file>");
|
||||
}
|
||||
|
||||
// TODO: Implement gfx export logic
|
||||
std::cout << "Gfx export not yet implemented." << std::endl;
|
||||
int sheet_id = std::stoi(arg_vec[0]);
|
||||
std::string output_file = arg_vec[1];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
auto& arena = gfx::Arena::Get();
|
||||
auto sheet = arena.gfx_sheet(sheet_id);
|
||||
if (!sheet.is_active()) {
|
||||
return absl::NotFoundError("Graphics sheet not found.");
|
||||
}
|
||||
|
||||
// For now, we will just save the raw 8bpp data.
|
||||
// TODO: Convert the 8bpp data to the correct SNES bpp format.
|
||||
std::vector<uint8_t> header; // Empty header for now
|
||||
auto status = gfx::SaveCgx(sheet.depth(), output_file, sheet.vector(), header);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::cout << "Successfully exported graphics sheet " << sheet_id << " to " << output_file << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
@@ -19,8 +51,39 @@ absl::Status GfxImport::Run(const std::vector<std::string>& arg_vec) {
|
||||
return absl::InvalidArgumentError("Usage: gfx import-sheet <sheet_id> --from <file>");
|
||||
}
|
||||
|
||||
// TODO: Implement gfx import logic
|
||||
std::cout << "Gfx import not yet implemented." << std::endl;
|
||||
int sheet_id = std::stoi(arg_vec[0]);
|
||||
std::string input_file = arg_vec[1];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
std::vector<uint8_t> cgx_data, cgx_loaded, cgx_header;
|
||||
auto status = gfx::LoadCgx(8, input_file, cgx_data, cgx_loaded, cgx_header);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
auto& arena = gfx::Arena::Get();
|
||||
auto sheet = arena.gfx_sheet(sheet_id);
|
||||
if (!sheet.is_active()) {
|
||||
return absl::NotFoundError("Graphics sheet not found.");
|
||||
}
|
||||
|
||||
// TODO: Convert the 8bpp data to the correct SNES bpp format before writing.
|
||||
// For now, we just replace the data directly.
|
||||
sheet.set_data(cgx_loaded);
|
||||
|
||||
// TODO: Implement saving the modified graphics sheet back to the ROM.
|
||||
|
||||
std::cout << "Successfully imported graphics sheet " << sheet_id << " from " << input_file << std::endl;
|
||||
std::cout << "(Saving to ROM not yet implemented)" << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
77
src/cli/handlers/overworld.cc
Normal file
77
src/cli/handlers/overworld.cc
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
absl::Status OverworldGetTile::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 3) {
|
||||
return absl::InvalidArgumentError("Usage: overworld get-tile --map <map_id> --x <x> --y <y>");
|
||||
}
|
||||
|
||||
// TODO: Implement proper argument parsing
|
||||
int map_id = std::stoi(arg_vec[0]);
|
||||
int x = std::stoi(arg_vec[1]);
|
||||
int y = std::stoi(arg_vec[2]);
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
zelda3::Overworld overworld(&rom_);
|
||||
overworld.Load(&rom_);
|
||||
|
||||
uint16_t tile = overworld.GetTile(x, y);
|
||||
|
||||
std::cout << "Tile at (" << x << ", " << y << ") on map " << map_id << " is: 0x" << std::hex << tile << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status OverworldSetTile::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 4) {
|
||||
return absl::InvalidArgumentError("Usage: overworld set-tile --map <map_id> --x <x> --y <y> --tile <tile_id>");
|
||||
}
|
||||
|
||||
// TODO: Implement proper argument parsing
|
||||
int map_id = std::stoi(arg_vec[0]);
|
||||
int x = std::stoi(arg_vec[1]);
|
||||
int y = std::stoi(arg_vec[2]);
|
||||
int tile_id = std::stoi(arg_vec[3], nullptr, 16);
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
zelda3::Overworld overworld(&rom_);
|
||||
overworld.Load(&rom_);
|
||||
|
||||
// TODO: Implement the actual set_tile method in Overworld class
|
||||
// overworld.SetTile(x, y, tile_id);
|
||||
|
||||
// rom_.SaveToFile({.filename = rom_file});
|
||||
|
||||
std::cout << "Set tile at (" << x << ", " << y << ") on map " << map_id << " to: 0x" << std::hex << tile_id << std::endl;
|
||||
std::cout << "(Not actually implemented yet)" << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
@@ -1,26 +1,135 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "cli/tui/palette_editor.h"
|
||||
|
||||
#include "app/gfx/scad_format.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
Palette::Palette() {}
|
||||
|
||||
absl::Status Palette::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: palette <export|import> [options]");
|
||||
}
|
||||
|
||||
const std::string& action = arg_vec[0];
|
||||
std::vector<std::string> new_args(arg_vec.begin() + 1, arg_vec.end());
|
||||
|
||||
if (action == "export") {
|
||||
PaletteExport handler;
|
||||
return handler.Run(new_args);
|
||||
} else if (action == "import") {
|
||||
PaletteImport handler;
|
||||
return handler.Run(new_args);
|
||||
}
|
||||
|
||||
return absl::InvalidArgumentError("Invalid action for palette command.");
|
||||
}
|
||||
|
||||
void Palette::RunTUI(ftxui::ScreenInteractive& screen) {
|
||||
// TODO: Implement palette editor TUI
|
||||
(void)screen; // Suppress unused parameter warning
|
||||
}
|
||||
|
||||
absl::Status PaletteExport::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
return absl::InvalidArgumentError("Usage: palette export --group <group> --id <id> --format <format>");
|
||||
if (arg_vec.size() < 3) {
|
||||
return absl::InvalidArgumentError("Usage: palette export --group <group> --id <id> --to <file>");
|
||||
}
|
||||
|
||||
// TODO: Implement palette export logic
|
||||
std::cout << "Palette export not yet implemented." << std::endl;
|
||||
// TODO: Implement proper argument parsing
|
||||
std::string group_name = arg_vec[0];
|
||||
int palette_id = std::stoi(arg_vec[1]);
|
||||
std::string output_file = arg_vec[2];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
auto palette_group = rom_.palette_group().get_group(group_name);
|
||||
if (!palette_group) {
|
||||
return absl::NotFoundError("Palette group not found.");
|
||||
}
|
||||
|
||||
auto palette = palette_group->palette(palette_id);
|
||||
if (palette.empty()) {
|
||||
return absl::NotFoundError("Palette not found.");
|
||||
}
|
||||
|
||||
std::vector<SDL_Color> sdl_palette;
|
||||
for (const auto& color : palette) {
|
||||
SDL_Color sdl_color;
|
||||
sdl_color.r = color.rgb().x;
|
||||
sdl_color.g = color.rgb().y;
|
||||
sdl_color.b = color.rgb().z;
|
||||
sdl_color.a = 255;
|
||||
sdl_palette.push_back(sdl_color);
|
||||
}
|
||||
|
||||
auto status = gfx::SaveCol(output_file, sdl_palette);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::cout << "Successfully exported palette " << palette_id << " from group " << group_name << " to " << output_file << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status PaletteImport::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
if (arg_vec.size() < 3) {
|
||||
return absl::InvalidArgumentError("Usage: palette import --group <group> --id <id> --from <file>");
|
||||
}
|
||||
|
||||
// TODO: Implement palette import logic
|
||||
std::cout << "Palette import not yet implemented." << std::endl;
|
||||
// TODO: Implement proper argument parsing
|
||||
std::string group_name = arg_vec[0];
|
||||
int palette_id = std::stoi(arg_vec[1]);
|
||||
std::string input_file = arg_vec[2];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
auto sdl_palette = gfx::DecodeColFile(input_file);
|
||||
if (sdl_palette.empty()) {
|
||||
return absl::AbortedError("Failed to load palette file.");
|
||||
}
|
||||
|
||||
gfx::SnesPalette snes_palette;
|
||||
for (const auto& sdl_color : sdl_palette) {
|
||||
snes_palette.AddColor(gfx::SnesColor(sdl_color.r, sdl_color.g, sdl_color.b));
|
||||
}
|
||||
|
||||
auto palette_group = rom_.palette_group().get_group(group_name);
|
||||
if (!palette_group) {
|
||||
return absl::NotFoundError("Palette group not found.");
|
||||
}
|
||||
|
||||
// Replace the palette at the specified index
|
||||
auto* pal = palette_group->mutable_palette(palette_id);
|
||||
*pal = snes_palette;
|
||||
|
||||
// TODO: Implement saving the modified palette back to the ROM.
|
||||
|
||||
std::cout << "Successfully imported palette " << palette_id << " to group " << group_name << " from " << input_file << std::endl;
|
||||
std::cout << "(Saving to ROM not yet implemented)" << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
#include "asar-dll-bindings/c/asar.h"
|
||||
#include "cli/z3ed.h"
|
||||
#include "cli/tui/asar_patch.h"
|
||||
#include "util/bps.h"
|
||||
#include "app/core/asar_wrapper.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
@@ -26,97 +34,56 @@ absl::Status ApplyPatch::Run(const std::vector<std::string>& arg_vec) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
AsarPatch::AsarPatch() {}
|
||||
|
||||
absl::Status AsarPatch::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
return absl::InvalidArgumentError("Usage: asar <patch_file> <rom_file>");
|
||||
if (arg_vec.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: patch apply-asar <patch.asm>");
|
||||
}
|
||||
const std::string& patch_file = arg_vec[0];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
app::core::AsarWrapper wrapper;
|
||||
auto init_status = wrapper.Initialize();
|
||||
if (!init_status.ok()) {
|
||||
return init_status;
|
||||
}
|
||||
|
||||
std::string patch_filename = arg_vec[0];
|
||||
std::string rom_filename = arg_vec[1];
|
||||
|
||||
// Load ROM file
|
||||
RETURN_IF_ERROR(rom_.LoadFromFile(rom_filename))
|
||||
|
||||
// Get ROM data
|
||||
auto rom_data = rom_.vector();
|
||||
int buflen = static_cast<int>(rom_data.size());
|
||||
int romlen = buflen;
|
||||
|
||||
// Ensure we have enough buffer space
|
||||
const int max_rom_size = asar_maxromsize();
|
||||
if (buflen < max_rom_size) {
|
||||
rom_data.resize(max_rom_size, 0);
|
||||
buflen = max_rom_size;
|
||||
auto patch_result = wrapper.ApplyPatch(patch_file, rom_data);
|
||||
|
||||
if (!patch_result.ok()) {
|
||||
return patch_result.status();
|
||||
}
|
||||
|
||||
// Apply Asar patch
|
||||
if (!asar_patch(patch_filename.c_str(),
|
||||
reinterpret_cast<char*>(rom_data.data()),
|
||||
buflen, &romlen)) {
|
||||
std::string error_message = "Failed to apply Asar patch:\n";
|
||||
int num_errors = 0;
|
||||
const errordata* errors = asar_geterrors(&num_errors);
|
||||
for (int i = 0; i < num_errors; i++) {
|
||||
error_message += absl::StrFormat(" %s\n", errors[i].fullerrdata);
|
||||
}
|
||||
return absl::InternalError(error_message);
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
if (!result.success) {
|
||||
return absl::AbortedError(absl::StrJoin(result.errors, "; "));
|
||||
}
|
||||
|
||||
// Resize ROM to actual size
|
||||
rom_data.resize(romlen);
|
||||
|
||||
// Update the ROM data by writing the patched data back
|
||||
for (size_t i = 0; i < rom_data.size(); ++i) {
|
||||
auto status = rom_.WriteByte(i, rom_data[i]);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Save patched ROM
|
||||
std::string output_filename = rom_filename;
|
||||
size_t dot_pos = output_filename.find_last_of('.');
|
||||
if (dot_pos != std::string::npos) {
|
||||
output_filename.insert(dot_pos, "_patched");
|
||||
} else {
|
||||
output_filename += "_patched";
|
||||
}
|
||||
|
||||
Rom::SaveSettings settings;
|
||||
settings.filename = output_filename;
|
||||
RETURN_IF_ERROR(rom_.SaveToFile(settings))
|
||||
|
||||
std::cout << "✅ Asar patch applied successfully!" << std::endl;
|
||||
std::cout << "📁 Output: " << output_filename << std::endl;
|
||||
std::cout << "📊 Final ROM size: " << romlen << " bytes" << std::endl;
|
||||
|
||||
// Show warnings if any
|
||||
int num_warnings = 0;
|
||||
const errordata* warnings = asar_getwarnings(&num_warnings);
|
||||
if (num_warnings > 0) {
|
||||
std::cout << "⚠️ Warnings:" << std::endl;
|
||||
for (int i = 0; i < num_warnings; i++) {
|
||||
std::cout << " " << warnings[i].fullerrdata << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Show extracted symbols
|
||||
int num_labels = 0;
|
||||
const labeldata* labels = asar_getalllabels(&num_labels);
|
||||
if (num_labels > 0) {
|
||||
std::cout << "🏷️ Extracted " << num_labels << " symbols:" << std::endl;
|
||||
for (int i = 0; i < std::min(10, num_labels); i++) { // Show first 10
|
||||
std::cout << " " << labels[i].name << " @ $"
|
||||
<< std::hex << std::uppercase << labels[i].location << std::endl;
|
||||
}
|
||||
if (num_labels > 10) {
|
||||
std::cout << " ... and " << (num_labels - 10) << " more" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: Save the patched ROM
|
||||
|
||||
std::cout << "Patch applied successfully!" << std::endl;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void AsarPatch::RunTUI(ftxui::ScreenInteractive& screen) {
|
||||
// TODO: Implement Asar patch TUI
|
||||
(void)screen; // Suppress unused parameter warning
|
||||
}
|
||||
|
||||
// ... rest of the file
|
||||
|
||||
|
||||
absl::Status CreatePatch::Run(const std::vector<std::string>& arg_vec) {
|
||||
std::vector<uint8_t> source;
|
||||
std::vector<uint8_t> target;
|
||||
|
||||
80
src/cli/handlers/project.cc
Normal file
80
src/cli/handlers/project.cc
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/core/project.h"
|
||||
#include "core/platform/file_dialog.h"
|
||||
#include "util/bps.h"
|
||||
#include <glob.h>
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
absl::Status ProjectInit::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: project init <project_name>");
|
||||
}
|
||||
|
||||
std::string project_name = arg_vec[0];
|
||||
core::YazeProject project;
|
||||
auto status = project.Create(project_name, ".");
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::cout << "✅ Successfully initialized project: " << project_name << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ProjectBuild::Run(const std::vector<std::string>& arg_vec) {
|
||||
core::YazeProject project;
|
||||
auto status = project.Open(".");
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
Rom rom;
|
||||
status = rom.LoadFromFile(project.rom_filename);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Apply BPS patches
|
||||
glob_t glob_result;
|
||||
std::string pattern = project.patches_folder + "/*.bps";
|
||||
glob(pattern.c_str(), GLOB_TILDE, NULL, &glob_result);
|
||||
for(unsigned int i=0; i<glob_result.gl_pathc; ++i){
|
||||
std::string patch_file = glob_result.gl_pathv[i];
|
||||
std::vector<uint8_t> patch_data;
|
||||
auto patch_contents = core::LoadFile(patch_file);
|
||||
std::copy(patch_contents.begin(), patch_contents.end(), std::back_inserter(patch_data));
|
||||
std::vector<uint8_t> patched_rom;
|
||||
util::ApplyBpsPatch(rom.vector(), patch_data, patched_rom);
|
||||
rom.LoadFromData(patched_rom);
|
||||
}
|
||||
|
||||
// Run asar on assembly files
|
||||
glob_t glob_result_asm;
|
||||
std::string pattern_asm = project.patches_folder + "/*.asm";
|
||||
glob(pattern_asm.c_str(), GLOB_TILDE, NULL, &glob_result_asm);
|
||||
for(unsigned int i=0; i<glob_result_asm.gl_pathc; ++i){
|
||||
AsarPatch asar_patch;
|
||||
auto status = asar_patch.Run({glob_result_asm.gl_pathv[i]});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
std::string output_file = project.name + ".sfc";
|
||||
status = rom.SaveToFile({.save_new = true, .filename = output_file});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::cout << "✅ Successfully built project: " << project.name << std::endl;
|
||||
std::cout << " Output ROM: " << output_file << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
116
src/cli/handlers/rom.cc
Normal file
116
src/cli/handlers/rom.cc
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
absl::Status RomValidate::Run(const std::vector<std::string>& arg_vec) {
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
bool all_ok = true;
|
||||
std::cout << "Validating ROM: " << rom_file << std::endl;
|
||||
|
||||
// Checksum validation
|
||||
std::cout << " - Verifying checksum... " << std::flush;
|
||||
// Basic ROM validation - check if ROM is loaded and has reasonable size
|
||||
if (rom_.is_loaded() && rom_.size() > 0) {
|
||||
std::cout << "✅ PASSED" << std::endl;
|
||||
} else {
|
||||
std::cout << "❌ FAILED" << std::endl;
|
||||
all_ok = false;
|
||||
}
|
||||
|
||||
// Header validation
|
||||
std::cout << " - Verifying header... " << std::flush;
|
||||
if (rom_.title() == "THE LEGEND OF ZELDA") {
|
||||
std::cout << "✅ PASSED" << std::endl;
|
||||
} else {
|
||||
std::cout << "❌ FAILED (Invalid title: " << rom_.title() << ")" << std::endl;
|
||||
all_ok = false;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
if (all_ok) {
|
||||
std::cout << "✅ ROM validation successful." << std::endl;
|
||||
} else {
|
||||
std::cout << "❌ ROM validation failed." << std::endl;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status RomDiff::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
return absl::InvalidArgumentError("Usage: rom diff <rom_a> <rom_b>");
|
||||
}
|
||||
|
||||
Rom rom_a;
|
||||
auto status_a = rom_a.LoadFromFile(arg_vec[0]);
|
||||
if (!status_a.ok()) {
|
||||
return status_a;
|
||||
}
|
||||
|
||||
Rom rom_b;
|
||||
auto status_b = rom_b.LoadFromFile(arg_vec[1]);
|
||||
if (!status_b.ok()) {
|
||||
return status_b;
|
||||
}
|
||||
|
||||
if (rom_a.size() != rom_b.size()) {
|
||||
std::cout << "ROMs have different sizes: " << rom_a.size() << " vs " << rom_b.size() << std::endl;
|
||||
}
|
||||
|
||||
int differences = 0;
|
||||
for (size_t i = 0; i < rom_a.size(); ++i) {
|
||||
if (rom_a.vector()[i] != rom_b.vector()[i]) {
|
||||
differences++;
|
||||
std::cout << absl::StrFormat("Difference at 0x%08X: 0x%02X vs 0x%02X\n", i, rom_a.vector()[i], rom_b.vector()[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (differences == 0) {
|
||||
std::cout << "ROMs are identical." << std::endl;
|
||||
} else {
|
||||
std::cout << "Found " << differences << " differences." << std::endl;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status RomGenerateGolden::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
return absl::InvalidArgumentError("Usage: rom generate-golden <rom_file> <golden_file>");
|
||||
}
|
||||
|
||||
Rom rom;
|
||||
auto status = rom.LoadFromFile(arg_vec[0]);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::ofstream file(arg_vec[1], std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return absl::NotFoundError("Could not open file for writing.");
|
||||
}
|
||||
|
||||
file.write(reinterpret_cast<const char*>(rom.vector().data()), rom.size());
|
||||
|
||||
std::cout << "Successfully generated golden file: " << arg_vec[1] << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
30
src/cli/handlers/sprite.cc
Normal file
30
src/cli/handlers/sprite.cc
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/zelda3/sprite/sprite_builder.h"
|
||||
#include "absl/flags/flag.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
absl::Status SpriteCreate::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2 || arg_vec[0] != "--name") {
|
||||
return absl::InvalidArgumentError("Usage: sprite create --name <sprite_name>");
|
||||
}
|
||||
|
||||
std::string sprite_name = arg_vec[1];
|
||||
|
||||
// Create a simple sprite with a single action
|
||||
auto builder = zelda3::SpriteBuilder::Create(sprite_name)
|
||||
.SetProperty("!Health", 1)
|
||||
.SetProperty("!Damage", 2)
|
||||
.AddAction(zelda3::SpriteAction::Create("MAIN")
|
||||
.AddInstruction(zelda3::SpriteInstruction::ApplySpeedTowardsPlayer(1))
|
||||
.AddInstruction(zelda3::SpriteInstruction::MoveXyz())
|
||||
);
|
||||
|
||||
std::cout << builder.Build() << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
412
src/cli/modern_cli.cc
Normal file
412
src/cli/modern_cli.cc
Normal file
@@ -0,0 +1,412 @@
|
||||
#include "cli/modern_cli.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
#include "app/core/asar_wrapper.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
ModernCLI::ModernCLI() {
|
||||
SetupCommands();
|
||||
}
|
||||
|
||||
void ModernCLI::SetupCommands() {
|
||||
commands_["patch apply-asar"] = {
|
||||
.name = "patch apply-asar",
|
||||
.description = "Apply Asar 65816 assembly patch to ROM",
|
||||
.usage = "z3ed patch apply-asar <patch.asm> [--rom=<rom_file>] [--output=<output_file>]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleAsarPatchCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["patch apply-bps"] = {
|
||||
.name = "patch apply-bps",
|
||||
.description = "Apply BPS patch to ROM",
|
||||
.usage = "z3ed patch apply-bps <patch.bps> [--rom=<rom_file>] [--output=<output_file>]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleBpsPatchCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["patch extract-symbols"] = {
|
||||
.name = "patch extract-symbols",
|
||||
.description = "Extract symbols from assembly file",
|
||||
.usage = "z3ed patch extract-symbols <patch.asm>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleExtractSymbolsCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["rom info"] = {
|
||||
.name = "rom info",
|
||||
.description = "Show ROM information",
|
||||
.usage = "z3ed rom info [--rom=<rom_file>]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleRomInfoCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["agent"] = {
|
||||
.name = "agent",
|
||||
.description = "Interact with the AI agent",
|
||||
.usage = "z3ed agent <run|plan|diff|test> [options]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleAgentCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["project build"] = {
|
||||
.name = "project build",
|
||||
.description = "Build the project and create a new ROM file",
|
||||
.usage = "z3ed project build",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleProjectBuildCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["project init"] = {
|
||||
.name = "project init",
|
||||
.description = "Initialize a new z3ed project",
|
||||
.usage = "z3ed project init <project_name>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleProjectInitCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["rom generate-golden"] = {
|
||||
.name = "rom generate-golden",
|
||||
.description = "Generate a golden file from a ROM for regression testing",
|
||||
.usage = "z3ed rom generate-golden <rom_file> <golden_file>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleRomGenerateGoldenCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["rom diff"] = {
|
||||
.name = "rom diff",
|
||||
.description = "Compare two ROM files and show the differences",
|
||||
.usage = "z3ed rom diff <rom_a> <rom_b>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleRomDiffCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["rom validate"] = {
|
||||
.name = "rom validate",
|
||||
.description = "Validate the ROM file",
|
||||
.usage = "z3ed rom validate [--rom=<rom_file>]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleRomValidateCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["dungeon export"] = {
|
||||
.name = "dungeon export",
|
||||
.description = "Export dungeon data to a file",
|
||||
.usage = "z3ed dungeon export <room_id> --to <file>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleDungeonExportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["dungeon list-objects"] = {
|
||||
.name = "dungeon list-objects",
|
||||
.description = "List all objects in a dungeon room",
|
||||
.usage = "z3ed dungeon list-objects <room_id>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleDungeonListObjectsCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["gfx export-sheet"] = {
|
||||
.name = "gfx export-sheet",
|
||||
.description = "Export a graphics sheet to a file",
|
||||
.usage = "z3ed gfx export-sheet <sheet_id> --to <file>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleGfxExportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["gfx import-sheet"] = {
|
||||
.name = "gfx import-sheet",
|
||||
.description = "Import a graphics sheet from a file",
|
||||
.usage = "z3ed gfx import-sheet <sheet_id> --from <file>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleGfxImportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["command-palette"] = {
|
||||
.name = "command-palette",
|
||||
.description = "Show the command palette",
|
||||
.usage = "z3ed command-palette",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleCommandPaletteCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["palette"] = {
|
||||
.name = "palette export",
|
||||
.description = "Export a palette to a file",
|
||||
.usage = "z3ed palette export --group <group> --id <id> --to <file>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandlePaletteExportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["palette import"] = {
|
||||
.name = "palette import",
|
||||
.description = "Import a palette from a file",
|
||||
.usage = "z3ed palette import --group <group> --id <id> --from <file>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandlePaletteImportCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["overworld get-tile"] = {
|
||||
.name = "overworld get-tile",
|
||||
.description = "Get a tile from the overworld",
|
||||
.usage = "z3ed overworld get-tile --map <map_id> --x <x> --y <y>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleOverworldGetTileCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["overworld set-tile"] = {
|
||||
.name = "overworld set-tile",
|
||||
.description = "Set a tile in the overworld",
|
||||
.usage = "z3ed overworld set-tile --map <map_id> --x <x> --y <y> --tile <tile_id>",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleOverworldSetTileCommand(args);
|
||||
}
|
||||
};
|
||||
|
||||
commands_["sprite create"] = {
|
||||
.name = "sprite create",
|
||||
.description = "Create a new sprite",
|
||||
.usage = "z3ed sprite create --name <name> [options]",
|
||||
.handler = [this](const std::vector<std::string>& args) -> absl::Status {
|
||||
return HandleSpriteCreateCommand(args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void ModernCLI::ShowHelp() {
|
||||
std::cout << "z3ed - Yet Another Zelda3 Editor CLI Tool" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "USAGE:" << std::endl;
|
||||
std::cout << " z3ed [--tui] <resource> <action> [arguments]" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "GLOBAL FLAGS:" << std::endl;
|
||||
std::cout << " --tui Launch Text User Interface" << std::endl;
|
||||
std::cout << " --version Show version information" << std::endl;
|
||||
std::cout << " --verbose Enable verbose output" << std::endl;
|
||||
std::cout << " --rom=<file> Specify ROM file to use" << std::endl;
|
||||
std::cout << " --output=<file> Specify output file path" << std::endl;
|
||||
std::cout << " --dry-run Perform operations without making changes" << std::endl;
|
||||
std::cout << " --backup=<bool> Create backup before modifying (default: true)" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "COMMANDS:" << std::endl;
|
||||
|
||||
for (const auto& [name, info] : commands_) {
|
||||
std::cout << absl::StrFormat(" %-25s %s", name, info.description) << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "EXAMPLES:" << std::endl;
|
||||
std::cout << " z3ed --tui # Launch TUI" << std::endl;
|
||||
std::cout << " z3ed patch apply-asar patch.asm --rom=zelda3.sfc # Apply Asar patch" << std::endl;
|
||||
std::cout << " z3ed patch apply-bps changes.bps --rom=zelda3.sfc # Apply BPS patch" << std::endl;
|
||||
std::cout << " z3ed patch extract-symbols patch.asm # Extract symbols" << std::endl;
|
||||
std::cout << " z3ed rom info --rom=zelda3.sfc # Show ROM info" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "For more information on a specific command:" << std::endl;
|
||||
std::cout << " z3ed help <resource> <action>" << std::endl;
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::Run(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
ShowHelp();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
std::string command;
|
||||
std::vector<std::string> command_args;
|
||||
|
||||
if (argc >= 3) {
|
||||
command = std::string(argv[1]) + " " + std::string(argv[2]);
|
||||
for (int i = 3; i < argc; ++i) {
|
||||
command_args.push_back(argv[i]);
|
||||
}
|
||||
} else {
|
||||
command = argv[1];
|
||||
}
|
||||
|
||||
auto it = commands_.find(command);
|
||||
if (it == commands_.end()) {
|
||||
ShowHelp();
|
||||
return absl::NotFoundError(absl::StrCat("Unknown command: ", command));
|
||||
}
|
||||
|
||||
return it->second.handler(command_args);
|
||||
}
|
||||
|
||||
CommandHandler* ModernCLI::GetCommandHandler(const std::string& name) {
|
||||
// This is not ideal, but it will work for now.
|
||||
if (name == "patch apply-asar") {
|
||||
static AsarPatch handler;
|
||||
return &handler;
|
||||
}
|
||||
if (name == "palette") {
|
||||
static Palette handler;
|
||||
return &handler;
|
||||
}
|
||||
if (name == "command-palette") {
|
||||
static CommandPalette handler;
|
||||
return &handler;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleAsarPatchCommand(const std::vector<std::string>& args) {
|
||||
AsarPatch handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleBpsPatchCommand(const std::vector<std::string>& args) {
|
||||
ApplyPatch handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleRomInfoCommand(const std::vector<std::string>& args) {
|
||||
Open handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleExtractSymbolsCommand(const std::vector<std::string>& args) {
|
||||
// Use the AsarWrapper to extract symbols
|
||||
yaze::app::core::AsarWrapper wrapper;
|
||||
RETURN_IF_ERROR(wrapper.Initialize());
|
||||
|
||||
auto symbols_result = wrapper.ExtractSymbols(args[0]);
|
||||
if (!symbols_result.ok()) {
|
||||
return symbols_result.status();
|
||||
}
|
||||
|
||||
const auto& symbols = symbols_result.value();
|
||||
std::cout << "🏷️ Extracted " << symbols.size() << " symbols from " << args[0] << ":" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
for (const auto& symbol : symbols) {
|
||||
std::cout << absl::StrFormat(" %-20s @ $%06X", symbol.name, symbol.address) << std::endl;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleAgentCommand(const std::vector<std::string>& args) {
|
||||
Agent handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleProjectBuildCommand(const std::vector<std::string>& args) {
|
||||
ProjectBuild handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleProjectInitCommand(const std::vector<std::string>& args) {
|
||||
ProjectInit handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleRomGenerateGoldenCommand(const std::vector<std::string>& args) {
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (!args.empty()) {
|
||||
rom_file = args[0];
|
||||
}
|
||||
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file required (use --rom=<file> or provide as argument)");
|
||||
}
|
||||
|
||||
Open handler;
|
||||
return handler.Run({rom_file});
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleDungeonExportCommand(const std::vector<std::string>& args) {
|
||||
DungeonExport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleDungeonListObjectsCommand(const std::vector<std::string>& args) {
|
||||
DungeonListObjects handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleGfxExportCommand(const std::vector<std::string>& args) {
|
||||
GfxExport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleGfxImportCommand(const std::vector<std::string>& args) {
|
||||
GfxImport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleCommandPaletteCommand(const std::vector<std::string>& args) {
|
||||
CommandPalette handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandlePaletteExportCommand(const std::vector<std::string>& args) {
|
||||
PaletteExport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandlePaletteCommand(const std::vector<std::string>& args) {
|
||||
Palette handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandlePaletteImportCommand(const std::vector<std::string>& args) {
|
||||
PaletteImport handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleRomDiffCommand(const std::vector<std::string>& args) {
|
||||
RomDiff handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleRomValidateCommand(const std::vector<std::string>& args) {
|
||||
RomValidate handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleOverworldGetTileCommand(const std::vector<std::string>& args) {
|
||||
OverworldGetTile handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleOverworldSetTileCommand(const std::vector<std::string>& args) {
|
||||
OverworldSetTile handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
absl::Status ModernCLI::HandleSpriteCreateCommand(const std::vector<std::string>& args) {
|
||||
SpriteCreate handler;
|
||||
return handler.Run(args);
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
61
src/cli/modern_cli.h
Normal file
61
src/cli/modern_cli.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef YAZE_SRC_CLI_MODERN_CLI_H_
|
||||
#define YAZE_SRC_CLI_MODERN_CLI_H_
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "cli/z3ed.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
struct CommandInfo {
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::string usage;
|
||||
std::function<absl::Status(const std::vector<std::string>&)> handler;
|
||||
};
|
||||
|
||||
class ModernCLI {
|
||||
public:
|
||||
ModernCLI();
|
||||
absl::Status Run(int argc, char* argv[]);
|
||||
CommandHandler* GetCommandHandler(const std::string& name);
|
||||
|
||||
std::map<std::string, CommandInfo> commands_;
|
||||
|
||||
private:
|
||||
void SetupCommands();
|
||||
void ShowHelp();
|
||||
|
||||
// Command Handlers
|
||||
absl::Status HandleAsarPatchCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleBpsPatchCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleExtractSymbolsCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleAgentCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleProjectBuildCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleProjectInitCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleRomInfoCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleRomGenerateGoldenCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleRomDiffCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleDungeonExportCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleDungeonListObjectsCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleGfxExportCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleGfxImportCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleCommandPaletteCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandlePaletteExportCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandlePaletteImportCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandlePaletteCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleRomValidateCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleOverworldGetTileCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleOverworldSetTileCommand(const std::vector<std::string>& args);
|
||||
absl::Status HandleSpriteCreateCommand(const std::vector<std::string>& args);
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_SRC_CLI_MODERN_CLI_H_
|
||||
23
src/cli/service/ai_service.cc
Normal file
23
src/cli/service/ai_service.cc
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "cli/service/ai_service.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
absl::StatusOr<std::vector<std::string>> MockAIService::GetCommands(
|
||||
const std::string& prompt) {
|
||||
if (prompt == "Make all the soldiers in Hyrule Castle wear red armor.") {
|
||||
return std::vector<std::string>{
|
||||
"palette export --group sprites_aux1 --id 4 --to soldier_palette.col",
|
||||
"palette set-color --file soldier_palette.col --index 5 --color \"#FF0000\"",
|
||||
"palette import --group sprites_aux1 --id 4 --from soldier_palette.col"};
|
||||
} else if (prompt.find("Place a tree at coordinates") != std::string::npos) {
|
||||
// Example prompt: "Place a tree at coordinates (10, 20) on the light world map"
|
||||
// For simplicity, we'll hardcode the tile id for a tree
|
||||
return std::vector<std::string>{"overworld set-tile --map 0 --x 10 --y 20 --tile 0x02E"};
|
||||
}
|
||||
return absl::UnimplementedError("Prompt not supported by mock AI service.");
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
28
src/cli/service/ai_service.h
Normal file
28
src/cli/service/ai_service.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef YAZE_SRC_CLI_AI_SERVICE_H_
|
||||
#define YAZE_SRC_CLI_AI_SERVICE_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/statusor.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
class AIService {
|
||||
public:
|
||||
virtual ~AIService() = default;
|
||||
virtual absl::StatusOr<std::vector<std::string>> GetCommands(
|
||||
const std::string& prompt) = 0;
|
||||
};
|
||||
|
||||
class MockAIService : public AIService {
|
||||
public:
|
||||
absl::StatusOr<std::vector<std::string>> GetCommands(
|
||||
const std::string& prompt) override;
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_SRC_CLI_AI_SERVICE_H_
|
||||
87
src/cli/service/gemini_ai_service.cc
Normal file
87
src/cli/service/gemini_ai_service.cc
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "cli/service/gemini_ai_service.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
#ifdef YAZE_WITH_JSON
|
||||
#include "incl/httplib.h"
|
||||
#include "third_party/json/src/json.hpp"
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
GeminiAIService::GeminiAIService(const std::string& api_key) : api_key_(api_key) {}
|
||||
|
||||
absl::StatusOr<std::vector<std::string>> GeminiAIService::GetCommands(
|
||||
const std::string& prompt) {
|
||||
#ifndef YAZE_WITH_JSON
|
||||
return absl::UnimplementedError(
|
||||
"Gemini AI service requires JSON support. Build with -DYAZE_WITH_JSON=ON");
|
||||
#else
|
||||
if (api_key_.empty()) {
|
||||
return absl::FailedPreconditionError("GEMINI_API_KEY not set.");
|
||||
}
|
||||
|
||||
httplib::Client cli("https://generativelanguage.googleapis.com");
|
||||
nlohmann::json request_body = {
|
||||
{"contents",
|
||||
{{"parts",
|
||||
{{"text",
|
||||
"You are an expert ROM hacker for The Legend of Zelda: A Link to the Past. "
|
||||
"Your task is to generate a sequence of `z3ed` CLI commands to achieve the user's request. "
|
||||
"Respond only with a JSON array of strings, where each string is a `z3ed` command. "
|
||||
"Do not include any other text or explanation. "
|
||||
"Available commands: "
|
||||
"palette export --group <group> --id <id> --to <file>, "
|
||||
"palette import --group <group> --id <id> --from <file>, "
|
||||
"palette set-color --file <file> --index <index> --color <hex_color>, "
|
||||
"overworld set-tile --map <map_id> --x <x> --y <y> --tile <tile_id>. "
|
||||
"User request: " + prompt}}}}}
|
||||
};
|
||||
|
||||
httplib::Headers headers = {
|
||||
{"Content-Type", "application/json"},
|
||||
{"x-goog-api-key", api_key_},
|
||||
};
|
||||
|
||||
auto res = cli.Post("/v1beta/models/gemini-pro:generateContent", headers, request_body.dump(), "application/json");
|
||||
|
||||
if (!res) {
|
||||
return absl::InternalError("Failed to connect to Gemini API.");
|
||||
}
|
||||
|
||||
if (res->status != 200) {
|
||||
return absl::InternalError(absl::StrCat("Gemini API error: ", res->status, " ", res->body));
|
||||
}
|
||||
|
||||
nlohmann::json response_json = nlohmann::json::parse(res->body);
|
||||
std::vector<std::string> commands;
|
||||
|
||||
try {
|
||||
for (const auto& candidate : response_json["candidates"]) {
|
||||
for (const auto& part : candidate["content"]["parts"]) {
|
||||
std::string text_content = part["text"];
|
||||
// Assuming the AI returns a JSON array of strings directly in the text content
|
||||
// This might need more robust parsing depending on actual AI output format
|
||||
nlohmann::json commands_array = nlohmann::json::parse(text_content);
|
||||
if (commands_array.is_array()) {
|
||||
for (const auto& cmd : commands_array) {
|
||||
if (cmd.is_string()) {
|
||||
commands.push_back(cmd.get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const nlohmann::json::exception& e) {
|
||||
return absl::InternalError(absl::StrCat("Failed to parse Gemini API response: ", e.what()));
|
||||
}
|
||||
|
||||
return commands;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
27
src/cli/service/gemini_ai_service.h
Normal file
27
src/cli/service/gemini_ai_service.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef YAZE_SRC_CLI_GEMINI_AI_SERVICE_H_
|
||||
#define YAZE_SRC_CLI_GEMINI_AI_SERVICE_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "cli/service/ai_service.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
class GeminiAIService : public AIService {
|
||||
public:
|
||||
explicit GeminiAIService(const std::string& api_key);
|
||||
absl::StatusOr<std::vector<std::string>> GetCommands(
|
||||
const std::string& prompt) override;
|
||||
|
||||
private:
|
||||
std::string api_key_;
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_SRC_CLI_GEMINI_AI_SERVICE_H_
|
||||
351
src/cli/tui.cc
351
src/cli/tui.cc
@@ -7,10 +7,12 @@
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "util/bps.h"
|
||||
#include "app/core/platform/file_dialog.h"
|
||||
#include "app/core/asar_wrapper.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
#include "cli/modern_cli.h"
|
||||
#include "cli/tui/command_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
@@ -219,131 +221,65 @@ void GenerateSaveFileComponent(ftxui::ScreenInteractive &screen) {
|
||||
screen.Loop(renderer);
|
||||
}
|
||||
|
||||
void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen) {
|
||||
static std::string patch_file;
|
||||
static std::string output_message;
|
||||
static std::vector<std::string> symbols_list;
|
||||
static bool show_symbols = false;
|
||||
|
||||
auto patch_file_input = Input(&patch_file, "Assembly patch file (.asm)");
|
||||
|
||||
void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen) {
|
||||
ReturnIfRomNotLoaded(screen);
|
||||
|
||||
static std::string asm_file;
|
||||
static std::string output_message;
|
||||
static Color output_color = Color::White;
|
||||
|
||||
auto asm_file_input = Input(&asm_file, "Assembly file (.asm)");
|
||||
|
||||
auto apply_button = Button("Apply Asar Patch", [&] {
|
||||
if (patch_file.empty()) {
|
||||
app_context.error_message = "Please specify an assembly patch file";
|
||||
SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!app_context.rom.is_loaded()) {
|
||||
app_context.error_message = "No ROM loaded. Please load a ROM first.";
|
||||
if (asm_file.empty()) {
|
||||
app_context.error_message = "Please specify an assembly file";
|
||||
SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
app::core::AsarWrapper wrapper;
|
||||
auto init_status = wrapper.Initialize();
|
||||
if (!init_status.ok()) {
|
||||
app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
|
||||
SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
// Use the command handler directly
|
||||
AsarPatch handler;
|
||||
auto status = handler.Run({asm_file});
|
||||
if (status.ok()) {
|
||||
output_message = "✅ Asar patch applied successfully!";
|
||||
output_color = Color::Green;
|
||||
} else {
|
||||
output_message = absl::StrCat("❌ Patch failed:\n", status.message());
|
||||
output_color = Color::Red;
|
||||
}
|
||||
|
||||
auto rom_data = app_context.rom.vector();
|
||||
auto patch_result = wrapper.ApplyPatch(patch_file, rom_data);
|
||||
|
||||
if (!patch_result.ok()) {
|
||||
app_context.error_message = absl::StrCat("Patch failed: ", patch_result.status().message());
|
||||
SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
if (!result.success) {
|
||||
app_context.error_message = absl::StrCat("Patch failed: ", absl::StrJoin(result.errors, "; "));
|
||||
SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update ROM with patched data
|
||||
// Note: ROM update would need proper implementation
|
||||
// For now, just indicate success
|
||||
|
||||
// Prepare success message
|
||||
output_message = absl::StrFormat(
|
||||
"✅ Patch applied successfully!\n"
|
||||
"📊 ROM size: %d bytes\n"
|
||||
"🏷️ Symbols found: %d",
|
||||
result.rom_size, result.symbols.size());
|
||||
|
||||
// Prepare symbols list
|
||||
symbols_list.clear();
|
||||
for (const auto& symbol : result.symbols) {
|
||||
symbols_list.push_back(absl::StrFormat("%-20s @ $%06X",
|
||||
symbol.name, symbol.address));
|
||||
}
|
||||
show_symbols = !symbols_list.empty();
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
app_context.error_message = "Exception: " + std::string(e.what());
|
||||
SwitchComponents(screen, LayoutID::kError);
|
||||
output_message = "Exception: " + std::string(e.what());
|
||||
output_color = Color::Red;
|
||||
}
|
||||
});
|
||||
|
||||
auto show_symbols_button = Button("Show Symbols", [&] {
|
||||
show_symbols = !show_symbols;
|
||||
});
|
||||
|
||||
auto back_button = Button("Back to Main Menu", [&] {
|
||||
output_message.clear();
|
||||
symbols_list.clear();
|
||||
show_symbols = false;
|
||||
SwitchComponents(screen, LayoutID::kMainMenu);
|
||||
});
|
||||
|
||||
std::vector<Component> container_items = {
|
||||
patch_file_input,
|
||||
auto container = Container::Vertical({
|
||||
asm_file_input,
|
||||
apply_button,
|
||||
};
|
||||
|
||||
if (!output_message.empty()) {
|
||||
container_items.push_back(show_symbols_button);
|
||||
}
|
||||
container_items.push_back(back_button);
|
||||
|
||||
auto container = Container::Vertical(container_items);
|
||||
back_button,
|
||||
});
|
||||
|
||||
auto renderer = Renderer(container, [&] {
|
||||
std::vector<Element> elements = {
|
||||
text("Apply Asar Assembly Patch") | center | bold,
|
||||
text("Apply Asar Patch") | center | bold,
|
||||
separator(),
|
||||
text("Assembly Patch File:"),
|
||||
patch_file_input->Render(),
|
||||
text("Assembly File:"),
|
||||
asm_file_input->Render(),
|
||||
separator(),
|
||||
apply_button->Render() | center,
|
||||
};
|
||||
|
||||
if (!output_message.empty()) {
|
||||
elements.push_back(separator());
|
||||
elements.push_back(text(output_message) | color(Color::Green));
|
||||
elements.push_back(show_symbols_button->Render() | center);
|
||||
|
||||
if (show_symbols && !symbols_list.empty()) {
|
||||
elements.push_back(separator());
|
||||
elements.push_back(text("Extracted Symbols:") | bold);
|
||||
|
||||
// Show symbols in a scrollable area
|
||||
std::vector<Element> symbol_elements;
|
||||
for (size_t i = 0; i < std::min(symbols_list.size(), size_t(10)); ++i) {
|
||||
symbol_elements.push_back(text(symbols_list[i]) | color(Color::Cyan));
|
||||
}
|
||||
if (symbols_list.size() > 10) {
|
||||
symbol_elements.push_back(text(absl::StrFormat("... and %d more",
|
||||
symbols_list.size() - 10)) |
|
||||
color(Color::Yellow));
|
||||
}
|
||||
elements.push_back(vbox(symbol_elements) | frame);
|
||||
}
|
||||
elements.push_back(text(output_message) | color(output_color));
|
||||
}
|
||||
|
||||
elements.push_back(separator());
|
||||
@@ -355,6 +291,26 @@ void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen) {
|
||||
screen.Loop(renderer);
|
||||
}
|
||||
|
||||
void PaletteEditorComponent(ftxui::ScreenInteractive &screen) {
|
||||
ReturnIfRomNotLoaded(screen);
|
||||
|
||||
auto back_button = Button("Back to Main Menu", [&] {
|
||||
SwitchComponents(screen, LayoutID::kMainMenu);
|
||||
});
|
||||
|
||||
auto renderer = Renderer(back_button, [&] {
|
||||
return vbox({
|
||||
text("Palette Editor") | center | bold,
|
||||
separator(),
|
||||
text("Palette editing functionality coming soon...") | center,
|
||||
separator(),
|
||||
back_button->Render() | center,
|
||||
}) | center | border;
|
||||
});
|
||||
|
||||
screen.Loop(renderer);
|
||||
}
|
||||
|
||||
void ExtractSymbolsComponent(ftxui::ScreenInteractive &screen) {
|
||||
static std::string asm_file;
|
||||
static std::vector<std::string> symbols_list;
|
||||
@@ -576,128 +532,7 @@ Element ColorBox(const Color &color) {
|
||||
return ftxui::text(" ") | ftxui::bgcolor(color);
|
||||
}
|
||||
|
||||
void PaletteEditorComponent(ftxui::ScreenInteractive &screen) {
|
||||
ReturnIfRomNotLoaded(screen);
|
||||
|
||||
auto back_button =
|
||||
Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
|
||||
|
||||
static auto palette_groups = app_context.rom.palette_group();
|
||||
static std::vector<gfx::PaletteGroup> ftx_palettes = {
|
||||
palette_groups.swords,
|
||||
palette_groups.shields,
|
||||
palette_groups.armors,
|
||||
palette_groups.overworld_main,
|
||||
palette_groups.overworld_aux,
|
||||
palette_groups.global_sprites,
|
||||
palette_groups.sprites_aux1,
|
||||
palette_groups.sprites_aux2,
|
||||
palette_groups.sprites_aux3,
|
||||
palette_groups.dungeon_main,
|
||||
palette_groups.overworld_mini_map,
|
||||
palette_groups.grass,
|
||||
palette_groups.object_3d,
|
||||
};
|
||||
|
||||
// Create a list of palette groups to pick from
|
||||
static int selected_palette_group = 0;
|
||||
static std::vector<std::string> palette_group_names;
|
||||
if (palette_group_names.empty()) {
|
||||
for (size_t i = 0; i < 14; ++i) {
|
||||
palette_group_names.push_back(gfx::kPaletteCategoryNames[i].data());
|
||||
}
|
||||
}
|
||||
|
||||
static bool show_palette_editor = false;
|
||||
static std::vector<std::vector<Element>> palette_elements;
|
||||
|
||||
const auto load_palettes_from_current_group = [&]() {
|
||||
auto palette_group = ftx_palettes[selected_palette_group];
|
||||
palette_elements.clear();
|
||||
// Create a list of colors to display in the palette editor.
|
||||
for (size_t i = 0; i < palette_group.size(); ++i) {
|
||||
palette_elements.push_back(std::vector<Element>());
|
||||
for (size_t j = 0; j < palette_group[i].size(); ++j) {
|
||||
auto color = palette_group[i][j];
|
||||
palette_elements[i].push_back(
|
||||
ColorBox(Color::RGB(color.rgb().x, color.rgb().y, color.rgb().z)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (show_palette_editor) {
|
||||
if (palette_elements.empty()) {
|
||||
load_palettes_from_current_group();
|
||||
}
|
||||
|
||||
auto palette_grid = Container::Vertical({});
|
||||
for (const auto &element : palette_elements) {
|
||||
auto row = Container::Horizontal({});
|
||||
for (const auto &color : element) {
|
||||
row->Add(Renderer([color] { return color; }));
|
||||
}
|
||||
palette_grid->Add(row);
|
||||
}
|
||||
|
||||
// Create a button to save the changes to the palette.
|
||||
auto save_button = Button("Save Changes", [&] {
|
||||
// Save the changes to the palette here.
|
||||
// You can use the current_palette vector to determine the new colors.
|
||||
// After saving the changes, you could either stay here or return to the
|
||||
// main menu.
|
||||
});
|
||||
|
||||
auto back_button = Button("Back", [&] {
|
||||
show_palette_editor = false;
|
||||
screen.ExitLoopClosure()();
|
||||
});
|
||||
|
||||
auto palette_editor_container = Container::Vertical({
|
||||
palette_grid,
|
||||
save_button,
|
||||
back_button,
|
||||
});
|
||||
|
||||
auto palette_editor_renderer = Renderer(palette_editor_container, [&] {
|
||||
return vbox({text(gfx::kPaletteCategoryNames[selected_palette_group]
|
||||
.data()) |
|
||||
center,
|
||||
separator(), palette_grid->Render(), separator(),
|
||||
hbox({
|
||||
save_button->Render() | center,
|
||||
separator(),
|
||||
back_button->Render() | center,
|
||||
}) | center}) |
|
||||
center;
|
||||
});
|
||||
screen.Loop(palette_editor_renderer);
|
||||
} else {
|
||||
auto palette_list = Menu(&palette_group_names, &selected_palette_group);
|
||||
palette_list = CatchEvent(palette_list, [&](Event event) {
|
||||
if (event == Event::Return) {
|
||||
// Load the selected palette group into the palette editor.
|
||||
// This will be a separate component.
|
||||
show_palette_editor = true;
|
||||
screen.ExitLoopClosure()();
|
||||
load_palettes_from_current_group();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
auto container = Container::Vertical({
|
||||
palette_list,
|
||||
back_button,
|
||||
});
|
||||
auto renderer = Renderer(container, [&] {
|
||||
return vbox({text("Palette Editor") | center, separator(),
|
||||
palette_list->Render(), separator(),
|
||||
back_button->Render() | center}) |
|
||||
center;
|
||||
});
|
||||
screen.Loop(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
void HelpComponent(ftxui::ScreenInteractive &screen) {
|
||||
auto help_text = vbox({
|
||||
@@ -846,6 +681,72 @@ void HelpComponent(ftxui::ScreenInteractive &screen) {
|
||||
screen.Loop(renderer);
|
||||
}
|
||||
|
||||
void HexViewerComponent(ftxui::ScreenInteractive &screen) {
|
||||
ReturnIfRomNotLoaded(screen);
|
||||
|
||||
auto back_button =
|
||||
Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
|
||||
|
||||
static int offset = 0;
|
||||
const int lines_to_show = 20;
|
||||
|
||||
auto renderer = Renderer(back_button, [&] {
|
||||
std::vector<Element> rows;
|
||||
for (int i = 0; i < lines_to_show; ++i) {
|
||||
int current_offset = offset + (i * 16);
|
||||
if (current_offset >= static_cast<int>(app_context.rom.size())) {
|
||||
break;
|
||||
}
|
||||
|
||||
Elements row;
|
||||
row.push_back(text(absl::StrFormat("0x%08X: ", current_offset)) | color(Color::Yellow));
|
||||
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
if (current_offset + j < static_cast<int>(app_context.rom.size())) {
|
||||
row.push_back(text(absl::StrFormat("%02X ", app_context.rom.vector()[current_offset + j])));
|
||||
} else {
|
||||
row.push_back(text(" "));
|
||||
}
|
||||
}
|
||||
|
||||
row.push_back(separator());
|
||||
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
if (current_offset + j < static_cast<int>(app_context.rom.size())) {
|
||||
char c = app_context.rom.vector()[current_offset + j];
|
||||
row.push_back(text(std::isprint(c) ? std::string(1, c) : "."));
|
||||
} else {
|
||||
row.push_back(text(" "));
|
||||
}
|
||||
}
|
||||
|
||||
rows.push_back(hbox(row));
|
||||
}
|
||||
|
||||
return vbox({
|
||||
text("Hex Viewer") | center | bold,
|
||||
separator(),
|
||||
vbox(rows) | frame | flex,
|
||||
separator(),
|
||||
text(absl::StrFormat("Offset: 0x%08X", offset)),
|
||||
separator(),
|
||||
back_button->Render() | center,
|
||||
}) | border;
|
||||
});
|
||||
|
||||
auto event_handler = CatchEvent(renderer, [&](Event event) {
|
||||
if (event == Event::ArrowUp) offset = std::max(0, offset - 16);
|
||||
if (event == Event::ArrowDown) offset = std::min(static_cast<int>(app_context.rom.size()) - (lines_to_show * 16), offset + 16);
|
||||
if (event == Event::PageUp) offset = std::max(0, offset - (lines_to_show * 16));
|
||||
if (event == Event::PageDown) offset = std::min(static_cast<int>(app_context.rom.size()) - (lines_to_show * 16), offset + (lines_to_show * 16));
|
||||
if (event == Event::Character('q')) SwitchComponents(screen, LayoutID::kExit);
|
||||
if (event == Event::Return) SwitchComponents(screen, LayoutID::kMainMenu);
|
||||
return false;
|
||||
});
|
||||
|
||||
screen.Loop(event_handler);
|
||||
}
|
||||
|
||||
void MainMenuComponent(ftxui::ScreenInteractive &screen) {
|
||||
// Tracks which menu item is selected.
|
||||
static int selected = 0;
|
||||
@@ -902,6 +803,12 @@ void MainMenuComponent(ftxui::ScreenInteractive &screen) {
|
||||
case MainMenuEntry::kPaletteEditor:
|
||||
SwitchComponents(screen, LayoutID::kPaletteEditor);
|
||||
return true;
|
||||
case MainMenuEntry::kHexViewer:
|
||||
SwitchComponents(screen, LayoutID::kHexViewer);
|
||||
return true;
|
||||
case MainMenuEntry::kCommandPalette:
|
||||
SwitchComponents(screen, LayoutID::kCommandPalette);
|
||||
return true;
|
||||
case MainMenuEntry::kHelp:
|
||||
SwitchComponents(screen, LayoutID::kHelp);
|
||||
return true;
|
||||
@@ -951,6 +858,14 @@ void ShowMain() {
|
||||
case LayoutID::kPaletteEditor: {
|
||||
PaletteEditorComponent(screen);
|
||||
} break;
|
||||
case LayoutID::kHexViewer: {
|
||||
HexViewerComponent(screen);
|
||||
} break;
|
||||
case LayoutID::kCommandPalette: {
|
||||
CommandPaletteComponent component;
|
||||
auto cmd_component = component.Render();
|
||||
screen.Loop(cmd_component);
|
||||
} break;
|
||||
case LayoutID::kHelp: {
|
||||
HelpComponent(screen);
|
||||
} break;
|
||||
|
||||
@@ -23,6 +23,8 @@ const std::vector<std::string> kMainMenuEntries = {
|
||||
"Validate Assembly",
|
||||
"Generate Save File",
|
||||
"Palette Editor",
|
||||
"Hex Viewer",
|
||||
"Command Palette",
|
||||
"Help",
|
||||
"Exit",
|
||||
};
|
||||
@@ -35,6 +37,8 @@ enum class MainMenuEntry {
|
||||
kValidateAssembly,
|
||||
kGenerateSaveFile,
|
||||
kPaletteEditor,
|
||||
kHexViewer,
|
||||
kCommandPalette,
|
||||
kHelp,
|
||||
kExit,
|
||||
};
|
||||
@@ -47,6 +51,8 @@ enum class LayoutID {
|
||||
kValidateAssembly,
|
||||
kGenerateSaveFile,
|
||||
kPaletteEditor,
|
||||
kHexViewer,
|
||||
kCommandPalette,
|
||||
kHelp,
|
||||
kExit,
|
||||
kMainMenu,
|
||||
|
||||
106
src/cli/tui/asar_patch.cc
Normal file
106
src/cli/tui/asar_patch.cc
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "cli/tui/asar_patch.h"
|
||||
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/screen_interactive.hpp>
|
||||
|
||||
#include "cli/tui.h"
|
||||
#include "app/core/asar_wrapper.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
ftxui::Component AsarPatchComponent::Render() {
|
||||
static std::string patch_file;
|
||||
static std::string output_message;
|
||||
static std::vector<std::string> symbols_list;
|
||||
static bool show_symbols = false;
|
||||
|
||||
auto patch_file_input = Input(&patch_file, "Assembly patch file (.asm)");
|
||||
|
||||
auto apply_button = Button("Apply Asar Patch", [&] {
|
||||
if (patch_file.empty()) {
|
||||
app_context.error_message = "Please specify an assembly patch file";
|
||||
//SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!app_context.rom.is_loaded()) {
|
||||
app_context.error_message = "No ROM loaded. Please load a ROM first.";
|
||||
//SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
app::core::AsarWrapper wrapper;
|
||||
auto init_status = wrapper.Initialize();
|
||||
if (!init_status.ok()) {
|
||||
app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message());
|
||||
//SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
auto rom_data = app_context.rom.vector();
|
||||
auto patch_result = wrapper.ApplyPatch(patch_file, rom_data);
|
||||
|
||||
if (!patch_result.ok()) {
|
||||
app_context.error_message = absl::StrCat("Patch failed: ", patch_result.status().message());
|
||||
//SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
if (!result.success) {
|
||||
app_context.error_message = absl::StrCat("Patch failed: ", absl::StrJoin(result.errors, "; "));
|
||||
//SwitchComponents(screen, LayoutID::kError);
|
||||
return;
|
||||
}
|
||||
|
||||
output_message = absl::StrFormat(
|
||||
"✅ Patch applied successfully!\n"
|
||||
"📊 ROM size: %d bytes\n"
|
||||
"🏷️ Symbols found: %d",
|
||||
result.rom_size, result.symbols.size());
|
||||
|
||||
symbols_list.clear();
|
||||
for (const auto& symbol : result.symbols) {
|
||||
symbols_list.push_back(absl::StrFormat("% -20s @ $%06X",
|
||||
symbol.name, symbol.address));
|
||||
}
|
||||
show_symbols = !symbols_list.empty();
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
app_context.error_message = "Exception: " + std::string(e.what());
|
||||
//SwitchComponents(screen, LayoutID::kError);
|
||||
}
|
||||
});
|
||||
|
||||
auto show_symbols_button = Button("Show Symbols", [&] {
|
||||
show_symbols = !show_symbols;
|
||||
});
|
||||
|
||||
auto back_button = Button("Back to Main Menu", [&] {
|
||||
output_message.clear();
|
||||
symbols_list.clear();
|
||||
show_symbols = false;
|
||||
//SwitchComponents(screen, LayoutID::kMainMenu);
|
||||
});
|
||||
|
||||
std::vector<Component> container_items = {
|
||||
patch_file_input,
|
||||
apply_button,
|
||||
};
|
||||
|
||||
if (!output_message.empty()) {
|
||||
container_items.push_back(show_symbols_button);
|
||||
}
|
||||
container_items.push_back(back_button);
|
||||
|
||||
return Container::Vertical(container_items);
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
17
src/cli/tui/asar_patch.h
Normal file
17
src/cli/tui/asar_patch.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef YAZE_SRC_CLI_TUI_ASAR_PATCH_H_
|
||||
#define YAZE_SRC_CLI_TUI_ASAR_PATCH_H_
|
||||
|
||||
#include "cli/tui/tui_component.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
class AsarPatchComponent : public TuiComponent {
|
||||
public:
|
||||
ftxui::Component Render() override;
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_SRC_CLI_TUI_ASAR_PATCH_H_
|
||||
63
src/cli/tui/command_palette.cc
Normal file
63
src/cli/tui/command_palette.cc
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "cli/tui/command_palette.h"
|
||||
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/screen_interactive.hpp>
|
||||
|
||||
#include "cli/tui.h"
|
||||
#include "cli/modern_cli.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
ftxui::Component CommandPaletteComponent::Render() {
|
||||
static std::string input;
|
||||
static std::vector<std::string> commands;
|
||||
if (commands.empty()) {
|
||||
ModernCLI cli;
|
||||
for (const auto& [name, info] : cli.commands_) {
|
||||
commands.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
auto input_component = Input(&input, "");
|
||||
|
||||
auto renderer = Renderer(input_component, [&] {
|
||||
std::vector<std::string> filtered_commands;
|
||||
for (const auto& cmd : commands) {
|
||||
if (cmd.find(input) != std::string::npos) {
|
||||
filtered_commands.push_back(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
Elements command_elements;
|
||||
for (const auto& cmd : filtered_commands) {
|
||||
command_elements.push_back(text(cmd));
|
||||
}
|
||||
|
||||
return vbox({
|
||||
text("Command Palette") | center | bold,
|
||||
separator(),
|
||||
input_component->Render(),
|
||||
separator(),
|
||||
vbox(command_elements) | frame | flex,
|
||||
}) | border;
|
||||
});
|
||||
|
||||
auto event_handler = CatchEvent(renderer, [&](Event event) {
|
||||
if (event == Event::Return) {
|
||||
// TODO: Execute the command
|
||||
//SwitchComponents(screen, LayoutID::kMainMenu);
|
||||
}
|
||||
if (event == Event::Escape) {
|
||||
//SwitchComponents(screen, LayoutID::kMainMenu);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return event_handler;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
17
src/cli/tui/command_palette.h
Normal file
17
src/cli/tui/command_palette.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef YAZE_SRC_CLI_TUI_COMMAND_PALETTE_H_
|
||||
#define YAZE_SRC_CLI_TUI_COMMAND_PALETTE_H_
|
||||
|
||||
#include "cli/tui/tui_component.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
class CommandPaletteComponent : public TuiComponent {
|
||||
public:
|
||||
ftxui::Component Render() override;
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_SRC_CLI_TUI_COMMAND_PALETTE_H_
|
||||
122
src/cli/tui/palette_editor.cc
Normal file
122
src/cli/tui/palette_editor.cc
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "cli/tui/palette_editor.h"
|
||||
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/screen_interactive.hpp>
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
#include <ftxui/screen/screen.hpp>
|
||||
#include "cli/tui.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
ftxui::Component PaletteEditorComponent::Render() {
|
||||
// static auto palette_groups = app_context.rom.palette_group();
|
||||
// static std::vector<gfx::PaletteGroup> ftx_palettes = {
|
||||
// palette_groups.swords,
|
||||
// palette_groups.shields,
|
||||
// palette_groups.armors,
|
||||
// palette_groups.overworld_main,
|
||||
// palette_groups.overworld_aux,
|
||||
// palette_groups.global_sprites,
|
||||
// palette_groups.sprites_aux1,
|
||||
// palette_groups.sprites_aux2,
|
||||
// palette_groups.sprites_aux3,
|
||||
// palette_groups.dungeon_main,
|
||||
// palette_groups.overworld_mini_map,
|
||||
// palette_groups.grass,
|
||||
// palette_groups.object_3d,
|
||||
// };
|
||||
|
||||
static int selected_palette_group = 0;
|
||||
static int selected_palette = 0;
|
||||
static int selected_color = 0;
|
||||
static std::string r_str, g_str, b_str;
|
||||
|
||||
static std::vector<std::string> palette_group_names;
|
||||
if (palette_group_names.empty()) {
|
||||
for (size_t i = 0; i < 14; ++i) {
|
||||
palette_group_names.push_back(gfx::kPaletteCategoryNames[i].data());
|
||||
}
|
||||
}
|
||||
|
||||
auto palette_group_menu = Menu(&palette_group_names, &selected_palette_group);
|
||||
|
||||
// auto save_button = Button("Save", [&] {
|
||||
// auto& color = ftx_palettes[selected_palette_group][selected_palette][selected_color];
|
||||
// color.set_r(std::stoi(r_str));
|
||||
// color.set_g(std::stoi(g_str));
|
||||
// color.set_b(std::stoi(b_str));
|
||||
// // TODO: Implement saving the modified palette to the ROM
|
||||
// });
|
||||
|
||||
// auto back_button = Button("Back", [&] {
|
||||
// // TODO: This needs to be handled by the main TUI loop
|
||||
// });
|
||||
|
||||
// auto component = Container::Vertical({
|
||||
// palette_group_menu,
|
||||
// save_button,
|
||||
// back_button,
|
||||
// });
|
||||
|
||||
// auto renderer = Renderer(component, [&] {
|
||||
// auto& current_palette_group = ftx_palettes[selected_palette_group];
|
||||
// std::vector<std::string> palette_names;
|
||||
// for (size_t i = 0; i < current_palette_group.size(); ++i) {
|
||||
// palette_names.push_back(absl::StrFormat("Palette %d", i));
|
||||
// }
|
||||
// auto palette_menu = Menu(&palette_names, &selected_palette);
|
||||
|
||||
// auto& current_palette = current_palette_group[selected_palette];
|
||||
// std::vector<Elements> color_boxes;
|
||||
// for (int i = 0; i < current_palette.size(); ++i) {
|
||||
// auto& color = current_palette[i];
|
||||
// Element element = text(" ") | bgcolor(Color::RGB(color.rgb().x, color.rgb().y, color.rgb().z));
|
||||
// if (i == selected_color) {
|
||||
// element = element | border;
|
||||
// }
|
||||
// color_boxes.push_back(element);
|
||||
// }
|
||||
|
||||
// auto color_grid = Wrap("color_grid", color_boxes);
|
||||
|
||||
// r_str = std::to_string(current_palette[selected_color].rgb().x);
|
||||
// g_str = std::to_string(current_palette[selected_color].rgb().y);
|
||||
// b_str = std::to_string(current_palette[selected_color].rgb().z);
|
||||
|
||||
// auto selected_color_view = vbox({
|
||||
// text("Selected Color") | bold,
|
||||
// separator(),
|
||||
// hbox({text("R: "), Input(&r_str, "")}),
|
||||
// hbox({text("G: "), Input(&g_str, "")}),
|
||||
// hbox({text("B: "), Input(&b_str, "")}),
|
||||
// save_button->Render(),
|
||||
// });
|
||||
|
||||
// return vbox({
|
||||
// text("Palette Editor") | center | bold,
|
||||
// separator(),
|
||||
// hbox({
|
||||
// palette_group_menu->Render() | frame,
|
||||
// separator(),
|
||||
// palette_menu->Render() | frame,
|
||||
// separator(),
|
||||
// color_grid | frame | flex,
|
||||
// separator(),
|
||||
// selected_color_view | frame,
|
||||
// }),
|
||||
// separator(),
|
||||
// back_button->Render() | center,
|
||||
// }) | border;
|
||||
// });
|
||||
|
||||
// return renderer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
17
src/cli/tui/palette_editor.h
Normal file
17
src/cli/tui/palette_editor.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef YAZE_SRC_CLI_TUI_PALETTE_EDITOR_H_
|
||||
#define YAZE_SRC_CLI_TUI_PALETTE_EDITOR_H_
|
||||
|
||||
#include "cli/tui/tui_component.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
class PaletteEditorComponent : public TuiComponent {
|
||||
public:
|
||||
ftxui::Component Render() override;
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_SRC_CLI_TUI_PALETTE_EDITOR_H_
|
||||
18
src/cli/tui/tui_component.h
Normal file
18
src/cli/tui/tui_component.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef YAZE_SRC_CLI_TUI_TUI_COMPONENT_H_
|
||||
#define YAZE_SRC_CLI_TUI_TUI_COMPONENT_H_
|
||||
|
||||
#include <ftxui/component/component.hpp>
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
class TuiComponent {
|
||||
public:
|
||||
virtual ~TuiComponent() = default;
|
||||
virtual ftxui::Component Render() = 0;
|
||||
};
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_SRC_CLI_TUI_TUI_COMPONENT_H_
|
||||
@@ -31,6 +31,19 @@ add_executable(
|
||||
cli/handlers/dungeon.cc
|
||||
cli/handlers/gfx.cc
|
||||
cli/handlers/palette.cc
|
||||
cli/handlers/rom.cc
|
||||
cli/handlers/overworld.cc
|
||||
cli/handlers/sprite.cc
|
||||
cli/tui/tui_component.h
|
||||
cli/tui/asar_patch.cc
|
||||
cli/tui/palette_editor.cc
|
||||
cli/tui/command_palette.cc
|
||||
cli/modern_cli.cc
|
||||
cli/handlers/command_palette.cc
|
||||
cli/handlers/project.cc
|
||||
cli/handlers/agent.cc
|
||||
cli/service/ai_service.cc
|
||||
cli/service/gemini_ai_service.cc
|
||||
app/rom.cc
|
||||
app/core/project.cc
|
||||
app/core/asar_wrapper.cc
|
||||
@@ -43,6 +56,14 @@ add_executable(
|
||||
${IMGUI_SRC}
|
||||
)
|
||||
|
||||
option(YAZE_WITH_JSON "Build with JSON support" OFF)
|
||||
if(YAZE_WITH_JSON)
|
||||
add_subdirectory(../../third_party/json)
|
||||
target_compile_definitions(z3ed PRIVATE YAZE_WITH_JSON)
|
||||
target_link_libraries(z3ed PRIVATE nlohmann_json::nlohmann_json)
|
||||
list(APPEND Z3ED_SRC_FILES cli/gemini_ai_service.cc)
|
||||
endif()
|
||||
|
||||
target_include_directories(
|
||||
z3ed PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/src/lib/
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
@@ -14,14 +13,25 @@
|
||||
#include "app/snes.h"
|
||||
#include "util/macro.h"
|
||||
|
||||
// Forward declarations
|
||||
namespace ftxui {
|
||||
class ScreenInteractive;
|
||||
}
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
// Forward declaration
|
||||
class TuiComponent;
|
||||
|
||||
class CommandHandler {
|
||||
public:
|
||||
CommandHandler() = default;
|
||||
virtual ~CommandHandler() = default;
|
||||
virtual absl::Status Run(const std::vector<std::string>& arg_vec) = 0;
|
||||
virtual void RunTUI(ftxui::ScreenInteractive& screen) {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
|
||||
Rom rom_;
|
||||
};
|
||||
@@ -33,7 +43,9 @@ class ApplyPatch : public CommandHandler {
|
||||
|
||||
class AsarPatch : public CommandHandler {
|
||||
public:
|
||||
AsarPatch();
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
void RunTUI(ftxui::ScreenInteractive& screen) override;
|
||||
};
|
||||
|
||||
class CreatePatch : public CommandHandler {
|
||||
@@ -56,6 +68,20 @@ class GfxImport : public CommandHandler {
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class Palette : public CommandHandler {
|
||||
public:
|
||||
Palette();
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
void RunTUI(ftxui::ScreenInteractive& screen) override;
|
||||
};
|
||||
|
||||
class CommandPalette : public CommandHandler {
|
||||
public:
|
||||
CommandPalette();
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
void RunTUI(ftxui::ScreenInteractive& screen) override;
|
||||
};
|
||||
|
||||
class PaletteExport : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
@@ -71,6 +97,56 @@ class DungeonExport : public CommandHandler {
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class DungeonListObjects : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class RomValidate : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class RomDiff : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class RomGenerateGolden : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class ProjectInit : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class ProjectBuild : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class Agent : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class OverworldGetTile : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class OverworldSetTile : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class SpriteCreate : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override;
|
||||
};
|
||||
|
||||
class Open : public CommandHandler {
|
||||
public:
|
||||
absl::Status Run(const std::vector<std::string>& arg_vec) override {
|
||||
|
||||
Reference in New Issue
Block a user