5#include "absl/strings/str_format.h"
9#include "nlohmann/json.hpp"
27 case SheetRole::kOverworldMain:
28 return "overworld_main";
29 case SheetRole::kOverworldGfx:
30 return "overworld_gfx";
31 case SheetRole::kOverworldAreaAux1:
32 return "overworld_area_aux1";
33 case SheetRole::kOverworldAreaAux2:
34 return "overworld_area_aux2";
35 case SheetRole::kOverworldAnimated:
36 return "overworld_animated";
37 case SheetRole::kSpriteAux1:
41 case SheetRole::kDungeonMain:
42 return "dungeon_main";
43 case SheetRole::kUnclassified:
44 return "unclassified";
46 return "unclassified";
50 return std::string(DefaultBindingFor(role).palette_group_name);
53uint64_t
MixRevision(uint64_t revision,
int slot, uint64_t sheet_revision) {
55 sheet_revision + 0x9e3779b97f4a7c15ULL *
static_cast<uint64_t
>(slot + 1);
60 if (game_data ==
nullptr || sheet_id < 0 ||
73 const auto& bytes = rom.
vector();
75 reinterpret_cast<const char*
>(bytes.data()), bytes.size()));
80 std::string_view surface_bytes) {
92 if (!document.contains(
"entries") || !document[
"entries"].is_array()) {
95 for (
const auto& entry : document[
"entries"]) {
96 if (!entry.contains(
"bindings") || !entry[
"bindings"].is_array()) {
99 for (
const auto& binding : entry[
"bindings"]) {
100 if (binding.contains(
"adapter") &&
101 binding[
"adapter"] ==
"alttp.overworld.tile16.v1") {
110 nlohmann::json
json = {
119 {
"width", slot.
width},
126 json[
"composite_upper_revision"] =
133 return area == 0x03 || area == 0x05 || area == 0x07 || area == 0x43 ||
134 area == 0x45 || area == 0x47;
142 return "dungeon_main";
157 return absl::OutOfRangeError(
"Palette address group/index is unavailable");
159 const auto& palette = group->palette_ref(address.
palette_index);
161 address.
color_index >=
static_cast<int>(palette.size())) {
162 return absl::OutOfRangeError(
"Palette address color is unavailable");
168 nlohmann::json document = {
182 {
"slots", nlohmann::json::array()},
183 {
"palettes", nlohmann::json::array()},
184 {
"alternates", nlohmann::json::array()},
186 for (
const auto& slot :
slots) {
187 document[
"slots"].push_back(SlotJson(slot));
189 for (
const auto& palette :
palettes) {
190 document[
"palettes"].push_back({
191 {
"name", palette.name},
192 {
"source", palette.source},
193 {
"selector", palette.selector},
194 {
"inherited", palette.inherited},
198 document[
"alternates"].push_back({
199 {
"name", alternate.name},
200 {
"state", alternate.state},
201 {
"detail", alternate.detail},
204 return document.dump();
210 context.
surface =
"overworld_map";
214 if (map.
rom_ ==
nullptr) {
215 AttachIdentity(&context,
nullptr, identity,
"");
219 AttachIdentity(&context, map.
rom_, identity,
"");
227 const bool custom_tiles =
230 const int world_offset = (map.
parent() & 0xC0) >> 3;
234 const auto record_group_source = [&](
const char*
name,
bool shared) {
236 {
name, shared ?
"session" :
"rom",
237 shared ?
"Includes unsaved shared group assignments"
238 :
"Session table is unloaded or uses a different ROM address"});
242 record_group_source(
"sprite_group_storage",
245 std::string fingerprint_bytes;
247 context.
slots.reserve(16);
248 for (
int slot = 0; slot < 16; ++slot) {
250 resolved.
slot = slot;
252 FillSheetShape(&resolved);
256 fingerprint_bytes.push_back(
static_cast<char>(resolved.
sheet_id));
259 resolved.
role = RoleName(RoleForBlocksetSlot(slot));
260 resolved.
palette_group = PaletteGroupFor(RoleForBlocksetSlot(slot));
262 uint8_t predicted = main_sheets[slot];
263 resolved.
source =
"main_group";
264 if (!custom_tiles && slot >= 3 && slot <= 6 &&
265 area_sheets[slot - 3] != 0) {
266 predicted = area_sheets[slot - 3];
267 resolved.
source =
"area_override";
270 uint8_t custom_sheet = props.custom_gfx_ids[slot];
271 if (custom_sheet == 0xFF) {
274 resolved.
source =
"world_default";
276 predicted = custom_sheet;
277 resolved.
source =
"custom_tile_gfx";
281 }
else if (slot < 12) {
282 static constexpr uint8_t kStaticSpriteSheets[] = {0x73, 0x74, 0x79, 0x7A};
283 resolved.
role =
"static_sprite";
284 resolved.
palette_group = PaletteGroupFor(SheetRole::kSpriteAux1);
285 resolved.
source =
"static_sprite";
289 resolved.
role =
"spriteset";
290 resolved.
palette_group = PaletteGroupFor(SheetRole::kSpriteAux1);
291 resolved.
source =
"spriteset";
293 const int predicted =
294 static_cast<uint8_t
>(sprite_sheets[slot - 12] + 0x73);
299 context.
slots.push_back(resolved);
302 auto& slot7 = context.
slots[7];
303 slot7.composite =
true;
304 const uint8_t raw_animated = props.animated_gfx;
307 uint8_t predicted_upper = raw_animated;
308 if (custom_animated) {
309 if (raw_animated == 0x00 || raw_animated == 0xFF) {
311 slot7.composite_upper_source =
"world_default";
313 slot7.composite_upper_source =
"animated_table";
315 }
else if (raw_animated == 0x00 || raw_animated == 0xFF) {
316 predicted_upper = DeathMountain(map.
parent()) ? 0x59 : 0x5B;
317 slot7.composite_upper_source =
"vanilla_default";
319 slot7.composite_upper_source =
"animated_table";
322 slot7.composite_upper_revision =
323 SheetRevision(map.
game_data_, slot7.composite_upper_sheet);
324 if (slot7.composite_upper_sheet != slot7.sheet_id) {
326 slot7.composite_upper_revision);
328 const bool upper_agrees = predicted_upper == slot7.composite_upper_sheet;
329 slot7.loader_agrees = slot7.loader_agrees && upper_agrees;
331 fingerprint_bytes.push_back(
static_cast<char>(slot7.composite_upper_sheet));
336 ?
"expanded_screen_palette"
337 :
"vanilla_screen_palette",
349 rom[constants.kOverworldMapPaletteGroup + (area_palette * 4)];
351 rom[constants.kOverworldMapPaletteGroup + (area_palette * 4) + 1];
352 const uint8_t animated =
353 rom[constants.kOverworldMapPaletteGroup + (area_palette * 4) + 2];
354 context.
palettes.push_back({
"aux1",
"overworld_aux", aux1, aux1 == 0xFF});
355 context.
palettes.push_back({
"aux2",
"overworld_aux", aux2, aux2 == 0xFF});
357 {
"animated",
"overworld_animated", animated, animated == 0xFF});
359 const uint8_t sprite0 =
361 const uint8_t sprite1 =
364 {
"sprite0",
"sprite_palette_group", sprite0, sprite0 == 0xFF});
366 {
"sprite1",
"sprite_palette_group", sprite1, sprite1 == 0xFF});
367 const bool custom_bg =
372 custom_bg ?
"area_specific_bg" :
"world_default",
373 custom_bg ? props.area_specific_bg_color
381 for (
int state = 0; state < 3; ++state) {
383 absl::StrFormat(
"sprite_state_%d", state),
384 state == map.
game_state() ?
"active" :
"recorded",
385 absl::StrFormat(
"graphics=0x%02X palette=0x%02X",
390 {
"day_night_tint",
"unsupported",
"not resolved by this loader"});
391 context.
alternates.push_back({
"runtime_palette_transfer",
"unsupported",
392 "not resolved by this loader"});
394 AttachIdentity(&context, &rom, identity, fingerprint_bytes);
401 context.
surface =
"dungeon_room";
402 context.
id = room.
id();
407 AttachIdentity(&context, room.
rom_, identity,
"");
413 const auto blocks = room.
blocks();
417 const size_t sprite_index =
static_cast<size_t>(room.
spriteset()) + 64;
418 const bool sprites_ok = sprite_index < data.
spriteset_ids.size();
419 static constexpr uint8_t kStaticSpriteSheets[] = {115, 125, 121, 122};
421 std::string fingerprint_bytes;
423 context.
slots.reserve(16);
424 for (
int slot = 0; slot < 16; ++slot) {
426 resolved.
slot = slot;
428 FillSheetShape(&resolved);
432 fingerprint_bytes.push_back(
static_cast<char>(resolved.
sheet_id));
436 resolved.
role =
"main_blockset";
437 resolved.
palette_group = PaletteGroupFor(SheetRole::kDungeonMain);
438 }
else if (slot >= 3) {
441 resolved.
role =
"room_blockset";
442 resolved.
palette_group = PaletteGroupFor(SheetRole::kDungeonMain);
444 resolved.
role =
"main_blockset";
445 resolved.
palette_group = PaletteGroupFor(SheetRole::kDungeonMain);
447 resolved.
source =
"main_blockset";
448 uint8_t predicted = 0;
453 if (slot >= 3 && slot <= 6 && room_ok) {
454 const uint8_t room_sheet =
456 if (room_sheet != 0) {
457 predicted = room_sheet;
458 resolved.
source =
"room_blockset";
462 }
else if (slot < 12) {
463 resolved.
role =
"static_sprite";
464 resolved.
palette_group = PaletteGroupFor(SheetRole::kSpriteAux1);
465 resolved.
source =
"static_sprite";
469 resolved.
role =
"spriteset";
470 resolved.
palette_group = PaletteGroupFor(SheetRole::kSpriteAux1);
471 resolved.
source = sprites_ok ?
"spriteset" :
"spriteset_missing";
473 const int predicted =
474 sprites_ok ?
static_cast<uint8_t
>(
480 context.
slots.push_back(resolved);
483 context.
palettes.push_back({
"header",
"room_header", room.
palette(),
false});
485 static constexpr const char* kSetNames[] = {
486 "dungeon_main_offset",
"sprite_aux1",
"sprite_aux3",
"sprite_aux3_b"};
487 for (
int i = 0; i < 4; ++i) {
488 context.
palettes.push_back({kSetNames[i],
"dungeon_palette_set",
493 context.
palettes.push_back({
"dungeon_main",
"palette_pointer",
497 {
"entrance_world",
"unresolved",
498 "the room does not retain the entrance overworld"});
500 {
"sprite_environment",
"light_world_fallback",
501 "preview uses the outdoor light-world sprite environment"});
503 AttachIdentity(&context, room.
rom_, identity, fingerprint_bytes);
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
const nlohmann::json & document() const
uint64_t Revision(uint16_t sheet) const
static constexpr uint16_t kSheetCount
static constexpr size_t kSheetWidth
static constexpr size_t kSheetHeight
Represents a single Overworld map screen.
const gfx::SnesPalette & current_palette() const
zelda3_version_pointers version_constants() const
bool UsesSessionAreaGraphicsGroup() const
bool has_inherited_area_properties() const
std::array< uint8_t, 4 > ResolveSpriteGraphicsGroup() const
auto sprite_graphics(int i) const
bool UsesSessionSpriteGraphicsGroup() const
bool UsesSessionMainGraphicsGroup() const
auto static_graphics(int i) const
auto area_palette() const
uint8_t animated_sheet() const
std::array< uint8_t, 4 > ResolveAreaGraphicsGroup() const
auto sprite_palette(int i) const
std::array< uint8_t, 8 > ResolveMainGraphicsGroup() const
AreaRenderProperties area_render_properties() const
static bool SupportsCustomBGColors(OverworldVersion version)
Check if ROM supports custom background colors per area (v2+)
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
static bool SupportsAreaEnum(OverworldVersion version)
Check if ROM supports area enum system (v3+ only)
static bool SupportsCustomTileGFX(OverworldVersion version)
Check if ROM supports custom tile GFX groups (v3+)
uint8_t resolved_main_blockset_
const gfx::SnesPalette & rendered_dungeon_palette() const
uint8_t spriteset() const
int ResolveDungeonPaletteId() const
One ROM session's graphics sheet pixels, with a revision per sheet.
std::string ComputeSourceArtifactSha256(std::string_view content)
std::string TerrainFingerprint(std::string_view bytes)
SheetRolePaletteBinding DefaultBindingFor(SheetRole role)
SheetRole RoleForRoomsetSlot(int slot_index)
SheetRole RoleForBlocksetSlot(int slot_index)
void FillSheetShape(ResolvedGraphicsSlot *slot)
uint64_t MixRevision(uint64_t revision, int slot, uint64_t sheet_revision)
nlohmann::json SlotJson(const ResolvedGraphicsSlot &slot)
bool DeathMountain(int area)
void AttachIdentity(ResolvedGraphicsContext *context, const Rom *rom, const GraphicsContextIdentity &identity, std::string_view surface_bytes)
std::string RoleName(SheetRole role)
std::string RomSha256(const Rom &rom)
uint64_t SheetRevision(const GameData *game_data, int sheet_id)
std::string PaletteGroupFor(SheetRole role)
Zelda 3 specific classes and functions.
constexpr int OverworldCustomTileGFXGroupEnabled
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kOverworldSpritePaletteGroup
constexpr int OverworldCustomDefaultGFXGroups
constexpr int OverworldCustomAnimatedGFXEnabled
ResolvedGraphicsContext DescribeDungeonGraphicsContext(const Room &room, const GraphicsContextIdentity &identity)
const char * PaletteAddressGroupName(PaletteAddressGroup group)
@ kVanilla
0xFF in ROM, no ZScream ASM applied
ResolvedGraphicsContext DescribeOverworldGraphicsContext(const OverworldMap &map, const GraphicsContextIdentity &identity)
absl::StatusOr< gfx::SnesColor > ReadPaletteColor(const GameData &owner, const PaletteColorAddress &address)
PaletteGroup * get_group(const std::string &group_name)
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
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
const core::TerrainCatalog * terrain_catalog
GraphicsSheetStore sheet_store
PaletteAddressGroup group
std::string ToJson() const
std::string graphics_fingerprint
std::string terrain_catalog
std::vector< ResolvedPaletteSource > palettes
int terrain_adapter_entries
std::vector< ResolvedAlternateContext > alternates
bool palette_colors_resolved
uint64_t graphics_revision
std::vector< ResolvedGraphicsSlot > slots
uint64_t composite_upper_revision
std::string palette_group
int composite_upper_sheet
std::string composite_upper_source