yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
title_screen.cc
Go to the documentation of this file.
1#include "title_screen.h"
2
3#include <cstdint>
4
7#include "rom/rom.h"
8#include "rom/snes.h"
9#include "util/log.h"
10
11namespace yaze {
12namespace zelda3 {
13
14absl::Status TitleScreen::Create(Rom* rom, GameData* game_data) {
15 if (!rom || !rom->is_loaded()) {
16 return absl::InvalidArgumentError("ROM is not loaded");
17 }
19 game_data_ = game_data;
20
21 // Initialize bitmaps for each layer
22 tiles8_bitmap_.Create(128, 512, 8, std::vector<uint8_t>(0x20000));
23 tiles_bg1_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
24 tiles_bg2_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
25 oam_bg_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
26
27 // Set metadata for title screen bitmaps
28 // Title screen uses 3BPP graphics (like all LTTP data) with composite
29 // 64-color palette
31 tiles8_bitmap_.metadata().palette_format = 0; // Full 64-color palette
32 tiles8_bitmap_.metadata().source_type = "graphics_sheet";
34
37 0; // Uses full palette with sub-palette indexing
38 tiles_bg1_bitmap_.metadata().source_type = "screen_buffer";
40
43 tiles_bg2_bitmap_.metadata().source_type = "screen_buffer";
45
48 oam_bg_bitmap_.metadata().source_type = "screen_buffer";
50
51 // Initialize composite bitmap for stacked BG rendering (256x256 = 65536
52 // bytes)
53 title_composite_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(256 * 256));
58
59 // Initialize tilemap buffers
60 tiles_bg1_buffer_.fill(0x492); // Default empty tile
61 tiles_bg2_buffer_.fill(0x492);
62
63 // Load palette (title screen uses 3BPP graphics with 8 palettes of 8 colors
64 // each) Build composite palette from multiple sources (matches ZScream's
65 // SetColorsPalette) Palette 0: OverworldMainPalettes[5] Palette 1:
66 // OverworldAnimatedPalettes[0] Palette 2: OverworldAuxPalettes[3] Palette 3:
67 // OverworldAuxPalettes[3] Palette 4: HudPalettes[0] Palette 5:
68 // Transparent/black Palette 6: SpritesAux1Palettes[1] Palette 7:
69 // SpritesAux1Palettes[1]
70
71 // Get palette group from GameData if available, otherwise fail
72 if (!game_data_) {
73 return absl::FailedPreconditionError("GameData not set for TitleScreen");
74 }
75 auto& pal_group = game_data_->palette_groups;
76
77 // Add each 8-color palette in sequence (EXACTLY 8 colors each for 64 total)
78 size_t palette_start = palette_.size();
79
80 // Palette 0: OverworldMainPalettes[5]
81 if (pal_group.overworld_main.size() > 5) {
82 const auto& src = pal_group.overworld_main[5];
83 size_t added = 0;
84 for (size_t i = 0; i < 8 && i < src.size(); i++) {
85 palette_.AddColor(src[i]);
86 added++;
87 }
88 // Pad with black if less than 8 colors
89 while (added < 8) {
91 added++;
92 }
93 LOG_INFO("TitleScreen",
94 "Palette 0: added %zu colors from overworld_main[5]", added);
95 }
96
97 // Palette 1: OverworldAnimatedPalettes[0]
98 if (pal_group.overworld_animated.size() > 0) {
99 const auto& src = pal_group.overworld_animated[0];
100 size_t added = 0;
101 for (size_t i = 0; i < 8 && i < src.size(); i++) {
102 palette_.AddColor(src[i]);
103 added++;
104 }
105 while (added < 8) {
107 added++;
108 }
109 LOG_INFO("TitleScreen",
110 "Palette 1: added %zu colors from overworld_animated[0]", added);
111 }
112
113 // Palette 2 & 3: OverworldAuxPalettes[3] (used twice)
114 if (pal_group.overworld_aux.size() > 3) {
115 auto src = pal_group.overworld_aux[3]; // Copy, as this returns by value
116 for (int pal = 0; pal < 2; pal++) {
117 size_t added = 0;
118 for (size_t i = 0; i < 8 && i < src.size(); i++) {
119 palette_.AddColor(src[i]);
120 added++;
121 }
122 while (added < 8) {
124 added++;
125 }
126 LOG_INFO("TitleScreen",
127 "Palette %d: added %zu colors from overworld_aux[3]", 2 + pal,
128 added);
129 }
130 }
131
132 // Palette 4: HudPalettes[0]
133 if (pal_group.hud.size() > 0) {
134 auto src = pal_group.hud.palette(0); // Copy, as this returns by value
135 size_t added = 0;
136 for (size_t i = 0; i < 8 && i < src.size(); i++) {
137 palette_.AddColor(src[i]);
138 added++;
139 }
140 while (added < 8) {
142 added++;
143 }
144 LOG_INFO("TitleScreen", "Palette 4: added %zu colors from hud[0]", added);
145 }
146
147 // Palette 5: 8 transparent/black colors
148 for (int i = 0; i < 8; i++) {
150 }
151 LOG_INFO("TitleScreen", "Palette 5: added 8 transparent/black colors");
152
153 // Palette 6 & 7: SpritesAux1Palettes[1] (used twice)
154 if (pal_group.sprites_aux1.size() > 1) {
155 auto src = pal_group.sprites_aux1[1]; // Copy, as this returns by value
156 for (int pal = 0; pal < 2; pal++) {
157 size_t added = 0;
158 for (size_t i = 0; i < 8 && i < src.size(); i++) {
159 palette_.AddColor(src[i]);
160 added++;
161 }
162 while (added < 8) {
164 added++;
165 }
166 LOG_INFO("TitleScreen",
167 "Palette %d: added %zu colors from sprites_aux1[1]", 6 + pal,
168 added);
169 }
170 }
171
172 LOG_INFO("TitleScreen", "Built composite palette: %zu colors (should be 64)",
173 palette_.size());
174
175 // Build tile16 blockset from graphics
177
178 // Load tilemap data from ROM
180
181 return absl::OkStatus();
182}
183
185 tiles_bg1_buffer_.fill(0x492);
186 tiles_bg2_buffer_.fill(0x492);
187 for (auto& tile : oam_data_) {
188 tile = {};
189 }
192 tile16_blockset_.map_size = {0, 0};
193 palette_.clear();
194 pal_selected_ = 2;
195 game_data_ = nullptr;
196}
197
198absl::Status TitleScreen::BuildTileset(Rom* rom) {
199 // Title screen uses specific graphics sheets
200 // Load sheet configuration from ROM (matches ZScream implementation)
201 uint8_t staticgfx[16] = {0};
202
203 // Read title screen GFX group indices from ROM
204 constexpr int kTitleScreenTilesGFX = 0x064207;
205 constexpr int kTitleScreenSpritesGFX = 0x06420C;
206
207 ASSIGN_OR_RETURN(uint8_t tiles_gfx_index,
208 rom->ReadByte(kTitleScreenTilesGFX));
209 ASSIGN_OR_RETURN(uint8_t sprites_gfx_index,
210 rom->ReadByte(kTitleScreenSpritesGFX));
211
212 LOG_INFO("TitleScreen", "GFX group indices: tiles=%d, sprites=%d",
213 tiles_gfx_index, sprites_gfx_index);
214
215 // Load main graphics sheets (slots 0-7) from GFX groups
216 // First, read the GFX groups pointer (2 bytes at 0x6237)
217 constexpr int kGfxGroupsPointer = 0x6237;
218 ASSIGN_OR_RETURN(uint16_t gfx_groups_snes, rom->ReadWord(kGfxGroupsPointer));
219 uint32_t main_gfx_table = SnesToPc(gfx_groups_snes);
220
221 LOG_INFO("TitleScreen", "GFX groups table: SNES=0x%04X, PC=0x%06X",
222 gfx_groups_snes, main_gfx_table);
223
224 // Read 8 bytes from mainGfx[tiles_gfx_index]
225 int main_gfx_offset = main_gfx_table + (tiles_gfx_index * 8);
226 for (int i = 0; i < 8; i++) {
227 ASSIGN_OR_RETURN(staticgfx[i], rom->ReadByte(main_gfx_offset + i));
228 }
229
230 // Load sprite graphics sheets (slots 8-12) - matches ZScream logic
231 // Sprite GFX groups are after the 37 main groups (37 * 8 = 296 bytes)
232 // and 82 room groups (82 * 4 = 328 bytes) = 624 bytes offset
233 int sprite_gfx_table = main_gfx_table + (37 * 8) + (82 * 4);
234 int sprite_gfx_offset = sprite_gfx_table + (sprites_gfx_index * 4);
235
236 staticgfx[8] = 115 + 0; // Title logo base
237 ASSIGN_OR_RETURN(uint8_t sprite3, rom->ReadByte(sprite_gfx_offset + 3));
238 staticgfx[9] = 115 + sprite3; // Sprite graphics slot 3
239 staticgfx[10] = 115 + 6; // Additional graphics
240 staticgfx[11] = 115 + 7; // Additional graphics
241 ASSIGN_OR_RETURN(uint8_t sprite0, rom->ReadByte(sprite_gfx_offset + 0));
242 staticgfx[12] = 115 + sprite0; // Sprite graphics slot 0
243 staticgfx[13] = 112; // UI graphics
244 staticgfx[14] = 112; // UI graphics
245 staticgfx[15] = 112; // UI graphics
246
247 // Use pre-converted graphics from GameData buffer
248 // Title screen uses standard 3BPP graphics, no special offset needed
249 if (!game_data_) {
250 return absl::FailedPreconditionError("GameData not set");
251 }
252 const auto& gfx_buffer = game_data_->graphics_buffer;
253 auto& tiles8_data = tiles8_bitmap_.mutable_data();
254
255 LOG_INFO("TitleScreen", "Graphics buffer size: %zu bytes", gfx_buffer.size());
256 LOG_INFO("TitleScreen", "Tiles8 bitmap size: %zu bytes", tiles8_data.size());
257
258 // Copy graphics sheets to tiles8_bitmap
259 LOG_INFO("TitleScreen", "Loading 16 graphics sheets:");
260 for (int i = 0; i < 16; i++) {
261 LOG_INFO("TitleScreen", " staticgfx[%d] = %d", i, staticgfx[i]);
262 }
263
264 for (int i = 0; i < 16; i++) {
265 int sheet_id = staticgfx[i];
266
267 // Validate sheet ID (ROM has 223 sheets: 0-222)
268 if (sheet_id > 222) {
269 LOG_ERROR(
270 "TitleScreen",
271 "Sheet %d: Invalid sheet_id=%d (max 222), using sheet 0 instead", i,
272 sheet_id);
273 sheet_id = 0; // Fallback to a valid sheet
274 }
275
276 int source_offset = sheet_id * 0x1000; // Each 8BPP sheet is 0x1000 bytes
277 int dest_offset = i * 0x1000;
278
279 if (source_offset + 0x1000 <= gfx_buffer.size() &&
280 dest_offset + 0x1000 <= tiles8_data.size()) {
281 std::copy(gfx_buffer.begin() + source_offset,
282 gfx_buffer.begin() + source_offset + 0x1000,
283 tiles8_data.begin() + dest_offset);
284
285 // Sample first few pixels
286 LOG_INFO("TitleScreen",
287 "Sheet %d (ID %d): Sample pixels: %02X %02X %02X %02X", i,
288 sheet_id, tiles8_data[dest_offset], tiles8_data[dest_offset + 1],
289 tiles8_data[dest_offset + 2], tiles8_data[dest_offset + 3]);
290 } else {
291 LOG_ERROR("TitleScreen",
292 "Sheet %d (ID %d): out of bounds! source=%d, dest=%d, "
293 "buffer_size=%zu",
294 i, sheet_id, source_offset, dest_offset, gfx_buffer.size());
295 }
296 }
297
298 // Set palette on tiles8 bitmap
300
301 LOG_INFO("TitleScreen", "Applied palette to tiles8_bitmap: %zu colors",
302 palette_.size());
303 // Log first few colors
304 if (palette_.size() >= 8) {
305 LOG_INFO("TitleScreen",
306 " Palette colors 0-7: %04X %04X %04X %04X %04X %04X %04X %04X",
307 palette_[0].snes(), palette_[1].snes(), palette_[2].snes(),
308 palette_[3].snes(), palette_[4].snes(), palette_[5].snes(),
309 palette_[6].snes(), palette_[7].snes());
310 }
311
312 // Queue texture creation via Arena's deferred system
315
316 // TODO: Build tile16 blockset from tile8 data
317 // This would involve composing 16x16 tiles from 8x8 tiles
318 // For now, we'll use the tile8 data directly
319
320 return absl::OkStatus();
321}
322
324 // Check if ROM uses ZScream's expanded format (data at 0x108000 PC)
325 // by reading the title screen pointer at 0x137A+3, 0x1383+3, 0x138C+3
326 ASSIGN_OR_RETURN(uint8_t bank_byte, rom->ReadByte(0x138C + 3));
327 ASSIGN_OR_RETURN(uint8_t high_byte, rom->ReadByte(0x1383 + 3));
328 ASSIGN_OR_RETURN(uint8_t low_byte, rom->ReadByte(0x137A + 3));
329
330 uint32_t snes_addr = (bank_byte << 16) | (high_byte << 8) | low_byte;
331 uint32_t pc_addr = SnesToPc(snes_addr);
332
333 LOG_INFO("TitleScreen", "Title screen pointer: SNES=0x%06X, PC=0x%06X",
334 snes_addr, pc_addr);
335
336 // Initialize buffers with default empty tile
337 for (int i = 0; i < 1024; i++) {
338 tiles_bg1_buffer_[i] = 0x492;
339 tiles_bg2_buffer_[i] = 0x492;
340 }
341
342 // ZScream expanded format at 0x108000 (PC)
343 if (pc_addr >= 0x108000 && pc_addr <= 0x10FFFF) {
344 LOG_INFO("TitleScreen", "Detected ZScream expanded format");
345
346 int pos = pc_addr;
347
348 // Read BG1 header: dest (word), length (word)
349 ASSIGN_OR_RETURN(uint16_t bg1_dest, rom->ReadWord(pos));
350 pos += 2;
351 ASSIGN_OR_RETURN(uint16_t bg1_length, rom->ReadWord(pos));
352 pos += 2;
353
354 LOG_INFO("TitleScreen", "BG1 Header: dest=0x%04X, length=0x%04X", bg1_dest,
355 bg1_length);
356
357 // Read 1024 BG1 tiles (2 bytes each = 2048 bytes)
358 for (int i = 0; i < 1024; i++) {
359 ASSIGN_OR_RETURN(uint16_t tile, rom->ReadWord(pos));
360 tiles_bg1_buffer_[i] = tile;
361 pos += 2;
362 }
363
364 // Read BG2 header: dest (word), length (word)
365 ASSIGN_OR_RETURN(uint16_t bg2_dest, rom->ReadWord(pos));
366 pos += 2;
367 ASSIGN_OR_RETURN(uint16_t bg2_length, rom->ReadWord(pos));
368 pos += 2;
369
370 LOG_INFO("TitleScreen", "BG2 Header: dest=0x%04X, length=0x%04X", bg2_dest,
371 bg2_length);
372
373 // Read 1024 BG2 tiles (2 bytes each = 2048 bytes)
374 for (int i = 0; i < 1024; i++) {
375 ASSIGN_OR_RETURN(uint16_t tile, rom->ReadWord(pos));
376 tiles_bg2_buffer_[i] = tile;
377 pos += 2;
378 }
379
380 LOG_INFO("TitleScreen",
381 "Loaded 2048 tilemap entries from ZScream expanded format");
382 }
383 // Vanilla format: Sequential DMA blocks at pointer location
384 // NOTE: This reads from the pointer but may not be the correct format
385 // See docs/screen-editor-status.md for details on this ongoing issue
386 else {
387 LOG_INFO("TitleScreen", "Using vanilla DMA format (EXPERIMENTAL)");
388
389 int pos = pc_addr;
390 int total_entries = 0;
391 int blocks_read = 0;
392
393 // Read DMA blocks until we hit terminator or safety limit
394 while (pos < rom->size() && blocks_read < 20) {
395 // Read destination address (word)
396 ASSIGN_OR_RETURN(uint16_t dest_addr, rom->ReadWord(pos));
397 pos += 2;
398
399 // Check for terminator
400 if (dest_addr == 0xFFFF || (dest_addr & 0xFF) == 0xFF) {
401 LOG_INFO("TitleScreen", "Found DMA terminator at pos=0x%06X", pos - 2);
402 break;
403 }
404
405 // Read length/flags (word)
406 ASSIGN_OR_RETURN(uint16_t length_flags, rom->ReadWord(pos));
407 pos += 2;
408
409 bool increment64 = (length_flags & 0x8000) == 0x8000;
410 bool fixsource = (length_flags & 0x4000) == 0x4000;
411 int length = (length_flags & 0x0FFF);
412
413 LOG_INFO("TitleScreen", "Block %d: dest=0x%04X, len=%d, inc64=%d, fix=%d",
414 blocks_read, dest_addr, length, increment64, fixsource);
415
416 int tile_count = (length / 2) + 1;
417 int source_start = pos;
418
419 // Read tiles
420 for (int j = 0; j < tile_count; j++) {
421 ASSIGN_OR_RETURN(uint16_t tiledata, rom->ReadWord(pos));
422
423 // Determine which layer based on destination address
424 if (dest_addr >= 0x1000 && dest_addr < 0x1400) {
425 // BG1 layer
426 int index = (dest_addr - 0x1000) / 2;
427 if (index < 1024) {
428 tiles_bg1_buffer_[index] = tiledata;
429 total_entries++;
430 }
431 } else if (dest_addr < 0x0800) {
432 // BG2 layer
433 int index = dest_addr / 2;
434 if (index < 1024) {
435 tiles_bg2_buffer_[index] = tiledata;
436 total_entries++;
437 }
438 }
439
440 // Advance destination address
441 if (increment64) {
442 dest_addr += 64;
443 } else {
444 dest_addr += 2;
445 }
446
447 // Advance source position
448 if (!fixsource) {
449 pos += 2;
450 }
451 }
452
453 // If fixsource, only advance by one tile
454 if (fixsource) {
455 pos = source_start + 2;
456 }
457
458 blocks_read++;
459 }
460
461 LOG_INFO("TitleScreen",
462 "Loaded %d tilemap entries from %d DMA blocks (may be incorrect)",
463 total_entries, blocks_read);
464 }
465
466 pal_selected_ = 2;
467
468 // Render tilemaps into bitmap pixels
471
472 // Apply palettes to layer bitmaps AFTER rendering
477
478 // Ensure bitmaps are marked as active
483
484 // Queue texture creation for all layer bitmaps
493
494 // Initial composite render (both layers visible)
496
497 return absl::OkStatus();
498}
499
501 // BG1 layer is 32x32 tiles (256x256 pixels)
502 auto& bg1_data = tiles_bg1_bitmap_.mutable_data();
503 const auto& tile8_bitmap_data = tiles8_bitmap_.vector();
504
505 // Render each tile in the 32x32 tilemap
506 for (int tile_y = 0; tile_y < 32; tile_y++) {
507 for (int tile_x = 0; tile_x < 32; tile_x++) {
508 int tilemap_index = tile_y * 32 + tile_x;
509 uint16_t tile_word = tiles_bg1_buffer_[tilemap_index];
510
511 // Extract tile info from SNES tile word (vhopppcc cccccccc format)
512 int tile_id = tile_word & 0x3FF; // Bits 0-9: tile ID
513 int palette = (tile_word >> 10) & 0x07; // Bits 10-12: palette
514 bool h_flip = (tile_word & 0x4000) != 0; // Bit 14: horizontal flip
515 bool v_flip = (tile_word & 0x8000) != 0; // Bit 15: vertical flip
516
517 // Debug: Log suspicious tile IDs
518 if (tile_id > 512) {
519 LOG_WARN("TitleScreen",
520 "BG1: Suspicious tile_id=%d at (%d,%d), word=0x%04X", tile_id,
521 tile_x, tile_y, tile_word);
522 }
523
524 // Calculate source position in tiles8_bitmap_
525 // tiles8_bitmap_ is 128 pixels wide, 512 pixels tall (16 sheets × 32
526 // pixels) Each sheet has 256 tiles (16×16 tiles, 128×32 pixels, 0x1000
527 // bytes)
528 int sheet_index = tile_id / 256; // Which sheet (0-15)
529 int tile_in_sheet = tile_id % 256; // Tile within sheet (0-255)
530 int src_tile_x = (tile_in_sheet % 16) * 8;
531 int src_tile_y = (sheet_index * 32) + ((tile_in_sheet / 16) * 8);
532
533 // Copy 8x8 tile pixels from tile8 bitmap to BG1 bitmap
534 for (int py = 0; py < 8; py++) {
535 for (int px = 0; px < 8; px++) {
536 // Apply flipping
537 int src_px = h_flip ? (7 - px) : px;
538 int src_py = v_flip ? (7 - py) : py;
539
540 // Calculate source and destination positions
541 int src_x = src_tile_x + src_px;
542 int src_y = src_tile_y + src_py;
543 int src_pos =
544 src_y * 128 + src_x; // tiles8_bitmap_ is 128 pixels wide
545
546 int dest_x = tile_x * 8 + px;
547 int dest_y = tile_y * 8 + py;
548 int dest_pos = dest_y * 256 + dest_x; // BG1 is 256 pixels wide
549
550 // Copy pixel with palette application
551 // Graphics are 3BPP in ROM, converted to 8BPP indexed with +0x88
552 // offset
553 if (src_pos < tile8_bitmap_data.size() &&
554 dest_pos < bg1_data.size()) {
555 uint8_t pixel_value = tile8_bitmap_data[src_pos];
556 // Pixel values already include palette information from +0x88
557 // offset Just copy directly (color index 0 = transparent)
558 bg1_data[dest_pos] = pixel_value;
559 }
560 }
561 }
562 }
563 }
564
565 // Update surface with rendered pixel data
567
568 // Queue texture update
571
572 return absl::OkStatus();
573}
574
576 // BG2 layer is 32x32 tiles (256x256 pixels)
577 auto& bg2_data = tiles_bg2_bitmap_.mutable_data();
578 const auto& tile8_bitmap_data = tiles8_bitmap_.vector();
579
580 // Render each tile in the 32x32 tilemap
581 for (int tile_y = 0; tile_y < 32; tile_y++) {
582 for (int tile_x = 0; tile_x < 32; tile_x++) {
583 int tilemap_index = tile_y * 32 + tile_x;
584 uint16_t tile_word = tiles_bg2_buffer_[tilemap_index];
585
586 // Extract tile info from SNES tile word (vhopppcc cccccccc format)
587 int tile_id = tile_word & 0x3FF; // Bits 0-9: tile ID
588 int palette = (tile_word >> 10) & 0x07; // Bits 10-12: palette
589 bool h_flip = (tile_word & 0x4000) != 0; // Bit 14: horizontal flip
590 bool v_flip = (tile_word & 0x8000) != 0; // Bit 15: vertical flip
591
592 // Calculate source position in tiles8_bitmap_
593 // tiles8_bitmap_ is 128 pixels wide, 512 pixels tall (16 sheets × 32
594 // pixels) Each sheet has 256 tiles (16×16 tiles, 128×32 pixels, 0x1000
595 // bytes)
596 int sheet_index = tile_id / 256; // Which sheet (0-15)
597 int tile_in_sheet = tile_id % 256; // Tile within sheet (0-255)
598 int src_tile_x = (tile_in_sheet % 16) * 8;
599 int src_tile_y = (sheet_index * 32) + ((tile_in_sheet / 16) * 8);
600
601 // Copy 8x8 tile pixels from tile8 bitmap to BG2 bitmap
602 for (int py = 0; py < 8; py++) {
603 for (int px = 0; px < 8; px++) {
604 // Apply flipping
605 int src_px = h_flip ? (7 - px) : px;
606 int src_py = v_flip ? (7 - py) : py;
607
608 // Calculate source and destination positions
609 int src_x = src_tile_x + src_px;
610 int src_y = src_tile_y + src_py;
611 int src_pos =
612 src_y * 128 + src_x; // tiles8_bitmap_ is 128 pixels wide
613
614 int dest_x = tile_x * 8 + px;
615 int dest_y = tile_y * 8 + py;
616 int dest_pos = dest_y * 256 + dest_x; // BG2 is 256 pixels wide
617
618 // Copy pixel with palette application
619 // Graphics are 3BPP in ROM, converted to 8BPP indexed with +0x88
620 // offset
621 if (src_pos < tile8_bitmap_data.size() &&
622 dest_pos < bg2_data.size()) {
623 uint8_t pixel_value = tile8_bitmap_data[src_pos];
624 // Pixel values already include palette information from +0x88
625 // offset Just copy directly (color index 0 = transparent)
626 bg2_data[dest_pos] = pixel_value;
627 }
628 }
629 }
630 }
631 }
632
633 // Update surface with rendered pixel data
635
636 // Queue texture update
639
640 return absl::OkStatus();
641}
642
643absl::Status TitleScreen::Save(Rom* rom) {
644 if (!rom || !rom->is_loaded()) {
645 return absl::InvalidArgumentError("ROM is not loaded");
646 }
647
648 // Title screen uses compressed tilemap format
649 // We'll write the data back in the same compressed format
650 std::vector<uint8_t> compressed_data;
651
652 // Helper to write word (little endian)
653 auto WriteWord = [&compressed_data](uint16_t value) {
654 compressed_data.push_back(value & 0xFF);
655 compressed_data.push_back((value >> 8) & 0xFF);
656 };
657
658 // Compress BG2 layer (dest < 0x1000)
659 uint16_t bg2_dest = 0x0000;
660 for (int i = 0; i < 1024; i++) {
661 if (i == 0 || tiles_bg2_buffer_[i] != tiles_bg2_buffer_[i - 1]) {
662 // Start a new run
663 WriteWord(bg2_dest + i); // Destination address
664
665 // Count consecutive identical tiles
666 int run_length = 1;
667 uint16_t tile_value = tiles_bg2_buffer_[i];
668 while (i + run_length < 1024 &&
669 tiles_bg2_buffer_[i + run_length] == tile_value) {
670 run_length++;
671 }
672
673 // Write length/flags (bit 14 = fixsource if run > 1)
674 uint16_t length_flags = (run_length - 1) * 2; // Length in bytes
675 if (run_length > 1) {
676 length_flags |= 0x4000; // fixsource flag
677 }
678 WriteWord(length_flags);
679
680 // Write tile data
681 WriteWord(tile_value);
682
683 i += run_length - 1; // Skip already processed tiles
684 }
685 }
686
687 // Compress BG1 layer (dest >= 0x1000)
688 uint16_t bg1_dest = 0x1000;
689 for (int i = 0; i < 1024; i++) {
690 if (i == 0 || tiles_bg1_buffer_[i] != tiles_bg1_buffer_[i - 1]) {
691 // Start a new run
692 WriteWord(bg1_dest + i); // Destination address
693
694 // Count consecutive identical tiles
695 int run_length = 1;
696 uint16_t tile_value = tiles_bg1_buffer_[i];
697 while (i + run_length < 1024 &&
698 tiles_bg1_buffer_[i + run_length] == tile_value) {
699 run_length++;
700 }
701
702 // Write length/flags (bit 14 = fixsource if run > 1)
703 uint16_t length_flags = (run_length - 1) * 2; // Length in bytes
704 if (run_length > 1) {
705 length_flags |= 0x4000; // fixsource flag
706 }
707 WriteWord(length_flags);
708
709 // Write tile data
710 WriteWord(tile_value);
711
712 i += run_length - 1; // Skip already processed tiles
713 }
714 }
715
716 // Write terminator byte
717 compressed_data.push_back(0x80);
718
719 // Calculate ROM address to write to
720 ASSIGN_OR_RETURN(uint8_t byte0, rom->ReadByte(0x137A + 3));
721 ASSIGN_OR_RETURN(uint8_t byte1, rom->ReadByte(0x1383 + 3));
722 ASSIGN_OR_RETURN(uint8_t byte2, rom->ReadByte(0x138C + 3));
723
724 int pos = (byte2 << 16) + (byte1 << 8) + byte0;
725 int write_pos = SnesToPc(pos);
726
727 // Write compressed data to ROM
728 for (size_t i = 0; i < compressed_data.size(); i++) {
729 RETURN_IF_ERROR(rom->WriteByte(write_pos + i, compressed_data[i]));
730 }
731
732 return absl::OkStatus();
733}
734
735absl::Status TitleScreen::RenderCompositeLayer(bool show_bg1, bool show_bg2) {
736 auto& composite_data = title_composite_bitmap_.mutable_data();
737 const auto& bg1_data = tiles_bg1_bitmap_.vector();
738 const auto& bg2_data = tiles_bg2_bitmap_.vector();
739
740 // Clear to transparent (color index 0)
741 std::fill(composite_data.begin(), composite_data.end(), 0);
742
743 // Layer BG2 first (if visible) - background layer
744 if (show_bg2) {
745 for (int i = 0; i < 256 * 256; i++) {
746 composite_data[i] = bg2_data[i];
747 }
748 }
749
750 // Layer BG1 on top (if visible), respecting transparency
751 if (show_bg1) {
752 for (int i = 0; i < 256 * 256; i++) {
753 uint8_t pixel = bg1_data[i];
754 // Check if color 0 in the sub-palette (transparent)
755 // Pixel format is (palette<<3) | color, so color is bits 0-2
756 if ((pixel & 0x07) != 0) {
757 composite_data[i] = pixel;
758 }
759 }
760 }
761
762 // Copy pixel data to SDL surface
764
765 // Queue texture update
768
769 return absl::OkStatus();
770}
771
772} // namespace zelda3
773} // 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< uint16_t, 0x1000 > tiles_bg2_buffer_
gfx::OamTile oam_data_[10]
gfx::SnesPalette palette_
gfx::Bitmap title_composite_bitmap_
std::array< uint16_t, 0x1000 > tiles_bg1_buffer_
absl::Status Create(Rom *rom, GameData *game_data=nullptr)
Initialize and load title screen data from ROM.
absl::Status RenderCompositeLayer(bool show_bg1, bool show_bg2)
Render composite layer with BG1 on top of BG2 with transparency.
absl::Status BuildTileset(Rom *rom)
Build the tile16 blockset from ROM graphics.
absl::Status Save(Rom *rom)
absl::Status RenderBG2Layer()
Render BG2 tilemap into bitmap pixels Converts tile IDs from tiles_bg2_buffer_ into pixel data.
absl::Status RenderBG1Layer()
Render BG1 tilemap into bitmap pixels Converts tile IDs from tiles_bg1_buffer_ into pixel data.
absl::Status LoadTitleScreen(Rom *rom)
Load title screen tilemap data from ROM.
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
constexpr int kGfxGroupsPointer
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Definition tilemap.h:123
Pair map_size
Size of tilemap in tiles.
Definition tilemap.h:124
std::vector< std::array< gfx::TileInfo, 4 > > tile_info
Tile metadata (4 tiles per 16x16)
Definition tilemap.h:122
gfx::PaletteGroupMap palette_groups
Definition game_data.h:92
std::vector< uint8_t > graphics_buffer
Definition game_data.h:84