yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
game_data.cc
Go to the documentation of this file.
1#include "zelda3/game_data.h"
2
3#include "absl/strings/str_format.h"
5#include "util/log.h"
6#include "util/macro.h"
8
9#ifdef __EMSCRIPTEN__
11#endif
12
13namespace yaze {
14namespace zelda3 {
15
16namespace {
17
18constexpr uint32_t kUncompressedSheetSize = 0x0800;
19// constexpr uint32_t kTile16Ptr = 0x78000;
20
21// Helper to get address from bytes
22uint32_t AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) {
23 return (bank << 16) | (high << 8) | low;
24}
25
26// Helper to convert SNES to PC address
27uint32_t SnesToPc(uint32_t snes_addr) {
28 return (snes_addr & 0x7FFF) | ((snes_addr & 0x7F0000) >> 1);
29}
30
31// Helper to convert PC to SNES address
32// uint32_t PcToSnes(uint32_t pc_addr) {
33// return ((pc_addr & 0x7FFF) | 0x8000) | ((pc_addr & 0x3F8000) << 1);
34// }
35
36// ============================================================================
37// Graphics Address Resolution
38// ============================================================================
39
65} // namespace
66
69uint32_t GetGraphicsAddress(const uint8_t* data, uint8_t addr, uint32_t ptr1,
70 uint32_t ptr2, uint32_t ptr3, size_t rom_size) {
71 if (ptr1 > UINT32_MAX - addr || ptr1 + addr >= rom_size ||
72 ptr2 > UINT32_MAX - addr || ptr2 + addr >= rom_size ||
73 ptr3 > UINT32_MAX - addr || ptr3 + addr >= rom_size) {
74 return static_cast<uint32_t>(rom_size);
75 }
76 return SnesToPc(AddressFromBytes(data[ptr1 + addr], data[ptr2 + addr],
77 data[ptr3 + addr]));
78}
79
80absl::Status LoadGameData(Rom& rom, GameData& data, const LoadOptions& options) {
81 data.Clear();
82
83 if (options.populate_metadata) {
84 RETURN_IF_ERROR(LoadMetadata(rom, data));
85 }
86
87 if (options.load_palettes) {
88 RETURN_IF_ERROR(LoadPalettes(rom, data));
89 }
90
91 if (options.load_gfx_groups) {
93 }
94
95 if (options.load_graphics) {
96 RETURN_IF_ERROR(LoadGraphics(rom, data));
97 }
98
99 if (options.expand_rom) {
100 if (rom.size() < 1048576 * 2) {
101 rom.Expand(1048576 * 2);
102 }
103 }
104
105 return absl::OkStatus();
106}
107
108absl::Status SaveGameData(Rom& rom, GameData& data) {
109 if (core::FeatureFlags::get().kSaveAllPalettes) {
110 // TODO: Implement SaveAllPalettes logic using Rom::WriteColor
111 // This was previously in Rom::SaveAllPalettes
112 return data.palette_groups.for_each([&](gfx::PaletteGroup& group) -> absl::Status {
113 for (size_t i = 0; i < group.size(); ++i) {
114 auto* palette = group.mutable_palette(i);
115 for (size_t j = 0; j < palette->size(); ++j) {
116 gfx::SnesColor color = (*palette)[j];
117 if (color.is_modified()) {
118 RETURN_IF_ERROR(rom.WriteColor(
119 gfx::GetPaletteAddress(group.name(), i, j), color));
120 color.set_modified(false);
121 }
122 }
123 }
124 return absl::OkStatus();
125 });
126 }
127
129 RETURN_IF_ERROR(SaveGfxGroups(rom, data));
130 }
131
132 // TODO: Implement SaveAllGraphicsData logic
133 return absl::OkStatus();
134}
135
136absl::Status LoadMetadata(const Rom& rom, GameData& data) {
137 constexpr uint32_t kTitleStringOffset = 0x7FC0;
138 constexpr uint32_t kTitleStringLength = 20;
139
140 if (rom.size() < kTitleStringOffset + kTitleStringLength) {
141 return absl::OutOfRangeError("ROM too small for metadata");
142 }
143
144 // Check version byte at offset + 0x19 (0x7FD9)
145 if (kTitleStringOffset + 0x19 < rom.size()) {
146 // Access directly via data() since ReadByte is non-const
147 uint8_t version_byte = rom.data()[kTitleStringOffset + 0x19];
148 data.version = (version_byte == 0) ? zelda3_version::JP : zelda3_version::US;
149 }
150
151 auto title_bytes = rom.ReadByteVector(kTitleStringOffset, kTitleStringLength);
152 if (title_bytes.ok()) {
153 data.title.assign(title_bytes->begin(), title_bytes->end());
154 }
155
156 return absl::OkStatus();
157}
158
159absl::Status LoadPalettes(const Rom& rom, GameData& data) {
160 // Create a vector from rom data for palette loading
161 const std::vector<uint8_t>& rom_vec = rom.vector();
162 return gfx::LoadAllPalettes(rom_vec, data.palette_groups);
163}
164
165absl::Status LoadGfxGroups(Rom& rom, GameData& data) {
166 if (kVersionConstantsMap.find(data.version) == kVersionConstantsMap.end()) {
167 return absl::FailedPreconditionError("Unsupported ROM version");
168 }
169
170 auto version_constants = kVersionConstantsMap.at(data.version);
171
172 // Load Main Blocksets
173 auto main_ptr_res = rom.ReadWord(kGfxGroupsPointer);
174 if (main_ptr_res.ok()) {
175 uint32_t main_ptr = SnesToPc(*main_ptr_res);
176 for (uint32_t i = 0; i < kNumMainBlocksets; i++) {
177 for (int j = 0; j < 8; j++) {
178 auto val = rom.ReadByte(main_ptr + (i * 8) + j);
179 if (val.ok()) data.main_blockset_ids[i][j] = *val;
180 }
181 }
182 }
183
184 // Load Room Blocksets
185 for (uint32_t i = 0; i < kNumRoomBlocksets; i++) {
186 for (int j = 0; j < 4; j++) {
187 auto val = rom.ReadByte(kEntranceGfxGroup + (i * 4) + j);
188 if (val.ok()) data.room_blockset_ids[i][j] = *val;
189 }
190 }
191
192 // Load Sprite Blocksets
193 for (uint32_t i = 0; i < kNumSpritesets; i++) {
194 for (int j = 0; j < 4; j++) {
195 auto val = rom.ReadByte(version_constants.kSpriteBlocksetPointer + (i * 4) + j);
196 if (val.ok()) data.spriteset_ids[i][j] = *val;
197 }
198 }
199
200 // Load Palette Sets
201 for (uint32_t i = 0; i < kNumPalettesets; i++) {
202 for (int j = 0; j < 4; j++) {
203 auto val = rom.ReadByte(version_constants.kDungeonPalettesGroups + (i * 4) + j);
204 if (val.ok()) data.paletteset_ids[i][j] = *val;
205 }
206 }
207
208 return absl::OkStatus();
209}
210
211absl::Status SaveGfxGroups(Rom& rom, const GameData& data) {
212 auto version_constants = kVersionConstantsMap.at(data.version);
213
214 ASSIGN_OR_RETURN(auto main_ptr, rom.ReadWord(kGfxGroupsPointer));
215 main_ptr = SnesToPc(main_ptr);
216
217 // Save Main Blocksets
218 for (uint32_t i = 0; i < kNumMainBlocksets; i++) {
219 for (int j = 0; j < 8; j++) {
220 RETURN_IF_ERROR(rom.WriteByte(main_ptr + (i * 8) + j,
221 data.main_blockset_ids[i][j]));
222 }
223 }
224
225 // Save Room Blocksets
226 for (uint32_t i = 0; i < kNumRoomBlocksets; i++) {
227 for (int j = 0; j < 4; j++) {
229 data.room_blockset_ids[i][j]));
230 }
231 }
232
233 // Save Sprite Blocksets
234 for (uint32_t i = 0; i < kNumSpritesets; i++) {
235 for (int j = 0; j < 4; j++) {
236 RETURN_IF_ERROR(rom.WriteByte(version_constants.kSpriteBlocksetPointer + (i * 4) + j,
237 data.spriteset_ids[i][j]));
238 }
239 }
240
241 // Save Palette Sets
242 for (uint32_t i = 0; i < kNumPalettesets; i++) {
243 for (int j = 0; j < 4; j++) {
244 RETURN_IF_ERROR(rom.WriteByte(version_constants.kDungeonPalettesGroups + (i * 4) + j,
245 data.paletteset_ids[i][j]));
246 }
247 }
248
249 return absl::OkStatus();
250}
251
252// ============================================================================
253// Main Graphics Loading
254// ============================================================================
255
285absl::Status LoadGraphics(Rom& rom, GameData& data) {
286 if (kVersionConstantsMap.find(data.version) == kVersionConstantsMap.end()) {
287 return absl::FailedPreconditionError("Unsupported ROM version for graphics");
288 }
289 auto version_constants = kVersionConstantsMap.at(data.version);
290
291 data.graphics_buffer.clear();
292
293#ifdef __EMSCRIPTEN__
294 auto loading_handle = app::platform::WasmLoadingManager::BeginLoading("Loading Graphics");
295#endif
296
297 // Initialize Diagnostics
298 auto& diag = data.diagnostics;
299 diag.rom_size = rom.size();
300 diag.ptr1_loc = version_constants.kOverworldGfxPtr1;
301 diag.ptr2_loc = version_constants.kOverworldGfxPtr2;
302 diag.ptr3_loc = version_constants.kOverworldGfxPtr3;
303
304 for (uint32_t i = 0; i < kNumGfxSheets; i++) {
305#ifdef __EMSCRIPTEN__
306 app::platform::WasmLoadingManager::UpdateProgress(loading_handle, static_cast<float>(i) / kNumGfxSheets);
307#endif
308
309 diag.sheets[i].index = i;
310 std::vector<uint8_t> sheet;
311 bool bpp3 = false;
312 uint32_t offset = 0;
313
314 // Uncompressed 3BPP (115-126)
315 if (i >= 115 && i <= 126) {
316 diag.sheets[i].is_compressed = false;
317 offset = GetGraphicsAddress(rom.data(), i, version_constants.kOverworldGfxPtr1, version_constants.kOverworldGfxPtr2, version_constants.kOverworldGfxPtr3, rom.size());
318 diag.sheets[i].pc_offset = offset;
319
320 auto read_res = rom.ReadByteVector(offset, zelda3::kUncompressedSheetSize);
321 if (read_res.ok()) {
322 sheet = *read_res;
323 diag.sheets[i].decompression_succeeded = true;
324 bpp3 = true;
325 } else {
326 sheet.assign(zelda3::kUncompressedSheetSize, 0);
327 diag.sheets[i].decompression_succeeded = false;
328 }
329 }
330 // 2BPP (113-114, 218+) - Skipped in main loop
331 else if (i == 113 || i == 114 || i >= 218) {
332 diag.sheets[i].is_compressed = true;
333 bpp3 = false;
334 }
335 // Compressed 3BPP (Standard)
336 else {
337 diag.sheets[i].is_compressed = true;
338 offset = GetGraphicsAddress(rom.data(), i, version_constants.kOverworldGfxPtr1, version_constants.kOverworldGfxPtr2, version_constants.kOverworldGfxPtr3, rom.size());
339 diag.sheets[i].pc_offset = offset;
340
341 if (offset < rom.size()) {
342 // Decompress using LC-LZ2 algorithm with 0x800 byte output buffer.
343 // IMPORTANT: The size parameter (0x800) must NOT be 0, or DecompressV2
344 // returns an empty vector immediately. This was a regression bug.
345 // See: docs/internal/graphics-loading-regression-2024.md
346 auto decomp_res = gfx::lc_lz2::DecompressV2(rom.data(), offset, 0x800, 1, rom.size());
347 if (decomp_res.ok()) {
348 sheet = *decomp_res;
349 diag.sheets[i].decompression_succeeded = true;
350 bpp3 = true;
351 } else {
352 diag.sheets[i].decompression_succeeded = false;
353 }
354 }
355 }
356
357 // Post-process
358 if (bpp3) {
359 auto converted_sheet = gfx::SnesTo8bppSheet(sheet, 3);
360 if (converted_sheet.size() != 4096) converted_sheet.resize(4096, 0);
361
362 data.raw_gfx_sheets[i] = converted_sheet;
364 gfx::kTilesheetDepth, converted_sheet);
365
366 // Apply default palettes
367 if (!data.palette_groups.empty()) {
368 gfx::SnesPalette default_palette;
369 if (i < 113 && data.palette_groups.dungeon_main.size() > 0) {
370 default_palette = data.palette_groups.dungeon_main[0];
371 } else if (i < 128 && data.palette_groups.sprites_aux1.size() > 0) {
372 default_palette = data.palette_groups.sprites_aux1[0];
373 } else if (data.palette_groups.hud.size() > 0) {
374 default_palette = data.palette_groups.hud.palette(0);
375 }
376
377 if (!default_palette.empty()) {
378 data.gfx_bitmaps[i].SetPalette(default_palette);
379 } else {
380 // Fallback to grayscale if no palette found
381 std::vector<gfx::SnesColor> grayscale;
382 for (int color_idx = 0; color_idx < 16; ++color_idx) {
383 float val = color_idx / 15.0f;
384 grayscale.emplace_back(ImVec4(val, val, val, 1.0f));
385 }
386 // Ensure index 0 is transparent for SNES compatibility
387 if (!grayscale.empty()) {
388 grayscale[0].set_transparent(true);
389 }
390 data.gfx_bitmaps[i].SetPalette(gfx::SnesPalette(grayscale));
391 }
392 } else {
393 // Fallback to grayscale if no palette groups loaded
394 std::vector<gfx::SnesColor> grayscale;
395 for (int color_idx = 0; color_idx < 16; ++color_idx) {
396 float val = color_idx / 15.0f;
397 grayscale.emplace_back(ImVec4(val, val, val, 1.0f));
398 }
399 // Ensure index 0 is transparent for SNES compatibility
400 if (!grayscale.empty()) {
401 grayscale[0].set_transparent(true);
402 }
403 data.gfx_bitmaps[i].SetPalette(gfx::SnesPalette(grayscale));
404 }
405
406 data.graphics_buffer.insert(data.graphics_buffer.end(),
407 data.gfx_bitmaps[i].data(),
408 data.gfx_bitmaps[i].data() + data.gfx_bitmaps[i].size());
409 } else {
410 // Placeholder - Fill with 0 (transparent) instead of 0xFF (white)
411 std::vector<uint8_t> placeholder(4096, 0);
412 data.raw_gfx_sheets[i] = placeholder;
414 gfx::kTilesheetDepth, placeholder);
415 data.graphics_buffer.resize(data.graphics_buffer.size() + 4096, 0);
416 }
417 }
418
419 diag.Analyze();
420
421#ifdef __EMSCRIPTEN__
422 app::platform::WasmLoadingManager::EndLoading(loading_handle);
423#endif
424
425 return absl::OkStatus();
426}
427
428// ============================================================================
429// Link Graphics Loading
430// ============================================================================
431
432absl::StatusOr<std::array<gfx::Bitmap, kNumLinkSheets>> LoadLinkGraphics(
433 const Rom& rom) {
434 std::array<gfx::Bitmap, kNumLinkSheets> link_graphics;
435 for (uint32_t i = 0; i < kNumLinkSheets; i++) {
436 auto link_sheet_data_result =
437 rom.ReadByteVector(/*offset=*/kLinkGfxOffset + (i * kLinkGfxLength),
438 /*length=*/kLinkGfxLength);
439 if (!link_sheet_data_result.ok()) {
440 return link_sheet_data_result.status();
441 }
442 auto link_sheet_8bpp = gfx::SnesTo8bppSheet(*link_sheet_data_result, /*bpp=*/4);
443 if (link_sheet_8bpp.size() != 4096) link_sheet_8bpp.resize(4096, 0);
444
445 link_graphics[i].Create(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
446 gfx::kTilesheetDepth, link_sheet_8bpp);
447 // Palette is applied by the caller since GameData may not be available here
448 }
449 return link_graphics;
450}
451
452// ============================================================================
453// 2BPP Graphics Loading
454// ============================================================================
455
456absl::StatusOr<std::vector<uint8_t>> Load2BppGraphics(const Rom& rom) {
457 std::vector<uint8_t> sheet;
458 const uint8_t sheets[] = {0x71, 0x72, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE};
459
460 // Get version constants - default to US if we don't know
461 auto version_constants = kVersionConstantsMap.at(zelda3_version::US);
462
463 for (const auto& sheet_id : sheets) {
464 auto offset = GetGraphicsAddress(rom.data(), sheet_id,
465 version_constants.kOverworldGfxPtr1,
466 version_constants.kOverworldGfxPtr2,
467 version_constants.kOverworldGfxPtr3,
468 rom.size());
469
470 if (offset >= rom.size()) {
471 return absl::OutOfRangeError(
472 absl::StrFormat("2BPP graphics sheet %u offset %u exceeds ROM size %zu",
473 sheet_id, offset, rom.size()));
474 }
475
476 // Decompress using LC-LZ2 algorithm with 0x800 byte output buffer.
477 auto decomp_result = gfx::lc_lz2::DecompressV2(rom.data(), offset, 0x800, 1, rom.size());
478 if (!decomp_result.ok()) {
479 return decomp_result.status();
480 }
481 auto converted_sheet = gfx::SnesTo8bppSheet(*decomp_result, 2);
482 for (const auto& each_pixel : converted_sheet) {
483 sheet.push_back(each_pixel);
484 }
485 }
486 return sheet;
487}
488
489// ============================================================================
490// Font Graphics Loading
491// ============================================================================
492
493absl::StatusOr<gfx::Bitmap> LoadFontGraphics(const Rom& rom) {
494 // Font sprites are located at 0x70000, 2BPP format
495 constexpr uint32_t kFontDataSize = 0x4000; // 16KB of font data
496
497 auto font_data_result = rom.ReadByteVector(kFontSpriteLocation, kFontDataSize);
498 if (!font_data_result.ok()) {
499 return font_data_result.status();
500 }
501
502 // Convert from 2BPP SNES format to 8BPP
503 auto font_8bpp = gfx::SnesTo8bppSheet(*font_data_result, /*bpp=*/2);
504
505 gfx::Bitmap font_bitmap;
507 gfx::kTilesheetDepth, font_8bpp);
508
509 return font_bitmap;
510}
511
512// ============================================================================
513// Graphics Saving
514// ============================================================================
515
517 [[maybe_unused]] Rom& rom, [[maybe_unused]] const std::array<gfx::Bitmap, kNumGfxSheets>& sheets) {
518 // For now, return OK status - full implementation would write sheets back
519 // to ROM at their respective addresses with proper compression
520 LOG_INFO("SaveAllGraphicsData", "Graphics save not yet fully implemented");
521 return absl::OkStatus();
522}
523
524} // namespace zelda3
525} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
Definition rom.cc:243
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:286
const auto & vector() const
Definition rom.h:139
absl::StatusOr< uint16_t > ReadWord(int offset)
Definition rom.cc:228
void Expand(int size)
Definition rom.h:49
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
absl::StatusOr< uint8_t > ReadByte(int offset)
Definition rom.cc:221
static Flags & get()
Definition features.h:92
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:199
SNES Color container.
Definition snes_color.h:110
constexpr bool is_modified() const
Definition snes_color.h:195
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
#define LOG_INFO(category, format,...)
Definition log.h:105
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
absl::StatusOr< std::vector< uint8_t > > DecompressV2(const uint8_t *data, int offset, int size, int mode, size_t rom_size)
Decompresses a buffer of data using the LC_LZ2 algorithm.
constexpr int kTilesheetHeight
Definition snes_tile.h:17
constexpr int kTilesheetWidth
Definition snes_tile.h:16
constexpr int kTilesheetDepth
Definition snes_tile.h:18
std::vector< uint8_t > SnesTo8bppSheet(std::span< uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:131
absl::Status LoadAllPalettes(const std::vector< uint8_t > &rom_data, PaletteGroupMap &groups)
Loads all the palettes for the game.
absl::Status SaveGfxGroups(Rom &rom, const GameData &data)
Definition game_data.cc:211
absl::StatusOr< std::vector< uint8_t > > Load2BppGraphics(const Rom &rom)
Loads 2BPP graphics sheets from ROM.
Definition game_data.cc:456
constexpr uint16_t kLinkGfxLength
Definition game_data.h:48
constexpr uint32_t kFontSpriteLocation
Definition game_data.h:51
absl::StatusOr< std::array< gfx::Bitmap, kNumLinkSheets > > LoadLinkGraphics(const Rom &rom)
Loads Link's graphics sheets from ROM.
Definition game_data.cc:432
absl::StatusOr< gfx::Bitmap > LoadFontGraphics(const Rom &rom)
Loads font graphics from ROM.
Definition game_data.cc:493
constexpr uint32_t kNumRoomBlocksets
Definition game_data.h:30
constexpr uint32_t kUncompressedSheetSize
Definition game_data.h:54
absl::Status LoadGameData(Rom &rom, GameData &data, const LoadOptions &options)
Loads all Zelda3-specific game data from a generic ROM.
Definition game_data.cc:80
absl::Status LoadMetadata(const Rom &rom, GameData &data)
Definition game_data.cc:136
constexpr uint32_t kNumPalettesets
Definition game_data.h:32
absl::Status SaveAllGraphicsData(Rom &rom, const std::array< gfx::Bitmap, kNumGfxSheets > &sheets)
Saves all graphics sheets back to ROM.
Definition game_data.cc:516
constexpr uint32_t kLinkGfxOffset
Definition game_data.h:47
constexpr uint32_t kNumMainBlocksets
Definition game_data.h:29
constexpr uint32_t kNumGfxSheets
Definition game_data.h:25
constexpr uint32_t kEntranceGfxGroup
Definition game_data.h:35
constexpr uint32_t kNumSpritesets
Definition game_data.h:31
constexpr uint32_t kNumLinkSheets
Definition game_data.h:26
absl::Status LoadPalettes(const Rom &rom, GameData &data)
Definition game_data.cc:159
absl::Status SaveGameData(Rom &rom, GameData &data)
Saves modified game data back to the ROM.
Definition game_data.cc:108
absl::Status LoadGraphics(Rom &rom, GameData &data)
Definition game_data.cc:285
uint32_t GetGraphicsAddress(const uint8_t *data, uint8_t addr, uint32_t ptr1, uint32_t ptr2, uint32_t ptr3, size_t rom_size)
Gets the graphics address for a sheet index.
Definition game_data.cc:69
absl::Status LoadGfxGroups(Rom &rom, GameData &data)
Definition game_data.cc:165
constexpr int kGfxGroupsPointer
int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept
Definition snes.h:39
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
absl::Status for_each(Func &&func)
Represents a group of palettes.
auto palette(int i) const
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
Definition game_data.h:93
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
Definition game_data.h:92
std::array< std::array< uint8_t, 4 >, kNumPalettesets > paletteset_ids
Definition game_data.h:99
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89
GraphicsLoadDiagnostics diagnostics
Definition game_data.h:102
zelda3_version version
Definition game_data.h:78
std::array< gfx::Bitmap, kNumGfxSheets > gfx_bitmaps
Definition game_data.h:84
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
Definition game_data.h:91
std::array< std::vector< uint8_t >, kNumGfxSheets > raw_gfx_sheets
Definition game_data.h:83
std::vector< uint8_t > graphics_buffer
Definition game_data.h:82