38inline absl::StatusOr<zelda3::OverworldGraphicsPreview>
LoadVersion2(
39 const nlohmann::json& document,
const std::string& filename) {
40 if (!document.at(
"resources").is_object() ||
41 !document.at(
"profiles").is_object() ||
42 !document.at(
"bindings").is_array()) {
43 return absl::InvalidArgumentError(
"Invalid graphics preview catalog");
45 std::map<std::string, std::vector<uint8_t>> resources;
46 for (
const auto& [
id, resource] : document.at(
"resources").items()) {
47 if (
id.empty() || resource.at(
"format") !=
"snes4bpp") {
48 return absl::InvalidArgumentError(
49 "Graphics preview resource needs an ID and snes4bpp format");
54 return count.status();
55 if (*count == 0 || *count > 0x8000 || *count % 32 != 0) {
56 return absl::InvalidArgumentError(
57 "Invalid graphics preview resource size");
59 auto expected = resource.at(
"sha256").get<std::string>();
60 if (expected.size() != 64 ||
61 !std::all_of(expected.begin(), expected.end(),
62 [](
unsigned char c) { return std::isxdigit(c); })) {
63 return absl::InvalidArgumentError(
64 "Invalid graphics preview resource SHA256");
66 std::transform(expected.begin(), expected.end(), expected.begin(),
67 [](
unsigned char c) { return std::tolower(c); });
68 const auto asset_path = resource.at(
"path").get<std::string>();
69 if (asset_path.empty()) {
70 return absl::InvalidArgumentError(
71 "Graphics preview resource path is empty");
74 std::filesystem::path(filename).parent_path() / asset_path;
76 const auto size = std::filesystem::file_size(path, ec);
78 return absl::NotFoundError(
"Missing graphics preview asset: " +
81 if (size !=
static_cast<size_t>(*count)) {
82 return absl::InvalidArgumentError(
83 "Graphics preview asset has wrong byte count: " + path.string());
85 std::vector<uint8_t> bytes(*count);
86 std::ifstream asset(path, std::ios::binary);
87 if (!asset.read(
reinterpret_cast<char*
>(bytes.data()), bytes.size())) {
88 return absl::DataLossError(
"Cannot read graphics preview asset: " +
92 {
reinterpret_cast<const char*
>(bytes.data()), bytes.size()}) !=
94 return absl::DataLossError(
"Graphics preview resource SHA256 mismatch: " +
97 resources.emplace(
id, std::move(bytes));
99 std::map<std::string, zelda3::AreaGraphicsPreview> profiles;
100 for (
const auto& [
id, profile] : document.at(
"profiles").items()) {
101 if (
id.empty() || !profile.at(
"transfers").is_array() ||
102 profile.at(
"transfers").empty()) {
103 return absl::InvalidArgumentError(
104 "Graphics preview profile needs transfers");
106 auto base =
NonnegativeInt(profile.at(
"bg_character_base_word"),
"BG base");
108 return base.status();
109 auto& tiles = profiles[id];
110 for (
const auto& transfer : profile.at(
"transfers")) {
111 const auto resource_id = transfer.at(
"resource_id").get<std::string>();
112 const auto resource = resources.find(resource_id);
113 if (resource == resources.end()) {
114 return absl::InvalidArgumentError(
115 "Unknown graphics preview resource: " + resource_id);
118 transfer.value(
"source_offset", nlohmann::json(0)),
"source_offset");
124 return offset.status();
126 return count.status();
129 if (*offset % 32 != 0 || *count == 0 || *count % 32 != 0 ||
130 static_cast<size_t>(*offset) > resource->second.size() ||
131 static_cast<size_t>(*count) > resource->second.size() - *offset) {
132 return absl::InvalidArgumentError(
133 "Invalid graphics preview resource slice");
136 std::span<const uint8_t>(resource->second).subspan(*offset, *count),
139 return decoded.status();
140 for (
const auto& [character, pixels] : *decoded) {
141 if (!tiles.emplace(character, pixels).second) {
142 return absl::InvalidArgumentError(
143 "Overlapping graphics preview transfers");
149 for (
const auto& binding : document.at(
"bindings")) {
150 const auto profile_id = binding.at(
"profile_id").get<std::string>();
151 const auto profile = profiles.find(profile_id);
152 if (profile == profiles.end()) {
153 return absl::InvalidArgumentError(
"Unknown graphics preview profile: " +
156 if (!binding.at(
"parent_maps").is_array() ||
157 binding.at(
"parent_maps").empty()) {
158 return absl::InvalidArgumentError(
159 "Graphics preview binding needs parent_maps");
161 for (
const auto& value : binding.at(
"parent_maps")) {
164 return parent.status();
165 if (*parent >= 160 || !result.emplace(*parent, profile->second).second) {
166 return absl::InvalidArgumentError(
167 "Invalid or duplicate graphics preview parent_map");