7#include "absl/status/status.h"
8#include "absl/strings/str_format.h"
9#include "absl/strings/str_join.h"
29 return {color.r, color.g, color.b};
34 return absl::InvalidArgumentError(
35 absl::StrFormat(
"Room 0x%X is out of range", room));
38 return absl::FailedPreconditionError(
"Dungeon palettes are not loaded");
49 for (
int i = 0; i < 8; ++i) {
50 const auto level =
static_cast<uint8_t
>(i * 255 / 7);
51 palette[i] = {level, level, level};
59 if (row < 0 || row > 7) {
60 return absl::InvalidArgumentError(
"Background palette row must be 0-7");
66 if (row >=
static_cast<int>(group.size()) ||
67 group.palette_ref(row).size() < 8) {
68 return absl::FailedPreconditionError(absl::StrFormat(
69 "Room 0x%X has no background palette row %d", room, row));
72 for (
int i = 0; i < 8; ++i) {
73 const auto rgb = group.palette_ref(row)[i].rgb();
74 palette[i] = {
static_cast<uint8_t
>(rgb.x),
static_cast<uint8_t
>(rgb.y),
75 static_cast<uint8_t
>(rgb.z)};
82 if (row < 0 || row > 7) {
83 return absl::InvalidArgumentError(
"Sprite palette row must be 0-7");
88 for (
int i = 0; i < 8; ++i) {
89 palette[i] = ToSheetColor(cgram[(8 + row) * 16 + i]);
95 const std::vector<uint8_t>& snes_3bpp,
const SheetPalette& palette,
96 int first_block,
int block_count) {
98 return absl::InvalidArgumentError(
"A 3bpp sheet is 0x600 bytes");
100 if (first_block < 0 || block_count <= 0 ||
102 return absl::InvalidArgumentError(
103 absl::StrFormat(
"Blocks %d-%d are outside the sheet's 16 blocks",
104 first_block, first_block + block_count - 1));
107 const int columns = std::min(block_count, 8);
108 const int rows = (block_count + 7) / 8;
109 const int width = columns * kBlockSize;
110 const int height = rows * kBlockSize;
111 std::vector<uint8_t> indices(
static_cast<size_t>(width) * height, 0);
112 for (
int k = 0; k < block_count; ++k) {
113 const auto [sx, sy] = BlockOrigin(first_block + k);
114 const int dx = (k % 8) * kBlockSize;
115 const int dy = (k / 8) * kBlockSize;
116 for (
int y = 0; y < kBlockSize; ++y) {
117 for (
int x = 0; x < kBlockSize; ++x) {
118 indices[(dy + y) * width + dx + x] =
119 sheet[(sy + y) * kSheetWidth + sx + x] & 0x07;
123 std::vector<std::array<uint8_t, 4>> png_palette;
124 for (
int i = 0; i < 8; ++i) {
125 png_palette.push_back({palette[i][0], palette[i][1], palette[i][2],
126 static_cast<uint8_t
>(i == 0 ? 0 : 255)});
135 return absl::InvalidArgumentError(
"A 3bpp sheet is 0x600 bytes");
137 if (png.
width % kBlockSize != 0 || png.
height % kBlockSize != 0) {
138 return absl::InvalidArgumentError(absl::StrFormat(
139 "PNG is %dx%d; width and height must be multiples of 16", png.
width,
142 const int columns = png.
width / kBlockSize;
143 const int block_count = columns * (png.
height / kBlockSize);
145 return absl::InvalidArgumentError(absl::StrFormat(
146 "The PNG holds %d 16x16 blocks; starting at block %d it runs past "
148 block_count, first_block));
152 std::vector<uint8_t> values(
static_cast<size_t>(png.
width) * png.
height);
153 std::vector<std::string> offenders;
155 for (
int y = 0; y < png.
height; ++y) {
156 for (
int x = 0; x < png.
width; ++x) {
157 const size_t i =
static_cast<size_t>(y) * png.
width + x;
162 seen = absl::StrFormat(
"index %d", value);
167 const uint8_t* p = &png.
rgba[i * 4];
168 seen = absl::StrFormat(
"#%02X%02X%02X", p[0], p[1], p[2]);
171 }
else if (p[3] == 255) {
172 for (
int c = 0; c < 8; ++c) {
173 if (palette[c][0] == p[0] && palette[c][1] == p[1] &&
174 palette[c][2] == p[2]) {
183 if (offenders.size() < 8) {
184 offenders.push_back(absl::StrFormat(
"(%d,%d) %s", x, y, seen));
188 values[i] =
static_cast<uint8_t
>(value);
191 if (bad_pixels > 0) {
192 return absl::InvalidArgumentError(absl::StrFormat(
193 "%d pixel(s) are not 3bpp indices 0-7 of the sheet palette; nothing "
194 "was imported. First: %s",
195 bad_pixels, absl::StrJoin(offenders,
", ")));
199 std::vector<uint8_t> sheet = old_sheet;
200 for (
int k = 0; k < block_count; ++k) {
201 const auto [dx, dy] = BlockOrigin(first_block + k);
202 const int sx = (k % columns) * kBlockSize;
203 const int sy = (k / columns) * kBlockSize;
204 for (
int y = 0; y < kBlockSize; ++y) {
205 for (
int x = 0; x < kBlockSize; ++x) {
206 sheet[(dy + y) * kSheetWidth + dx + x] =
207 values[(sy + y) * png.
width + sx + x];
216 const auto [bx, by] = BlockOrigin(block);
217 bool changed =
false;
218 for (
int y = 0; y < kBlockSize && !changed; ++y) {
219 for (
int x = 0; x < kBlockSize; ++x) {
220 const size_t i = (by + y) * kSheetWidth + bx + x;
221 if ((sheet[i] & 7) != (old_sheet[i] & 7)) {
231 for (
int tile = 0; tile < 64; ++tile) {
232 if (!std::equal(result.
snes_3bpp.begin() + tile * 24,
233 result.
snes_3bpp.begin() + tile * 24 + 24,
234 snes_3bpp.begin() + tile * 24)) {
238 for (
size_t i = 0; i < snes_3bpp.size();) {
239 if (result.
snes_3bpp[i] == snes_3bpp[i]) {
244 while (end < snes_3bpp.size() && result.
snes_3bpp[end] != snes_3bpp[end]) {
248 static_cast<int>(end));
255 uint8_t header_blockset) {
261 for (
int i = 0; i < 8; ++i) {
265 if (room_sheet != 0) {
266 set.
sheets[i] = room_sheet;
275 Rom& rom,
GameData& data,
int room,
int slot,
int row) {
276 if (slot < 0 || slot >= 8 || row < 0 || row >= 8) {
277 return absl::InvalidArgumentError(
"Background slot and row must be 0-7");
295 if (row >=
static_cast<int>(group.size()) ||
296 group.palette_ref(row).size() < 16) {
297 return absl::FailedPreconditionError(
298 "Room background palette is incomplete");
300 for (
int index = 0; index < 8; ++index) {
301 const int column = index == 0 ? 0 : index + result.
palette_offset;
303 const auto rgb = group.palette_ref(row)[column].rgb();
304 result.
colors[index] = {
static_cast<uint8_t
>(rgb.x),
305 static_cast<uint8_t
>(rgb.y),
306 static_cast<uint8_t
>(rgb.z)};
313 std::vector<RoomBackgroundSet> sets;
322 constexpr int kHeight = kSheetHeight * 8;
323 std::vector<uint8_t> indices(
static_cast<size_t>(kSheetWidth) * kHeight, 0);
324 for (
int slot = 0; slot < 8; ++slot) {
325 const uint16_t sheet = set.
sheets[slot];
332 for (
int y = 0; y < kSheetHeight; ++y) {
333 for (
int x = 0; x < kSheetWidth; ++x) {
334 indices[(slot * kSheetHeight + y) * kSheetWidth + x] =
335 pixels[y * kSheetWidth + x] & 0x07;
339 std::vector<std::array<uint8_t, 4>> png_palette;
340 for (
int i = 0; i < 8; ++i) {
341 png_palette.push_back({palette[i][0], palette[i][1], palette[i][2],
342 static_cast<uint8_t
>(i == 0 ? 0 : 255)});
350 if (png.
width != kSheetWidth || png.
height != kSheetHeight * 8) {
351 return absl::InvalidArgumentError(absl::StrFormat(
352 "A room background PNG is 128x256 (8 stacked sheets); got %dx%d",
355 std::vector<RoomSheetImport> imports;
356 std::map<uint16_t, std::pair<int, std::vector<uint8_t>>> seen;
357 for (
int slot = 0; slot < 8; ++slot) {
358 const uint16_t sheet = set.
sheets[slot];
361 band.
width = kSheetWidth;
362 band.
height = kSheetHeight;
365 const size_t begin =
static_cast<size_t>(slot) * kSheetHeight * kSheetWidth;
366 const size_t count =
static_cast<size_t>(kSheetHeight) * kSheetWidth;
369 png.
indices.begin() + begin + count);
371 band.
rgba.assign(png.
rgba.begin() + begin * 4,
372 png.
rgba.begin() + (begin + count) * 4);
375 const bool writable =
384 return absl::Status(result.status().code(),
385 absl::StrFormat(
"Slot %d (sheet 0x%02X): %s", slot,
386 sheet, result.status().message()));
389 if (!result->changed_blocks.empty()) {
390 return absl::FailedPreconditionError(absl::StrFormat(
391 "Slot %d shows 2bpp sheet 0x%02X, which cannot be edited here",
398 auto first = seen.find(sheet);
399 if (first != seen.end()) {
400 if (first->second.second != result->snes_3bpp) {
401 return absl::InvalidArgumentError(absl::StrFormat(
402 "Sheet 0x%02X appears in slots %d and %d with different pixels",
403 sheet, first->second.first, slot));
406 seen.emplace(sheet, std::make_pair(slot, result->snes_3bpp));
408 if (result->changed_blocks.empty()) {
411 auto existing = std::find_if(
412 imports.begin(), imports.end(),
413 [sheet](
const RoomSheetImport& other) { return other.sheet == sheet; });
414 if (existing != imports.end()) {
415 existing->slots.push_back(slot);
418 imports.push_back({sheet, {slot}, std::move(*result)});
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
int ResolveDungeonPaletteId() const
void SetGameData(GameData *data)
Which graphics sheets exist, which tables use them, and which 16x16 blocks are free.
3bpp graphics sheets to and from indexed PNGs.
Locates graphics sheets in the ROM and writes edited sheets back.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
std::vector< uint8_t > IndexedToSnesSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
absl::StatusOr< std::vector< uint8_t > > EncodeIndexedPng(int width, int height, const std::vector< uint8_t > &indices, const std::vector< std::array< uint8_t, 4 > > &palette)
std::pair< int, int > BlockOrigin(int block)
SheetColor ToSheetColor(const SDL_Color &color)
constexpr int kSheetWidth
absl::StatusOr< Room > LoadRoomWithPalettes(Rom &rom, GameData &data, int room)
constexpr int kSheetHeight
Zelda 3 specific classes and functions.
std::vector< RoomBackgroundSet > ResolveAllRoomBackgroundSets(const Rom &rom, const GameData &data)
All 296 rooms' background sets (header bytes read from the ROM).
absl::StatusOr< RoomSheetPaletteContext > ResolveRoomSheetPalette(Rom &rom, GameData &data, int room, int slot, int row)
absl::StatusOr< std::vector< uint8_t > > ExportRoomBackgroundPng(const Rom &rom, const RoomBackgroundSet &set, const SheetPalette &palette)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
absl::StatusOr< SheetImportResult > ImportSheetBlocksPng(const std::vector< uint8_t > &snes_3bpp, const util::PngImage &png, int first_block, const SheetPalette &palette)
Places a PNG's 16x16 blocks over a sheet from first_block.
constexpr size_t kGfxSheet3bppBytes
RoomBackgroundSet ResolveRoomBackgroundSet(const GameData &data, int room, uint8_t header_blockset)
std::map< int, RoomGfxInfo > CollectRoomGfx(const Rom &rom)
gfx::PaletteGroup BuildDungeonRenderPaletteGroupFromGameData(const gfx::SnesPalette &dungeon_palette, const GameData *game_data)
absl::StatusOr< std::vector< uint8_t > > ReadGfxSheetData(const Rom &rom, uint16_t sheet_id, const GfxSheetPointerTables &tables)
Returns the sheet's decoded bytes (SNES planar data).
std::array< uint8_t, 3 > SheetColor
int DungeonBackgroundPaletteOffset(uint8_t main_blockset, int destination_slot)
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
constexpr size_t kGfxSheetCount
constexpr int kGfxSheetBlockCount
16x16 blocks per sheet: 8 columns x 2 rows over the 128x32 sheet.
RoomMainBlocksetResolution ResolveRoomMainBlockset(int room_id, uint8_t header_blockset, uint8_t requested_main_blockset, const GameData &game_data)
std::array< SheetColor, 8 > SheetPalette
Colors for 3bpp indices 0-7. Index 0 is exported as transparent.
absl::StatusOr< SheetPalette > RoomSpriteSheetPalette(Rom &rom, GameData &data, int room, int row)
Sprite palette row (OAM palette 0-7, CGRAM row 8 + row) of room room.
absl::StatusOr< std::vector< uint8_t > > ExportSheetBlocksPng(const std::vector< uint8_t > &snes_3bpp, const SheetPalette &palette, int first_block, int block_count)
SheetPalette GrayscaleSheetPalette()
absl::StatusOr< SheetPalette > RoomBackgroundSheetPalette(Rom &rom, GameData &data, int room, int row)
constexpr int kNumberOfRooms
std::array< SDL_Color, 256 > BuildDungeonSpriteRenderPalette(const Room &room, const GameData *game_data)
absl::StatusOr< std::vector< RoomSheetImport > > ImportRoomBackgroundPng(const Rom &rom, const RoomBackgroundSet &set, const util::PngImage &png, const SheetPalette &palette)
PaletteGroup dungeon_main
const SnesPalette & palette_ref(int i) const
A decoded PNG: palette indices for indexed files, RGBA otherwise.
std::vector< std::array< uint8_t, 4 > > palette
std::vector< uint8_t > indices
std::vector< uint8_t > rgba
std::vector< RoomDefaultEntrance > room_default_entrances
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
gfx::PaletteGroupMap palette_groups
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
std::array< uint8_t, 8 > sheets
std::array< bool, 8 > from_room_blockset
RoomMainBlocksetSource source
std::array< uint8_t, 8 > cgram_indices
RoomDefaultEntrance entrance
RoomMainBlocksetResolution main
RoomBackgroundSet background
std::vector< std::pair< int, int > > changed_ranges
std::vector< uint8_t > snes_3bpp
std::vector< int > changed_tiles
std::vector< int > changed_blocks