export_plugin.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /**************************************************************************/
  2. /* export_plugin.h */
  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. #ifndef UWP_EXPORT_PLUGIN_H
  31. #define UWP_EXPORT_PLUGIN_H
  32. #include "app_packager.h"
  33. #include "core/config/project_settings.h"
  34. #include "core/crypto/crypto_core.h"
  35. #include "core/io/dir_access.h"
  36. #include "core/io/file_access.h"
  37. #include "core/io/marshalls.h"
  38. #include "core/io/zip_io.h"
  39. #include "core/object/class_db.h"
  40. #include "core/version.h"
  41. #include "editor/editor_node.h"
  42. #include "editor/editor_paths.h"
  43. #include "editor/export/editor_export_platform.h"
  44. #include "scene/resources/compressed_texture.h"
  45. #include "thirdparty/minizip/unzip.h"
  46. #include "thirdparty/minizip/zip.h"
  47. #include <zlib.h>
  48. // Capabilities
  49. static const char *uwp_capabilities[] = {
  50. "allJoyn",
  51. "codeGeneration",
  52. "internetClient",
  53. "internetClientServer",
  54. "privateNetworkClientServer",
  55. nullptr
  56. };
  57. static const char *uwp_uap_capabilities[] = {
  58. "appointments",
  59. "blockedChatMessages",
  60. "chat",
  61. "contacts",
  62. "enterpriseAuthentication",
  63. "musicLibrary",
  64. "objects3D",
  65. "picturesLibrary",
  66. "phoneCall",
  67. "removableStorage",
  68. "sharedUserCertificates",
  69. "userAccountInformation",
  70. "videosLibrary",
  71. "voipCall",
  72. nullptr
  73. };
  74. static const char *uwp_device_capabilities[] = {
  75. "bluetooth",
  76. "location",
  77. "microphone",
  78. "proximity",
  79. "webcam",
  80. nullptr
  81. };
  82. // Optional environment variables for defining confidential information. If any
  83. // of these is set, they will override the values set in the credentials file.
  84. const String ENV_UWP_SIGNING_CERT = "GODOT_UWP_SIGNING_CERTIFICATE";
  85. const String ENV_UWP_SIGNING_PASS = "GODOT_UWP_SIGNING_PASSWORD";
  86. class EditorExportPlatformUWP : public EditorExportPlatform {
  87. GDCLASS(EditorExportPlatformUWP, EditorExportPlatform);
  88. Ref<ImageTexture> logo;
  89. bool _valid_resource_name(const String &p_name) const {
  90. if (p_name.is_empty()) {
  91. return false;
  92. }
  93. if (p_name.ends_with(".")) {
  94. return false;
  95. }
  96. static const char *invalid_names[] = {
  97. "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",
  98. "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
  99. nullptr
  100. };
  101. const char **t = invalid_names;
  102. while (*t) {
  103. if (p_name == *t) {
  104. return false;
  105. }
  106. t++;
  107. }
  108. return true;
  109. }
  110. bool _valid_guid(const String &p_guid) const {
  111. Vector<String> parts = p_guid.split("-");
  112. if (parts.size() != 5) {
  113. return false;
  114. }
  115. if (parts[0].length() != 8) {
  116. return false;
  117. }
  118. for (int i = 1; i < 4; i++) {
  119. if (parts[i].length() != 4) {
  120. return false;
  121. }
  122. }
  123. if (parts[4].length() != 12) {
  124. return false;
  125. }
  126. return true;
  127. }
  128. bool _valid_bgcolor(const String &p_color) const {
  129. if (p_color.is_empty()) {
  130. return true;
  131. }
  132. if (p_color.begins_with("#") && p_color.is_valid_html_color()) {
  133. return true;
  134. }
  135. // Colors from https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
  136. static const char *valid_colors[] = {
  137. "aliceBlue", "antiqueWhite", "aqua", "aquamarine", "azure", "beige",
  138. "bisque", "black", "blanchedAlmond", "blue", "blueViolet", "brown",
  139. "burlyWood", "cadetBlue", "chartreuse", "chocolate", "coral", "cornflowerBlue",
  140. "cornsilk", "crimson", "cyan", "darkBlue", "darkCyan", "darkGoldenrod",
  141. "darkGray", "darkGreen", "darkKhaki", "darkMagenta", "darkOliveGreen", "darkOrange",
  142. "darkOrchid", "darkRed", "darkSalmon", "darkSeaGreen", "darkSlateBlue", "darkSlateGray",
  143. "darkTurquoise", "darkViolet", "deepPink", "deepSkyBlue", "dimGray", "dodgerBlue",
  144. "firebrick", "floralWhite", "forestGreen", "fuchsia", "gainsboro", "ghostWhite",
  145. "gold", "goldenrod", "gray", "green", "greenYellow", "honeydew",
  146. "hotPink", "indianRed", "indigo", "ivory", "khaki", "lavender",
  147. "lavenderBlush", "lawnGreen", "lemonChiffon", "lightBlue", "lightCoral", "lightCyan",
  148. "lightGoldenrodYellow", "lightGreen", "lightGray", "lightPink", "lightSalmon", "lightSeaGreen",
  149. "lightSkyBlue", "lightSlateGray", "lightSteelBlue", "lightYellow", "lime", "limeGreen",
  150. "linen", "magenta", "maroon", "mediumAquamarine", "mediumBlue", "mediumOrchid",
  151. "mediumPurple", "mediumSeaGreen", "mediumSlateBlue", "mediumSpringGreen", "mediumTurquoise", "mediumVioletRed",
  152. "midnightBlue", "mintCream", "mistyRose", "moccasin", "navajoWhite", "navy",
  153. "oldLace", "olive", "oliveDrab", "orange", "orangeRed", "orchid",
  154. "paleGoldenrod", "paleGreen", "paleTurquoise", "paleVioletRed", "papayaWhip", "peachPuff",
  155. "peru", "pink", "plum", "powderBlue", "purple", "red",
  156. "rosyBrown", "royalBlue", "saddleBrown", "salmon", "sandyBrown", "seaGreen",
  157. "seaShell", "sienna", "silver", "skyBlue", "slateBlue", "slateGray",
  158. "snow", "springGreen", "steelBlue", "tan", "teal", "thistle",
  159. "tomato", "transparent", "turquoise", "violet", "wheat", "white",
  160. "whiteSmoke", "yellow", "yellowGreen",
  161. nullptr
  162. };
  163. const char **color = valid_colors;
  164. while (*color) {
  165. if (p_color == *color) {
  166. return true;
  167. }
  168. color++;
  169. }
  170. return false;
  171. }
  172. bool _valid_image(const CompressedTexture2D *p_image, int p_width, int p_height) const {
  173. if (!p_image) {
  174. return false;
  175. }
  176. // TODO: Add resource creation or image rescaling to enable other scales:
  177. // 1.25, 1.5, 2.0
  178. return p_width == p_image->get_width() && p_height == p_image->get_height();
  179. }
  180. Vector<uint8_t> _fix_manifest(const Ref<EditorExportPreset> &p_preset, const Vector<uint8_t> &p_template, bool p_give_internet) const {
  181. String result = String::utf8((const char *)p_template.ptr(), p_template.size());
  182. result = result.replace("$godot_version$", VERSION_FULL_NAME);
  183. result = result.replace("$identity_name$", p_preset->get("package/unique_name"));
  184. result = result.replace("$publisher$", p_preset->get("package/publisher"));
  185. result = result.replace("$product_guid$", p_preset->get("identity/product_guid"));
  186. result = result.replace("$publisher_guid$", p_preset->get("identity/publisher_guid"));
  187. String version = itos(p_preset->get("version/major")) + "." + itos(p_preset->get("version/minor")) + "." + itos(p_preset->get("version/build")) + "." + itos(p_preset->get("version/revision"));
  188. result = result.replace("$version_string$", version);
  189. String arch = p_preset->get("binary_format/architecture");
  190. String architecture = arch == "arm32" ? "arm" : (arch == "x86_32" ? "x86" : "x64");
  191. result = result.replace("$architecture$", architecture);
  192. result = result.replace("$display_name$", String(p_preset->get("package/display_name")).is_empty() ? (String)GLOBAL_GET("application/config/name") : String(p_preset->get("package/display_name")));
  193. result = result.replace("$publisher_display_name$", p_preset->get("package/publisher_display_name"));
  194. result = result.replace("$app_description$", p_preset->get("package/description"));
  195. result = result.replace("$bg_color$", p_preset->get("images/background_color"));
  196. result = result.replace("$short_name$", p_preset->get("package/short_name"));
  197. String name_on_tiles = "";
  198. if ((bool)p_preset->get("tiles/show_name_on_square150x150")) {
  199. name_on_tiles += " <uap:ShowOn Tile=\"square150x150Logo\" />\n";
  200. }
  201. if ((bool)p_preset->get("tiles/show_name_on_wide310x150")) {
  202. name_on_tiles += " <uap:ShowOn Tile=\"wide310x150Logo\" />\n";
  203. }
  204. if ((bool)p_preset->get("tiles/show_name_on_square310x310")) {
  205. name_on_tiles += " <uap:ShowOn Tile=\"square310x310Logo\" />\n";
  206. }
  207. String show_name_on_tiles = "";
  208. if (!name_on_tiles.is_empty()) {
  209. show_name_on_tiles = "<uap:ShowNameOnTiles>\n" + name_on_tiles + " </uap:ShowNameOnTiles>";
  210. }
  211. result = result.replace("$name_on_tiles$", name_on_tiles);
  212. String rotations = "";
  213. if ((bool)p_preset->get("orientation/landscape")) {
  214. rotations += " <uap:Rotation Preference=\"landscape\" />\n";
  215. }
  216. if ((bool)p_preset->get("orientation/portrait")) {
  217. rotations += " <uap:Rotation Preference=\"portrait\" />\n";
  218. }
  219. if ((bool)p_preset->get("orientation/landscape_flipped")) {
  220. rotations += " <uap:Rotation Preference=\"landscapeFlipped\" />\n";
  221. }
  222. if ((bool)p_preset->get("orientation/portrait_flipped")) {
  223. rotations += " <uap:Rotation Preference=\"portraitFlipped\" />\n";
  224. }
  225. String rotation_preference = "";
  226. if (!rotations.is_empty()) {
  227. rotation_preference = "<uap:InitialRotationPreference>\n" + rotations + " </uap:InitialRotationPreference>";
  228. }
  229. result = result.replace("$rotation_preference$", rotation_preference);
  230. String capabilities_elements = "";
  231. const char **basic = uwp_capabilities;
  232. while (*basic) {
  233. if ((bool)p_preset->get("capabilities/" + String(*basic))) {
  234. capabilities_elements += " <Capability Name=\"" + String(*basic) + "\" />\n";
  235. }
  236. basic++;
  237. }
  238. const char **uap = uwp_uap_capabilities;
  239. while (*uap) {
  240. if ((bool)p_preset->get("capabilities/" + String(*uap))) {
  241. capabilities_elements += " <uap:Capability Name=\"" + String(*uap) + "\" />\n";
  242. }
  243. uap++;
  244. }
  245. const char **device = uwp_device_capabilities;
  246. while (*device) {
  247. if ((bool)p_preset->get("capabilities/" + String(*device))) {
  248. capabilities_elements += " <DeviceCapability Name=\"" + String(*device) + "\" />\n";
  249. }
  250. device++;
  251. }
  252. if (!((bool)p_preset->get("capabilities/internetClient")) && p_give_internet) {
  253. capabilities_elements += " <Capability Name=\"internetClient\" />\n";
  254. }
  255. String capabilities_string = "<Capabilities />";
  256. if (!capabilities_elements.is_empty()) {
  257. capabilities_string = "<Capabilities>\n" + capabilities_elements + " </Capabilities>";
  258. }
  259. result = result.replace("$capabilities_place$", capabilities_string);
  260. Vector<uint8_t> r_ret;
  261. r_ret.resize(result.length());
  262. for (int i = 0; i < result.length(); i++) {
  263. r_ret.write[i] = result.utf8().get(i);
  264. }
  265. return r_ret;
  266. }
  267. Vector<uint8_t> _get_image_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
  268. Vector<uint8_t> data;
  269. CompressedTexture2D *texture = nullptr;
  270. if (p_path.find("StoreLogo") != -1) {
  271. texture = p_preset->get("images/store_logo").is_zero() ? nullptr : Object::cast_to<CompressedTexture2D>(((Object *)p_preset->get("images/store_logo")));
  272. } else if (p_path.find("Square44x44Logo") != -1) {
  273. texture = p_preset->get("images/square44x44_logo").is_zero() ? nullptr : Object::cast_to<CompressedTexture2D>(((Object *)p_preset->get("images/square44x44_logo")));
  274. } else if (p_path.find("Square71x71Logo") != -1) {
  275. texture = p_preset->get("images/square71x71_logo").is_zero() ? nullptr : Object::cast_to<CompressedTexture2D>(((Object *)p_preset->get("images/square71x71_logo")));
  276. } else if (p_path.find("Square150x150Logo") != -1) {
  277. texture = p_preset->get("images/square150x150_logo").is_zero() ? nullptr : Object::cast_to<CompressedTexture2D>(((Object *)p_preset->get("images/square150x150_logo")));
  278. } else if (p_path.find("Square310x310Logo") != -1) {
  279. texture = p_preset->get("images/square310x310_logo").is_zero() ? nullptr : Object::cast_to<CompressedTexture2D>(((Object *)p_preset->get("images/square310x310_logo")));
  280. } else if (p_path.find("Wide310x150Logo") != -1) {
  281. texture = p_preset->get("images/wide310x150_logo").is_zero() ? nullptr : Object::cast_to<CompressedTexture2D>(((Object *)p_preset->get("images/wide310x150_logo")));
  282. } else if (p_path.find("SplashScreen") != -1) {
  283. texture = p_preset->get("images/splash_screen").is_zero() ? nullptr : Object::cast_to<CompressedTexture2D>(((Object *)p_preset->get("images/splash_screen")));
  284. } else {
  285. ERR_PRINT("Unable to load logo");
  286. }
  287. if (!texture) {
  288. return data;
  289. }
  290. String tmp_path = EditorPaths::get_singleton()->get_cache_dir().path_join("uwp_tmp_logo.png");
  291. Error err = texture->get_image()->save_png(tmp_path);
  292. if (err != OK) {
  293. String err_string = "Couldn't save temp logo file.";
  294. EditorNode::add_io_error(err_string);
  295. ERR_FAIL_V_MSG(data, err_string);
  296. }
  297. {
  298. Ref<FileAccess> f = FileAccess::open(tmp_path, FileAccess::READ, &err);
  299. if (err != OK) {
  300. String err_string = "Couldn't open temp logo file.";
  301. // Cleanup generated file.
  302. DirAccess::remove_file_or_error(tmp_path);
  303. EditorNode::add_io_error(err_string);
  304. ERR_FAIL_V_MSG(data, err_string);
  305. }
  306. data.resize(f->get_length());
  307. f->get_buffer(data.ptrw(), data.size());
  308. }
  309. DirAccess::remove_file_or_error(tmp_path);
  310. return data;
  311. }
  312. static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data) {
  313. /* TODO: This was copied verbatim from Android export. It should be
  314. * refactored to the parent class and also be used for .zip export.
  315. */
  316. /*
  317. * By not compressing files with little or not benefit in doing so,
  318. * a performance gain is expected at runtime. Moreover, if the APK is
  319. * zip-aligned, assets stored as they are can be efficiently read by
  320. * Android by memory-mapping them.
  321. */
  322. // -- Unconditional uncompress to mimic AAPT plus some other
  323. static const char *unconditional_compress_ext[] = {
  324. // From https://github.com/android/platform_frameworks_base/blob/master/tools/aapt/Package.cpp
  325. // These formats are already compressed, or don't compress well:
  326. ".jpg", ".jpeg", ".png", ".gif",
  327. ".wav", ".mp2", ".mp3", ".ogg", ".aac",
  328. ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
  329. ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
  330. ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
  331. ".amr", ".awb", ".wma", ".wmv",
  332. // Godot-specific:
  333. ".webp", // Same reasoning as .png
  334. ".cfb", // Don't let small config files slow-down startup
  335. ".scn", // Binary scenes are usually already compressed
  336. ".ctex", // Streamable textures are usually already compressed
  337. // Trailer for easier processing
  338. nullptr
  339. };
  340. for (const char **ext = unconditional_compress_ext; *ext; ++ext) {
  341. if (p_path.to_lower().ends_with(String(*ext))) {
  342. return false;
  343. }
  344. }
  345. // -- Compressed resource?
  346. if (p_data.size() >= 4 && p_data[0] == 'R' && p_data[1] == 'S' && p_data[2] == 'C' && p_data[3] == 'C') {
  347. // Already compressed
  348. return false;
  349. }
  350. // --- TODO: Decide on texture resources according to their image compression setting
  351. return true;
  352. }
  353. static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
  354. AppxPackager *packager = static_cast<AppxPackager *>(p_userdata);
  355. String dst_path = p_path.replace_first("res://", "game/");
  356. return packager->add_file(dst_path, p_data.ptr(), p_data.size(), p_file, p_total, _should_compress_asset(p_path, p_data));
  357. }
  358. public:
  359. virtual String get_name() const override;
  360. virtual String get_os_name() const override;
  361. virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
  362. virtual Ref<Texture2D> get_logo() const override;
  363. virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
  364. virtual void get_export_options(List<ExportOption> *r_options) const override;
  365. virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
  366. virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
  367. virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
  368. virtual void get_platform_features(List<String> *r_features) const override;
  369. virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override;
  370. EditorExportPlatformUWP();
  371. };
  372. #endif // UWP_EXPORT_PLUGIN_H