// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/> // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. #include "nel/misc/types_nl.h" #include "nel/misc/app_context.h" #include "nel/misc/path.h" #include "nel/misc/sstring.h" #include "nel/misc/algo.h" using namespace std; using namespace NLMISC; enum TAction { pack, unpack, undefined }; const string DefaultExt("xml_pack"); const uint32 MaxLineSize = 16*1024; const string ExcludeFiles(".#*;*.log;*.bin;*.xml_pack_index"); const string ExcludeDirs("CVS"); bool isExcludedFile(const std::string &fileName) { static vector<string> excludeFileVect; static bool init = false; if (!init) { explode(ExcludeFiles, string(";"), excludeFileVect, true); init = true; } bool excluded = false; for (uint i=0; i<excludeFileVect.size(); ++i) { if (testWildCard(fileName, excludeFileVect[i])) { excluded = true; break; } } return excluded; } bool isExcludedDir(const std::string &dirName) { static vector<string> excludeDirVect; static bool init = false; if (!init) { explode(ExcludeDirs, string(";"), excludeDirVect, true); } bool excluded = false; for (uint i=0; i<excludeDirVect.size(); ++i) { if (testWildCard(dirName, excludeDirVect[i])) { excluded = true; break; } } return excluded; } string getLastDirName(const std::string &path) { string dirName; string::size_type pos = path.size()-1; // skip any terminal directory separator if (pos > 0 && (path[pos] == '\\' || path[pos] == '/')) --pos; while(pos > 0 && path[pos] != '\\' && path[pos] != '/' ) dirName = path[pos--] + dirName; return dirName; } int main(int argc, char *argv[]) { printf("NeL XML Packer/Unpacker V0.3\n"); CApplicationContext appContext; TAction action = undefined; bool recursive = false; // compute the current folder name string currentPath = CPath::getCurrentPath(); string dirName = getLastDirName(currentPath);; string filename = dirName + "."+DefaultExt; // check the params to choose action for (uint i=0; i<uint(argc); ++i) { if (strcmp(argv[i], "-p") == 0) { // pack the current folder action = pack; printf("Packing files\n"); } else if (strcmp(argv[i], "-u") == 0) { // unpack the current folder action = unpack; printf("Unpacking files\n"); } else if (strcmp(argv[i], "-r") == 0) { // unpack the current folder recursive = true; printf("Recursive mode\n"); } // else if (strcmp(argv[i], "-f") == 0) // { // if (uint(argc) < i+i) // { // printf("Error : missing file name after -f\n"); // return -1; // } // // use the specified file archive instead of the directory name // filename = argv[++i]; // } } if (action == undefined) { printf("Usage : %s -u|-p [-r]\n", argv[0]); printf(" -p : pack the current folder\n"); printf(" -u : unpack the current folder\n"); printf(" -r : pack or unpack subdirectories recursively\n"); return -1; } vector<string> dirStack; printf("Current path is '%s'\n", CPath::getCurrentPath().c_str()); // push the current directory to start the loop dirStack.push_back(CPath::getCurrentPath()); while(!dirStack.empty()) { string dirName = dirStack.back(); dirStack.pop_back(); string filename = dirName+"/"+getLastDirName(dirName) + "."+DefaultExt; switch (action) { case pack: { printf("Packing directory '%s'...\n", dirName.c_str()); // string packFileName = dirName+"/tmp."+DefaultExt; string packFileName = filename; string indexFileName = dirName+"/.xml_pack_index"; // get the current directory content vector<string> files; CPath::getPathContent(dirName, false, false, true, files); vector<string> validFiles; // first loop to build the list of valid file for (uint i=0; i<files.size(); ++i) { if (files[i].find(DefaultExt) == files[i].size() - DefaultExt.size()) continue; string &subFileName = files[i]; // check exclude filter if (isExcludedFile(subFileName)) { continue; } // ok, this file is valid validFiles.push_back(subFileName); } bool needRepack = true; // if an xml pack already exist in the folder... if (CFile::fileExists(packFileName)) { breakable { if (validFiles.empty()) { // no file in the directory, erase the pack file CFile::deleteFile(packFileName); break; } uint32 packDate = CFile::getFileModificationDate(packFileName); if (!CFile::fileExists(indexFileName) || CFile::getFileModificationDate(indexFileName) < packDate) { // no index file or index file older than pack file, repack break; } // read the index file set<string> fileInIndex; char lineBuffer[1024]; FILE *fp = fopen(indexFileName.c_str(), "rt"); while (fgets(lineBuffer, 1024, fp)) fileInIndex.insert(CSString(lineBuffer).strip()); fclose(fp); // loop to check for file time stamp for (uint i=0; i<validFiles.size(); ++i) { uint32 fileDate = CFile::getFileModificationDate(validFiles[i]); if (fileDate >= packDate) // no more to check break; // remove this file from the file index fileInIndex.erase(CFile::getFilename(validFiles[i])); } // check if there are some some deleted in the directory if (!fileInIndex.empty()) { // need to repack, there are erased files break; } // all files are older than the pack file ! no repack needed needRepack = false; } } // we need to repack and have some file to store ? if (!validFiles.empty() && needRepack) { // open the pack file // FILE *fp = fopen(filename.c_str(), "wt"); FILE *fp = fopen(packFileName.c_str(), "wt"); fprintf(fp, "<nel:packed_xml>\n"); for (uint i=0; i<validFiles.size(); ++i) { string &subFileName = validFiles[i]; printf("Adding file '%s'...\n", CFile::getFilename(subFileName).c_str()); fprintf(fp, " <nel:xml_file name=\"%s\">\n", CFile::getFilename(subFileName).c_str()); FILE *subFp = fopen(subFileName.c_str(), "rt"); nlassert(subFp != NULL); char buffer[MaxLineSize]; char *result; bool needFinalReturn = false; result = fgets(buffer, MaxLineSize, subFp); needFinalReturn = result != NULL ? buffer[strlen(buffer)-1] != '\n' : true; while(result != 0) { fputs(buffer, fp); result = fgets(buffer, MaxLineSize, subFp); needFinalReturn = result != NULL ? buffer[strlen(buffer)-1] != '\n' : needFinalReturn; } if (needFinalReturn) { const char *finalReturn = "\n"; fputs(finalReturn, fp); } fclose(subFp); fprintf(fp, " </nel:xml_file>\n"); } fprintf(fp, "</nel:packed_xml>\n"); fclose(fp); // write the disposable index file used by pack to check for erased file fp = fopen(indexFileName.c_str(), "wt"); for (uint i=0; i<validFiles.size(); ++i) { fprintf(fp, "%s\n", CFile::getFilename(validFiles[i]).c_str()); } fclose(fp); // set the file 'hidden'n use the plain old system command... sint res = system(toString("attrib +h %s", indexFileName.c_str()).c_str()); if (res) { nlwarning("attrib failed with return code %d", res); } } else { printf("Directory %s is up to date, no repack\n", dirName.c_str()); } } break; case unpack: { printf("Unpacking directory '%s'...\n", dirName.c_str()); // open the pack file // FILE *fp = fopen((dirName+"/tmp."+DefaultExt).c_str(), "rt"); FILE *fp = fopen(filename.c_str(), "rt"); if (!recursive) { // if we are not recursive, we MUST have a file here printf("Error : can't find a xml_pack file in current directory\n"); exit(-1); } else { // just continue to recurse, there is no file at this level break; } uint linecount = 0; // read the first line char buffer[MaxLineSize]; if (!fgets(buffer, MaxLineSize, fp) || strcmp(buffer, "<nel:packed_xml>\n") != 0) { printf ("Error : invalid pack file '%s'\n", filename.c_str()); return -1; } linecount++; char *result = NULL; do { // read a file line linecount++; if (!fgets(buffer, MaxLineSize, fp)) { fclose(fp); printf ("Error : invalid pack file '%s' at line %u", filename.c_str(), linecount); return -1; } CSString parser(buffer); if (parser.find(" <nel:xml_file name=") != 0) { fclose(fp); // end of pack file if (parser.find("</nel:packed_xml>") == 0) break; printf ("Error : invalid pack file '%s' at line %u", filename.c_str(), linecount); return -1; } CSString subFileName = parser.leftCrop(sizeof(" <nel:xml_file name=")-1); subFileName = subFileName.matchDelimiters(false, false, true, false); subFileName = subFileName.unquoteIfQuoted(); subFileName = dirName+"/"+subFileName; printf("Extracting file '%s'...\n", CFile::getFilename(subFileName).c_str()); // open the output file FILE *output = fopen (subFileName.c_str(), "wt"); if (output == NULL) { printf ("Error : can not open output file '%s' from pack file '%s'", subFileName.c_str(), filename.c_str()); exit(-1); } result = fgets(buffer, MaxLineSize, fp); linecount++; while (result != NULL && strcmp(buffer, " </nel:xml_file>\n") != 0) { fputs(result, output); // read next line result = fgets(buffer, MaxLineSize, fp); linecount++; } fclose(output); } while(result != NULL); } break; default: // this shouldn't happen / keep compiler happy break; } if (recursive) { vector<string> subDirs; CPath::getPathContent(dirName, false, true, false, subDirs); // filter the directories for (uint i=(uint)subDirs.size(); i>0; --i) { if (!isExcludedDir(subDirs[i-1])) dirStack.push_back(subDirs[i-1]); } } } return 0; }