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

@@ -1,4 +1,4 @@
# YAZE clang-tidy configuration # yaze clang-tidy configuration
# More lenient configuration for easier compliance # More lenient configuration for easier compliance
Checks: > Checks: >

View File

@@ -22,10 +22,12 @@ Hover:
Diagnostics: Diagnostics:
ClangTidy: ClangTidy:
Add: Add:
- readability-* # - readability-*
- modernize-* # - modernize-*
- performance-* - performance-*
Remove: Remove:
- readability-*
- modernize-*
- modernize-use-trailing-return-type - modernize-use-trailing-return-type
- readability-braces-around-statements - readability-braces-around-statements
- readability-magic-numbers - readability-magic-numbers
@@ -33,3 +35,4 @@ Diagnostics:
- readability-identifier-naming - readability-identifier-naming
- readability-function-cognitive-complexity - readability-function-cognitive-complexity
- readability-function-size - readability-function-size
- readability-uppercase-literal-suffix

6
.gitmodules vendored
View File

@@ -22,3 +22,9 @@
[submodule "assets/asm/usdasm"] [submodule "assets/asm/usdasm"]
path = assets/asm/usdasm path = assets/asm/usdasm
url = https://github.com/spannerisms/usdasm.git url = https://github.com/spannerisms/usdasm.git
[submodule "third_party/json"]
path = third_party/json
url = https://github.com/nlohmann/json.git
[submodule "third_party/httplib"]
path = third_party/httplib
url = https://github.com/yhirose/cpp-httplib.git

View File

@@ -1,4 +1,4 @@
# YAZE - Yet Another Zelda3 Editor # yaze - Yet Another Zelda3 Editor
A modern, cross-platform editor for The Legend of Zelda: A Link to the Past ROM hacking, built with C++23 and featuring complete Asar 65816 assembler integration. A modern, cross-platform editor for The Legend of Zelda: A Link to the Past ROM hacking, built with C++23 and featuring complete Asar 65816 assembler integration.

237
docs/E6-z3ed-cli-design.md Normal file
View File

