3#include "absl/strings/str_format.h"
26 return (bank << 16) | (high << 8) | low;
31 return (snes_addr & 0x7FFF) | ((snes_addr & 0x7F0000) >> 1);
41 const int palette_size =
static_cast<int>(palette.
size());
42 if (palette_size <= 0) {
46 const bool has_explicit_transparent =
47 palette_size >= 16 && (palette_size % 16 == 0);
49 slice.
offset = has_explicit_transparent ? 1 : 0;
50 slice.
length = has_explicit_transparent ? 15 : std::min(palette_size, 15);
57 slice.
length = std::min(palette_size, 15);
113 uint32_t ptr2, uint32_t ptr3,
size_t rom_size) {
114 if (ptr1 > UINT32_MAX - addr || ptr1 + addr >= rom_size ||
115 ptr2 > UINT32_MAX - addr || ptr2 + addr >= rom_size ||
116 ptr3 > UINT32_MAX - addr || ptr3 + addr >= rom_size) {
117 return static_cast<uint32_t
>(rom_size);
145 if (rom.
size() < 1048576 * 2) {
154 return absl::OkStatus();
163 for (
size_t i = 0; i < group.
size(); ++i) {
165 for (
size_t j = 0; j < palette->size(); ++j) {
168 RETURN_IF_ERROR(rom.WriteColor(
169 gfx::GetPaletteAddress(group.name(), i, j), color));
170 color.set_modified(false);
174 return absl::OkStatus();
183 return absl::OkStatus();
187 constexpr uint32_t kTitleStringOffset = 0x7FC0;
188 constexpr uint32_t kTitleStringLength = 20;
190 if (rom.
size() < kTitleStringOffset + kTitleStringLength) {
191 return absl::OutOfRangeError(
"ROM too small for metadata");
195 if (kTitleStringOffset + 0x19 < rom.
size()) {
197 uint8_t version_byte = rom.
data()[kTitleStringOffset + 0x19];
199 (version_byte == 0) ? zelda3_version::JP : zelda3_version::US;
202 auto title_bytes = rom.
ReadByteVector(kTitleStringOffset, kTitleStringLength);
203 if (title_bytes.ok()) {
204 data.
title.assign(title_bytes->begin(), title_bytes->end());
207 return absl::OkStatus();
212 const std::vector<uint8_t>& rom_vec = rom.
vector();
219 if (kVersionConstantsMap.find(data.
version) == kVersionConstantsMap.end()) {
220 return absl::FailedPreconditionError(
"Unsupported ROM version");
223 auto version_constants = kVersionConstantsMap.at(data.
version);
227 const uint32_t main_pc =
SnesToPc(main_ptr);
229 for (
int j = 0; j < 8; j++) {
231 rom.
ReadByte(main_pc + (i * 8) + j));
237 for (
int j = 0; j < 4; j++) {
245 for (
int j = 0; j < 4; j++) {
248 rom.
ReadByte(version_constants.kSpriteBlocksetPointer + (i * 4) + j));
254 for (
int j = 0; j < 4; j++) {
257 rom.
ReadByte(version_constants.kDungeonPalettesGroups + (i * 4) + j));
263 return absl::OkStatus();
272 uint32_t palette = 0;
277 const auto version = kVersionConstantsMap.find(data.
version);
278 if (version == kVersionConstantsMap.end() ||
279 version->second.kSpriteBlocksetPointer == 0) {
280 return absl::FailedPreconditionError(
281 "Gfx group tables are unknown for this ROM version");
284 return absl::OutOfRangeError(
"Main blockset pointer is past the ROM end");
290 tables.
sprite = version->second.kSpriteBlocksetPointer;
291 tables.
palette = version->second.kDungeonPalettesGroups;
292 const std::pair<uint32_t, size_t> extents[] = {
297 for (
const auto& [pc, size] : extents) {
298 if (
static_cast<size_t>(pc) + size > rom.
size()) {
299 return absl::OutOfRangeError(
300 absl::StrFormat(
"Gfx group table at 0x%06X is past the ROM end", pc));
307template <
typename Visit>
309 const GameData& data, Visit visit) {
311 for (
int j = 0; j < 8; ++j) {
316 for (
int j = 0; j < 4; ++j) {
321 for (
int j = 0; j < 4; ++j) {
326 for (
int j = 0; j < 4; ++j) {
338 ForEachGfxGroupByte(tables, data,
339 [&](uint32_t pc, uint8_t wanted,
int table) {
340 if (rom.
data()[pc] == wanted) {
365 ForEachGfxGroupByte(tables, data, [&](uint32_t pc, uint8_t wanted,
int) {
366 if (status.ok() && rom.
data()[pc] != wanted) {
367 status = rom.WriteByte(static_cast<int>(pc), wanted);
373 if (readback.any()) {
374 return absl::DataLossError(absl::StrFormat(
375 "Gfx group read-back differs in %d byte(s)", readback.changed_bytes));
377 return absl::OkStatus();
422 uint32_t ptr2, uint32_t ptr3) {
427 if (i >= 115 && i <= 126) {
435 result.
data = *read_res;
443 else if (i == 113 || i == 114 || i >= 218) {
455 0x800, 1, rom.
size());
456 if (decomp_res.ok()) {
457 result.
data = *decomp_res;
472 if (converted_sheet.size() != 4096)
473 converted_sheet.resize(4096, 0);
490 if (!default_palette.
empty()) {
491 ApplyDefaultPalette(data.
gfx_bitmaps[i], default_palette);
494 std::vector<gfx::SnesColor> grayscale;
495 for (
int color_idx = 0; color_idx < 16; ++color_idx) {
496 float val = color_idx / 15.0f;
497 grayscale.emplace_back(ImVec4(val, val, val, 1.0f));
499 if (!grayscale.empty()) {
500 grayscale[0].set_transparent(
true);
506 std::vector<gfx::SnesColor> grayscale;
507 for (
int color_idx = 0; color_idx < 16; ++color_idx) {
508 float val = color_idx / 15.0f;
509 grayscale.emplace_back(ImVec4(val, val, val, 1.0f));
511 if (!grayscale.empty()) {
512 grayscale[0].set_transparent(
true);
522 std::vector<uint8_t> placeholder(4096, 0);
531 if (kVersionConstantsMap.find(data.
version) == kVersionConstantsMap.end()) {
532 return absl::FailedPreconditionError(
533 "Unsupported ROM version for graphics");
535 auto version_constants = kVersionConstantsMap.at(data.
version);
538 version_constants.kOverworldGfxPtr1);
541 version_constants.kOverworldGfxPtr2);
544 version_constants.kOverworldGfxPtr3);
549 auto loading_handle =
550 app::platform::WasmLoadingManager::BeginLoading(
"Loading Graphics");
556 diag.ptr1_loc = gfx_ptr1;
557 diag.ptr2_loc = gfx_ptr2;
558 diag.ptr3_loc = gfx_ptr3;
564 app::platform::WasmLoadingManager::UpdateProgress(
569 auto result =
LoadSheetRaw(rom, i, gfx_ptr1, gfx_ptr2, gfx_ptr3);
572 auto& sd = diag.sheets[i];
574 sd.is_compressed = result.is_compressed;
575 sd.pc_offset = result.pc_offset;
576 sd.decompression_succeeded = result.decompression_succeeded;
577 sd.actual_decomp_size = result.data.size();
578 if (!result.data.empty()) {
579 size_t count = std::min<size_t>(result.data.size(), 8);
580 sd.first_bytes.assign(result.data.begin(), result.data.begin() + count);
582 if (result.is_compressed && !result.is_bpp3) {
583 sd.decomp_size_param = 0x800;
589 LOG_DEBUG(
"Graphics",
"Sheet %d: offset=0x%06X, size=%zu, %s", i,
590 result.pc_offset, result.data.size(),
591 result.decompression_succeeded ?
"OK" :
"FAILED");
598 LOG_INFO(
"Graphics",
"Graphics loading complete. Sheets processed: %d",
602 app::platform::WasmLoadingManager::EndLoading(loading_handle);
605 return absl::OkStatus();
614 std::array<gfx::Bitmap, kNumLinkSheets> link_graphics;
616 auto link_sheet_data_result =
619 if (!link_sheet_data_result.ok()) {
620 return link_sheet_data_result.status();
622 auto link_sheet_8bpp =
624 if (link_sheet_8bpp.size() != 4096)
625 link_sheet_8bpp.resize(4096, 0);
631 return link_graphics;
639 std::vector<uint8_t> sheet;
640 const uint8_t sheets[] = {0x71, 0x72, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE};
643 auto version_constants = kVersionConstantsMap.at(zelda3_version::US);
646 version_constants.kOverworldGfxPtr1);
649 version_constants.kOverworldGfxPtr2);
652 version_constants.kOverworldGfxPtr3);
654 for (
const auto& sheet_id : sheets) {
656 gfx_ptr3, rom.
size());
658 if (offset >= rom.
size()) {
659 return absl::OutOfRangeError(absl::StrFormat(
660 "2BPP graphics sheet %u offset %u exceeds ROM size %zu", sheet_id,
661 offset, rom.
size()));
667 if (!decomp_result.ok()) {
668 return decomp_result.status();
671 for (
const auto& each_pixel : converted_sheet) {
672 sheet.push_back(each_pixel);
684 constexpr uint32_t kFontDataSize = 0x4000;
686 auto font_data_result =
688 if (!font_data_result.ok()) {
689 return font_data_result.status();
707 [[maybe_unused]]
Rom& rom,
708 [[maybe_unused]]
const std::array<gfx::Bitmap, kNumGfxSheets>& sheets) {
712 return absl::UnimplementedError(
713 "SaveAllGraphicsData is not implemented; write edited sheets with "
714 "zelda3::WriteGfxSheet");
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
absl::StatusOr< uint8_t > ReadByte(int offset) const
const auto & vector() const
absl::StatusOr< uint16_t > ReadWord(int offset) const
static RomSettings & Get()
uint32_t GetAddressOr(const std::string &key, uint32_t default_value) const
Represents a bitmap image optimized for SNES ROM hacking.
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
constexpr bool is_modified() const
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void MarkAllSheetsChanged()
Call after the loader refills the buffer: bumps every revision.
void Clear()
Empties the buffer and bumps every revision.
static absl::Status LoadFromRom(Rom *rom, PitDamageTable *out)
int main(int argc, char **argv)
#define LOG_DEBUG(category, format,...)
#define LOG_INFO(category, format,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr char kOverworldGfxPtr3[]
constexpr char kOverworldGfxPtr1[]
constexpr char kOverworldGfxPtr2[]
absl::StatusOr< std::vector< uint8_t > > DecompressV2(const uint8_t *data, int offset, int size, int mode, size_t rom_size)
Decompresses a buffer of data using the LC_LZ2 algorithm.
constexpr int kTilesheetHeight
constexpr int kTilesheetWidth
constexpr int kTilesheetDepth
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
absl::Status LoadAllPalettes(const std::vector< uint8_t > &rom_data, PaletteGroupMap &groups)
Loads all the palettes for the game.
void ApplyDefaultPalette(gfx::Bitmap &bitmap, const gfx::SnesPalette &palette)
void ForEachGfxGroupByte(const GfxGroupTableAddresses &tables, const GameData &data, Visit visit)
absl::StatusOr< GfxGroupTableAddresses > ResolveGfxGroupTables(const Rom &rom, const GameData &data)
PaletteSlice GetDefaultPaletteSlice(const gfx::SnesPalette &palette)
absl::Status SaveGfxGroups(Rom &rom, const GameData &data)
absl::StatusOr< std::vector< uint8_t > > Load2BppGraphics(const Rom &rom)
Loads 2BPP graphics sheets from ROM.
constexpr uint16_t kLinkGfxLength
constexpr uint32_t kFontSpriteLocation
absl::StatusOr< std::array< gfx::Bitmap, kNumLinkSheets > > LoadLinkGraphics(const Rom &rom)
Loads Link's graphics sheets from ROM.
absl::StatusOr< gfx::Bitmap > LoadFontGraphics(const Rom &rom)
Loads font graphics from ROM.
constexpr uint32_t kNumRoomBlocksets
constexpr uint32_t kUncompressedSheetSize
absl::StatusOr< GfxGroupDiff > DiffGfxGroups(const Rom &rom, const GameData &data)
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
absl::Status LoadMetadata(const Rom &rom, GameData &data)
constexpr uint32_t kNumPalettesets
absl::Status SaveAllGraphicsData(Rom &rom, const std::array< gfx::Bitmap, kNumGfxSheets > &sheets)
Not implemented; always returns UnimplementedError.
std::vector< RoomDefaultEntrance > ComputeRoomDefaultEntrances(Rom &rom)
constexpr uint32_t kLinkGfxOffset
constexpr uint32_t kNumMainBlocksets
constexpr uint32_t kNumGfxSheets
constexpr uint32_t kEntranceGfxGroup
void ProcessSheetBitmap(GameData &data, uint32_t i, const SheetLoadResult &result)
constexpr uint32_t kNumSpritesets
constexpr uint32_t kNumLinkSheets
absl::Status LoadPalettes(const Rom &rom, GameData &data)
absl::Status SaveGameData(Rom &rom, GameData &data)
Saves modified game data back to the ROM.
absl::Status LoadGraphics(Rom &rom, GameData &data)
uint32_t GetGraphicsAddress(const uint8_t *data, uint8_t addr, uint32_t ptr1, uint32_t ptr2, uint32_t ptr3, size_t rom_size)
Gets the graphics address for a sheet index.
absl::Status LoadGfxGroups(Rom &rom, GameData &data)
constexpr int kGfxGroupsPointer
SheetLoadResult LoadSheetRaw(const Rom &rom, uint32_t i, uint32_t ptr1, uint32_t ptr2, uint32_t ptr3)
int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept
uint32_t SnesToPc(uint32_t addr) noexcept
#define RETURN_IF_ERROR(expr)
absl::Status for_each(Func &&func)
PaletteGroup sprites_aux1
PaletteGroup dungeon_main
Represents a group of palettes.
auto palette(int i) const
auto mutable_palette(int i)
std::vector< RoomDefaultEntrance > room_default_entrances
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
std::array< std::array< uint8_t, 4 >, kNumPalettesets > paletteset_ids
gfx::PaletteGroupMap palette_groups
GraphicsLoadDiagnostics diagnostics
PitDamageTable pit_damage_table
std::array< gfx::Bitmap, kNumGfxSheets > gfx_bitmaps
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
uint32_t gfx_main_group_address
std::array< std::vector< uint8_t >, kNumGfxSheets > raw_gfx_sheets
Which gfx group tables in data differ from the bytes in rom.
std::vector< uint8_t > & graphics_buffer
Legacy name for sheet_store.mutable_pixels().
GraphicsSheetStore sheet_store
std::vector< uint8_t > data
bool decompression_succeeded