7#include "absl/strings/str_cat.h"
8#include "absl/strings/str_format.h"
19 return absl::StrFormat(
"$%06X", addr);
23 return absl::StrFormat(
"$%02X",
byte);
31 std::ostringstream asm_code;
34 asm_code <<
"; =============================================================================\n";
35 asm_code <<
"; Diggable Tiles Table Patch\n";
36 asm_code <<
"; Generated by yaze - Yet Another Zelda3 Editor\n";
37 asm_code <<
"; =============================================================================\n";
38 asm_code <<
"; Replaces hardcoded diggable tile checks with bitfield table lookup.\n";
39 asm_code <<
"; Each Map16 tile ID (0-511) has one bit indicating diggability.\n";
40 asm_code <<
"; =============================================================================\n\n";
42 asm_code <<
"lorom\n\n";
50 return asm_code.str();
56 std::ostringstream asm_code;
59 asm_code <<
"; Hook the original diggable tile check\n";
60 asm_code <<
"org " << FormatAddress(config.
hook_address) <<
"\n";
61 asm_code <<
" JSL DiggableTileCheck\n";
62 asm_code <<
" NOP ; Pad remaining bytes\n";
64 asm_code <<
" NOP\n\n";
67 asm_code <<
"; New diggable tile lookup routine\n";
69 asm_code <<
"DiggableTileCheck:\n";
70 asm_code <<
" PHX ; Save X register\n";
71 asm_code <<
" PHY ; Save Y register\n";
73 asm_code <<
" ; Get tile ID from $7E2000,X (already in A from caller)\n";
74 asm_code <<
" LDA $7E2000,X\n";
75 asm_code <<
" AND #$01FF ; Mask to 9 bits (0-511)\n";
77 asm_code <<
" ; Calculate byte index: tile_id / 8\n";
78 asm_code <<
" LSR A\n";
79 asm_code <<
" LSR A\n";
80 asm_code <<
" LSR A\n";
81 asm_code <<
" TAX ; X = byte index\n";
83 asm_code <<
" ; Calculate bit position: tile_id % 8\n";
84 asm_code <<
" LDA $7E2000,X ; Reload tile ID (lost after shifts)\n";
85 asm_code <<
" AND #$0007 ; Get bit position (0-7)\n";
86 asm_code <<
" TAY ; Y = bit position\n";
88 asm_code <<
" ; Load bit mask from lookup table\n";
89 asm_code <<
" LDA BitMaskTable,Y\n";
90 asm_code <<
" AND #$00FF ; Ensure 8-bit mask\n";
91 asm_code <<
" STA $00 ; Store mask in scratch\n";
93 asm_code <<
" ; Load byte from diggable table and test bit\n";
94 asm_code <<
" LDA DiggableTilesTable,X\n";
95 asm_code <<
" AND #$00FF ; Ensure 8-bit\n";
96 asm_code <<
" AND $00 ; Test bit\n";
98 asm_code <<
" PLY ; Restore Y\n";
99 asm_code <<
" PLX ; Restore X\n";
101 asm_code <<
" BEQ .not_diggable\n";
103 asm_code <<
" ; Tile is diggable - jump to handler\n";
106 asm_code <<
".not_diggable:\n";
107 asm_code <<
" ; Tile is not diggable - continue normal flow\n";
108 asm_code <<
" JML " << FormatAddress(config.
exit_address) <<
"\n";
112 asm_code <<
"; Bit mask table for positions 0-7\n";
113 asm_code <<
"BitMaskTable:\n";
114 asm_code <<
" db $01, $02, $04, $08, $10, $20, $40, $80\n\n";
119 return asm_code.str();
125 std::ostringstream asm_code;
127 asm_code <<
"; ZSCustomOverworld Compatible Mode\n";
128 asm_code <<
"; This patch works alongside ZScream modifications\n\n";
131 asm_code <<
"; Extended hook point for ZS compatibility\n";
133 asm_code <<
"DiggableTileCheck_ZS:\n";
134 asm_code <<
" ; ZS-compatible lookup routine\n";
135 asm_code <<
" ; Check our table first, then fall through to ZS handling\n";
136 asm_code <<
" PHX\n";
137 asm_code <<
" PHY\n";
139 asm_code <<
" LDA $7E2000,X\n";
140 asm_code <<
" AND #$01FF\n";
142 asm_code <<
" ; Calculate byte index\n";
143 asm_code <<
" LSR A\n";
144 asm_code <<
" LSR A\n";
145 asm_code <<
" LSR A\n";
146 asm_code <<
" TAX\n";
148 asm_code <<
" ; Get bit position\n";
149 asm_code <<
" LDA $7E2000,X\n";
150 asm_code <<
" AND #$0007\n";
151 asm_code <<
" TAY\n";
153 asm_code <<
" ; Test diggable bit\n";
154 asm_code <<
" LDA BitMaskTable_ZS,Y\n";
155 asm_code <<
" AND DiggableTilesTable,X\n";
157 asm_code <<
" PLY\n";
158 asm_code <<
" PLX\n";
160 asm_code <<
" BEQ .not_diggable_zs\n";
162 asm_code <<
".not_diggable_zs:\n";
163 asm_code <<
" JML " << FormatAddress(config.
exit_address) <<
"\n";
166 asm_code <<
"BitMaskTable_ZS:\n";
167 asm_code <<
" db $01, $02, $04, $08, $10, $20, $40, $80\n\n";
171 return asm_code.str();
176 uint32_t table_address) {
177 std::ostringstream asm_code;
179 asm_code <<
"; Diggable tiles bitfield table (64 bytes = 512 bits)\n";
180 asm_code <<
"; Each bit represents a Map16 tile ID (0-511)\n";
181 asm_code <<
"org " << FormatAddress(table_address) <<
"\n";
182 asm_code <<
"DiggableTilesTable:\n";
184 const auto& data = diggable_tiles.
GetRawData();
187 for (
int row = 0; row < 4; ++row) {
189 for (
int col = 0; col < 16; ++col) {
190 int idx = row * 16 + col;
191 asm_code << FormatByte(data[idx]);
196 asm_code <<
" ; Tiles " << (row * 128) <<
"-" << ((row + 1) * 128 - 1) <<
"\n";
203 if (!diggable_ids.empty()) {
204 asm_code <<
"; Currently marked diggable: ";
205 for (
size_t i = 0; i < diggable_ids.size(); ++i) {
206 if (i > 0) asm_code <<
", ";
207 asm_code << absl::StrFormat(
"$%03X", diggable_ids[i]);
208 if (i > 0 && (i + 1) % 10 == 0 && i < diggable_ids.size() - 1) {
215 return asm_code.str();
220 const std::string& output_path,
222 std::string patch_content =
GeneratePatch(diggable_tiles, config);
224 std::ofstream file(output_path);
225 if (!file.is_open()) {
226 return absl::InternalError(
227 absl::StrCat(
"Failed to open file for writing: ", output_path));
230 file << patch_content;
234 return absl::InternalError(
235 absl::StrCat(
"Failed to write patch file: ", output_path));
238 return absl::OkStatus();
253 constexpr uint32_t rom_offset = 0xDBDF4;
255 if (rom_offset +
sizeof(kVanillaBytes) > rom.
size()) {
260 const auto& data = rom.
data();
261 for (
size_t i = 0; i <
sizeof(kVanillaBytes); ++i) {
262 if (data[rom_offset + i] != kVanillaBytes[i]) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
static std::string GenerateDataTable(const DiggableTiles &diggable_tiles, uint32_t table_address)
Generate the data table section of the patch.
static bool DetectZSDiggingHooks(const Rom &rom)
Detect if ROM has ZSCustomOverworld modifications to digging code.
static std::string GenerateVanillaRoutine(const DiggableTiles &diggable_tiles, const DiggableTilesPatchConfig &config)
Generate the vanilla-mode lookup routine ASM.
static std::string GenerateZSCompatibleRoutine(const DiggableTiles &diggable_tiles, const DiggableTilesPatchConfig &config)
Generate the ZS-compatible lookup routine ASM.
static absl::Status ExportPatchFile(const DiggableTiles &diggable_tiles, const std::string &output_path, const DiggableTilesPatchConfig &config={})
Export ASM patch to a file.
static std::string GeneratePatch(const DiggableTiles &diggable_tiles, const DiggableTilesPatchConfig &config={})
Generate ASM patch code for the diggable tiles table.
static DiggableTilesPatchConfig GetRecommendedConfig(const Rom &rom)
Get recommended patch configuration based on ROM analysis.
Manages diggable tile state as a 512-bit bitfield.
std::vector< uint16_t > GetAllDiggableTileIds() const
Get all tile IDs that are currently marked as diggable.
const std::array< uint8_t, kDiggableTilesBitfieldSize > & GetRawData() const
Get raw bitfield data for direct ROM writing.
std::string FormatAddress(uint32_t addr)
constexpr uint8_t kVanillaBytes[]
std::string FormatByte(uint8_t byte)
Zelda 3 specific classes and functions.
Configuration for diggable tiles ASM patch generation.
uint32_t diggable_handler
uint32_t freespace_address
bool use_zs_compatible_mode