99 const Rom& rom,
int state,
int map,
102 if (state < 0 || state >= 3 || map < 0 || map >= layout.counts[state]) {
103 return absl::InvalidArgumentError(
104 "Sprite map is not supported in this game state");
106 if (replacement.empty() || replacement.back() != 0xFF ||
107 replacement.size() % 3 != 1) {
108 return absl::InvalidArgumentError(
"Invalid overworld sprite stream");
110 for (
size_t i = 0; i + 1 < replacement.size(); i += 3) {
111 if (replacement[i] == 0xFF)
112 return absl::InvalidArgumentError(
113 "Sprite Y byte collides with terminator");
115 const int region_start = layout.data_start;
117 const auto& data = rom.
vector();
120 std::vector<uint8_t> referenced(region_end - region_start, 0);
121 std::vector<std::pair<std::pair<int, int>, ListSpan>> spans;
122 for (
int s = 0; s < 3; ++s) {
123 for (
int m = 0; m < layout.counts[s]; ++m) {
125 region_start, region_end));
126 for (
int i = span.start; i < span.start + span.size; ++i)
127 referenced[i - region_start] = 1;
128 spans.push_back({{s, m}, span});
133 plan.
pointer_pc = layout.tables[state] + map * 2;
137 std::count(referenced.begin(), referenced.end(), uint8_t{0}));
139 for (
const auto& [slot, span] : spans) {
140 if (slot == std::make_pair(state, map))
144 plan.
old_bytes.assign(data.begin() + target.start,
145 data.begin() + target.start + target.size);
148 for (
const auto& [slot, span] : spans) {
149 if (slot == std::make_pair(state, map))
151 if (span.start < target.start + target.size &&
152 target.start < span.start + span.size) {
159 const int new_size =
static_cast<int>(replacement.size());
160 auto is_free = [&](
int pc) {
161 return pc >= region_start && pc < region_end &&
162 !referenced[pc - region_start];
165 if (new_size <= target.size) {
169 for (
int pc = target.start + target.size; pc < target.start + new_size;
180 auto usable = [&](
int pc) {
183 return plan.
sharers.empty() && pc >= target.start &&
184 pc < target.start + target.size;
188 for (
int pc = region_start; pc < region_end;) {
194 while (end < region_end && usable(end))
196 const int len = end - pc;
197 if (len >= new_size && (best < 0 || len < best_len)) {
204 return absl::ResourceExhaustedError(absl::StrFormat(
205 "Overworld sprite list for state %d map $%02X cannot grow in place "
206 "and no unreferenced run of %d bytes exists in PC $%06X-$%06X "
207 "(%d unreferenced bytes total); no bytes written",
208 state, map, new_size, region_start, region_end,
217 const uint16_t pointer =
static_cast<uint16_t
>(plan.
new_list_pc - 0x40000);
219 {
static_cast<uint8_t
>(pointer & 0xFF),
220 static_cast<uint8_t
>(pointer >> 8)}});
222 plan.
writes.push_back({target.start, replacement});
227 std::vector<uint8_t> sim = data;
228 for (
const auto& write : plan.
writes)
229 std::copy(write.bytes.begin(), write.bytes.end(),
230 sim.begin() + write.address);
231 for (
const auto& [slot, span] : spans) {
234 ReadListSpan(sim, layout.tables[slot.first] + slot.second * 2,
235 region_start, region_end));
237 sim.begin() + after.start + after.size);
239 slot == std::make_pair(state, map)
242 data.begin() + span.start + span.size);
244 return absl::InternalError(absl::StrFormat(
245 "Sprite edit plan would change state %d map $%02X; no bytes written",
246 slot.first, slot.second));
256 std::vector<OverworldSpriteBytes> lists;
257 bool changed =
false;
258 for (
int state = 0; state < 3; ++state) {
259 for (
int map = 0; map < 160; ++map) {
260 if (map >= layout.counts[state]) {
261 if (edits[state][map])
262 return absl::InvalidArgumentError(
263 "Sprite map is not supported in this game state");
267 rom, layout.tables[state] + map * 2));
268 if (edits[state][map]) {
269 const auto& replacement = *edits[state][map];
270 if (replacement.empty() || replacement.back() != 0xFF ||
271 replacement.size() % 3 != 1) {
272 return absl::InvalidArgumentError(
"Invalid overworld sprite stream");
274 for (
size_t i = 0; i + 1 < replacement.size(); i += 3) {
275 if (replacement[i] == 0xFF)
276 return absl::InvalidArgumentError(
277 "Sprite Y byte collides with terminator");
279 changed |= replacement != bytes;
282 lists.push_back(std::move(bytes));
287 std::map<OverworldSpriteBytes, uint16_t> shared;
288 std::vector<uint8_t> payload;
290 size_t list_index = 0;
291 for (
int state = 0; state < 3; ++state) {
292 std::vector<uint8_t> pointers;
293 for (
int map = 0; map < layout.counts[state]; ++map) {
294 const auto& bytes = lists[list_index++];
295 auto it = shared.find(bytes);
296 if (it == shared.end()) {
297 if (payload.size() + bytes.size() >
298 static_cast<size_t>(region_end - layout.data_start)) {
299 return absl::ResourceExhaustedError(
300 "Overworld sprite data exceeds reserved region; no bytes "
304 static_cast<uint16_t
>(layout.data_start + payload.size() - 0x40000);
305 it = shared.emplace(bytes, pointer).first;
306 payload.insert(payload.end(), bytes.begin(), bytes.end());
308 pointers.push_back(it->second & 0xFF);
309 pointers.push_back(it->second >> 8);
311 plan.push_back({layout.tables[state], std::move(pointers)});
313 plan.push_back({layout.data_start, std::move(payload)});