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:
232
src/cli/handlers/agent.cc
Normal file
232
src/cli/handlers/agent.cc
Normal file
@@ -0,0 +1,232 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "cli/modern_cli.h"
|
||||
#include "cli/service/ai_service.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
// Mock AI service is defined in ai_service.h
|
||||
|
||||
namespace {
|
||||
|
||||
absl::Status HandleRunCommand(const std::vector<std::string>& arg_vec, Rom& rom) {
|
||||
if (arg_vec.size() < 2 || arg_vec[0] != "--prompt") {
|
||||
return absl::InvalidArgumentError("Usage: agent run --prompt <prompt>");
|
||||
}
|
||||
std::string prompt = arg_vec[1];
|
||||
|
||||
// Save a temporary copy of the ROM
|
||||
if (rom.is_loaded()) {
|
||||
auto status = rom.SaveToFile({.save_new = true, .filename = "temp_rom.sfc"});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
MockAIService ai_service;
|
||||
auto commands_or = ai_service.GetCommands(prompt);
|
||||
if (!commands_or.ok()) {
|
||||
return commands_or.status();
|
||||
}
|
||||
std::vector<std::string> commands = commands_or.value();
|
||||
|
||||
ModernCLI cli;
|
||||
for (const auto& command : commands) {
|
||||
std::vector<std::string> command_parts;
|
||||
std::string current_part;
|
||||
bool in_quotes = false;
|
||||
for (char c : command) {
|
||||
if (c == '\"') {
|
||||
in_quotes = !in_quotes;
|
||||
} else if (c == ' ' && !in_quotes) {
|
||||
command_parts.push_back(current_part);
|
||||
current_part.clear();
|
||||
} else {
|
||||
current_part += c;
|
||||
}
|
||||
}
|
||||
command_parts.push_back(current_part);
|
||||
|
||||
std::string cmd_name = command_parts[0] + " " + command_parts[1];
|
||||
std::vector<std::string> cmd_args(command_parts.begin() + 2, command_parts.end());
|
||||
|
||||
auto it = cli.commands_.find(cmd_name);
|
||||
if (it != cli.commands_.end()) {
|
||||
auto status = it->second.handler(cmd_args);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandlePlanCommand(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2 || arg_vec[0] != "--prompt") {
|
||||
return absl::InvalidArgumentError("Usage: agent plan --prompt <prompt>");
|
||||
}
|
||||
std::string prompt = arg_vec[1];
|
||||
MockAIService ai_service;
|
||||
auto commands_or = ai_service.GetCommands(prompt);
|
||||
if (!commands_or.ok()) {
|
||||
return commands_or.status();
|
||||
}
|
||||
std::vector<std::string> commands = commands_or.value();
|
||||
|
||||
std::cout << "AI Agent Plan:" << std::endl;
|
||||
for (const auto& command : commands) {
|
||||
std::cout << " - " << command << std::endl;
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleDiffCommand(Rom& rom) {
|
||||
if (rom.is_loaded()) {
|
||||
RomDiff diff_handler;
|
||||
auto status = diff_handler.Run({rom.filename(), "temp_rom.sfc"});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
} else {
|
||||
return absl::AbortedError("No ROM loaded.");
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleTestCommand(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2 || arg_vec[0] != "--test") {
|
||||
return absl::InvalidArgumentError("Usage: agent test --test <test_name>");
|
||||
}
|
||||
std::string test_name = arg_vec[1];
|
||||
|
||||
#ifdef _WIN32
|
||||
return absl::UnimplementedError(
|
||||
"GUI test command is not supported on Windows. "
|
||||
"Please run yaze_test.exe directly with --enable-ui-tests flag.");
|
||||
#else
|
||||
char exe_path[1024];
|
||||
#ifdef __APPLE__
|
||||
uint32_t size = sizeof(exe_path);
|
||||
if (_NSGetExecutablePath(exe_path, &size) != 0) {
|
||||
return absl::InternalError("Could not get executable path");
|
||||
}
|
||||
#else
|
||||
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
if (len == -1) {
|
||||
return absl::InternalError("Could not get executable path");
|
||||
}
|
||||
exe_path[len] = '\0';
|
||||
#endif
|
||||
|
||||
std::string exe_dir = std::string(exe_path);
|
||||
exe_dir = exe_dir.substr(0, exe_dir.find_last_of("/"));
|
||||
|
||||
std::string yaze_test_path = exe_dir + "/yaze_test";
|
||||
|
||||
std::vector<std::string> command_args;
|
||||
command_args.push_back(yaze_test_path);
|
||||
command_args.push_back("--enable-ui-tests");
|
||||
command_args.push_back("--test=" + test_name);
|
||||
|
||||
std::vector<char*> argv;
|
||||
for (const auto& arg : command_args) {
|
||||
argv.push_back((char*)arg.c_str());
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
return absl::InternalError("Failed to fork process");
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process
|
||||
execv(yaze_test_path.c_str(), argv.data());
|
||||
// If execv returns, it must have failed
|
||||
return absl::InternalError("Failed to execute yaze_test");
|
||||
} else {
|
||||
// Parent process
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
if (WIFEXITED(status)) {
|
||||
int exit_code = WEXITSTATUS(status);
|
||||
if (exit_code == 0) {
|
||||
return absl::OkStatus();
|
||||
} else {
|
||||
return absl::InternalError(absl::StrFormat("yaze_test exited with code %d", exit_code));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
#endif
|
||||
}
|
||||
|
||||
absl::Status HandleLearnCommand() {
|
||||
std::cout << "Agent learn not yet implemented." << std::endl;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleCommitCommand(Rom& rom) {
|
||||
if (rom.is_loaded()) {
|
||||
auto status = rom.SaveToFile({.save_new = false});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
std::cout << "✅ Changes committed successfully." << std::endl;
|
||||
} else {
|
||||
return absl::AbortedError("No ROM loaded.");
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status HandleRevertCommand(Rom& rom) {
|
||||
if (rom.is_loaded()) {
|
||||
auto status = rom.LoadFromFile(rom.filename());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
std::cout << "✅ Changes reverted successfully." << std::endl;
|
||||
} else {
|
||||
return absl::AbortedError("No ROM loaded.");
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
absl::Status Agent::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: agent <run|plan|diff|test|learn|commit|revert> [options]");
|
||||
}
|
||||
|
||||
std::string subcommand = arg_vec[0];
|
||||
std::vector<std::string> subcommand_args(arg_vec.begin() + 1, arg_vec.end());
|
||||
|
||||
if (subcommand == "run") {
|
||||
return HandleRunCommand(subcommand_args, rom_);
|
||||
} else if (subcommand == "plan") {
|
||||
return HandlePlanCommand(subcommand_args);
|
||||
} else if (subcommand == "diff") {
|
||||
return HandleDiffCommand(rom_);
|
||||
} else if (subcommand == "test") {
|
||||
return HandleTestCommand(subcommand_args);
|
||||
} else if (subcommand == "learn") {
|
||||
return HandleLearnCommand();
|
||||
} else if (subcommand == "commit") {
|
||||
return HandleCommitCommand(rom_);
|
||||
} else if (subcommand == "revert") {
|
||||
return HandleRevertCommand(rom_);
|
||||
} else {
|
||||
return absl::InvalidArgumentError("Invalid subcommand for agent command.");
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
18
src/cli/handlers/command_palette.cc
Normal file
18
src/cli/handlers/command_palette.cc
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "cli/tui/command_palette.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
CommandPalette::CommandPalette() {}
|
||||
|
||||
absl::Status CommandPalette::Run(const std::vector<std::string>& arg_vec) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void CommandPalette::RunTUI(ftxui::ScreenInteractive& screen) {
|
||||
// TODO: Implement command palette TUI
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
@@ -1,5 +1,10 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
@@ -10,9 +15,61 @@ absl::Status DungeonExport::Run(const std::vector<std::string>& arg_vec) {
|
||||
}
|
||||
|
||||
int room_id = std::stoi(arg_vec[0]);
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
// TODO: Implement dungeon export logic
|
||||
std::cout << "Dungeon export for room " << room_id << " not yet implemented." << std::endl;
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
zelda3::DungeonEditorSystem dungeon_editor(&rom_);
|
||||
auto room_or = dungeon_editor.GetRoom(room_id);
|
||||
if (!room_or.ok()) {
|
||||
return room_or.status();
|
||||
}
|
||||
zelda3::Room room = room_or.value();
|
||||
|
||||
std::cout << "Room ID: " << room_id << std::endl;
|
||||
std::cout << "Blockset: " << (int)room.blockset << std::endl;
|
||||
std::cout << "Spriteset: " << (int)room.spriteset << std::endl;
|
||||
std::cout << "Palette: " << (int)room.palette << std::endl;
|
||||
std::cout << "Layout: " << (int)room.layout << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonListObjects::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 1) {
|
||||
return absl::InvalidArgumentError("Usage: dungeon list-objects <room_id>");
|
||||
}
|
||||
|
||||
int room_id = std::stoi(arg_vec[0]);
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
zelda3::DungeonEditorSystem dungeon_editor(&rom_);
|
||||
auto room_or = dungeon_editor.GetRoom(room_id);
|
||||
if (!room_or.ok()) {
|
||||
return room_or.status();
|
||||
}
|
||||
zelda3::Room room = room_or.value();
|
||||
room.LoadObjects();
|
||||
|
||||
std::cout << "Objects in Room " << room_id << ":" << std::endl;
|
||||
for (const auto& obj : room.GetTileObjects()) {
|
||||
std::cout << absl::StrFormat(" - ID: 0x%04X, Pos: (%d, %d), Size: 0x%02X, Layer: %d\n",
|
||||
obj.id_, obj.x_, obj.y_, obj.size_, obj.layer_);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/gfx/scad_format.h"
|
||||
#include "app/gfx/arena.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
@@ -8,8 +14,34 @@ absl::Status GfxExport::Run(const std::vector<std::string>& arg_vec) {
|
||||
return absl::InvalidArgumentError("Usage: gfx export-sheet <sheet_id> --to <file>");
|
||||
}
|
||||
|
||||
// TODO: Implement gfx export logic
|
||||
std::cout << "Gfx export not yet implemented." << std::endl;
|
||||
int sheet_id = std::stoi(arg_vec[0]);
|
||||
std::string output_file = arg_vec[1];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
auto& arena = gfx::Arena::Get();
|
||||
auto sheet = arena.gfx_sheet(sheet_id);
|
||||
if (!sheet.is_active()) {
|
||||
return absl::NotFoundError("Graphics sheet not found.");
|
||||
}
|
||||
|
||||
// For now, we will just save the raw 8bpp data.
|
||||
// TODO: Convert the 8bpp data to the correct SNES bpp format.
|
||||
std::vector<uint8_t> header; // Empty header for now
|
||||
auto status = gfx::SaveCgx(sheet.depth(), output_file, sheet.vector(), header);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::cout << "Successfully exported graphics sheet " << sheet_id << " to " << output_file << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
@@ -19,8 +51,39 @@ absl::Status GfxImport::Run(const std::vector<std::string>& arg_vec) {
|
||||
return absl::InvalidArgumentError("Usage: gfx import-sheet <sheet_id> --from <file>");
|
||||
}
|
||||
|
||||
// TODO: Implement gfx import logic
|
||||
std::cout << "Gfx import not yet implemented." << std::endl;
|
||||
int sheet_id = std::stoi(arg_vec[0]);
|
||||
std::string input_file = arg_vec[1];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
std::vector<uint8_t> cgx_data, cgx_loaded, cgx_header;
|
||||
auto status = gfx::LoadCgx(8, input_file, cgx_data, cgx_loaded, cgx_header);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
auto& arena = gfx::Arena::Get();
|
||||
auto sheet = arena.gfx_sheet(sheet_id);
|
||||
if (!sheet.is_active()) {
|
||||
return absl::NotFoundError("Graphics sheet not found.");
|
||||
}
|
||||
|
||||
// TODO: Convert the 8bpp data to the correct SNES bpp format before writing.
|
||||
// For now, we just replace the data directly.
|
||||
sheet.set_data(cgx_loaded);
|
||||
|
||||
// TODO: Implement saving the modified graphics sheet back to the ROM.
|
||||
|
||||
std::cout << "Successfully imported graphics sheet " << sheet_id << " from " << input_file << std::endl;
|
||||
std::cout << "(Saving to ROM not yet implemented)" << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
77
src/cli/handlers/overworld.cc
Normal file
77
src/cli/handlers/overworld.cc
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/zelda3/overworld/overworld.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
absl::Status OverworldGetTile::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 3) {
|
||||
return absl::InvalidArgumentError("Usage: overworld get-tile --map <map_id> --x <x> --y <y>");
|
||||
}
|
||||
|
||||
// TODO: Implement proper argument parsing
|
||||
int map_id = std::stoi(arg_vec[0]);
|
||||
int x = std::stoi(arg_vec[1]);
|
||||
int y = std::stoi(arg_vec[2]);
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
zelda3::Overworld overworld(&rom_);
|
||||
overworld.Load(&rom_);
|
||||
|
||||
uint16_t tile = overworld.GetTile(x, y);
|
||||
|
||||
std::cout << "Tile at (" << x << ", " << y << ") on map " << map_id << " is: 0x" << std::hex << tile << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status OverworldSetTile::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 4) {
|
||||
return absl::InvalidArgumentError("Usage: overworld set-tile --map <map_id> --x <x> --y <y> --tile <tile_id>");
|
||||
}
|
||||
|
||||
// TODO: Implement proper argument parsing
|
||||
int map_id = std::stoi(arg_vec[0]);
|
||||
int x = std::stoi(arg_vec[1]);
|
||||
int y = std::stoi(arg_vec[2]);
|
||||
int tile_id = std::stoi(arg_vec[3], nullptr, 16);
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
zelda3::Overworld overworld(&rom_);
|
||||
overworld.Load(&rom_);
|
||||
|
||||
// TODO: Implement the actual set_tile method in Overworld class
|
||||
// overworld.SetTile(x, y, tile_id);
|
||||
|
||||
// rom_.SaveToFile({.filename = rom_file});
|
||||
|
||||
std::cout << "Set tile at (" << x << ", " << y << ") on map " << map_id << " to: 0x" << std::hex << tile_id << std::endl;
|
||||
std::cout << "(Not actually implemented yet)" << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
@@ -1,26 +1,135 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "cli/tui/palette_editor.h"
|
||||
|
||||
#include "app/gfx/scad_format.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
Palette::Palette() {}
|
||||
|
||||
absl::Status Palette::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: palette <export|import> [options]");
|
||||
}
|
||||
|
||||
const std::string& action = arg_vec[0];
|
||||
std::vector<std::string> new_args(arg_vec.begin() + 1, arg_vec.end());
|
||||
|
||||
if (action == "export") {
|
||||
PaletteExport handler;
|
||||
return handler.Run(new_args);
|
||||
} else if (action == "import") {
|
||||
PaletteImport handler;
|
||||
return handler.Run(new_args);
|
||||
}
|
||||
|
||||
return absl::InvalidArgumentError("Invalid action for palette command.");
|
||||
}
|
||||
|
||||
void Palette::RunTUI(ftxui::ScreenInteractive& screen) {
|
||||
// TODO: Implement palette editor TUI
|
||||
(void)screen; // Suppress unused parameter warning
|
||||
}
|
||||
|
||||
absl::Status PaletteExport::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
return absl::InvalidArgumentError("Usage: palette export --group <group> --id <id> --format <format>");
|
||||
if (arg_vec.size() < 3) {
|
||||
return absl::InvalidArgumentError("Usage: palette export --group <group> --id <id> --to <file>");
|
||||
}
|
||||
|
||||
// TODO: Implement palette export logic
|
||||
std::cout << "Palette export not yet implemented." << std::endl;
|
||||
// TODO: Implement proper argument parsing
|
||||
std::string group_name = arg_vec[0];
|
||||
int palette_id = std::stoi(arg_vec[1]);
|
||||
std::string output_file = arg_vec[2];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
auto palette_group = rom_.palette_group().get_group(group_name);
|
||||
if (!palette_group) {
|
||||
return absl::NotFoundError("Palette group not found.");
|
||||
}
|
||||
|
||||
auto palette = palette_group->palette(palette_id);
|
||||
if (palette.empty()) {
|
||||
return absl::NotFoundError("Palette not found.");
|
||||
}
|
||||
|
||||
std::vector<SDL_Color> sdl_palette;
|
||||
for (const auto& color : palette) {
|
||||
SDL_Color sdl_color;
|
||||
sdl_color.r = color.rgb().x;
|
||||
sdl_color.g = color.rgb().y;
|
||||
sdl_color.b = color.rgb().z;
|
||||
sdl_color.a = 255;
|
||||
sdl_palette.push_back(sdl_color);
|
||||
}
|
||||
|
||||
auto status = gfx::SaveCol(output_file, sdl_palette);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::cout << "Successfully exported palette " << palette_id << " from group " << group_name << " to " << output_file << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status PaletteImport::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
if (arg_vec.size() < 3) {
|
||||
return absl::InvalidArgumentError("Usage: palette import --group <group> --id <id> --from <file>");
|
||||
}
|
||||
|
||||
// TODO: Implement palette import logic
|
||||
std::cout << "Palette import not yet implemented." << std::endl;
|
||||
// TODO: Implement proper argument parsing
|
||||
std::string group_name = arg_vec[0];
|
||||
int palette_id = std::stoi(arg_vec[1]);
|
||||
std::string input_file = arg_vec[2];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
auto sdl_palette = gfx::DecodeColFile(input_file);
|
||||
if (sdl_palette.empty()) {
|
||||
return absl::AbortedError("Failed to load palette file.");
|
||||
}
|
||||
|
||||
gfx::SnesPalette snes_palette;
|
||||
for (const auto& sdl_color : sdl_palette) {
|
||||
snes_palette.AddColor(gfx::SnesColor(sdl_color.r, sdl_color.g, sdl_color.b));
|
||||
}
|
||||
|
||||
auto palette_group = rom_.palette_group().get_group(group_name);
|
||||
if (!palette_group) {
|
||||
return absl::NotFoundError("Palette group not found.");
|
||||
}
|
||||
|
||||
// Replace the palette at the specified index
|
||||
auto* pal = palette_group->mutable_palette(palette_id);
|
||||
*pal = snes_palette;
|
||||
|
||||
// TODO: Implement saving the modified palette back to the ROM.
|
||||
|
||||
std::cout << "Successfully imported palette " << palette_id << " to group " << group_name << " from " << input_file << std::endl;
|
||||
std::cout << "(Saving to ROM not yet implemented)" << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
#include "asar-dll-bindings/c/asar.h"
|
||||
#include "cli/z3ed.h"
|
||||
#include "cli/tui/asar_patch.h"
|
||||
#include "util/bps.h"
|
||||
#include "app/core/asar_wrapper.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
@@ -26,97 +34,56 @@ absl::Status ApplyPatch::Run(const std::vector<std::string>& arg_vec) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
AsarPatch::AsarPatch() {}
|
||||
|
||||
absl::Status AsarPatch::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
return absl::InvalidArgumentError("Usage: asar <patch_file> <rom_file>");
|
||||
if (arg_vec.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: patch apply-asar <patch.asm>");
|
||||
}
|
||||
const std::string& patch_file = arg_vec[0];
|
||||
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
app::core::AsarWrapper wrapper;
|
||||
auto init_status = wrapper.Initialize();
|
||||
if (!init_status.ok()) {
|
||||
return init_status;
|
||||
}
|
||||
|
||||
std::string patch_filename = arg_vec[0];
|
||||
std::string rom_filename = arg_vec[1];
|
||||
|
||||
// Load ROM file
|
||||
RETURN_IF_ERROR(rom_.LoadFromFile(rom_filename))
|
||||
|
||||
// Get ROM data
|
||||
auto rom_data = rom_.vector();
|
||||
int buflen = static_cast<int>(rom_data.size());
|
||||
int romlen = buflen;
|
||||
|
||||
// Ensure we have enough buffer space
|
||||
const int max_rom_size = asar_maxromsize();
|
||||
if (buflen < max_rom_size) {
|
||||
rom_data.resize(max_rom_size, 0);
|
||||
buflen = max_rom_size;
|
||||
auto patch_result = wrapper.ApplyPatch(patch_file, rom_data);
|
||||
|
||||
if (!patch_result.ok()) {
|
||||
return patch_result.status();
|
||||
}
|
||||
|
||||
// Apply Asar patch
|
||||
if (!asar_patch(patch_filename.c_str(),
|
||||
reinterpret_cast<char*>(rom_data.data()),
|
||||
buflen, &romlen)) {
|
||||
std::string error_message = "Failed to apply Asar patch:\n";
|
||||
int num_errors = 0;
|
||||
const errordata* errors = asar_geterrors(&num_errors);
|
||||
for (int i = 0; i < num_errors; i++) {
|
||||
error_message += absl::StrFormat(" %s\n", errors[i].fullerrdata);
|
||||
}
|
||||
return absl::InternalError(error_message);
|
||||
|
||||
const auto& result = patch_result.value();
|
||||
if (!result.success) {
|
||||
return absl::AbortedError(absl::StrJoin(result.errors, "; "));
|
||||
}
|
||||
|
||||
// Resize ROM to actual size
|
||||
rom_data.resize(romlen);
|
||||
|
||||
// Update the ROM data by writing the patched data back
|
||||
for (size_t i = 0; i < rom_data.size(); ++i) {
|
||||
auto status = rom_.WriteByte(i, rom_data[i]);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Save patched ROM
|
||||
std::string output_filename = rom_filename;
|
||||
size_t dot_pos = output_filename.find_last_of('.');
|
||||
if (dot_pos != std::string::npos) {
|
||||
output_filename.insert(dot_pos, "_patched");
|
||||
} else {
|
||||
output_filename += "_patched";
|
||||
}
|
||||
|
||||
Rom::SaveSettings settings;
|
||||
settings.filename = output_filename;
|
||||
RETURN_IF_ERROR(rom_.SaveToFile(settings))
|
||||
|
||||
std::cout << "✅ Asar patch applied successfully!" << std::endl;
|
||||
std::cout << "📁 Output: " << output_filename << std::endl;
|
||||
std::cout << "📊 Final ROM size: " << romlen << " bytes" << std::endl;
|
||||
|
||||
// Show warnings if any
|
||||
int num_warnings = 0;
|
||||
const errordata* warnings = asar_getwarnings(&num_warnings);
|
||||
if (num_warnings > 0) {
|
||||
std::cout << "⚠️ Warnings:" << std::endl;
|
||||
for (int i = 0; i < num_warnings; i++) {
|
||||
std::cout << " " << warnings[i].fullerrdata << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Show extracted symbols
|
||||
int num_labels = 0;
|
||||
const labeldata* labels = asar_getalllabels(&num_labels);
|
||||
if (num_labels > 0) {
|
||||
std::cout << "🏷️ Extracted " << num_labels << " symbols:" << std::endl;
|
||||
for (int i = 0; i < std::min(10, num_labels); i++) { // Show first 10
|
||||
std::cout << " " << labels[i].name << " @ $"
|
||||
<< std::hex << std::uppercase << labels[i].location << std::endl;
|
||||
}
|
||||
if (num_labels > 10) {
|
||||
std::cout << " ... and " << (num_labels - 10) << " more" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: Save the patched ROM
|
||||
|
||||
std::cout << "Patch applied successfully!" << std::endl;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void AsarPatch::RunTUI(ftxui::ScreenInteractive& screen) {
|
||||
// TODO: Implement Asar patch TUI
|
||||
(void)screen; // Suppress unused parameter warning
|
||||
}
|
||||
|
||||
// ... rest of the file
|
||||
|
||||
|
||||
absl::Status CreatePatch::Run(const std::vector<std::string>& arg_vec) {
|
||||
std::vector<uint8_t> source;
|
||||
std::vector<uint8_t> target;
|
||||
|
||||
80
src/cli/handlers/project.cc
Normal file
80
src/cli/handlers/project.cc
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/core/project.h"
|
||||
#include "core/platform/file_dialog.h"
|
||||
#include "util/bps.h"
|
||||
#include <glob.h>
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
absl::Status ProjectInit::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.empty()) {
|
||||
return absl::InvalidArgumentError("Usage: project init <project_name>");
|
||||
}
|
||||
|
||||
std::string project_name = arg_vec[0];
|
||||
core::YazeProject project;
|
||||
auto status = project.Create(project_name, ".");
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::cout << "✅ Successfully initialized project: " << project_name << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ProjectBuild::Run(const std::vector<std::string>& arg_vec) {
|
||||
core::YazeProject project;
|
||||
auto status = project.Open(".");
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
Rom rom;
|
||||
status = rom.LoadFromFile(project.rom_filename);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Apply BPS patches
|
||||
glob_t glob_result;
|
||||
std::string pattern = project.patches_folder + "/*.bps";
|
||||
glob(pattern.c_str(), GLOB_TILDE, NULL, &glob_result);
|
||||
for(unsigned int i=0; i<glob_result.gl_pathc; ++i){
|
||||
std::string patch_file = glob_result.gl_pathv[i];
|
||||
std::vector<uint8_t> patch_data;
|
||||
auto patch_contents = core::LoadFile(patch_file);
|
||||
std::copy(patch_contents.begin(), patch_contents.end(), std::back_inserter(patch_data));
|
||||
std::vector<uint8_t> patched_rom;
|
||||
util::ApplyBpsPatch(rom.vector(), patch_data, patched_rom);
|
||||
rom.LoadFromData(patched_rom);
|
||||
}
|
||||
|
||||
// Run asar on assembly files
|
||||
glob_t glob_result_asm;
|
||||
std::string pattern_asm = project.patches_folder + "/*.asm";
|
||||
glob(pattern_asm.c_str(), GLOB_TILDE, NULL, &glob_result_asm);
|
||||
for(unsigned int i=0; i<glob_result_asm.gl_pathc; ++i){
|
||||
AsarPatch asar_patch;
|
||||
auto status = asar_patch.Run({glob_result_asm.gl_pathv[i]});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
std::string output_file = project.name + ".sfc";
|
||||
status = rom.SaveToFile({.save_new = true, .filename = output_file});
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::cout << "✅ Successfully built project: " << project.name << std::endl;
|
||||
std::cout << " Output ROM: " << output_file << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
116
src/cli/handlers/rom.cc
Normal file
116
src/cli/handlers/rom.cc
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/declare.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
ABSL_DECLARE_FLAG(std::string, rom);
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
absl::Status RomValidate::Run(const std::vector<std::string>& arg_vec) {
|
||||
std::string rom_file = absl::GetFlag(FLAGS_rom);
|
||||
if (rom_file.empty()) {
|
||||
return absl::InvalidArgumentError("ROM file must be provided via --rom flag.");
|
||||
}
|
||||
|
||||
rom_.LoadFromFile(rom_file);
|
||||
if (!rom_.is_loaded()) {
|
||||
return absl::AbortedError("Failed to load ROM.");
|
||||
}
|
||||
|
||||
bool all_ok = true;
|
||||
std::cout << "Validating ROM: " << rom_file << std::endl;
|
||||
|
||||
// Checksum validation
|
||||
std::cout << " - Verifying checksum... " << std::flush;
|
||||
// Basic ROM validation - check if ROM is loaded and has reasonable size
|
||||
if (rom_.is_loaded() && rom_.size() > 0) {
|
||||
std::cout << "✅ PASSED" << std::endl;
|
||||
} else {
|
||||
std::cout << "❌ FAILED" << std::endl;
|
||||
all_ok = false;
|
||||
}
|
||||
|
||||
// Header validation
|
||||
std::cout << " - Verifying header... " << std::flush;
|
||||
if (rom_.title() == "THE LEGEND OF ZELDA") {
|
||||
std::cout << "✅ PASSED" << std::endl;
|
||||
} else {
|
||||
std::cout << "❌ FAILED (Invalid title: " << rom_.title() << ")" << std::endl;
|
||||
all_ok = false;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
if (all_ok) {
|
||||
std::cout << "✅ ROM validation successful." << std::endl;
|
||||
} else {
|
||||
std::cout << "❌ ROM validation failed." << std::endl;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status RomDiff::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
return absl::InvalidArgumentError("Usage: rom diff <rom_a> <rom_b>");
|
||||
}
|
||||
|
||||
Rom rom_a;
|
||||
auto status_a = rom_a.LoadFromFile(arg_vec[0]);
|
||||
if (!status_a.ok()) {
|
||||
return status_a;
|
||||
}
|
||||
|
||||
Rom rom_b;
|
||||
auto status_b = rom_b.LoadFromFile(arg_vec[1]);
|
||||
if (!status_b.ok()) {
|
||||
return status_b;
|
||||
}
|
||||
|
||||
if (rom_a.size() != rom_b.size()) {
|
||||
std::cout << "ROMs have different sizes: " << rom_a.size() << " vs " << rom_b.size() << std::endl;
|
||||
}
|
||||
|
||||
int differences = 0;
|
||||
for (size_t i = 0; i < rom_a.size(); ++i) {
|
||||
if (rom_a.vector()[i] != rom_b.vector()[i]) {
|
||||
differences++;
|
||||
std::cout << absl::StrFormat("Difference at 0x%08X: 0x%02X vs 0x%02X\n", i, rom_a.vector()[i], rom_b.vector()[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (differences == 0) {
|
||||
std::cout << "ROMs are identical." << std::endl;
|
||||
} else {
|
||||
std::cout << "Found " << differences << " differences." << std::endl;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status RomGenerateGolden::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2) {
|
||||
return absl::InvalidArgumentError("Usage: rom generate-golden <rom_file> <golden_file>");
|
||||
}
|
||||
|
||||
Rom rom;
|
||||
auto status = rom.LoadFromFile(arg_vec[0]);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::ofstream file(arg_vec[1], std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return absl::NotFoundError("Could not open file for writing.");
|
||||
}
|
||||
|
||||
file.write(reinterpret_cast<const char*>(rom.vector().data()), rom.size());
|
||||
|
||||
std::cout << "Successfully generated golden file: " << arg_vec[1] << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
30
src/cli/handlers/sprite.cc
Normal file
30
src/cli/handlers/sprite.cc
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "cli/z3ed.h"
|
||||
#include "app/zelda3/sprite/sprite_builder.h"
|
||||
#include "absl/flags/flag.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
|
||||
absl::Status SpriteCreate::Run(const std::vector<std::string>& arg_vec) {
|
||||
if (arg_vec.size() < 2 || arg_vec[0] != "--name") {
|
||||
return absl::InvalidArgumentError("Usage: sprite create --name <sprite_name>");
|
||||
}
|
||||
|
||||
std::string sprite_name = arg_vec[1];
|
||||
|
||||
// Create a simple sprite with a single action
|
||||
auto builder = zelda3::SpriteBuilder::Create(sprite_name)
|
||||
.SetProperty("!Health", 1)
|
||||
.SetProperty("!Damage", 2)
|
||||
.AddAction(zelda3::SpriteAction::Create("MAIN")
|
||||
.AddInstruction(zelda3::SpriteInstruction::ApplySpeedTowardsPlayer(1))
|
||||
.AddInstruction(zelda3::SpriteInstruction::MoveXyz())
|
||||
);
|
||||
|
||||
std::cout << builder.Build() << std::endl;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
Reference in New Issue
Block a user