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"});
62 templates.push_back({
"sprite",
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)
73 CMP #$08 : BEQ .initialize
79 LDA #$09 : STA $0DD0, X
86 {"SPRITE_NAME",
"INIT_CODE",
"MAIN_CODE"},
87 "Complete sprite with init and main loop"});
92 R
"(; Freespace Allocation
93org ${{FREESPACE_ADDRESS}}
98; Hook from existing code
103 {"LABEL",
"FREESPACE_ADDRESS",
"HOOK_ADDRESS",
"CODE",
"NOP_FILL"},
104 "Allocate code in freespace with hook from existing code"});
107 templates.push_back({
"jsl_hook",
113 {"HOOK_ADDRESS",
"LABEL",
"NOP_FILL"},
114 "Simple JSL hook at address"});
117 templates.push_back({
"event_handler",
118 R
"(; {{EVENT_TYPE}} Event Handler
120 JSL {{LABEL}}_Handler
132 {"EVENT_TYPE",
"HOOK_ADDRESS",
"LABEL",
"CUSTOM_CODE"},
133 "Event handler with state preservation"});
143 uint32_t address)
const {
145 if (address >= rom->
size()) {
146 return absl::InvalidArgumentError(absl::StrFormat(
147 "Address 0x%06X is beyond ROM size (0x%X)", address, rom->
size()));
151 auto opcode_result = rom->
ReadByte(address);
152 if (!opcode_result.ok()) {
153 return opcode_result.status();
155 uint8_t opcode = *opcode_result;
157 if (opcode == 0x22) {
158 return absl::AlreadyExistsError(absl::StrFormat(
159 "Address 0x%06X already contains a JSL instruction", address));
161 if (opcode == 0x5C) {
162 return absl::AlreadyExistsError(absl::StrFormat(
163 "Address 0x%06X already contains a JML instruction", address));
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));
172 return absl::OkStatus();
176 Rom* rom,
size_t min_size)
const {
177 std::vector<FreeSpaceRegion> regions;
180 struct RegionCandidate {
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)"},
194 for (
const auto& candidate : candidates) {
196 if (candidate.start >= rom->
size()) {
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;
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()) {
211 uint8_t
byte = *byte_result;
212 if (
byte == 0x00 ||
byte == 0xFF) {
217 int free_percent = (free_bytes * 100) / region_size;
220 if (free_percent >= 80 && region_size >= min_size) {
222 region.
start = candidate.start;
223 region.
end = actual_end;
226 regions.push_back(region);
234 const std::string& template_code,
235 const std::map<std::string, std::string>& params)
const {
236 std::string result = template_code;
239 for (
const auto& [key, value] : params) {
240 std::string placeholder =
"{{" + key +
"}}";
241 result = absl::StrReplaceAll(result, {{placeholder, value}});
248 const std::string&
name)
const {
250 if (tmpl.name ==
name) {
254 return absl::NotFoundError(absl::StrFormat(
"Template '%s' not found",
name));
263 if (addr == address) {
271 uint32_t address)
const {
273 if (addr == address) {
281 const CodeGenerationResult& result)
const {
282 std::ostringstream
json;
285 json <<
" \"success\": " << (result.success ?
"true" :
"false") <<
",\n";
288 json <<
" \"code\": ";
289 if (!result.generated_code.empty()) {
291 std::string escaped_code = result.generated_code;
292 escaped_code = absl::StrReplaceAll(
293 escaped_code, {{
"\\",
"\\\\"}, {
"\"",
"\\\""}, {
"\n",
"\\n"}});
294 json <<
"\"" << escaped_code <<
"\"";
301 json <<
" \"symbols\": {";
302 bool first_symbol =
true;
303 for (
const auto& [label, address] : result.symbols) {
306 json <<
"\"" << label <<
"\": \"" << absl::StrFormat(
"0x%06X", address)
308 first_symbol =
false;
313 json <<
" \"diagnostics\": [\n";
314 for (
size_t i = 0; i < result.diagnostics.size(); ++i) {
315 const auto& diag = result.diagnostics[i];
317 json <<
"\"severity\": \"" << diag.SeverityString() <<
"\", ";
318 json <<
"\"message\": \"" << diag.message <<
"\"";
319 if (diag.address != 0) {
320 json <<
", \"address\": \"" << absl::StrFormat(
"0x%06X", diag.address)
324 if (i < result.diagnostics.size() - 1)
337 std::ostringstream text;
339 text <<
"Code Generation Result\n";
340 text <<
"======================\n\n";
342 text <<
"Status: " << (result.
success ?
"SUCCESS" :
"FAILED") <<
"\n\n";
346 text <<
"Generated Code:\n";
347 text <<
"---------------\n";
353 text <<
"Symbols:\n";
354 for (
const auto& [label, address] : result.symbols) {
355 text <<
" " << label <<
" = " << absl::StrFormat(
"$%06X", address)
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;
425 result.AddInfo(absl::StrFormat(
"Using known safe hook: %s",
430 "Address is not a known safe hook location. Verify manually.", address);
434 std::string nop_fill_str;
435 for (
int i = 0; i < nop_fill; ++i) {
436 nop_fill_str +=
"NOP\n ";
438 if (!nop_fill_str.empty()) {
439 nop_fill_str.pop_back();
440 nop_fill_str.pop_back();
446 result.AddError(
"Failed to load jsl_hook template");
449 return tmpl.status();
452 std::map<std::string, std::string> params = {
453 {
"HOOK_ADDRESS", absl::StrFormat(
"%06X", address)},
455 {
"NOP_FILL", nop_fill_str},
459 result.symbols[label] = address;
462 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()) {
494 absl::StrFormat(
"No suitable freespace found for %d bytes (need >=%d "
495 "bytes with >=80%% free)",
499 return absl::NotFoundError(
"No freespace available");
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;
514 result.AddInfo(absl::StrFormat(
"Selected region: %s ($%06X-$%06X, %d%% free)",
516 selected_region.
start, selected_region.
end,
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";
528 result.generated_code = code.str();
529 result.symbols[label] = selected_region.
start;
531 result.AddInfo(absl::StrFormat(
"Allocated %d bytes in %s", size,
533 selected_region.
start);
536 if (regions.size() > 1) {
537 result.AddInfo(absl::StrFormat(
"Found %zu other suitable regions",
538 regions.size() - 1));
545 formatter.AddField(
"status",
"complete");
546 return absl::OkStatus();
561 std::string init_code = parser.
GetString(
"init-code")
563 "; Initialize sprite here\n LDA #$00 : STA "
564 "$0F50, X ; Example: Clear state");
565 std::string main_code = parser.
GetString(
"main-code")
567 "; Main sprite logic here\n JSR Sprite_Move "
568 " ; Example: Move sprite");
569 std::string format = parser.
GetString(
"format").value_or(
"json");
574 result.
AddError(
"Failed to load sprite template");
577 return tmpl.status();
581 std::map<std::string, std::string> params = {
582 {
"SPRITE_NAME",
name},
583 {
"INIT_CODE", init_code},
584 {
"MAIN_CODE", main_code},
589 absl::StrFormat(
"Generated sprite template for '%s'",
name.c_str()));
595 formatter.
AddField(
"status",
"complete");
596 return absl::OkStatus();
604 Rom* ,
const resources::ArgumentParser& parser,
605 resources::OutputFormatter& formatter) {
606 CodeGenerationResult result;
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");
617 std::map<std::string, uint32_t> event_addresses = {
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()));
629 return absl::InvalidArgumentError(
"Invalid event type");
632 uint32_t hook_address = it->second;
637 result.AddError(
"Failed to load event_handler template");
640 return tmpl.status();
644 std::map<std::string, std::string> params = {
645 {
"EVENT_TYPE", type},
646 {
"HOOK_ADDRESS", absl::StrFormat(
"%06X", hook_address)},
648 {
"CUSTOM_CODE", custom_code},
652 result.symbols[label +
"_Handler"] = hook_address;
654 result.AddInfo(absl::StrFormat(
"Generated %s event handler '%s'",
655 type.c_str(), label.c_str()),
662 formatter.AddField(
"status",
"complete");
663 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)