diff --git a/src/cli/handlers/project.cc b/src/cli/handlers/project.cc index 19d1851b..d5a467c9 100644 --- a/src/cli/handlers/project.cc +++ b/src/cli/handlers/project.cc @@ -2,6 +2,7 @@ #include "cli/z3ed.h" #include "util/file_util.h" #include "util/bps.h" +#include #ifndef _WIN32 #include #endif @@ -40,17 +41,21 @@ absl::Status ProjectBuild::Run(const std::vector& arg_vec) { return status; } -#ifdef _WIN32 - return absl::UnimplementedError( - "Project build with patches is not implemented on Windows yet."); -#else - - // 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]; + // Apply BPS patches - cross-platform with std::filesystem + namespace fs = std::filesystem; + std::vector bps_files; + + try { + for (const auto& entry : fs::directory_iterator(project.patches_folder)) { + if (entry.path().extension() == ".bps") { + bps_files.push_back(entry.path().string()); + } + } + } catch (const fs::filesystem_error& e) { + // Patches folder doesn't exist or not accessible + } + + for (const auto& patch_file : bps_files) { std::vector patch_data; auto patch_contents = util::LoadFile(patch_file); std::copy(patch_contents.begin(), patch_contents.end(), @@ -60,13 +65,21 @@ absl::Status ProjectBuild::Run(const std::vector& arg_vec) { 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) { + // Run asar on assembly files - cross-platform + std::vector asm_files; + try { + for (const auto& entry : fs::directory_iterator(project.patches_folder)) { + if (entry.path().extension() == ".asm") { + asm_files.push_back(entry.path().string()); + } + } + } catch (const fs::filesystem_error& e) { + // No asm files + } + + for (const auto& asm_file : asm_files) { AsarPatch asar_patch; - auto status = asar_patch.Run({glob_result_asm.gl_pathv[i]}); + auto status = asar_patch.Run({asm_file}); if (!status.ok()) { return status; } @@ -82,7 +95,6 @@ absl::Status ProjectBuild::Run(const std::vector& arg_vec) { std::cout << " Output ROM: " << output_file << std::endl; return absl::OkStatus(); -#endif } } // namespace cli diff --git a/src/cli/service/agent/advanced_routing.cc b/src/cli/service/agent/advanced_routing.cc new file mode 100644 index 00000000..61fcab0d --- /dev/null +++ b/src/cli/service/agent/advanced_routing.cc @@ -0,0 +1,208 @@ +#include "cli/service/agent/advanced_routing.h" + +#include +#include +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" + +namespace yaze { +namespace cli { +namespace agent { + +AdvancedRouter::RoutedResponse AdvancedRouter::RouteHexAnalysis( + const std::vector& data, + uint32_t address, + const RouteContext& ctx) { + + RoutedResponse response; + + // Infer data type + std::string data_type = InferDataType(data); + auto patterns = ExtractPatterns(data); + + // Summary for user + response.summary = absl::StrFormat( + "Address 0x%06X contains %s (%zu bytes)", + address, data_type, data.size()); + + // Detailed data for agent with structure hints + std::ostringstream detailed; + detailed << absl::StrFormat("Raw hex at 0x%06X:\n", address); + for (size_t i = 0; i < data.size(); i += 16) { + detailed << absl::StrFormat("%06X: ", address + i); + for (size_t j = i; j < std::min(i + 16, data.size()); ++j) { + detailed << absl::StrFormat("%02X ", data[j]); + } + detailed << " | "; + for (size_t j = i; j < std::min(i + 16, data.size()); ++j) { + char c = data[j]; + detailed << (isprint(c) ? c : '.'); + } + detailed << "\n"; + } + + if (!patterns.empty()) { + detailed << "\nDetected patterns:\n"; + for (const auto& pattern : patterns) { + detailed << "- " << pattern << "\n"; + } + } + + response.detailed_data = detailed.str(); + + // Next steps based on data type + if (data_type.find("sprite") != std::string::npos) { + response.next_steps = "Use resource-list --type=sprite to identify sprite IDs"; + } else if (data_type.find("tile") != std::string::npos) { + response.next_steps = "Use overworld-find-tile to see where this tile appears"; + } else if (data_type.find("palette") != std::string::npos) { + response.next_steps = "Use palette-get-colors to see full palette"; + } else { + response.next_steps = "Use hex-search to find similar patterns in ROM"; + } + + return response; +} + +AdvancedRouter::RoutedResponse AdvancedRouter::RouteMapEdit( + const std::string& edit_intent, + const RouteContext& ctx) { + + RoutedResponse response; + + // Parse intent and generate action sequence + response.summary = "Preparing map edit operation"; + response.needs_approval = true; + + // Generate GUI automation steps + response.gui_actions = { + "Click(\"Overworld Editor\")", + "Wait(500)", + "Click(canvas, x, y)", + "SelectTile(tile_id)", + "Click(target_x, target_y)", + "Wait(100)", + "Screenshot(\"after_edit.png\")", + }; + + response.detailed_data = GenerateGUIScript(response.gui_actions); + response.next_steps = "Review proposed changes, then approve or modify"; + + return response; +} + +AdvancedRouter::RoutedResponse AdvancedRouter::RoutePaletteAnalysis( + const std::vector& colors, + const RouteContext& ctx) { + + RoutedResponse response; + + // Analyze color relationships + int unique_colors = 0; + std::map color_counts; + for (uint16_t c : colors) { + color_counts[c]++; + } + unique_colors = color_counts.size(); + + response.summary = absl::StrFormat( + "Palette has %zu colors (%d unique)", + colors.size(), unique_colors); + + // Detailed breakdown + std::ostringstream detailed; + detailed << "Color breakdown:\n"; + for (size_t i = 0; i < colors.size(); ++i) { + uint16_t snes = colors[i]; + uint8_t r = (snes & 0x1F) << 3; + uint8_t g = ((snes >> 5) & 0x1F) << 3; + uint8_t b = ((snes >> 10) & 0x1F) << 3; + detailed << absl::StrFormat(" [%zu] $%04X = #%02X%02X%02X\n", i, snes, r, g, b); + } + + if (color_counts.size() < colors.size()) { + detailed << "\nDuplicates found - optimization possible\n"; + } + + response.detailed_data = detailed.str(); + response.next_steps = "Use palette-set-color to modify colors"; + + return response; +} + +AdvancedRouter::RoutedResponse AdvancedRouter::SynthesizeMultiToolResponse( + const std::vector& tool_results, + const RouteContext& ctx) { + + RoutedResponse response; + + // Combine results intelligently + response.summary = absl::StrFormat("Analyzed %zu data sources", tool_results.size()); + response.detailed_data = absl::StrJoin(tool_results, "\n---\n"); + + // Generate insights + response.next_steps = "Analysis complete. " + ctx.user_intent; + + return response; +} + +std::string AdvancedRouter::GenerateGUIScript( + const std::vector& actions) { + std::ostringstream script; + script << "# Generated GUI Automation Script\n"; + script << "test: \"Automated Edit\"\n"; + script << "steps:\n"; + for (const auto& action : actions) { + script << " - " << action << "\n"; + } + return script.str(); +} + +std::string AdvancedRouter::InferDataType(const std::vector& data) { + if (data.size() == 8) return "tile16 data"; + if (data.size() % 3 == 0 && data.size() <= 48) return "sprite data"; + if (data.size() == 32) return "palette data (16 colors)"; + if (data.size() > 1000) return "compressed data block"; + return "unknown data"; +} + +std::vector AdvancedRouter::ExtractPatterns( + const std::vector& data) { + std::vector patterns; + + // Check for repeating bytes + if (data.size() > 2) { + bool all_same = true; + for (size_t i = 1; i < data.size(); ++i) { + if (data[i] != data[0]) { + all_same = false; + break; + } + } + if (all_same) { + patterns.push_back(absl::StrFormat("Repeating byte: 0x%02X", data[0])); + } + } + + // Check for ascending/descending sequences + if (data.size() > 3) { + bool ascending = true, descending = true; + for (size_t i = 1; i < data.size(); ++i) { + if (data[i] != data[i-1] + 1) ascending = false; + if (data[i] != data[i-1] - 1) descending = false; + } + if (ascending) patterns.push_back("Ascending sequence"); + if (descending) patterns.push_back("Descending sequence"); + } + + return patterns; +} + +std::string AdvancedRouter::FormatForAgent(const std::string& raw_data) { + // Format data for easy agent consumption + return "```\n" + raw_data + "\n```"; +} + +} // namespace agent +} // namespace cli +} // namespace yaze diff --git a/src/cli/service/agent/advanced_routing.h b/src/cli/service/agent/advanced_routing.h new file mode 100644 index 00000000..e16eadfc --- /dev/null +++ b/src/cli/service/agent/advanced_routing.h @@ -0,0 +1,85 @@ +#ifndef YAZE_CLI_SERVICE_AGENT_ADVANCED_ROUTING_H_ +#define YAZE_CLI_SERVICE_AGENT_ADVANCED_ROUTING_H_ + +#include +#include +#include +#include "absl/status/statusor.h" + +namespace yaze { +class Rom; + +namespace cli { +namespace agent { + +/** + * @brief Advanced routing system for agent tool responses + * + * Optimizes information flow back to agent for: + * - Map editing with GUI automation + * - Hex data analysis and pattern recognition + * - Multi-step operations with context preservation + */ +class AdvancedRouter { + public: + struct RouteContext { + Rom* rom; + std::string user_intent; + std::vector tool_calls_made; + std::string accumulated_knowledge; + }; + + struct RoutedResponse { + std::string summary; // High-level answer + std::string detailed_data; // Raw data for agent processing + std::string next_steps; // Suggested follow-up actions + std::vector gui_actions; // For test harness + bool needs_approval; // For proposals + }; + + /** + * @brief Route hex data analysis response + */ + static RoutedResponse RouteHexAnalysis( + const std::vector& data, + uint32_t address, + const RouteContext& ctx); + + /** + * @brief Route map editing response + */ + static RoutedResponse RouteMapEdit( + const std::string& edit_intent, + const RouteContext& ctx); + + /** + * @brief Route palette analysis response + */ + static RoutedResponse RoutePaletteAnalysis( + const std::vector& colors, + const RouteContext& ctx); + + /** + * @brief Synthesize multi-tool response + */ + static RoutedResponse SynthesizeMultiToolResponse( + const std::vector& tool_results, + const RouteContext& ctx); + + /** + * @brief Generate GUI automation script + */ + static std::string GenerateGUIScript( + const std::vector& actions); + + private: + static std::string InferDataType(const std::vector& data); + static std::vector ExtractPatterns(const std::vector& data); + static std::string FormatForAgent(const std::string& raw_data); +}; + +} // namespace agent +} // namespace cli +} // namespace yaze + +#endif // YAZE_CLI_SERVICE_AGENT_ADVANCED_ROUTING_H_ diff --git a/src/cli/service/agent/agent_pretraining.cc b/src/cli/service/agent/agent_pretraining.cc new file mode 100644 index 00000000..6c73bd40 --- /dev/null +++ b/src/cli/service/agent/agent_pretraining.cc @@ -0,0 +1,206 @@ +#include "cli/service/agent/agent_pretraining.h" + +#include "absl/strings/str_format.h" +#include "app/rom.h" +#include "app/zelda3/overworld/overworld.h" + +namespace yaze { +namespace cli { +namespace agent { + +std::vector AgentPretraining::GetModules() { + return { + {"rom_structure", GetRomStructureKnowledge(nullptr), true}, + {"hex_analysis", GetHexAnalysisKnowledge(), true}, + {"map_editing", GetMapEditingKnowledge(), true}, + {"tool_usage", GetToolUsageExamples(), true}, + }; +} + +std::string AgentPretraining::GetRomStructureKnowledge(Rom* rom) { + return R"( +# ALTTP ROM Structure Deep Dive + +## Memory Map +- 0x00000-0x07FFF: Header + Low ROM +- 0x08000-0x0FFFF: Character data +- 0x10000-0x1FFFF: Overworld maps +- 0x1C800-0x1D7FF: Overworld tile16 data +- 0x20000-0x2FFFF: Dungeon rooms (296 rooms) +- 0xDE6C8-0xDEDC7: Overworld palettes +- 0xDD308-0xDD3C7: Dungeon palettes + +## Overworld Structure +- 64 maps (0x00-0x3F): Light world + Dark world +- Each map: 32x32 tiles (1024 tile16s) +- Tile16 address = 0x1C800 + (tile_id * 8 bytes) +- Map data stored compressed + +## Dungeon Structure +- 296 rooms total (0x00-0x127) +- Room header: 14 bytes +- Sprite data: 3 bytes per sprite, up to 16 sprites +- Layer 1/2 separate data +- Object encoding: Layer/Size/X/Y format + +## Palette System +- 8 groups, 8 palettes per group +- 16 colors per palette (SNES 555 format) +- $0000-$7FFF range (5 bits per R/G/B channel) +- Conversion: RGB8 = (SNES_val & 0x1F) << 3 + +## Critical Addresses +- Sprite sheets: 0x80000+ +- Entrance table: 0x02C000 +- Item table: 0x0DC800 +- Text data: 0x0E0000+ +)"; +} + +std::string AgentPretraining::GetHexAnalysisKnowledge() { + return R"( +# Hex Data Analysis Patterns + +## Pattern Recognition + +### Sprite Data (3-byte pattern) +- Byte 0: Sprite ID (0x00-0xFF) +- Byte 1: X position in room +- Byte 2: Y position in room +- Example: "09 48 56" = Sprite 0x09 at (72, 86) + +### Tile16 Structure (8 bytes) +- Bytes 0-1: Top-left tile8 +- Bytes 2-3: Top-right tile8 +- Bytes 4-5: Bottom-left tile8 +- Bytes 6-7: Bottom-right tile8 +- Format: tile8_id (lower byte) + properties (upper byte) + +### Pointers (Little Endian) +- 2-byte pointer: Low byte first, high byte second +- Example: "00 1C" = 0x1C00 (address 0x001C00) +- SNES addressing: Add bank byte for 24-bit + +## Search Strategies + +### Finding Unused Space +- Pattern: "FF FF FF FF" (often unused) +- Pattern: "00 00 00 00" (zeroed regions) + +### Finding Sprite Definitions +- Search for known sprite IDs +- Look for X/Y coordinate patterns (0x00-0x1F typical range) + +### Finding Compressed Data +- Look for compression headers (0xE0-0xFF often signify compression) +- Data density changes (sparse vs dense byte values) +)"; +} + +std::string AgentPretraining::GetMapEditingKnowledge() { + return R"( +# Map Editing Workflow with Test Harness + +## Tile Placement Flow +1. Parse natural language: "Place water tile at (5, 7)" +2. Tool chain: + a. overworld-find-tile to get water tile ID + b. Calculate screen coordinates from game coords + c. Generate GUI action: Click(overworld_canvas, x, y) + d. Generate GUI action: SelectTile(tile_id) + e. Generate GUI action: Click(target_x, target_y) + +## Coordinate Systems +- Game coords: Tile-based (0-31 for 32x32 map) +- Screen coords: Pixel-based, depends on zoom/scroll +- Conversion: screen_x = game_x * tile_size * zoom + canvas_offset_x + +## GUI Automation Best Practices +- Always wait for UI updates (Wait(100ms) between actions) +- Assert widget states before clicking +- Screenshot before/after for verification +- Use widget discovery to find exact IDs + +## Proposal System Integration +- Read-only tools: Execute directly +- Write operations: Submit as proposal first +- Wait for approval before proceeding +- Show user what will change (diff view) +)"; +} + +std::string AgentPretraining::GetToolUsageExamples() { + return R"( +# Tool Usage Examples & Chaining + +## Example 1: Find and Analyze Sprites +User: "What enemies are in Hyrule Castle?" +Tool chain: +1. resource-search --type=dungeon --query=hyrule +2. dungeon-list-sprites --dungeon=hyrule_castle +3. resource-list --type=sprite (to get sprite names) +Result: "Hyrule Castle (dungeon 0) has 3 guards (sprite 0x41), 2 knights..." + +## Example 2: Palette Investigation +User: "What colors are used for grass?" +Tool chain: +1. overworld-find-tile --tile_id=0x20 (grass tile) +2. palette-get-colors --group=0 --palette=0 +3. palette-analyze --type=palette --id=0/0 +Result: "Grass uses palette 0/0 with colors: light green #98FB98..." + +## Example 3: Hex Pattern Search +User: "Find all chests in the ROM" +Tool chain: +1. hex-search --pattern="20 ?? ??" (chest object code 0x20) +2. For each match, hex-read context bytes +3. Parse room numbers from addresses +Result: "Found 50 chests across 30 rooms..." + +## Example 4: Complex Edit Planning +User: "Add a new enemy to room 5" +Steps: +1. dungeon-describe-room --room_id=5 (check current sprites) +2. resource-list --type=sprite (available enemies) +3. todo-create --title="Add sprite to room 5" --steps="find_sprite,get_coords,write_data" +4. hex-read --address= (check free slots) +5. Submit proposal with hex-write +)"; +} + +std::string AgentPretraining::GeneratePretrainingPrompt(Rom* rom) { + std::string prompt = "# Agent Pre-Training Session\n\n"; + prompt += "You are being initialized with deep knowledge about this ROM.\n\n"; + + if (rom && rom->is_loaded()) { + prompt += absl::StrFormat("## Current ROM: %s\n", rom->title()); + prompt += absl::StrFormat("Size: %zu bytes\n", rom->size()); + prompt += absl::StrFormat("Type: %s\n\n", rom->is_expanded() ? "Expanded" : "Vanilla"); + } + + for (const auto& module : GetModules()) { + prompt += absl::StrFormat("## Module: %s\n", module.name); + prompt += module.content; + prompt += "\n---\n\n"; + } + + prompt += R"( +## Your Capabilities After Training + +You now understand: +✓ ROM memory layout and addressing +✓ How to chain tools for complex analysis +✓ Hex pattern recognition and data structures +✓ GUI automation for map editing +✓ Proposal system for safe ROM modification + +**Test your knowledge**: When I ask about sprites, dungeons, or tiles, +use multiple tools in one response to give comprehensive answers. +)"; + + return prompt; +} + +} // namespace agent +} // namespace cli +} // namespace yaze diff --git a/src/cli/service/agent/agent_pretraining.h b/src/cli/service/agent/agent_pretraining.h new file mode 100644 index 00000000..cf227c01 --- /dev/null +++ b/src/cli/service/agent/agent_pretraining.h @@ -0,0 +1,63 @@ +#ifndef YAZE_CLI_SERVICE_AGENT_AGENT_PRETRAINING_H_ +#define YAZE_CLI_SERVICE_AGENT_AGENT_PRETRAINING_H_ + +#include +#include +#include "absl/status/status.h" + +namespace yaze { +class Rom; + +namespace cli { +namespace agent { + +/** + * @brief Pre-training system for AI agents + * + * Provides structured knowledge injection before interactive use. + * Teaches agent about ROM structure, common patterns, and tool usage. + */ +class AgentPretraining { + public: + struct KnowledgeModule { + std::string name; + std::string content; + bool required; + }; + + /** + * @brief Load all pre-training modules + */ + static std::vector GetModules(); + + /** + * @brief Get ROM structure explanation + */ + static std::string GetRomStructureKnowledge(Rom* rom); + + /** + * @brief Get hex data analysis patterns + */ + static std::string GetHexAnalysisKnowledge(); + + /** + * @brief Get map editing workflow + */ + static std::string GetMapEditingKnowledge(); + + /** + * @brief Get tool usage examples + */ + static std::string GetToolUsageExamples(); + + /** + * @brief Generate pre-training prompt for agent + */ + static std::string GeneratePretrainingPrompt(Rom* rom); +}; + +} // namespace agent +} // namespace cli +} // namespace yaze + +#endif // YAZE_CLI_SERVICE_AGENT_AGENT_PRETRAINING_H_