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 << "; "
35 "================================================================"
36 "=============\n";
37 asm_code << "; Diggable Tiles Table Patch\n";
38 asm_code << "; Generated by yaze - Yet Another Zelda3 Editor\n";
39 asm_code << "; "
40 "================================================================"
41 "=============\n";
42 asm_code << "; Replaces hardcoded diggable tile checks with bitfield table "
43 "lookup.\n";
44 asm_code
45 << "; Each Map16 tile ID (0-511) has one bit indicating diggability.\n";
46 asm_code << "; "
47 "================================================================"
48 "=============\n\n";
49
50 asm_code << "lorom\n\n";
51
52 if (config.use_zs_compatible_mode) {
53 asm_code << GenerateZSCompatibleRoutine(diggable_tiles, config);
54 } else {
55 asm_code << GenerateVanillaRoutine(diggable_tiles, config);
56 }
57
58 return asm_code.str();
59}
60
62 const DiggableTiles& diggable_tiles,
63 const DiggableTilesPatchConfig& config) {
64 std::ostringstream asm_code;
65
66 // Hook at original location
67 asm_code << "; Hook the original diggable tile check\n";
68 asm_code << "org " << FormatAddress(config.hook_address) << "\n";
69 asm_code << " JSL DiggableTileCheck\n";
70 asm_code << " NOP ; Pad remaining bytes\n";
71 asm_code << " NOP\n";
72 asm_code << " NOP\n\n";
73
74 // New routine in freespace
75 asm_code << "; New diggable tile lookup routine\n";
76 asm_code << "org " << FormatAddress(config.freespace_address) << "\n";
77 asm_code << "DiggableTileCheck:\n";
78 asm_code << " PHX ; Save X register\n";
79 asm_code << " PHY ; Save Y register\n";
80 asm_code << " \n";
81 asm_code << " ; Get tile ID from $7E2000,X (already in A from caller)\n";
82 asm_code << " LDA $7E2000,X\n";
83 asm_code << " AND #$01FF ; Mask to 9 bits (0-511)\n";
84 asm_code << " \n";
85 asm_code << " ; Calculate byte index: tile_id / 8\n";
86 asm_code << " LSR A\n";
87 asm_code << " LSR A\n";
88 asm_code << " LSR A\n";
89 asm_code << " TAX ; X = byte index\n";
90 asm_code << " \n";
91 asm_code << " ; Calculate bit position: tile_id % 8\n";
92 asm_code
93 << " LDA $7E2000,X ; Reload tile ID (lost after shifts)\n";
94 asm_code << " AND #$0007 ; Get bit position (0-7)\n";
95 asm_code << " TAY ; Y = bit position\n";
96 asm_code << " \n";
97 asm_code << " ; Load bit mask from lookup table\n";
98 asm_code << " LDA BitMaskTable,Y\n";
99 asm_code << " AND #$00FF ; Ensure 8-bit mask\n";
100 asm_code << " STA $00 ; Store mask in scratch\n";
101 asm_code << " \n";
102 asm_code << " ; Load byte from diggable table and test bit\n";
103 asm_code << " LDA DiggableTilesTable,X\n";
104 asm_code << " AND #$00FF ; Ensure 8-bit\n";
105 asm_code << " AND $00 ; Test bit\n";
106 asm_code << " \n";
107 asm_code << " PLY ; Restore Y\n";
108 asm_code << " PLX ; Restore X\n";
109 asm_code << " \n";
110 asm_code << " BEQ .not_diggable\n";
111 asm_code << " \n";
112 asm_code << " ; Tile is diggable - jump to handler\n";
113 asm_code << " JML " << FormatAddress(config.diggable_handler) << "\n";
114 asm_code << " \n";
115 asm_code << ".not_diggable:\n";
116 asm_code << " ; Tile is not diggable - continue normal flow\n";
117 asm_code << " JML " << FormatAddress(config.exit_address) << "\n";
118 asm_code << "\n";
119
120 // Bit mask lookup table
121 asm_code << "; Bit mask table for positions 0-7\n";
122 asm_code << "BitMaskTable:\n";
123 asm_code << " db $01, $02, $04, $08, $10, $20, $40, $80\n\n";
124
125 // Data table
126 asm_code << GenerateDataTable(diggable_tiles, config.table_address);
127
128 return asm_code.str();
129}
130
132 const DiggableTiles& diggable_tiles,
133 const DiggableTilesPatchConfig& config) {
134 std::ostringstream asm_code;
135
136 asm_code << "; ZSCustomOverworld Compatible Mode\n";
137 asm_code << "; This patch works alongside ZScream modifications\n\n";
138
139 // For ZS compatibility, we hook differently and chain to existing handlers
140 asm_code << "; Extended hook point for ZS compatibility\n";
141 asm_code << "org " << FormatAddress(config.freespace_address) << "\n";
142 asm_code << "DiggableTileCheck_ZS:\n";
143 asm_code << " ; ZS-compatible lookup routine\n";
144 asm_code << " ; Check our table first, then fall through to ZS handling\n";
145 asm_code << " PHX\n";
146 asm_code << " PHY\n";
147 asm_code << " \n";
148 asm_code << " LDA $7E2000,X\n";
149 asm_code << " AND #$01FF\n";
150 asm_code << " \n";
151 asm_code << " ; Calculate byte index\n";
152 asm_code << " LSR A\n";
153 asm_code << " LSR A\n";
154 asm_code << " LSR A\n";
155 asm_code << " TAX\n";
156 asm_code << " \n";
157 asm_code << " ; Get bit position\n";
158 asm_code << " LDA $7E2000,X\n";
159 asm_code << " AND #$0007\n";
160 asm_code << " TAY\n";
161 asm_code << " \n";
162 asm_code << " ; Test diggable bit\n";
163 asm_code << " LDA BitMaskTable_ZS,Y\n";
164 asm_code << " AND DiggableTilesTable,X\n";
165 asm_code << " \n";
166 asm_code << " PLY\n";
167 asm_code << " PLX\n";
168 asm_code << " \n";
169 asm_code << " BEQ .not_diggable_zs\n";
170 asm_code << " JML " << FormatAddress(config.diggable_handler) << "\n";
171 asm_code << ".not_diggable_zs:\n";
172 asm_code << " JML " << FormatAddress(config.exit_address) << "\n";
173 asm_code << "\n";
174
175 asm_code << "BitMaskTable_ZS:\n";
176 asm_code << " db $01, $02, $04, $08, $10, $20, $40, $80\n\n";
177
178 asm_code << GenerateDataTable(diggable_tiles, config.table_address);
179
180 return asm_code.str();
181}
182
184 const DiggableTiles& diggable_tiles, uint32_t table_address) {
185 std::ostringstream asm_code;
186
187 asm_code << "; Diggable tiles bitfield table (64 bytes = 512 bits)\n";
188 asm_code << "; Each bit represents a Map16 tile ID (0-511)\n";
189 asm_code << "org " << FormatAddress(table_address) << "\n";
190 asm_code << "DiggableTilesTable:\n";
191
192 const auto& data = diggable_tiles.GetRawData();
193
194 // Write data in rows of 16 bytes
195 for (int row = 0; row < 4; ++row) {
196 asm_code << " db ";
197 for (int col = 0; col < 16; ++col) {
198 int idx = row * 16 + col;
199 asm_code << FormatByte(data[idx]);
200 if (col < 15) {
201 asm_code << ", ";
202 }
203 }
204 asm_code << " ; Tiles " << (row * 128) << "-" << ((row + 1) * 128 - 1)
205 << "\n";
206 }
207
208 asm_code << "\n";
209
210 // Add comment showing which tiles are diggable
211 auto diggable_ids = diggable_tiles.GetAllDiggableTileIds();
212 if (!diggable_ids.empty()) {
213 asm_code << "; Currently marked diggable: ";
214 for (size_t i = 0; i < diggable_ids.size(); ++i) {
215 if (i > 0)
216 asm_code << ", ";
217 asm_code << absl::StrFormat("$%03X", diggable_ids[i]);
218 if (i > 0 && (i + 1) % 10 == 0 && i < diggable_ids.size() - 1) {
219 asm_code << "\n; ";
220 }
221 }
222 asm_code << "\n";
223 }
224
225 return asm_code.str();
226}
227
229 const DiggableTiles& diggable_tiles, const std::string& output_path,
230 const DiggableTilesPatchConfig& config) {
231 std::string patch_content = GeneratePatch(diggable_tiles, config);
232
233 std::ofstream file(output_path);
234 if (!file.is_open()) {
235 return absl::InternalError(
236 absl::StrCat("Failed to open file for writing: ", output_path));
237 }
238
239 file << patch_content;
240 file.close();
241
242 if (file.fail()) {
243 return absl::InternalError(
244 absl::StrCat("Failed to write patch file: ", output_path));
245 }
246
247 return absl::OkStatus();
248}
249
251 // Check if the bytes at the vanilla hook address match vanilla code
252 // Vanilla: CMP.w #$0034 = $C9 $34 $00
253 if (!rom.is_loaded()) {
254 return false;
255 }
256
257 // Convert SNES address to ROM offset for LoROM
258 // Bank $1B = ROM offset 0x0D8000 (bank $1B - $80) * 0x8000
259 // $1BBDF4 in LoROM: ((0x1B & 0x7F) * 0x8000) + (0xBDF4 - 0x8000)
260 // = (0x1B * 0x8000) + 0x3DF4
261 // = 0x0D8000 + 0x3DF4 = 0xDBDF4
262 constexpr uint32_t rom_offset = 0xDBDF4;
263
264 if (rom_offset + sizeof(kVanillaBytes) > rom.size()) {
265 return false;
266 }
267
268 // Check if bytes match vanilla using direct data access
269 const auto& data = rom.data();
270 for (size_t i = 0; i < sizeof(kVanillaBytes); ++i) {
271 if (data[rom_offset + i] != kVanillaBytes[i]) {
272 // Bytes differ from vanilla - ZS or other patch detected
273 return true;
274 }
275 }
276
277 return false; // Bytes match vanilla, no ZS hooks detected
278}
279
281 const Rom& rom) {
283
284 if (DetectZSDiggingHooks(rom)) {
285 config.use_zs_compatible_mode = true;
286 // Adjust freespace for ZS compatibility
287 config.freespace_address =
288 0x1BF800; // Different location to avoid conflicts
289 }
290
291 return config;
292}
293
294} // 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.