312 Rom& rom, uint16_t sheet_id,
const std::vector<uint8_t>& snes_3bpp,
315 return absl::FailedPreconditionError(
"ROM not loaded");
318 return absl::InvalidArgumentError(
319 absl::StrFormat(
"Graphics sheet %d is out of range (0-%zu)", sheet_id,
323 return absl::FailedPreconditionError(absl::StrFormat(
324 "Graphics sheet 0x%02X is reserved by the project and must not be "
330 return absl::UnimplementedError(absl::StrFormat(
331 "Graphics sheet 0x%02X is 2bpp; 2bpp sheets are read-only", sheet_id));
334 return absl::InvalidArgumentError(absl::StrFormat(
335 "Sheet 0x%02X data is %zu bytes; a 3bpp sheet is exactly 0x%zX",
340 reserved != policy.
reserved_blocks.end() && !reserved->second.empty()) {
343 return absl::FailedPreconditionError(absl::StrFormat(
344 "Sheet 0x%02X has reserved 16x16 blocks but its current data "
345 "cannot be read to protect them",
348 constexpr size_t kTileBytes = 24;
349 for (uint16_t block : reserved->second) {
351 const size_t begin =
static_cast<size_t>(tile) * kTileBytes;
353 !std::equal(snes_3bpp.begin() + begin,
354 snes_3bpp.begin() + begin + kTileBytes,
355 current->begin() + begin)) {
356 return absl::FailedPreconditionError(absl::StrFormat(
357 "Sheet 0x%02X 16x16 block %d is reserved by the project and "
358 "must keep its pixels",
377 std::equal(snes_3bpp.begin(), snes_3bpp.end(), current->begin())) {
385 RomWriteJournal journal(rom);
392 if (readback != snes_3bpp) {
393 return absl::DataLossError(absl::StrFormat(
394 "Raw sheet 0x%02X read-back differs from the edited sheet",
406 int encoded_size = 0;
408 snes_3bpp.data(),
static_cast<int>(snes_3bpp.size()), &encoded_size,
410 encoded.resize(encoded_size);
415 if (!check.ok() || check->data != snes_3bpp ||
416 check->compressed_size != encoded.size()) {
417 return absl::InternalError(absl::StrFormat(
418 "Encoding sheet 0x%02X did not round-trip; nothing was written",
424 const OtherSheetExtents others = CollectOtherExtents(rom, sheet_id, tables);
426 bool fits_in_place =
false;
427 if (old_extent.ok()) {
429 fits_in_place = encoded.size() <= old_extent->stored_size;
430 const uint32_t old_end =
431 old_pc +
static_cast<uint32_t
>(old_extent->stored_size);
432 for (
const auto& other : others.extents) {
434 if (Overlaps(old_pc, old_end, other.pc,
435 other.pc +
static_cast<uint32_t
>(other.stored_size))) {
436 fits_in_place =
false;
442 uint32_t target = old_pc;
443 if (!fits_in_place) {
445 return absl::FailedPreconditionError(absl::StrFormat(
446 "Sheet 0x%02X needs %zu bytes but its current stream holds %zu, "
447 "and no graphics free space is registered "
448 "(hack manifest graphics_sheet_regions.allocation_regions)",
451 if (!others.unreadable.empty()) {
452 std::vector<std::string> ids;
453 for (uint16_t
id : others.unreadable) {
454 ids.push_back(absl::StrFormat(
"0x%02X",
id));
456 return absl::FailedPreconditionError(absl::StrFormat(
457 "Cannot compute graphics free space: sheet(s) %s do not decode",
458 absl::StrJoin(ids,
", ")));
461 target, FindFreeSpace(rom, policy, others, tables, encoded.size()));
465 policy, target, target +
static_cast<uint32_t
>(encoded.size())));
468 if (target != old_pc) {
469 const uint8_t old_bank = rom.
data()[tables.
bank + sheet_id];
470 uint8_t bank =
static_cast<uint8_t
>(target >> 15);
472 if ((old_bank & 0x80) != 0 || bank >= 0x7E) {
475 const uint16_t addr =
static_cast<uint16_t
>(0x8000 | (target & 0x7FFF));
476 const std::pair<uint32_t, uint8_t> pointer_bytes[] = {
477 {tables.
bank + sheet_id, bank},
478 {tables.
high + sheet_id,
static_cast<uint8_t
>(addr >> 8)},
479 {tables.
low + sheet_id,
static_cast<uint8_t
>(addr & 0xFF)}};
480 for (
const auto& [pc, value] : pointer_bytes) {
488 RETURN_IF_ERROR(VerifyCompressedSheet(rom, sheet_id, target, snes_3bpp,
489 encoded.
size(), tables));
491 LOG_INFO(
"GfxSheetStorage",
"Wrote sheet %02X: %zu bytes %s at 0x%06X",
492 sheet_id, encoded.size(),
Where one sheet's bytes live and how many bytes they occupy.