7#include <unordered_map>
8#include <unordered_set>
10#include "absl/strings/str_format.h"
31 if (i < source.
size()) {
42 for (
int i = 0; i < palette.
size(); ++i) {
44 for (
size_t color = 0; color < padded.size(); ++color) {
52 if (palette.
empty()) {
55 if (requested >= 0 && requested <
static_cast<int>(palette.
size())) {
69bool RangesOverlap(int64_t lhs_start, int64_t lhs_end, int64_t rhs_start,
71 return lhs_start < rhs_end && rhs_start < lhs_end;
75 return RangesOverlap(lhs_address, lhs_address + 2, rhs_address,
82 return absl::FailedPreconditionError(
83 "Editable object cell is outside the audited 2x2 layout");
88 return static_cast<size_t>(cell.
rel_x * 2 + cell.
rel_y);
98 const std::vector<ObjectDrawer::TileTrace>& traces) {
100 if (traces.empty()) {
104 layout.
object_id =
static_cast<int16_t
>(traces[0].object_id);
107 int min_x = traces[0].x_tile;
108 int min_y = traces[0].y_tile;
111 for (
const auto& t : traces) {
112 min_x = std::min(min_x,
static_cast<int>(t.x_tile));
113 min_y = std::min(min_y,
static_cast<int>(t.y_tile));
114 max_x = std::max(max_x,
static_cast<int>(t.x_tile));
115 max_y = std::max(max_y,
static_cast<int>(t.y_tile));
123 std::unordered_map<uint32_t, size_t> last_trace_by_cell;
124 last_trace_by_cell.reserve(traces.size());
125 for (
size_t i = 0; i < traces.size(); ++i) {
126 const auto& t = traces[i];
127 const uint32_t cell_key =
128 (
static_cast<uint32_t
>(
static_cast<uint16_t
>(t.x_tile)) << 16) |
129 static_cast<uint16_t
>(t.y_tile);
130 last_trace_by_cell[cell_key] = i;
133 std::vector<size_t> surviving_trace_indices;
134 surviving_trace_indices.reserve(last_trace_by_cell.size());
135 for (
const auto& [_, trace_index] : last_trace_by_cell) {
136 surviving_trace_indices.push_back(trace_index);
138 std::sort(surviving_trace_indices.begin(), surviving_trace_indices.end());
140 layout.
cells.reserve(surviving_trace_indices.size());
141 for (
size_t trace_index : surviving_trace_indices) {
142 const auto& t = traces[trace_index];
144 cell.
rel_x = t.x_tile - min_x;
145 cell.
rel_y = t.y_tile - min_y;
148 bool h_mirror = (t.flags & 0x1) != 0;
149 bool v_mirror = (t.flags & 0x2) != 0;
150 bool priority = (t.flags & 0x4) != 0;
151 uint8_t palette = (t.flags >> 3) & 0x7;
153 gfx::TileInfo(t.tile_id, palette, v_mirror, h_mirror, priority);
158 layout.
cells.push_back(cell);
165 for (
auto& cell :
cells) {
166 if (cell.rel_x == rel_x && cell.rel_y == rel_y)
174 for (
const auto& cell :
cells) {
175 if (cell.rel_x == rel_x && cell.rel_y == rel_y)
182 for (
const auto& cell :
cells) {
190 for (
auto& cell :
cells) {
193 cell.modified =
false;
205 int16_t object_id,
int subtype) {
206 const int runtime_count =
208 if (runtime_count == 0) {
209 return absl::UnimplementedError(
210 "Custom tile editing supports only registered fixed runtime objects");
212 if (subtype < 0 || subtype >= runtime_count) {
213 return absl::OutOfRangeError(
214 "Custom object subtype is outside the runtime dispatch table");
218 const std::string filename = manager.ResolveFilename(object_id, subtype);
219 if (filename.empty()) {
220 return absl::NotFoundError(
221 "Custom object runtime slot has no mapped asset filename");
244 cell.
rel_x = tile.rel_x;
245 cell.
rel_y = tile.rel_y;
249 layout.
cells.push_back(cell);
262 uint8_t object_size) {
264 return absl::FailedPreconditionError(
"ROM not loaded");
275 auto [anchor_x, anchor_y] =
277 RoomObject obj(object_id, anchor_x, anchor_y, preview_size, 0);
282 bool is_custom =
false;
283 std::string custom_filename;
284 int subtype = obj.
size_ & 0x1F;
288 if (custom_result.ok()) {
299 std::vector<ObjectDrawer::TileTrace> traces;
306 auto status = drawer.
DrawObject(obj, dummy_bg1, dummy_bg2, palette);
313 if (traces.empty()) {
314 return absl::NotFoundError(
"Object produced no tile traces");
332 return std::find(kEditableStandardObjectIds.begin(),
333 kEditableStandardObjectIds.end(),
334 object_id) != kEditableStandardObjectIds.end();
340 return absl::UnimplementedError(
341 "Standard object tile editing is not supported for this object");
344 return absl::FailedPreconditionError(
"ROM not loaded");
347 const uint32_t descriptor_pc_address =
349 auto descriptor_or =
rom_->
ReadWord(
static_cast<int>(descriptor_pc_address));
350 if (!descriptor_or.ok()) {
351 return descriptor_or.status();
353 const uint16_t descriptor_word = *descriptor_or;
354 const int64_t source_address =
356 if (source_address < 0 ||
357 source_address +
static_cast<int64_t
>(kFixed2x2SourceWordCount * 2) >
358 static_cast<int64_t
>(
rom_->
size()) ||
359 source_address > std::numeric_limits<int>::max()) {
360 return absl::OutOfRangeError(
361 "Editable object tile source is outside the loaded ROM");
363 const int64_t source_end =
364 source_address +
static_cast<int64_t
>(kFixed2x2SourceWordCount * 2);
365 if (RangesOverlap(source_address, source_end, kObjectMetadataStart,
366 kObjectMetadataEnd)) {
367 return absl::FailedPreconditionError(
368 "Editable object tile source overlaps object descriptor or routine "
373 if (!layout_or.ok()) {
374 return layout_or.status();
378 return absl::FailedPreconditionError(
379 "Custom objects do not use standard ROM source provenance");
382 layout.
cells.size() != kFixed2x2SourceWordCount ||
384 return absl::FailedPreconditionError(
385 "Object draw does not match the audited fixed 2x2 layout");
389 span.
pc_address =
static_cast<uint32_t
>(source_address);
391 for (
size_t word_index = 0; word_index < kFixed2x2SourceWordCount;
396 return word_or.status();
401 std::array<bool, kFixed2x2SourceWordCount> mapped_words{};
402 for (
auto& cell : layout.
cells) {
403 auto source_word_index_or = ResolveFixed2x2SourceWordIndex(cell);
404 if (!source_word_index_or.ok()) {
405 return source_word_index_or.status();
407 const size_t source_word_index = *source_word_index_or;
408 if (mapped_words[source_word_index]) {
409 return absl::FailedPreconditionError(
410 "Editable object layout maps multiple cells to one source word");
412 if (cell.original_word != span.
expected_words[source_word_index]) {
413 return absl::FailedPreconditionError(
414 "Draw trace does not match the resolved ROM source word");
416 mapped_words[source_word_index] =
true;
419 if (std::find(mapped_words.begin(), mapped_words.end(),
false) !=
420 mapped_words.end()) {
421 return absl::FailedPreconditionError(
422 "Editable object layout does not cover every source word");
429 provenance.
spans.push_back(std::move(span));
438 if (!room_gfx_buffer) {
439 return absl::FailedPreconditionError(
"No room graphics buffer");
441 if (layout.
cells.empty()) {
442 return absl::OkStatus();
449 std::vector<uint8_t> pixel_data(bmp_w * bmp_h, 255);
450 bitmap.
Create(bmp_w, bmp_h, 8, pixel_data);
454 if (!palette.
empty()) {
455 bitmap.
SetPalette(BuildCombinedPaletteBanks(palette));
458 SDL_SetColorKey(bitmap.
surface(), SDL_TRUE, 255);
459 SDL_SetSurfaceBlendMode(bitmap.
surface(), SDL_BLENDMODE_BLEND);
465 for (
const auto& cell : layout.
cells) {
470 if (runtime_word == 0) {
475 int px = cell.rel_x * 8;
476 int py = cell.rel_y * 8;
480 return absl::OkStatus();
484 gfx::Bitmap& atlas,
const uint8_t* room_gfx_buffer,
486 std::optional<int16_t> custom_object_id, uint16_t retained_attributes) {
487 if (!room_gfx_buffer) {
488 return absl::FailedPreconditionError(
"No room graphics buffer");
492 custom_object_id.has_value() ? 255 : 0);
495 const int resolved_palette = ResolvePaletteIndex(palette, display_palette);
496 if (!palette.
empty()) {
498 BuildPaddedPaletteBank(palette.
palette_ref(resolved_palette)));
501 SDL_SetColorKey(atlas.
surface(), SDL_TRUE, 255);
502 SDL_SetSurfaceBlendMode(atlas.
surface(), SDL_BLENDMODE_BLEND);
517 if (custom_object_id.has_value()) {
521 const uint16_t source_word =
522 static_cast<uint16_t
>(tile_id | ((display_palette & 7) << 10) |
523 (retained_attributes & 0xE000));
524 const uint16_t runtime_word =
526 if (runtime_word == 0) {
529 info.
id_ = runtime_word & 0x03FF;
534 return absl::OkStatus();
539 return absl::OkStatus();
544 return absl::FailedPreconditionError(
545 "Custom object has no filename for write-back");
548 return absl::FailedPreconditionError(
549 "Custom object has no source snapshot for safe write-back");
551 const int runtime_count =
554 return absl::FailedPreconditionError(
555 "Custom object layout no longer identifies a fixed runtime slot");
560 for (
const auto& cell : layout.
cells) {
561 object.tiles.push_back(
568 return absl::AbortedError(
569 "Custom object slot mapping changed after it was opened; edits were "
575 if (!published_or.ok()) {
576 return published_or.status();
581 return absl::OkStatus();
586 return plan_or.status();
594 return absl::InvalidArgumentError(
595 "Cannot build a standard ROM write plan for a custom object");
600 return absl::FailedPreconditionError(
"ROM not loaded");
603 return absl::FailedPreconditionError(
604 "Standard object layout has no editable source provenance");
607 return absl::UnimplementedError(
608 "Standard object tile editing is not supported for this object");
612 if (provenance.object_id != layout.
object_id) {
613 return absl::FailedPreconditionError(
614 "Object tile source provenance does not match the layout object");
616 const uint32_t expected_descriptor_address =
static_cast<uint32_t
>(
618 if (provenance.descriptor_pc_address != expected_descriptor_address) {
619 return absl::FailedPreconditionError(
620 "Object tile source descriptor address does not match the object");
622 if (provenance.spans.size() != 1 ||
623 provenance.spans.front().expected_words.size() !=
624 kFixed2x2SourceWordCount) {
625 return absl::FailedPreconditionError(
626 "Object tile source provenance does not match the audited source map");
629 const int64_t rom_size =
static_cast<int64_t
>(
rom_->
size());
630 if (provenance.descriptor_pc_address >
631 static_cast<uint32_t
>(std::numeric_limits<int>::max()) ||
632 static_cast<int64_t
>(provenance.descriptor_pc_address) + 2 > rom_size) {
633 return absl::OutOfRangeError(
634 "Object tile source descriptor is outside the loaded ROM");
636 auto current_descriptor_or =
637 rom_->
ReadWord(
static_cast<int>(provenance.descriptor_pc_address));
638 if (!current_descriptor_or.ok()) {
639 return current_descriptor_or.status();
641 if (*current_descriptor_or != provenance.expected_descriptor_word) {
642 return absl::FailedPreconditionError(
643 "Object tile source descriptor changed after capture");
646 const auto& span = provenance.spans.front();
647 const int64_t expected_span_address =
649 provenance.expected_descriptor_word;
650 if (span.pc_address != expected_span_address) {
651 return absl::FailedPreconditionError(
652 "Object tile source span does not match its descriptor");
654 if (span.pc_address >
655 static_cast<uint32_t
>(std::numeric_limits<int>::max()) ||
656 static_cast<int64_t
>(span.pc_address) +
657 static_cast<int64_t
>(span.expected_words.size() * 2) >
659 return absl::OutOfRangeError(
660 "Object tile source span is outside the loaded ROM");
663 const int64_t span_start =
static_cast<int64_t
>(span.pc_address);
664 const int64_t span_end =
665 span_start +
static_cast<int64_t
>(span.expected_words.size() * 2);
666 if (RangesOverlap(span_start, span_end, kObjectMetadataStart,
667 kObjectMetadataEnd)) {
668 return absl::FailedPreconditionError(
669 "Object tile source span overlaps object descriptor or routine "
673 std::vector<int> precondition_addresses;
674 const int descriptor_address =
675 static_cast<int>(provenance.descriptor_pc_address);
676 precondition_addresses.push_back(descriptor_address);
678 {descriptor_address, provenance.expected_descriptor_word});
679 for (
size_t word_index = 0; word_index < span.expected_words.size();
681 const int address =
static_cast<int>(span.pc_address + word_index * 2);
682 if (std::any_of(precondition_addresses.begin(),
683 precondition_addresses.end(), [&](
int existing_address) {
684 return WordRangesOverlap(existing_address, address);
686 return absl::FailedPreconditionError(
687 "Object tile descriptor and source preconditions overlap");
689 precondition_addresses.push_back(address);
691 if (!current_word_or.ok()) {
692 return current_word_or.status();
694 if (*current_word_or != span.expected_words[word_index]) {
695 return absl::FailedPreconditionError(
696 "Object tile source word changed after capture");
698 plan.
preconditions_.push_back({address, span.expected_words[word_index]});
701 if (layout.
cells.size() != kFixed2x2SourceWordCount ||
703 return absl::FailedPreconditionError(
704 "Object tile layout does not match the audited fixed 2x2 shape");
707 std::unordered_set<int> resolved_addresses;
708 for (
const auto& cell : layout.
cells) {
709 if (!cell.source_ref.has_value()) {
710 return absl::FailedPreconditionError(
711 "Object tile cell has no editable source reference");
713 const auto& source_ref = *cell.source_ref;
714 if (source_ref.span_index >= provenance.spans.size()) {
715 return absl::FailedPreconditionError(
716 "Object tile source span reference is out of bounds");
718 const auto& referenced_span = provenance.spans[source_ref.span_index];
719 if (source_ref.word_index >= referenced_span.expected_words.size()) {
720 return absl::FailedPreconditionError(
721 "Object tile source word reference is out of bounds");
724 auto expected_source_index_or = ResolveFixed2x2SourceWordIndex(cell);
725 if (!expected_source_index_or.ok()) {
726 return expected_source_index_or.status();
728 if (source_ref.span_index != 0 ||
729 source_ref.word_index != *expected_source_index_or) {
730 return absl::FailedPreconditionError(
731 "Object tile cell source reference does not match the audited map");
734 const int64_t address64 =
static_cast<int64_t
>(referenced_span.pc_address) +
735 static_cast<int64_t
>(source_ref.word_index) * 2;
736 if (address64 < 0 || address64 + 2 > rom_size ||
737 address64 > std::numeric_limits<int>::max()) {
738 return absl::OutOfRangeError(
739 "Object tile write range is outside the loaded ROM");
741 const int address =
static_cast<int>(address64);
742 if (!resolved_addresses.insert(address).second) {
743 return absl::FailedPreconditionError(
744 "Object tile cells resolve to a duplicate ROM address");
747 const uint16_t expected_word =
748 referenced_span.expected_words[source_ref.word_index];
749 if (cell.original_word != expected_word) {
750 return absl::FailedPreconditionError(
751 "Object tile cell original word does not match its source");
754 if (!cell.modified && current_layout_word != cell.original_word) {
755 return absl::FailedPreconditionError(
756 "Object tile cell changed without being marked modified");
758 if (!cell.modified) {
762 plan.
writes_.push_back({address, expected_word, current_layout_word});
763 plan.
write_ranges_.emplace_back(
static_cast<uint32_t
>(address),
764 static_cast<uint32_t
>(address + 2));
773 return absl::OkStatus();
776 return absl::FailedPreconditionError(
777 "Object tile write plan ranges do not match its writes");
780 return absl::FailedPreconditionError(
781 "Object tile write plan has no source provenance preconditions");
784 return absl::FailedPreconditionError(
"ROM not loaded");
787 const int64_t rom_size =
static_cast<int64_t
>(
rom_->
size());
788 std::unordered_map<int, uint16_t> precondition_words;
789 std::vector<int> precondition_addresses;
791 const int64_t address =
static_cast<int64_t
>(precondition.address);
792 if (address < 0 || address + 2 > rom_size) {
793 return absl::OutOfRangeError(
794 "Object tile precondition is outside the loaded ROM");
796 if (std::any_of(precondition_addresses.begin(),
797 precondition_addresses.end(), [&](
int existing_address) {
798 return WordRangesOverlap(existing_address,
799 precondition.address);
801 return absl::FailedPreconditionError(
802 "Object tile plan has overlapping CAS preconditions");
804 precondition_addresses.push_back(precondition.address);
805 if (!precondition_words
806 .emplace(precondition.address, precondition.expected_word)
808 return absl::FailedPreconditionError(
809 "Object tile plan has duplicate CAS preconditions");
813 std::unordered_set<int> write_addresses;
814 std::vector<int> validated_write_addresses;
815 for (
size_t write_index = 0; write_index < plan.
writes_.size();
817 const auto& write = plan.
writes_[write_index];
818 const int64_t address =
static_cast<int64_t
>(write.address);
819 if (address < 0 || address + 2 > rom_size) {
820 return absl::OutOfRangeError(
821 "Object tile write range is outside the loaded ROM");
823 if (std::any_of(validated_write_addresses.begin(),
824 validated_write_addresses.end(), [&](
int existing_address) {
825 return WordRangesOverlap(existing_address, write.address);
827 return absl::FailedPreconditionError(
828 "Object tile plan has overlapping write ranges");
830 validated_write_addresses.push_back(write.address);
831 if (!write_addresses.insert(write.address).second) {
832 return absl::FailedPreconditionError(
833 "Object tile plan has duplicate write addresses");
835 const std::pair<uint32_t, uint32_t> expected_range = {
836 static_cast<uint32_t
>(write.address),
837 static_cast<uint32_t
>(write.address + 2)};
839 return absl::FailedPreconditionError(
840 "Object tile write plan range does not match its write address");
842 const auto precondition = precondition_words.find(write.address);
843 if (precondition == precondition_words.end() ||
844 precondition->second != write.expected_word) {
845 return absl::FailedPreconditionError(
846 "Object tile write has no matching source precondition");
853 auto current_word_or =
rom_->
ReadWord(precondition.address);
854 if (!current_word_or.ok()) {
855 return current_word_or.status();
857 if (*current_word_or != precondition.expected_word) {
858 return absl::FailedPreconditionError(
859 "Object tile source changed after the write plan was built");
862 for (
const auto& write : plan.
writes_) {
864 if (!current_word_or.ok()) {
865 return current_word_or.status();
867 if (*current_word_or != write.expected_word) {
868 return absl::FailedPreconditionError(
869 "Object tile write target changed after the plan was built");
875 for (
const auto& write : plan.
writes_) {
876 transaction.
WriteWord(write.address, write.word);
878 const absl::Status status = transaction.
Commit();
884 return absl::OkStatus();
887absl::StatusOr<ObjectTileSourceImpact>
891 return absl::FailedPreconditionError(
"ROM not loaded");
894 return absl::InvalidArgumentError(
895 "Custom objects do not use standard ROM tile sources");
898 return absl::FailedPreconditionError(
899 "Standard object layout has no editable source provenance");
907 return plan_or.status();
910 std::vector<ObjectTileReadRange> target_ranges;
912 if (span.expected_words.empty()) {
913 return absl::FailedPreconditionError(
914 "Editable object tile source span is empty");
916 const uint64_t end =
static_cast<uint64_t
>(span.pc_address) +
917 static_cast<uint64_t
>(span.expected_words.size()) * 2;
918 if (end >
rom_->
size() || end > std::numeric_limits<uint32_t>::max()) {
919 return absl::OutOfRangeError(
920 "Editable object tile source span is outside the loaded ROM");
922 target_ranges.push_back({span.pc_address,
static_cast<uint32_t
>(end)});
925 const auto overlaps_target =
927 return std::any_of(target_ranges.begin(), target_ranges.end(),
929 return candidate.begin < target.end &&
930 target.begin < candidate.end;
936 const auto inspect_object = [&](
int object_id) -> absl::Status {
939 if (!ranges_or.ok()) {
940 return absl::FailedPreconditionError(absl::StrFormat(
941 "Unable to resolve tile sources for object 0x%03X: %s", object_id,
942 ranges_or.status().message()));
946 entry.
object_id =
static_cast<int16_t
>(object_id);
947 for (
const auto& range : *ranges_or) {
948 if (overlaps_target(range)) {
955 return absl::OkStatus();
961 for (
int object_id = 0x000; object_id <= 0x0F7; ++object_id) {
962 const absl::Status status = inspect_object(object_id);
967 for (
int object_id = 0x100; object_id <= 0x13F; ++object_id) {
968 const absl::Status status = inspect_object(object_id);
973 for (
int object_id = 0xF80; object_id <= 0xFFF; ++object_id) {
974 const absl::Status status = inspect_object(object_id);
984 constexpr int kLightableTorchTileCount = 4;
991 for (
const auto consumer :
995 for (
const auto& range : kLightableTorchSources) {
996 if (overlaps_target(range)) {
1000 if (!torch_impact.overlapping_ranges.empty()) {
1005 const bool includes_edited_object = std::any_of(
1007 [&](
const auto& entry) { return entry.object_id == layout.object_id; });
1008 if (!includes_edited_object) {
1009 return absl::FailedPreconditionError(
1010 "Edited object is missing from its tile source impact inventory");
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
void set_dirty(bool dirty)
absl::StatusOr< uint16_t > ReadWord(int offset) const
void AdvanceObjectTileRevision()
Transaction & WriteWord(int address, uint16_t value)
Represents a bitmap image optimized for SNES ROM hacking.
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
SDL_Surface * surface() const
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void AddColor(const SnesColor &color)
SNES 16-bit tile metadata container.
static int RuntimeSubtypeCountForObject(int object_id)
static CustomObjectManager & Get()
absl::StatusOr< std::shared_ptr< CustomObject > > GetObjectInternal(int object_id, int subtype)
std::string ResolveFilename(int object_id, int subtype) const
Draws dungeon objects to background buffers using game patterns.
void DrawTileToBitmap(gfx::Bitmap &bitmap, const gfx::TileInfo &tile_info, int pixel_x, int pixel_y, const uint8_t *tiledata)
Draw a single tile directly to bitmap.
void SetRoomFloorGraphics(uint8_t floor1, uint8_t floor2)
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr, gfx::BackgroundBuffer *layout_bg2=nullptr)
Draw a room object to background buffers.
void ClearTraceCollector()
void SetTraceCollector(std::vector< TileTrace > *collector, bool trace_only=false)
std::pair< int, int > ResolveAnchor(int16_t object_id, uint8_t size_byte) const
Resolve the canvas anchor (x, y) for a given object's draw routine.
static ObjectGeometry & Get()
Direct ROM parser for dungeon objects.
absl::StatusOr< std::vector< ObjectTileReadRange > > ResolveTileReadRanges(int16_t object_id)
Resolve every tile-data range consumed by ParseObject.
absl::StatusOr< ObjectTileSourceImpact > AnalyzeStandardTileSourceImpact(const ObjectTileLayout &layout) const
absl::Status ApplyStandardWritePlan(const ObjectTileWritePlan &plan)
static constexpr int kAtlasTilesPerRow
ObjectTileEditor(Rom *rom)
absl::Status RenderLayoutToBitmap(const ObjectTileLayout &layout, gfx::Bitmap &bitmap, const uint8_t *room_gfx_buffer, const gfx::PaletteGroup &palette)
static bool IsEditableStandardObject(int16_t object_id)
absl::Status BuildTile8Atlas(gfx::Bitmap &atlas, const uint8_t *room_gfx_buffer, const gfx::PaletteGroup &palette, int display_palette=2, std::optional< int16_t > custom_object_id=std::nullopt, uint16_t retained_attributes=0)
absl::StatusOr< ObjectTileLayout > CaptureObjectLayout(int16_t object_id, const Room &room, const gfx::PaletteGroup &palette)
absl::Status WriteBack(ObjectTileLayout &layout)
static constexpr int kAtlasWidthPx
static constexpr int kAtlasTileCount
static constexpr int kAtlasHeightPx
absl::StatusOr< ObjectTileLayout > CaptureEditableObjectLayout(int16_t object_id, const Room &room, const gfx::PaletteGroup &palette)
absl::StatusOr< ObjectTileWritePlan > BuildStandardWritePlan(const ObjectTileLayout &layout) const
absl::StatusOr< ObjectTileLayout > LoadCustomObjectLayout(int16_t object_id, int subtype)
Fully resolved standard-object ROM writes.
std::vector< std::pair< uint32_t, uint32_t > > write_ranges_
std::vector< Write > writes_
std::vector< WordPrecondition > preconditions_
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
#define ASSIGN_OR_RETURN(type_variable_name, expression)
uint16_t TileInfoToWord(TileInfo tile_info)
TileInfo WordToTileInfo(uint16_t word)
gfx::SnesPalette BuildCombinedPaletteBanks(const gfx::PaletteGroup &palette)
constexpr int64_t kObjectMetadataEnd
constexpr int64_t kObjectMetadataStart
bool WordRangesOverlap(int64_t lhs_address, int64_t rhs_address)
absl::StatusOr< size_t > ResolveFixed2x2SourceWordIndex(const ObjectTileLayout::Cell &cell)
gfx::SnesPalette BuildPaddedPaletteBank(const gfx::SnesPalette &source)
constexpr int kPaletteBankSize
int ResolvePaletteIndex(const gfx::PaletteGroup &palette, int requested)
bool RangesOverlap(int64_t lhs_start, int64_t lhs_end, int64_t rhs_start, int64_t rhs_end)
constexpr size_t kFixed2x2SourceWordCount
constexpr std::array< int16_t, 2 > kEditableStandardObjectIds
uint8_t DefaultRoomObjectSizeForPlacement(int object_id)
constexpr int kRoomObjectSubtype3
uint8_t CanonicalRoomObjectSize(int object_id, uint8_t requested_size)
constexpr int kRoomObjectSubtype1
constexpr int kRoomObjectSubtype2
uint16_t CustomObjectRuntimeTileWord(int object_id, uint16_t source_word)
constexpr int kRoomObjectTileAddress
absl::StatusOr< std::vector< uint8_t > > PublishCustomObjectBinary(const std::string &custom_objects_folder, const std::string &filename, const CustomObject &object, const std::vector< uint8_t > &expected_source_bytes, const fs::path &expected_resolved_path)
absl::StatusOr< CustomObjectAsset > LoadCustomObjectAsset(const std::string &custom_objects_folder, const std::string &filename)
Represents a group of palettes.
const SnesPalette & palette_ref(int i) const
std::filesystem::path resolved_path
std::vector< uint8_t > source_bytes
Represents a decoded custom object (from binary format)
BoundingBox GetBoundingBox() const
std::vector< TileMapEntry > tiles
Editable tile8 layout captured from an object's draw trace.
bool HasModifications() const
std::vector< uint8_t > custom_source_bytes
std::optional< ObjectTileSourceProvenance > source_provenance
std::filesystem::path custom_resolved_path
Cell * FindCell(int rel_x, int rel_y)
std::vector< Cell > cells
std::string custom_filename
static ObjectTileLayout FromTraces(const std::vector< ObjectDrawer::TileTrace > &traces)
One half-open PC range consumed while parsing an object's tile data.
std::vector< ObjectTileReadRange > overlapping_ranges
One standard object whose parser-visible tile sources overlap an editable layout's authorized source ...
std::vector< ObjectTileReadRange > overlapping_ranges
Fail-closed impact inventory within ObjectParser's source model plus explicitly modeled runtime consu...
std::vector< ObjectTileRuntimeConsumerImpactEntry > runtime_consumers
std::vector< ObjectTileSourceImpactEntry > affected_objects
ROM descriptor and source snapshot authorizing an editable capture.
uint16_t expected_descriptor_word
uint32_t descriptor_pc_address
std::vector< ObjectTileSourceSpan > spans
A cell's exact word within an authorized source span.
One contiguous ROM-backed source span for an object's tile words.
std::vector< uint16_t > expected_words