#include "overworld.h" #include #include #include #include #include #include #include "absl/container/flat_hash_map.h" #include "absl/status/status.h" #include "app/core/constants.h" #include "app/core/common.h" #include "app/gfx/bitmap.h" #include "app/gfx/compression.h" #include "app/gfx/snes_tile.h" #include "app/rom.h" #include "app/zelda3/overworld_map.h" #include "app/zelda3/sprite/sprite.h" namespace yaze { namespace app { namespace zelda3 { namespace { uint GetOwMapGfxHighPtr(const uchar *rom, int index, uint32_t map_high_ptr) { int p1 = (rom[map_high_ptr + 2 + (3 * index)] << 16) + (rom[map_high_ptr + 1 + (3 * index)] << 8) + (rom[map_high_ptr + (3 * index)]); return core::SnesToPc(p1); } uint GetOwMapGfxLowPtr(const uchar *rom, int index, uint32_t map_low_ptr) { int p2 = (rom[map_low_ptr + 2 + (3 * index)] << 16) + (rom[map_low_ptr + 1 + (3 * index)] << 8) + (rom[map_low_ptr + (3 * index)]); return core::SnesToPc(p2); } std::vector GetAllTile16(OWBlockset &tiles_used) { std::vector all_tile_16; // Ensure it's 64 bits int sx = 0; int sy = 0; int c = 0; for (int i = 0; i < kNumOverworldMaps; i++) { for (int y = 0; y < 32; y += 2) { for (int x = 0; x < 32; x += 2) { gfx::Tile32 current_tile( tiles_used[x + (sx * 32)][y + (sy * 32)], tiles_used[x + 1 + (sx * 32)][y + (sy * 32)], tiles_used[x + (sx * 32)][y + 1 + (sy * 32)], tiles_used[x + 1 + (sx * 32)][y + 1 + (sy * 32)]); all_tile_16.push_back(current_tile.GetPackedValue()); } } sx++; if (sx >= 8) { sy++; sx = 0; } c++; if (c >= 64) { sx = 0; sy = 0; c = 0; } } return all_tile_16; } absl::flat_hash_map parseFile(const std::string &filename) { absl::flat_hash_map resultMap; std::ifstream file(filename); if (!file.is_open()) { std::cerr << "Failed to open file: " << filename << std::endl; return resultMap; } std::string line; int currentKey; bool isHigh = true; while (getline(file, line)) { // Skip empty or whitespace-only lines if (line.find_first_not_of(" \t\r\n") == std::string::npos) { continue; } // If the line starts with "MAPDTH" or "MAPDTL", extract the ID. if (line.find("MAPDTH") == 0) { auto num_str = line.substr(6); // Extract ID after "MAPDTH" currentKey = std::stoi(num_str); isHigh = true; } else if (line.find("MAPDTL") == 0) { auto num_str = line.substr(6); // Extract ID after "MAPDTH" currentKey = std::stoi(num_str); isHigh = false; } else { // Check if the currentKey is already in the map. If not, initialize it. if (resultMap.find(currentKey) == resultMap.end()) { resultMap[currentKey] = MapData(); } // Split the line by commas and convert to uint8_t. std::stringstream ss(line); std::string valueStr; while (getline(ss, valueStr, ',')) { uint8_t value = std::stoi(valueStr, nullptr, 16); if (isHigh) { resultMap[currentKey].highData.push_back(value); } else { resultMap[currentKey].lowData.push_back(value); } } } } return resultMap; } } // namespace absl::Status Overworld::Load(ROM &rom) { rom_ = rom; AssembleMap32Tiles(); AssembleMap16Tiles(); RETURN_IF_ERROR(DecompressAllMapTiles()) for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index) overworld_maps_.emplace_back(map_index, rom_, tiles16); FetchLargeMaps(); LoadEntrances(); RETURN_IF_ERROR(LoadOverworldMaps()) if (flags()->kDrawOverworldSprites) { LoadSprites(); } is_loaded_ = true; return absl::OkStatus(); } OWBlockset &Overworld::GetMapTiles(int world_type) { switch (world_type) { case 0: return map_tiles_.light_world; case 1: return map_tiles_.dark_world; case 2: return map_tiles_.special_world; default: return map_tiles_.light_world; } } absl::Status Overworld::LoadOverworldMaps() { auto size = tiles16.size(); std::vector> futures; for (int i = 0; i < kNumOverworldMaps; ++i) { int world_type = 0; if (i >= 64 && i < 0x80) { world_type = 1; } else if (i >= 0x80) { world_type = 2; } futures.push_back(std::async(std::launch::async, [this, i, size, world_type]() { return overworld_maps_[i].BuildMap(size, game_state_, world_type, map_parent_, GetMapTiles(world_type)); })); } // Wait for all tasks to complete and check their results for (auto &future : futures) { absl::Status status = future.get(); if (!status.ok()) { return status; } } return absl::OkStatus(); } absl::Status Overworld::SaveOverworldMaps() { // Initialize map pointers std::fill(map_pointers1_id.begin(), map_pointers1_id.end(), -1); std::fill(map_pointers1_id.begin(), map_pointers1_id.end(), -1); // Compress and save each map int pos = 0x058000; for (int i = 0; i < 160; i++) { std::vector single_map_1(512); std::vector single_map_2(512); // Copy tiles32 data to single_map_1 and single_map_2 int npos = 0; for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { auto packed1 = tiles32[npos + (i * 256)].GetPackedValue(); auto packed2 = tiles32[npos + (i * 256) + 16].GetPackedValue(); single_map_1[npos] = static_cast(packed1 & 0xFF); single_map_2[npos] = static_cast(packed2 & 0xFF); npos++; } } // Compress single_map_1 and single_map_2 ASSIGN_OR_RETURN( auto a, gfx::lc_lz2::CompressOverworld(single_map_1.data(), 0, 256)) ASSIGN_OR_RETURN( auto b, gfx::lc_lz2::CompressOverworld(single_map_2.data(), 0, 256)) if (a.empty() || b.empty()) { return absl::AbortedError("Error compressing map gfx."); } // Save compressed data and pointers map_data_p1[i] = a; map_data_p2[i] = b; if (map_pointers1_id[i] == -1) { // Save compressed data and pointer for map1 std::copy(a.begin(), a.end(), map_data_p1[i].begin()); int snes_pos = core::PcToSnes(pos); map_pointers1[i] = snes_pos; RETURN_IF_ERROR(rom()->RunTransaction( WriteAction{kCompressedAllMap32PointersLow + 0 + 3 * i, uint8_t(snes_pos & 0xFF)}, WriteAction{kCompressedAllMap32PointersLow + 1 + 3 * i, uint8_t((snes_pos >> 8) & 0xFF)}, WriteAction{kCompressedAllMap32PointersLow + 2 + 3 * i, uint8_t((snes_pos >> 16) & 0xFF)}, WriteAction{pos, std::vector(a)})) pos += a.size(); } else { // Save pointer for map1 int snes_pos = map_pointers1[map_pointers1_id[i]]; RETURN_IF_ERROR(rom()->RunTransaction( WriteAction{kCompressedAllMap32PointersLow + 0 + 3 * i, uint8_t(snes_pos & 0xFF)}, WriteAction{kCompressedAllMap32PointersLow + 1 + 3 * i, uint8_t((snes_pos >> 8) & 0xFF)}, WriteAction{kCompressedAllMap32PointersLow + 2 + 3 * i, uint8_t((snes_pos >> 16) & 0xFF)})) } if (map_pointers2_id[i] == -1) { // Save compressed data and pointer for map2 std::copy(b.begin(), b.end(), map_data_p2[i].begin()); int snes_pos = core::PcToSnes(pos); map_pointers2[i] = snes_pos; RETURN_IF_ERROR(rom()->RunTransaction( WriteAction{kCompressedAllMap32PointersHigh + 0 + 3 * i, static_cast(snes_pos & 0xFF)}, WriteAction{kCompressedAllMap32PointersHigh + 1 + 3 * i, static_cast((snes_pos >> 8) & 0xFF)}, WriteAction{kCompressedAllMap32PointersHigh + 2 + 3 * i, static_cast((snes_pos >> 16) & 0xFF)}, WriteAction{pos, std::vector(b)})) pos += b.size(); } else { // Save pointer for map2 int snes_pos = map_pointers2[map_pointers2_id[i]]; RETURN_IF_ERROR(rom()->RunTransaction( WriteAction{kCompressedAllMap32PointersHigh + 0 + 3 * i, static_cast(snes_pos & 0xFF)}, WriteAction{kCompressedAllMap32PointersHigh + 1 + 3 * i, static_cast((snes_pos >> 8) & 0xFF)}, WriteAction{kCompressedAllMap32PointersHigh + 2 + 3 * i, static_cast((snes_pos >> 16) & 0xFF)})) } } // Check if too many maps data if (pos > 0x137FFF) { std::cerr << "Too many maps data " << std::hex << pos << std::endl; return absl::AbortedError("Too many maps data"); } // Save large maps RETURN_IF_ERROR(SaveLargeMaps()) return absl::OkStatus(); } absl::Status Overworld::SaveLargeMaps() { for (int i = 0; i < 0x40; i++) { int yPos = i / 8; int xPos = i % 8; int parentyPos = overworld_maps_[i].Parent() / 8; int parentxPos = overworld_maps_[i].Parent() % 8; std::unordered_map checkedMap; // Always write the map parent since it should not matter RETURN_IF_ERROR( rom()->Write(overworldMapParentId + i, overworld_maps_[i].Parent())) if (checkedMap.count(overworld_maps_[i].Parent()) > 0) { continue; } // If it's large then save parent pos * // 0x200 otherwise pos * 0x200 if (overworld_maps_[i].IsLargeMap()) { RETURN_IF_ERROR(rom()->RunTransaction( // Check 1 WriteAction{overworldMapSize + i, 0x20}, WriteAction{overworldMapSize + i + 1, 0x20}, WriteAction{overworldMapSize + i + 8, 0x20}, WriteAction{overworldMapSize + i + 9, 0x20}, // Check 2 WriteAction{overworldMapSizeHighByte + i, 0x03}, WriteAction{overworldMapSizeHighByte + i + 1, 0x03}, WriteAction{overworldMapSizeHighByte + i + 8, 0x03}, WriteAction{overworldMapSizeHighByte + i + 9, 0x03}, // Check 3 WriteAction{overworldScreenSize + i, 0x00}, WriteAction{overworldScreenSize + i + 64, 0x00}, WriteAction{overworldScreenSize + i + 1, 0x00}, WriteAction{overworldScreenSize + i + 1 + 64, 0x00}, WriteAction{overworldScreenSize + i + 8, 0x00}, WriteAction{overworldScreenSize + i + 8 + 64, 0x00}, WriteAction{overworldScreenSize + i + 9, 0x00}, WriteAction{overworldScreenSize + i + 9 + 64, 0x00}, // Check 4 WriteAction{OverworldScreenSizeForLoading + i, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 64, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 128, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 1, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 1 + 64, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 1 + 128, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 8, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 8 + 64, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 8 + 128, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 9, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 9 + 64, 0x04}, WriteAction{OverworldScreenSizeForLoading + i + 9 + 128, 0x04}, // Check 5 and 6 WriteAction{transition_target_north + (i * 2) + 2, (short)((parentyPos * 0x200) - 0xE0)}, // (short) is placed to reduce the int to // 2 bytes. WriteAction{transition_target_west + (i * 2) + 2, (short)((parentxPos * 0x200) - 0x100)}, // (short) is placed to reduce the int to 2 bytes. WriteAction{transition_target_north + (i * 2) + 16, (short)((parentyPos * 0x200) - 0xE0)}, WriteAction{transition_target_west + (i * 2) + 16, (short)((parentxPos * 0x200) - 0x100)}, // (short) is placed to reduce the int to 2 bytes. WriteAction{transition_target_north + (i * 2) + 18, (short)((parentyPos * 0x200) - 0xE0)}, WriteAction{transition_target_west + (i * 2) + 18, (short)((parentxPos * 0x200) - 0x100)}, // Check 7 and 8 WriteAction{overworldTransitionPositionX + (i * 2), (parentxPos * 0x200)}, WriteAction{overworldTransitionPositionY + (i * 2), (parentyPos * 0x200)}, WriteAction{overworldTransitionPositionX + (i * 2) + 2, (parentxPos * 0x200)}, WriteAction{overworldTransitionPositionY + (i * 2) + 2, (parentyPos * 0x200)}, WriteAction{overworldTransitionPositionX + (i * 2) + 16, (parentxPos * 0x200)}, WriteAction{overworldTransitionPositionY + (i * 2) + 16, (parentyPos * 0x200)}, WriteAction{overworldTransitionPositionX + (i * 2) + 18, (parentxPos * 0x200)}, WriteAction{overworldTransitionPositionY + (i * 2) + 18, (parentyPos * 0x200)}, // Check 9 // Always 0x0060 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2), 0x0060}, // Always 0x0060 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 2, 0x0060})) uint16_t lowerSubmaps; // If parentX == 0 then lower submaps == 0x0060 too if (parentxPos == 0) { lowerSubmaps = 0x0060; } else { // Otherwise lower submaps == 0x1060 lowerSubmaps = 0x1060; } RETURN_IF_ERROR(rom()->RunTransaction( WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 16, uint16_t(lowerSubmaps)}, WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 18, uint16_t(lowerSubmaps)}, WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 128, uint16_t(0x0080)}, // Always 0x0080 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 2 + 128, uint16_t(0x0080)}, // Always 0x0080 // Lower are always 8010 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 16 + 128, uint16_t(0x1080)}, // Always 0x1080 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 18 + 128, uint16_t(0x1080)}, // Always 0x1080 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 256, uint16_t(0x1800)}, // Always 0x1800 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 16 + 256, uint16_t(0x1800)}, // Always 0x1800 // Right side is always 1840 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 2 + 256, uint16_t(0x1840)}, // Always 0x1840 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 18 + 256, uint16_t(0x1840)}, // Always 0x1840 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 384, uint16_t(0x2000)}, // Always 0x2000 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 16 + 384, uint16_t(0x2000)}, // Always 0x2000 // Right side is always 0x2040 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 2 + 384, uint16_t(0x2040)}, // Always 0x2000 WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 18 + 384, uint16_t(0x2040)})) // Always 0x2000 checkedMap.emplace(i, 1); checkedMap.emplace((i + 1), 1); checkedMap.emplace((i + 8), 1); checkedMap.emplace((i + 9), 1); } else { RETURN_IF_ERROR(rom()->RunTransaction( WriteAction{overworldMapSize + i, 0x00}, WriteAction{overworldMapSizeHighByte + i, 0x01}, WriteAction{overworldScreenSize + i, 0x01}, WriteAction{overworldScreenSize + i + 64, 0x01}, WriteAction{OverworldScreenSizeForLoading + i, 0x02}, WriteAction{OverworldScreenSizeForLoading + i + 64, 0x02}, WriteAction{OverworldScreenSizeForLoading + i + 128, 0x02}, WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2), uint16_t(0x0060)}, WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 128, uint16_t(0x0040)}, WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 256, uint16_t(0x1800)}, WriteAction{OverworldScreenTileMapChangeByScreen + (i * 2) + 384, (0x1000)}, WriteAction{transition_target_north + (i * 2), uint16_t((yPos * 0x200) - 0xE0)}, WriteAction{transition_target_west + (i * 2), uint16_t((xPos * 0x200) - 0x100)}, WriteAction{overworldTransitionPositionX + (i * 2), uint16_t(xPos * 0x200)}, WriteAction{overworldTransitionPositionY + (i * 2), uint16_t(yPos * 0x200)})) checkedMap.emplace(i, 1); } } return absl::OkStatus(); } bool Overworld::CreateTile32Tilemap(bool only_show) { tiles32_unique_.clear(); tiles32.clear(); OWBlockset *tiles_used; for (int i = 0; i < kNumOverworldMaps; i++) { if (i < 64) { tiles_used = &map_tiles_.light_world; } else if (i < 128 && i >= 64) { tiles_used = &map_tiles_.dark_world; } else { tiles_used = &map_tiles_.special_world; } std::vector all_tile_16 = GetAllTile16(*tiles_used); std::vector unique_tiles(all_tile_16); // Ensure it's 64 bits std::sort(unique_tiles.begin(), unique_tiles.end()); unique_tiles.erase(std::unique(unique_tiles.begin(), unique_tiles.end()), unique_tiles.end()); // Ensure it's 64 bits std::unordered_map all_tiles_indexed; for (size_t j = 0; j < unique_tiles.size(); j++) { all_tiles_indexed.insert({unique_tiles[j], static_cast(j)}); } for (int j = 0; j < NumberOfMap32; j++) { tiles32.push_back(all_tiles_indexed[all_tile_16[j]]); } for (const auto &tile : unique_tiles) { tiles32_unique_.push_back(static_cast(tile)); } while (tiles32_unique_.size() % 4 != 0) { gfx::Tile32 padding_tile(420, 420, 420, 420); tiles32_unique_.push_back(padding_tile.GetPackedValue()); } } if (only_show) { std::cout << "Number of unique Tiles32: " << tiles32_unique_.size() << " Out of: " << LimitOfMap32 << std::endl; } else if (tiles32_unique_.size() > LimitOfMap32) { std::cerr << "Number of unique Tiles32: " << tiles32_unique_.size() << " Out of: " << LimitOfMap32 << "\nUnique Tile32 count exceed the limit" << "\nThe ROM Has not been saved" << "\nYou can fill maps with grass tiles to free some space" << "\nOr use the option Clear DW Tiles in the Overworld Menu" << std::endl; return true; } std::cout << "Number of unique Tiles32: " << tiles32_unique_.size() << " Saved:" << tiles32_unique_.size() << " Out of: " << LimitOfMap32 << std::endl; int v = tiles32_unique_.size(); for (int i = v; i < LimitOfMap32; i++) { gfx::Tile32 padding_tile(420, 420, 420, 420); tiles32_unique_.push_back(padding_tile.GetPackedValue()); } return false; } absl::Status Overworld::SaveMap16Tiles() { int tpos = kMap16Tiles; // 3760 for (int i = 0; i < NumberOfMap16; i += 1) { RETURN_IF_ERROR(rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile0_))) tpos += 2; RETURN_IF_ERROR(rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile1_))) tpos += 2; RETURN_IF_ERROR(rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile2_))) tpos += 2; RETURN_IF_ERROR(rom()->WriteShort(tpos, TileInfoToShort(tiles16[i].tile3_))) tpos += 2; } return absl::OkStatus(); } absl::Status Overworld::SaveMap32Tiles() { constexpr int kMaxUniqueTiles = 0x4540; constexpr int kTilesPer32x32Tile = 6; constexpr int kQuadrantsPer32x32Tile = 4; if (tiles32_unique_.size() % kTilesPer32x32Tile != 0) { return absl::InvalidArgumentError("Invalid number of unique tiles."); } int unique_tile_index = 0; int num_unique_tiles = tiles32_unique_.size(); int num_32x32_tiles = num_unique_tiles / kTilesPer32x32Tile; if (num_32x32_tiles > kMaxUniqueTiles / kQuadrantsPer32x32Tile) { return absl::AbortedError("Too many unique tile32 definitions."); } for (int i = 0; i < num_32x32_tiles; ++i) { int base_addr = rom()->version_constants().kMap32TileTL + i * kQuadrantsPer32x32Tile; auto write_quadrant_to_rom = [&](int quadrant, auto get_tile) -> absl::Status { for (int j = 0; j < kQuadrantsPer32x32Tile; ++j) { int tile_index = unique_tile_index + j; const gfx::Tile32 &tile = tiles32_unique_[tile_index]; RETURN_IF_ERROR( rom()->Write(base_addr + quadrant + j, get_tile(tile) & 0xFF)); } int tile0 = get_tile(tiles32_unique_[unique_tile_index]); int tile1 = get_tile(tiles32_unique_[unique_tile_index + 1]); int tile2 = get_tile(tiles32_unique_[unique_tile_index + 2]); int tile3 = get_tile(tiles32_unique_[unique_tile_index + 3]); RETURN_IF_ERROR( rom()->Write(base_addr + quadrant + 4, ((tile0 >> 4) & 0xF0) | ((tile1 >> 8) & 0x0F))); RETURN_IF_ERROR( rom()->Write(base_addr + quadrant + 5, ((tile2 >> 4) & 0xF0) | ((tile3 >> 8) & 0x0F))); return absl::OkStatus(); }; RETURN_IF_ERROR(write_quadrant_to_rom( 0, [](const gfx::Tile32 &t) { return t.tile0_; })); RETURN_IF_ERROR(write_quadrant_to_rom( 1, [](const gfx::Tile32 &t) { return t.tile1_; })); RETURN_IF_ERROR(write_quadrant_to_rom( 2, [](const gfx::Tile32 &t) { return t.tile2_; })); RETURN_IF_ERROR(write_quadrant_to_rom( 3, [](const gfx::Tile32 &t) { return t.tile3_; })); unique_tile_index += kTilesPer32x32Tile; } return absl::OkStatus(); } uint16_t Overworld::GenerateTile32(int index, int quadrant, int dimension) { // The addresses of the four 32x32 pixel tiles in the ROM. const uint32_t map32address[4] = {rom()->version_constants().kMap32TileTL, rom()->version_constants().kMap32TileTR, rom()->version_constants().kMap32TileBL, rom()->version_constants().kMap32TileBR}; return (ushort)(rom_[map32address[dimension] + quadrant + (index)] + (((rom_[map32address[dimension] + (index) + (quadrant <= 1 ? 4 : 5)] >> (quadrant % 2 == 0 ? 4 : 0)) & 0x0F) * 256)); } void Overworld::AssembleMap32Tiles() { // Loop through each 32x32 pixel tile in the ROM. for (int i = 0; i < 0x33F0; i += 6) { // Loop through each quadrant of the 32x32 pixel tile. for (int k = 0; k < 4; k++) { // Generate the 16-bit tile for the current quadrant of the current // 32x32 pixel tile. uint16_t tl = GenerateTile32(i, k, (int)Dimension::map32TilesTL); uint16_t tr = GenerateTile32(i, k, (int)Dimension::map32TilesTR); uint16_t bl = GenerateTile32(i, k, (int)Dimension::map32TilesBL); uint16_t br = GenerateTile32(i, k, (int)Dimension::map32TilesBR); // Add the generated 16-bit tiles to the tiles32 vector. tiles32.push_back(gfx::Tile32(tl, tr, bl, br)); } } // Initialize the light_world, dark_world, and special_world vectors with // the appropriate number of tiles. map_tiles_.light_world.resize(kTile32Num); map_tiles_.dark_world.resize(kTile32Num); map_tiles_.special_world.resize(kTile32Num); for (int i = 0; i < kTile32Num; i++) { map_tiles_.light_world[i].resize(kTile32Num); map_tiles_.dark_world[i].resize(kTile32Num); map_tiles_.special_world[i].resize(kTile32Num); } } void Overworld::AssembleMap16Tiles() { int tpos = kMap16Tiles; for (int i = 0; i < 4096; i += 1) { auto t0 = gfx::GetTilesInfo(rom()->toint16(tpos)); tpos += 2; auto t1 = gfx::GetTilesInfo(rom()->toint16(tpos)); tpos += 2; auto t2 = gfx::GetTilesInfo(rom()->toint16(tpos)); tpos += 2; auto t3 = gfx::GetTilesInfo(rom()->toint16(tpos)); tpos += 2; tiles16.emplace_back(t0, t1, t2, t3); } } void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos, OWBlockset &world) { int position_x1 = (x * 2) + (sx * 32); int position_y1 = (y * 2) + (sy * 32); int position_x2 = (x * 2) + 1 + (sx * 32); int position_y2 = (y * 2) + 1 + (sy * 32); world[position_x1][position_y1] = tiles32[tpos].tile0_; world[position_x2][position_y1] = tiles32[tpos].tile1_; world[position_x1][position_y2] = tiles32[tpos].tile2_; world[position_x2][position_y2] = tiles32[tpos].tile3_; } void Overworld::OrganizeMapTiles(Bytes &bytes, Bytes &bytes2, int i, int sx, int sy, int &ttpos) { for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { auto tidD = (ushort)((bytes2[ttpos] << 8) + bytes[ttpos]); if (int tpos = tidD; tpos < tiles32.size()) { if (i < 64) { AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.light_world); } else if (i < 128 && i >= 64) { AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.dark_world); } else { AssignWorldTiles(x, y, sx, sy, tpos, map_tiles_.special_world); } } ttpos += 1; } } } absl::Status Overworld::DecompressAllMapTiles() { int lowest = 0x0FFFFF; int highest = 0x0F8000; int sx = 0; int sy = 0; int c = 0; for (int i = 0; i < 160; i++) { auto p1 = GetOwMapGfxHighPtr( rom()->data(), i, rom()->version_constants().kCompressedAllMap32PointersHigh); auto p2 = GetOwMapGfxLowPtr( rom()->data(), i, rom()->version_constants().kCompressedAllMap32PointersLow); int ttpos = 0; if (p1 >= highest) { highest = p1; } if (p2 >= highest) { highest = p2; } if (p1 <= lowest && p1 > 0x0F8000) { lowest = p1; } if (p2 <= lowest && p2 > 0x0F8000) { lowest = p2; } ASSIGN_OR_RETURN(auto bytes, gfx::lc_lz2::DecompressOverworld(rom()->data(), p2, 1000)) ASSIGN_OR_RETURN(auto bytes2, gfx::lc_lz2::DecompressOverworld(rom()->data(), p1, 1000)) OrganizeMapTiles(bytes, bytes2, i, sx, sy, ttpos); sx++; if (sx >= 8) { sy++; sx = 0; } c++; if (c >= 64) { sx = 0; sy = 0; c = 0; } } return absl::OkStatus(); } absl::Status Overworld::DecompressProtoMapTiles(const std::string &filename) { proto_map_data_ = parseFile(filename); int sx = 0; int sy = 0; int c = 0; for (int i = 0; i < proto_map_data_.size(); i++) { int ttpos = 0; ASSIGN_OR_RETURN(auto bytes, gfx::lc_lz2::DecompressOverworld( proto_map_data_[i].lowData, 0, proto_map_data_[i].lowData.size())) ASSIGN_OR_RETURN(auto bytes2, gfx::lc_lz2::DecompressOverworld( proto_map_data_[i].highData, 0, proto_map_data_[i].highData.size())) OrganizeMapTiles(bytes, bytes2, i, sx, sy, ttpos); sx++; if (sx >= 8) { sy++; sx = 0; } c++; if (c >= 64) { sx = 0; sy = 0; c = 0; } } return absl::OkStatus(); } void Overworld::FetchLargeMaps() { for (int i = 128; i < 145; i++) { map_parent_[i] = 0; } map_parent_[128] = 128; map_parent_[129] = 129; map_parent_[130] = 129; map_parent_[137] = 129; map_parent_[138] = 129; map_parent_[136] = 136; overworld_maps_[136].SetLargeMap(false); std::vector mapChecked; mapChecked.reserve(0x40); for (int i = 0; i < 64; i++) { mapChecked[i] = false; } int xx = 0; int yy = 0; while (true) { if (int i = xx + (yy * 8); mapChecked[i] == false) { if (overworld_maps_[i].IsLargeMap() == true) { mapChecked[i] = true; map_parent_[i] = (uchar)i; map_parent_[i + 64] = (uchar)(i + 64); mapChecked[i + 1] = true; map_parent_[i + 1] = (uchar)i; map_parent_[i + 65] = (uchar)(i + 64); mapChecked[i + 8] = true; map_parent_[i + 8] = (uchar)i; map_parent_[i + 72] = (uchar)(i + 64); mapChecked[i + 9] = true; map_parent_[i + 9] = (uchar)i; map_parent_[i + 73] = (uchar)(i + 64); xx++; } else { map_parent_[i] = (uchar)i; map_parent_[i + 64] = (uchar)(i + 64); mapChecked[i] = true; } } xx++; if (xx >= 8) { xx = 0; yy += 1; if (yy >= 8) { break; } } } } void Overworld::LoadEntrances() { for (int i = 0; i < 129; i++) { short mapId = rom()->toint16(OWEntranceMap + (i * 2)); ushort mapPos = rom()->toint16(OWEntrancePos + (i * 2)); uchar entranceId = (rom_[OWEntranceEntranceId + i]); int p = mapPos >> 1; int x = (p % 64); int y = (p >> 6); bool deleted = false; if (mapPos == 0xFFFF) { deleted = true; } all_entrances_.emplace_back( (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512), (y * 16) + (((mapId % 64) / 8) * 512), entranceId, mapId, mapPos, deleted); } for (int i = 0; i < 0x13; i++) { auto mapId = (short)((rom_[OWHoleArea + (i * 2) + 1] << 8) + (rom_[OWHoleArea + (i * 2)])); auto mapPos = (short)((rom_[OWHolePos + (i * 2) + 1] << 8) + (rom_[OWHolePos + (i * 2)])); uchar entranceId = (rom_[OWHoleEntrance + i]); int p = (mapPos + 0x400) >> 1; int x = (p % 64); int y = (p >> 6); all_holes_.emplace_back( (x * 16) + (((mapId % 64) - (((mapId % 64) / 8) * 8)) * 512), (y * 16) + (((mapId % 64) / 8) * 512), entranceId, mapId, (ushort)(mapPos + 0x400), true); } } void Overworld::LoadSprites() { for (int i = 0; i < 3; i++) { all_sprites_.emplace_back(); } for (int i = 0; i < 64; i++) { all_sprites_[0].emplace_back(); } for (int i = 0; i < 144; i++) { all_sprites_[1].emplace_back(); } for (int i = 0; i < 144; i++) { all_sprites_[2].emplace_back(); } LoadSpritesFromMap(overworldSpritesBegining, 64, 0); LoadSpritesFromMap(overworldSpritesZelda, 144, 1); LoadSpritesFromMap(overworldSpritesAgahnim, 144, 2); } void Overworld::LoadSpritesFromMap(int spriteStart, int spriteCount, int spriteIndex) { for (int i = 0; i < spriteCount; i++) { if (map_parent_[i] != i) continue; int ptrPos = spriteStart + (i * 2); int spriteAddress = core::SnesToPc((0x09 << 0x10) + rom()->toint16(ptrPos)); while (true) { uchar b1 = rom_[spriteAddress]; uchar b2 = rom_[spriteAddress + 1]; uchar b3 = rom_[spriteAddress + 2]; if (b1 == 0xFF) break; int editorMapIndex = i; if (editorMapIndex >= 128) editorMapIndex -= 128; else if (editorMapIndex >= 64) editorMapIndex -= 64; int mapY = (editorMapIndex / 8); int mapX = (editorMapIndex % 8); int realX = ((b2 & 0x3F) * 16) + mapX * 512; int realY = ((b1 & 0x3F) * 16) + mapY * 512; auto graphics_bytes = overworld_maps_[i].AreaGraphics(); all_sprites_[spriteIndex][i].InitSprite(graphics_bytes, (uchar)i, b3, (uchar)(b2 & 0x3F), (uchar)(b1 & 0x3F), realX, realY); all_sprites_[spriteIndex][i].Draw(); spriteAddress += 3; } } } absl::Status Overworld::LoadPrototype(ROM &rom, const std::string &tilemap_filename) { rom_ = rom; AssembleMap32Tiles(); AssembleMap16Tiles(); RETURN_IF_ERROR(DecompressProtoMapTiles(tilemap_filename)) for (int map_index = 0; map_index < kNumOverworldMaps; ++map_index) overworld_maps_.emplace_back(map_index, rom_, tiles16); FetchLargeMaps(); LoadEntrances(); auto size = tiles16.size(); std::vector> futures; for (int i = 0; i < kNumOverworldMaps; ++i) { futures.push_back(std::async(std::launch::async, [this, i, size]() { if (i < 64) { return overworld_maps_[i].BuildMap(size, game_state_, 0, map_parent_, map_tiles_.light_world); } else if (i < 0x80 && i >= 0x40) { return overworld_maps_[i].BuildMap(size, game_state_, 1, map_parent_, map_tiles_.dark_world); } else { return overworld_maps_[i].BuildMap(size, game_state_, 2, map_parent_, map_tiles_.special_world); } })); } // Wait for all tasks to complete and check their results for (auto &future : futures) { absl::Status status = future.get(); if (!status.ok()) { return status; } } // LoadSprites(); is_loaded_ = true; return absl::OkStatus(); } } // namespace zelda3 } // namespace app } // namespace yaze