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