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:
scawful
2025-10-01 08:57:10 -04:00
parent e7d4f5ea02
commit ba50d89e7d
46 changed files with 2421 additions and 965 deletions

View File

@@ -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_) {

View File

@@ -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,

View File

@@ -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

View File

@@ -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";

View File

@@ -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
View 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

View 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

View File

@@ -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();
}

View File

@@ -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();
}

View 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

View File

@@ -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();
}

View File

@@ -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;

View 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
View 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

View 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
View 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
View 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_

View 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

View 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_

View 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

View 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_

View File

@@ -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;

View File

@@ -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
View 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
View 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_

View 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

View 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_

View 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

View 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_

View 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_

View File

@@ -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/

View File

@@ -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 {