@@ -0,0 +1,237 @@
## 1. Overview
This document outlines a design for the evolution of `z3ed`, the command-line interface for the YAZE project. The goal is to transform `z3ed` from a collection of utility commands into a powerful, scriptable, and extensible tool for both manual and automated ROM hacking, with a forward-looking approach to AI-driven generative development.
### 1.1. Current State
`z3ed` has evolved significantly. The initial limitations regarding scope, inconsistent structure, basic TUI, and limited scriptability have largely been addressed through the implementation of resource-oriented commands, modular TUI components, and structured output. The focus has shifted towards building a robust foundation for AI-driven generative hacking.
## 2. Design Goals
The proposed redesign focuses on three core pillars:
1. **Power & Usability for ROM Hackers**: Empower users with fine-grained control over all aspects of the ROM directly from the command line.
2. **Testability & Automation**: Provide robust commands for validating ROM integrity and automating complex testing scenarios.
3. **AI & Generative Hacking**: Establish a powerful, scriptable API that an AI agent (MCP) can use to perform complex, generative tasks on the ROM.
## 3. Proposed CLI Architecture: Resource-Oriented Commands
The CLI has adopted a `z3ed <resource> <action> [options]` structure, similar to modern CLIs like `gcloud` or `kubectl`, improving clarity and extensibility.
### 3.1. Top-Level Resources
- `rom`: Commands for interacting with the ROM file itself.
- `patch`: Commands for applying and creating patches.
- `gfx`: Commands for graphics manipulation.
- `palette`: Commands for palette manipulation.
- `overworld`: Commands for overworld editing.
- `dungeon`: Commands for dungeon editing.
- `sprite`: Commands for sprite management and creation.
- `test`: Commands for running tests.
- `tui`: The entrypoint for the enhanced Text User Interface.
- `agent`: Commands for interacting with the AI agent.
### 3.2. Example Command Mapping
The command mapping has been successfully implemented, transitioning from the old flat structure to the new resource-oriented approach.
## 4. New Features & Commands
### 4.1. For the ROM Hacker (Power & Scriptability)
These commands focus on exporting data to and from the original SCAD (Nintendo Super Famicom/SNES CAD) binary formats found in the gigaleak, as well as other relevant binary formats. This enables direct interaction with development assets, version control, and sharing. Many of these commands have been implemented or are in progress.
- **Dungeon Editing**: Commands for exporting, importing, listing, and adding objects.
- **Overworld Editing**: Commands for getting, setting tiles, listing, and moving sprites.
- **Graphics & Palettes**: Commands for exporting/importing sheets and palettes.
### 4.2. For Testing & Automation
- **ROM Validation & Comparison**: `z3ed rom validate`, `z3ed rom diff`, and `z3ed rom generate-golden` have been implemented.
- **Test Execution**: `z3ed test run` and `z3ed test list-suites` are in progress.
## 5. TUI Enhancements
The `--tui` flag now launches a significantly enhanced, interactive terminal application built with FTXUI. The TUI has been decomposed into a set of modular components, with each command handler responsible for its own TUI representation, making it more extensible and easier to maintain.
- **Dashboard View**: The main screen is evolving into a dashboard.
- **Interactive Palette Editor**: In progress.
- **Interactive Hex Viewer**: Implemented.
- **Command Palette**: In progress.
- **Tabbed Layout**: Implemented.
## 6. Generative & Agentic Workflows (MCP Integration)
The redesigned CLI serves as the foundational API for an AI-driven Model-Code-Program (MCP) loop. The AI agent's "program" is a script of `z3ed` commands.
### 6.1. The Generative Workflow
The generative workflow has been refined to incorporate more detailed planning and verification steps, leveraging the `z3ed agent` commands.
### 6.2. Key Enablers
- **Granular Commands**: The CLI provides commands to manipulate data within the binary formats (e.g., `palette set-color`, `gfx set-pixel`), abstracting complexity from the AI agent.
- **Idempotency**: Commands are designed to be idempotent where possible.
- **SpriteBuilder CLI**: Deprioritized for now, pending further research and development of the underlying assembly generation capabilities.
## 7. Implementation Roadmap
### Phase 1: Core CLI & TUI Foundation (Done)
- **CLI Structure**: Implemented.
- **Command Migration**: Implemented.
- **TUI Decomposition**: Implemented.
### Phase 2: Interactive TUI & Command Palette (Done)
- **Interactive Palette Editor**: Implemented.
- **Interactive Hex Viewer**: Implemented.
- **Command Palette**: Implemented.
### Phase 3: Testing & Project Management (Done)
- **`rom validate`**: Implemented.
- **`rom diff`**: Implemented.
- **`rom generate-golden`**: Implemented.
- **Project Scaffolding**: Implemented.
### Phase 4: Agentic Framework & Generative AI (In Progress)
- **`z3ed agent` command**: Implemented with `run`, `plan`, `diff`, `test`, `commit`, `revert`, and `learn` subcommands.
- **AI Model Interaction**: In progress, with `MockAIService` and `GeminiAIService` (conditional) implemented.
- **Execution Loop (MCP)**: In progress, with command parsing and execution logic.
- **Leveraging `ImGuiTestEngine`**: In progress, with `agent test` subcommand.
- **Granular Data Commands**: Not started, but planned.
- **SpriteBuilder CLI**: Deprioritized.
### Phase 5: Code Structure & UX Improvements (Completed)
- **Modular Architecture**: Refactored CLI handlers into clean, focused modules with proper separation of concerns.
- **TUI Component System**: Implemented `TuiComponent` interface for consistent UI components across the application.
- **Unified Command Interface**: Standardized `CommandHandler` base class with both CLI and TUI execution paths.
- **Error Handling**: Improved error handling with consistent `absl::Status` usage throughout the codebase.
- **Build System**: Streamlined CMake configuration with proper dependency management and conditional compilation.
- **Code Quality**: Resolved linting errors and improved code maintainability through better header organization and forward declarations.
## 8. Agentic Framework Architecture - Advanced Dive
The agentic framework is designed to allow an AI agent to make edits to the ROM based on high-level natural language prompts. The framework is built around the `z3ed` CLI and the `ImGuiTestEngine`. This section provides a more advanced look into its architecture and future development.
### 8.1. The `z3ed agent` Command
The `z3ed agent` command is the main entry point for the agent. It has the following subcommands:
- `run --prompt "..."`: Executes a prompt by generating and running a sequence of `z3ed` commands.
- `plan --prompt "..."`: Shows the sequence of `z3ed` commands the AI plans to execute.
- `diff`: Shows a diff of the changes made to the ROM after running a prompt.
- `test --prompt "..."`: Generates changes and then runs an `ImGuiTestEngine` test to verify them.
- `commit`: Saves the modified ROM and any new assets to the project.
- `revert`: Reverts the changes made by the agent.
- `learn --description "..."`: Records a sequence of user actions (CLI commands and GUI interactions) and associates them with a natural language description, allowing the agent to learn new workflows.
### 8.2. The Agentic Loop (MCP) - Detailed Workflow
1. **Model (Planner)**: The agent receives a high-level natural language prompt. It leverages an LLM to break down this goal into a detailed, executable plan. This plan is a sequence of `z3ed` CLI commands, potentially interleaved with `ImGuiTestEngine` test steps for intermediate verification. The LLM's prompt includes the user's request, a comprehensive list of available `z3ed` commands (with their parameters and expected effects), and relevant contextual information about the current ROM state (e.g., loaded ROM, project files, current editor view).
2. **Code (Command & Test Generation)**: The LLM returns the generated plan as a structured JSON object. This JSON object contains an array of actions, where each action specifies a `z3ed` command (with its arguments) or an `ImGuiTestEngine` test to execute. This structured output is crucial for reliable parsing and execution by the `z3ed` agent.
3. **Program (Execution Engine)**: The `z3ed agent` parses the JSON plan and executes each command sequentially. For `z3ed` commands, it directly invokes the corresponding internal `CommandHandler` methods. For `ImGuiTestEngine` steps, it launches the `yaze_test` executable with the appropriate test arguments. The output (stdout, stderr, exit codes) of each executed command is captured. This output, along with any visual feedback from `ImGuiTestEngine` (e.g., screenshots), can be fed back to the LLM for iterative refinement of the plan.
4. **Verification (Tester)**: The `ImGuiTestEngine` plays a critical role here. After the agent executes a sequence of commands, it can generate and run a specific `ImGuiTestEngine` script. This script can interact with the YAZE GUI (e.g., open a specific editor, navigate to a location, assert visual properties) to verify that the changes were applied correctly and as intended. The results of these tests (pass/fail, detailed logs, comparison screenshots) are reported back to the user and can be used by the LLM to self-correct or refine its strategy.
### 8.3. AI Model & Protocol Strategy
- **Models**: The framework will support both local and remote AI models, offering flexibility and catering to different user needs.
- **Local Models (macOS Setup)**: For privacy, offline use, and reduced operational costs, integration with local LLMs via [Ollama](https://ollama.ai/) is a priority. Users can easily install Ollama on macOS and pull models optimized for code generation, such as `codellama:7b`. The `z3ed` agent will communicate with Ollama's local API endpoint.
- **Remote Models (Gemini API)**: For more complex tasks requiring advanced reasoning capabilities, integration with powerful remote models like the Gemini API will be available. Users will need to provide a `GEMINI_API_KEY` environment variable. A new `GeminiAIService` class will be implemented to handle the secure API requests and responses.
- **Protocol**: A robust, yet simple, JSON-based protocol will be used for communication between `z3ed` and the AI model. This ensures structured data exchange, critical for reliable parsing and execution. The `z3ed` tool will serialize the user's prompt, current ROM context, available `z3ed` commands, and any relevant `ImGuiTestEngine` capabilities into a JSON object. The AI model will be expected to return a JSON object containing the sequence of commands to be executed, along with potential explanations or confidence scores.
### 8.4. GUI Integration & User Experience
- **Agent Control Panel**: A dedicated TUI/GUI panel will be created for managing the agent. This panel will serve as the primary interface for users to interact with the AI. It will feature:
- A multi-line text input for entering natural language prompts.
- Buttons for `Run`, `Plan`, `Diff`, `Test`, `Commit`, `Revert`, and `Learn` actions.
- A real-time log view displaying the agent's thought process, executed commands, and their outputs.
- A status bar indicating the agent's current state (e.g., "Idle", "Planning", "Executing Commands", "Verifying Changes").
- **Diff Editing UI**: A TUI-based visual diff viewer will be implemented. This UI will present a side-by-side comparison of the original ROM state (or a previous checkpoint) and the changes proposed or made by the agent. Users will be able to:
- Navigate through individual differences (e.g., changed bytes, modified tiles, added objects).
- Highlight specific changes.
- Accept or reject individual changes or groups of changes, providing fine-grained control over the agent's output.
- **Interactive Planning**: The agent will present its generated plan in a human-readable format within the GUI. Users will have the opportunity to:
- Review each step of the plan.
- Approve the entire plan for execution.
- Reject specific steps or the entire plan.
- Edit the plan directly (e.g., modify command arguments, reorder steps, insert new commands) before allowing the agent to proceed.
### 8.5. Testing & Verification
- **`ImGuiTestEngine` Integration**: The agent will be able to dynamically generate and execute `ImGuiTestEngine` tests. This allows for automated visual verification of the agent's work, ensuring that changes are not only functionally correct but also visually appealing and consistent with design principles. The agent can be trained to generate test scripts that assert specific pixel colors, UI element positions, or overall visual layouts.
- **Mock Testing Framework**: A robust "mock" mode will be implemented for the `z3ed agent`. In this mode, the agent will simulate the execution of commands without modifying the actual ROM. This is crucial for safe and fast testing of the agent's planning and command generation capabilities. The existing `MockRom` class will be extended to fully support all `z3ed` commands, providing a consistent interface for both real and mock execution.
- **User-Facing Tests**: A "tutorial" or "challenge" mode will be created where users can test the agent with a series of predefined tasks. This will serve as an educational tool for users to understand the agent's capabilities and provide a way to benchmark its performance against specific ROM hacking challenges.
### 8.6. Safety & Sandboxing
- **Dry Run Mode**: The agent will always offer a "dry run" mode, where it only shows the commands it would execute without making any actual changes to the ROM. This provides a critical safety net for users.
- **Command Whitelisting**: The agent's execution environment will enforce a strict command whitelisting policy. Only a predefined set of "safe" `z3ed` commands will be executable by the AI. Any attempt to execute an unauthorized command will be blocked.
- **Resource Limits**: The agent will operate within defined resource limits (e.g., maximum number of commands per plan, maximum data modification size) to prevent unintended extensive changes or infinite loops.
- **Human Oversight**: Given the inherent unpredictability of AI models, human oversight will be a fundamental principle. The interactive planning and diff editing UIs are designed to keep the user in control at all times.
### 8.7. Optional JSON Dependency
To avoid breaking platform builds where a JSON library is not available or desired, the JSON-related code will be conditionally compiled using a preprocessor macro (e.g., `YAZE_WITH_JSON`). When this macro is not defined, the agentic features that rely on JSON will be disabled. The `nlohmann/json` library will be added as a submodule to the project and included in the build only when `YAZE_WITH_JSON` is defined.
### 8.8. Contextual Awareness & Feedback Loop
- **Contextual Information**: The agent's prompts to the LLM will be enriched with comprehensive contextual information, including:
- The current state of the loaded ROM (e.g., ROM header, loaded assets, current editor view).
- Relevant project files (e.g., `.yaze` project configuration, symbol files).
- User preferences and previous interactions.
- A dynamic list of available `z3ed` commands and their detailed usage.
- **Feedback Loop for Learning**: The results of `ImGuiTestEngine` verifications and user accept/reject actions will form a crucial feedback loop. This data can be used to fine-tune the LLM or train smaller, specialized models to improve the agent's planning and command generation capabilities over time.
### 8.9. Error Handling and Recovery
- **Robust Error Reporting**: The agent will provide clear and actionable error messages when commands fail or unexpected situations arise.
- **Rollback Mechanisms**: The `revert` command provides a basic rollback. More advanced mechanisms, such as transactional changes or snapshotting, could be explored for complex multi-step operations.
- **Interactive Debugging**: In case of errors, the agent could pause execution and allow the user to inspect the current state, modify the plan, or provide corrective instructions.
### 8.10. Extensibility
- **Modular Command Handlers**: The `z3ed` CLI's modular design allows for easy addition of new commands, which automatically become available to the AI agent.
- **Pluggable AI Models**: The `AIService` interface enables seamless integration of different AI models (local or remote) without modifying the core agent logic.
- **Custom Test Generation**: Users or developers can extend the `ImGuiTestEngine` capabilities to create custom verification tests for specific hacking scenarios.
## 9. UX Improvements and Architectural Decisions
### 9.1. TUI Component Architecture
The TUI system has been redesigned around a consistent component architecture:
- **`TuiComponent` Interface**: All UI components implement a standard interface with a `Render()` method, ensuring consistency across the application.
- **Component Composition**: Complex UIs are built by composing simpler components, making the code more maintainable and testable.
- **Event Handling**: Standardized event handling patterns across all components for consistent user experience.
### 9.2. Command Handler Unification
The CLI and TUI systems now share a unified command handler architecture:
- **Dual Execution Paths**: Each command handler supports both CLI (`Run()`) and TUI (`RunTUI()`) execution modes.
- **Shared State Management**: Common functionality like ROM loading and validation is centralized in the base `CommandHandler` class.
- **Consistent Error Handling**: All commands use `absl::Status` for uniform error reporting across CLI and TUI modes.
### 9.3. Interface Consolidation
Several interfaces have been combined and simplified:
- **Unified Menu System**: The main menu now serves as a central hub for both direct command execution and TUI mode switching.
- **Integrated Help System**: Help information is accessible from both CLI and TUI modes with consistent formatting.
- **Streamlined Navigation**: Reduced cognitive load by consolidating related functionality into single interfaces.
### 9.4. Code Organization Improvements
The codebase has been restructured for better maintainability:
- **Header Organization**: Proper forward declarations and include management to reduce compilation dependencies.
- **Namespace Management**: Clean namespace usage to avoid conflicts and improve code clarity.
- **Build System Optimization**: Streamlined CMake configuration with conditional compilation for optional features.
### 9.5. Future UX Enhancements
Based on the current architecture, several UX improvements are planned:
- **Progressive Disclosure**: Complex commands will offer both simple and advanced modes.
- **Context-Aware Help**: Help text will adapt based on current ROM state and available commands.
- **Undo/Redo System**: Command history tracking for safer experimentation.
- **Batch Operations**: Support for executing multiple related commands as a single operation.

View File

@@ -1,4 +1,4 @@
# YAZE Performance Optimization Summary # yaze Performance Optimization Summary
## 🎉 **Massive Performance Improvements Achieved!** ## 🎉 **Massive Performance Improvements Achieved!**

View File

@@ -41,7 +41,7 @@ private List<Tile32> AssembleMap32Tiles()
} }
``` ```
#### YAZE C++ Logic (`overworld.cc:AssembleMap32Tiles`) #### yaze C++ Logic (`overworld.cc:AssembleMap32Tiles`)
```cpp ```cpp
absl::Status Overworld::AssembleMap32Tiles() { absl::Status Overworld::AssembleMap32Tiles() {
ASSIGN_OR_RETURN(auto count, rom_->ReadLong(kMap32TilesCountAddr)); ASSIGN_OR_RETURN(auto count, rom_->ReadLong(kMap32TilesCountAddr));
@@ -79,7 +79,7 @@ private List<Tile16> AssembleMap16Tiles()
} }
``` ```
#### YAZE C++ Logic (`overworld.cc:AssembleMap16Tiles`) #### yaze C++ Logic (`overworld.cc:AssembleMap16Tiles`)
```cpp ```cpp
absl::Status Overworld::AssembleMap16Tiles() { absl::Status Overworld::AssembleMap16Tiles() {
ASSIGN_OR_RETURN(auto bank, rom_->ReadByte(kMap16TilesBankAddr)); ASSIGN_OR_RETURN(auto bank, rom_->ReadByte(kMap16TilesBankAddr));
@@ -108,7 +108,7 @@ private (ushort[,], ushort[,], ushort[,]) DecompressAllMapTiles()
} }
``` ```
#### YAZE C++ Logic (`overworld.cc:DecompressAllMapTiles`) #### yaze C++ Logic (`overworld.cc:DecompressAllMapTiles`)
```cpp ```cpp
absl::StatusOr<OverworldMapTiles> Overworld::DecompressAllMapTiles() { absl::StatusOr<OverworldMapTiles> Overworld::DecompressAllMapTiles() {
// Use HyruleMagicDecompress for each world // Use HyruleMagicDecompress for each world
@@ -144,7 +144,7 @@ private EntranceOW[] LoadEntrances()
} }
``` ```
#### YAZE C++ Logic (`overworld.cc:LoadEntrances`) #### yaze C++ Logic (`overworld.cc:LoadEntrances`)
```cpp ```cpp
absl::Status Overworld::LoadEntrances() { absl::Status Overworld::LoadEntrances() {
for (int i = 0; i < kNumEntrances; i++) { for (int i = 0; i < kNumEntrances; i++) {
@@ -188,7 +188,7 @@ private EntranceOW[] LoadHoles()
} }
``` ```
#### YAZE C++ Logic (`overworld.cc:LoadHoles`) #### yaze C++ Logic (`overworld.cc:LoadHoles`)
```cpp ```cpp
absl::Status Overworld::LoadHoles() { absl::Status Overworld::LoadHoles() {
for (int i = 0; i < kNumHoles; i++) { for (int i = 0; i < kNumHoles; i++) {
@@ -233,7 +233,7 @@ private List<RoomPotSaveEditor> LoadItems()
} }
``` ```
#### YAZE C++ Logic (`overworld.cc:LoadItems`) #### yaze C++ Logic (`overworld.cc:LoadItems`)
```cpp ```cpp
absl::Status Overworld::LoadItems() { absl::Status Overworld::LoadItems() {
ASSIGN_OR_RETURN(auto asm_version, rom_->ReadByte(kOverworldCustomASMAddr)); ASSIGN_OR_RETURN(auto asm_version, rom_->ReadByte(kOverworldCustomASMAddr));
@@ -275,7 +275,7 @@ private List<Sprite>[] LoadSprites()
} }
``` ```
#### YAZE C++ Logic (`overworld.cc:LoadSprites`) #### yaze C++ Logic (`overworld.cc:LoadSprites`)
```cpp ```cpp
absl::Status Overworld::LoadSprites() { absl::Status Overworld::LoadSprites() {
// Three game states: 0=rain, 1=pre-Agahnim, 2=post-Agahnim // Three game states: 0=rain, 1=pre-Agahnim, 2=post-Agahnim
@@ -323,7 +323,7 @@ public OverworldMap[] AssignMapSizes(OverworldMap[] givenMaps)
} }
``` ```
#### YAZE C++ Logic (`overworld.cc:AssignMapSizes`) #### yaze C++ Logic (`overworld.cc:AssignMapSizes`)
```cpp ```cpp
absl::Status Overworld::AssignMapSizes() { absl::Status Overworld::AssignMapSizes() {
for (int i = 0; i < kNumOverworldMaps; i++) { for (int i = 0; i < kNumOverworldMaps; i++) {

View File

@@ -1,4 +1,4 @@
# YAZE Graphics System Optimizations - Implementation Summary # yaze Graphics System Optimizations - Implementation Summary
## Overview ## Overview
This document summarizes the comprehensive graphics optimizations implemented in the YAZE ROM hacking editor, targeting significant performance improvements for Link to the Past graphics editing workflows. This document summarizes the comprehensive graphics optimizations implemented in the YAZE ROM hacking editor, targeting significant performance improvements for Link to the Past graphics editing workflows.

View File

@@ -1,4 +1,4 @@
# YAZE Documentation # yaze Documentation
Yet Another Zelda3 Editor - A comprehensive ROM editor for The Legend of Zelda: A Link to the Past. Yet Another Zelda3 Editor - A comprehensive ROM editor for The Legend of Zelda: A Link to the Past.

View File

@@ -1,4 +1,4 @@
# YAZE Overworld Testing Guide # yaze Overworld Testing Guide
## Overview ## Overview
@@ -211,7 +211,7 @@ cmake --build build --target extract_vanilla_values
The test runner generates comprehensive reports: The test runner generates comprehensive reports:
```markdown ```markdown
# YAZE Overworld Test Report # yaze Overworld Test Report
**Generated:** 2024-01-15 14:30:00 **Generated:** 2024-01-15 14:30:00
**ROM:** zelda3.sfc **ROM:** zelda3.sfc

View File

@@ -1,4 +1,4 @@
# YAZE Build Scripts # yaze Build Scripts
This directory contains build and setup scripts for YAZE development on different platforms. This directory contains build and setup scripts for YAZE development on different platforms.

View File

@@ -242,7 +242,7 @@ absl::Status YazeProject::SaveToYazeFormat() {
} }
// Write header comment // Write header comment
file << "# YAZE Project File\n"; file << "# yaze Project File\n";
file << "# Format Version: 2.0\n"; file << "# Format Version: 2.0\n";
file << "# Generated by YAZE " << metadata.yaze_version << "\n"; file << "# Generated by YAZE " << metadata.yaze_version << "\n";
file << "# Last Modified: " << metadata.last_modified << "\n\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); std::filesystem::path abs_labels = GetAbsolutePath(labels_filename);
if (!std::filesystem::exists(abs_labels)) { if (!std::filesystem::exists(abs_labels)) {
std::ofstream labels_file(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 << "# Format: [type] key=value\n\n";
labels_file.close(); labels_file.close();
} }
@@ -755,7 +755,7 @@ bool ResourceLabelManager::SaveLabels() {
std::ofstream file(filename_); std::ofstream file(filename_);
if (!file.is_open()) return false; 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"; file << "# Format: [type] followed by key=value pairs\n\n";
for (const auto& [type, type_labels] : labels_) { 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(); return absl::OkStatus();
} }
absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_data, absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_bitmap_data,
std::vector<uint8_t>& map_bitmap_data, std::vector<uint8_t>& map_data,
std::vector<uint8_t>& cgx_loaded) { std::vector<uint8_t>& cgx_loaded) {
const std::vector<uint16_t> dimensions = {0x000, 0x400, 0x800, 0xC00}; const std::vector<uint16_t> dimensions = {0x000, 0x400, 0x800, 0xC00};
uint8_t p = 0; uint8_t p = 0;
@@ -155,6 +155,34 @@ absl::Status DrawScrWithCgx(uint8_t bpp, std::vector<uint8_t>& map_data,
return absl::OkStatus(); 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> DecodeColFile(const std::string_view filename) {
std::vector<SDL_Color> decoded_col; std::vector<SDL_Color> decoded_col;
std::ifstream file(filename.data(), std::ios::binary | std::ios::ate); 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; 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( absl::Status DecodeObjFile(
std::string_view filename, std::vector<uint8_t>& obj_data, std::string_view filename, std::vector<uint8_t>& obj_data,
std::vector<uint8_t> actual_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>& map_data,
std::vector<uint8_t>& cgx_loaded); 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 * @brief Decode color file
*/ */
@@ -87,6 +94,11 @@ absl::Status DecodeObjFile(
std::unordered_map<std::string, std::vector<uint8_t>> decoded_obj, std::unordered_map<std::string, std::vector<uint8_t>> decoded_obj,
std::vector<uint8_t>& decoded_extra_obj, int& obj_loaded); 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 gfx
} // namespace yaze } // 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); 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 << "# Generated by YAZE Theme Editor\n";
ss << "name=" << theme.name << "\n"; ss << "name=" << theme.name << "\n";
ss << "description=" << theme.description << "\n"; ss << "description=" << theme.description << "\n";

View File

@@ -1,653 +1,39 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <vector>
#include <map>
#include <memory>
#include <SDL.h>
#include "absl/flags/flag.h" #include "absl/flags/flag.h"
#include "absl/flags/parse.h" #include "absl/flags/parse.h"
#include "absl/flags/usage.h" #include "absl/status/status.h"
#ifndef _WIN32
#include <unistd.h>
#include <sys/wait.h>
#endif
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
#include "absl/strings/str_format.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 "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 Text User Interface");
ABSL_FLAG(bool, tui, false, "Launch the Text User Interface");
ABSL_FLAG(bool, verbose, false, "Enable verbose output");
ABSL_FLAG(std::string, rom, "", "Path to the ROM file"); ABSL_FLAG(std::string, rom, "", "Path to the ROM file");
// Command-specific flags
ABSL_FLAG(std::string, output, "", "Output file path"); 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(bool, backup, true, "Create a backup before modifying files");
ABSL_FLAG(std::string, test, "", "Name of the test to run"); ABSL_FLAG(std::string, test, "", "Name of the test to run");
ABSL_FLAG(bool, show_gui, false, "Show the test engine GUI"); 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[]) { int main(int argc, char* argv[]) {
#endif // Parse command line flags
absl::SetProgramUsageMessage( absl::ParseCommandLine(argc, argv);
"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"
);
auto args = absl::ParseCommandLine(argc, argv); // Check if TUI mode is requested
yaze::cli::ModernCLI cli;
// Handle TUI flag
if (absl::GetFlag(FLAGS_tui)) { if (absl::GetFlag(FLAGS_tui)) {
yaze::cli::ShowMain(); yaze::cli::ShowMain();
return 0; return 0;
} }
// Handle command line arguments // Run CLI commands
if (args.size() < 2) { yaze::cli::ModernCLI cli;
cli.ShowHelp(); auto status = cli.Run(argc, argv);
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);
if (!status.ok()) { if (!status.ok()) {
std::cerr << "Error: " << status.message() << std::endl; 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; 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 "cli/z3ed.h"
#include "app/zelda3/dungeon/dungeon_editor_system.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 yaze {
namespace cli { 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]); 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 rom_.LoadFromFile(rom_file);
std::cout << "Dungeon export for room " << room_id << " not yet implemented." << std::endl; 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(); return absl::OkStatus();
} }

View File

@@ -1,4 +1,10 @@
#include "cli/z3ed.h" #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 yaze {
namespace cli { 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>"); return absl::InvalidArgumentError("Usage: gfx export-sheet <sheet_id> --to <file>");
} }
// TODO: Implement gfx export logic int sheet_id = std::stoi(arg_vec[0]);
std::cout << "Gfx export not yet implemented." << std::endl; 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(); 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>"); return absl::InvalidArgumentError("Usage: gfx import-sheet <sheet_id> --from <file>");
} }
// TODO: Implement gfx import logic int sheet_id = std::stoi(arg_vec[0]);
std::cout << "Gfx import not yet implemented." << std::endl; 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(); 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/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 yaze {
namespace cli { 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) { absl::Status PaletteExport::Run(const std::vector<std::string>& arg_vec) {
if (arg_vec.size() < 2) { if (arg_vec.size() < 3) {
return absl::InvalidArgumentError("Usage: palette export --group <group> --id <id> --format <format>"); return absl::InvalidArgumentError("Usage: palette export --group <group> --id <id> --to <file>");
} }
// TODO: Implement palette export logic // TODO: Implement proper argument parsing
std::cout << "Palette export not yet implemented." << std::endl; 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(); return absl::OkStatus();
} }
absl::Status PaletteImport::Run(const std::vector<std::string>& arg_vec) { 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>"); return absl::InvalidArgumentError("Usage: palette import --group <group> --id <id> --from <file>");
} }
// TODO: Implement palette import logic // TODO: Implement proper argument parsing
std::cout << "Palette import not yet implemented." << std::endl; 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(); return absl::OkStatus();
} }

View File

@@ -1,6 +1,14 @@
#include "asar-dll-bindings/c/asar.h" #include "asar-dll-bindings/c/asar.h"
#include "cli/z3ed.h" #include "cli/z3ed.h"
#include "cli/tui/asar_patch.h"
#include "util/bps.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 yaze {
namespace cli { namespace cli {
@@ -26,97 +34,56 @@ absl::Status ApplyPatch::Run(const std::vector<std::string>& arg_vec) {
return absl::OkStatus(); return absl::OkStatus();
} }
AsarPatch::AsarPatch() {}
absl::Status AsarPatch::Run(const std::vector<std::string>& arg_vec) { absl::Status AsarPatch::Run(const std::vector<std::string>& arg_vec) {
if (arg_vec.size() < 2) { if (arg_vec.empty()) {
return absl::InvalidArgumentError("Usage: asar <patch_file> <rom_file>"); 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.");
} }
std::string patch_filename = arg_vec[0]; rom_.LoadFromFile(rom_file);
std::string rom_filename = arg_vec[1]; if (!rom_.is_loaded()) {
return absl::AbortedError("Failed to load ROM.");
}
// Load ROM file app::core::AsarWrapper wrapper;
RETURN_IF_ERROR(rom_.LoadFromFile(rom_filename)) auto init_status = wrapper.Initialize();
if (!init_status.ok()) {
return init_status;
}
// Get ROM data
auto rom_data = rom_.vector(); auto rom_data = rom_.vector();
int buflen = static_cast<int>(rom_data.size()); auto patch_result = wrapper.ApplyPatch(patch_file, rom_data);
int romlen = buflen;
// Ensure we have enough buffer space if (!patch_result.ok()) {
const int max_rom_size = asar_maxromsize(); return patch_result.status();
if (buflen < max_rom_size) {
rom_data.resize(max_rom_size, 0);
buflen = max_rom_size;
} }
// Apply Asar patch const auto& result = patch_result.value();
if (!asar_patch(patch_filename.c_str(), if (!result.success) {
reinterpret_cast<char*>(rom_data.data()), return absl::AbortedError(absl::StrJoin(result.errors, "; "));
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);
} }
// Resize ROM to actual size // TODO: Save the patched ROM
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;
}
}
std::cout << "Patch applied successfully!" << std::endl;
return absl::OkStatus(); 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) { absl::Status CreatePatch::Run(const std::vector<std::string>& arg_vec) {
std::vector<uint8_t> source; std::vector<uint8_t> source;
std::vector<uint8_t> target; 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_cat.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "util/bps.h" #include "util/bps.h"
#include "app/core/platform/file_dialog.h" #include "app/core/platform/file_dialog.h"
#include "app/core/asar_wrapper.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 yaze {
namespace cli { namespace cli {
@@ -219,131 +221,65 @@ void GenerateSaveFileComponent(ftxui::ScreenInteractive &screen) {
screen.Loop(renderer); 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", [&] { auto apply_button = Button("Apply Asar Patch", [&] {
if (patch_file.empty()) { if (asm_file.empty()) {
app_context.error_message = "Please specify an assembly patch file"; app_context.error_message = "Please specify an assembly 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); SwitchComponents(screen, LayoutID::kError);
return; return;
} }
try { try {
app::core::AsarWrapper wrapper; // Use the command handler directly
auto init_status = wrapper.Initialize(); AsarPatch handler;
if (!init_status.ok()) { auto status = handler.Run({asm_file});
app_context.error_message = absl::StrCat("Failed to initialize Asar: ", init_status.message()); if (status.ok()) {
SwitchComponents(screen, LayoutID::kError); output_message = "✅ Asar patch applied successfully!";
return; 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) { } catch (const std::exception& e) {
app_context.error_message = "Exception: " + std::string(e.what()); output_message = "Exception: " + std::string(e.what());
SwitchComponents(screen, LayoutID::kError); output_color = Color::Red;
} }
}); });
auto show_symbols_button = Button("Show Symbols", [&] {
show_symbols = !show_symbols;
});
auto back_button = Button("Back to Main Menu", [&] { auto back_button = Button("Back to Main Menu", [&] {
output_message.clear(); output_message.clear();
symbols_list.clear();
show_symbols = false;
SwitchComponents(screen, LayoutID::kMainMenu); SwitchComponents(screen, LayoutID::kMainMenu);
}); });
std::vector<Component> container_items = { auto container = Container::Vertical({
patch_file_input, asm_file_input,
apply_button, apply_button,
}; back_button,
});
if (!output_message.empty()) {
container_items.push_back(show_symbols_button);
}
container_items.push_back(back_button);
auto container = Container::Vertical(container_items);
auto renderer = Renderer(container, [&] { auto renderer = Renderer(container, [&] {
std::vector<Element> elements = { std::vector<Element> elements = {
text("Apply Asar Assembly Patch") | center | bold, text("Apply Asar Patch") | center | bold,
separator(), separator(),
text("Assembly Patch File:"), text("Assembly File:"),
patch_file_input->Render(), asm_file_input->Render(),
separator(), separator(),
apply_button->Render() | center, apply_button->Render() | center,
}; };
if (!output_message.empty()) { if (!output_message.empty()) {
elements.push_back(separator()); elements.push_back(separator());
elements.push_back(text(output_message) | color(Color::Green)); elements.push_back(text(output_message) | color(output_color));
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(separator()); elements.push_back(separator());
@@ -355,6 +291,26 @@ void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen) {
screen.Loop(renderer); 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) { void ExtractSymbolsComponent(ftxui::ScreenInteractive &screen) {
static std::string asm_file; static std::string asm_file;
static std::vector<std::string> symbols_list; static std::vector<std::string> symbols_list;
@@ -576,128 +532,7 @@ Element ColorBox(const Color &color) {
return ftxui::text(" ") | ftxui::bgcolor(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) { void HelpComponent(ftxui::ScreenInteractive &screen) {
auto help_text = vbox({ auto help_text = vbox({
@@ -846,6 +681,72 @@ void HelpComponent(ftxui::ScreenInteractive &screen) {
screen.Loop(renderer); 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) { void MainMenuComponent(ftxui::ScreenInteractive &screen) {
// Tracks which menu item is selected. // Tracks which menu item is selected.
static int selected = 0; static int selected = 0;
@@ -902,6 +803,12 @@ void MainMenuComponent(ftxui::ScreenInteractive &screen) {
case MainMenuEntry::kPaletteEditor: case MainMenuEntry::kPaletteEditor:
SwitchComponents(screen, LayoutID::kPaletteEditor); SwitchComponents(screen, LayoutID::kPaletteEditor);
return true; return true;
case MainMenuEntry::kHexViewer:
SwitchComponents(screen, LayoutID::kHexViewer);
return true;
case MainMenuEntry::kCommandPalette:
SwitchComponents(screen, LayoutID::kCommandPalette);
return true;
case MainMenuEntry::kHelp: case MainMenuEntry::kHelp:
SwitchComponents(screen, LayoutID::kHelp); SwitchComponents(screen, LayoutID::kHelp);
return true; return true;
@@ -951,6 +858,14 @@ void ShowMain() {
case LayoutID::kPaletteEditor: { case LayoutID::kPaletteEditor: {
PaletteEditorComponent(screen); PaletteEditorComponent(screen);
} break; } 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: { case LayoutID::kHelp: {
HelpComponent(screen); HelpComponent(screen);
} break; } break;

View File

@@ -23,6 +23,8 @@ const std::vector<std::string> kMainMenuEntries = {
"Validate Assembly", "Validate Assembly",
"Generate Save File", "Generate Save File",
"Palette Editor", "Palette Editor",
"Hex Viewer",
"Command Palette",
"Help", "Help",
"Exit", "Exit",
}; };
@@ -35,6 +37,8 @@ enum class MainMenuEntry {
kValidateAssembly, kValidateAssembly,
kGenerateSaveFile, kGenerateSaveFile,
kPaletteEditor, kPaletteEditor,
kHexViewer,
kCommandPalette,
kHelp, kHelp,
kExit, kExit,
}; };
@@ -47,6 +51,8 @@ enum class LayoutID {
kValidateAssembly, kValidateAssembly,
kGenerateSaveFile, kGenerateSaveFile,
kPaletteEditor, kPaletteEditor,
kHexViewer,
kCommandPalette,
kHelp, kHelp,
kExit, kExit,
kMainMenu, 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/dungeon.cc
cli/handlers/gfx.cc cli/handlers/gfx.cc
cli/handlers/palette.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/rom.cc
app/core/project.cc app/core/project.cc
app/core/asar_wrapper.cc app/core/asar_wrapper.cc
@@ -43,6 +56,14 @@ add_executable(
${IMGUI_SRC} ${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( target_include_directories(
z3ed PUBLIC z3ed PUBLIC
${CMAKE_SOURCE_DIR}/src/lib/ ${CMAKE_SOURCE_DIR}/src/lib/

View File

@@ -6,7 +6,6 @@
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
#include "absl/status/status.h" #include "absl/status/status.h"
@@ -14,14 +13,25 @@
#include "app/snes.h" #include "app/snes.h"
#include "util/macro.h" #include "util/macro.h"
// Forward declarations
namespace ftxui {
class ScreenInteractive;
}
namespace yaze { namespace yaze {
namespace cli { namespace cli {
// Forward declaration
class TuiComponent;
class CommandHandler { class CommandHandler {
public: public:
CommandHandler() = default; CommandHandler() = default;
virtual ~CommandHandler() = default; virtual ~CommandHandler() = default;
virtual absl::Status Run(const std::vector<std::string>& arg_vec) = 0; virtual absl::Status Run(const std::vector<std::string>& arg_vec) = 0;
virtual void RunTUI(ftxui::ScreenInteractive& screen) {
// Default implementation does nothing
}
Rom rom_; Rom rom_;
}; };
@@ -33,7 +43,9 @@ class ApplyPatch : public CommandHandler {
class AsarPatch : public CommandHandler { class AsarPatch : public CommandHandler {
public: public:
AsarPatch();
absl::Status Run(const std::vector<std::string>& arg_vec) override; absl::Status Run(const std::vector<std::string>& arg_vec) override;
void RunTUI(ftxui::ScreenInteractive& screen) override;
}; };
class CreatePatch : public CommandHandler { class CreatePatch : public CommandHandler {
@@ -56,6 +68,20 @@ class GfxImport : public CommandHandler {
absl::Status Run(const std::vector<std::string>& arg_vec) override; 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 { class PaletteExport : public CommandHandler {
public: public:
absl::Status Run(const std::vector<std::string>& arg_vec) override; 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; 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 { class Open : public CommandHandler {
public: public:
absl::Status Run(const std::vector<std::string>& arg_vec) override { absl::Status Run(const std::vector<std::string>& arg_vec) override {

View File

@@ -1,4 +1,4 @@
# YAZE Test Suite # yaze Test Suite
This directory contains the comprehensive test suite for YAZE, organized for optimal AI agent testing and development workflow. This directory contains the comprehensive test suite for YAZE, organized for optimal AI agent testing and development workflow.

1
third_party/httplib vendored Submodule

Submodule third_party/httplib added at 35c52c1ab9

1
third_party/json vendored Submodule

Submodule third_party/json added at 3ed64e502a