1 /*************************************************************************/
2 /*  export.cpp                                                           */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
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 
31 #include "core/os/file_access.h"
32 #include "core/os/os.h"
33 #include "editor/editor_export.h"
34 #include "editor/editor_node.h"
35 #include "editor/editor_settings.h"
36 #include "platform/windows/logo.gen.h"
37 
38 static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size);
39 
40 class EditorExportPlatformWindows : public EditorExportPlatformPC {
41 
42 	void _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path);
43 	Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path);
44 
45 public:
46 	virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
47 	virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
48 	virtual void get_export_options(List<ExportOption> *r_options);
49 };
50 
sign_shared_object(const Ref<EditorExportPreset> & p_preset,bool p_debug,const String & p_path)51 Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
52 	if (p_preset->get("codesign/enable")) {
53 		return _code_sign(p_preset, p_path);
54 	} else {
55 		return OK;
56 	}
57 }
58 
export_project(const Ref<EditorExportPreset> & p_preset,bool p_debug,const String & p_path,int p_flags)59 Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
60 	Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, p_path, p_flags);
61 
62 	if (err != OK) {
63 		return err;
64 	}
65 
66 	_rcedit_add_data(p_preset, p_path);
67 
68 	if (p_preset->get("codesign/enable") && err == OK) {
69 		err = _code_sign(p_preset, p_path);
70 	}
71 
72 	return err;
73 }
74 
get_export_options(List<ExportOption> * r_options)75 void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) {
76 	EditorExportPlatformPC::get_export_options(r_options);
77 
78 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false));
79 #ifdef WINDOWS_ENABLED
80 	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA1 hash)"), 0));
81 #endif
82 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), ""));
83 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password"), ""));
84 	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true));
85 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/timestamp_server_url"), ""));
86 	r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/digest_algorithm", PROPERTY_HINT_ENUM, "SHA1,SHA256"), 1));
87 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/description"), ""));
88 	r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray()));
89 
90 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), ""));
91 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), ""));
92 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), ""));
93 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), ""));
94 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
95 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), ""));
96 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
97 	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/trademarks"), ""));
98 }
99 
_rcedit_add_data(const Ref<EditorExportPreset> & p_preset,const String & p_path)100 void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
101 	String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
102 
103 	if (rcedit_path == String()) {
104 		return;
105 	}
106 
107 	if (!FileAccess::exists(rcedit_path)) {
108 		ERR_PRINTS("Could not find rcedit executable at " + rcedit_path + ", no icon or app information data will be included.");
109 		return;
110 	}
111 
112 #ifndef WINDOWS_ENABLED
113 	// On non-Windows we need WINE to run rcedit
114 	String wine_path = EditorSettings::get_singleton()->get("export/windows/wine");
115 
116 	if (wine_path != String() && !FileAccess::exists(wine_path)) {
117 		ERR_PRINTS("Could not find wine executable at " + wine_path + ", no icon or app information data will be included.");
118 		return;
119 	}
120 
121 	if (wine_path == String()) {
122 		wine_path = "wine"; // try to run wine from PATH
123 	}
124 #endif
125 
126 	String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon"));
127 	String file_verion = p_preset->get("application/file_version");
128 	String product_version = p_preset->get("application/product_version");
129 	String company_name = p_preset->get("application/company_name");
130 	String product_name = p_preset->get("application/product_name");
131 	String file_description = p_preset->get("application/file_description");
132 	String copyright = p_preset->get("application/copyright");
133 	String trademarks = p_preset->get("application/trademarks");
134 	String comments = p_preset->get("application/comments");
135 
136 	List<String> args;
137 	args.push_back(p_path);
138 	if (icon_path != String()) {
139 		args.push_back("--set-icon");
140 		args.push_back(icon_path);
141 	}
142 	if (file_verion != String()) {
143 		args.push_back("--set-file-version");
144 		args.push_back(file_verion);
145 	}
146 	if (product_version != String()) {
147 		args.push_back("--set-product-version");
148 		args.push_back(product_version);
149 	}
150 	if (company_name != String()) {
151 		args.push_back("--set-version-string");
152 		args.push_back("CompanyName");
153 		args.push_back(company_name);
154 	}
155 	if (product_name != String()) {
156 		args.push_back("--set-version-string");
157 		args.push_back("ProductName");
158 		args.push_back(product_name);
159 	}
160 	if (file_description != String()) {
161 		args.push_back("--set-version-string");
162 		args.push_back("FileDescription");
163 		args.push_back(file_description);
164 	}
165 	if (copyright != String()) {
166 		args.push_back("--set-version-string");
167 		args.push_back("LegalCopyright");
168 		args.push_back(copyright);
169 	}
170 	if (trademarks != String()) {
171 		args.push_back("--set-version-string");
172 		args.push_back("LegalTrademarks");
173 		args.push_back(trademarks);
174 	}
175 
176 #ifdef WINDOWS_ENABLED
177 	OS::get_singleton()->execute(rcedit_path, args, true);
178 #else
179 	// On non-Windows we need WINE to run rcedit
180 	args.push_front(rcedit_path);
181 	OS::get_singleton()->execute(wine_path, args, true);
182 #endif
183 }
184 
_code_sign(const Ref<EditorExportPreset> & p_preset,const String & p_path)185 Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
186 	List<String> args;
187 
188 #ifdef WINDOWS_ENABLED
189 	String signtool_path = EditorSettings::get_singleton()->get("export/windows/signtool");
190 	if (signtool_path != String() && !FileAccess::exists(signtool_path)) {
191 		ERR_PRINTS("Could not find signtool executable at " + signtool_path + ", aborting.");
192 		return ERR_FILE_NOT_FOUND;
193 	}
194 	if (signtool_path == String()) {
195 		signtool_path = "signtool"; // try to run signtool from PATH
196 	}
197 #else
198 	String signtool_path = EditorSettings::get_singleton()->get("export/windows/osslsigncode");
199 	if (signtool_path != String() && !FileAccess::exists(signtool_path)) {
200 		ERR_PRINTS("Could not find osslsigncode executable at " + signtool_path + ", aborting.");
201 		return ERR_FILE_NOT_FOUND;
202 	}
203 	if (signtool_path == String()) {
204 		signtool_path = "osslsigncode"; // try to run signtool from PATH
205 	}
206 #endif
207 
208 	args.push_back("sign");
209 
210 	//identity
211 #ifdef WINDOWS_ENABLED
212 	int id_type = p_preset->get("codesign/identity_type");
213 	if (id_type == 0) { //auto select
214 		args.push_back("/a");
215 	} else if (id_type == 1) { //pkcs12
216 		if (p_preset->get("codesign/identity") != "") {
217 			args.push_back("/f");
218 			args.push_back(p_preset->get("codesign/identity"));
219 		} else {
220 			EditorNode::add_io_error("codesign: no identity found");
221 			return FAILED;
222 		}
223 	} else if (id_type == 2) { //Windows certificate store
224 		if (p_preset->get("codesign/identity") != "") {
225 			args.push_back("/sha1");
226 			args.push_back(p_preset->get("codesign/identity"));
227 		} else {
228 			EditorNode::add_io_error("codesign: no identity found");
229 			return FAILED;
230 		}
231 	} else {
232 		EditorNode::add_io_error("codesign: invalid identity type");
233 		return FAILED;
234 	}
235 #else
236 	if (p_preset->get("codesign/identity") != "") {
237 		args.push_back("-pkcs12");
238 		args.push_back(p_preset->get("codesign/identity"));
239 	} else {
240 		EditorNode::add_io_error("codesign: no identity found");
241 		return FAILED;
242 	}
243 #endif
244 
245 	//password
246 	if (p_preset->get("codesign/password") != "") {
247 #ifdef WINDOWS_ENABLED
248 		args.push_back("/p");
249 #else
250 		args.push_back("-pass");
251 #endif
252 		args.push_back(p_preset->get("codesign/password"));
253 	}
254 
255 	//timestamp
256 	if (p_preset->get("codesign/timestamp")) {
257 		if (p_preset->get("codesign/timestamp_server") != "") {
258 #ifdef WINDOWS_ENABLED
259 			args.push_back("/tr");
260 			args.push_back(p_preset->get("codesign/timestamp_server_url"));
261 			args.push_back("/td");
262 			if ((int)p_preset->get("codesign/digest_algorithm") == 0) {
263 				args.push_back("sha1");
264 			} else {
265 				args.push_back("sha256");
266 			}
267 #else
268 			args.push_back("-ts");
269 			args.push_back(p_preset->get("codesign/timestamp_server_url"));
270 #endif
271 		} else {
272 			EditorNode::add_io_error("codesign: invalid timestamp server");
273 			return FAILED;
274 		}
275 	}
276 
277 	//digest
278 #ifdef WINDOWS_ENABLED
279 	args.push_back("/fd");
280 #else
281 	args.push_back("-h");
282 #endif
283 	if ((int)p_preset->get("codesign/digest_algorithm") == 0) {
284 		args.push_back("sha1");
285 	} else {
286 		args.push_back("sha256");
287 	}
288 
289 	//description
290 	if (p_preset->get("codesign/description") != "") {
291 #ifdef WINDOWS_ENABLED
292 		args.push_back("/d");
293 #else
294 		args.push_back("-n");
295 #endif
296 		args.push_back(p_preset->get("codesign/description"));
297 	}
298 
299 	//user options
300 	PoolStringArray user_args = p_preset->get("codesign/custom_options");
301 	for (int i = 0; i < user_args.size(); i++) {
302 		String user_arg = user_args[i].strip_edges();
303 		if (!user_arg.empty()) {
304 			args.push_back(user_arg);
305 		}
306 	}
307 
308 #ifndef WINDOWS_ENABLED
309 	args.push_back("-in");
310 #endif
311 	args.push_back(p_path);
312 #ifndef WINDOWS_ENABLED
313 	args.push_back("-out");
314 	args.push_back(p_path);
315 #endif
316 
317 	String str;
318 	Error err = OS::get_singleton()->execute(signtool_path, args, true, NULL, &str, NULL, true);
319 	ERR_FAIL_COND_V(err != OK, err);
320 
321 	print_line("codesign (" + p_path + "): " + str);
322 #ifndef WINDOWS_ENABLED
323 	if (str.find("SignTool Error") != -1) {
324 #else
325 	if (str.find("Failed") != -1) {
326 #endif
327 		return FAILED;
328 	}
329 
330 	return OK;
331 }
332 
333 void register_windows_exporter() {
334 
335 	EDITOR_DEF("export/windows/rcedit", "");
336 	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/rcedit", PROPERTY_HINT_GLOBAL_FILE, "*.exe"));
337 #ifdef WINDOWS_ENABLED
338 	EDITOR_DEF("export/windows/signtool", "");
339 	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/signtool", PROPERTY_HINT_GLOBAL_FILE, "*.exe"));
340 #else
341 	EDITOR_DEF("export/windows/osslsigncode", "");
342 	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/osslsigncode", PROPERTY_HINT_GLOBAL_FILE));
343 	// On non-Windows we need WINE to run rcedit
344 	EDITOR_DEF("export/windows/wine", "");
345 	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/wine", PROPERTY_HINT_GLOBAL_FILE));
346 #endif
347 
348 	Ref<EditorExportPlatformWindows> platform;
349 	platform.instance();
350 
351 	Ref<Image> img = memnew(Image(_windows_logo));
352 	Ref<ImageTexture> logo;
353 	logo.instance();
354 	logo->create_from_image(img);
355 	platform->set_logo(logo);
356 	platform->set_name("Windows Desktop");
357 	platform->set_extension("exe");
358 	platform->set_release_32("windows_32_release.exe");
359 	platform->set_debug_32("windows_32_debug.exe");
360 	platform->set_release_64("windows_64_release.exe");
361 	platform->set_debug_64("windows_64_debug.exe");
362 	platform->set_os_name("Windows");
363 	platform->set_fixup_embedded_pck_func(&fixup_embedded_pck);
364 
365 	EditorExport::get_singleton()->add_export_platform(platform);
366 }
367 
368 static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) {
369 
370 	// Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data
371 
372 	FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE);
373 	if (!f) {
374 		return ERR_CANT_OPEN;
375 	}
376 
377 	// Jump to the PE header and check the magic number
378 	{
379 		f->seek(0x3c);
380 		uint32_t pe_pos = f->get_32();
381 
382 		f->seek(pe_pos);
383 		uint32_t magic = f->get_32();
384 		if (magic != 0x00004550) {
385 			f->close();
386 			return ERR_FILE_CORRUPT;
387 		}
388 	}
389 
390 	// Process header
391 
392 	int num_sections;
393 	{
394 		int64_t header_pos = f->get_position();
395 
396 		f->seek(header_pos + 2);
397 		num_sections = f->get_16();
398 		f->seek(header_pos + 16);
399 		uint16_t opt_header_size = f->get_16();
400 
401 		// Skip rest of header + optional header to go to the section headers
402 		f->seek(f->get_position() + 2 + opt_header_size);
403 	}
404 
405 	// Search for the "pck" section
406 
407 	int64_t section_table_pos = f->get_position();
408 
409 	bool found = false;
410 	for (int i = 0; i < num_sections; ++i) {
411 
412 		int64_t section_header_pos = section_table_pos + i * 40;
413 		f->seek(section_header_pos);
414 
415 		uint8_t section_name[9];
416 		f->get_buffer(section_name, 8);
417 		section_name[8] = '\0';
418 
419 		if (strcmp((char *)section_name, "pck") == 0) {
420 			// "pck" section found, let's patch!
421 
422 			// Set virtual size to a little to avoid it taking memory (zero would give issues)
423 			f->seek(section_header_pos + 8);
424 			f->store_32(8);
425 
426 			f->seek(section_header_pos + 16);
427 			f->store_32(p_embedded_size);
428 			f->seek(section_header_pos + 20);
429 			f->store_32(p_embedded_start);
430 
431 			found = true;
432 			break;
433 		}
434 	}
435 
436 	f->close();
437 
438 	return found ? OK : ERR_FILE_CORRUPT;
439 }
440