7#include "absl/strings/str_format.h"
24 std::vector<DungeonMap> dungeon_maps;
25 std::vector<std::array<uint8_t, kNumRooms>> current_floor_rooms_d;
26 std::vector<std::array<uint8_t, kNumRooms>> current_floor_gfx_d;
29 uint8_t nbr_basement_d;
32 current_floor_rooms_d.clear();
33 current_floor_gfx_d.clear();
45 nbr_basement_d &= 0x0F;
49 nbr_floor_d = nbr_floor_d >> 4;
51 total_floors_d = nbr_basement_d + nbr_floor_d;
54 for (
int i = 0; i < total_floors_d; i++) {
55 dungeon_map_labels[d].emplace_back();
57 std::array<uint8_t, kNumRooms> rdata;
58 std::array<uint8_t, kNumRooms> gdata;
65 gdata[j] = rdata[j] == 0x0F ? 0xFF : rom.
data()[pc_ptr_gfx++];
68 dungeon_map_labels[d][i][j] = label;
71 current_floor_gfx_d.push_back(gdata);
72 current_floor_rooms_d.push_back(rdata);
75 dungeon_maps.emplace_back(boss_room_d, nbr_floor_d, nbr_basement_d,
76 current_floor_rooms_d, current_floor_gfx_d);
83 const std::vector<DungeonMap>& dungeon_maps,
size_t rom_size,
84 bool relocated_tile16) {
87 return absl::InvalidArgumentError(
88 "A full set of 14 dungeon maps is required");
90 std::array<uint16_t, kNumDungeons> boss_floors;
91 boss_floors.fill(0xFFFF);
92 for (
size_t d = 0; d < dungeon_maps.size(); ++d) {
93 const auto& map = dungeon_maps[d];
94 const size_t count = map.nbr_of_floor + map.nbr_of_basement;
95 if (map.nbr_of_floor > 0x0F || map.nbr_of_basement > 0x0F ||
96 map.floor_rooms.size() != count || map.floor_gfx.size() != count) {
97 return absl::InvalidArgumentError(
98 absl::StrFormat(
"Invalid floor dimensions for dungeon %d", d));
100 if (map.boss_room == 0x000F || map.boss_room == 0xFFFF)
102 if (map.boss_room > 0xFF) {
103 return absl::InvalidArgumentError(
104 absl::StrFormat(
"Boss room cannot fit a map cell for dungeon %d", d));
106 for (
size_t f = 0; f < count; ++f) {
107 const auto& rooms = map.floor_rooms[f];
108 if (std::find(rooms.begin(), rooms.end(), map.boss_room) != rooms.end()) {
112 static_cast<uint8_t
>(
static_cast<int>(f) - map.nbr_of_basement);
116 if (boss_floors[d] == 0xFFFF) {
117 return absl::NotFoundError(
118 absl::StrFormat(
"Boss room not found for dungeon %d", d));
126 constexpr std::array<std::pair<int, int>, 3> regions = {{
133 auto allocate = [&](std::vector<uint8_t> bytes) -> absl::StatusOr<uint16_t> {
137 if (!bytes.empty()) {
139 for (
const auto& [begin, end] : regions) {
140 start = std::max(cursor, begin);
141 if (start < end && bytes.size() <=
static_cast<size_t>(end - start)) {
147 return absl::OutOfRangeError(
148 "Dungeon map streams exceed safe capacity");
153 start +
static_cast<int>(bytes.empty() ? 0 : bytes.size() - 1);
154 if ((snes >> 16) != 0x0A || (snes & 0xFFFF) < 0x8000 ||
156 static_cast<size_t>(last) >= rom_size) {
157 return absl::OutOfRangeError(
158 "Dungeon map stream violates ROM/bank bounds");
160 if (!bytes.empty()) {
161 cursor = start + bytes.size();
162 plan.
writes.push_back({start, std::move(bytes)});
164 return static_cast<uint16_t
>(snes);
166 auto word = [&](
int address, uint16_t value) {
169 {
static_cast<uint8_t
>(value),
static_cast<uint8_t
>(value >> 8)}});
171 std::array<std::vector<uint8_t>,
kNumDungeons> room_streams, gfx_streams;
172 std::array<uint16_t, kNumDungeons> room_pointers, gfx_pointers;
174 const auto& map = dungeon_maps[d];
175 for (
size_t f = 0; f < map.floor_rooms.size(); ++f) {
177 room_streams[d].push_back(map.floor_rooms[f][r]);
178 if (map.floor_rooms[f][r] != 0x0F)
179 gfx_streams[d].push_back(map.floor_gfx[f][r]);
192 const auto& map = dungeon_maps[d];
196 (map.nbr_of_floor << 4) | map.nbr_of_basement);
201 std::vector<std::pair<int, int>> extents;
202 for (
const auto& write : plan.
writes) {
203 const int end = write.pc_address + write.data.size();
204 if (write.pc_address < 0 ||
static_cast<size_t>(end) > rom_size ||
205 (
PcToSnes(write.pc_address) >> 16) != 0x0A ||
206 (
PcToSnes(end - 1) >> 16) != 0x0A) {
207 return absl::OutOfRangeError(
208 "Dungeon map write violates ROM/bank bounds");
210 extents.emplace_back(write.pc_address, end);
212 std::sort(extents.begin(), extents.end());
213 for (
size_t i = 1; i < extents.size(); ++i) {
214 if (extents[i].first < extents[i - 1].second)
215 return absl::InternalError(
"Overlapping dungeon map write plan");
221 Rom& rom,
const std::vector<DungeonMap>& dungeon_maps) {
223 if (marker != 0xB9 && marker != 0x20) {
224 return absl::FailedPreconditionError(
225 "Unsupported dungeon map Tile16 layout");
227 const bool relocated_tile16 = marker == 0x20;
229 const int64_t tile16_end =
231 if (relocated_tile16) {
232 if (tile16 < 0 || tile16_end > rom.
size() ||
234 return absl::FailedPreconditionError(
235 "Invalid relocated dungeon map Tile16 extent");
240 if (relocated_tile16) {
241 for (
const auto& write : plan.writes) {
242 const int64_t write_end =
static_cast<int64_t
>(write.pc_address) +
243 static_cast<int64_t
>(write.data.size());
244 if ((write.pc_address < tile16_end) && (write_end > tile16)) {
245 return absl::FailedPreconditionError(
246 "Dungeon map write overlaps relocated Tile16 artwork");
256 for (
const auto& write : plan.writes) {
260 return absl::OkStatus();
265 const std::vector<uint8_t>& gfx_data,
268 tile16_blockset.
map_size = {186, 186};
270 std::vector<uint8_t>(256 * 192, 0x00));
290 int sheet_offset = 212;
294 ComposeTile16(tile16_blockset, gfx_data, t1, t2, t3, t4, sheet_offset);
304 &tile16_blockset.
atlas);
306 return absl::OkStatus();
333 return absl::OkStatus();
338 std::array<gfx::Bitmap, 4>& sheets,
339 std::vector<uint8_t>& gfx_bin_data) {
341 if (bin_file.empty()) {
342 return absl::InternalError(
"No file selected");
345 std::ifstream file(bin_file, std::ios::binary);
346 if (!file.is_open()) {
347 return absl::InternalError(
"Failed to open file");
351 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
352 std::istreambuf_iterator<char>());
354 gfx_bin_data = converted_bin;
357 std::vector<std::vector<uint8_t>> gfx_sheets;
358 for (
int i = 0; i < 4; i++) {
359 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
360 converted_bin.begin() + ((i + 1) * 0x1000));
361 sheets[i] =
gfx::Bitmap(128, 32, 8, gfx_sheets[i]);
363 sheets[i].SetPalette(
374 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< uint8_t > ReadByte(int offset) const
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
absl::StatusOr< uint16_t > ReadWord(int offset) const
absl::Status WriteWord(int addr, uint16_t value)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
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.
SNES 16-bit tile metadata container.
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define ASSIGN_OR_RETURN(type_variable_name, expression)
uint16_t TileInfoToWord(TileInfo tile_info)
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
TileInfo WordToTileInfo(uint16_t word)
std::string HexByte(uint8_t byte, HexStringParams params)
Zelda 3 specific classes and functions.
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom, GameData *game_data, const std::vector< uint8_t > &gfx_data, bool bin_mode)
Load the dungeon map tile16 from the ROM.
constexpr int kDungeonMapDataStart
int GetDungeonMapTile16Expanded()
constexpr int kDungeonMapExpCheck
constexpr int kDungeonMapBossFloors
constexpr int kDungeonMapDataReservedEnd
absl::StatusOr< DungeonMapWritePlan > BuildDungeonMapWritePlan(const std::vector< DungeonMap > &dungeon_maps, size_t rom_size, bool relocated_tile16)
constexpr int kDungeonMapFloors
constexpr int kDungeonMapTile16
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom)
Save the dungeon map tile16 to the ROM.
constexpr int kDungeonMapDataReservedStart
absl::Status LoadDungeonMapGfxFromBinary(Rom &rom, GameData *game_data, gfx::Tilemap &tile16_blockset, std::array< gfx::Bitmap, 4 > &sheets, std::vector< uint8_t > &gfx_bin_data)
Load the dungeon map gfx from binary.
constexpr int kDungeonMapDataLimit
constexpr int kDungeonMapBossRooms
constexpr int kDungeonMapRoomsPtr
constexpr int kNumDungeonMapTile16
constexpr int kNumDungeons
std::array< std::vector< std::array< std::string, kNumRooms > >, kNumDungeons > DungeonMapLabels
absl::StatusOr< std::vector< DungeonMap > > LoadDungeonMaps(Rom &rom, DungeonMapLabels &dungeon_map_labels)
Load the dungeon maps from the ROM.
constexpr int kDungeonMapGfxPtr
absl::Status SaveDungeonMaps(Rom &rom, std::vector< DungeonMap > &dungeon_maps)
Save the dungeon maps to the ROM.
uint32_t PcToSnes(uint32_t addr)
uint32_t SnesToPc(uint32_t addr) noexcept
#define RETURN_IF_ERROR(expr)
PaletteGroup dungeon_main
auto mutable_palette(int i)
Tilemap structure for SNES tile-based graphics management.
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Pair map_size
Size of tilemap in tiles.
Bitmap atlas
Master bitmap containing all tiles.
std::vector< std::array< gfx::TileInfo, 4 > > tile_info
Tile metadata (4 tiles per 16x16)
std::vector< DungeonMapWrite > writes
gfx::PaletteGroupMap palette_groups