app_packager.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. /**************************************************************************/
  2. /* app_packager.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "app_packager.h"
  31. #include "editor/editor_node.h"
  32. #include "editor/editor_paths.h"
  33. String AppxPackager::hash_block(const uint8_t *p_block_data, size_t p_block_len) {
  34. unsigned char hash[32];
  35. char base64[45];
  36. CryptoCore::sha256(p_block_data, p_block_len, hash);
  37. size_t len = 0;
  38. CryptoCore::b64_encode((unsigned char *)base64, 45, &len, (unsigned char *)hash, 32);
  39. base64[44] = '\0';
  40. return String(base64);
  41. }
  42. void AppxPackager::make_block_map(const String &p_path) {
  43. Ref<FileAccess> tmp_file = FileAccess::open(p_path, FileAccess::WRITE);
  44. tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
  45. tmp_file->store_string("<BlockMap xmlns=\"http://schemas.microsoft.com/appx/2010/blockmap\" HashMethod=\"http://www.w3.org/2001/04/xmlenc#sha256\">");
  46. for (int i = 0; i < file_metadata.size(); i++) {
  47. FileMeta file = file_metadata[i];
  48. tmp_file->store_string(
  49. "<File Name=\"" + file.name.replace("/", "\\") + "\" Size=\"" + itos(file.uncompressed_size) + "\" LfhSize=\"" + itos(file.lfh_size) + "\">");
  50. for (int j = 0; j < file.hashes.size(); j++) {
  51. tmp_file->store_string("<Block Hash=\"" + file.hashes[j].base64_hash + "\" ");
  52. if (file.compressed) {
  53. tmp_file->store_string("Size=\"" + itos(file.hashes[j].compressed_size) + "\" ");
  54. }
  55. tmp_file->store_string("/>");
  56. }
  57. tmp_file->store_string("</File>");
  58. }
  59. tmp_file->store_string("</BlockMap>");
  60. }
  61. String AppxPackager::content_type(String p_extension) {
  62. if (p_extension == "png") {
  63. return "image/png";
  64. } else if (p_extension == "jpg") {
  65. return "image/jpg";
  66. } else if (p_extension == "xml") {
  67. return "application/xml";
  68. } else if (p_extension == "exe" || p_extension == "dll") {
  69. return "application/x-msdownload";
  70. } else {
  71. return "application/octet-stream";
  72. }
  73. }
  74. void AppxPackager::make_content_types(const String &p_path) {
  75. Ref<FileAccess> tmp_file = FileAccess::open(p_path, FileAccess::WRITE);
  76. tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  77. tmp_file->store_string("<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">");
  78. HashMap<String, String> types;
  79. for (int i = 0; i < file_metadata.size(); i++) {
  80. String ext = file_metadata[i].name.get_extension().to_lower();
  81. if (types.has(ext)) {
  82. continue;
  83. }
  84. types[ext] = content_type(ext);
  85. tmp_file->store_string("<Default Extension=\"" + ext + "\" ContentType=\"" + types[ext] + "\" />");
  86. }
  87. // Appx signature file
  88. tmp_file->store_string("<Default Extension=\"p7x\" ContentType=\"application/octet-stream\" />");
  89. // Override for package files
  90. tmp_file->store_string("<Override PartName=\"/AppxManifest.xml\" ContentType=\"application/vnd.ms-appx.manifest+xml\" />");
  91. tmp_file->store_string("<Override PartName=\"/AppxBlockMap.xml\" ContentType=\"application/vnd.ms-appx.blockmap+xml\" />");
  92. tmp_file->store_string("<Override PartName=\"/AppxSignature.p7x\" ContentType=\"application/vnd.ms-appx.signature\" />");
  93. tmp_file->store_string("<Override PartName=\"/AppxMetadata/CodeIntegrity.cat\" ContentType=\"application/vnd.ms-pkiseccat\" />");
  94. tmp_file->store_string("</Types>");
  95. }
  96. Vector<uint8_t> AppxPackager::make_file_header(FileMeta p_file_meta) {
  97. Vector<uint8_t> buf;
  98. buf.resize(BASE_FILE_HEADER_SIZE + p_file_meta.name.length());
  99. int offs = 0;
  100. // Write magic
  101. offs += buf_put_int32(FILE_HEADER_MAGIC, &buf.write[offs]);
  102. // Version
  103. offs += buf_put_int16(ZIP_VERSION, &buf.write[offs]);
  104. // Special flag
  105. offs += buf_put_int16(GENERAL_PURPOSE, &buf.write[offs]);
  106. // Compression
  107. offs += buf_put_int16(p_file_meta.compressed ? Z_DEFLATED : 0, &buf.write[offs]);
  108. // File date and time
  109. offs += buf_put_int32(0, &buf.write[offs]);
  110. // CRC-32
  111. offs += buf_put_int32(p_file_meta.file_crc32, &buf.write[offs]);
  112. // Compressed size
  113. offs += buf_put_int32(p_file_meta.compressed_size, &buf.write[offs]);
  114. // Uncompressed size
  115. offs += buf_put_int32(p_file_meta.uncompressed_size, &buf.write[offs]);
  116. // File name length
  117. offs += buf_put_int16(p_file_meta.name.length(), &buf.write[offs]);
  118. // Extra data length
  119. offs += buf_put_int16(0, &buf.write[offs]);
  120. // File name
  121. offs += buf_put_string(p_file_meta.name, &buf.write[offs]);
  122. // Done!
  123. return buf;
  124. }
  125. void AppxPackager::store_central_dir_header(const FileMeta &p_file, bool p_do_hash) {
  126. Vector<uint8_t> &buf = central_dir_data;
  127. int offs = buf.size();
  128. buf.resize(buf.size() + BASE_CENTRAL_DIR_SIZE + p_file.name.length());
  129. // Write magic
  130. offs += buf_put_int32(CENTRAL_DIR_MAGIC, &buf.write[offs]);
  131. // ZIP versions
  132. offs += buf_put_int16(ZIP_ARCHIVE_VERSION, &buf.write[offs]);
  133. offs += buf_put_int16(ZIP_VERSION, &buf.write[offs]);
  134. // General purpose flag
  135. offs += buf_put_int16(GENERAL_PURPOSE, &buf.write[offs]);
  136. // Compression
  137. offs += buf_put_int16(p_file.compressed ? Z_DEFLATED : 0, &buf.write[offs]);
  138. // Modification date/time
  139. offs += buf_put_int32(0, &buf.write[offs]);
  140. // Crc-32
  141. offs += buf_put_int32(p_file.file_crc32, &buf.write[offs]);
  142. // File sizes
  143. offs += buf_put_int32(p_file.compressed_size, &buf.write[offs]);
  144. offs += buf_put_int32(p_file.uncompressed_size, &buf.write[offs]);
  145. // File name length
  146. offs += buf_put_int16(p_file.name.length(), &buf.write[offs]);
  147. // Extra field length
  148. offs += buf_put_int16(0, &buf.write[offs]);
  149. // Comment length
  150. offs += buf_put_int16(0, &buf.write[offs]);
  151. // Disk number start, internal/external file attributes
  152. for (int i = 0; i < 8; i++) {
  153. buf.write[offs++] = 0;
  154. }
  155. // Relative offset
  156. offs += buf_put_int32(p_file.zip_offset, &buf.write[offs]);
  157. // File name
  158. offs += buf_put_string(p_file.name, &buf.write[offs]);
  159. // Done!
  160. }
  161. Vector<uint8_t> AppxPackager::make_end_of_central_record() {
  162. Vector<uint8_t> buf;
  163. buf.resize(ZIP64_END_OF_CENTRAL_DIR_SIZE + 12 + END_OF_CENTRAL_DIR_SIZE); // Size plus magic
  164. int offs = 0;
  165. // Write magic
  166. offs += buf_put_int32(ZIP64_END_OF_CENTRAL_DIR_MAGIC, &buf.write[offs]);
  167. // Size of this record
  168. offs += buf_put_int64(ZIP64_END_OF_CENTRAL_DIR_SIZE, &buf.write[offs]);
  169. // Version (yes, twice)
  170. offs += buf_put_int16(ZIP_ARCHIVE_VERSION, &buf.write[offs]);
  171. offs += buf_put_int16(ZIP_ARCHIVE_VERSION, &buf.write[offs]);
  172. // Disk number
  173. for (int i = 0; i < 8; i++) {
  174. buf.write[offs++] = 0;
  175. }
  176. // Number of entries (total and per disk)
  177. offs += buf_put_int64(file_metadata.size(), &buf.write[offs]);
  178. offs += buf_put_int64(file_metadata.size(), &buf.write[offs]);
  179. // Size of central dir
  180. offs += buf_put_int64(central_dir_data.size(), &buf.write[offs]);
  181. // Central dir offset
  182. offs += buf_put_int64(central_dir_offset, &buf.write[offs]);
  183. ////// ZIP64 locator
  184. // Write magic for zip64 central dir locator
  185. offs += buf_put_int32(ZIP64_END_DIR_LOCATOR_MAGIC, &buf.write[offs]);
  186. // Disk number
  187. for (int i = 0; i < 4; i++) {
  188. buf.write[offs++] = 0;
  189. }
  190. // Relative offset
  191. offs += buf_put_int64(end_of_central_dir_offset, &buf.write[offs]);
  192. // Number of disks
  193. offs += buf_put_int32(1, &buf.write[offs]);
  194. /////// End of zip directory
  195. // Write magic for end central dir
  196. offs += buf_put_int32(END_OF_CENTRAL_DIR_MAGIC, &buf.write[offs]);
  197. // Dummy stuff for Zip64
  198. for (int i = 0; i < 4; i++) {
  199. buf.write[offs++] = 0x0;
  200. }
  201. for (int i = 0; i < 12; i++) {
  202. buf.write[offs++] = 0xFF;
  203. }
  204. // Size of comments
  205. for (int i = 0; i < 2; i++) {
  206. buf.write[offs++] = 0;
  207. }
  208. // Done!
  209. return buf;
  210. }
  211. void AppxPackager::init(Ref<FileAccess> p_fa) {
  212. package = p_fa;
  213. central_dir_offset = 0;
  214. end_of_central_dir_offset = 0;
  215. }
  216. Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) {
  217. if (p_file_no >= 1 && p_total_files >= 1) {
  218. if (EditorNode::progress_task_step(progress_task, "File: " + p_file_name, (p_file_no * 100) / p_total_files)) {
  219. return ERR_SKIP;
  220. }
  221. }
  222. FileMeta meta;
  223. meta.name = p_file_name;
  224. meta.uncompressed_size = p_len;
  225. meta.compressed = p_compress;
  226. meta.zip_offset = package->get_position();
  227. Vector<uint8_t> file_buffer;
  228. // Data for compression
  229. z_stream strm{};
  230. Vector<uint8_t> strm_in;
  231. strm_in.resize(BLOCK_SIZE);
  232. Vector<uint8_t> strm_out;
  233. if (p_compress) {
  234. strm.zalloc = zipio_alloc;
  235. strm.zfree = zipio_free;
  236. strm.opaque = Z_NULL;
  237. strm_out.resize(BLOCK_SIZE + 8);
  238. deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
  239. }
  240. int step = 0;
  241. while (p_len - step > 0) {
  242. size_t block_size = (p_len - step) > BLOCK_SIZE ? (size_t)BLOCK_SIZE : (p_len - step);
  243. for (uint64_t i = 0; i < block_size; i++) {
  244. strm_in.write[i] = p_buffer[step + i];
  245. }
  246. BlockHash bh;
  247. bh.base64_hash = hash_block(strm_in.ptr(), block_size);
  248. if (p_compress) {
  249. strm.avail_in = block_size;
  250. strm.avail_out = strm_out.size();
  251. strm.next_in = (uint8_t *)strm_in.ptr();
  252. strm.next_out = strm_out.ptrw();
  253. int total_out_before = strm.total_out;
  254. int err = deflate(&strm, Z_FULL_FLUSH);
  255. ERR_FAIL_COND_V(err < 0, ERR_BUG); // Negative means bug
  256. bh.compressed_size = strm.total_out - total_out_before;
  257. //package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before);
  258. int start = file_buffer.size();
  259. file_buffer.resize(file_buffer.size() + bh.compressed_size);
  260. for (uint64_t i = 0; i < bh.compressed_size; i++) {
  261. file_buffer.write[start + i] = strm_out[i];
  262. }
  263. } else {
  264. bh.compressed_size = block_size;
  265. //package->store_buffer(strm_in.ptr(), block_size);
  266. int start = file_buffer.size();
  267. file_buffer.resize(file_buffer.size() + block_size);
  268. for (uint64_t i = 0; i < bh.compressed_size; i++) {
  269. file_buffer.write[start + i] = strm_in[i];
  270. }
  271. }
  272. meta.hashes.push_back(bh);
  273. step += block_size;
  274. }
  275. if (p_compress) {
  276. strm.avail_in = 0;
  277. strm.avail_out = strm_out.size();
  278. strm.next_in = (uint8_t *)strm_in.ptr();
  279. strm.next_out = strm_out.ptrw();
  280. int total_out_before = strm.total_out;
  281. deflate(&strm, Z_FINISH);
  282. //package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before);
  283. int start = file_buffer.size();
  284. file_buffer.resize(file_buffer.size() + (strm.total_out - total_out_before));
  285. for (uint64_t i = 0; i < (strm.total_out - total_out_before); i++) {
  286. file_buffer.write[start + i] = strm_out[i];
  287. }
  288. deflateEnd(&strm);
  289. meta.compressed_size = strm.total_out;
  290. } else {
  291. meta.compressed_size = p_len;
  292. }
  293. // Calculate file CRC-32
  294. uLong crc = crc32(0L, Z_NULL, 0);
  295. crc = crc32(crc, p_buffer, p_len);
  296. meta.file_crc32 = crc;
  297. // Create file header
  298. Vector<uint8_t> file_header = make_file_header(meta);
  299. meta.lfh_size = file_header.size();
  300. // Store the header and file;
  301. package->store_buffer(file_header.ptr(), file_header.size());
  302. package->store_buffer(file_buffer.ptr(), file_buffer.size());
  303. file_metadata.push_back(meta);
  304. return OK;
  305. }
  306. void AppxPackager::finish() {
  307. // Create and add block map file
  308. EditorNode::progress_task_step("export", "Creating block map...", 4);
  309. const String &tmp_blockmap_file_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpblockmap.xml");
  310. make_block_map(tmp_blockmap_file_path);
  311. {
  312. Ref<FileAccess> blockmap_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::READ);
  313. Vector<uint8_t> blockmap_buffer;
  314. blockmap_buffer.resize(blockmap_file->get_length());
  315. blockmap_file->get_buffer(blockmap_buffer.ptrw(), blockmap_buffer.size());
  316. add_file("AppxBlockMap.xml", blockmap_buffer.ptr(), blockmap_buffer.size(), -1, -1, true);
  317. }
  318. // Add content types
  319. EditorNode::progress_task_step("export", "Setting content types...", 5);
  320. const String &tmp_content_types_file_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpcontenttypes.xml");
  321. make_content_types(tmp_content_types_file_path);
  322. {
  323. Ref<FileAccess> types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ);
  324. Vector<uint8_t> types_buffer;
  325. types_buffer.resize(types_file->get_length());
  326. types_file->get_buffer(types_buffer.ptrw(), types_buffer.size());
  327. add_file("[Content_Types].xml", types_buffer.ptr(), types_buffer.size(), -1, -1, true);
  328. }
  329. // Cleanup generated files.
  330. DirAccess::remove_file_or_error(tmp_blockmap_file_path);
  331. DirAccess::remove_file_or_error(tmp_content_types_file_path);
  332. // Pre-process central directory before signing
  333. for (int i = 0; i < file_metadata.size(); i++) {
  334. store_central_dir_header(file_metadata[i]);
  335. }
  336. // Write central directory
  337. EditorNode::progress_task_step("export", "Finishing package...", 6);
  338. central_dir_offset = package->get_position();
  339. package->store_buffer(central_dir_data.ptr(), central_dir_data.size());
  340. // End record
  341. end_of_central_dir_offset = package->get_position();
  342. Vector<uint8_t> end_record = make_end_of_central_record();
  343. package->store_buffer(end_record.ptr(), end_record.size());
  344. package.unref();
  345. }
  346. AppxPackager::AppxPackager() {}
  347. AppxPackager::~AppxPackager() {}