yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
code_gen_tool.cc
Go to the documentation of this file.
1
7
8#include <algorithm>
9#include <iostream>
10#include <regex>
11#include <sstream>
12
13#include "absl/strings/str_format.h"
14#include "absl/strings/str_join.h"
15#include "absl/strings/str_replace.h"
16#include "rom/rom.h"
17
18namespace yaze {
19namespace cli {
20namespace agent {
21namespace tools {
22
23// =============================================================================
24// Static Data
25// =============================================================================
26
27// Known safe hook locations (address -> description)
28const std::map<std::string, uint32_t> CodeGenToolBase::kKnownHooks = {
29 {"EnableForceBlank", 0x00893D},
30 {"Overworld_LoadMapProperties", 0x02AB08},
31 {"Overworld_LoadSubscreenAndSilenceSFX1", 0x02AF19},
32 {"Sprite_OverworldReloadAll", 0x09C499},
33};
34
35// Built-in ASM templates
36const std::vector<AsmTemplate> CodeGenToolBase::kTemplates =
38
39std::vector<AsmTemplate> CodeGenToolBase::InitializeTemplates() {
40 std::vector<AsmTemplate> templates;
41
42 // NMI Hook Template
43 templates.push_back(
44 {"nmi_hook",
45 R"(; NMI Hook Template
46; Hooks into the NMI (Non-Maskable Interrupt) handler
47org ${{NMI_HOOK_ADDRESS}}
48 JSL {{LABEL}}_NMI
49 NOP
50
51freecode
52{{LABEL}}_NMI:
53 PHB : PHK : PLB
54 {{CUSTOM_CODE}}
55 PLB
56 RTL
57)",
58 {"LABEL", "NMI_HOOK_ADDRESS", "CUSTOM_CODE"},
59 "Hook into NMI handler for frame-by-frame code execution"});
60
61 // Sprite Template
62 templates.push_back(
63 {"sprite",
64 R"(; Sprite Template
65; Sprite Variables:
66; $0D00,X = Y pos (low) $0D10,X = X pos (low)
67; $0D20,X = Y pos (high) $0D30,X = X pos (high)
68; $0DD0,X = State (08=init, 09=active)
69
70freecode
71{{SPRITE_NAME}}:
72 PHB : PHK : PLB
73 LDA $0DD0, X
74 CMP #$08 : BEQ .initialize
75 CMP #$09 : BEQ .main
76 PLB : RTL
77
78.initialize
79 {{INIT_CODE}}
80 LDA #$09 : STA $0DD0, X
81 PLB : RTL
82
83.main
84 {{MAIN_CODE}}
85 PLB : RTL
86)",
87 {"SPRITE_NAME", "INIT_CODE", "MAIN_CODE"},
88 "Complete sprite with init and main loop"});
89
90 // Freespace Allocation Template
91 templates.push_back(
92 {"freespace_alloc",
93 R"(; Freespace Allocation
94org ${{FREESPACE_ADDRESS}}
95{{LABEL}}:
96 {{CODE}}
97 RTL
98
99; Hook from existing code
100org ${{HOOK_ADDRESS}}
101 JSL {{LABEL}}
102 {{NOP_FILL}}
103)",
104 {"LABEL", "FREESPACE_ADDRESS", "HOOK_ADDRESS", "CODE", "NOP_FILL"},
105 "Allocate code in freespace with hook from existing code"});
106
107 // Simple JSL Hook Template
108 templates.push_back(
109 {"jsl_hook",
110 R"(; JSL Hook
111org ${{HOOK_ADDRESS}}
112 JSL {{LABEL}}
113 {{NOP_FILL}}
114)",
115 {"HOOK_ADDRESS", "LABEL", "NOP_FILL"},
116 "Simple JSL hook at address"});
117
118 // Event Handler Template
119 templates.push_back(
120 {"event_handler",
121 R"(; {{EVENT_TYPE}} Event Handler
122org ${{HOOK_ADDRESS}}
123 JSL {{LABEL}}_Handler
124 NOP
125
126freecode
127{{LABEL}}_Handler:
128 PHB : PHK : PLB
129 PHP
130 {{CUSTOM_CODE}}
131 PLP
132 PLB
133 RTL
134)",
135 {"EVENT_TYPE", "HOOK_ADDRESS", "LABEL", "CUSTOM_CODE"},
136 "Event handler with state preservation"});
137
138 return templates;
139}
140
141// =============================================================================
142// CodeGenToolBase
143// =============================================================================
144
146 uint32_t address) const {
147 // Check ROM bounds
148 if (address >= rom->size()) {
149 return absl::InvalidArgumentError(
150 absl::StrFormat("Address 0x%06X is beyond ROM size (0x%X)", address,
151 rom->size()));
152 }
153
154 // Check if address is already hooked (has JSL or JML)
155 auto opcode_result = rom->ReadByte(address);
156 if (!opcode_result.ok()) {
157 return opcode_result.status();
158 }
159 uint8_t opcode = *opcode_result;
160
161 if (opcode == 0x22) { // JSL
162 return absl::AlreadyExistsError(
163 absl::StrFormat("Address 0x%06X already contains a JSL instruction",
164 address));
165 }
166 if (opcode == 0x5C) { // JML
167 return absl::AlreadyExistsError(
168 absl::StrFormat("Address 0x%06X already contains a JML instruction",
169 address));
170 }
171
172 // Check alignment (JSL is 4 bytes, so we need at least 4 bytes available)
173 if (address + 4 > rom->size()) {
174 return absl::InvalidArgumentError(
175 absl::StrFormat("Not enough space at 0x%06X for JSL hook (need 4 bytes)",
176 address));
177 }
179 return absl::OkStatus();
180}
181
182std::vector<FreeSpaceRegion> CodeGenToolBase::DetectFreeSpace(
183 Rom* rom, size_t min_size) const {
184 std::vector<FreeSpaceRegion> regions;
185
186 // Known freespace regions to check
187 struct RegionCandidate {
188 uint32_t start;
189 uint32_t end;
190 const char* description;
191 };
193 const std::vector<RegionCandidate> candidates = {
194 {0x1F8000, 0x1FFFFF, "Bank $3F freespace (32KB)"},
195 {0x278000, 0x27FFFF, "Bank $4F freespace (32KB)"},
196 {0x2F8000, 0x2FFFFF, "Bank $5F freespace (32KB)"},
197 {0x378000, 0x37FFFF, "Bank $6F freespace (32KB)"},
198 {0x3F8000, 0x3FFFFF, "Bank $7F freespace (32KB)"},
199 };
200
201 for (const auto& candidate : candidates) {
202 // Ensure region is within ROM bounds
203 if (candidate.start >= rom->size()) {
204 continue;
205 }
206
207 uint32_t actual_end = std::min(candidate.end, static_cast<uint32_t>(rom->size() - 1));
208 size_t region_size = actual_end - candidate.start + 1;
209
210 // Count free bytes (0x00 or 0xFF)
211 size_t free_bytes = 0;
212 for (uint32_t addr = candidate.start; addr <= actual_end; ++addr) {
213 auto byte_result = rom->ReadByte(addr);
214 if (!byte_result.ok()) {
215 continue; // Skip unreadable bytes
217 uint8_t byte = *byte_result;
218 if (byte == 0x00 || byte == 0xFF) {
219 free_bytes++;
220 }
221 }
222
223 int free_percent = (free_bytes * 100) / region_size;
224
225 // Only include regions with significant free space and meeting min_size
226 if (free_percent >= 80 && region_size >= min_size) {
227 FreeSpaceRegion region;
228 region.start = candidate.start;
229 region.end = actual_end;
230 region.description = candidate.description;
231 region.free_percent = free_percent;
232 regions.push_back(region);
233 }
234 }
235
236 return regions;
237}
238
240 const std::string& template_code,
241 const std::map<std::string, std::string>& params) const {
242 std::string result = template_code;
243
244 // Replace all {{PLACEHOLDER}} with corresponding values
245 for (const auto& [key, value] : params) {
246 std::string placeholder = "{{" + key + "}}";
247 result = absl::StrReplaceAll(result, {{placeholder, value}});
248 }
249
250 return result;
251}
252
253absl::StatusOr<AsmTemplate> CodeGenToolBase::GetTemplate(
254 const std::string& name) const {
255 for (const auto& tmpl : kTemplates) {
256 if (tmpl.name == name) {
257 return tmpl;
258 }
259 }
260 return absl::NotFoundError(
261 absl::StrFormat("Template '%s' not found", name));
262}
263
264const std::vector<AsmTemplate>& CodeGenToolBase::GetAllTemplates() const {
265 return kTemplates;
266}
267
268bool CodeGenToolBase::IsKnownHookLocation(uint32_t address) const {
269 for (const auto& [name, addr] : kKnownHooks) {
270 if (addr == address) {
271 return true;
272 }
273 }
274 return false;
276
277std::string CodeGenToolBase::GetHookLocationDescription(uint32_t address) const {
278 for (const auto& [name, addr] : kKnownHooks) {
279 if (addr == address) {
280 return name;
281 }
282 }
283 return "Unknown";
284}
285
287 const CodeGenerationResult& result) const {
288 std::ostringstream json;
289
290 json << "{\n";
291 json << " \"success\": " << (result.success ? "true" : "false") << ",\n";
292
293 // Generated code
294 json << " \"code\": ";
295 if (!result.generated_code.empty()) {
296 // Escape newlines and quotes for JSON
297 std::string escaped_code = result.generated_code;
298 escaped_code = absl::StrReplaceAll(escaped_code, {{"\\", "\\\\"}, {"\"", "\\\""}, {"\n", "\\n"}});
299 json << "\"" << escaped_code << "\"";
300 } else {
301 json << "null";
302 }
303 json << ",\n";
304
305 // Symbols
306 json << " \"symbols\": {";
307 bool first_symbol = true;
308 for (const auto& [label, address] : result.symbols) {
309 if (!first_symbol) json << ", ";
310 json << "\"" << label << "\": \"" << absl::StrFormat("0x%06X", address) << "\"";
311 first_symbol = false;
312 }
313 json << "},\n";
314
315 // Diagnostics
316 json << " \"diagnostics\": [\n";
317 for (size_t i = 0; i < result.diagnostics.size(); ++i) {
318 const auto& diag = result.diagnostics[i];
319 json << " {";
320 json << "\"severity\": \"" << diag.SeverityString() << "\", ";
321 json << "\"message\": \"" << diag.message << "\"";
322 if (diag.address != 0) {
323 json << ", \"address\": \"" << absl::StrFormat("0x%06X", diag.address) << "\"";
324 }
325 json << "}";
326 if (i < result.diagnostics.size() - 1) json << ",";
327 json << "\n";
328 }
329 json << " ]\n";
330
331 json << "}\n";
333 return json.str();
334}
335
337 const CodeGenerationResult& result) const {
338 std::ostringstream text;
339
340 text << "Code Generation Result\n";
341 text << "======================\n\n";
342
343 text << "Status: " << (result.success ? "SUCCESS" : "FAILED") << "\n\n";
344
345 // Generated code
346 if (!result.generated_code.empty()) {
347 text << "Generated Code:\n";
348 text << "---------------\n";
349 text << result.generated_code << "\n\n";
350 }
351
352 // Symbols
353 if (!result.symbols.empty()) {
354 text << "Symbols:\n";
355 for (const auto& [label, address] : result.symbols) {
356 text << " " << label << " = " << absl::StrFormat("$%06X", address) << "\n";
357 }
358 text << "\n";
359 }
360
361 // Diagnostics
362 if (!result.diagnostics.empty()) {
363 text << "Diagnostics:\n";
364 for (const auto& diag : result.diagnostics) {
365 std::string prefix;
366 switch (diag.severity) {
368 prefix = "[INFO]";
369 break;
371 prefix = "[WARN]";
372 break;
374 prefix = "[ERROR]";
375 break;
376 }
377
378 text << " " << prefix << " " << diag.message;
379 if (diag.address != 0) {
380 text << absl::StrFormat(" (at $%06X)", diag.address);
381 }
382 text << "\n";
383 }
384 }
385
386 return text.str();
387}
388
389// =============================================================================
390// CodeGenAsmHookTool
391// =============================================================================
392
393absl::Status CodeGenAsmHookTool::Execute(
394 Rom* rom, const resources::ArgumentParser& parser,
395 resources::OutputFormatter& formatter) {
396 CodeGenerationResult result;
397 result.success = true;
398
399 // Parse arguments
400 auto address_result = parser.GetHex("address");
401 if (!address_result.ok()) {
402 result.AddError("Invalid or missing address");
403 std::string format = parser.GetString("format").value_or("json");
404 std::cout << (format == "json" ? FormatResultAsJson(result)
405 : FormatResultAsText(result));
406 return address_result.status();
407 }
408 uint32_t address = static_cast<uint32_t>(*address_result);
409
410 std::string label = parser.GetString("label").value();
411 int nop_fill = parser.GetInt("nop-fill").value_or(0);
412 std::string format = parser.GetString("format").value_or("json");
413
414 // Validate hook address
415 auto validate_status = ValidateHookAddress(rom, address);
416 if (!validate_status.ok()) {
417 result.AddError(std::string(validate_status.message()), address);
418 std::cout << (format == "json" ? FormatResultAsJson(result)
419 : FormatResultAsText(result));
420 return validate_status;
421 }
422
423 // Check if this is a known safe hook location
424 if (IsKnownHookLocation(address)) {
425 result.AddInfo(
426 absl::StrFormat("Using known safe hook: %s",
428 address);
429 } else {
430 result.AddWarning(
431 "Address is not a known safe hook location. Verify manually.", address);
432 }
433
434 // Generate NOP fill
435 std::string nop_fill_str;
436 for (int i = 0; i < nop_fill; ++i) {
437 nop_fill_str += "NOP\n ";
438 }
439 if (!nop_fill_str.empty()) {
440 nop_fill_str.pop_back(); // Remove trailing newline
441 nop_fill_str.pop_back();
442 }
443
444 // Generate hook code using jsl_hook template
445 auto tmpl = GetTemplate("jsl_hook");
446 if (!tmpl.ok()) {
447 result.AddError("Failed to load jsl_hook template");
448 std::cout << (format == "json" ? FormatResultAsJson(result)
449 : FormatResultAsText(result));
450 return tmpl.status();
451 }
452
453 std::map<std::string, std::string> params = {
454 {"HOOK_ADDRESS", absl::StrFormat("%06X", address)},
455 {"LABEL", label},
456 {"NOP_FILL", nop_fill_str},
457 };
458
459 result.generated_code = SubstitutePlaceholders(tmpl->code_template, params);
460 result.symbols[label] = address;
461
462 result.AddInfo(absl::StrFormat("Generated JSL hook to %s at $%06X", label, address),
463 address);
464
465 // Output result
466 std::cout << (format == "json" ? FormatResultAsJson(result)
467 : FormatResultAsText(result));
468
469 formatter.AddField("status", "complete");
470 return absl::OkStatus();
471}
472
473// =============================================================================
474// CodeGenFreespacePatchTool
475// =============================================================================
476
478 Rom* rom, const resources::ArgumentParser& parser,
479 resources::OutputFormatter& formatter) {
480 CodeGenerationResult result;
481 result.success = true;
482
483 // Parse arguments
484 std::string label = parser.GetString("label").value();
485 int size = parser.GetInt("size").value_or(0x100);
486 int prefer_bank = parser.GetInt("prefer-bank").value_or(-1);
487 std::string format = parser.GetString("format").value_or("json");
488
489 // Detect available freespace
490 auto regions = DetectFreeSpace(rom, size);
492 if (regions.empty()) {
493 result.AddError(absl::StrFormat(
494 "No suitable freespace found for %d bytes (need >=%d bytes with >=80%% free)",
495 size, size));
496 std::cout << (format == "json" ? FormatResultAsJson(result)
497 : FormatResultAsText(result));
498 return absl::NotFoundError("No freespace available");
499 }
500
501 // Select best region (prefer requested bank if specified)
502 FreeSpaceRegion selected_region = regions[0];
503 if (prefer_bank >= 0) {
504 for (const auto& region : regions) {
505 uint8_t bank = (region.start >> 16) & 0xFF;
506 if (bank == static_cast<uint8_t>(prefer_bank)) {
507 selected_region = region;
508 break;
509 }
510 }
511 }
512
513 result.AddInfo(absl::StrFormat("Selected region: %s ($%06X-$%06X, %d%% free)",
514 selected_region.description.c_str(),
515 selected_region.start, selected_region.end,
516 selected_region.free_percent));
517
518 // Generate patch code
519 std::ostringstream code;
520 code << "; Freespace Allocation\n";
521 code << "; Selected: " << selected_region.description << "\n";
522 code << absl::StrFormat("org $%06X\n", selected_region.start);
523 code << label << ":\n";
524 code << " ; Your code here (up to " << size << " bytes)\n";
525 code << " RTL\n";
526
527 result.generated_code = code.str();
528 result.symbols[label] = selected_region.start;
529
530 result.AddInfo(absl::StrFormat("Allocated %d bytes in %s", size,
531 selected_region.description.c_str()),
532 selected_region.start);
533
534 // List other available regions
535 if (regions.size() > 1) {
536 result.AddInfo(absl::StrFormat("Found %zu other suitable regions",
537 regions.size() - 1));
538 }
539
540 // Output result
541 std::cout << (format == "json" ? FormatResultAsJson(result)
542 : FormatResultAsText(result));
543
544 formatter.AddField("status", "complete");
545 return absl::OkStatus();
546}
547
548// =============================================================================
549// CodeGenSpriteTemplateTool
550// =============================================================================
551
553 Rom* /*rom*/, const resources::ArgumentParser& parser,
554 resources::OutputFormatter& formatter) {
556 result.success = true;
557
558 // Parse arguments
559 std::string name = parser.GetString("name").value();
560 std::string init_code = parser.GetString("init-code").value_or(
561 "; Initialize sprite here\n LDA #$00 : STA $0F50, X ; Example: Clear state");
562 std::string main_code = parser.GetString("main-code").value_or(
563 "; Main sprite logic here\n JSR Sprite_Move ; Example: Move sprite");
564 std::string format = parser.GetString("format").value_or("json");
565
566 // Get sprite template
567 auto tmpl = GetTemplate("sprite");
568 if (!tmpl.ok()) {
569 result.AddError("Failed to load sprite template");
570 std::cout << (format == "json" ? FormatResultAsJson(result)
571 : FormatResultAsText(result));
572 return tmpl.status();
573 }
574
575 // Substitute placeholders
576 std::map<std::string, std::string> params = {
577 {"SPRITE_NAME", name},
578 {"INIT_CODE", init_code},
579 {"MAIN_CODE", main_code},
580 };
581
582 result.generated_code = SubstitutePlaceholders(tmpl->code_template, params);
583 result.AddInfo(absl::StrFormat("Generated sprite template for '%s'", name.c_str()));
584
585 // Output result
586 std::cout << (format == "json" ? FormatResultAsJson(result)
587 : FormatResultAsText(result));
588
589 formatter.AddField("status", "complete");
590 return absl::OkStatus();
591}
592
593// =============================================================================
594// CodeGenEventHandlerTool
595// =============================================================================
596
598 Rom* /*rom*/, const resources::ArgumentParser& parser,
599 resources::OutputFormatter& formatter) {
600 CodeGenerationResult result;
601 result.success = true;
602
603 // Parse arguments
604 std::string type = parser.GetString("type").value();
605 std::string label = parser.GetString("label").value();
606 std::string custom_code = parser.GetString("custom-code").value_or(
607 "; Your custom code here");
608 std::string format = parser.GetString("format").value_or("json");
609
610 // Validate event type
611 std::map<std::string, uint32_t> event_addresses = {
612 {"nmi", 0x008040}, // NMI hook location
613 {"irq", 0x008050}, // IRQ hook location (example)
614 {"reset", 0x008000}, // Reset vector (example)
615 };
616
617 auto it = event_addresses.find(type);
618 if (it == event_addresses.end()) {
619 result.AddError(absl::StrFormat(
620 "Unknown event type '%s'. Valid types: nmi, irq, reset", type.c_str()));
621 std::cout << (format == "json" ? FormatResultAsJson(result)
622 : FormatResultAsText(result));
623 return absl::InvalidArgumentError("Invalid event type");
624 }
625
626 uint32_t hook_address = it->second;
627
628 // Get event handler template
629 auto tmpl = GetTemplate("event_handler");
630 if (!tmpl.ok()) {
631 result.AddError("Failed to load event_handler template");
632 std::cout << (format == "json" ? FormatResultAsJson(result)
633 : FormatResultAsText(result));
634 return tmpl.status();
635 }
636
637 // Substitute placeholders
638 std::map<std::string, std::string> params = {
639 {"EVENT_TYPE", type},
640 {"HOOK_ADDRESS", absl::StrFormat("%06X", hook_address)},
641 {"LABEL", label},
642 {"CUSTOM_CODE", custom_code},
643 };
644
645 result.generated_code = SubstitutePlaceholders(tmpl->code_template, params);
646 result.symbols[label + "_Handler"] = hook_address;
647
648 result.AddInfo(absl::StrFormat("Generated %s event handler '%s'",
649 type.c_str(), label.c_str()),
650 hook_address);
651
652 // Output result
653 std::cout << (format == "json" ? FormatResultAsJson(result)
654 : FormatResultAsText(result));
655
656 formatter.AddField("status", "complete");
657 return absl::OkStatus();
658}
659
660} // namespace tools
661} // namespace agent
662} // namespace cli
663} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
auto size() const
Definition rom.h:134
absl::StatusOr< uint8_t > ReadByte(int offset)
Definition rom.cc:221
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
static const std::map< std::string, uint32_t > kKnownHooks
const std::vector< AsmTemplate > & GetAllTemplates() const
Get all available built-in templates.
std::string SubstitutePlaceholders(const std::string &template_code, const std::map< std::string, std::string > &params) const
Substitute placeholders in template with parameter values.
static const std::vector< AsmTemplate > kTemplates
std::vector< FreeSpaceRegion > DetectFreeSpace(Rom *rom, size_t min_size=0x100) const
Detect available free space regions in ROM.
static std::vector< AsmTemplate > InitializeTemplates()
std::string FormatResultAsText(const CodeGenerationResult &result) const
Format code generation result as text.
absl::StatusOr< AsmTemplate > GetTemplate(const std::string &name) const
Get a built-in ASM template by name.
std::string GetHookLocationDescription(uint32_t address) const
Get description of a known hook location.
std::string FormatResultAsJson(const CodeGenerationResult &result) const
Format code generation result as JSON.
bool IsKnownHookLocation(uint32_t address) const
Check if an address is within a known safe hook location.
absl::Status ValidateHookAddress(Rom *rom, uint32_t address) const
Validate that an address is safe for hooking.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
Utility for consistent output formatting across commands.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
Code generation tools for AI agents.
Result of code generation operation.
std::map< std::string, uint32_t > symbols
std::vector< CodeGenerationDiagnostic > diagnostics
void AddError(const std::string &message, uint32_t address=0)
void AddInfo(const std::string &message, uint32_t address=0)