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