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