yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
diggable_tiles_patch.cc
Go to the documentation of this file.
2
3#include <fstream>
4#include <iomanip>
5#include <sstream>
6
7#include "absl/strings/str_cat.h"
8#include "absl/strings/str_format.h"
9
10namespace yaze::zelda3 {
11
12namespace {
13
14// Vanilla bytes at $1BBDF4 - first instruction of diggable check
15// CMP.w #$0034 = $C9 $34 $00
16constexpr uint8_t kVanillaBytes[] = {0xC9, 0x34, 0x00};
17
18std::string FormatAddress(uint32_t addr) {
19 return absl::StrFormat("$%06X", addr);
20}
21
22std::string FormatByte(uint8_t byte) {
23 return absl::StrFormat("$%02X", byte);
24}
25
26} // namespace
27
29 const DiggableTiles& diggable_tiles,
30 const DiggableTilesPatchConfig& config) {
31 std::ostringstream asm_code;
32
33 // Header comment
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";
41
42 asm_code << "lorom\n\n";
43
44 if (config.use_zs_compatible_mode) {
45 asm_code << GenerateZSCompatibleRoutine(diggable_tiles, config);
46 } else {
47 asm_code << GenerateVanillaRoutine(diggable_tiles, config);
48 }
49
50 return asm_code.str();
51}
52
54 const DiggableTiles& diggable_tiles,
55 const DiggableTilesPatchConfig& config) {
56 std::ostringstream asm_code;
57
58 // Hook at original location
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";
63 asm_code << " NOP\n";
64 asm_code << " NOP\n\n";
65
66 // New routine in freespace
67 asm_code << "; New diggable tile lookup routine\n";
68 asm_code << "org " << FormatAddress(config.freespace_address) << "\n";
69 asm_code << "DiggableTileCheck:\n";
70 asm_code << " PHX ; Save X register\n";
71 asm_code << " PHY ; Save Y register\n";
72 asm_code << " \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";
76 asm_code << " \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";
82 asm_code << " \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";
87 asm_code << " \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";
92 asm_code << " \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";
97 asm_code << " \n";
98 asm_code << " PLY ; Restore Y\n";
99 asm_code << " PLX ; Restore X\n";
100 asm_code << " \n";
101 asm_code << " BEQ .not_diggable\n";
102 asm_code << " \n";
103 asm_code << " ; Tile is diggable - jump to handler\n";
104 asm_code << " JML " << FormatAddress(config.diggable_handler) << "\n";
105 asm_code << " \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";
109 asm_code << "\n";
110
111 // Bit mask lookup table
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";
115
116 // Data table
117 asm_code << GenerateDataTable(diggable_tiles, config.table_address);
118
119 return asm_code.str();
120}
121
123 const DiggableTiles& diggable_tiles,
124 const DiggableTilesPatchConfig& config) {
125 std::ostringstream asm_code;
126
127 asm_code << "; ZSCustomOverworld Compatible Mode\n";
128 asm_code << "; This patch works alongside ZScream modifications\n\n";
129
130 // For ZS compatibility, we hook differently and chain to existing handlers
131 asm_code << "; Extended hook point for ZS compatibility\n";
132 asm_code << "org " << FormatAddress(config.freespace_address) << "\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";
138 asm_code << " \n";
139 asm_code << " LDA $7E2000,X\n";
140 asm_code << " AND #$01FF\n";
141 asm_code << " \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";
147 asm_code << " \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";
152 asm_code << " \n";
153 asm_code << " ; Test diggable bit\n";
154 asm_code << " LDA BitMaskTable_ZS,Y\n";
155 asm_code << " AND DiggableTilesTable,X\n";
156 asm_code << " \n";
157 asm_code << " PLY\n";
158 asm_code << " PLX\n";
159 asm_code << " \n";
160 asm_code << " BEQ .not_diggable_zs\n";
161 asm_code << " JML " << FormatAddress(config.diggable_handler) << "\n";
162 asm_code << ".not_diggable_zs:\n";
163 asm_code << " JML " << FormatAddress(config.exit_address) << "\n";
164 asm_code << "\n";
165
166 asm_code << "BitMaskTable_ZS:\n";
167 asm_code << " db $01, $02, $04, $08, $10, $20, $40, $80\n\n";
168
169 asm_code << GenerateDataTable(diggable_tiles, config.table_address);
170
171 return asm_code.str();
172}
173
175 const DiggableTiles& diggable_tiles,
176 uint32_t table_address) {
177 std::ostringstream asm_code;
178
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";
183
184 const auto& data = diggable_tiles.GetRawData();
185
186 // Write data in rows of 16 bytes
187 for (int row = 0; row < 4; ++row) {
188 asm_code << " db ";
189 for (int col = 0; col < 16; ++col) {
190 int idx = row * 16 + col;
191 asm_code << FormatByte(data[idx]);
192 if (col < 15) {
193 asm_code << ", ";
194 }
195 }
196 asm_code << " ; Tiles " << (row * 128) << "-" << ((row + 1) * 128 - 1) << "\n";
197 }
198
199 asm_code << "\n";
200
201 // Add comment showing which tiles are diggable
202 auto diggable_ids = diggable_tiles.GetAllDiggableTileIds();
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) {
209 asm_code << "\n; ";
210 }
211 }
212 asm_code << "\n";
213 }
214
215 return asm_code.str();
216}
217
219 const DiggableTiles& diggable_tiles,
220 const std::string& output_path,
221 const DiggableTilesPatchConfig& config) {
222 std::string patch_content = GeneratePatch(diggable_tiles, config);
223
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));
228 }
229
230 file << patch_content;
231 file.close();
232
233 if (file.fail()) {
234 return absl::InternalError(
235 absl::StrCat("Failed to write patch file: ", output_path));
236 }
237
238 return absl::OkStatus();
239}
240
242 // Check if the bytes at the vanilla hook address match vanilla code
243 // Vanilla: CMP.w #$0034 = $C9 $34 $00
244 if (!rom.is_loaded()) {
245 return false;
246 }
247
248 // Convert SNES address to ROM offset for LoROM
249 // Bank $1B = ROM offset 0x0D8000 (bank $1B - $80) * 0x8000
250 // $1BBDF4 in LoROM: ((0x1B & 0x7F) * 0x8000) + (0xBDF4 - 0x8000)
251 // = (0x1B * 0x8000) + 0x3DF4
252 // = 0x0D8000 + 0x3DF4 = 0xDBDF4
253 constexpr uint32_t rom_offset = 0xDBDF4;
254
255 if (rom_offset + sizeof(kVanillaBytes) > rom.size()) {
256 return false;
257 }
258
259 // Check if bytes match vanilla using direct data access
260 const auto& data = rom.data();
261 for (size_t i = 0; i < sizeof(kVanillaBytes); ++i) {
262 if (data[rom_offset + i] != kVanillaBytes[i]) {
263 // Bytes differ from vanilla - ZS or other patch detected
264 return true;
265 }
266 }
267
268 return false; // Bytes match vanilla, no ZS hooks detected
269}
270
272 const Rom& rom) {
274
275 if (DetectZSDiggingHooks(rom)) {
276 config.use_zs_compatible_mode = true;
277 // Adjust freespace for ZS compatibility
278 config.freespace_address = 0x1BF800; // Different location to avoid conflicts
279 }
280
281 return config;
282}
283
284} // namespace yaze::zelda3
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 data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
bool is_loaded() const
Definition rom.h:128
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.
Zelda 3 specific classes and functions.
Definition editor.h:35
Configuration for diggable tiles ASM patch generation.