#include #include #define _COMMON_NO_THREADS #include "common.h" #include "Auth/MD5Hash.h" #include "tools.h" #include "MPQHelper.h" #include "dbcfile.h" #include "ADTFile.h" #include "WDTFile.h" #include "StuffExtract.h" #include "DBCFieldData.h" #include "Locale.h" std::map mapNames; std::set texNames; std::set modelNames; std::set wmoNames; std::set soundFileSet; // default config; SCPs are dont always bool doMaps=true, doSounds=false, doTextures=false, doWmos=false, doModels=false, doMd5=true, doAutoclose=false; int main(int argc, char *argv[]) { char input[200]; printf("StuffExtract [version %u]\n",SE_VERSION); printf("Use -help or -? to display help about command line arguments and config.\n\n",SE_VERSION); ProcessCmdArgs(argc, argv); PrintConfig(); if(!GetLocale()) { printf("Enter your locale (enUS, enGB, deDE, ...) or leave blank to autodetect: "); fgets(input,sizeof(input),stdin); char loc[5]; input[strlen(input)-1] = 0; memcpy(loc,input,4); loc[4]=0; SetLocale(loc); } if(GetLocale() && FileExists(std::string("Data/")+GetLocale()+"/locale-"+GetLocale()+".MPQ")) { printf("Locale \"%s\" seems valid, starting conversion...\n",GetLocale()); CreateDir("stuffextract"); CreateDir("stuffextract/data"); ConvertDBC(); if(doMaps) ExtractMaps(); if(doTextures || doModels || doWmos) ExtractMapDependencies(); if(doSounds) ExtractSoundFiles(); //... if (!doAutoclose) printf("\n -- finished, press enter to exit --\n"); } else { printf("ERROR: Invalid locale \"%s\"! Press Enter to exit...\n",GetLocale()); } if (!doAutoclose) fgets(input,sizeof(input),stdin); //while(true); return 0; } void ProcessCmdArgs(int argc, char *argv[]) { bool on,help=false; char *what; for(int i = 1; i < argc; i++) { if(strlen(argv[i]) > 1) { if(argv[i][0] == '-') on = false; else if(argv[i][0] == '+') on = true; else if(!stricmp(argv[i],"/?") || !stricmp(argv[i],"/help")) { help = true; break; } else { printf("Incorrect cmd arg: \"%s\"\n",argv[i]); continue; } what = argv[i]+1; // skip first byte (+/-) if (!stricmp(what,"maps")) doMaps = on; else if(!stricmp(what,"textures")) doTextures = on; else if(!stricmp(what,"wmos")) doWmos = on; else if(!stricmp(what,"models")) doModels = on; else if(!stricmp(what,"sounds")) doSounds = on; else if(!stricmp(what,"md5")) doMd5 = on; else if(!stricmp(what,"autoclose")) doAutoclose = on; // autodetect or use given locale. + or - as arg start doesnt matter here else if(!strnicmp(what,"locale:",7)) { if(!stricmp(what+7,"auto")) SetLocale(NULL); else SetLocale(what+7); } else if(!stricmp(what,"?") || !stricmp(what,"help")) { help = true; break; } else { printf("Unknown cmd arg: \"%s\"\n",what); } } } // fix up wrong configs. not necessary but better for display. // TODO: as soon as M2 model or WMO reading is done, extract those textures to, but independent from maps!! if(!doMaps) { doTextures = false; doWmos = false; } if(help) { PrintHelp(); printf("\n\n- Press any key to exit -\n"); getchar(); exit(0); } } void PrintConfig(void) { printf("config: Do maps: %s\n",doMaps?"yes":"no"); printf("config: Do textures: %s\n",doTextures?"yes":"no"); printf("config: Do wmos: %s\n",doWmos?"yes":"no"); printf("config: Do models: %s\n",doModels?"yes":"no"); printf("config: Do sounds: %s\n",doSounds?"yes":"no"); printf("config: Calc md5: %s\n",doMd5?"yes":"no"); printf("config: Autoclose: %s\n",doAutoclose?"yes":"no"); } void PrintHelp(void) { printf("Usage information:\n\n"); printf("Use + or - to turn a feature on or off.\n"); printf("Features are:\n"); printf("maps - map extraction\n"); printf("textures - extract textures (requires maps extraction, for now)\n"); printf("wmos - extract map WMOs (requires maps extraction)\n"); printf("models - extract models\n"); printf("sounds - extract sound files (wav/mp3)\n"); printf("md5 - write MD5 checksum lists of extracted files\n"); printf("autoclose - close program when done\n"); printf("\n"); printf("Use -locale:xxXX to set a locale. If you don't use this, you will be asked.\n"); printf("Use -locale:auto to autodetect currently used locale.\n"); printf("\n"); printf("Examples:\n"); printf("stuffextract +sounds +md5 -maps +autoclose -locale:enGB\n"); printf("stuffextract +md5 -wmos -sounds -locale:auto -autoclose\n"); printf("\nDefault is: +maps -sounds -textures -wmos -models +md5 -autoclose\n"); } // be careful using this, that you supply correct format string std::string AutoGetDataString(DBCFile::Iterator& it, const char* format, uint32 field, bool skip_null = true) { if(format[field]=='i') { if((*it).getInt(field) == 0 && skip_null) return ""; // do not explicitly write int fields that are 0 std::stringstream s; s << (*it).getInt(field); return s.str(); } else if(format[field]=='f') { if((*it).getFloat(field) == 0 && skip_null) return ""; // do not explicitly write float fields that are 0 std::stringstream s; s << (*it).getFloat(field); return s.str(); } else if(format[field]=='s' && (*it).getUInt(field)) { return (*it).getString(field); } return ""; } // output a formatted scp file void OutSCP(char *fn, SCPStorageMap& scp, std::string dbName="") { std::fstream f; f.open(fn, std::ios_base::out); if(f.is_open()) { if(dbName.length()) { f << "#dbname=" << dbName << "\n"; } for(SCPStorageMap::iterator mi = scp.begin(); mi != scp.end(); mi++) { f << "[" << mi->first << "]\n"; for(std::list::iterator li = mi->second.begin(); li != mi->second.end(); li++) { f << *li << "\n"; } f << "\n"; } f.close(); } else { printf("OutSCP: unable to write '%s'\n",fn); } } void OutMD5(char *path, MD5FileMap& fm) { if(!doMd5) return; std::string fullname(path); fullname += "/md5.txt"; printf("Writing MD5 file checksums to '%s'\n",fullname.c_str()); std::fstream fh; fh.open(fullname.c_str(), std::ios_base::out); if(fh.is_open()) { for(MD5FileMap::iterator i = fm.begin(); i != fm.end(); i++) { fh << i->first << "|" << toHexDump(i->second,MD5_DIGEST_LENGTH,false) << std::endl; // write file content delete [] i->second; // and delete previously allocated memory } fh.close(); } else { printf("Couldn't output MD5 list to '%s'\n",fullname.c_str()); } } bool ConvertDBC(void) { std::map racemap; // needed to extract other dbc files correctly SCPStorageMap EmoteDataStorage,RaceDataStorage,SoundDataStorage,MapDataStorage,ZoneDataStorage,ItemDisplayInfoStorage, CreatureModelStorage,CreatureDisplayInfoStorage,NPCSoundStorage; // will store the converted data from dbc files DBCFile EmotesText,EmotesTextData,EmotesTextSound,ChrRaces,SoundEntries,Map,AreaTable,ItemDisplayInfo, CreatureModelData,CreatureDisplayInfo,NPCSounds; printf("Opening DBC archive...\n"); MPQHelper mpq("dbc"); printf("Opening DBC files...\n"); EmotesText.openmem(mpq.ExtractFile("DBFilesClient\\EmotesText.dbc")); EmotesTextData.openmem(mpq.ExtractFile("DBFilesClient\\EmotesTextData.dbc")); EmotesTextSound.openmem(mpq.ExtractFile("DBFilesClient\\EmotesTextSound.dbc")); ChrRaces.openmem(mpq.ExtractFile("DBFilesClient\\ChrRaces.dbc")); SoundEntries.openmem(mpq.ExtractFile("DBFilesClient\\SoundEntries.dbc")); Map.openmem(mpq.ExtractFile("DBFilesClient\\Map.dbc")); AreaTable.openmem(mpq.ExtractFile("DBFilesClient\\AreaTable.dbc")); ItemDisplayInfo.openmem(mpq.ExtractFile("DBFilesClient\\ItemDisplayInfo.dbc")); CreatureModelData.openmem(mpq.ExtractFile("DBFilesClient\\CreatureModelData.dbc")); CreatureDisplayInfo.openmem(mpq.ExtractFile("DBFilesClient\\CreatureDisplayInfo.dbc")); NPCSounds.openmem(mpq.ExtractFile("DBFilesClient\\NPCSounds.dbc")); //... printf("DBC files opened.\n"); //... printf("Reading data: races.."); for(DBCFile::Iterator it = ChrRaces.begin(); it != ChrRaces.end(); ++it) { uint32 id = (*it).getUInt(CHRRACES_RACEID); racemap[id] = (*it).getString(CHRRACES_NAME_GENERAL); // for later use for(uint32 field=CHRRACES_RACEID; field < CHARRACES_END; field++) { if(strlen(ChrRacesFieldNames[field])) { std::string value = AutoGetDataString(it,ChrRacesFormat,field); if(!value.empty()) RaceDataStorage[id].push_back(std::string(ChrRacesFieldNames[field]).append("=").append(value)); } } } printf("emotes.."); for(DBCFile::Iterator it = EmotesText.begin(); it != EmotesText.end(); ++it) { uint32 em = (*it).getUInt(EMOTESTEXT_EMOTE_ID); EmoteDataStorage[em].push_back(std::string("name=") + (*it).getString(EMOTESTEXT_EMOTE_STRING)); EmoteDataStorage[em].push_back(std::string("anim=") + toString( (*it).getUInt(EMOTESTEXT_ANIM)) ); for(uint32 field=EMOTESTEXT_EMOTE_ID; fieldgetInt(MAP_ID)] = it->getString(MAP_NAME_GENERAL); uint32 id = it->getUInt(MAP_ID); for(uint32 field=MAP_ID; field < MAP_END; field++) { if(strlen(MapFieldNames[field])) { std::string value = AutoGetDataString(it,MapFormat,field); if(value.size()) // only store if not null MapDataStorage[id].push_back(std::string(MapFieldNames[field]) + "=" + value); } } } printf("zonedata.."); for(DBCFile::Iterator it = AreaTable.begin(); it != AreaTable.end(); ++it) { uint32 id = it->getUInt(MAP_ID); for(uint32 field=AREATABLE_ID; field < AREATABLE_END; field++) { if(strlen(AreaTableFieldNames[field])) { std::string value = AutoGetDataString(it,AreaTableFormat,field,false); if(value.size()) // only store if not null ZoneDataStorage[id].push_back(std::string(AreaTableFieldNames[field]) + "=" + value); } } } printf("itemdisplayinfo.."); for(DBCFile::Iterator it = ItemDisplayInfo.begin(); it != ItemDisplayInfo.end(); ++it) { uint32 id = it->getUInt(ITEMDISPLAYINFO_ID); for(uint32 field=ITEMDISPLAYINFO_ID; field < ITEMDISPLAYINFO_END; field++) { if(strlen(ItemDisplayInfoFieldNames[field])) { std::string value = AutoGetDataString(it,ItemDisplayInfoFormat,field); if(value.size()) // only store if not null ItemDisplayInfoStorage[id].push_back(std::string(ItemDisplayInfoFieldNames[field]) + "=" + value); } } } printf("creaturemodeldata.."); for(DBCFile::Iterator it = CreatureModelData.begin(); it != CreatureModelData.end(); ++it) { uint32 id = it->getUInt(CREATUREMODELDATA_ID); for(uint32 field=CREATUREMODELDATA_ID; field < CREATUREMODELDATA_END; field++) { if(strlen(CreatureModelDataFieldNames[field])) { std::string value = AutoGetDataString(it,CreatureModelDataFormat,field); if(value.size()) // only store if not null { modelNames.insert(value); // we need to extract model later, store it std::string fn = _PathToFileName(value); if(stricmp(fn.c_str()+fn.length()-4, "mdx")) fn = fn.substr(0,fn.length()-3) + "m2"; CreatureModelStorage[id].push_back(std::string(CreatureModelDataFieldNames[field]) + "=" + fn); } } } } printf("creaturedisplayinfo.."); for(DBCFile::Iterator it = CreatureDisplayInfo.begin(); it != CreatureDisplayInfo.end(); ++it) { uint32 id = it->getUInt(CREATUREDISPLAYINFO_ID); for(uint32 field=CREATUREDISPLAYINFO_ID; field < CREATUREDISPLAYINFO_END; field++) { if(strlen(CreatureDisplayInfoFieldNames[field])) { std::string value = AutoGetDataString(it,CreatureDisplayInfoFormat,field); if(value.size()) // only store if not null CreatureDisplayInfoStorage[id].push_back(std::string(CreatureDisplayInfoFieldNames[field]) + "=" + value); } } } printf("npcsounds.."); for(DBCFile::Iterator it = NPCSounds.begin(); it != NPCSounds.end(); ++it) { uint32 id = it->getUInt(NPCSOUNDS_ID); for(uint32 field=NPCSOUNDS_ID; field < NPCSOUNDS_END; field++) { if(strlen(NPCSoundsFieldNames[field])) { std::string value = AutoGetDataString(it,NPCSoundsFormat,field); if(value.size()) // only store if not null NPCSoundStorage[id].push_back(std::string(NPCSoundsFieldNames[field]) + "=" + value); } } } //... printf("DONE!\n"); //... CreateDir("stuffextract/data/scp"); printf("Writing SCP files:\n"); printf("emote.."); OutSCP(SCPDIR "/emote.scp",EmoteDataStorage, "emote"); printf("race.."); OutSCP(SCPDIR "/race.scp",RaceDataStorage, "race"); printf("sound.."); OutSCP(SCPDIR "/sound.scp",SoundDataStorage, "sound"); printf("map.."); OutSCP(SCPDIR "/map.scp",MapDataStorage, "map"); printf("area.."); OutSCP(SCPDIR "/zone.scp",ZoneDataStorage, "zone"); printf("itemdisplayinfo.."); OutSCP(SCPDIR "/itemdisplayinfo.scp",ItemDisplayInfoStorage, "itemdisplayinfo"); printf("creaturemodeldata.."); OutSCP(SCPDIR "/creaturemodeldata.scp",CreatureModelStorage,"creaturemodeldata"); printf("creaturedisplayinfo.."); OutSCP(SCPDIR "/creaturedisplayinfo.scp",CreatureDisplayInfoStorage,"creaturedisplayinfo"); printf("npcsound.."); OutSCP(SCPDIR "/npcsound.scp",NPCSoundStorage,"npcsound"); //... printf("DONE!\n"); // wait for all container destructors to finish printf("DBC files converted, cleaning up...\n"); return true; } void ExtractMaps(void) { printf("\nExtracting maps...\n"); char namebuf[200]; char outbuf[2000]; uint32 extr,extrtotal=0; MPQHelper mpq("terrain"); MD5FileMap md5map; CreateDir("stuffextract/data/maps"); for(std::map::iterator it = mapNames.begin(); it != mapNames.end(); it++) { // extract the WDT file that stores tile information char wdt_name[300], wdt_out[300]; sprintf(wdt_name,"World\\Maps\\%s\\%s.wdt",it->second.c_str(),it->second.c_str()); sprintf(wdt_out,MAPSDIR"/%u.wdt",it->first); ByteBuffer& wdt_bb = mpq.ExtractFile(wdt_name); std::fstream wdt_fh; wdt_fh.open(wdt_out, std::ios_base::out|std::ios_base::binary); if(!wdt_fh.is_open()) { printf("\nERROR: Map extraction failed: could not save file %s\n",wdt_out); return; } wdt_fh.write((char*)wdt_bb.contents(),wdt_bb.size()); wdt_fh.close(); printf("Extracted WDT '%s'\n",wdt_name); // then extract all ADT files extr=0; for(uint32 x=0; x<64; x++) { for(uint32 y=0;y<64; y++) { uint32 olddeps; uint32 depdiff; sprintf(namebuf,"World\\Maps\\%s\\%s_%u_%u.adt",it->second.c_str(),it->second.c_str(),x,y); sprintf(outbuf,MAPSDIR"/%u_%u_%u.adt",it->first,x,y); if(mpq.FileExists(namebuf)) { ByteBuffer& bb = mpq.ExtractFile(namebuf); if(bb.size()) { std::fstream fh; //printf("Extracting map [ %s ]\n",outbuf); fh.open(outbuf, std::ios_base::out|std::ios_base::binary); if(!fh.is_open()) { printf("\nERROR: Map extraction failed: could not save file %s\n",outbuf); return; } fh.write((char*)bb.contents(),bb.size()); fh.flush(); fh.close(); olddeps = texNames.size() + modelNames.size() + wmoNames.size(); if(doTextures) ADT_FillTextureData(bb.contents(),texNames); if(doModels) ADT_FillModelData(bb.contents(),modelNames); if(doWmos) ADT_FillWMOData(bb.contents(),wmoNames); depdiff = texNames.size() + modelNames.size() + wmoNames.size() - olddeps; if(doMd5) { MD5Hash h; h.Update((uint8*)bb.contents(), bb.size()); h.Finalize(); uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH]; md5map[_PathToFileName(outbuf)] = md5ptr; memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH); } extr++; printf("[%u:%u] %s; %u new deps.\n",extr,it->first,namebuf,depdiff); } } } } extrtotal+=extr; printf("\n"); } printf("\nDONE - %u maps extracted, %u total dependencies.\n",extrtotal, texNames.size() + modelNames.size() + wmoNames.size()); OutMD5(MAPSDIR,md5map); } void ExtractMapDependencies(void) { printf("\nExtracting map dependencies...\n\n"); printf("- Preparing to read MPQ arcives...\n"); MPQHelper mpqmodel("model"); MPQHelper mpqtex("texture"); MPQHelper mpqwmo("wmo"); std::string path = "stuffextract/data"; std::string pathtex = path + "/texture"; std::string pathmodel = path + "/model"; std::string pathwmo = path + "/wmo"; std::string mpqfn,realfn; MD5FileMap md5Tex, md5Wmo, md5Model; CreateDir(pathtex.c_str()); CreateDir(pathmodel.c_str()); CreateDir(pathwmo.c_str()); uint32 wmosdone=0,texdone=0,mdone=0; for(std::set::iterator i = texNames.begin(); i != texNames.end(); i++) { mpqfn = *i; if(!mpqtex.FileExists((char*)mpqfn.c_str())) continue; realfn = pathtex + "/" + _PathToFileName(mpqfn); std::fstream fh; fh.open(realfn.c_str(),std::ios_base::out | std::ios_base::binary); if(fh.is_open()) { ByteBuffer& bb = mpqtex.ExtractFile((char*)mpqfn.c_str()); fh.write((const char*)bb.contents(),bb.size()); if(doMd5) { MD5Hash h; h.Update((uint8*)bb.contents(), bb.size()); h.Finalize(); uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH]; md5Tex[_PathToFileName(realfn)] = md5ptr; memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH); } texdone++; printf("- textures... %u\r",texdone); } else printf("Could not write texture %s\n",realfn.c_str()); fh.close(); } printf("\n"); OutMD5((char*)pathtex.c_str(),md5Tex); for(std::set::iterator i = modelNames.begin(); i != modelNames.end(); i++) { mpqfn = *i; // no idea what bliz intended by this. the ADT files refer to .mdx models, // however there are only .m2 files in the MPQ archives. // so we just need to check if there is a .m2 file instead of the .mdx file, and load that one. if(!mpqmodel.FileExists((char*)mpqfn.c_str())) { std::string alt = i->substr(0,i->length()-3) + "m2"; DEBUG(printf("MDX model not found, trying M2 file.")); if(!mpqmodel.FileExists((char*)alt.c_str())) { DEBUG(printf(" fail.\n")); continue; } else { mpqfn = alt; DEBUG(printf(" success.\n")); } } realfn = pathmodel + "/" + _PathToFileName(mpqfn); std::fstream fh; fh.open(realfn.c_str(),std::ios_base::out | std::ios_base::binary); if(fh.is_open()) { ByteBuffer& bb = mpqmodel.ExtractFile((char*)mpqfn.c_str()); fh.write((const char*)bb.contents(),bb.size()); if(doMd5) { MD5Hash h; h.Update((uint8*)bb.contents(), bb.size()); h.Finalize(); uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH]; md5Model[_PathToFileName(realfn)] = md5ptr; memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH); } mdone++; printf("- models... %u\r",mdone); } else printf("Could not write model %s\n",realfn.c_str()); fh.close(); } printf("\n"); OutMD5((char*)pathmodel.c_str(),md5Model); for(std::set::iterator i = wmoNames.begin(); i != wmoNames.end(); i++) { mpqfn = *i; if(!mpqwmo.FileExists((char*)mpqfn.c_str())) continue; realfn = pathwmo + "/" + _PathToFileName(mpqfn); std::fstream fh; fh.open(realfn.c_str(),std::ios_base::out | std::ios_base::binary); if(fh.is_open()) { ByteBuffer& bb = mpqwmo.ExtractFile((char*)mpqfn.c_str()); fh.write((const char*)bb.contents(),bb.size()); if(doMd5) { MD5Hash h; h.Update((uint8*)bb.contents(), bb.size()); h.Finalize(); uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH]; md5Wmo[_PathToFileName(realfn)] = md5ptr; memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH); } wmosdone++; printf("- WMOs... %u\r",wmosdone); } else printf("Could not write WMO %s\n",realfn.c_str()); fh.close(); } printf("\n"); OutMD5((char*)pathwmo.c_str(),md5Wmo); } void ExtractSoundFiles(void) { MD5FileMap md5data; uint32 done = 0; printf("\nExtracting game audio files, %u found in DBC...\n",soundFileSet.size()); CreateDir(SOUNDDIR); MPQHelper smpq("sound"); std::string outfn; for(std::set::iterator i = soundFileSet.begin(); i != soundFileSet.end(); i++) { if(!smpq.FileExists((char*)(*i).c_str())) { DEBUG( printf("MPQ: File not found: '%s'\n",(*i).c_str()) ); continue; } outfn = std::string(SOUNDDIR) + "/" + _PathToFileName(*i); std::fstream fh; fh.open(outfn.c_str(), std::ios_base::out | std::ios_base::binary); if(fh.is_open()) { ByteBuffer& bb = smpq.ExtractFile((char*)(*i).c_str()); if(bb.size()) { fh.write((const char*)bb.contents(),bb.size()); if(doMd5) { MD5Hash h; h.Update((uint8*)bb.contents(), bb.size()); h.Finalize(); uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH]; md5data[_PathToFileName(*i)] = md5ptr; memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH); } done++; printf("- %u files done.\r",done); } } else { printf("Could not write sound file '%s'\n",outfn.c_str()); } fh.close(); } OutMD5(SOUNDDIR,md5data); printf("\n"); }