yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
rom.cc
Go to the documentation of this file.
1#include "rom.h"
2
3#include <algorithm>
4#include <array>
5#include <chrono>
6#include <cstddef>
7#include <cstdint>
8#include <cstring>
9#include <ctime>
10#include <filesystem>
11#include <fstream>
12#include <iostream>
13#include <new>
14#include <string>
15#include <vector>
16
17#include "absl/status/status.h"
18#include "absl/status/statusor.h"
19#include "absl/strings/str_cat.h"
20#include "absl/strings/str_format.h"
21#include "absl/strings/string_view.h"
22#include "core/features.h"
27#include "app/snes.h"
28#include "app/gfx/core/bitmap.h"
29#include "util/log.h"
30#include "util/hex.h"
31#include "util/macro.h"
32#include "zelda.h"
33
34namespace yaze {
35constexpr int Uncompressed3BPPSize = 0x0600;
36
37namespace {
38constexpr size_t kBaseRomSize = 1048576; // 1MB
39constexpr size_t kHeaderSize = 0x200; // 512 bytes
40
41void MaybeStripSmcHeader(std::vector<uint8_t> &rom_data, unsigned long &size) {
42 if (size % kBaseRomSize == kHeaderSize && size >= kHeaderSize) {
43 rom_data.erase(rom_data.begin(), rom_data.begin() + kHeaderSize);
44 size -= kHeaderSize;
45 }
46}
47
48} // namespace
49
51
53 RomLoadOptions options;
54 options.populate_palettes = false;
55 options.populate_gfx_groups = false;
56 options.expand_to_full_image = false;
57 options.load_resource_labels = false;
58 return options;
59}
60
62 RomLoadOptions options;
63 options.load_zelda3_content = false;
64 options.strip_header = false;
65 options.populate_metadata = false;
66 options.populate_palettes = false;
67 options.populate_gfx_groups = false;
68 options.expand_to_full_image = false;
69 options.load_resource_labels = false;
70 return options;
71}
72
73uint32_t GetGraphicsAddress(const uint8_t *data, uint8_t addr, uint32_t ptr1,
74 uint32_t ptr2, uint32_t ptr3) {
75 return SnesToPc(AddressFromBytes(data[ptr1 + addr], data[ptr2 + addr],
76 data[ptr3 + addr]));
77}
78
79absl::StatusOr<std::vector<uint8_t>> Load2BppGraphics(const Rom &rom) {
80 std::vector<uint8_t> sheet;
81 const uint8_t sheets[] = {0x71, 0x72, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE};
82 for (const auto &sheet_id : sheets) {
83 auto offset = GetGraphicsAddress(rom.data(), sheet_id,
87 ASSIGN_OR_RETURN(auto decomp_sheet,
88 gfx::lc_lz2::DecompressV2(rom.data(), offset));
89 auto converted_sheet = gfx::SnesTo8bppSheet(decomp_sheet, 2);
90 for (const auto &each_pixel : converted_sheet) {
91 sheet.push_back(each_pixel);
92 }
93 }
94 return sheet;
95}
96
97absl::StatusOr<std::array<gfx::Bitmap, kNumLinkSheets>> LoadLinkGraphics(
98 const Rom &rom) {
99 const uint32_t kLinkGfxOffset = 0x80000; // $10:8000
100 const uint16_t kLinkGfxLength = 0x800; // 0x4000 or 0x7000?
101 std::array<gfx::Bitmap, kNumLinkSheets> link_graphics;
102 for (uint32_t i = 0; i < kNumLinkSheets; i++) {
104 auto link_sheet_data,
105 rom.ReadByteVector(/*offset=*/kLinkGfxOffset + (i * kLinkGfxLength),
106 /*length=*/kLinkGfxLength));
107 auto link_sheet_8bpp = gfx::SnesTo8bppSheet(link_sheet_data, /*bpp=*/4);
108 link_graphics[i].Create(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
109 gfx::kTilesheetDepth, link_sheet_8bpp);
110 link_graphics[i].SetPalette(rom.palette_group().armors[0]);
111 // Texture creation is deferred until GraphicsEditor is opened and renderer is available.
112 // The graphics will be queued for texturing when needed via Arena's deferred system.
113 }
114 return link_graphics;
115}
116
117absl::StatusOr<gfx::Bitmap> LoadFontGraphics(const Rom &rom) {
118 std::vector<uint8_t> data(0x2000);
119 for (int i = 0; i < 0x2000; i++) {
120 data[i] = rom.data()[0x70000 + i];
121 }
122
123 std::vector<uint8_t> new_data(0x4000);
124 std::vector<uint8_t> mask = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
125 int sheet_position = 0;
126
127 // 8x8 tile
128 for (int s = 0; s < 4; s++) { // Per Sheet
129 for (int j = 0; j < 4; j++) { // Per Tile Line Y
130 for (int i = 0; i < 16; i++) { // Per Tile Line X
131 for (int y = 0; y < 8; y++) { // Per Pixel Line
132 uint8_t line_bits0 =
133 data[(y * 2) + (i * 16) + (j * 256) + sheet_position];
134 uint8_t line_bits1 =
135 data[(y * 2) + (i * 16) + (j * 256) + 1 + sheet_position];
136
137 for (int x = 0; x < 4; x++) { // Per Pixel X
138 uint8_t pixdata = 0;
139 uint8_t pixdata2 = 0;
140
141 if ((line_bits0 & mask[(x * 2)]) == mask[(x * 2)]) {
142 pixdata += 1;
143 }
144 if ((line_bits1 & mask[(x * 2)]) == mask[(x * 2)]) {
145 pixdata += 2;
146 }
147
148 if ((line_bits0 & mask[(x * 2) + 1]) == mask[(x * 2) + 1]) {
149 pixdata2 += 1;
150 }
151 if ((line_bits1 & mask[(x * 2) + 1]) == mask[(x * 2) + 1]) {
152 pixdata2 += 2;
153 }
154
155 new_data[(y * 64) + (x) + (i * 4) + (j * 512) + (s * 2048)] =
156 (uint8_t)((pixdata << 4) | pixdata2);
157 }
158 }
159 }
160 }
161
162 sheet_position += 0x400;
163 }
164
165 std::vector<uint8_t> fontgfx16_data(0x4000);
166 for (int i = 0; i < 0x4000; i++) {
167 fontgfx16_data[i] = new_data[i];
168 }
169
170 gfx::Bitmap font_gfx;
171 font_gfx.Create(128, 128, 64, fontgfx16_data);
172 return font_gfx;
173}
174
175absl::StatusOr<std::array<gfx::Bitmap, kNumGfxSheets>> LoadAllGraphicsData(
176 Rom &rom, bool defer_render) {
177 std::array<gfx::Bitmap, kNumGfxSheets> graphics_sheets;
178 std::vector<uint8_t> sheet;
179 bool bpp3 = false;
180 // CRITICAL: Clear the graphics buffer before loading to prevent corruption!
181 // Without this, multiple ROM loads would accumulate corrupted data.
182 rom.mutable_graphics_buffer()->clear();
183 LOG_DEBUG("Graphics", "Cleared graphics buffer, loading %d sheets", kNumGfxSheets);
184
185 for (uint32_t i = 0; i < kNumGfxSheets; i++) {
186 if (i >= 115 && i <= 126) { // uncompressed sheets
187 sheet.resize(Uncompressed3BPPSize);
188 auto offset = GetGraphicsAddress(
192 std::copy(rom.data() + offset, rom.data() + offset + Uncompressed3BPPSize,
193 sheet.begin());
194 bpp3 = true;
195 } else if (i == 113 || i == 114 || i >= 218) {
196 bpp3 = false;
197 } else {
198 auto offset = GetGraphicsAddress(
202 ASSIGN_OR_RETURN(sheet, gfx::lc_lz2::DecompressV2(rom.data(), offset));
203 bpp3 = true;
204 }
205
206 if (bpp3) {
207 auto converted_sheet = gfx::SnesTo8bppSheet(sheet, 3);
208
209 graphics_sheets[i].Create(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
210 gfx::kTilesheetDepth, converted_sheet);
211
212 // Apply default palette based on sheet index to prevent white sheets
213 // This ensures graphics are visible immediately after loading
214 if (!rom.palette_group().empty()) {
215 gfx::SnesPalette default_palette;
216
217 if (i < 113) {
218 // Overworld/Dungeon graphics - use dungeon main palette
219 auto palette_group = rom.palette_group().dungeon_main;
220 if (palette_group.size() > 0) {
221 default_palette = palette_group[0];
222 }
223 } else if (i < 128) {
224 // Sprite graphics - use sprite palettes
225 auto palette_group = rom.palette_group().sprites_aux1;
226 if (palette_group.size() > 0) {
227 default_palette = palette_group[0];
228 }
229 } else {
230 // Auxiliary graphics - use HUD/menu palettes
231 auto palette_group = rom.palette_group().hud;
232 if (palette_group.size() > 0) {
233 default_palette = palette_group.palette(0);
234 }
235 }
236
237 // Apply palette if we have one
238 if (!default_palette.empty()) {
239 graphics_sheets[i].SetPalette(default_palette);
240 }
241 }
242
243 for (int j = 0; j < graphics_sheets[i].size(); ++j) {
244 rom.mutable_graphics_buffer()->push_back(graphics_sheets[i].at(j));
245 }
246
247 } else {
248 for (int j = 0; j < graphics_sheets[0].size(); ++j) {
249 rom.mutable_graphics_buffer()->push_back(0xFF);
250 }
251 }
252 }
253 return graphics_sheets;
254}
255
257 Rom &rom, std::array<gfx::Bitmap, kNumGfxSheets> &gfx_sheets) {
258 for (int i = 0; i < kNumGfxSheets; i++) {
259 if (gfx_sheets[i].is_active()) {
260 int to_bpp = 3;
261 std::vector<uint8_t> final_data;
262 bool compressed = true;
263 if (i >= 115 && i <= 126) {
264 compressed = false;
265 } else if (i == 113 || i == 114 || i >= 218) {
266 to_bpp = 2;
267 continue;
268 }
269
270 std::cout << "Sheet ID " << i << " BPP: " << to_bpp << std::endl;
271 auto sheet_data = gfx_sheets[i].vector();
272 std::cout << "Sheet data size: " << sheet_data.size() << std::endl;
273 final_data = gfx::Bpp8SnesToIndexed(sheet_data, 8);
274 int size = 0;
275 if (compressed) {
276 auto compressed_data = gfx::HyruleMagicCompress(
277 final_data.data(), final_data.size(), &size, 1);
278 for (int j = 0; j < size; j++) {
279 sheet_data[j] = compressed_data[j];
280 }
281 }
282 auto offset = GetGraphicsAddress(
286 std::copy(final_data.begin(), final_data.end(), rom.begin() + offset);
287 }
288 }
289 return absl::OkStatus();
290}
291
292absl::Status Rom::LoadFromFile(const std::string &filename, bool z3_load) {
293 return LoadFromFile(
296}
297
298absl::Status Rom::LoadFromFile(const std::string &filename,
299 const RomLoadOptions &options) {
300 if (filename.empty()) {
301 return absl::InvalidArgumentError(
302 "Could not load ROM: parameter `filename` is empty.");
303 }
304
305 // Validate file exists before proceeding
306 if (!std::filesystem::exists(filename)) {
307 return absl::NotFoundError(
308 absl::StrCat("ROM file does not exist: ", filename));
309 }
310
311 filename_ = std::filesystem::absolute(filename).string();
312 short_name_ = filename_.substr(filename_.find_last_of("/\\") + 1);
313
314 std::ifstream file(filename_, std::ios::binary);
315 if (!file.is_open()) {
316 return absl::NotFoundError(
317 absl::StrCat("Could not open ROM file: ", filename_));
318 }
319
320 // Get file size and validate
321 try {
322 size_ = std::filesystem::file_size(filename_);
323
324 // Validate ROM size (minimum 32KB, maximum 8MB for expanded ROMs)
325 if (size_ < 32768) {
326 return absl::InvalidArgumentError(
327 absl::StrFormat("ROM file too small (%zu bytes), minimum is 32KB", size_));
328 }
329 if (size_ > 8 * 1024 * 1024) {
330 return absl::InvalidArgumentError(
331 absl::StrFormat("ROM file too large (%zu bytes), maximum is 8MB", size_));
332 }
333 } catch (const std::filesystem::filesystem_error &e) {
334 // Try to get the file size from the open file stream
335 file.seekg(0, std::ios::end);
336 if (!file) {
337 return absl::InternalError(absl::StrCat(
338 "Could not get file size: ", filename_, " - ", e.what()));
339 }
340 size_ = file.tellg();
341
342 // Validate size from stream
343 if (size_ < 32768 || size_ > 8 * 1024 * 1024) {
344 return absl::InvalidArgumentError(
345 absl::StrFormat("Invalid ROM size: %zu bytes", size_));
346 }
347 }
348
349 // Allocate and read ROM data
350 try {
351 rom_data_.resize(size_);
352 file.seekg(0, std::ios::beg);
353 file.read(reinterpret_cast<char *>(rom_data_.data()), size_);
354
355 if (!file) {
356 return absl::InternalError(
357 absl::StrFormat("Failed to read ROM data, read %zu of %zu bytes",
358 file.gcount(), size_));
359 }
360 } catch (const std::bad_alloc& e) {
361 return absl::ResourceExhaustedError(
362 absl::StrFormat("Failed to allocate memory for ROM (%zu bytes)", size_));
363 }
364
365 file.close();
366
367 if (!options.load_zelda3_content) {
368 if (options.strip_header) {
369 MaybeStripSmcHeader(rom_data_, size_);
370 }
371 size_ = rom_data_.size();
372 } else {
373 RETURN_IF_ERROR(LoadZelda3(options));
374 }
375
376 if (options.load_resource_labels) {
378 absl::StrFormat("%s.labels", filename));
379 }
380
381 return absl::OkStatus();
382}
383
384absl::Status Rom::LoadFromData(const std::vector<uint8_t> &data, bool z3_load) {
385 return LoadFromData(
388}
389
390absl::Status Rom::LoadFromData(const std::vector<uint8_t> &data,
391 const RomLoadOptions &options) {
392 if (data.empty()) {
393 return absl::InvalidArgumentError(
394 "Could not load ROM: parameter `data` is empty.");
395 }
396 rom_data_ = data;
397 size_ = data.size();
398
399 if (!options.load_zelda3_content) {
400 if (options.strip_header) {
401 MaybeStripSmcHeader(rom_data_, size_);
402 }
403 size_ = rom_data_.size();
404 } else {
405 RETURN_IF_ERROR(LoadZelda3(options));
406 }
407
408 return absl::OkStatus();
409}
410
411absl::Status Rom::LoadZelda3() {
413}
414
415absl::Status Rom::LoadZelda3(const RomLoadOptions &options) {
416 if (rom_data_.empty()) {
417 return absl::FailedPreconditionError("ROM data is empty");
418 }
419
420 if (options.strip_header) {
421 MaybeStripSmcHeader(rom_data_, size_);
422 }
423
424 size_ = rom_data_.size();
425
426 constexpr uint32_t kTitleStringOffset = 0x7FC0;
427 constexpr uint32_t kTitleStringLength = 20;
428 constexpr uint32_t kTitleStringOffsetWithHeader = 0x81C0;
429
430 if (options.populate_metadata) {
431 uint32_t offset = options.strip_header ? kTitleStringOffset
433 if (offset + kTitleStringLength > rom_data_.size()) {
434 return absl::OutOfRangeError(
435 "ROM image is too small to contain title metadata.");
436 }
437 title_.assign(rom_data_.begin() + offset,
438 rom_data_.begin() + offset + kTitleStringLength);
439 if (rom_data_[offset + 0x19] == 0) {
440 version_ = zelda3_version::JP;
441 } else {
442 version_ = zelda3_version::US;
443 }
444 }
445
446 if (options.populate_palettes) {
449 } else {
451 }
452
453 if (options.populate_gfx_groups) {
455 } else {
458 spriteset_ids = {};
459 paletteset_ids = {};
460 }
461
462 if (options.expand_to_full_image) {
463 if (rom_data_.size() < kBaseRomSize * 2) {
464 rom_data_.resize(kBaseRomSize * 2);
465 }
466 }
467
468 size_ = rom_data_.size();
469
470 return absl::OkStatus();
471}
472
473absl::Status Rom::LoadGfxGroups() {
476
477 for (uint32_t i = 0; i < kNumMainBlocksets; i++) {
478 for (int j = 0; j < 8; j++) {
480 }
481 }
482
483 for (uint32_t i = 0; i < kNumRoomBlocksets; i++) {
484 for (int j = 0; j < 4; j++) {
486 }
487 }
488
489 for (uint32_t i = 0; i < kNumSpritesets; i++) {
490 for (int j = 0; j < 4; j++) {
491 spriteset_ids[i][j] =
493 }
494 }
495
496 for (uint32_t i = 0; i < kNumPalettesets; i++) {
497 for (int j = 0; j < 4; j++) {
498 paletteset_ids[i][j] =
500 }
501 }
502
503 return absl::OkStatus();
504}
505
506absl::Status Rom::SaveGfxGroups() {
509
510 for (uint32_t i = 0; i < kNumMainBlocksets; i++) {
511 for (int j = 0; j < 8; j++) {
513 }
514 }
515
516 for (uint32_t i = 0; i < kNumRoomBlocksets; i++) {
517 for (int j = 0; j < 4; j++) {
519 }
520 }
521
522 for (uint32_t i = 0; i < kNumSpritesets; i++) {
523 for (int j = 0; j < 4; j++) {
525 spriteset_ids[i][j];
526 }
527 }
528
529 for (uint32_t i = 0; i < kNumPalettesets; i++) {
530 for (int j = 0; j < 4; j++) {
532 paletteset_ids[i][j];
533 }
534 }
535
536 return absl::OkStatus();
537}
538
539absl::Status Rom::SaveToFile(const SaveSettings &settings) {
540 absl::Status non_firing_status;
541 if (rom_data_.empty()) {
542 return absl::InternalError("ROM data is empty.");
543 }
544
545 std::string filename = settings.filename;
546 auto backup = settings.backup;
547 auto save_new = settings.save_new;
548
549 // Check if filename is empty
550 if (filename == "") {
552 }
553
554 // Check if backup is enabled
555 if (backup) {
556 // Create a backup file with timestamp in its name
557 auto now = std::chrono::system_clock::now();
558 auto now_c = std::chrono::system_clock::to_time_t(now);
559 std::string backup_filename =
560 absl::StrCat(filename, "_backup_", std::ctime(&now_c));
561
562 // Remove newline character from ctime()
563 backup_filename.erase(
564 std::remove(backup_filename.begin(), backup_filename.end(), '\n'),
565 backup_filename.end());
566
567 // Replace spaces with underscores
568 std::replace(backup_filename.begin(), backup_filename.end(), ' ', '_');
569
570 // Now, copy the original file to the backup file
571 try {
572 std::filesystem::copy(filename_, backup_filename,
573 std::filesystem::copy_options::overwrite_existing);
574 } catch (const std::filesystem::filesystem_error &e) {
575 non_firing_status = absl::InternalError(absl::StrCat(
576 "Could not create backup file: ", backup_filename, " - ", e.what()));
577 }
578 }
579
580 // Run the other save functions
581 if (settings.z3_save) {
582 if (core::FeatureFlags::get().kSaveAllPalettes)
584 if (core::FeatureFlags::get().kSaveGfxGroups)
586 }
587
588 if (save_new) {
589 // Create a file of the same name and append the date between the filename
590 // and file extension
591 auto now = std::chrono::system_clock::now();
592 auto now_c = std::chrono::system_clock::to_time_t(now);
593 auto filename_no_ext = filename.substr(0, filename.find_last_of("."));
594 std::cout << filename_no_ext << std::endl;
595 filename = absl::StrCat(filename_no_ext, "_", std::ctime(&now_c));
596 // Remove spaces from new_filename and replace with _
597 filename.erase(std::remove(filename.begin(), filename.end(), ' '),
598 filename.end());
599 // Remove newline character from ctime()
600 filename.erase(std::remove(filename.begin(), filename.end(), '\n'),
601 filename.end());
602 // Add the file extension back to the new_filename
603 filename = filename + ".sfc";
604 std::cout << filename << std::endl;
605 }
606
607 // Open the file for writing and truncate existing content
608 std::ofstream file(filename.data(), std::ios::binary | std::ios::trunc);
609 if (!file) {
610 return absl::InternalError(
611 absl::StrCat("Could not open ROM file for writing: ", filename));
612 }
613
614 // Save the data to the file
615 try {
616 file.write(
617 static_cast<const char *>(static_cast<const void *>(rom_data_.data())),
618 rom_data_.size());
619 } catch (const std::ofstream::failure &e) {
620 return absl::InternalError(absl::StrCat(
621 "Error while writing to ROM file: ", filename, " - ", e.what()));
622 }
623
624 // Check for write errors
625 if (!file) {
626 return absl::InternalError(
627 absl::StrCat("Error while writing to ROM file: ", filename));
628 }
629
630 if (non_firing_status.ok()) dirty_ = false;
631 return non_firing_status.ok() ? absl::OkStatus() : non_firing_status;
632}
633
634absl::Status Rom::SavePalette(int index, const std::string &group_name,
635 gfx::SnesPalette &palette) {
636 for (size_t j = 0; j < palette.size(); ++j) {
637 gfx::SnesColor color = palette[j];
638 // If the color is modified, save the color to the ROM
639 if (color.is_modified()) {
641 WriteColor(gfx::GetPaletteAddress(group_name, index, j), color));
642 color.set_modified(false); // Reset the modified flag after saving
643 }
644 }
645 return absl::OkStatus();
646}
647
648absl::Status Rom::SaveAllPalettes() {
650 palette_groups_.for_each([&](gfx::PaletteGroup &group) -> absl::Status {
651 for (size_t i = 0; i < group.size(); ++i) {
652 RETURN_IF_ERROR(
653 SavePalette(i, group.name(), *group.mutable_palette(i)));
654 }
655 return absl::OkStatus();
656 }));
657
658 return absl::OkStatus();
659}
660
661absl::StatusOr<uint8_t> Rom::ReadByte(int offset) {
662 if (offset >= static_cast<int>(rom_data_.size())) {
663 return absl::FailedPreconditionError("Offset out of range");
664 }
665 return rom_data_[offset];
666}
667
668absl::StatusOr<uint16_t> Rom::ReadWord(int offset) {
669 if (offset + 1 >= static_cast<int>(rom_data_.size())) {
670 return absl::FailedPreconditionError("Offset out of range");
671 }
672 auto result = (uint16_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8));
673 return result;
674}
675
676absl::StatusOr<uint32_t> Rom::ReadLong(int offset) {
677 if (offset + 2 >= static_cast<int>(rom_data_.size())) {
678 return absl::OutOfRangeError("Offset out of range");
679 }
680 auto result = (uint32_t)(rom_data_[offset] | (rom_data_[offset + 1] << 8) |
681 (rom_data_[offset + 2] << 16));
682 return result;
683}
684
685absl::StatusOr<std::vector<uint8_t>> Rom::ReadByteVector(
686 uint32_t offset, uint32_t length) const {
687 if (offset + length > static_cast<uint32_t>(rom_data_.size())) {
688 return absl::OutOfRangeError("Offset and length out of range");
689 }
690 std::vector<uint8_t> result;
691 for (uint32_t i = offset; i < offset + length; i++) {
692 result.push_back(rom_data_[i]);
693 }
694 return result;
695}
696
697absl::StatusOr<gfx::Tile16> Rom::ReadTile16(uint32_t tile16_id) {
698 // Skip 8 bytes per tile.
699 auto tpos = kTile16Ptr + (tile16_id * 0x08);
700 gfx::Tile16 tile16 = {};
701 ASSIGN_OR_RETURN(auto new_tile0, ReadWord(tpos));
702 tile16.tile0_ = gfx::WordToTileInfo(new_tile0);
703 tpos += 2;
704 ASSIGN_OR_RETURN(auto new_tile1, ReadWord(tpos));
705 tile16.tile1_ = gfx::WordToTileInfo(new_tile1);
706 tpos += 2;
707 ASSIGN_OR_RETURN(auto new_tile2, ReadWord(tpos));
708 tile16.tile2_ = gfx::WordToTileInfo(new_tile2);
709 tpos += 2;
710 ASSIGN_OR_RETURN(auto new_tile3, ReadWord(tpos));
711 tile16.tile3_ = gfx::WordToTileInfo(new_tile3);
712 return tile16;
713}
714
715absl::Status Rom::WriteTile16(int tile16_id, const gfx::Tile16 &tile) {
716 // Skip 8 bytes per tile.
717 auto tpos = kTile16Ptr + (tile16_id * 0x08);
718 RETURN_IF_ERROR(WriteShort(tpos, gfx::TileInfoToWord(tile.tile0_)));
719 tpos += 2;
720 RETURN_IF_ERROR(WriteShort(tpos, gfx::TileInfoToWord(tile.tile1_)));
721 tpos += 2;
722 RETURN_IF_ERROR(WriteShort(tpos, gfx::TileInfoToWord(tile.tile2_)));
723 tpos += 2;
724 RETURN_IF_ERROR(WriteShort(tpos, gfx::TileInfoToWord(tile.tile3_)));
725 return absl::OkStatus();
726}
727
728absl::Status Rom::WriteByte(int addr, uint8_t value) {
729 if (addr >= static_cast<int>(rom_data_.size())) {
730 return absl::OutOfRangeError(absl::StrFormat(
731 "Attempt to write byte %#02x value failed, address %d out of range",
732 value, addr));
733 }
734 rom_data_[addr] = value;
735 LOG_DEBUG("Rom", "WriteByte: %#06X: %s", addr, util::HexByte(value).data());
736 dirty_ = true;
737 return absl::OkStatus();
738}
739
740absl::Status Rom::WriteWord(int addr, uint16_t value) {
741 if (addr + 1 >= static_cast<int>(rom_data_.size())) {
742 return absl::OutOfRangeError(absl::StrFormat(
743 "Attempt to write word %#04x value failed, address %d out of range",
744 value, addr));
745 }
746 rom_data_[addr] = (uint8_t)(value & 0xFF);
747 rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
748 LOG_DEBUG("Rom", "WriteWord: %#06X: %s", addr, util::HexWord(value).data());
749 dirty_ = true;
750 return absl::OkStatus();
751}
752
753absl::Status Rom::WriteShort(int addr, uint16_t value) {
754 if (addr + 1 >= static_cast<int>(rom_data_.size())) {
755 return absl::OutOfRangeError(absl::StrFormat(
756 "Attempt to write short %#04x value failed, address %d out of range",
757 value, addr));
758 }
759 rom_data_[addr] = (uint8_t)(value & 0xFF);
760 rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
761 LOG_DEBUG("Rom", "WriteShort: %#06X: %s", addr, util::HexWord(value).data());
762 dirty_ = true;
763 return absl::OkStatus();
764}
765
766absl::Status Rom::WriteLong(uint32_t addr, uint32_t value) {
767 if (addr + 2 >= static_cast<uint32_t>(rom_data_.size())) {
768 return absl::OutOfRangeError(absl::StrFormat(
769 "Attempt to write long %#06x value failed, address %d out of range",
770 value, addr));
771 }
772 rom_data_[addr] = (uint8_t)(value & 0xFF);
773 rom_data_[addr + 1] = (uint8_t)((value >> 8) & 0xFF);
774 rom_data_[addr + 2] = (uint8_t)((value >> 16) & 0xFF);
775 LOG_DEBUG("Rom", "WriteLong: %#06X: %s", addr, util::HexLong(value).data());
776 dirty_ = true;
777 return absl::OkStatus();
778}
779
780absl::Status Rom::WriteVector(int addr, std::vector<uint8_t> data) {
781 if (addr + static_cast<int>(data.size()) >
782 static_cast<int>(rom_data_.size())) {
783 return absl::InvalidArgumentError(absl::StrFormat(
784 "Attempt to write vector value failed, address %d out of range", addr));
785 }
786 for (int i = 0; i < static_cast<int>(data.size()); i++) {
787 rom_data_[addr + i] = data[i];
788 }
789 LOG_DEBUG("Rom", "WriteVector: %#06X: %s", addr,
790 util::HexByte(data[0]).data());
791 dirty_ = true;
792 return absl::OkStatus();
793}
794
795absl::Status Rom::WriteColor(uint32_t address, const gfx::SnesColor &color) {
796 uint16_t bgr = ((color.snes() >> 10) & 0x1F) | ((color.snes() & 0x1F) << 10) |
797 (color.snes() & 0x7C00);
798
799 // Write the 16-bit color value to the ROM at the specified address
800 LOG_DEBUG("Rom", "WriteColor: %#06X: %s", address, util::HexWord(bgr).data());
801 auto st = WriteShort(address, bgr);
802 if (st.ok())
803 dirty_ = true;
804 return st;
805}
806
807} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:74
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
Definition rom.cc:685
zelda3_version version_
Definition rom.h:259
absl::Status LoadFromFile(const std::string &filename, bool z3_load=true)
Definition rom.cc:292
auto mutable_graphics_buffer()
Definition rom.h:215
auto begin()
Definition rom.h:208
std::vector< uint8_t > rom_data_
Definition rom.h:247
gfx::PaletteGroupMap palette_groups_
Definition rom.h:256
auto palette_group() const
Definition rom.h:216
absl::Status WriteColor(uint32_t address, const gfx::SnesColor &color)
Definition rom.cc:795
auto filename() const
Definition rom.h:211
absl::Status LoadFromData(const std::vector< uint8_t > &data, bool z3_load=true)
Definition rom.cc:384
auto vector() const
Definition rom.h:210
absl::Status LoadZelda3()
Definition rom.cc:411
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
Definition rom.h:230
std::array< std::array< uint8_t, 4 >, kNumPalettesets > paletteset_ids
Definition rom.h:231
absl::StatusOr< uint16_t > ReadWord(int offset)
Definition rom.cc:668
std::string title_
Definition rom.h:238
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:539
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
Definition rom.h:229
auto data() const
Definition rom.h:206
zelda3_version_pointers version_constants() const
Definition rom.h:224
absl::Status SaveGfxGroups()
Definition rom.cc:506
absl::Status LoadGfxGroups()
Definition rom.cc:473
bool dirty_
Definition rom.h:262
std::string filename_
Definition rom.h:241
unsigned long size_
Definition rom.h:235
absl::Status SavePalette(int index, const std::string &group_name, gfx::SnesPalette &palette)
Definition rom.cc:634
project::ResourceLabelManager resource_label_manager_
Definition rom.h:253
std::string short_name_
Definition rom.h:244
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
Definition rom.h:228
absl::Status SaveAllPalettes()
Definition rom.cc:648
static Flags & get()
Definition features.h:82
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:66
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
SNES Color container.
Definition snes_color.h:109
constexpr void set_modified(bool m)
Definition snes_color.h:197
constexpr bool is_modified() const
Definition snes_color.h:194
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:192
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
Tile composition of four 8x8 tiles.
Definition snes_tile.h:137
#define LOG_DEBUG(category, format,...)
Definition log.h:104
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
void MaybeStripSmcHeader(std::vector< uint8_t > &rom_data, unsigned long &size)
Definition rom.cc:41
constexpr size_t kHeaderSize
Definition rom.cc:39
constexpr size_t kBaseRomSize
Definition rom.cc:38
absl::StatusOr< std::vector< uint8_t > > DecompressV2(const uint8_t *data, int offset, int size, int mode)
Decompresses a buffer of data using the LC_LZ2 algorithm.
constexpr int kTilesheetHeight
Definition snes_tile.h:17
constexpr int kTilesheetWidth
Definition snes_tile.h:16
constexpr int kTilesheetDepth
Definition snes_tile.h:18
std::vector< uint8_t > Bpp8SnesToIndexed(std::vector< uint8_t > data, uint64_t bpp)
Definition snes_tile.cc:189
std::vector< uint8_t > SnesTo8bppSheet(std::span< uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:129
uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index, size_t color_index)
std::vector< uint8_t > HyruleMagicCompress(uint8_t const *const src, int const oldsize, int *const size, int const flag)
absl::Status LoadAllPalettes(const std::vector< uint8_t > &rom_data, PaletteGroupMap &groups)
Loads all the palettes for the game.
Main namespace for the application.
Definition controller.cc:20
constexpr uint32_t kGfxGroupsPointer
Definition rom.h:37
absl::StatusOr< std::vector< uint8_t > > Load2BppGraphics(const Rom &rom)
Loads 2bpp graphics from Rom data.
Definition rom.cc:79
absl::StatusOr< std::array< gfx::Bitmap, kNumLinkSheets > > LoadLinkGraphics(const Rom &rom)
Loads the players 4bpp graphics sheet from Rom data.
Definition rom.cc:97
constexpr int Uncompressed3BPPSize
Definition rom.cc:35
constexpr uint32_t kNumLinkSheets
Definition rom.h:32
constexpr uint32_t kEntranceGfxGroup
Definition rom.h:43
constexpr uint32_t kNumSpritesets
Definition rom.h:41
int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept
Definition snes.h:39
constexpr uint32_t kTile16Ptr
Definition rom.h:33
constexpr uint32_t kNumMainBlocksets
Definition rom.h:39
constexpr uint32_t kNumRoomBlocksets
Definition rom.h:40
absl::StatusOr< gfx::Bitmap > LoadFontGraphics(const Rom &rom)
Definition rom.cc:117
constexpr uint32_t kNumGfxSheets
Definition rom.h:31
absl::StatusOr< std::array< gfx::Bitmap, kNumGfxSheets > > LoadAllGraphicsData(Rom &rom, bool defer_render)
This function iterates over all graphics sheets in the Rom and loads them into memory....
Definition rom.cc:175
uint32_t GetGraphicsAddress(const uint8_t *data, uint8_t addr, uint32_t ptr1, uint32_t ptr2, uint32_t ptr3)
Definition rom.cc:73
constexpr uint32_t kNumPalettesets
Definition rom.h:42
absl::Status SaveAllGraphicsData(Rom &rom, std::array< gfx::Bitmap, kNumGfxSheets > &gfx_sheets)
Definition rom.cc:256
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
bool strip_header
Definition rom.h:48
static RomLoadOptions AppDefaults()
Definition rom.cc:50
bool load_zelda3_content
Definition rom.h:47
bool expand_to_full_image
Definition rom.h:52
bool populate_palettes
Definition rom.h:50
static RomLoadOptions CliDefaults()
Definition rom.cc:52
bool populate_metadata
Definition rom.h:49
static RomLoadOptions RawDataOnly()
Definition rom.cc:61
bool populate_gfx_groups
Definition rom.h:51
bool load_resource_labels
Definition rom.h:53
absl::Status for_each(Func &&func)
Represents a group of palettes.
bool LoadLabels(const std::string &filename)
Definition project.cc:770
uint32_t kOverworldGfxPtr1
Definition zelda.h:102
uint32_t kOverworldGfxPtr2
Definition zelda.h:103
uint32_t kSpriteBlocksetPointer
Definition zelda.h:109
uint32_t kOverworldGfxPtr3
Definition zelda.h:104
uint32_t kDungeonPalettesGroups
Definition zelda.h:110
The Legend of Zelda: A Link to the Past - Data Structures and Constants.