yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
gfx_sheet_png.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <map>
5#include <string>
6
7#include "absl/status/status.h"
8#include "absl/strings/str_format.h"
9#include "absl/strings/str_join.h"
11#include "util/macro.h"
12#include "zelda3/dungeon/room.h"
13#include "zelda3/game_data.h"
16
17namespace yaze::zelda3 {
18namespace {
19
20constexpr int kSheetWidth = 128;
21constexpr int kSheetHeight = 32;
22constexpr int kBlockSize = 16;
23
24std::pair<int, int> BlockOrigin(int block) {
25 return {(block % 8) * kBlockSize, (block / 8) * kBlockSize};
26}
27
28SheetColor ToSheetColor(const SDL_Color& color) {
29 return {color.r, color.g, color.b};
30}
31
32absl::StatusOr<Room> LoadRoomWithPalettes(Rom& rom, GameData& data, int room) {
33 if (room < 0 || room >= kNumberOfRooms) {
34 return absl::InvalidArgumentError(
35 absl::StrFormat("Room 0x%X is out of range", room));
36 }
37 if (data.palette_groups.dungeon_main.size() == 0) {
38 return absl::FailedPreconditionError("Dungeon palettes are not loaded");
39 }
40 Room loaded = LoadRoomHeaderFromRom(&rom, room);
41 loaded.SetGameData(&data);
42 return loaded;
43}
44
45} // namespace
46
48 SheetPalette palette;
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};
52 }
53 return palette;
54}
55
56absl::StatusOr<SheetPalette> RoomBackgroundSheetPalette(Rom& rom,
57 GameData& data,
58 int room, int row) {
59 if (row < 0 || row > 7) {
60 return absl::InvalidArgumentError("Background palette row must be 0-7");
61 }
62 ASSIGN_OR_RETURN(Room loaded, LoadRoomWithPalettes(rom, data, room));
63 const int palette_id = loaded.ResolveDungeonPaletteId();
65 data.palette_groups.dungeon_main.palette_ref(palette_id), &data);
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));
70 }
71 SheetPalette palette;
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)};
76 }
77 return palette;
78}
79
80bool IsHighHalfSpriteSheet(int sheet) {
81 switch (sheet - kSpriteSheetBase) {
82 case 0x52:
83 case 0x53:
84 case 0x5A:
85 case 0x5B:
86 case 0x5C:
87 case 0x5E:
88 case 0x5F:
89 return true;
90 default:
91 return false;
92 }
93}
94
95absl::StatusOr<SheetPalette> RoomSpriteSheetPalette(Rom& rom, GameData& data,
96 int room, int row,
97 bool high_half) {
98 if (row < 0 || row > 7) {
99 return absl::InvalidArgumentError("Sprite palette row must be 0-7");
100 }
101 ASSIGN_OR_RETURN(Room loaded, LoadRoomWithPalettes(rom, data, room));
102 const auto cgram = BuildDungeonSpriteRenderPalette(loaded, &data);
103 SheetPalette palette;
104 for (int i = 0; i < 8; ++i) {
105 palette[i] = ToSheetColor(cgram[(8 + row) * 16 + (high_half ? 8 : 0) + i]);
106 }
107 return palette;
108}
109
110absl::StatusOr<std::vector<uint8_t>> ExportSheetBlocksPng(
111 const std::vector<uint8_t>& snes_3bpp, const SheetPalette& palette,
112 int first_block, int block_count) {
113 if (snes_3bpp.size() != kGfxSheet3bppBytes) {
114 return absl::InvalidArgumentError("A 3bpp sheet is 0x600 bytes");
115 }
116 if (first_block < 0 || block_count <= 0 ||
117 first_block + block_count > kGfxSheetBlockCount) {
118 return absl::InvalidArgumentError(
119 absl::StrFormat("Blocks %d-%d are outside the sheet's 16 blocks",
120 first_block, first_block + block_count - 1));
121 }
122 const auto sheet = gfx::SnesTo8bppSheet(snes_3bpp, 3);
123 const int columns = std::min(block_count, 8);
124 const int rows = (block_count + 7) / 8;
125 const int width = columns * kBlockSize;
126 const int height = rows * kBlockSize;
127 std::vector<uint8_t> indices(static_cast<size_t>(width) * height, 0);
128 for (int k = 0; k < block_count; ++k) {
129 const auto [sx, sy] = BlockOrigin(first_block + k);
130 const int dx = (k % 8) * kBlockSize;
131 const int dy = (k / 8) * kBlockSize;
132 for (int y = 0; y < kBlockSize; ++y) {
133 for (int x = 0; x < kBlockSize; ++x) {
134 indices[(dy + y) * width + dx + x] =
135 sheet[(sy + y) * kSheetWidth + sx + x] & 0x07;
136 }
137 }
138 }
139 std::vector<std::array<uint8_t, 4>> png_palette;
140 for (int i = 0; i < 8; ++i) {
141 png_palette.push_back({palette[i][0], palette[i][1], palette[i][2],
142 static_cast<uint8_t>(i == 0 ? 0 : 255)});
143 }
144 return util::EncodeIndexedPng(width, height, indices, png_palette);
145}
146
147absl::StatusOr<std::vector<uint8_t>> ExportSheetDiffPng(
148 const std::vector<uint8_t>& before_3bpp,
149 const std::vector<uint8_t>& after_3bpp, const SheetPalette& palette) {
150 if (before_3bpp.size() != kGfxSheet3bppBytes ||
151 after_3bpp.size() != kGfxSheet3bppBytes) {
152 return absl::InvalidArgumentError("A 3bpp sheet is 0x600 bytes");
153 }
154 constexpr int kPanel = kSheetWidth * kSheetHeight;
155 constexpr uint8_t kHighlight = 8;
156 const auto before = gfx::SnesTo8bppSheet(before_3bpp, 3);
157 const auto after = gfx::SnesTo8bppSheet(after_3bpp, 3);
158 std::vector<uint8_t> indices(static_cast<size_t>(kPanel) * 3, 0);
159 for (int i = 0; i < kPanel; ++i) {
160 indices[i] = before[i] & 0x07;
161 indices[kPanel + i] = after[i] & 0x07;
162 if (indices[i] != indices[kPanel + i])
163 indices[2 * kPanel + i] = kHighlight;
164 }
165 std::vector<std::array<uint8_t, 4>> png_palette;
166 for (int i = 0; i < 8; ++i) {
167 png_palette.push_back({palette[i][0], palette[i][1], palette[i][2],
168 static_cast<uint8_t>(i == 0 ? 0 : 255)});
169 }
170 png_palette.push_back({255, 0, 255, 255});
171 return util::EncodeIndexedPng(kSheetWidth, kSheetHeight * 3, indices,
172 png_palette);
173}
174
175absl::StatusOr<SheetImportResult> ImportSheetBlocksPng(
176 const std::vector<uint8_t>& snes_3bpp, const util::PngImage& png,
177 int first_block, const SheetPalette& palette) {
178 if (snes_3bpp.size() != kGfxSheet3bppBytes) {
179 return absl::InvalidArgumentError("A 3bpp sheet is 0x600 bytes");
180 }
181 if (png.width % kBlockSize != 0 || png.height % kBlockSize != 0) {
182 return absl::InvalidArgumentError(absl::StrFormat(
183 "PNG is %dx%d; width and height must be multiples of 16", png.width,
184 png.height));
185 }
186 const int columns = png.width / kBlockSize;
187 const int block_count = columns * (png.height / kBlockSize);
188 if (first_block < 0 || first_block + block_count > kGfxSheetBlockCount) {
189 return absl::InvalidArgumentError(absl::StrFormat(
190 "The PNG holds %d 16x16 blocks; starting at block %d it runs past "
191 "block 15",
192 block_count, first_block));
193 }
194
195 // Resolve every pixel to a 3bpp index before touching the sheet.
196 std::vector<uint8_t> values(static_cast<size_t>(png.width) * png.height);
197 std::vector<std::string> offenders;
198 int bad_pixels = 0;
199 for (int y = 0; y < png.height; ++y) {
200 for (int x = 0; x < png.width; ++x) {
201 const size_t i = static_cast<size_t>(y) * png.width + x;
202 int value = -1;
203 std::string seen;
204 if (png.indexed) {
205 value = png.indices[i];
206 seen = absl::StrFormat("index %d", value);
207 if (value > 7) {
208 value = -1;
209 }
210 } else {
211 const uint8_t* p = &png.rgba[i * 4];
212 seen = absl::StrFormat("#%02X%02X%02X", p[0], p[1], p[2]);
213 if (p[3] == 0) {
214 value = 0;
215 } else if (p[3] == 255) {
216 for (int c = 0; c < 8; ++c) {
217 if (palette[c][0] == p[0] && palette[c][1] == p[1] &&
218 palette[c][2] == p[2]) {
219 value = c;
220 break;
221 }
222 }
223 }
224 }
225 if (value < 0) {
226 ++bad_pixels;
227 if (offenders.size() < 8) {
228 offenders.push_back(absl::StrFormat("(%d,%d) %s", x, y, seen));
229 }
230 continue;
231 }
232 values[i] = static_cast<uint8_t>(value);
233 }
234 }
235 if (bad_pixels > 0) {
236 return absl::InvalidArgumentError(absl::StrFormat(
237 "%d pixel(s) are not 3bpp indices 0-7 of the sheet palette; nothing "
238 "was imported. First: %s",
239 bad_pixels, absl::StrJoin(offenders, ", ")));
240 }
241
242 const auto old_sheet = gfx::SnesTo8bppSheet(snes_3bpp, 3);
243 std::vector<uint8_t> sheet = old_sheet;
244 for (int k = 0; k < block_count; ++k) {
245 const auto [dx, dy] = BlockOrigin(first_block + k);
246 const int sx = (k % columns) * kBlockSize;
247 const int sy = (k / columns) * kBlockSize;
248 for (int y = 0; y < kBlockSize; ++y) {
249 for (int x = 0; x < kBlockSize; ++x) {
250 sheet[(dy + y) * kSheetWidth + dx + x] =
251 values[(sy + y) * png.width + sx + x];
252 }
253 }
254 }
255
256 SheetImportResult result;
257 result.snes_3bpp = gfx::IndexedToSnesSheet(sheet, 3);
258 result.snes_3bpp.resize(kGfxSheet3bppBytes);
259 for (int block = 0; block < kGfxSheetBlockCount; ++block) {
260 const auto [bx, by] = BlockOrigin(block);
261 bool changed = false;
262 for (int y = 0; y < kBlockSize && !changed; ++y) {
263 for (int x = 0; x < kBlockSize; ++x) {
264 const size_t i = (by + y) * kSheetWidth + bx + x;
265 if ((sheet[i] & 7) != (old_sheet[i] & 7)) {
266 changed = true;
267 break;
268 }
269 }
270 }
271 if (changed) {
272 result.changed_blocks.push_back(block);
273 }
274 }
275 for (int tile = 0; tile < 64; ++tile) {
276 if (!std::equal(result.snes_3bpp.begin() + tile * 24,
277 result.snes_3bpp.begin() + tile * 24 + 24,
278 snes_3bpp.begin() + tile * 24)) {
279 result.changed_tiles.push_back(tile);
280 }
281 }
282 for (size_t i = 0; i < snes_3bpp.size();) {
283 if (result.snes_3bpp[i] == snes_3bpp[i]) {
284 ++i;
285 continue;
286 }
287 size_t end = i;
288 while (end < snes_3bpp.size() && result.snes_3bpp[end] != snes_3bpp[end]) {
289 ++end;
290 }
291 result.changed_ranges.emplace_back(static_cast<int>(i),
292 static_cast<int>(end));
293 i = end;
294 }
295 return result;
296}
297
299 uint8_t header_blockset) {
301 set.room = room;
302 set.header_blockset = header_blockset;
303 set.main_blockset =
304 ResolveRoomMainBlockset(room, header_blockset, 0xFF, data).main_blockset;
305 for (int i = 0; i < 8; ++i) {
306 set.sheets[i] = data.main_blockset_ids[set.main_blockset][i];
307 if (i >= 3 && i <= 6 && header_blockset < data.room_blockset_ids.size()) {
308 const uint8_t room_sheet = data.room_blockset_ids[header_blockset][i - 3];
309 if (room_sheet != 0) {
310 set.sheets[i] = room_sheet;
311 set.from_room_blockset[i] = true;
312 }
313 }
314 }
315 return set;
316}
317
318absl::StatusOr<RoomSheetPaletteContext> ResolveRoomSheetPalette(
319 Rom& rom, GameData& data, int room, int slot, int row) {
320 if (slot < 0 || slot >= 8 || row < 0 || row >= 8) {
321 return absl::InvalidArgumentError("Background slot and row must be 0-7");
322 }
323 ASSIGN_OR_RETURN(Room loaded, LoadRoomWithPalettes(rom, data, room));
325 result.background = ResolveRoomBackgroundSet(data, room, loaded.blockset());
326 result.main = ResolveRoomMainBlockset(room, loaded.blockset(), 0xFF, data);
329 result.entrance = data.room_default_entrances[room];
330 }
331 result.slot = slot;
332 result.row = row;
333 result.palette_offset =
337 loaded.ResolveDungeonPaletteId()),
338 &data);
339 if (row >= static_cast<int>(group.size()) ||
340 group.palette_ref(row).size() < 16) {
341 return absl::FailedPreconditionError(
342 "Room background palette is incomplete");
343 }
344 for (int index = 0; index < 8; ++index) {
345 const int column = index == 0 ? 0 : index + result.palette_offset;
346 result.cgram_indices[index] = row * 16 + column;
347 const auto rgb = group.palette_ref(row)[column].rgb();
348 result.colors[index] = {static_cast<uint8_t>(rgb.x),
349 static_cast<uint8_t>(rgb.y),
350 static_cast<uint8_t>(rgb.z)};
351 }
352 return result;
353}
354
355std::vector<RoomBackgroundSet> ResolveAllRoomBackgroundSets(
356 const Rom& rom, const GameData& data) {
357 std::vector<RoomBackgroundSet> sets;
358 for (const auto& [room, info] : CollectRoomGfx(rom)) {
359 sets.push_back(ResolveRoomBackgroundSet(data, room, info.blockset));
360 }
361 return sets;
362}
363
364absl::StatusOr<std::vector<uint8_t>> ExportRoomBackgroundPng(
365 const Rom& rom, const RoomBackgroundSet& set, const SheetPalette& palette) {
366 constexpr int kHeight = kSheetHeight * 8;
367 std::vector<uint8_t> indices(static_cast<size_t>(kSheetWidth) * kHeight, 0);
368 for (int slot = 0; slot < 8; ++slot) {
369 const uint16_t sheet = set.sheets[slot];
370 if (sheet >= kGfxSheetCount ||
372 continue;
373 }
374 ASSIGN_OR_RETURN(const auto data, ReadGfxSheetData(rom, sheet));
375 const auto pixels = gfx::SnesTo8bppSheet(data, 3);
376 for (int y = 0; y < kSheetHeight; ++y) {
377 for (int x = 0; x < kSheetWidth; ++x) {
378 indices[(slot * kSheetHeight + y) * kSheetWidth + x] =
379 pixels[y * kSheetWidth + x] & 0x07;
380 }
381 }
382 }
383 std::vector<std::array<uint8_t, 4>> png_palette;
384 for (int i = 0; i < 8; ++i) {
385 png_palette.push_back({palette[i][0], palette[i][1], palette[i][2],
386 static_cast<uint8_t>(i == 0 ? 0 : 255)});
387 }
388 return util::EncodeIndexedPng(kSheetWidth, kHeight, indices, png_palette);
389}
390
391absl::StatusOr<std::vector<RoomSheetImport>> ImportRoomBackgroundPng(
392 const Rom& rom, const RoomBackgroundSet& set, const util::PngImage& png,
393 const SheetPalette& palette) {
394 if (png.width != kSheetWidth || png.height != kSheetHeight * 8) {
395 return absl::InvalidArgumentError(absl::StrFormat(
396 "A room background PNG is 128x256 (8 stacked sheets); got %dx%d",
397 png.width, png.height));
398 }
399 std::vector<RoomSheetImport> imports;
400 std::map<uint16_t, std::pair<int, std::vector<uint8_t>>> seen;
401 for (int slot = 0; slot < 8; ++slot) {
402 const uint16_t sheet = set.sheets[slot];
403 // Crop this slot's 128x32 band.
404 util::PngImage band;
405 band.width = kSheetWidth;
406 band.height = kSheetHeight;
407 band.indexed = png.indexed;
408 band.palette = png.palette;
409 const size_t begin = static_cast<size_t>(slot) * kSheetHeight * kSheetWidth;
410 const size_t count = static_cast<size_t>(kSheetHeight) * kSheetWidth;
411 if (png.indexed) {
412 band.indices.assign(png.indices.begin() + begin,
413 png.indices.begin() + begin + count);
414 } else {
415 band.rgba.assign(png.rgba.begin() + begin * 4,
416 png.rgba.begin() + (begin + count) * 4);
417 }
418
419 const bool writable =
420 sheet < kGfxSheetCount &&
422 std::vector<uint8_t> current(kGfxSheet3bppBytes, 0);
423 if (writable) {
424 ASSIGN_OR_RETURN(current, ReadGfxSheetData(rom, sheet));
425 }
426 auto result = ImportSheetBlocksPng(current, band, 0, palette);
427 if (!result.ok()) {
428 return absl::Status(result.status().code(),
429 absl::StrFormat("Slot %d (sheet 0x%02X): %s", slot,
430 sheet, result.status().message()));
431 }
432 if (!writable) {
433 if (!result->changed_blocks.empty()) {
434 return absl::FailedPreconditionError(absl::StrFormat(
435 "Slot %d shows 2bpp sheet 0x%02X, which cannot be edited here",
436 slot, sheet));
437 }
438 continue;
439 }
440 // Every slot showing the same sheet must end up with the same pixels,
441 // including slots left unedited.
442 auto first = seen.find(sheet);
443 if (first != seen.end()) {
444 if (first->second.second != result->snes_3bpp) {
445 return absl::InvalidArgumentError(absl::StrFormat(
446 "Sheet 0x%02X appears in slots %d and %d with different pixels",
447 sheet, first->second.first, slot));
448 }
449 } else {
450 seen.emplace(sheet, std::make_pair(slot, result->snes_3bpp));
451 }
452 if (result->changed_blocks.empty()) {
453 continue;
454 }
455 auto existing = std::find_if(
456 imports.begin(), imports.end(),
457 [sheet](const RoomSheetImport& other) { return other.sheet == sheet; });
458 if (existing != imports.end()) {
459 existing->slots.push_back(slot);
460 continue;
461 }
462 imports.push_back({sheet, {slot}, std::move(*result)});
463 }
464 return imports;
465}
466
467} // namespace yaze::zelda3
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:52
uint8_t blockset() const
Definition room.h:1015
int ResolveDungeonPaletteId() const
Definition room.cc:921
void SetGameData(GameData *data)
Definition room.h:1088
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)
Definition macro.h:62
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:133
std::vector< uint8_t > IndexedToSnesSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:204
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)
SheetColor ToSheetColor(const SDL_Color &color)
absl::StatusOr< Room > LoadRoomWithPalettes(Rom &rom, GameData &data, int room)
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< 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)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
Definition room.cc:680
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)
bool IsHighHalfSpriteSheet(int sheet)
std::map< int, RoomGfxInfo > CollectRoomGfx(const Rom &rom)
gfx::PaletteGroup BuildDungeonRenderPaletteGroupFromGameData(const gfx::SnesPalette &dungeon_palette, const GameData *game_data)
Definition room.cc:191
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).
constexpr uint16_t kSpriteSheetBase
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< 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
absl::StatusOr< std::vector< uint8_t > > ExportSheetDiffPng(const std::vector< uint8_t > &before_3bpp, const std::vector< uint8_t > &after_3bpp, const SheetPalette &palette)
std::array< SDL_Color, 256 > BuildDungeonSpriteRenderPalette(const Room &room, const GameData *game_data)
Definition room.cc:200
absl::StatusOr< std::vector< RoomSheetImport > > ImportRoomBackgroundPng(const Rom &rom, const RoomBackgroundSet &set, const util::PngImage &png, const SheetPalette &palette)
const SnesPalette & palette_ref(int i) const
A decoded PNG: palette indices for indexed files, RGBA otherwise.
Definition indexed_png.h:21
std::vector< std::array< uint8_t, 4 > > palette
Definition indexed_png.h:28
std::vector< uint8_t > indices
Definition indexed_png.h:26
std::vector< uint8_t > rgba
Definition indexed_png.h:30
std::vector< RoomDefaultEntrance > room_default_entrances
Definition game_data.h:118
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
Definition game_data.h:98
gfx::PaletteGroupMap palette_groups
Definition game_data.h:95
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
Definition game_data.h:97
std::array< uint8_t, 8 > sheets
std::array< bool, 8 > from_room_blockset
std::array< uint8_t, 8 > cgram_indices
RoomMainBlocksetResolution main
std::vector< std::pair< int, int > > changed_ranges
std::vector< uint8_t > snes_3bpp
std::vector< int > changed_tiles
std::vector< int > changed_blocks