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({"sprite",
63 R"(; Sprite Template
64; Sprite Variables:
65; $0D00,X = Y pos (low) $0D10,X = X pos (low)
66; $0D20,X = Y pos (high) $0D30,X = X pos (high)
67; $0DD0,X = State (08=init, 09=active)
68
69freecode
70{{SPRITE_NAME}}:
71 PHB : PHK : PLB
72 LDA $0DD0, X
73 CMP #$08 : BEQ .initialize
74 CMP #$09 : BEQ .main
75 PLB : RTL
76
77.initialize
78 {{INIT_CODE}}
79 LDA #$09 : STA $0DD0, X
80 PLB : RTL
82.main
83 {{MAIN_CODE}}
84 PLB : RTL
85)",
86 {"SPRITE_NAME", "INIT_CODE", "MAIN_CODE"},
87 "Complete sprite with init and main loop"});
88
89 // Freespace Allocation Template
90 templates.push_back(
91 {"freespace_alloc",
92 R"(; Freespace Allocation
93org ${{FREESPACE_ADDRESS}}
94{{LABEL}}:
95 {{CODE}}
96 RTL
97
98; Hook from existing code
99org ${{HOOK_ADDRESS}}
100 JSL {{LABEL}}
101 {{NOP_FILL}}
102)",
103 {"LABEL", "FREESPACE_ADDRESS", "HOOK_ADDRESS", "CODE", "NOP_FILL"},
104 "Allocate code in freespace with hook from existing code"});
105
106 // Simple JSL Hook Template
107 templates.push_back({"jsl_hook",
108 R"(; JSL Hook
109org ${{HOOK_ADDRESS}}
110 JSL {{LABEL}}
111 {{NOP_FILL}}
112)",
113 {"HOOK_ADDRESS", "LABEL", "NOP_FILL"},
114 "Simple JSL hook at address"});
115
116 // Event Handler Template
117 templates.push_back({"event_handler",
118 R"(; {{EVENT_TYPE}} Event Handler
119org ${{HOOK_ADDRESS}}
120 JSL {{LABEL}}_Handler
121 NOP
122
123freecode
124{{LABEL}}_Handler:
125 PHB : PHK : PLB
126 PHP
127 {{CUSTOM_CODE}}
128 PLP
129 PLB
130 RTL
131)",
132 {"EVENT_TYPE", "HOOK_ADDRESS", "LABEL", "CUSTOM_CODE"},
133 "Event handler with state preservation"});
134
135 return templates;
136}
137
138// =============================================================================
139// CodeGenToolBase
140// =============================================================================
141
143 uint32_t address) const {
144 // Check ROM bounds
145 if (address >= rom->size()) {
146 return absl::InvalidArgumentError(absl::StrFormat(
147 "Address 0x%06X is beyond ROM size (0x%X)", address, rom->size()));
148 }
149
150 // Check if address is already hooked (has JSL or JML)
151 auto opcode_result = rom->ReadByte(address);
152 if (!opcode_result.ok()) {
153 return opcode_result.status();
154 }
155 uint8_t opcode = *opcode_result;
156
157 if (opcode == 0x22) { // JSL
158 return absl::AlreadyExistsError(absl::StrFormat(
159 "Address 0x%06X already contains a JSL instruction", address));
160 }
161 if (opcode == 0x5C) { // JML
162 return absl::AlreadyExistsError(absl::StrFormat(
163 "Address 0x%06X already contains a JML instruction", address));
164 }
165
166 // Check alignment (JSL is 4 bytes, so we need at least 4 bytes available)
167 if (address + 4 > rom->size()) {
168 return absl::InvalidArgumentError(absl::StrFormat(
169 "Not enough space at 0x%06X for JSL hook (need 4 bytes)", address));
170 }
171
172 return absl::OkStatus();
173}
174
175std::vector<FreeSpaceRegion> CodeGenToolBase::DetectFreeSpace(
176 Rom* rom, size_t min_size) const {
177 std::vector<FreeSpaceRegion> regions;
178
179 // Known freespace regions to check
180 struct RegionCandidate {
181 uint32_t start;
182 uint32_t end;
183 const char* description;
184 };
185
186 const std::vector<RegionCandidate> candidates = {
187 {0x1F8000, 0x1FFFFF, "Bank $3F freespace (32KB)"},
188 {0x278000, 0x27FFFF, "Bank $4F freespace (32KB)"},
189 {0x2F8000, 0x2FFFFF, "Bank $5F freespace (32KB)"},
190 {0x378000, 0x37FFFF, "Bank $6F freespace (32KB)"},
191 {0x3F8000, 0x3FFFFF, "Bank $7F freespace (32KB)"},
192 };
193
194 for (const auto& candidate : candidates) {
195 // Ensure region is within ROM bounds
196 if (candidate.start >= rom->size()) {
197 continue;
198 }
199
200 uint32_t actual_end =
201 std::min(candidate.end, static_cast<uint32_t>(rom->size() - 1));
202 size_t region_size = actual_end - candidate.start + 1;
203
204 // Count free bytes (0x00 or 0xFF)
205 size_t free_bytes = 0;
206 for (uint32_t addr = candidate.start; addr <= actual_end; ++addr) {
207 auto byte_result = rom->ReadByte(addr);
208 if (!byte_result.ok()) {
209 continue; // Skip unreadable bytes
210 }
211 uint8_t byte = *byte_result;
212 if (byte == 0x00 || byte == 0xFF) {
213 free_bytes++;
214 }
215 }
216
217 int free_percent = (free_bytes * 100) / region_size;
218
219 // Only include regions with significant free space and meeting min_size
220 if (free_percent >= 80 && region_size >= min_size) {
221 FreeSpaceRegion region;
222 region.start = candidate.start;
223 region.end = actual_end;
224 region.description = candidate.description;
225 region.free_percent = free_percent;
226 regions.push_back(region);
227 }
228 }
229
230 return regions;
231}
232
234 const std::string& template_code,
235 const std::map<std::string, std::string>& params) const {
236 std::string result = template_code;
237
238 // Replace all {{PLACEHOLDER}} with corresponding values
239 for (const auto& [key, value] : params) {
240 std::string placeholder = "{{" + key + "}}";
241 result = absl::StrReplaceAll(result, {{placeholder, value}});
242 }
243
244 return result;
245}
246
247absl::StatusOr<AsmTemplate> CodeGenToolBase::GetTemplate(
248 const std::string& name) const {
249 for (const auto& tmpl : kTemplates) {
250 if (tmpl.name == name) {
251 return tmpl;
252 }
253 }
254 return absl::NotFoundError(absl::StrFormat("Template '%s' not found", name));
255}
256
257const std::vector<AsmTemplate>& CodeGenToolBase::GetAllTemplates() const {
258 return kTemplates;
259}
260
261bool CodeGenToolBase::IsKnownHookLocation(uint32_t address) const {
262 for (const auto& [name, addr] : kKnownHooks) {
263 if (addr == address) {
264 return true;
265 }
266 }
267 return false;
268}
269
271 uint32_t address) const {
272 for (const auto& [name, addr] : kKnownHooks) {
273 if (addr == address) {
274 return name;
275 }
276 }
277 return "Unknown";
278}
279
281 const CodeGenerationResult& result) const {
282 std::ostringstream json;
283
284 json << "{\n";
285 json << " \"success\": " << (result.success ? "true" : "false") << ",\n";
286
287 // Generated code
288 json << " \"code\": ";
289 if (!result.generated_code.empty()) {
290 // Escape newlines and quotes for JSON
291 std::string escaped_code = result.generated_code;
292 escaped_code = absl::StrReplaceAll(
293 escaped_code, {{"\\", "\\\\"}, {"\"", "\\\""}, {"\n", "\\n"}});
294 json << "\"" << escaped_code << "\"";
295 } else {
296 json << "null";
297 }
298 json << ",\n";
299
300 // Symbols
301 json << " \"symbols\": {";
302 bool first_symbol = true;
303 for (const auto& [label, address] : result.symbols) {
304 if (!first_symbol)
305 json << ", ";
306 json << "\"" << label << "\": \"" << absl::StrFormat("0x%06X", address)
307 << "\"";
308 first_symbol = false;
309 }
310 json << "},\n";
311
312 // Diagnostics
313 json << " \"diagnostics\": [\n";
314 for (size_t i = 0; i < result.diagnostics.size(); ++i) {
315 const auto& diag = result.diagnostics[i];
316 json << " {";
317 json << "\"severity\": \"" << diag.SeverityString() << "\", ";
318 json << "\"message\": \"" << diag.message << "\"";
319 if (diag.address != 0) {
320 json << ", \"address\": \"" << absl::StrFormat("0x%06X", diag.address)
321 << "\"";
322 }
323 json << "}";
324 if (i < result.diagnostics.size() - 1)
325 json << ",";
326 json << "\n";
327 }
328 json << " ]\n";
329
330 json << "}\n";
331
332 return json.str();
333}
334
336 const CodeGenerationResult& result) const {
337 std::ostringstream text;
338
339 text << "Code Generation Result\n";
340 text << "======================\n\n";
341
342 text << "Status: " << (result.success ? "SUCCESS" : "FAILED") << "\n\n";
343
344 // Generated code
345 if (!result.generated_code.empty()) {
346 text << "Generated Code:\n";
347 text << "---------------\n";
348 text << result.generated_code << "\n\n";
349 }
350
351 // Symbols
352 if (!result.symbols.empty()) {
353 text << "Symbols:\n";
354 for (const auto& [label, address] : result.symbols) {
355 text << " " << label << " = " << absl::StrFormat("$%06X", address)
356 << "\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(absl::StrFormat("Using known safe hook: %s",
427 address);
428 } else {
429 result.AddWarning(
430 "Address is not a known safe hook location. Verify manually.", address);
431 }
432
433 // Generate NOP fill
434 std::string nop_fill_str;
435 for (int i = 0; i < nop_fill; ++i) {
436 nop_fill_str += "NOP\n ";
437 }
438 if (!nop_fill_str.empty()) {
439 nop_fill_str.pop_back(); // Remove trailing newline
440 nop_fill_str.pop_back();
441 }
442
443 // Generate hook code using jsl_hook template
444 auto tmpl = GetTemplate("jsl_hook");
445 if (!tmpl.ok()) {
446 result.AddError("Failed to load jsl_hook template");
447 std::cout << (format == "json" ? FormatResultAsJson(result)
448 : FormatResultAsText(result));
449 return tmpl.status();
450 }
451
452 std::map<std::string, std::string> params = {
453 {"HOOK_ADDRESS", absl::StrFormat("%06X", address)},
454 {"LABEL", label},
455 {"NOP_FILL", nop_fill_str},
456 };
457
458 result.generated_code = SubstitutePlaceholders(tmpl->code_template, params);
459 result.symbols[label] = address;
460
461 result.AddInfo(
462 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);
491
492 if (regions.empty()) {
493 result.AddError(
494 absl::StrFormat("No suitable freespace found for %d bytes (need >=%d "
495 "bytes with >=80%% free)",
496 size, size));
497 std::cout << (format == "json" ? FormatResultAsJson(result)
498 : FormatResultAsText(result));
499 return absl::NotFoundError("No freespace available");
500 }
501
502 // Select best region (prefer requested bank if specified)
503 FreeSpaceRegion selected_region = regions[0];
504 if (prefer_bank >= 0) {
505 for (const auto& region : regions) {
506 uint8_t bank = (region.start >> 16) & 0xFF;
507 if (bank == static_cast<uint8_t>(prefer_bank)) {
508 selected_region = region;
509 break;
510 }
511 }
512 }
513
514 result.AddInfo(absl::StrFormat("Selected region: %s ($%06X-$%06X, %d%% free)",
515 selected_region.description.c_str(),
516 selected_region.start, selected_region.end,
517 selected_region.free_percent));
518
519 // Generate patch code
520 std::ostringstream code;
521 code << "; Freespace Allocation\n";
522 code << "; Selected: " << selected_region.description << "\n";
523 code << absl::StrFormat("org $%06X\n", selected_region.start);
524 code << label << ":\n";
525 code << " ; Your code here (up to " << size << " bytes)\n";
526 code << " RTL\n";
527
528 result.generated_code = code.str();
529 result.symbols[label] = selected_region.start;
530
531 result.AddInfo(absl::StrFormat("Allocated %d bytes in %s", size,
532 selected_region.description.c_str()),
533 selected_region.start);
534
535 // List other available regions
536 if (regions.size() > 1) {
537 result.AddInfo(absl::StrFormat("Found %zu other suitable regions",
538 regions.size() - 1));
539 }
540
541 // Output result
542 std::cout << (format == "json" ? FormatResultAsJson(result)
543 : FormatResultAsText(result));
544
545 formatter.AddField("status", "complete");
546 return absl::OkStatus();
547}
548
549// =============================================================================
550// CodeGenSpriteTemplateTool
551// =============================================================================
552
554 Rom* /*rom*/, const resources::ArgumentParser& parser,
555 resources::OutputFormatter& formatter) {
557 result.success = true;
558
559 // Parse arguments
560 std::string name = parser.GetString("name").value();
561 std::string init_code = parser.GetString("init-code")
562 .value_or(
563 "; Initialize sprite here\n LDA #$00 : STA "
564 "$0F50, X ; Example: Clear state");
565 std::string main_code = parser.GetString("main-code")
566 .value_or(
567 "; Main sprite logic here\n JSR Sprite_Move "
568 " ; Example: Move sprite");
569 std::string format = parser.GetString("format").value_or("json");
570
571 // Get sprite template
572 auto tmpl = GetTemplate("sprite");
573 if (!tmpl.ok()) {
574 result.AddError("Failed to load sprite template");
575 std::cout << (format == "json" ? FormatResultAsJson(result)
576 : FormatResultAsText(result));
577 return tmpl.status();
578 }
579
580 // Substitute placeholders
581 std::map<std::string, std::string> params = {
582 {"SPRITE_NAME", name},
583 {"INIT_CODE", init_code},
584 {"MAIN_CODE", main_code},
585 };
586
587 result.generated_code = SubstitutePlaceholders(tmpl->code_template, params);
588 result.AddInfo(
589 absl::StrFormat("Generated sprite template for '%s'", name.c_str()));
590
591 // Output result
592 std::cout << (format == "json" ? FormatResultAsJson(result)
593 : FormatResultAsText(result));
594
595 formatter.AddField("status", "complete");
596 return absl::OkStatus();
597}
598
599// =============================================================================
600// CodeGenEventHandlerTool
601// =============================================================================
602
604 Rom* /*rom*/, const resources::ArgumentParser& parser,
605 resources::OutputFormatter& formatter) {
606 CodeGenerationResult result;
607 result.success = true;
608
609 // Parse arguments
610 std::string type = parser.GetString("type").value();
611 std::string label = parser.GetString("label").value();
612 std::string custom_code =
613 parser.GetString("custom-code").value_or("; Your custom code here");
614 std::string format = parser.GetString("format").value_or("json");
615
616 // Validate event type
617 std::map<std::string, uint32_t> event_addresses = {
618 {"nmi", 0x008040}, // NMI hook location
619 {"irq", 0x008050}, // IRQ hook location (example)
620 {"reset", 0x008000}, // Reset vector (example)
621 };
622
623 auto it = event_addresses.find(type);
624 if (it == event_addresses.end()) {
625 result.AddError(absl::StrFormat(
626 "Unknown event type '%s'. Valid types: nmi, irq, reset", type.c_str()));
627 std::cout << (format == "json" ? FormatResultAsJson(result)
628 : FormatResultAsText(result));
629 return absl::InvalidArgumentError("Invalid event type");
630 }
631
632 uint32_t hook_address = it->second;
633
634 // Get event handler template
635 auto tmpl = GetTemplate("event_handler");
636 if (!tmpl.ok()) {
637 result.AddError("Failed to load event_handler template");
638 std::cout << (format == "json" ? FormatResultAsJson(result)
639 : FormatResultAsText(result));
640 return tmpl.status();
641 }
642
643 // Substitute placeholders
644 std::map<std::string, std::string> params = {
645 {"EVENT_TYPE", type},
646 {"HOOK_ADDRESS", absl::StrFormat("%06X", hook_address)},
647 {"LABEL", label},
648 {"CUSTOM_CODE", custom_code},
649 };
650
651 result.generated_code = SubstitutePlaceholders(tmpl->code_template, params);
652 result.symbols[label + "_Handler"] = hook_address;
653
654 result.AddInfo(absl::StrFormat("Generated %s event handler '%s'",
655 type.c_str(), label.c_str()),
656 hook_address);
657
658 // Output result
659 std::cout << (format == "json" ? FormatResultAsJson(result)
660 : FormatResultAsText(result));
661
662 formatter.AddField("status", "complete");
663 return absl::OkStatus();
664}
665
666} // namespace tools
667} // namespace agent
668} // namespace cli
669} // 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:222
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)