yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_map_screen.cc
Go to the documentation of this file.
2
3#include <fstream>
4
7#include "rom/rom.h"
8#include "rom/snes.h"
9
10namespace yaze {
11namespace zelda3 {
12
13absl::Status OverworldMapScreen::Create(Rom* rom) {
14 if (!rom || !rom->is_loaded()) {
15 return absl::InvalidArgumentError("ROM is not loaded");
16 }
18
19 // Set metadata for overworld map bitmaps
20 // Mode 7 graphics use full 128-color palettes
21
22 // Load Mode 7 graphics (256 tiles, 8x8 pixels each, 8BPP)
23 const int mode7_gfx_addr = 0x0C4000;
24 std::vector<uint8_t> mode7_gfx_raw(0x4000); // Raw tileset data from ROM
25
26 for (int i = 0; i < 0x4000; i++) {
27 ASSIGN_OR_RETURN(mode7_gfx_raw[i], rom->ReadByte(mode7_gfx_addr + i));
28 }
29
30 // Mode 7 tiles are stored in tiled format (each tile's rows are consecutive)
31 // but we need linear bitmap format (all tiles' first rows, then all second
32 // rows) Convert from tiled to linear bitmap layout
33 std::vector<uint8_t> mode7_gfx(0x4000);
34 int pos = 0;
35 for (int sy = 0; sy < 16 * 1024; sy += 1024) { // 16 rows of tiles
36 for (int sx = 0; sx < 16 * 8; sx += 8) { // 16 columns of tiles
37 for (int y = 0; y < 8 * 128; y += 128) { // 8 pixel rows within tile
38 for (int x = 0; x < 8; x++) { // 8 pixels per row
39 mode7_gfx[x + sx + y + sy] = mode7_gfx_raw[pos];
40 pos++;
41 }
42 }
43 }
44 }
45
46 // Create tiles8 bitmap: 128×128 pixels (16×16 tiles = 256 tiles)
47 tiles8_bitmap_.Create(128, 128, 8, mode7_gfx);
50 tiles8_bitmap_.metadata().source_type = "mode7_tileset";
52
53 // Create map bitmap (512x512 for 64x64 tiles at 8x8 each)
54 map_bitmap_.Create(512, 512, 8, std::vector<uint8_t>(512 * 512));
57 map_bitmap_.metadata().source_type = "mode7_map";
59
60 // Light World palette at 0x055B27
61 const int lw_pal_addr = 0x055B27;
62 for (int i = 0; i < 128; i++) {
63 ASSIGN_OR_RETURN(uint16_t snes_color, rom->ReadWord(lw_pal_addr + (i * 2)));
64 // Create SnesColor directly from SNES 15-bit format
66 }
67
68 // Dark World palette at 0x055C27
69 const int dw_pal_addr = 0x055C27;
70 for (int i = 0; i < 128; i++) {
71 ASSIGN_OR_RETURN(uint16_t snes_color, rom->ReadWord(dw_pal_addr + (i * 2)));
72 // Create SnesColor directly from SNES 15-bit format
74 }
75
76 // Load map tile data
78
79 // Render initial map (Light World)
81
82 // Apply palettes AFTER bitmaps are fully initialized
84 map_bitmap_.SetPalette(lw_palette_); // Map also needs palette
85
86 // Ensure bitmaps are marked as active
89
90 // Queue texture creation
95
96 return absl::OkStatus();
97}
98
105
107 // Map data is stored in interleaved format across 4 sections + 1 DW section
108 // Based on ZScream's Constants.IDKZarby = 0x054727
109 // The data alternates between left (32 columns) and right (32 columns)
110 // for the first 2048 tiles, then continues for bottom half
111
112 const int base_addr = 0x054727; // IDKZarby constant from ZScream
113 int p1 = base_addr + 0x0000; // Top-left quadrant data
114 int p2 = base_addr + 0x0400; // Top-right quadrant data
115 int p3 = base_addr + 0x0800; // Bottom-left quadrant data
116 int p4 = base_addr + 0x0C00; // Bottom-right quadrant data
117 int p5 = base_addr + 0x1000; // Dark World additional section
118
119 bool rSide = false; // false = left side, true = right side
120 int cSide = 0; // Column counter within side (0-31)
121 int count = 0; // Output tile index
122
123 // Load 64x64 map with interleaved left/right format
124 while (count < 64 * 64) {
125 if (count < 0x800) { // Top half (first 2048 tiles)
126 if (!rSide) {
127 // Read from left side (p1)
128 ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p1));
129 lw_map_tiles_[count] = tile;
130 dw_map_tiles_[count] = tile;
131 p1++;
132
133 if (cSide >= 31) {
134 cSide = 0;
135 rSide = true;
136 count++;
137 continue;
138 }
139 } else {
140 // Read from right side (p2)
141 ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p2));
142 lw_map_tiles_[count] = tile;
143 dw_map_tiles_[count] = tile;
144 p2++;
145
146 if (cSide >= 31) {
147 cSide = 0;
148 rSide = false;
149 count++;
150 continue;
151 }
152 }
153 } else { // Bottom half (remaining 2048 tiles)
154 if (!rSide) {
155 // Read from left side (p3)
156 ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p3));
157 lw_map_tiles_[count] = tile;
158 dw_map_tiles_[count] = tile;
159 p3++;
160
161 if (cSide >= 31) {
162 cSide = 0;
163 rSide = true;
164 count++;
165 continue;
166 }
167 } else {
168 // Read from right side (p4)
169 ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p4));
170 lw_map_tiles_[count] = tile;
171 dw_map_tiles_[count] = tile;
172 p4++;
173
174 if (cSide >= 31) {
175 cSide = 0;
176 rSide = false;
177 count++;
178 continue;
179 }
180 }
181 }
182
183 cSide++;
184 count++;
185 }
186
187 // Load Dark World specific data (bottom-right 32x32 section)
188 count = 0;
189 int line = 0;
190 while (true) {
191 ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p5));
192 dw_map_tiles_[1040 + count + (line * 64)] = tile;
193 p5++;
194 count++;
195 if (count >= 32) {
196 count = 0;
197 line++;
198 if (line >= 32) {
199 break;
200 }
201 }
202 }
203
204 return absl::OkStatus();
205}
206
207absl::Status OverworldMapScreen::RenderMapLayer(bool use_dark_world) {
208 auto& map_data = map_bitmap_.mutable_data();
209 const auto& tiles8_data = tiles8_bitmap_.vector();
210 const auto& tile_source = use_dark_world ? dw_map_tiles_ : lw_map_tiles_;
211
212 // Render 64x64 tiles (each 8x8 pixels) into 512x512 bitmap
213 for (int yy = 0; yy < 64; yy++) {
214 for (int xx = 0; xx < 64; xx++) {
215 uint8_t tile_id = tile_source[xx + (yy * 64)];
216
217 // Calculate tile position in tiles8_bitmap (16 tiles per row)
218 int tile_x = (tile_id % 16) * 8;
219 int tile_y = (tile_id / 16) * 8;
220
221 // Copy 8x8 tile pixels
222 for (int py = 0; py < 8; py++) {
223 for (int px = 0; px < 8; px++) {
224 int src_index = (tile_x + px) + ((tile_y + py) * 128);
225 int dest_index = (xx * 8 + px) + ((yy * 8 + py) * 512);
226
227 if (src_index < tiles8_data.size() && dest_index < map_data.size()) {
228 map_data[dest_index] = tiles8_data[src_index];
229 }
230 }
231 }
232 }
233 }
234
235 // Copy pixel data to SDL surface
237
238 return absl::OkStatus();
239}
240
241absl::Status OverworldMapScreen::Save(Rom* rom) {
242 // Write data back in the same interleaved format
243 const int base_addr = 0x054727;
244 int p1 = base_addr + 0x0000;
245 int p2 = base_addr + 0x0400;
246 int p3 = base_addr + 0x0800;
247 int p4 = base_addr + 0x0C00;
248 int p5 = base_addr + 0x1000;
249
250 bool rSide = false;
251 int cSide = 0;
252 int count = 0;
253
254 // Write 64x64 map with interleaved left/right format
255 while (count < 64 * 64) {
256 if (count < 0x800) {
257 if (!rSide) {
258 RETURN_IF_ERROR(rom->WriteByte(p1, lw_map_tiles_[count]));
259 p1++;
260
261 if (cSide >= 31) {
262 cSide = 0;
263 rSide = true;
264 count++;
265 continue;
266 }
267 } else {
268 RETURN_IF_ERROR(rom->WriteByte(p2, lw_map_tiles_[count]));
269 p2++;
270
271 if (cSide >= 31) {
272 cSide = 0;
273 rSide = false;
274 count++;
275 continue;
276 }
277 }
278 } else {
279 if (!rSide) {
280 RETURN_IF_ERROR(rom->WriteByte(p3, lw_map_tiles_[count]));
281 p3++;
282
283 if (cSide >= 31) {
284 cSide = 0;
285 rSide = true;
286 count++;
287 continue;
288 }
289 } else {
290 RETURN_IF_ERROR(rom->WriteByte(p4, lw_map_tiles_[count]));
291 p4++;
292
293 if (cSide >= 31) {
294 cSide = 0;
295 rSide = false;
296 count++;
297 continue;
298 }
299 }
300 }
301
302 cSide++;
303 count++;
304 }
305
306 // Write Dark World specific data
307 count = 0;
308 int line = 0;
309 while (true) {
311 rom->WriteByte(p5, dw_map_tiles_[1040 + count + (line * 64)]));
312 p5++;
313 count++;
314 if (count >= 32) {
315 count = 0;
316 line++;
317 if (line >= 32) {
318 break;
319 }
320 }
321 }
322
323 return absl::OkStatus();
324}
325
326absl::Status OverworldMapScreen::LoadCustomMap(const std::string& file_path) {
327 // Load custom map from external binary file
328 std::ifstream file(file_path, std::ios::binary | std::ios::ate);
329 if (!file.is_open()) {
330 return absl::NotFoundError("Could not open custom map file: " + file_path);
331 }
332
333 std::streamsize size = file.tellg();
334 if (size != 4096) {
335 return absl::InvalidArgumentError(
336 "Custom map file must be exactly 4096 bytes (64×64 tiles)");
337 }
338
339 file.seekg(0, std::ios::beg);
340
341 // Read into Light World map buffer (could add option for Dark World later)
342 file.read(reinterpret_cast<char*>(lw_map_tiles_.data()), 4096);
343
344 if (!file) {
345 return absl::InternalError("Failed to read custom map data");
346 }
347
348 // Re-render with new data
351 &map_bitmap_);
352
353 return absl::OkStatus();
354}
355
356absl::Status OverworldMapScreen::SaveCustomMap(const std::string& file_path,
357 bool use_dark_world) {
358 std::ofstream file(file_path, std::ios::binary);
359 if (!file.is_open()) {
360 return absl::InternalError("Could not create custom map file: " +
361 file_path);
362 }
363
364 const auto& tiles = use_dark_world ? dw_map_tiles_ : lw_map_tiles_;
365 file.write(reinterpret_cast<const char*>(tiles.data()), tiles.size());
366
367 if (!file) {
368 return absl::InternalError("Failed to write custom map data");
369 }
370
371 return absl::OkStatus();
372}
373
374} // namespace zelda3
375} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:631
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:563
absl::StatusOr< uint16_t > ReadWord(int offset) const
Definition rom.cc:571
bool is_loaded() const
Definition rom.h:155
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
static Arena & Get()
Definition arena.cc:21
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:202
const std::vector< uint8_t > & vector() const
Definition bitmap.h:402
void UpdateSurfacePixels()
Update SDL surface with current pixel data from data_ vector Call this after modifying pixel data via...
Definition bitmap.cc:379
BitmapMetadata & metadata()
Definition bitmap.h:391
void set_active(bool active)
Definition bitmap.h:407
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:394
std::vector< uint8_t > & mutable_data()
Definition bitmap.h:399
SNES Color container.
Definition snes_color.h:110
void AddColor(const SnesColor &color)
std::array< uint8_t, 64 *64 > dw_map_tiles_
absl::Status SaveCustomMap(const std::string &file_path, bool use_dark_world)
Save map data to external binary file.
absl::Status LoadCustomMap(const std::string &file_path)
Load custom map from external binary file.
std::array< uint8_t, 64 *64 > lw_map_tiles_
absl::Status LoadMapData(Rom *rom)
Load map tile data from ROM Reads the interleaved tile format from 4 ROM sections.
absl::Status Save(Rom *rom)
Save changes back to ROM.
absl::Status RenderMapLayer(bool use_dark_world)
Render map tiles into bitmap.
absl::Status Create(Rom *rom)
Initialize and load overworld map data from ROM.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
SNES color in 15-bit RGB format (BGR555)