yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_item.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdint>
5#include <vector>
6
7#include "absl/status/status.h"
8#include "absl/status/statusor.h"
9#include "rom/rom.h"
10#include "rom/snes.h"
11#include "util/log.h"
12#include "util/macro.h"
13#include "zelda3/common.h"
16
17namespace yaze::zelda3 {
18
19absl::StatusOr<std::vector<OverworldItem>> LoadItems(
20 Rom* rom, std::vector<OverworldMap>& overworld_maps) {
21 std::vector<OverworldItem> items;
22
23 // Version 0x03 of the OW ASM added item support for the SW.
24 auto version = OverworldVersionHelper::GetVersion(*rom);
25
26 // Determine max number of overworld maps based on actual ASM version
27 // Only use expanded maps (0xA0) if v3+ ASM is actually applied
30 : 0x80;
31
32 ASSIGN_OR_RETURN(uint32_t pointer_snes,
34 uint32_t item_pointer_address =
35 SnesToPc(pointer_snes); // 0x1BC2F9 -> 0x0DC2F9
36
37 for (int i = 0; i < max_ow; i++) {
38 ASSIGN_OR_RETURN(uint8_t bank_byte,
40 int bank = bank_byte & 0x7F;
41
42 ASSIGN_OR_RETURN(uint8_t addr_low,
43 rom->ReadByte(item_pointer_address + (i * 2)));
44 ASSIGN_OR_RETURN(uint8_t addr_high,
45 rom->ReadByte(item_pointer_address + (i * 2) + 1));
46
47 uint32_t addr = (bank << 16) + // 1B
48 (addr_high << 8) + // F9
49 addr_low; // 3C
50 addr = SnesToPc(addr);
51
52 // Check if this is a large map and skip if not the parent
53 if (overworld_maps[i].area_size() != zelda3::AreaSizeEnum::SmallArea) {
54 if (overworld_maps[i].parent() != (uint8_t)i) {
55 continue;
56 }
57 }
58
59 while (true) {
60 ASSIGN_OR_RETURN(uint8_t b1, rom->ReadByte(addr));
61 ASSIGN_OR_RETURN(uint8_t b2, rom->ReadByte(addr + 1));
62 ASSIGN_OR_RETURN(uint8_t b3, rom->ReadByte(addr + 2));
63
64 if (b1 == 0xFF && b2 == 0xFF) {
65 break;
66 }
67
68 int p = (((b2 & 0x1F) << 8) + b1) >> 1;
69
70 int x = p % 0x40; // Use 0x40 instead of 64 to match ZS
71 int y = p >> 6;
72
73 int fakeID = i % 0x40; // Use modulo 0x40 to match ZS
74
75 int sy = fakeID / 8;
76 int sx = fakeID - (sy * 8);
77
78 items.emplace_back(b3, (uint16_t)i, (x * 16) + (sx * 512),
79 (y * 16) + (sy * 512), false);
80 auto size = items.size();
81
82 items[size - 1].game_x_ = (uint8_t)x;
83 items[size - 1].game_y_ = (uint8_t)y;
84 addr += 3;
85 }
86 }
87 return items;
88}
89
90absl::Status SaveItems(Rom* rom, const std::vector<OverworldItem>& items) {
91 const auto version = OverworldVersionHelper::GetVersion(*rom);
92 const int pointer_count = OverworldVersionHelper::SupportsAreaEnum(version)
95
96 std::vector<std::vector<OverworldItem>> room_items(pointer_count);
97
98 // Reset bomb door lookup table used by special item (0x86)
99 for (int i = 0; i < pointer_count; ++i) {
102 }
103
104 for (const OverworldItem& item : items) {
105 if (item.deleted)
106 continue;
107
108 const int map_index = static_cast<int>(item.room_map_id_);
109 if (map_index < 0 || map_index >= pointer_count) {
110 LOG_WARN(
111 "Overworld::SaveItems",
112 "Skipping item with map index %d outside pointer table (size=%d)",
113 map_index, pointer_count);
114 continue;
115 }
116
117 room_items[map_index].push_back(item);
118
119 if (item.id_ == 0x86) {
120 const int lookup_index = std::min(map_index, pointer_count - 1);
123 static_cast<uint16_t>((item.game_x_ + (item.game_y_ * 64)) * 2)));
124 }
125 }
126
127 // Prepare pointer reuse cache
128 std::vector<int> item_pointers(pointer_count, -1);
129 std::vector<int> item_pointers_reuse(pointer_count, -1);
130
131 for (int i = 0; i < pointer_count; ++i) {
132 item_pointers_reuse[i] = -1;
133 for (int ci = 0; ci < i; ++ci) {
134 if (room_items[i].empty()) {
135 item_pointers_reuse[i] = -2; // reuse empty terminator
136 break;
137 }
138
139 if (CompareItemsArrays(room_items[i], room_items[ci])) {
140 item_pointers_reuse[i] = ci;
141 break;
142 }
143 }
144 }
145
146 // Item data always lives in the vanilla data block
148 int empty_pointer = -1;
149
150 for (int i = 0; i < pointer_count; ++i) {
151 if (item_pointers_reuse[i] == -1) {
152 item_pointers[i] = data_pos;
153 for (const OverworldItem& item : room_items[i]) {
154 const uint16_t map_pos =
155 static_cast<uint16_t>(((item.game_y_ << 6) + item.game_x_) << 1);
156 const uint32_t data =
157 static_cast<uint32_t>(map_pos & 0xFF) |
158 (static_cast<uint32_t>((map_pos >> 8) & 0xFF) << 8) |
159 (static_cast<uint32_t>(item.id_) << 16);
160
161 RETURN_IF_ERROR(rom->WriteLong(data_pos, data));
162 data_pos += 3;
163 }
164
165 empty_pointer = data_pos;
166 RETURN_IF_ERROR(rom->WriteShort(data_pos, 0xFFFF));
167 data_pos += 2;
168 } else if (item_pointers_reuse[i] == -2) {
169 if (empty_pointer < 0) {
170 item_pointers[i] = data_pos;
171 empty_pointer = data_pos;
172 RETURN_IF_ERROR(rom->WriteShort(data_pos, 0xFFFF));
173 data_pos += 2;
174 } else {
175 item_pointers[i] = empty_pointer;
176 }
177 } else {
178 item_pointers[i] = item_pointers[item_pointers_reuse[i]];
179 }
180 }
181
182 if (data_pos > kOverworldItemsEndData) {
183 return absl::AbortedError("Too many items");
184 }
185
186 // Update pointer table metadata to the expanded location used by ZScream
191 static_cast<uint8_t>(
193
194 // Clear pointer table (write zero) to avoid stale values when pointer count
195 // shrinks
196 for (int i = 0; i < pointer_count; ++i) {
198 rom->WriteShort(zelda3::kOverworldItemsPointersNew + (i * 2), 0x0000));
199 }
200
201 for (int i = 0; i < pointer_count; ++i) {
202 const uint32_t snes_addr = PcToSnes(item_pointers[i]);
205 static_cast<uint16_t>(snes_addr & 0xFFFF)));
206 }
207
208 util::logf("End of Items : %d", data_pos);
209
210 return absl::OkStatus();
211}
212
213} // 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:28
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:586
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:518
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:628
absl::Status WriteLong(uint32_t addr, uint32_t value)
Definition rom.cc:632
absl::StatusOr< uint32_t > ReadLong(int offset) const
Definition rom.cc:533
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
static bool SupportsAreaEnum(OverworldVersion version)
Check if ROM supports area enum system (v3+ only)
#define LOG_WARN(category, format,...)
Definition log.h:107
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:115
Zelda 3 specific classes and functions.
absl::Status SaveItems(Rom *rom, const std::vector< OverworldItem > &items)
absl::StatusOr< std::vector< OverworldItem > > LoadItems(Rom *rom, std::vector< OverworldMap > &overworld_maps)
constexpr int overworldItemsAddressBank
constexpr int kNumOverworldMapItemPointers
constexpr int kNumOverworldMaps
Definition common.h:85
bool CompareItemsArrays(std::vector< OverworldItem > item_array1, std::vector< OverworldItem > item_array2)
constexpr int kOverworldItemsEndData
constexpr int kOverworldItemsStartDataNew
constexpr int overworldItemsAddress
constexpr int kOverworldBombDoorItemLocationsNew
constexpr int kOverworldItemsPointersNew
uint32_t PcToSnes(uint32_t addr)
Definition snes.h:17
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22