deploy-stub.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. /* Python interpreter main program for frozen scripts */
  2. #include "Python.h"
  3. #ifdef _WIN32
  4. # include "malloc.h"
  5. # include <Shlobj.h>
  6. #else
  7. # include <sys/mman.h>
  8. # include <pwd.h>
  9. #endif
  10. #ifdef __FreeBSD__
  11. # include <sys/sysctl.h>
  12. #endif
  13. #ifdef __APPLE__
  14. # include <mach-o/dyld.h>
  15. # include <libgen.h>
  16. #endif
  17. #include <stdio.h>
  18. #include <stdint.h>
  19. #include <fcntl.h>
  20. #include <locale.h>
  21. /* Leave room for future expansion. We only read pointer 0, but there are
  22. other pointers that are being read by configPageManager.cxx. */
  23. #define MAX_NUM_POINTERS 24
  24. /* Stored in the flags field of the blobinfo structure below. */
  25. enum Flags {
  26. F_log_append = 1,
  27. };
  28. /* Define an exposed symbol where we store the offset to the module data. */
  29. #ifdef _MSC_VER
  30. __declspec(dllexport)
  31. #else
  32. __attribute__((__visibility__("default"), used))
  33. #endif
  34. volatile struct {
  35. uint64_t blob_offset;
  36. uint64_t blob_size;
  37. uint16_t version;
  38. uint16_t num_pointers;
  39. uint16_t codepage;
  40. uint16_t flags;
  41. uint64_t reserved;
  42. void *pointers[MAX_NUM_POINTERS];
  43. // The reason we initialize it to -1 is because otherwise, smart linkers may
  44. // end up putting it in the .bss section for zero-initialized data.
  45. } blobinfo = {(uint64_t)-1};
  46. #ifdef MS_WINDOWS
  47. # define WIN32_LEAN_AND_MEAN
  48. # include <windows.h>
  49. extern void PyWinFreeze_ExeInit(void);
  50. extern void PyWinFreeze_ExeTerm(void);
  51. static struct _inittab extensions[] = {
  52. {0, 0},
  53. };
  54. # define WIN_UNICODE
  55. #endif
  56. #ifdef _WIN32
  57. static wchar_t *log_pathw = NULL;
  58. #endif
  59. #if defined(_WIN32) && PY_VERSION_HEX < 0x03060000
  60. static int supports_code_page(UINT cp) {
  61. if (cp == 0) {
  62. cp = GetACP();
  63. }
  64. /* Shortcut, because we know that these encodings are bundled by default--
  65. * see FreezeTool.py and Python's encodings/aliases.py */
  66. if (cp != 0 && cp != 1252 && cp != 367 && cp != 437 && cp != 850 && cp != 819) {
  67. const struct _frozen *moddef;
  68. char codec[100];
  69. /* Check if the codec was frozen into the program. We can't check this
  70. * using _PyCodec_Lookup, since Python hasn't been initialized yet. */
  71. PyOS_snprintf(codec, sizeof(codec), "encodings.cp%u", (unsigned int)cp);
  72. moddef = PyImport_FrozenModules;
  73. while (moddef->name) {
  74. if (strcmp(moddef->name, codec) == 0) {
  75. return 1;
  76. }
  77. ++moddef;
  78. }
  79. return 0;
  80. }
  81. return 1;
  82. }
  83. #endif
  84. /**
  85. * Sets the main_dir field of the blobinfo structure, but only if it wasn't
  86. * already set.
  87. */
  88. static void set_main_dir(char *main_dir) {
  89. if (blobinfo.num_pointers >= 10) {
  90. if (blobinfo.num_pointers == 10) {
  91. ++blobinfo.num_pointers;
  92. blobinfo.pointers[10] = NULL;
  93. }
  94. if (blobinfo.pointers[10] == NULL) {
  95. blobinfo.pointers[10] = main_dir;
  96. }
  97. }
  98. }
  99. /**
  100. * Creates the parent directories of the given path. Returns 1 on success.
  101. */
  102. #ifdef _WIN32
  103. static int mkdir_parent(const wchar_t *path) {
  104. // Copy the path to a temporary buffer.
  105. wchar_t buffer[4096];
  106. size_t buflen = wcslen(path);
  107. if (buflen + 1 >= _countof(buffer)) {
  108. return 0;
  109. }
  110. wcscpy_s(buffer, _countof(buffer), path);
  111. // Seek back to find the last path separator.
  112. while (buflen-- > 0) {
  113. if (buffer[buflen] == '/' || buffer[buflen] == '\\') {
  114. buffer[buflen] = 0;
  115. break;
  116. }
  117. }
  118. if (buflen == (size_t)-1 || buflen == 0) {
  119. // There was no path separator, or this was the root directory.
  120. return 0;
  121. }
  122. if (CreateDirectoryW(buffer, NULL) != 0) {
  123. // Success!
  124. return 1;
  125. }
  126. // Failed.
  127. DWORD last_error = GetLastError();
  128. if (last_error == ERROR_ALREADY_EXISTS) {
  129. // Not really an error: the directory is already there.
  130. return 1;
  131. }
  132. if (last_error == ERROR_PATH_NOT_FOUND) {
  133. // We need to make the parent directory first.
  134. if (mkdir_parent(buffer)) {
  135. // Parent successfully created. Try again to make the child.
  136. if (CreateDirectoryW(buffer, NULL) != 0) {
  137. // Got it!
  138. return 1;
  139. }
  140. }
  141. }
  142. return 0;
  143. }
  144. #else
  145. static int mkdir_parent(const char *path) {
  146. // Copy the path to a temporary buffer.
  147. char buffer[4096];
  148. size_t buflen = strlen(path);
  149. if (buflen + 1 >= sizeof(buffer)) {
  150. return 0;
  151. }
  152. strcpy(buffer, path);
  153. // Seek back to find the last path separator.
  154. while (buflen-- > 0) {
  155. if (buffer[buflen] == '/') {
  156. buffer[buflen] = 0;
  157. break;
  158. }
  159. }
  160. if (buflen == (size_t)-1 || buflen == 0) {
  161. // There was no path separator, or this was the root directory.
  162. return 0;
  163. }
  164. if (mkdir(buffer, 0755) == 0) {
  165. // Success!
  166. return 1;
  167. }
  168. // Failed.
  169. if (errno == EEXIST) {
  170. // Not really an error: the directory is already there.
  171. return 1;
  172. }
  173. if (errno == ENOENT || errno == EACCES) {
  174. // We need to make the parent directory first.
  175. if (mkdir_parent(buffer)) {
  176. // Parent successfully created. Try again to make the child.
  177. if (mkdir(buffer, 0755) == 0) {
  178. // Got it!
  179. return 1;
  180. }
  181. }
  182. }
  183. return 0;
  184. }
  185. #endif
  186. /**
  187. * Redirects the output streams to point to the log file with the given path.
  188. *
  189. * @param path specifies the location of log file, may start with ~
  190. * @param append should be nonzero if it should not truncate the log file.
  191. */
  192. static int setup_logging(const char *path, int append) {
  193. #ifdef _WIN32
  194. // Does it start with a tilde? Perform tilde expansion if so.
  195. wchar_t *pathw = (wchar_t *)malloc(sizeof(wchar_t) * MAX_PATH);
  196. pathw[0] = 0;
  197. size_t offset = 0;
  198. if (path[0] == '~' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) {
  199. // Strip off the tilde.
  200. ++path;
  201. // Get the home directory path for the current user.
  202. if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathw))) {
  203. free(pathw);
  204. return 0;
  205. }
  206. offset = wcslen(pathw);
  207. }
  208. // We need to convert the rest of the path from UTF-8 to UTF-16.
  209. if (MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw + offset,
  210. (int)(MAX_PATH - offset)) == 0) {
  211. free(pathw);
  212. return 0;
  213. }
  214. DWORD access = append ? FILE_APPEND_DATA : (GENERIC_READ | GENERIC_WRITE);
  215. int creation = append ? OPEN_ALWAYS : CREATE_ALWAYS;
  216. HANDLE handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
  217. NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
  218. if (handle == INVALID_HANDLE_VALUE) {
  219. // Make the parent directories first.
  220. mkdir_parent(pathw);
  221. handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
  222. NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
  223. }
  224. if (handle == INVALID_HANDLE_VALUE) {
  225. free(pathw);
  226. return 0;
  227. }
  228. log_pathw = pathw;
  229. if (append) {
  230. SetFilePointer(handle, 0, NULL, FILE_END);
  231. }
  232. SetStdHandle(STD_OUTPUT_HANDLE, handle);
  233. SetStdHandle(STD_ERROR_HANDLE, handle);
  234. // If we are running under the UCRT in a GUI application, we can't be sure
  235. // that we have valid fds for stdout and stderr, so we have to set them up.
  236. // One way to do this is to reopen them to something silly (like NUL).
  237. if (_fileno(stdout) < 0) {
  238. _close(1);
  239. _wfreopen(L"\\\\.\\NUL", L"w", stdout);
  240. }
  241. if (_fileno(stderr) < 0) {
  242. _close(2);
  243. _wfreopen(L"\\\\.\\NUL", L"w", stderr);
  244. }
  245. // Now replace the stdout and stderr file descriptors with one pointing to
  246. // our desired handle.
  247. int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | _O_APPEND);
  248. _dup2(fd, _fileno(stdout));
  249. _dup2(fd, _fileno(stderr));
  250. _close(fd);
  251. return 1;
  252. #else
  253. // Does it start with a tilde? Perform tilde expansion if so.
  254. char buffer[PATH_MAX * 2];
  255. size_t offset = 0;
  256. if (path[0] == '~' && (path[1] == 0 || path[1] == '/')) {
  257. // Strip off the tilde.
  258. ++path;
  259. // Get the home directory path for the current user.
  260. const char *home_dir = getenv("HOME");
  261. if (home_dir == NULL) {
  262. home_dir = getpwuid(getuid())->pw_dir;
  263. }
  264. offset = strlen(home_dir);
  265. assert(offset < sizeof(buffer));
  266. strncpy(buffer, home_dir, sizeof(buffer));
  267. }
  268. // Copy over the rest of the path.
  269. strcpy(buffer + offset, path);
  270. mode_t mode = O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC);
  271. int fd = open(buffer, mode, 0644);
  272. if (fd == -1) {
  273. // Make the parent directories first.
  274. mkdir_parent(buffer);
  275. fd = open(buffer, mode, 0644);
  276. }
  277. if (fd == -1) {
  278. perror(buffer);
  279. return 0;
  280. }
  281. fflush(stdout);
  282. fflush(stderr);
  283. dup2(fd, 1);
  284. dup2(fd, 2);
  285. close(fd);
  286. return 1;
  287. #endif
  288. }
  289. /* Main program */
  290. #ifdef WIN_UNICODE
  291. int Py_FrozenMain(int argc, wchar_t **argv)
  292. #else
  293. int Py_FrozenMain(int argc, char **argv)
  294. #endif
  295. {
  296. char *p;
  297. int n, sts = 1;
  298. int inspect = 0;
  299. int unbuffered = 0;
  300. #ifndef WIN_UNICODE
  301. int i;
  302. char *oldloc;
  303. wchar_t **argv_copy = NULL;
  304. /* We need a second copies, as Python might modify the first one. */
  305. wchar_t **argv_copy2 = NULL;
  306. if (argc > 0) {
  307. argv_copy = (wchar_t **)alloca(sizeof(wchar_t *) * argc);
  308. argv_copy2 = (wchar_t **)alloca(sizeof(wchar_t *) * argc);
  309. }
  310. #endif
  311. #if defined(MS_WINDOWS) && PY_VERSION_HEX >= 0x03040000 && PY_VERSION_HEX < 0x03060000
  312. if (!supports_code_page(GetConsoleOutputCP()) ||
  313. !supports_code_page(GetConsoleCP())) {
  314. /* Revert to the active codepage, and tell Python to use the 'mbcs'
  315. * encoding (which always uses the active codepage). In 99% of cases,
  316. * this will be the same thing anyway. */
  317. UINT acp = GetACP();
  318. SetConsoleCP(acp);
  319. SetConsoleOutputCP(acp);
  320. Py_SetStandardStreamEncoding("mbcs", NULL);
  321. }
  322. #endif
  323. Py_FrozenFlag = 1; /* Suppress errors from getpath.c */
  324. Py_NoSiteFlag = 0;
  325. Py_NoUserSiteDirectory = 1;
  326. if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
  327. inspect = 1;
  328. if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
  329. unbuffered = 1;
  330. if (unbuffered) {
  331. setbuf(stdin, (char *)NULL);
  332. setbuf(stdout, (char *)NULL);
  333. setbuf(stderr, (char *)NULL);
  334. }
  335. #ifndef WIN_UNICODE
  336. oldloc = setlocale(LC_ALL, NULL);
  337. setlocale(LC_ALL, "");
  338. for (i = 0; i < argc; i++) {
  339. argv_copy[i] = Py_DecodeLocale(argv[i], NULL);
  340. argv_copy2[i] = argv_copy[i];
  341. if (!argv_copy[i]) {
  342. fprintf(stderr, "Unable to decode the command line argument #%i\n",
  343. i + 1);
  344. argc = i;
  345. goto error;
  346. }
  347. }
  348. setlocale(LC_ALL, oldloc);
  349. #endif
  350. #ifdef MS_WINDOWS
  351. PyImport_ExtendInittab(extensions);
  352. #endif /* MS_WINDOWS */
  353. if (argc >= 1) {
  354. #ifndef WIN_UNICODE
  355. Py_SetProgramName(argv_copy[0]);
  356. #else
  357. Py_SetProgramName(argv[0]);
  358. #endif
  359. }
  360. Py_Initialize();
  361. #ifdef MS_WINDOWS
  362. PyWinFreeze_ExeInit();
  363. #endif
  364. if (Py_VerboseFlag)
  365. fprintf(stderr, "Python %s\n%s\n",
  366. Py_GetVersion(), Py_GetCopyright());
  367. #ifndef WIN_UNICODE
  368. PySys_SetArgv(argc, argv_copy);
  369. #else
  370. PySys_SetArgv(argc, argv);
  371. #endif
  372. #ifdef MACOS_APP_BUNDLE
  373. // Add the Frameworks directory to sys.path.
  374. char buffer[PATH_MAX];
  375. uint32_t bufsize = sizeof(buffer);
  376. if (_NSGetExecutablePath(buffer, &bufsize) != 0) {
  377. assert(false);
  378. return 1;
  379. }
  380. char resolved[PATH_MAX];
  381. if (!realpath(buffer, resolved)) {
  382. perror("realpath");
  383. return 1;
  384. }
  385. const char *dir = dirname(resolved);
  386. sprintf(buffer, "%s/../Frameworks", dir);
  387. PyObject *sys_path = PyList_New(1);
  388. PyList_SET_ITEM(sys_path, 0, PyUnicode_FromString(buffer));
  389. PySys_SetObject("path", sys_path);
  390. Py_DECREF(sys_path);
  391. // Now, store a path to the Resources directory into the main_dir pointer,
  392. // for ConfigPageManager to read out and assign to MAIN_DIR.
  393. sprintf(buffer, "%s/../Resources", dir);
  394. set_main_dir(buffer);
  395. // Finally, chdir to it, so that regular Python files are read from the
  396. // right location.
  397. chdir(buffer);
  398. #endif
  399. n = PyImport_ImportFrozenModule("__main__");
  400. if (n == 0)
  401. Py_FatalError("__main__ not frozen");
  402. if (n < 0) {
  403. PyErr_Print();
  404. sts = 1;
  405. }
  406. else
  407. sts = 0;
  408. if (inspect && isatty((int)fileno(stdin)))
  409. sts = PyRun_AnyFile(stdin, "<stdin>") != 0;
  410. #ifdef MS_WINDOWS
  411. PyWinFreeze_ExeTerm();
  412. #endif
  413. Py_Finalize();
  414. #ifndef WIN_UNICODE
  415. error:
  416. if (argv_copy2) {
  417. for (i = 0; i < argc; i++) {
  418. PyMem_RawFree(argv_copy2[i]);
  419. }
  420. }
  421. #endif
  422. return sts;
  423. }
  424. /**
  425. * Maps the binary blob at the given memory address to memory, and returns the
  426. * pointer to the beginning of it.
  427. */
  428. static void *map_blob(off_t offset, size_t size) {
  429. void *blob;
  430. FILE *runtime;
  431. #ifdef _WIN32
  432. wchar_t buffer[2048];
  433. GetModuleFileNameW(NULL, buffer, 2048);
  434. runtime = _wfopen(buffer, L"rb");
  435. #elif defined(__FreeBSD__)
  436. size_t bufsize = 4096;
  437. char buffer[4096];
  438. int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
  439. mib[3] = getpid();
  440. if (sysctl(mib, 4, (void *)buffer, &bufsize, NULL, 0) == -1) {
  441. perror("sysctl");
  442. return NULL;
  443. }
  444. runtime = fopen(buffer, "rb");
  445. #elif defined(__APPLE__)
  446. char buffer[4096];
  447. uint32_t bufsize = sizeof(buffer);
  448. if (_NSGetExecutablePath(buffer, &bufsize) != 0) {
  449. return NULL;
  450. }
  451. runtime = fopen(buffer, "rb");
  452. #else
  453. char buffer[4096];
  454. ssize_t pathlen = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1);
  455. if (pathlen <= 0) {
  456. perror("readlink(/proc/self/exe)");
  457. return NULL;
  458. }
  459. buffer[pathlen] = '\0';
  460. runtime = fopen(buffer, "rb");
  461. #endif
  462. // Get offsets. In version 0, we read it from the end of the file.
  463. if (blobinfo.version == 0) {
  464. uint64_t end, begin;
  465. fseek(runtime, -8, SEEK_END);
  466. end = ftell(runtime);
  467. fread(&begin, 8, 1, runtime);
  468. offset = (off_t)begin;
  469. size = (size_t)(end - begin);
  470. }
  471. // mmap the section indicated by the offset (or malloc/fread on windows)
  472. #ifdef _WIN32
  473. blob = (void *)malloc(size);
  474. assert(blob != NULL);
  475. fseek(runtime, (long)offset, SEEK_SET);
  476. fread(blob, size, 1, runtime);
  477. #else
  478. blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset);
  479. assert(blob != MAP_FAILED);
  480. #endif
  481. fclose(runtime);
  482. return blob;
  483. }
  484. /**
  485. * The inverse of map_blob.
  486. */
  487. static void unmap_blob(void *blob) {
  488. if (blob) {
  489. #ifdef _WIN32
  490. free(blob);
  491. #else
  492. munmap(blob, blobinfo.blob_size);
  493. #endif
  494. }
  495. }
  496. /**
  497. * Main entry point to deploy-stub.
  498. */
  499. #ifdef _WIN32
  500. int wmain(int argc, wchar_t *argv[]) {
  501. #else
  502. int main(int argc, char *argv[]) {
  503. #endif
  504. int retval;
  505. struct _frozen *moddef;
  506. const char *log_filename;
  507. void *blob = NULL;
  508. log_filename = NULL;
  509. #ifdef __APPLE__
  510. // Strip a -psn_xxx argument passed in by macOS when run from an .app bundle.
  511. if (argc > 1 && strncmp(argv[1], "-psn_", 5) == 0) {
  512. argv[1] = argv[0];
  513. ++argv;
  514. --argc;
  515. }
  516. #endif
  517. /*
  518. printf("blob_offset: %d\n", (int)blobinfo.blob_offset);
  519. printf("blob_size: %d\n", (int)blobinfo.blob_size);
  520. printf("version: %d\n", (int)blobinfo.version);
  521. printf("num_pointers: %d\n", (int)blobinfo.num_pointers);
  522. printf("codepage: %d\n", (int)blobinfo.codepage);
  523. printf("flags: %d\n", (int)blobinfo.flags);
  524. printf("reserved: %d\n", (int)blobinfo.reserved);
  525. */
  526. // If we have a blob offset, we have to map the blob to memory.
  527. if (blobinfo.version == 0 || blobinfo.blob_offset != 0) {
  528. void *blob = map_blob((off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size);
  529. assert(blob != NULL);
  530. // Offset the pointers in the header using the base mmap address.
  531. if (blobinfo.version > 0 && blobinfo.num_pointers > 0) {
  532. uint32_t i;
  533. assert(blobinfo.num_pointers <= MAX_NUM_POINTERS);
  534. for (i = 0; i < blobinfo.num_pointers; ++i) {
  535. // Only offset if the pointer is non-NULL. Except for the first
  536. // pointer, which may never be NULL and usually (but not always)
  537. // points to the beginning of the blob.
  538. if (i == 0 || blobinfo.pointers[i] != 0) {
  539. blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob);
  540. }
  541. }
  542. if (blobinfo.num_pointers >= 12) {
  543. log_filename = blobinfo.pointers[11];
  544. }
  545. } else {
  546. blobinfo.pointers[0] = blob;
  547. }
  548. // Offset the pointers in the module table using the base mmap address.
  549. moddef = blobinfo.pointers[0];
  550. while (moddef->name) {
  551. moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob);
  552. if (moddef->code != 0) {
  553. moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob);
  554. }
  555. //printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size);
  556. moddef++;
  557. }
  558. }
  559. if (log_filename != NULL) {
  560. setup_logging(log_filename, (blobinfo.flags & F_log_append) != 0);
  561. }
  562. #ifdef _WIN32
  563. if (blobinfo.codepage != 0) {
  564. SetConsoleCP(blobinfo.codepage);
  565. SetConsoleOutputCP(blobinfo.codepage);
  566. }
  567. #endif
  568. // Run frozen application
  569. PyImport_FrozenModules = blobinfo.pointers[0];
  570. retval = Py_FrozenMain(argc, argv);
  571. fflush(stdout);
  572. fflush(stderr);
  573. unmap_blob(blob);
  574. return retval;
  575. }
  576. #ifdef WIN_UNICODE
  577. int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *lpCmdLine, int nCmdShow) {
  578. return wmain(__argc, __wargv);
  579. }
  580. #elif defined(_WIN32)
  581. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char *lpCmdLine, int nCmdShow) {
  582. return main(__argc, __argv);
  583. }
  584. #endif