10#include "absl/status/status.h"
11#include "absl/strings/match.h"
12#include "absl/strings/numbers.h"
13#include "absl/strings/str_format.h"
14#include "absl/strings/str_join.h"
15#include "absl/strings/str_split.h"
16#include "absl/strings/strip.h"
19#include "nlohmann/json.hpp"
34absl::StatusOr<int>
ParseNumber(absl::string_view text,
const char* what) {
35 text = absl::StripAsciiWhitespace(text);
38 const bool hex = absl::StartsWith(text,
"0x") ||
39 absl::StartsWith(text,
"0X") || absl::StartsWith(text,
"$");
42 : absl::SimpleAtoi(text, &value);
44 return absl::InvalidArgumentError(
45 absl::StrFormat(
"%s '%s' is not a number", what, std::string(text)));
52 if (!text.has_value()) {
53 return absl::InvalidArgumentError(
"--sheet is required");
57 return absl::InvalidArgumentError(
"--sheet must be 0-222");
61 return absl::UnimplementedError(
62 "2bpp sheets (113, 114, 218+) are not supported");
64 return static_cast<uint16_t
>(sheet);
68 const char* name,
int fallback) {
70 if (!text.has_value()) {
78 std::optional<zelda3::RoomSheetPaletteContext>* context =
nullptr) {
79 const std::string spec = parser.
GetString(
"palette").value_or(
"gray");
83 if (absl::StartsWith(spec,
"rgb:")) {
84 std::vector<std::string> parts = absl::StrSplit(spec.substr(4),
',');
85 if (parts.size() != 8) {
86 return absl::InvalidArgumentError(
"rgb: palettes need 8 colors");
89 for (
int i = 0; i < 8; ++i) {
90 absl::string_view hex = absl::StripAsciiWhitespace(parts[i]);
91 absl::ConsumePrefix(&hex,
"#");
93 if (hex.size() != 6 ||
94 !std::all_of(hex.begin(), hex.end(),
95 [](
unsigned char c) { return std::isxdigit(c) != 0; }) ||
97 return absl::InvalidArgumentError(
"rgb: colors are #RRGGBB");
99 palette[i] = {
static_cast<uint8_t
>(rgb >> 16),
100 static_cast<uint8_t
>(rgb >> 8),
static_cast<uint8_t
>(rgb)};
104 if (absl::StartsWith(spec,
"room-slot:")) {
105 const std::vector<std::string> parts = absl::StrSplit(spec,
':');
106 if (parts.size() != 4 || sheet < 0) {
107 return absl::InvalidArgumentError(
108 "room-slot:<room>:<slot>:<row> requires a single --sheet; use "
109 "gfx-room-export --out-dir for per-slot room palettes");
120 rom, data, room, slot, row));
121 if (resolved.background.sheets[slot] != sheet) {
122 return absl::InvalidArgumentError(absl::StrFormat(
123 "Room 0x%03X slot %d loads sheet 0x%02X, not --sheet 0x%02X", room,
124 slot, resolved.background.sheets[slot], sheet));
126 if (context !=
nullptr)
128 return resolved.colors;
130 const bool background = absl::StartsWith(spec,
"room-bg:");
131 if (background || absl::StartsWith(spec,
"room-sprite:")) {
132 std::vector<std::string> parts = absl::StrSplit(spec,
':');
133 if (parts.size() != 3) {
134 return absl::InvalidArgumentError(
135 "Room palettes are room-bg:<room>:<row> or room-sprite:<room>:<row>");
146 rom, data, room, row,
149 return absl::InvalidArgumentError(
"Unknown --palette " + spec);
153 return absl::StrJoin(palette,
",", [](std::string* out,
const auto& color) {
154 out->append(absl::StrFormat(
"#%02X%02X%02X", color[0], color[1], color[2]));
161 const char* main_source =
"zero_fallback";
163 case Source::kExplicitPreview:
164 main_source =
"explicit_preview";
166 case Source::kDirectDefault:
167 main_source =
"direct_default";
169 case Source::kInferredDefault:
170 main_source =
"inferred_default";
172 case Source::kHeaderFallback:
173 main_source =
"header_fallback";
175 case Source::kZeroFallback:
179 const char* entrance_source =
"none";
181 case EntranceSource::kDirect:
182 entrance_source =
"direct";
184 case EntranceSource::kDungeonMap:
185 entrance_source =
"dungeon_map";
187 case EntranceSource::kNeighbour:
188 entrance_source =
"neighbour";
190 case EntranceSource::kNone:
194 {
"slot", context.
slot},
197 {
"main_source", main_source},
203 {
"entrance_source", entrance_source},
205 {
"palette_row", context.
row},
208 {
"colors_rgb", context.
colors},
209 {
"transparent_index", 0},
210 {
"animation_overlay_included",
false}};
216 const std::vector<std::string>& outputs) {
217 std::vector<std::string> seen;
220 for (
const auto& path : outputs) {
222 return absl::InvalidArgumentError(
"Empty export path");
224 const auto canonical = std::filesystem::weakly_canonical(path, ec);
226 return absl::InvalidArgumentError(
"Invalid export path: " + path);
227 for (
const auto& other : seen) {
228 const bool equivalent = std::filesystem::equivalent(path, other, ec);
230 const auto other_canonical = std::filesystem::weakly_canonical(other, ec);
231 if (equivalent || (!ec && canonical == other_canonical)) {
232 return absl::InvalidArgumentError(
233 "Export destinations must differ from each other and the input "
237 seen.push_back(path);
239 return absl::OkStatus();
242absl::Status
WriteJson(
const std::string& path,
const nlohmann::json& value) {
243 const std::string text = value.dump(2) +
"\n";
247std::string
RangeText(
const std::vector<std::pair<int, int>>& ranges) {
248 return absl::StrJoin(ranges,
" ", [](std::string* out,
const auto& range) {
250 absl::StrFormat(
"0x%03X-0x%03X", range.first, range.second - 1));
263 auto room_text = parser.
GetString(
"room");
264 if (!room_text.has_value()) {
265 return absl::InvalidArgumentError(
"--room is required");
269 return absl::InvalidArgumentError(
"--room must be 0x000-0x127");
278 const auto header = rooms.find(room);
279 if (header == rooms.end()) {
280 return absl::DataLossError(
281 absl::StrFormat(
"Room 0x%03X header is unreadable", room));
284 header->second.blockset);
285 if (parser.
GetString(
"palette").has_value()) {
296 const std::string& directory,
298 if (directory.empty())
299 return absl::InvalidArgumentError(
"Empty --out-dir");
302 std::vector<std::pair<std::string, std::vector<uint8_t>>> files;
303 nlohmann::json manifest = {{
"schema_version", 1},
305 {
"palette_row", context.
row},
307 {
"slots", nlohmann::json::array()}};
308 for (
int slot = 0; slot < 8; ++slot) {
310 const auto palette_context,
314 const uint16_t sheet = context.
set.
sheets[slot];
318 entry[
"status"] =
"unsupported_sheet_format";
323 const std::string name =
324 absl::StrFormat(
"slot-%d-sheet-%02X.png", slot, sheet);
325 entry[
"status"] =
"exported";
327 entry[
"palette_spec"] = absl::StrFormat(
328 "room-slot:0x%03X:%d:%d", context.
set.
room, slot, context.
row);
329 files.emplace_back(name, std::move(png));
331 manifest[
"slots"].push_back(std::move(entry));
334 if (!std::filesystem::create_directory(directory, ec)) {
335 return absl::FailedPreconditionError(
336 "--out-dir must be a new directory in an existing parent: " +
337 directory + (ec ?
" (" + ec.message() +
")" :
""));
339 auto write = [&]() -> absl::Status {
340 for (
const auto& [name, png] : files) {
342 (std::filesystem::path(directory) / name).
string(), png));
345 (std::filesystem::path(directory) /
"manifest.json").
string(),
348 const absl::Status status = write();
351 std::filesystem::remove_all(directory, ec);
354 formatter.
AddField(
"out_dir", directory);
356 return absl::OkStatus();
359std::string
HexIds(
const std::vector<int>& ids) {
360 return absl::StrJoin(ids,
" ", [](std::string* out,
int id) {
361 out->append(absl::StrFormat(
"%03X",
id));
369 if (!absl::StartsWith(parser.
GetString(
"palette").value_or(
""),
374 :
"low (columns 0-7)";
386 if (sheet >= inventory.sheets.size()) {
387 return absl::OutOfRangeError(absl::StrFormat(
"No sheet 0x%02X", sheet));
389 const auto& entry = inventory.sheets[sheet];
390 *reserved = entry.reserved;
391 nlohmann::json shared = nlohmann::json::object();
392 const std::pair<const char*, const std::vector<int>*> lists[] = {
393 {
"main_blocksets", &entry.usage.main_blocksets},
394 {
"room_blocksets", &entry.usage.room_blocksets},
395 {
"ow_areas_static", &entry.usage.ow_areas_static},
396 {
"spritesets", &entry.usage.spritesets},
397 {
"ow_areas_sprite", &entry.usage.ow_areas_sprite},
398 {
"rooms_sprite", &entry.usage.rooms_sprite}};
399 for (
const auto& [key, ids] : lists) {
410 return absl::FailedPreconditionError(
411 "This ROM uses the Japanese graphics pointer tables (header byte "
412 "$7FD9 = 0); the gfx commands support US-table ROMs only");
414 return absl::OkStatus();
422 for (
const char* flag : {
"project",
"manifest"}) {
423 if (parser.
GetString(flag).has_value()) {
424 return absl::InvalidArgumentError(absl::StrFormat(
425 "--%s is not read here; pass --project-context <path.yaze> so the "
426 "project's hack manifest (graphics_sheet_regions) sets where a "
427 "grown sheet may move",
431 return absl::OkStatus();
435 "no --project-context given, so no hack manifest free space was used";
450 ASSIGN_OR_RETURN(
const int count, ParseIntArg(parser,
"count", 16 - block));
451 std::optional<zelda3::RoomSheetPaletteContext> context;
453 ResolvePalette(*rom, parser, sheet, &context));
457 const std::string path = *parser.
GetString(
"png");
458 const auto json_path = parser.
GetString(
"json");
459 std::vector<std::string> outputs = {path};
460 if (json_path.has_value())
461 outputs.push_back(*json_path);
463 nlohmann::json metadata = {{
"sheet", sheet}, {
"png", path},
464 {
"first_block", block}, {
"block_count", count},
465 {
"colors_rgb", palette}, {
"transparent_index", 0}};
466 if (context.has_value())
467 metadata[
"room_context"] = PaletteContextJson(*context);
469 if (json_path.has_value())
472 formatter.
AddField(
"sheet", absl::StrFormat(
"0x%02X", sheet));
475 absl::StrFormat(
"%d-%d", block, block + count - 1));
476 formatter.
AddField(
"width", std::min(count, 8) * 16);
477 formatter.
AddField(
"height", (count + 7) / 8 * 16);
478 formatter.
AddField(
"palette", PaletteText(palette));
479 if (
const auto half = SpritePaletteHalf(parser, sheet); half.has_value())
480 formatter.
AddField(
"palette_half", *half);
481 if (context.has_value()) {
482 formatter.
AddRawJsonField(
"room_context", metadata[
"room_context"].dump());
484 return absl::OkStatus();
491 const bool dry_run = parser.
HasFlag(
"dry-run");
492 const bool write = parser.
HasFlag(
"write");
493 if (dry_run == write) {
494 return absl::InvalidArgumentError(
"Pass exactly one of --dry-run, --write");
496 if (write && !parser.
GetString(
"out").has_value()) {
497 return absl::InvalidArgumentError(
498 "--write needs --out <rom file>; the input ROM is never overwritten");
500 return absl::OkStatus();
509 return absl::PermissionDeniedError(
510 "Graphics import is disabled by the project's read-only policy");
515 std::optional<zelda3::RoomSheetPaletteContext> context;
517 ResolvePalette(*rom, parser, sheet, &context));
518 if (context.has_value()) {
520 PaletteContextJson(*context).dump());
523 const std::string png_path = *parser.
GetString(
"png");
524 const auto diff_png = parser.
GetString(
"diff-png");
525 std::vector<std::string> paths = {png_path};
529 paths.push_back(*parser.
GetString(
"out"));
530 if (diff_png.has_value())
531 paths.push_back(*diff_png);
537 if (diff_png.has_value()) {
543 bool reserved =
false;
544 const auto shared_by = SheetSharedBy(
553 Rom simulated = *rom;
557 formatter.
AddField(
"sheet", absl::StrFormat(
"0x%02X", sheet));
558 formatter.
AddField(
"png", png_path);
559 formatter.
AddField(
"changed_blocks",
560 absl::StrJoin(imported.changed_blocks,
" "));
562 static_cast<int>(imported.changed_tiles.size()));
563 formatter.
AddField(
"sheet_byte_ranges", RangeText(imported.changed_ranges));
564 if (
const auto half = SpritePaletteHalf(parser, sheet); half.has_value())
565 formatter.
AddField(
"palette_half", *half);
566 if (shared_by.ok()) {
569 formatter.
AddField(
"shared_by_error",
570 std::string(shared_by.status().message()));
573 formatter.
AddField(
"reserved_sheet",
574 "The project reserves this sheet; check its owner "
577 if (diff_png.has_value())
578 formatter.
AddField(
"diff_png", *diff_png);
582 formatter.
AddField(
"write_status", std::string(written.status().message()));
584 formatter.
AddField(
"hint", kNoProjectHint);
587 formatter.
AddField(
"mode",
"dry-run");
588 return written.status();
590 formatter.
AddField(
"write_status",
"ok");
591 const auto rom_diff =
597 formatter.
AddField(
"rom_pc", absl::StrFormat(
"0x%06X", written->new_pc));
599 absl::StrFormat(
"%zu -> %zu", written->old_stored_size,
600 written->new_stored_size));
601 formatter.
AddField(
"rom_bytes_changed",
602 static_cast<int>(rom_diff.total_bytes_changed));
605 absl::StrJoin(rom_diff.ranges,
" ",
606 [](std::string* out,
const auto& range) {
607 out->append(absl::StrFormat(
"0x%06X-0x%06X", range.first,
611 if (parser.
HasFlag(
"dry-run")) {
612 formatter.
AddField(
"mode",
"dry-run");
613 return absl::OkStatus();
615 const std::string out = *parser.
GetString(
"out");
618 std::filesystem::equivalent(out, rom->
filename(), ec)) {
619 return absl::InvalidArgumentError(
620 "--out is the input ROM; write to a new file");
623 formatter.
AddField(
"mode",
"write");
625 return absl::OkStatus();
631 const bool bundle = parser.
GetString(
"out-dir").has_value();
632 if (bundle == parser.
GetString(
"png").has_value()) {
633 return absl::InvalidArgumentError(
"Pass exactly one of --png or --out-dir");
635 if (bundle && (parser.
GetString(
"palette").has_value() ||
637 return absl::InvalidArgumentError(
638 "--out-dir resolves each slot's palette and writes manifest.json; "
639 "do not pass --palette or --json");
641 return absl::OkStatus();
649 if (
const auto directory = parser.
GetString(
"out-dir");
650 directory.has_value()) {
651 return ExportRoomBundle(*rom, context, *directory, formatter);
653 const auto& set = context.set;
656 const std::string png_path = *parser.
GetString(
"png");
657 std::vector<std::string> outputs = {png_path};
658 if (
const auto json_path = parser.
GetString(
"json"); json_path.has_value()) {
659 outputs.push_back(*json_path);
665 const auto all_sets =
667 std::vector<int> same_main, same_room_blockset;
668 for (
const auto& other : all_sets) {
669 if (other.main_blockset == set.main_blockset) {
670 same_main.push_back(other.room);
672 if (other.header_blockset == set.header_blockset) {
673 same_room_blockset.push_back(other.room);
676 nlohmann::json slots = nlohmann::json::array();
677 for (
int slot = 0; slot < 8; ++slot) {
678 std::vector<int> rooms;
679 for (
const auto& other : all_sets) {
680 if (std::find(other.sheets.begin(), other.sheets.end(),
681 set.sheets[slot]) != other.sheets.end()) {
682 rooms.push_back(other.room);
685 slots.push_back({{
"slot", slot},
686 {
"sheet", absl::StrFormat(
"0x%02X", set.sheets[slot])},
687 {
"source", set.from_room_blockset[slot] ?
"room_blockset"
689 {
"png_y", slot * 32},
690 {
"rooms_using_sheet", rooms}});
692 const std::string rom_path = rom->
filename();
693 const std::string import_command = absl::StrFormat(
694 "z3ed gfx-room-import --rom %s --room 0x%03X --png %s --row %d --dry-run",
695 rom_path, set.room, png_path, context.row);
696 nlohmann::json sidecar = {
697 {
"room", absl::StrFormat(
"0x%03X", set.room)},
698 {
"main_blockset", set.main_blockset},
699 {
"room_blockset", set.header_blockset},
700 {
"palette_row", context.row},
701 {
"palette", PaletteText(context.palette)},
702 {
"palette_mode",
"uniform_palette_all_slots"},
705 {
"rooms_with_same_main_blockset", same_main},
706 {
"rooms_with_same_room_blockset", same_room_blockset},
707 {
"reimport", import_command},
709 if (
auto json_path = parser.
GetString(
"json"); json_path.has_value()) {
710 const std::string text = sidecar.dump(1) +
"\n";
712 *json_path, std::vector<uint8_t>(text.begin(), text.end())));
714 for (
const auto& [key, value] : sidecar.items()) {
717 return absl::OkStatus();
724 const bool dry_run = parser.
HasFlag(
"dry-run");
725 const bool write = parser.
HasFlag(
"write");
726 if (dry_run == write) {
727 return absl::InvalidArgumentError(
"Pass exactly one of --dry-run, --write");
729 if (write && !parser.
GetString(
"out").has_value()) {
730 return absl::InvalidArgumentError(
731 "--write needs --out <rom file>; the input ROM is never overwritten");
733 return absl::OkStatus();
742 return absl::PermissionDeniedError(
743 "Graphics import is disabled by the project's read-only policy");
747 const std::string png_path = *parser.
GetString(
"png");
758 Rom simulated = *rom;
759 const auto all_sets =
761 nlohmann::json sheets = nlohmann::json::array();
763 std::vector<int> affected_rooms;
764 for (
const auto&
import : imports) {
766 import.result.snes_3bpp, policy);
767 nlohmann::json entry = {
768 {
"sheet", absl::StrFormat(
"0x%02X",
import.sheet)},
769 {
"slots",
import.slots},
770 {
"changed_blocks",
import.result.changed_blocks},
771 {
"changed_tiles",
import.result.changed_tiles.size()},
778 entry[
"stored_bytes"] = absl::StrFormat(
779 "%zu -> %zu", written->old_stored_size, written->new_stored_size);
781 entry[
"write_status"] = std::string(written.status().message());
782 if (refusal.empty()) {
783 refusal = entry[
"write_status"];
786 for (
const auto& other : all_sets) {
787 if (std::find(other.sheets.begin(), other.sheets.end(),
import.sheet) !=
788 other.sheets.end()) {
789 affected_rooms.push_back(other.room);
792 sheets.push_back(std::move(entry));
794 std::sort(affected_rooms.begin(), affected_rooms.end());
795 affected_rooms.erase(
796 std::unique(affected_rooms.begin(), affected_rooms.end()),
797 affected_rooms.end());
799 formatter.
AddField(
"room", absl::StrFormat(
"0x%03X", context.set.room));
801 formatter.
AddField(
"rooms_affected", HexIds(affected_rooms));
802 formatter.
AddField(
"write_status", refusal.empty() ?
"ok" : refusal);
803 if (!refusal.empty() &&
project_ ==
nullptr) {
804 formatter.
AddField(
"hint", kNoProjectHint);
806 const auto rom_diff =
808 formatter.
AddField(
"rom_bytes_changed",
809 static_cast<int>(rom_diff.total_bytes_changed));
811 formatter.
AddField(
"mode",
"dry-run");
813 if (!refusal.empty()) {
814 return absl::FailedPreconditionError(refusal);
817 return absl::OkStatus();
818 const std::string out = *parser.
GetString(
"out");
821 std::filesystem::equivalent(out, rom->
filename(), ec)) {
822 return absl::InvalidArgumentError(
823 "--out is the input ROM; write to a new file");
826 formatter.
AddField(
"mode",
"write");
828 return absl::OkStatus();
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
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status ValidateArgs(const resources::ArgumentParser &parser) override
Validate command arguments.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
bool HasFlag(const std::string &name) const
Check if a flag is present.
absl::Status RequireArgs(const std::vector< std::string > &required) const
Validate that required arguments are present.
project::YazeProject * project_
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::string HexIds(const std::vector< int > &ids)
absl::Status ExportRoomBundle(Rom &rom, RoomContext &context, const std::string &directory, resources::OutputFormatter &formatter)
absl::Status RejectUnreadProjectFlags(const resources::ArgumentParser &parser)
std::string RangeText(const std::vector< std::pair< int, int > > &ranges)
std::string PaletteText(const zelda3::SheetPalette &palette)
absl::StatusOr< zelda3::SheetPalette > ResolvePalette(Rom &rom, const resources::ArgumentParser &parser, int sheet=-1, std::optional< zelda3::RoomSheetPaletteContext > *context=nullptr)
absl::StatusOr< uint16_t > ParseSheet(const resources::ArgumentParser &parser)
absl::StatusOr< nlohmann::json > SheetSharedBy(Rom &rom, uint16_t sheet, const zelda3::GfxSheetInventoryOptions &options, bool *reserved)
absl::Status WriteJson(const std::string &path, const nlohmann::json &value)
nlohmann::json PaletteContextJson(const zelda3::RoomSheetPaletteContext &context)
absl::StatusOr< int > ParseIntArg(const resources::ArgumentParser &parser, const char *name, int fallback)
constexpr char kNoProjectHint[]
absl::StatusOr< RoomContext > LoadRoomContext(Rom &rom, const resources::ArgumentParser &parser)
absl::StatusOr< int > ParseNumber(absl::string_view text, const char *what)
absl::Status ValidateExportPaths(const Rom &rom, const std::vector< std::string > &outputs)
absl::Status RequireUsGfxTables(const Rom &rom)
std::optional< std::string > SpritePaletteHalf(const resources::ArgumentParser &parser, uint16_t sheet)
absl::StatusOr< zelda3::GfxSheetWritePolicy > BuildGfxSheetWritePolicy(const HackManifest &manifest)
zelda3::GfxSheetInventoryOptions BuildGfxSheetInventoryOptions(const project::YazeProject *project)
DiffSummary ComputeDiffRanges(const std::vector< uint8_t > &before, const std::vector< uint8_t > &after)
absl::Status WriteBinaryFile(const std::string &path, const std::vector< uint8_t > &bytes)
absl::StatusOr< std::vector< uint8_t > > ReadBinaryFile(const std::string &path)
absl::StatusOr< PngImage > DecodePng(const std::vector< uint8_t > &bytes)
bool ParseHexString(absl::string_view str, uint64_t *out)
constexpr int kNumDungeonRooms
std::vector< RoomBackgroundSet > ResolveAllRoomBackgroundSets(const Rom &rom, const GameData &data)
All 296 rooms' background sets (header bytes read from the ROM).
absl::StatusOr< SheetPalette > RoomSpriteSheetPalette(Rom &rom, GameData &data, int room, int row, bool high_half)
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)
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.
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
std::map< int, OverworldAreaGfxInfo > CollectOverworldAreaGfx(Rom &rom, GameData *game_data)
RoomBackgroundSet ResolveRoomBackgroundSet(const GameData &data, int room, uint8_t header_blockset)
absl::StatusOr< GfxSheetInventory > BuildGfxSheetInventory(const Rom &rom, const std::map< int, OverworldAreaGfxInfo > &areas, const std::map< int, RoomGfxInfo > &rooms, const GfxSheetInventoryOptions &options)
bool IsHighHalfSpriteSheet(int sheet)
std::map< int, RoomGfxInfo > CollectRoomGfx(const Rom &rom)
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).
absl::StatusOr< GfxSheetWriteResult > WriteGfxSheet(Rom &rom, uint16_t sheet_id, const std::vector< uint8_t > &snes_3bpp, const GfxSheetWritePolicy &policy, const GfxSheetPointerTables &tables)
Writes one edited 3bpp sheet to the ROM buffer.
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
constexpr size_t kGfxSheetCount
bool UsesJapaneseGfxSheetTables(const Rom &rom)
std::array< SheetColor, 8 > SheetPalette
Colors for 3bpp indices 0-7. Index 0 is exported as transparent.
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)
absl::StatusOr< std::vector< uint8_t > > ExportSheetDiffPng(const std::vector< uint8_t > &before_3bpp, const std::vector< uint8_t > &after_3bpp, const SheetPalette &palette)
absl::StatusOr< std::vector< RoomSheetImport > > ImportRoomBackgroundPng(const Rom &rom, const RoomBackgroundSet &set, const util::PngImage &png, const SheetPalette &palette)
#define RETURN_IF_ERROR(expr)
zelda3::SheetPalette palette
zelda3::RoomBackgroundSet set
Rules a sheet write must follow.
std::array< uint8_t, 8 > sheets
std::array< bool, 8 > from_room_blockset
RoomEntranceSource source
RoomMainBlocksetSource source
std::array< uint8_t, 8 > cgram_indices
RoomDefaultEntrance entrance
RoomMainBlocksetResolution main
RoomBackgroundSet background