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