13#include "absl/strings/str_format.h"
14#include "absl/strings/str_join.h"
15#include "absl/strings/str_replace.h"
29 {
"EnableForceBlank", 0x00893D},
30 {
"Overworld_LoadMapProperties", 0x02AB08},
31 {
"Overworld_LoadSubscreenAndSilenceSFX1", 0x02AF19},
32 {
"Sprite_OverworldReloadAll", 0x09C499},
40 std::vector<AsmTemplate> templates;
45 R
"(; NMI Hook Template
46; Hooks into the NMI (Non-Maskable Interrupt) handler
47org ${{NMI_HOOK_ADDRESS}}
58 {"LABEL",
"NMI_HOOK_ADDRESS",
"CUSTOM_CODE"},
59 "Hook into NMI handler for frame-by-frame code execution"});
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)
74 CMP #$08 : BEQ .initialize
80 LDA #$09 : STA $0DD0, X
87 {"SPRITE_NAME",
"INIT_CODE",
"MAIN_CODE"},
88 "Complete sprite with init and main loop"});
93 R
"(; Freespace Allocation
94org ${{FREESPACE_ADDRESS}}
99; Hook from existing code
104 {"LABEL",
"FREESPACE_ADDRESS",
"HOOK_ADDRESS",
"CODE",
"NOP_FILL"},
105 "Allocate code in freespace with hook from existing code"});
115 {"HOOK_ADDRESS",
"LABEL",
"NOP_FILL"},
116 "Simple JSL hook at address"});
121 R
"(; {{EVENT_TYPE}} Event Handler
123 JSL {{LABEL}}_Handler
135 {"EVENT_TYPE",
"HOOK_ADDRESS",
"LABEL",
"CUSTOM_CODE"},
136 "Event handler with state preservation"});
146 uint32_t address)
const {
148 if (address >= rom->
size()) {
149 return absl::InvalidArgumentError(
150 absl::StrFormat(
"Address 0x%06X is beyond ROM size (0x%X)", address,
155 auto opcode_result = rom->
ReadByte(address);
156 if (!opcode_result.ok()) {
157 return opcode_result.status();
159 uint8_t opcode = *opcode_result;
161 if (opcode == 0x22) {
162 return absl::AlreadyExistsError(
163 absl::StrFormat(
"Address 0x%06X already contains a JSL instruction",
166 if (opcode == 0x5C) {
167 return absl::AlreadyExistsError(
168 absl::StrFormat(
"Address 0x%06X already contains a JML instruction",
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)",
179 return absl::OkStatus();
183 Rom* rom,
size_t min_size)
const {
184 std::vector<FreeSpaceRegion> regions;
187 struct RegionCandidate {
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)"},
201 for (
const auto& candidate : candidates) {
203 if (candidate.start >= rom->
size()) {
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;
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()) {
217 uint8_t
byte = *byte_result;
218 if (
byte == 0x00 ||
byte == 0xFF) {
223 int free_percent = (free_bytes * 100) / region_size;
226 if (free_percent >= 80 && region_size >= min_size) {
228 region.
start = candidate.start;
229 region.
end = actual_end;
232 regions.push_back(region);
240 const std::string& template_code,
241 const std::map<std::string, std::string>& params)
const {
242 std::string result = template_code;
245 for (
const auto& [key, value] : params) {
246 std::string placeholder =
"{{" + key +
"}}";
247 result = absl::StrReplaceAll(result, {{placeholder, value}});
254 const std::string&
name)
const {
256 if (tmpl.name ==
name) {
260 return absl::NotFoundError(
261 absl::StrFormat(
"Template '%s' not found",
name));
270 if (addr == address) {
279 if (addr == address) {
287 const CodeGenerationResult& result)
const {
288 std::ostringstream
json;
291 json <<
" \"success\": " << (result.success ?
"true" :
"false") <<
",\n";
294 json <<
" \"code\": ";
295 if (!result.generated_code.empty()) {
297 std::string escaped_code = result.generated_code;
298 escaped_code = absl::StrReplaceAll(escaped_code, {{
"\\",
"\\\\"}, {
"\"",
"\\\""}, {
"\n",
"\\n"}});
299 json <<
"\"" << escaped_code <<
"\"";
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;
316 json <<
" \"diagnostics\": [\n";
317 for (
size_t i = 0; i < result.diagnostics.size(); ++i) {
318 const auto& diag = result.diagnostics[i];
320 json <<
"\"severity\": \"" << diag.SeverityString() <<
"\", ";
321 json <<
"\"message\": \"" << diag.message <<
"\"";
322 if (diag.address != 0) {
323 json <<
", \"address\": \"" << absl::StrFormat(
"0x%06X", diag.address) <<
"\"";
326 if (i < result.diagnostics.size() - 1)
json <<
",";
338 std::ostringstream text;
340 text <<
"Code Generation Result\n";
341 text <<
"======================\n\n";
343 text <<
"Status: " << (result.
success ?
"SUCCESS" :
"FAILED") <<
"\n\n";
347 text <<
"Generated Code:\n";
348 text <<
"---------------\n";
354 text <<
"Symbols:\n";
355 for (
const auto& [label, address] : result.symbols) {
356 text <<
" " << label <<
" = " << absl::StrFormat(
"$%06X", address) <<
"\n";
363 text <<
"Diagnostics:\n";
364 for (
const auto& diag : result.diagnostics) {
366 switch (diag.severity) {
378 text <<
" " << prefix <<
" " << diag.message;
379 if (diag.address != 0) {
380 text << absl::StrFormat(
" (at $%06X)", diag.address);
394 Rom* rom,
const resources::ArgumentParser& parser,
395 resources::OutputFormatter& formatter) {
396 CodeGenerationResult result;
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");
406 return address_result.status();
408 uint32_t address =
static_cast<uint32_t
>(*address_result);
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");
416 if (!validate_status.ok()) {
417 result.AddError(std::string(validate_status.message()), address);
420 return validate_status;
426 absl::StrFormat(
"Using known safe hook: %s",
431 "Address is not a known safe hook location. Verify manually.", address);
435 std::string nop_fill_str;
436 for (
int i = 0; i < nop_fill; ++i) {
437 nop_fill_str +=
"NOP\n ";
439 if (!nop_fill_str.empty()) {
440 nop_fill_str.pop_back();
441 nop_fill_str.pop_back();
447 result.AddError(
"Failed to load jsl_hook template");
450 return tmpl.status();
453 std::map<std::string, std::string> params = {
454 {
"HOOK_ADDRESS", absl::StrFormat(
"%06X", address)},
456 {
"NOP_FILL", nop_fill_str},
460 result.symbols[label] = address;
462 result.AddInfo(absl::StrFormat(
"Generated JSL hook to %s at $%06X", label, address),
469 formatter.AddField(
"status",
"complete");
470 return absl::OkStatus();
478 Rom* rom,
const resources::ArgumentParser& parser,
479 resources::OutputFormatter& formatter) {
480 CodeGenerationResult result;
481 result.success =
true;
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");
492 if (regions.empty()) {
493 result.AddError(absl::StrFormat(
494 "No suitable freespace found for %d bytes (need >=%d bytes with >=80%% free)",
498 return absl::NotFoundError(
"No freespace available");
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;
513 result.AddInfo(absl::StrFormat(
"Selected region: %s ($%06X-$%06X, %d%% free)",
515 selected_region.
start, selected_region.
end,
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";
527 result.generated_code = code.str();
528 result.symbols[label] = selected_region.
start;
530 result.AddInfo(absl::StrFormat(
"Allocated %d bytes in %s", size,
532 selected_region.
start);
535 if (regions.size() > 1) {
536 result.AddInfo(absl::StrFormat(
"Found %zu other suitable regions",
537 regions.size() - 1));
544 formatter.AddField(
"status",
"complete");
545 return absl::OkStatus();
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");
569 result.
AddError(
"Failed to load sprite template");
572 return tmpl.status();
576 std::map<std::string, std::string> params = {
577 {
"SPRITE_NAME",
name},
578 {
"INIT_CODE", init_code},
579 {
"MAIN_CODE", main_code},
583 result.
AddInfo(absl::StrFormat(
"Generated sprite template for '%s'",
name.c_str()));
589 formatter.
AddField(
"status",
"complete");
590 return absl::OkStatus();
598 Rom* ,
const resources::ArgumentParser& parser,
599 resources::OutputFormatter& formatter) {
600 CodeGenerationResult result;
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");
611 std::map<std::string, uint32_t> event_addresses = {
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()));
623 return absl::InvalidArgumentError(
"Invalid event type");
626 uint32_t hook_address = it->second;
631 result.AddError(
"Failed to load event_handler template");
634 return tmpl.status();
638 std::map<std::string, std::string> params = {
639 {
"EVENT_TYPE", type},
640 {
"HOOK_ADDRESS", absl::StrFormat(
"%06X", hook_address)},
642 {
"CUSTOM_CODE", custom_code},
646 result.symbols[label +
"_Handler"] = hook_address;
648 result.AddInfo(absl::StrFormat(
"Generated %s event handler '%s'",
649 type.c_str(), label.c_str()),
656 formatter.AddField(
"status",
"complete");
657 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< uint8_t > ReadByte(int offset)
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)