36#include <boost/filesystem/directory.hpp>
37#include <boost/filesystem/operations.hpp>
38#include <boost/filesystem/path.hpp>
41#include <unordered_map>
45#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
120#define CASC_LOCALES_COUNT 17
137 CASC_LOCALE_ENUS | CASC_LOCALE_ENGB,
147 CASC_LOCALE_PTBR | CASC_LOCALE_PTPT,
154 if (fs::exists(path))
157 boost::system::error_code err;
158 if (!fs::create_directory(path, err) || err)
159 throw std::runtime_error(
"Unable to create directory" + path.string());
166 "%s -[var] [value]\n"\
167 "-i set input path\n"\
168 "-o set output path\n"\
169 "-e extract only MAP(1)/DBC(2)/Camera(4)/gt(8) - standard: all(15)\n"\
170 "-f height stored as int (less map size but lost some accuracy) 1 by default\n"\
172 "-p which installed product to open (wow/wowt/wow_beta)\n"\
173 "-c use remote casc\n"\
174 "-r set remote casc region - standard: eu\n"\
175 "Example: %s -f 0 -i \"c:\\games\\game\"\n", prg, prg);
181 for (
int c = 1; c < argc; ++c)
191 if (arg[c][0] !=
'-')
197 if (c + 1 < argc && strlen(arg[c + 1]))
198 input_path = boost::filesystem::path(arg[c++ + 1]);
203 if (c + 1 < argc && strlen(arg[c + 1]))
204 output_path = boost::filesystem::path(arg[c++ + 1]);
236 if (c + 1 < argc && strlen(arg[c + 1]))
248 if (c + 1 < argc && strlen(arg[c + 1]))
266 db2->
Load(source, loadInfo);
268 catch (std::exception
const& e)
277 printf(
"Read Map.db2 file...\n");
284 std::unordered_map<uint32, std::size_t> idToIndex;
304 if (itr != idToIndex.end())
322 printf(
"Read LiquidMaterial.db2 file...\n");
347 printf(
"Read LiquidObject.db2 file...\n");
371 printf(
"Read LiquidType.db2 file...\n");
396 printf(
"Read CinematicCamera.db2 file...\n");
422 return 255 / maxDiff;
427 return 65535 / maxDiff;
441 auto liquidMaterial =
LiquidMaterials.find(liquidType->second.MaterialID);
451 for (
int32 i = 0; i < 8; i++)
453 for (
int32 j = 0; j < 8; j++)
455 int32 holeIdxL = (i / 2) * 4 + (j / 2);
456 if (((lowResHoles >> holeIdxL) & 1) == 1)
457 hiResHoles[i] |= (1 << j);
461 return advstd::bit_cast<uint64>(hiResHoles) != 0;
464template <
typename T, std::
size_t N>
467template <
typename T, std::
size_t N>
470 (void)::fwrite(&data[0][0],
sizeof(T), N * N, f);
479 map.buildMagic = build;
501 bool hasHoles =
false;
502 bool hasFlightBox =
false;
536 V9[cy][cx] = mcnk->
ypos;
546 V8[cy][cx] = mcnk->
ypos;
590 if (liquid->
flags[y][x] != 0x0F)
592 liquid_show[cy][cx] =
true;
593 if (!ignoreDeepWater && liquid->
flags[y][x] & (1 << 7))
601 if (c_flag & (1 << 2))
603 liquid_entry[mcnk->
iy][mcnk->
ix] = 1;
606 if (c_flag & (1 << 3))
608 liquid_entry[mcnk->
iy][mcnk->
ix] = 2;
611 if (c_flag & (1 << 4))
613 liquid_entry[mcnk->
iy][mcnk->
ix] = 3;
618 fprintf(stderr,
"Wrong liquid detect in MCLQ chunk");
633 if (!(mcnk->
flags & 0x10000))
642 if (advstd::bit_cast<uint64>(holes[mcnk->
iy][mcnk->
ix]) != 0)
660 auto liquidTypeEntry =
LiquidTypes.find(liquid_entry[i][j]);
680 liquid_show[cy][cx] =
true;
687 switch (liquidTypeEntry->second.SoundBank)
694 printf(
"\nCan't find Liquid type %u for map %s [%u,%u]\nchunk %d,%d\n", h->
LiquidType, mapName.c_str(), gx, gy, i, j);
699 printf(
"Wrong liquid detect in MH2O chunk");
719 memcpy(flight_box_max, &mfbo->
max,
sizeof(flight_box_max));
720 memcpy(flight_box_min, &mfbo->
min,
sizeof(flight_box_min));
727 uint16 areaId = area_ids[0][0];
728 bool fullAreaData =
false;
733 if (area_ids[y][x] != areaId)
742 map.areaMapOffset =
sizeof(map);
751 map.areaMapSize +=
sizeof(area_ids);
762 float maxHeight = -20000;
763 float minHeight = 20000;
769 if (maxHeight < h) maxHeight = h;
770 if (minHeight > h) minHeight = h;
778 if (maxHeight < h) maxHeight = h;
779 if (minHeight > h) minHeight = h;
800 map.heightMapOffset = map.areaMapOffset + map.areaMapSize;
809 if (maxHeight == minHeight)
819 map.heightMapSize +=
sizeof(flight_box_max) +
sizeof(flight_box_min);
829 float diff = maxHeight - minHeight;
847 uint8_V8[y][x] =
uint8((V8[y][x] - minHeight) * step + 0.5f);
850 uint8_V9[y][x] =
uint8((V9[y][x] - minHeight) * step + 0.5f);
851 map.heightMapSize+=
sizeof(uint8_V9) +
sizeof(uint8_V8);
857 uint16_V8[y][x] =
uint16((V8[y][x] - minHeight) * step + 0.5f);
860 uint16_V9[y][x] =
uint16((V9[y][x] - minHeight) * step + 0.5f);
861 map.heightMapSize+=
sizeof(uint16_V9) +
sizeof(uint16_V8);
864 map.heightMapSize+=
sizeof(V9) +
sizeof(V8);
870 uint16 firstLiquidType = liquid_entry[0][0];
872 bool fullType =
false;
877 if (liquid_entry[y][x] != firstLiquidType || liquid_flags[y][x] != firstLiquidFlag)
892 map.liquidMapOffset = 0;
893 map.liquidMapSize = 0;
898 int maxX = 0, maxY = 0;
905 if (liquid_show[y][x])
907 if (minX > x) minX = x;
908 if (maxX < x) maxX = x;
909 if (minY > y) minY = y;
910 if (maxY < y) maxY = y;
911 float h = liquid_height[y][x];
912 if (maxHeight < h) maxHeight = h;
913 if (minHeight > h) minHeight = h;
922 map.liquidMapOffset = map.heightMapOffset + map.heightMapSize;
929 liquidHeader.
width = maxX - minX + 1 + 1;
930 liquidHeader.
height = maxY - minY + 1 + 1;
933 if (minY > maxY || minX > maxX)
936 if (maxHeight == minHeight)
952 map.liquidMapSize +=
sizeof(liquid_entry) +
sizeof(liquid_flags);
955 map.liquidMapSize +=
sizeof(
float)*liquidHeader.
width*liquidHeader.
height;
960 if (map.liquidMapOffset)
961 map.holesOffset = map.liquidMapOffset + map.liquidMapSize;
963 map.holesOffset = map.heightMapOffset + map.heightMapSize;
965 map.holesSize =
sizeof(holes);
974 auto outFile = Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(outputPath.c_str(),
"wb"));
977 printf(
"Can't create the output file '%s'\n", outputPath.c_str());
981 fwrite(&map,
sizeof(map), 1, outFile.get());
983 fwrite(&areaHeader,
sizeof(areaHeader), 1, outFile.get());
988 fwrite(&heightHeader,
sizeof(heightHeader), 1, outFile.get());
1015 if (map.liquidMapOffset)
1017 fwrite(&liquidHeader,
sizeof(liquidHeader), 1, outFile.get());
1025 for (
int y = 0; y < liquidHeader.
height; y++)
1026 fwrite(&liquid_height[y + liquidHeader.
offsetY][liquidHeader.
offsetX],
sizeof(
float), liquidHeader.
width, outFile.get());
1036bool ConvertADT(std::string
const& fileName, std::string
const& mapName, std::string
const& outputPath,
int gx,
int gy,
uint32 build,
bool ignoreDeepWater)
1043 return ConvertADT(adt, mapName, outputPath, gx, gy, build, ignoreDeepWater);
1046bool ConvertADT(
uint32 fileDataId, std::string
const& mapName, std::string
const& outputPath,
int gx,
int gy,
uint32 build,
bool ignoreDeepWater)
1053 return ConvertADT(adt, mapName, outputPath, gx, gy, build, ignoreDeepWater);
1070 return (x >= 39 && x <= 40 && y >= 24 && y <= 26) || (x >= 41 && x <= 46 && y >= 18 && y <= 26);
1077 return x == 43 && (y == 39 || y == 40);
1085 std::string outputFileName;
1087 printf(
"Extracting maps...\n");
1097 printf(
"Convert map files\n");
1098 for (std::size_t z = 0; z <
map_ids.size(); ++z)
1131 printf(
"Processing........................%d%%\r", (100 * (y + 1)) /
WDT_MAP_SIZE);
1139 fwrite(&build,
sizeof(build), 1, tileList.get());
1140 fwrite(existingTiles.to_string().c_str(), 1, existingTiles.size(), tileList.get());
1152 printf(
"Can't read file size of '%s'\n", filename.c_str());
1156 auto output = Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(filename.c_str(),
"wb"));
1159 printf(
"Can't create the output file '%s'\n", filename.c_str());
1163 char buffer[0x10000];
1169 if (!fileInArchive->
ReadFile(buffer, std::min<uint32>(fileSize,
sizeof(buffer)), &readBytes))
1171 printf(
"Can't read file '%s'\n", filename.c_str());
1173 boost::filesystem::remove(filename);
1180 fwrite(buffer, 1, readBytes, output.get());
1181 fileSize -= readBytes;
1190bool ExtractDB2File(
uint32 fileDataId,
char const* cascFileName,
int locale, boost::filesystem::path
const& outputPath)
1202 printf(
"Can't read file size of '%s'\n", cascFileName);
1211 catch (std::exception
const& e)
1213 printf(
"Can't read DB2 headers of '%s': %s\n", cascFileName, e.what());
1217 std::string outputFileName = outputPath.string();
1218 auto output = Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(outputFileName.c_str(),
"wb"));
1221 printf(
"Can't create the output file '%s'\n", outputFileName.c_str());
1227 int64 posAfterHeaders = 0;
1228 posAfterHeaders += fwrite(&header, 1,
sizeof(header), output.get());
1237 posAfterHeaders += fwrite(§ionHeader, 1,
sizeof(sectionHeader), output.get());
1240 char buffer[0x10000];
1241 uint32 readBatchSize = 0x10000;
1250 printf(
"Can't read file '%s'\n", outputFileName.c_str());
1252 boost::filesystem::remove(outputPath);
1259 fwrite(buffer, 1, readBytes, output.get());
1260 fileSize -= readBytes;
1261 readBatchSize = 0x10000;
1272 if (
char const* lastSep = strrchr(cascPath,
'\\'))
1280 printf(
"Extracting dbc/db2 files...\n");
1287 printf(
"locale %s output path %s\n",
localeNames[l], localePath.string().c_str());
1292 boost::filesystem::path filePath = localePath / db2.Name;
1294 if (!boost::filesystem::exists(filePath))
1295 if (
ExtractDB2File(db2.FileDataId, db2.Name, l, filePath.string()))
1300 printf(
"Extracted %u files\n\n", count);
1305 printf(
"Extracting camera files...\n");
1310 boost::filesystem::path outputPath =
output_path /
"cameras";
1314 printf(
"output path %s\n", outputPath.string().c_str());
1320 std::unique_ptr<CASC::File> cameraFile(
CascStorage->OpenFile(cameraFileDataId, CASC_LOCALE_NONE));
1323 boost::filesystem::path filePath = outputPath /
Trinity::StringFormat(
"FILE{:08X}.xxx", cameraFileDataId);
1325 if (!boost::filesystem::exists(filePath))
1326 if (
ExtractFile(cameraFile.get(), filePath.string()))
1333 printf(
"Extracted %u camera files\n", count);
1338 printf(
"Extracting game tables...\n");
1340 boost::filesystem::path outputPath =
output_path /
"gt";
1344 printf(
"output path %s\n", outputPath.string().c_str());
1348 { .
FileDataId = 1582086, .Name =
"ArtifactKnowledgeMultiplier.txt" },
1349 { .FileDataId = 1391662, .Name =
"ArtifactLevelXP.txt" },
1350 { .FileDataId = 1391663, .Name =
"BarberShopCostBase.txt" },
1351 { .FileDataId = 1391664, .Name =
"BaseMp.txt" },
1352 { .FileDataId = 4494528, .Name =
"BaseProfessionRatings.txt" },
1353 { .FileDataId = 1391665, .Name =
"BattlePetTypeDamageMod.txt" },
1354 { .FileDataId = 1391666, .Name =
"BattlePetXP.txt" },
1355 { .FileDataId = 1391669, .Name =
"CombatRatings.txt" },
1356 { .FileDataId = 1391670, .Name =
"CombatRatingsMultByILvl.txt" },
1357 { .FileDataId = 1391671, .Name =
"HonorLevel.txt" },
1358 { .FileDataId = 1391642, .Name =
"HpPerSta.txt" },
1359 { .FileDataId = 2012881, .Name =
"ItemLevelByLevel.txt" },
1360 { .FileDataId = 1726830, .Name =
"ItemLevelSquish.txt" },
1361 { .FileDataId = 1391643, .Name =
"ItemSocketCostPerLevel.txt" },
1362 { .FileDataId = 1391651, .Name =
"NPCManaCostScaler.txt" },
1363 { .FileDataId = 4492239, .Name =
"ProfessionRatings.txt" },
1364 { .FileDataId = 1391659, .Name =
"SandboxScaling.txt" },
1365 { .FileDataId = 1391660, .Name =
"SpellScaling.txt" },
1366 { .FileDataId = 1980632, .Name =
"StaminaMultByILvl.txt" },
1367 { .FileDataId = 1391661, .Name =
"xp.txt" }
1373 std::unique_ptr<CASC::File> dbcFile(
CascStorage->OpenFile(gt.FileDataId, CASC_LOCALE_NONE));
1376 boost::filesystem::path filePath = outputPath / gt.Name;
1378 if (!boost::filesystem::exists(filePath))
1379 if (
ExtractFile(dbcFile.get(), filePath.string()))
1386 printf(
"Extracted %u files\n\n", count);
1395 boost::filesystem::path
const cache_dir(boost::filesystem::canonical(
input_path) /
"CascCache");
1400 printf(
"Unable to open remote casc fallback to local casc\n");
1403 boost::filesystem::path
const storage_dir(boost::filesystem::canonical(
input_path) /
"Data");
1407 printf(
"error opening casc storage '%s' locale %s\n", storage_dir.string().c_str(),
localeNames[locale]);
1413 catch (std::exception
const& error)
1415 printf(
"Error opening CASC storage: %s\n", error.what());
1426 boost::filesystem::path
const cache_dir(boost::filesystem::canonical(
input_path) /
"CascCache");
1429 return CASC_LOCALE_ALL_WOW;
1431 printf(
"Unable to open remote casc fallback to local casc\n");
1434 boost::filesystem::path
const storage_dir(boost::filesystem::canonical(
input_path) /
"Data");
1439 return storage->GetInstalledLocalesMask();
1441 catch (std::exception
const& error)
1443 printf(
"Unable to determine installed locales mask: %s\n", error.what());
1456 boost::filesystem::path storageDir(boost::filesystem::canonical(
input_path) /
"Data");
1457 boost::filesystem::directory_iterator end;
1458 for (boost::filesystem::directory_iterator itr(storageDir); itr != end; ++itr)
1460 if (itr->path().extension() ==
".MPQ")
1462 printf(
"MPQ files found in Data directory!\n");
1463 printf(
"This tool works only with World of Warcraft: Battle for Azeroth\n");
1465 printf(
"To extract maps for Wrath of the Lich King, rebuild tools using 3.3.5 branch!\n");
1467 printf(
"Press ENTER to exit...\n");
1473 catch (std::exception
const& error)
1475 printf(
"Error checking client version: %s\n", error.what());
1487 Trinity::Banner::Show(
"Map & DBC Extractor", [](
char const* text) { printf(
"%s\n", text); },
nullptr);
1490 input_path = boost::filesystem::current_path();
1499 int32 firstInstalledLocale = -1;
1518 firstInstalledLocale = i;
1526 printf(
"Detected client build: %u\n\n", build);
1538 printf(
"Detected client build %u for locale %s\n\n", tempBuild,
localeNames[i]);
1542 if (firstInstalledLocale < 0)
1544 firstInstalledLocale = i;
1549 if (firstInstalledLocale < 0)
1551 printf(
"No locales detected\n");
1579#if TRINITY_PLATFORM == TRINITY_PLATFORM_WINDOWS
char const * localeNames[TOTAL_LOCALES]
constinit uint64 DUMMY_KNOWN_TACT_ID
constexpr DB2FileInfo DBFilesClientList[]
uint32 const MapVersionMagic
u_map_magic const MapAreaMagic
u_map_magic const MapLiquidMagic
u_map_magic const MapHeightMagic
u_map_magic const MapMagic
map_liquidHeaderTypeFlags
float selectUInt8StepStore(float maxDiff)
float CONF_float_to_int8_limit
char const * CONF_Product
uint32 GetInstalledLocalesMask()
bool CONF_allow_height_limit
static bool RetardCheck()
void HandleArgs(int argc, char *arg[])
boost::filesystem::path output_path
bool CONF_allow_float_to_int
void ReadLiquidObjectTable()
bool TransformToHighRes(uint16 lowResHoles, uint8(&hiResHoles)[8])
std::shared_ptr< CASC::Storage > CascStorage
void CreateDir(boost::filesystem::path const &path)
char const * CascLocaleNames[CASC_LOCALES_COUNT]
std::unordered_map< uint32, LiquidTypeEntry > LiquidTypes
std::unordered_map< uint32, LiquidObjectEntry > LiquidObjects
bool ExtractFile(CASC::File *fileInArchive, std::string const &filename)
uint32 WowLocaleToCascLocaleFlags[12]
void ExtractDBFilesClient(int l)
float CONF_flat_liquid_delta_limit
boost::filesystem::path input_path
void ReadLiquidMaterialTable()
bool ReadCinematicCameraDBC()
void ExtractCameraFiles()
float selectUInt16StepStore(float maxDiff)
std::unordered_map< uint32, LiquidMaterialEntry > LiquidMaterials
char const * GetCascFilenamePart(char const *cascPath)
bool OpenCascStorage(int locale)
std::set< uint32 > CameraFileDataIds
bool IsDeepWaterIgnored(uint32 mapId, uint32 x, uint32 y)
void ExtractMaps(uint32 build)
static void WriteArray2D(Array2D< T, N > const &data, FILE *f)
std::vector< MapEntry > map_ids
int main(int argc, char *arg[])
void TryLoadDB2(char const *name, DB2CascFileSource *source, DB2FileLoader *db2, DB2FileLoadInfo const *loadInfo)
bool ExtractDB2File(uint32 fileDataId, char const *cascFileName, int locale, boost::filesystem::path const &outputPath)
void ReadLiquidTypeTable()
#define CASC_LOCALES_COUNT
void Usage(char const *prg)
bool ConvertADT(ChunkedFile &adt, std::string const &mapName, std::string const &outputPath, int gx, int gy, uint32 build, bool ignoreDeepWater)
float CONF_float_to_int16_limit
float CONF_flat_height_delta_limit
#define ADT_CELLS_PER_GRID
bool ReadFile(void *buffer, uint32 bytes, uint32 *bytesRead)
static Storage * Open(boost::filesystem::path const &path, uint32 localeMask, char const *product)
static Storage * OpenRemote(boost::filesystem::path const &path, uint32 localeMask, char const *product, char const *region)
FileChunk const * GetChunk(std::string_view name) const
bool loadFile(std::shared_ptr< CASC::Storage const > mpq, std::string const &fileName, bool log=true)
std::multimap< std::string_view, FileChunk > chunks
void Load(DB2FileSource *source, DB2FileLoadInfo const *loadInfo)
DB2Record GetRecord(uint32 recordNumber) const
DB2RecordCopy GetRecordCopy(uint32 copyNumber) const
DB2Header const & GetHeader() const
void LoadHeaders(DB2FileSource *source, DB2FileLoadInfo const *loadInfo)
DB2SectionHeader const & GetSectionHeader(uint32 section) const
uint32 GetRecordCopyCount() const
uint32 GetRecordCount() const
int32 GetInt32(uint32 field, uint32 arrayIndex) const
uint32 GetUInt32(uint32 field, uint32 arrayIndex) const
uint16 GetUInt16(uint32 field, uint32 arrayIndex) const
uint8 GetUInt8(uint32 field, uint32 arrayIndex) const
char const * GetString(uint32 field, uint32 arrayIndex) const
constexpr bool HasFlag(T flag) const
FileChunk const * GetSubChunk(std::string_view name) const
struct wdt_MAID::@364 adt_files[64][64]
struct wdt_MAIN::adtData adt_list[64][64]
char const * HumanReadableCASCError(uint32 error)
TC_COMMON_API void Show(char const *applicationName, void(*log)(char const *text), void(*logExtraInfo)())
auto MapEqualRange(M &map, typename M::key_type const &key)
auto MapGetValuePtr(M &map, typename M::key_type const &key)
TC_COMMON_API void Init()
TC_COMMON_API void VerifyOsVersion()
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
static constexpr DB2LoadInfo Instance
bool IsOpen() const override
int64 GetFileSize() const override
bool SetPosition(int64 position) override
CASC::File * GetNativeHandle() const
EnumFlag< LiquidMaterialFlags > Flags
static constexpr DB2FileLoadInfo Instance
static constexpr DB2FileLoadInfo Instance
static constexpr DB2LoadInfo Instance
static constexpr DB2LoadInfo Instance
struct adt_MCLQ::liquid_data liquid[ADT_CELL_SIZE+1][ADT_CELL_SIZE+1]
uint8 flags[ADT_CELL_SIZE][ADT_CELL_SIZE]
union adt_MCNK::@350 union_5_3_0
float height_map[(ADT_CELL_SIZE+1) *(ADT_CELL_SIZE+1)+ADT_CELL_SIZE *ADT_CELL_SIZE]
adt_liquid_attributes GetLiquidAttributes(int32 x, int32 y) const
adt_liquid_instance const * GetLiquidInstance(int32 x, int32 y) const
float GetLiquidHeight(adt_liquid_instance const *h, int32 pos) const
LiquidVertexFormatType GetLiquidVertexFormat(adt_liquid_instance const *liquidInstance) const
uint64 GetLiquidExistsBitmap(adt_liquid_instance const *h) const
uint16 GetLiquidType(adt_liquid_instance const *h) const
uint16 LiquidVertexFormat