2
0

PackageTool.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. //
  2. // Copyright (c) 2008-2018 the Urho3D project.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include <Urho3D/Core/Context.h>
  23. #include <Urho3D/Container/ArrayPtr.h>
  24. #include <Urho3D/Core/ProcessUtils.h>
  25. #include <Urho3D/IO/File.h>
  26. #include <Urho3D/IO/FileSystem.h>
  27. #include <Urho3D/IO/PackageFile.h>
  28. #ifdef WIN32
  29. #include <windows.h>
  30. #endif
  31. #include <LZ4/lz4.h>
  32. #include <LZ4/lz4hc.h>
  33. #include <Urho3D/DebugNew.h>
  34. using namespace Urho3D;
  35. static const unsigned COMPRESSED_BLOCK_SIZE = 32768;
  36. struct FileEntry
  37. {
  38. String name_;
  39. unsigned offset_{};
  40. unsigned size_{};
  41. unsigned checksum_{};
  42. };
  43. SharedPtr<Context> context_(new Context());
  44. SharedPtr<FileSystem> fileSystem_(new FileSystem(context_));
  45. String basePath_;
  46. Vector<FileEntry> entries_;
  47. unsigned checksum_ = 0;
  48. bool compress_ = false;
  49. bool quiet_ = false;
  50. unsigned blockSize_ = COMPRESSED_BLOCK_SIZE;
  51. String ignoreExtensions_[] = {
  52. ".bak",
  53. ".rule",
  54. ""
  55. };
  56. int main(int argc, char** argv);
  57. void Run(const Vector<String>& arguments);
  58. void ProcessFile(const String& fileName, const String& rootDir);
  59. void WritePackageFile(const String& fileName, const String& rootDir);
  60. void WriteHeader(File& dest);
  61. int main(int argc, char** argv)
  62. {
  63. Vector<String> arguments;
  64. #ifdef WIN32
  65. arguments = ParseArguments(GetCommandLineW());
  66. #else
  67. arguments = ParseArguments(argc, argv);
  68. #endif
  69. Run(arguments);
  70. return 0;
  71. }
  72. void Run(const Vector<String>& arguments)
  73. {
  74. if (arguments.Size() < 2)
  75. ErrorExit(
  76. "Usage: PackageTool <directory to process> <package name> [basepath] [options]\n"
  77. "\n"
  78. "Options:\n"
  79. "-c Enable package file LZ4 compression\n"
  80. "-q Enable quiet mode\n"
  81. "\n"
  82. "Basepath is an optional prefix that will be added to the file entries.\n\n"
  83. "Alternative output usage: PackageTool <output option> <package name>\n"
  84. "Output option:\n"
  85. "-i Output package file information\n"
  86. "-l Output file names (including their paths) contained in the package\n"
  87. "-L Similar to -l but also output compression ratio (compressed package file only)\n"
  88. );
  89. const String& dirName = arguments[0];
  90. const String& packageName = arguments[1];
  91. bool isOutputMode = arguments[0].Length() == 2 && arguments[0][0] == '-';
  92. if (arguments.Size() > 2)
  93. {
  94. for (unsigned i = 2; i < arguments.Size(); ++i)
  95. {
  96. if (arguments[i][0] != '-')
  97. basePath_ = AddTrailingSlash(arguments[i]);
  98. else
  99. {
  100. if (arguments[i].Length() > 1)
  101. {
  102. switch (arguments[i][1])
  103. {
  104. case 'c':
  105. compress_ = true;
  106. break;
  107. case 'q':
  108. quiet_ = true;
  109. break;
  110. default:
  111. ErrorExit("Unrecognized option");
  112. }
  113. }
  114. }
  115. }
  116. }
  117. if (!isOutputMode)
  118. {
  119. if (!quiet_)
  120. PrintLine("Scanning directory " + dirName + " for files");
  121. // Get the file list recursively
  122. Vector<String> fileNames;
  123. fileSystem_->ScanDir(fileNames, dirName, "*.*", SCAN_FILES, true);
  124. if (!fileNames.Size())
  125. ErrorExit("No files found");
  126. // Check for extensions to ignore
  127. for (unsigned i = fileNames.Size() - 1; i < fileNames.Size(); --i)
  128. {
  129. String extension = GetExtension(fileNames[i]);
  130. for (unsigned j = 0; j < ignoreExtensions_[j].Length(); ++j)
  131. {
  132. if (extension == ignoreExtensions_[j])
  133. {
  134. fileNames.Erase(fileNames.Begin() + i);
  135. break;
  136. }
  137. }
  138. }
  139. for (unsigned i = 0; i < fileNames.Size(); ++i)
  140. ProcessFile(fileNames[i], dirName);
  141. WritePackageFile(packageName, dirName);
  142. }
  143. else
  144. {
  145. SharedPtr<PackageFile> packageFile(new PackageFile(context_, packageName));
  146. bool outputCompressionRatio = false;
  147. switch (arguments[0][1])
  148. {
  149. case 'i':
  150. PrintLine("Number of files: " + String(packageFile->GetNumFiles()));
  151. PrintLine("File data size: " + String(packageFile->GetTotalDataSize()));
  152. PrintLine("Package size: " + String(packageFile->GetTotalSize()));
  153. PrintLine("Checksum: " + String(packageFile->GetChecksum()));
  154. PrintLine("Compressed: " + String(packageFile->IsCompressed() ? "yes" : "no"));
  155. break;
  156. case 'L':
  157. if (!packageFile->IsCompressed())
  158. ErrorExit("Invalid output option: -L is applicable for compressed package file only");
  159. outputCompressionRatio = true;
  160. // Fallthrough
  161. case 'l':
  162. {
  163. const HashMap<String, PackageEntry>& entries = packageFile->GetEntries();
  164. for (HashMap<String, PackageEntry>::ConstIterator i = entries.Begin(); i != entries.End();)
  165. {
  166. HashMap<String, PackageEntry>::ConstIterator current = i++;
  167. String fileEntry(current->first_);
  168. if (outputCompressionRatio)
  169. {
  170. unsigned compressedSize =
  171. (i == entries.End() ? packageFile->GetTotalSize() - sizeof(unsigned) : i->second_.offset_) -
  172. current->second_.offset_;
  173. fileEntry.AppendWithFormat("\tin: %u\tout: %u\tratio: %f", current->second_.size_, compressedSize,
  174. compressedSize ? 1.f * current->second_.size_ / compressedSize : 0.f);
  175. }
  176. PrintLine(fileEntry);
  177. }
  178. }
  179. break;
  180. default:
  181. ErrorExit("Unrecognized output option");
  182. }
  183. }
  184. }
  185. void ProcessFile(const String& fileName, const String& rootDir)
  186. {
  187. String fullPath = rootDir + "/" + fileName;
  188. File file(context_);
  189. if (!file.Open(fullPath))
  190. ErrorExit("Could not open file " + fileName);
  191. if (!file.GetSize())
  192. return;
  193. FileEntry newEntry;
  194. newEntry.name_ = fileName;
  195. newEntry.offset_ = 0; // Offset not yet known
  196. newEntry.size_ = file.GetSize();
  197. newEntry.checksum_ = 0; // Will be calculated later
  198. entries_.Push(newEntry);
  199. }
  200. void WritePackageFile(const String& fileName, const String& rootDir)
  201. {
  202. if (!quiet_)
  203. PrintLine("Writing package");
  204. File dest(context_);
  205. if (!dest.Open(fileName, FILE_WRITE))
  206. ErrorExit("Could not open output file " + fileName);
  207. // Write ID, number of files & placeholder for checksum
  208. WriteHeader(dest);
  209. for (unsigned i = 0; i < entries_.Size(); ++i)
  210. {
  211. // Write entry (correct offset is still unknown, will be filled in later)
  212. dest.WriteString(basePath_ + entries_[i].name_);
  213. dest.WriteUInt(entries_[i].offset_);
  214. dest.WriteUInt(entries_[i].size_);
  215. dest.WriteUInt(entries_[i].checksum_);
  216. }
  217. unsigned totalDataSize = 0;
  218. unsigned lastOffset;
  219. // Write file data, calculate checksums & correct offsets
  220. for (unsigned i = 0; i < entries_.Size(); ++i)
  221. {
  222. lastOffset = entries_[i].offset_ = dest.GetSize();
  223. String fileFullPath = rootDir + "/" + entries_[i].name_;
  224. File srcFile(context_, fileFullPath);
  225. if (!srcFile.IsOpen())
  226. ErrorExit("Could not open file " + fileFullPath);
  227. unsigned dataSize = entries_[i].size_;
  228. totalDataSize += dataSize;
  229. SharedArrayPtr<unsigned char> buffer(new unsigned char[dataSize]);
  230. if (srcFile.Read(&buffer[0], dataSize) != dataSize)
  231. ErrorExit("Could not read file " + fileFullPath);
  232. srcFile.Close();
  233. for (unsigned j = 0; j < dataSize; ++j)
  234. {
  235. checksum_ = SDBMHash(checksum_, buffer[j]);
  236. entries_[i].checksum_ = SDBMHash(entries_[i].checksum_, buffer[j]);
  237. }
  238. if (!compress_)
  239. {
  240. if (!quiet_)
  241. PrintLine(entries_[i].name_ + " size " + String(dataSize));
  242. dest.Write(&buffer[0], entries_[i].size_);
  243. }
  244. else
  245. {
  246. SharedArrayPtr<unsigned char> compressBuffer(new unsigned char[LZ4_compressBound(blockSize_)]);
  247. unsigned pos = 0;
  248. while (pos < dataSize)
  249. {
  250. unsigned unpackedSize = blockSize_;
  251. if (pos + unpackedSize > dataSize)
  252. unpackedSize = dataSize - pos;
  253. auto packedSize = (unsigned)LZ4_compress_HC((const char*)&buffer[pos], (char*)compressBuffer.Get(), unpackedSize, LZ4_compressBound(unpackedSize), 0);
  254. if (!packedSize)
  255. ErrorExit("LZ4 compression failed for file " + entries_[i].name_ + " at offset " + String(pos));
  256. dest.WriteUShort((unsigned short)unpackedSize);
  257. dest.WriteUShort((unsigned short)packedSize);
  258. dest.Write(compressBuffer.Get(), packedSize);
  259. pos += unpackedSize;
  260. }
  261. if (!quiet_)
  262. {
  263. unsigned totalPackedBytes = dest.GetSize() - lastOffset;
  264. String fileEntry(entries_[i].name_);
  265. fileEntry.AppendWithFormat("\tin: %u\tout: %u\tratio: %f", dataSize, totalPackedBytes,
  266. totalPackedBytes ? 1.f * dataSize / totalPackedBytes : 0.f);
  267. PrintLine(fileEntry);
  268. }
  269. }
  270. }
  271. // Write package size to the end of file to allow finding it linked to an executable file
  272. unsigned currentSize = dest.GetSize();
  273. dest.WriteUInt(currentSize + sizeof(unsigned));
  274. // Write header again with correct offsets & checksums
  275. dest.Seek(0);
  276. WriteHeader(dest);
  277. for (unsigned i = 0; i < entries_.Size(); ++i)
  278. {
  279. dest.WriteString(basePath_ + entries_[i].name_);
  280. dest.WriteUInt(entries_[i].offset_);
  281. dest.WriteUInt(entries_[i].size_);
  282. dest.WriteUInt(entries_[i].checksum_);
  283. }
  284. if (!quiet_)
  285. {
  286. PrintLine("Number of files: " + String(entries_.Size()));
  287. PrintLine("File data size: " + String(totalDataSize));
  288. PrintLine("Package size: " + String(dest.GetSize()));
  289. PrintLine("Checksum: " + String(checksum_));
  290. PrintLine("Compressed: " + String(compress_ ? "yes" : "no"));
  291. }
  292. }
  293. void WriteHeader(File& dest)
  294. {
  295. if (!compress_)
  296. dest.WriteFileID("UPAK");
  297. else
  298. dest.WriteFileID("ULZ4");
  299. dest.WriteUInt(entries_.Size());
  300. dest.WriteUInt(checksum_);
  301. }