common.h 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  1. /*
  2. * meg4/platform/common.h
  3. *
  4. * Copyright (C) 2023 bzt
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  19. *
  20. * @brief Common on all platforms, mostly OS specific file management stuff
  21. *
  22. */
  23. #include <stdio.h>
  24. #include <limits.h>
  25. #ifndef PATH_MAX
  26. #define PATH_MAX 4096
  27. #endif
  28. #ifndef FILENAME_MAX
  29. #define FILENAME_MAX 255
  30. #endif
  31. /* command line flags */
  32. int verbose = 0, zenity = 0, nearest = 0, windowed = 0, strace = 0;
  33. char *main_floppydir = NULL;
  34. void main_hdr(void);
  35. void meg4_export(char *name, int binary);
  36. extern uint8_t binary_game[];
  37. /**
  38. * Windows workaround
  39. */
  40. #ifdef __WIN32__
  41. #define CLIFLAG "/"
  42. #define SEP "\\"
  43. #include <windows.h>
  44. #include <winnls.h>
  45. #include <fileapi.h>
  46. #include <shlobj.h>
  47. #include <shellapi.h>
  48. /* Ehhh, we MUST include this AFTER the standard windows headers, otherwise OFN doesn't work */
  49. #ifdef SDL_VERSION_ATLEAST
  50. #include <SDL_syswm.h>
  51. #endif
  52. static HWND hwnd = NULL;
  53. static HINSTANCE hWinInstance;
  54. static char *main_lng = NULL;
  55. /* these two functions were borrowed from sdl_windows_main.c */
  56. static void UnEscapeQuotes(char *arg)
  57. {
  58. char *last = NULL, *c_curr, *c_last;
  59. while (*arg) {
  60. if (*arg == '"' && (last != NULL && *last == '\\')) {
  61. c_curr = arg;
  62. c_last = last;
  63. while (*c_curr) {
  64. *c_last = *c_curr;
  65. c_last = c_curr;
  66. c_curr++;
  67. }
  68. *c_last = '\0';
  69. }
  70. last = arg;
  71. arg++;
  72. }
  73. }
  74. /* Parse a command line buffer into arguments */
  75. static int ParseCommandLine(WCHAR *cmdline, char **argv)
  76. {
  77. char *bufp, *args;
  78. char *lastp = NULL;
  79. int argc, last_argc, l;
  80. l = WideCharToMultiByte(CP_UTF8, 0, cmdline, -1, NULL, 0, NULL, NULL);
  81. if(!l || !(args = malloc(l))) return 0;
  82. WideCharToMultiByte(CP_UTF8, 0, cmdline, -1, args, l, NULL, NULL);
  83. argc = last_argc = 0;
  84. for (bufp = args; *bufp;) {
  85. /* Skip leading whitespace */
  86. while (*bufp == ' ') {
  87. ++bufp;
  88. }
  89. /* Skip over argument */
  90. if (*bufp == '"') {
  91. ++bufp;
  92. if (*bufp) {
  93. if (argv) {
  94. argv[argc] = bufp;
  95. }
  96. ++argc;
  97. }
  98. /* Skip over word */
  99. lastp = bufp;
  100. while (*bufp && (*bufp != '"' || *lastp == '\\')) {
  101. lastp = bufp;
  102. ++bufp;
  103. }
  104. } else {
  105. if (*bufp) {
  106. if (argv) {
  107. argv[argc] = bufp;
  108. }
  109. ++argc;
  110. }
  111. /* Skip over word */
  112. while (*bufp && *bufp != ' ') {
  113. ++bufp;
  114. }
  115. }
  116. if (*bufp) {
  117. if (argv) {
  118. *bufp = '\0';
  119. }
  120. ++bufp;
  121. }
  122. /* Strip out \ from \" sequences */
  123. if (argv && last_argc != argc) {
  124. UnEscapeQuotes(argv[last_argc]);
  125. }
  126. last_argc = argc;
  127. }
  128. if (argv) {
  129. argv[argc] = NULL;
  130. }
  131. return (argc);
  132. }
  133. /* Windows entry point */
  134. int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  135. {
  136. WCHAR *cmdline = GetCommandLineW();
  137. int ret, argc = ParseCommandLine(cmdline, NULL);
  138. char **argv = calloc(argc+2, sizeof(char*));
  139. char *lng = getenv("LANG");
  140. FILE *f;
  141. int lid;
  142. (void)hPrevInstance;
  143. (void)lpCmdLine;
  144. (void)nCmdShow;
  145. hWinInstance = hInstance;
  146. if(!lng) {
  147. lid = GetUserDefaultLangID(); /* GetUserDefaultUILanguage(); */
  148. /* see https://docs.microsoft.com/en-us/windows/win32/intl/language-identifier-constants-and-strings */
  149. switch(lid & 0xFF) {
  150. case 0x01: lng = "ar"; break; case 0x02: lng = "bg"; break; case 0x03: lng = "ca"; break;
  151. case 0x04: lng = "zh"; break; case 0x05: lng = "cs"; break; case 0x06: lng = "da"; break;
  152. case 0x07: lng = "de"; break; case 0x08: lng = "el"; break; case 0x0A: lng = "es"; break;
  153. case 0x0B: lng = "fi"; break; case 0x0C: lng = "fr"; break; case 0x0D: lng = "he"; break;
  154. case 0x0E: lng = "hu"; break; case 0x0F: lng = "is"; break; case 0x10: lng = "it"; break;
  155. case 0x11: lng = "ja"; break; case 0x12: lng = "ko"; break; case 0x13: lng = "nl"; break;
  156. case 0x14: lng = "no"; break; case 0x15: lng = "pl"; break; case 0x16: lng = "pt"; break;
  157. case 0x17: lng = "rm"; break; case 0x18: lng = "ro"; break; case 0x19: lng = "ru"; break;
  158. case 0x1A: lng = "hr"; break; case 0x1B: lng = "sk"; break; case 0x1C: lng = "sq"; break;
  159. case 0x1D: lng = "sv"; break; case 0x1E: lng = "th"; break; case 0x1F: lng = "tr"; break;
  160. case 0x20: lng = "ur"; break; case 0x21: lng = "id"; break; case 0x22: lng = "uk"; break;
  161. case 0x23: lng = "be"; break; case 0x24: lng = "sl"; break; case 0x25: lng = "et"; break;
  162. case 0x26: lng = "lv"; break; case 0x27: lng = "lt"; break; case 0x29: lng = "fa"; break;
  163. case 0x2A: lng = "vi"; break; case 0x2B: lng = "hy"; break; case 0x2D: lng = "bq"; break;
  164. case 0x2F: lng = "mk"; break; case 0x36: lng = "af"; break; case 0x37: lng = "ka"; break;
  165. case 0x38: lng = "fo"; break; case 0x39: lng = "hi"; break; case 0x3A: lng = "mt"; break;
  166. case 0x3C: lng = "gd"; break; case 0x3E: lng = "ms"; break; case 0x3F: lng = "kk"; break;
  167. case 0x40: lng = "ky"; break; case 0x45: lng = "bn"; break; case 0x47: lng = "gu"; break;
  168. case 0x4D: lng = "as"; break; case 0x4E: lng = "mr"; break; case 0x4F: lng = "sa"; break;
  169. case 0x53: lng = "kh"; break; case 0x54: lng = "lo"; break; case 0x56: lng = "gl"; break;
  170. case 0x5E: lng = "am"; break; case 0x62: lng = "fy"; break; case 0x68: lng = "ha"; break;
  171. case 0x6D: lng = "ba"; break; case 0x6E: lng = "lb"; break; case 0x6F: lng = "kl"; break;
  172. case 0x7E: lng = "br"; break; case 0x92: lng = "ku"; break; case 0x09: default: lng = "en"; break;
  173. }
  174. }
  175. main_lng = lng;
  176. /* restore stdout to console */
  177. AttachConsole(ATTACH_PARENT_PROCESS);
  178. f = _fdopen(_open_osfhandle((intptr_t)GetStdHandle(STD_OUTPUT_HANDLE), 0x4000/*_O_TEXT*/), "w");
  179. if(f) { *stdout = *f; *stderr = *f; }
  180. printf("\r\n");
  181. ParseCommandLine(cmdline, argv);
  182. #ifdef SDL_h_
  183. SDL_SetMainReady();
  184. #endif
  185. ret = main(argc, argv);
  186. free(argv);
  187. exit(ret);
  188. return ret;
  189. }
  190. /**
  191. * Read a file entirely into memory
  192. */
  193. uint8_t* main_readfile(char *file, int *size)
  194. {
  195. #ifdef __EMSCRIPTEN__
  196. (void)file; (void)size;
  197. return NULL;
  198. #else
  199. wchar_t szFile[PATH_MAX + FILENAME_MAX + 1];
  200. uint8_t *data = NULL;
  201. HANDLE f;
  202. DWORD r, t;
  203. if(!file || !*file || !size) return NULL;
  204. MultiByteToWideChar(CP_UTF8, 0, file, -1, szFile, PATH_MAX + FILENAME_MAX);
  205. f = CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
  206. if(f) {
  207. r = GetFileSize(f, NULL);
  208. *size = r;
  209. data = (uint8_t*)malloc(*size);
  210. if(data) {
  211. memset(data, 0, *size);
  212. if(!ReadFile(f, data, r, &t, NULL) || r != t) { free(data); data = NULL; *size = 0; }
  213. }
  214. CloseHandle(f);
  215. }
  216. return data;
  217. #endif
  218. }
  219. /**
  220. * Write to a file
  221. */
  222. int main_writefile(char *file, uint8_t *buf, int size)
  223. {
  224. #ifdef __EMSCRIPTEN__
  225. (void)file; (void)buf; (void)size;
  226. return 0;
  227. #else
  228. int ret = 0;
  229. wchar_t szFile[PATH_MAX + FILENAME_MAX + 1];
  230. HANDLE f;
  231. DWORD w = size, t;
  232. if(!file || !*file || !buf || size < 1) return 0;
  233. MultiByteToWideChar(CP_UTF8, 0, file, -1, szFile, PATH_MAX + FILENAME_MAX);
  234. f = CreateFileW(szFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  235. if(f) {
  236. ret = (WriteFile(f, buf, w, &t, NULL) && w == t);
  237. CloseHandle(f);
  238. }
  239. return ret;
  240. #endif
  241. }
  242. /**
  243. * Open open file modal and import selected file
  244. */
  245. void main_openfile(void)
  246. {
  247. #if !defined(EMBED) && !defined(NOEDITORS)
  248. #ifdef __EMSCRIPTEN__
  249. EM_ASM({ document.getElementById('meg4_upload').click(); });
  250. #else
  251. OPENFILENAMEW ofn;
  252. wchar_t szFile[PATH_MAX + FILENAME_MAX + 1];
  253. char fn[PATH_MAX + FILENAME_MAX + 1], *n;
  254. int len;
  255. uint8_t *buf;
  256. memset(&szFile,0,sizeof(szFile));
  257. memset(&ofn,0,sizeof(ofn));
  258. ofn.lStructSize = sizeof(ofn);
  259. ofn.hwndOwner = hwnd;
  260. ofn.hInstance = hWinInstance;
  261. ofn.lpstrFile = szFile;
  262. ofn.nMaxFile = sizeof(szFile)-1;
  263. ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST;
  264. if(GetOpenFileNameW(&ofn)) {
  265. WideCharToMultiByte(CP_UTF8, 0, szFile, -1, fn, PATH_MAX + FILENAME_MAX, NULL, NULL);
  266. if((buf = main_readfile(fn, &len))) {
  267. n = strrchr(fn, SEP[0]); if(!n) n = fn; else n++;
  268. meg4_insert(fn, buf, len);
  269. free(buf);
  270. }
  271. main_focus();
  272. }
  273. #endif
  274. #endif
  275. }
  276. /**
  277. * Open save file modal and write buffer to selected file
  278. */
  279. int main_savefile(const char *name, uint8_t *buf, int len)
  280. {
  281. #ifndef NOEDITORS
  282. #ifdef __EMSCRIPTEN__
  283. if(name && buf && len > 0) {
  284. EM_ASM({ meg4_savefile($0, $1, $2, $3); }, name, strlen(name), buf, len);
  285. return 1;
  286. }
  287. return 0;
  288. #else
  289. #ifndef EMBED
  290. OPENFILENAMEW ofn;
  291. wchar_t szExt[FILENAME_MAX];
  292. int l = 0;
  293. #endif
  294. wchar_t szFile[PATH_MAX + FILENAME_MAX + 1];
  295. char fn[PATH_MAX + FILENAME_MAX + 1];
  296. int ret = 0;
  297. if(!name || !*name || !buf || len < 1) return 0;
  298. memset(&szFile,0,sizeof(szFile));
  299. if(main_floppydir && *main_floppydir) {
  300. strcpy(fn, main_floppydir);
  301. if(fn[strlen(fn) - 1] == SEP[0]) fn[strlen(fn) - 1] = 0;
  302. MultiByteToWideChar(CP_UTF8, 0, fn, -1, szFile, sizeof(szFile)-1);
  303. _wmkdir(szFile);
  304. strcat(fn, SEP);
  305. strcat(fn, name);
  306. return main_writefile(fn, buf, len);
  307. }
  308. #ifndef EMBED
  309. else {
  310. memset(&szExt,0,sizeof(szExt));
  311. memcpy(szExt, L"All\0*.*\0", 18);
  312. l = MultiByteToWideChar(CP_UTF8, 0, name, -1, szFile, sizeof(szFile)-1);
  313. while(l > 0 && szFile[l - 1] != L'.') l--;
  314. if(l > 0) wsprintfW(szExt, L"%s\0*.%s\0", szFile + l, szFile + l);
  315. memset(&ofn,0,sizeof(ofn));
  316. ofn.lStructSize = sizeof(ofn);
  317. ofn.hwndOwner = hwnd;
  318. ofn.hInstance = hWinInstance;
  319. ofn.lpstrFilter = szExt;
  320. ofn.lpstrFile = szFile;
  321. ofn.nMaxFile = sizeof(szFile)-1;
  322. ofn.lpstrDefExt = l > 0 ? &szFile[l] : NULL;
  323. ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
  324. if(buf && len > 0 && GetSaveFileNameW(&ofn)) {
  325. WideCharToMultiByte(CP_UTF8, 0, szFile, -1, fn, PATH_MAX + FILENAME_MAX + 1, NULL, NULL);
  326. ret = main_writefile(fn, buf, len);
  327. }
  328. main_focus();
  329. }
  330. #endif
  331. #endif
  332. return ret;
  333. #else
  334. (void)name; (void)buf; (void)len;
  335. return 0;
  336. #endif
  337. }
  338. /**
  339. * Return floppy list
  340. */
  341. char **main_getfloppies(void)
  342. {
  343. #if !defined(__EMSCRIPTEN__) && !defined(NOEDITORS)
  344. wchar_t szFile[PATH_MAX + FILENAME_MAX + 1];
  345. WIN32_FIND_DATAW ffd;
  346. HANDLE h;
  347. int n = 0, j, l;
  348. char **ret = NULL;
  349. if(!main_floppydir || !*main_floppydir) return NULL;
  350. ret = (char**)malloc(sizeof(char*));
  351. if(!ret) return NULL; else ret[0] = NULL;
  352. memset(&szFile,0,sizeof(szFile));
  353. l = MultiByteToWideChar(CP_UTF8, 0, main_floppydir, -1, szFile, sizeof(szFile)-1) - 1;
  354. if(l < 1) return NULL;
  355. if(szFile[l - 1] != L'\\') szFile[l++] = L'\\';
  356. wcscpy_s(szFile + l, FILENAME_MAX, L"*.PNG");
  357. h = FindFirstFileW(szFile, &ffd);
  358. if(h != INVALID_HANDLE_VALUE) {
  359. do {
  360. wcscpy_s(szFile + l, FILENAME_MAX, ffd.cFileName);
  361. j = wcslen(ffd.cFileName) * 4;
  362. if(ffd.cFileName[0] == L'.' || !j) continue;
  363. ret = (char**)realloc(ret, (n + 2) * sizeof(char*));
  364. if(!ret) break;
  365. ret[n + 1] = NULL;
  366. ret[n] = (char*)malloc(l + j + 1);
  367. if(!ret[n]) continue;
  368. WideCharToMultiByte(CP_UTF8, 0, szFile, -1, ret[n], l + j + 1, NULL, NULL);
  369. n++;
  370. } while(FindNextFileW(h, &ffd) != 0);
  371. FindClose(h);
  372. }
  373. return ret;
  374. #else
  375. return NULL;
  376. #endif
  377. }
  378. #ifndef __EMSCRIPTEN__
  379. /**
  380. * Save a config file
  381. */
  382. int main_cfgsave(char *cfg, uint8_t *buf, int len)
  383. {
  384. HANDLE f;
  385. DWORD w = len, t;
  386. wchar_t szFile[PATH_MAX + FILENAME_MAX + 1], szCfg[FILENAME_MAX];
  387. int ret = 0, i;
  388. if(!SHGetFolderPathW(HWND_DESKTOP, CSIDL_PROFILE, NULL, 0, szFile)) {
  389. MultiByteToWideChar(CP_UTF8, 0, cfg, -1, szCfg, sizeof(szCfg)-1);
  390. wcscat(szFile, L"\\AppData"); _wmkdir(szFile);
  391. wcscat(szFile, L"\\Local"); _wmkdir(szFile);
  392. wcscat(szFile, L"\\MEG-4"); _wmkdir(szFile);
  393. wcscat(szFile, L"\\"); i = wcslen(szFile); wcscat(szFile, szCfg);
  394. for(; szFile[i]; i++) if(szFile[i] == L'/' || szFile[i] == L'\\') { szFile[i] = 0; _wmkdir(szFile); szFile[i] = L'\\'; }
  395. f = CreateFileW(szFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  396. if(f) {
  397. ret = (WriteFile(f, buf, w, &t, NULL) && w == t);
  398. CloseHandle(f);
  399. }
  400. }
  401. return ret;
  402. }
  403. /**
  404. * Load a config file
  405. */
  406. uint8_t *main_cfgload(char *cfg, int *len)
  407. {
  408. uint8_t *data = NULL;
  409. HANDLE f;
  410. DWORD r, t;
  411. wchar_t szFile[PATH_MAX + FILENAME_MAX + 1], szCfg[FILENAME_MAX];
  412. int i;
  413. if(!SHGetFolderPathW(HWND_DESKTOP, CSIDL_PROFILE, NULL, 0, szFile)) {
  414. MultiByteToWideChar(CP_UTF8, 0, cfg, -1, szCfg, sizeof(szCfg)-1);
  415. wcscat(szFile, L"\\AppData\\Local\\MEG-4\\"); wcscat(szFile, szCfg);
  416. for(i = 0; szFile[i]; i++) if(szFile[i] == L'/') szFile[i] = '\\';
  417. f = CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
  418. if(f) {
  419. r = GetFileSize(f, NULL);
  420. *len = r;
  421. data = (uint8_t*)malloc(*len + 1);
  422. if(data) {
  423. memset(data, 0, *len + 1);
  424. if(!ReadFile(f, data, r, &t, NULL) || r != t) { free(data); data = NULL; *len = 0; }
  425. }
  426. CloseHandle(f);
  427. }
  428. }
  429. return data;
  430. }
  431. #endif
  432. #else
  433. #define CLIFLAG "-"
  434. #define SEP "/"
  435. #include <dirent.h>
  436. #ifdef _MACOS_
  437. #include <AppKit/AppKit.h>
  438. #else
  439. void main_delay(int msec);
  440. #include <signal.h> /* including the glib version of this with __USE_MISC doesn't compile... */
  441. #define __USE_MISC /* needed for MAP_ANONYMOUS */
  442. #include <dlfcn.h>
  443. #include <unistd.h>
  444. #include <sys/mman.h>
  445. #include <sys/stat.h> /* mkdir... */
  446. #include <sys/types.h>
  447. #ifndef MAP_ANONYMOUS
  448. #define MAP_ANONYMOUS 0x20
  449. #endif
  450. int strcasecmp(const char *, const char *);
  451. /* some moron hide these behind feature defines which don't work if you have __USE_MISC... */
  452. int kill(pid_t pid, int sig);
  453. FILE *popen(const char *command, const char *type);
  454. int pclose(FILE *stream);
  455. /* gtk is sooo fucked up, DO NOT include its headers... */
  456. /*#include <gtk/gtk.h>*/
  457. void (*gtk_init)(int *argc, char ***argv);
  458. void* (*gtk_file_chooser_dialog_new)(const char *title, void *parent, int action, const char *first_button_text, ...);
  459. int (*gtk_dialog_run)(void *dialog);
  460. void (*gtk_widget_destroy)(void *widget);
  461. char* (*gtk_file_chooser_get_filename)(void *chooser);
  462. void (*gtk_file_chooser_set_do_overwrite_confirmation)(void *chooser, int do_overwrite_confirmation);
  463. void (*gtk_file_chooser_set_create_folders)(void *chooser, int create_folders);
  464. int (*gtk_file_chooser_set_current_name)(void *chooser, const char *filename);
  465. #endif
  466. /**
  467. * Read a file entirely into memory
  468. */
  469. uint8_t* main_readfile(char *file, int *size)
  470. {
  471. #ifdef __EMSCRIPTEN__
  472. (void)file; (void)size;
  473. return NULL;
  474. #else
  475. uint8_t *data = NULL;
  476. FILE *f;
  477. if(!file || !*file || !size) return NULL;
  478. f = fopen(file, "rb");
  479. if(f) {
  480. fseek(f, 0L, SEEK_END);
  481. *size = (int)ftell(f);
  482. fseek(f, 0L, SEEK_SET);
  483. data = (uint8_t*)malloc(*size);
  484. if(data) {
  485. memset(data, 0, *size);
  486. if((int)fread(data, 1, *size, f) != *size) { free(data); data = NULL; *size = 0; }
  487. }
  488. fclose(f);
  489. }
  490. return data;
  491. #endif
  492. }
  493. /**
  494. * Write to a file
  495. */
  496. int main_writefile(char *file, uint8_t *buf, int size)
  497. {
  498. #ifdef __EMSCRIPTEN__
  499. (void)file; (void)buf; (void)size;
  500. return 0;
  501. #else
  502. int ret = 0;
  503. FILE *f;
  504. if(!file || !*file || !buf || size < 1) return 0;
  505. f = fopen(file, "wb");
  506. if(f) {
  507. ret = ((int)fwrite(buf, 1, size, f) == size);
  508. fclose(f);
  509. }
  510. return ret;
  511. #endif
  512. }
  513. /**
  514. * Open open file modal and import selected file
  515. */
  516. void main_openfile(void)
  517. {
  518. #if !defined(EMBED) && !defined(NOEDITORS)
  519. #ifdef __EMSCRIPTEN__
  520. EM_ASM({ document.getElementById('meg4_upload').click(); });
  521. #else
  522. char *fn = NULL, *n;
  523. int len;
  524. uint8_t *buf;
  525. #ifdef __ANDROID__
  526. /* TODO */
  527. #else
  528. #ifdef _MACOS_
  529. const char *utf8Path;
  530. NSURL *url;
  531. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  532. NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
  533. NSOpenPanel *dialog = [NSOpenPanel openPanel];
  534. [dialog setAllowsMultipleSelection:NO];
  535. if([dialog runModal] == NSModalResponseOK) {
  536. url = [dialog URL];
  537. utf8Path = [[url path] UTF8String];
  538. len = strlen(utf8Path);
  539. fn = (char*)malloc(len + 1);
  540. if(fn) strcpy(ret, utf8Path);
  541. }
  542. [pool release];
  543. [keyWindow makeKeyAndOrderFront:nil];
  544. #else
  545. FILE *p = NULL;
  546. pid_t pid;
  547. void *chooser;
  548. void *handle;
  549. char *tmp1, *tmp2;
  550. tmp1 = mmap(NULL, PATH_MAX, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  551. if(!tmp1) return;
  552. memset(tmp1, 0, PATH_MAX);
  553. meg4_showcursor();
  554. if(zenity) p = popen("/usr/bin/zenity --file-selection", "r");
  555. if(p) {
  556. main_log(1, "using zenity hack");
  557. if(!fgets(tmp1 + 1, PATH_MAX - 1, p)) main_log(1, "fgets error");
  558. pclose(p);
  559. for(tmp2 = tmp1 + 1; *tmp2 && *tmp2 != '\n' && *tmp2 != '\r'; tmp2++);
  560. *tmp2 = 0;
  561. tmp1[0] = 1;
  562. } else {
  563. main_log(1, "trying to dlopen gtk");
  564. if(!(pid = fork())) {
  565. handle = dlopen("libgtk-3.so", RTLD_LAZY | RTLD_LOCAL);
  566. if(handle) {
  567. #ifdef __GNUC__
  568. #pragma GCC diagnostic push
  569. #pragma GCC diagnostic ignored "-Wpedantic"
  570. #endif
  571. if( (gtk_init = dlsym(handle, "gtk_init")) &&
  572. (gtk_file_chooser_dialog_new = dlsym(handle, "gtk_file_chooser_dialog_new")) &&
  573. (gtk_dialog_run = dlsym(handle, "gtk_dialog_run")) &&
  574. (gtk_file_chooser_set_create_folders = dlsym(handle, "gtk_file_chooser_set_create_folders")) &&
  575. (gtk_file_chooser_set_do_overwrite_confirmation = dlsym(handle, "gtk_file_chooser_set_do_overwrite_confirmation")) &&
  576. (gtk_file_chooser_set_current_name = dlsym(handle, "gtk_file_chooser_set_current_name")) &&
  577. (gtk_file_chooser_get_filename = dlsym(handle, "gtk_file_chooser_get_filename")) &&
  578. (gtk_widget_destroy = dlsym(handle, "gtk_widget_destroy")) ) {
  579. #ifdef __GNUC__
  580. #pragma GCC diagnostic pop
  581. #endif
  582. (*gtk_init)(NULL, NULL);
  583. chooser = (*gtk_file_chooser_dialog_new)(NULL, NULL, 0, "gtk-cancel", -6, "gtk-open", -3, NULL);
  584. if((*gtk_dialog_run)(chooser) == -3) {
  585. tmp2 = (*gtk_file_chooser_get_filename)(chooser);
  586. if(tmp2) strncpy(tmp1 + 1, tmp2, PATH_MAX - 2);
  587. }
  588. (*gtk_widget_destroy)(chooser);
  589. } else fprintf(stderr, "meg4: unable to load GTK symbols\n");
  590. dlclose(handle);
  591. } else fprintf(stderr, "meg4: unable to run-time link the GTK3 library\n");
  592. tmp1[0] = 1;
  593. /* this will actually hang if gtk_init was called... */
  594. exit(0);
  595. }
  596. if(pid < 0) fprintf(stderr, "meg4: unable to fork\n");
  597. else {
  598. /* waitpid(pid, &ret, 0); */
  599. while(!tmp1[0] && !kill(pid, SIGCONT)) main_delay(100);
  600. kill(pid, SIGKILL);
  601. }
  602. }
  603. meg4_hidecursor();
  604. if(tmp1[1]) {
  605. fn = (char*)malloc(strlen(tmp1 + 1) + 1);
  606. if(fn) strcpy(fn, tmp1 + 1);
  607. }
  608. munmap(tmp1, PATH_MAX);
  609. #endif
  610. #endif
  611. main_focus();
  612. if(fn) {
  613. if((buf = main_readfile(fn, &len))) {
  614. n = strrchr(fn, SEP[0]); if(!n) n = fn; else n++;
  615. meg4_insert(fn, buf, len);
  616. free(buf);
  617. }
  618. free(fn);
  619. }
  620. #endif
  621. #endif
  622. }
  623. /**
  624. * Open save file modal and write buffer to selected file
  625. */
  626. int main_savefile(const char *name, uint8_t *buf, int len)
  627. {
  628. #ifndef NOEDITORS
  629. #ifdef __EMSCRIPTEN__
  630. if(name && buf && len > 0) {
  631. EM_ASM({ meg4_savefile($0, $1, $2, $3); }, name, strlen(name), buf, len);
  632. return 1;
  633. }
  634. return 0;
  635. #else
  636. char path[PATH_MAX + FILENAME_MAX + 2];
  637. int ret = 0;
  638. #ifndef EMBED
  639. char *fn = NULL;
  640. int i;
  641. #endif
  642. #ifdef __ANDROID__
  643. /* TODO */
  644. #else
  645. #ifdef _MACOS_
  646. const char *utf8Path;
  647. NSURL *url;
  648. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  649. NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
  650. #else
  651. #ifndef EMBED
  652. FILE *p = NULL;
  653. pid_t pid;
  654. void *chooser;
  655. void *handle;
  656. char *tmp1, *tmp2;
  657. #endif
  658. #endif
  659. #endif
  660. if(!name || !*name || !buf || len < 1) return 0;
  661. if(main_floppydir && *main_floppydir) {
  662. strcpy(path, main_floppydir);
  663. if(path[strlen(path) - 1] == SEP[0]) path[strlen(path) - 1] = 0;
  664. mkdir(path, 0755);
  665. strcat(path, SEP);
  666. strcat(path, name);
  667. ret = main_writefile(path, buf, len);
  668. if(!memcmp(buf, "\177ELF", 4)) chmod(path, 0755);
  669. return ret;
  670. }
  671. #ifndef EMBED
  672. else {
  673. #ifdef __ANDROID__
  674. /* TODO */
  675. #else
  676. #ifdef _MACOS_
  677. NSSavePanel *dialog = [NSSavePanel savePanel];
  678. [dialog setExtensionHidden:NO];
  679. if([dialog runModal] == NSModalResponseOK) {
  680. url = [dialog URL];
  681. utf8Path = [[url path] UTF8String];
  682. i = strlen(utf8Path);
  683. fn = (char*)malloc(i + 1);
  684. if(fn) strcpy(ret, utf8Path);
  685. }
  686. [pool release];
  687. [keyWindow makeKeyAndOrderFront:nil];
  688. #else
  689. tmp1 = mmap(NULL, PATH_MAX, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  690. if(!tmp1) return 0;
  691. memset(tmp1, 0, PATH_MAX);
  692. meg4_showcursor();
  693. if(zenity) {
  694. strcpy(tmp1, "/usr/bin/zenity --file-selection --save --filename=\""); tmp2 = tmp1 + strlen(tmp1);
  695. for(i = 0; name && name[i]; i++)
  696. if(name[i] >= ' ' && name[i] != '\"') *tmp2++ = name[i];
  697. *tmp2 = '\"';
  698. p = popen(tmp1, "r");
  699. memset(tmp1, 0, PATH_MAX);
  700. }
  701. if(p) {
  702. main_log(1, "using zenity hack");
  703. if(!fgets(tmp1 + 1, PATH_MAX - 1, p)) main_log(1, "fgets error");
  704. pclose(p);
  705. for(tmp2 = tmp1 + 1; *tmp2 && *tmp2 != '\n' && *tmp2 != '\r'; tmp2++);
  706. *tmp2 = 0;
  707. tmp1[0] = 1;
  708. } else {
  709. main_log(1, "trying to dlopen gtk");
  710. if(!(pid = fork())) {
  711. handle = dlopen("libgtk-3.so", RTLD_LAZY | RTLD_LOCAL);
  712. if(handle) {
  713. #ifdef __GNUC__
  714. #pragma GCC diagnostic push
  715. #pragma GCC diagnostic ignored "-Wpedantic"
  716. #endif
  717. if( (gtk_init = dlsym(handle, "gtk_init")) &&
  718. (gtk_file_chooser_dialog_new = dlsym(handle, "gtk_file_chooser_dialog_new")) &&
  719. (gtk_dialog_run = dlsym(handle, "gtk_dialog_run")) &&
  720. (gtk_file_chooser_set_create_folders = dlsym(handle, "gtk_file_chooser_set_create_folders")) &&
  721. (gtk_file_chooser_set_do_overwrite_confirmation = dlsym(handle, "gtk_file_chooser_set_do_overwrite_confirmation")) &&
  722. (gtk_file_chooser_set_current_name = dlsym(handle, "gtk_file_chooser_set_current_name")) &&
  723. (gtk_file_chooser_get_filename = dlsym(handle, "gtk_file_chooser_get_filename")) &&
  724. (gtk_widget_destroy = dlsym(handle, "gtk_widget_destroy")) ) {
  725. #ifdef __GNUC__
  726. #pragma GCC diagnostic pop
  727. #endif
  728. (*gtk_init)(NULL, NULL);
  729. chooser = (*gtk_file_chooser_dialog_new)(NULL, NULL, 1, "gtk-cancel", -6, "gtk-save", -3, NULL);
  730. (*gtk_file_chooser_set_create_folders)(chooser, 1);
  731. (*gtk_file_chooser_set_do_overwrite_confirmation)(chooser, 1);
  732. (*gtk_file_chooser_set_current_name)(chooser, name);
  733. if((*gtk_dialog_run)(chooser) == -3) {
  734. tmp2 = (*gtk_file_chooser_get_filename)(chooser);
  735. if(tmp2) strncpy(tmp1 + 1, tmp2, PATH_MAX - 2);
  736. }
  737. (*gtk_widget_destroy)(chooser);
  738. } else fprintf(stderr, "meg4: unable to load GTK symbols\n");
  739. dlclose(handle);
  740. } else fprintf(stderr, "meg4: unable to run-time link the GTK3 library\n");
  741. tmp1[0] = 1;
  742. /* this will actually hang if gtk_init was called... */
  743. exit(0);
  744. }
  745. if(pid < 0) fprintf(stderr, "meg4: unable to fork\n");
  746. else {
  747. /* waitpid(pid, &ret, 0); */
  748. while(!tmp1[0] && !kill(pid, SIGCONT)) main_delay(100);
  749. kill(pid, SIGKILL);
  750. }
  751. }
  752. meg4_hidecursor();
  753. if(tmp1[1]) {
  754. fn = (char*)malloc(strlen(tmp1 + 1) + 1);
  755. if(fn) strcpy(fn, tmp1 + 1);
  756. }
  757. munmap(tmp1, PATH_MAX);
  758. #endif
  759. main_focus();
  760. if(fn) {
  761. ret = main_writefile(fn, buf, len);
  762. if(!memcmp(buf, "\177ELF", 4)) chmod(fn, 0755);
  763. free(fn);
  764. }
  765. }
  766. #endif
  767. #endif
  768. return ret;
  769. #endif
  770. #else
  771. (void)name; (void)buf; (void)len;
  772. return 0;
  773. #endif
  774. }
  775. /**
  776. * Return floppy list
  777. */
  778. char **main_getfloppies(void)
  779. {
  780. #if !defined(__EMSCRIPTEN__) && !defined(NOEDITORS)
  781. DIR *dir;
  782. struct dirent *ent;
  783. char fn[PATH_MAX + FILENAME_MAX + 1], *ext, **ret = NULL;
  784. int n = 0, j, l;
  785. if(!main_floppydir || !*main_floppydir) return NULL;
  786. ret = (char**)malloc(sizeof(char*));
  787. if(!ret) return NULL; else ret[0] = NULL;
  788. strcpy(fn, main_floppydir); l = strlen(fn);
  789. if(l > 0 && fn[l - 1] == SEP[0]) fn[--l] = 0;
  790. if((dir = opendir(fn)) != NULL) {
  791. while ((ent = readdir(dir)) != NULL) {
  792. if(ent->d_name[0] == '.' || !(ext = strrchr(ent->d_name, '.')) || strcasecmp(ext, ".png")) continue;
  793. fn[l] = SEP[0]; strncpy(fn + l + 1, ent->d_name, FILENAME_MAX);
  794. j = strlen(fn);
  795. if(!j) continue;
  796. ret = (char**)realloc(ret, (n + 2) * sizeof(char*));
  797. if(!ret) break;
  798. ret[n + 1] = NULL;
  799. ret[n] = (char*)malloc(j + 1);
  800. if(!ret[n]) continue;
  801. strcpy(ret[n], fn);
  802. n++;
  803. }
  804. closedir(dir);
  805. }
  806. return ret;
  807. #else
  808. return NULL;
  809. #endif
  810. }
  811. #ifndef __EMSCRIPTEN__
  812. /**
  813. * Save a config file
  814. */
  815. int main_cfgsave(char *cfg, uint8_t *buf, int len)
  816. {
  817. char file[PATH_MAX + FILENAME_MAX + 1], *tmp =
  818. #ifdef USE_INIT
  819. "/mnt/MEG-4";
  820. #else
  821. # ifdef __ANDROID__
  822. SDL_AndroidGetExternalStoragePath();
  823. # else
  824. # ifdef __LIBRETRO__
  825. main_floppydir;
  826. # else
  827. getenv("HOME");
  828. # endif
  829. # endif
  830. #endif
  831. int ret = 0, i;
  832. if(tmp && *tmp) {
  833. strcpy(file, tmp);
  834. strcat(file, "/.config"); mkdir(file, 0755);
  835. strcat(file, "/MEG-4"); mkdir(file, 0700);
  836. strcat(file, "/"); i = strlen(file); strcat(file, cfg);
  837. for(; file[i]; i++) if(file[i] == '/') { file[i] = 0; mkdir(file, 0755); file[i] = '/'; }
  838. ret = main_writefile(file, buf, len);
  839. }
  840. return ret;
  841. }
  842. /**
  843. * Load a config file
  844. */
  845. uint8_t *main_cfgload(char *cfg, int *len)
  846. {
  847. uint8_t *ret = NULL;
  848. char file[PATH_MAX + FILENAME_MAX + 1], *tmp =
  849. #ifdef USE_INIT
  850. "/mnt/MEG-4";
  851. #else
  852. # ifdef __ANDROID__
  853. SDL_AndroidGetExternalStoragePath();
  854. # else
  855. # ifdef __LIBRETRO__
  856. main_floppydir;
  857. # else
  858. getenv("HOME");
  859. # endif
  860. # endif
  861. #endif
  862. if(tmp && *tmp) {
  863. strcpy(file, tmp);
  864. strcat(file, "/.config/MEG-4/"); strcat(file, cfg);
  865. ret = main_readfile(file, len);
  866. }
  867. return ret;
  868. }
  869. #endif
  870. #endif
  871. #ifdef __EMSCRIPTEN__
  872. /**
  873. * Save a config file
  874. */
  875. int main_cfgsave(char *cfg, uint8_t *buf, int len)
  876. {
  877. EM_ASM({
  878. var name = new TextDecoder("utf-8").decode(new Uint8Array(Module.HEAPU8.buffer,$0,$1));
  879. var data = new TextDecoder("utf-8").decode(new Uint8Array(Module.HEAPU8.buffer,$2,$3));
  880. localStorage.setItem("MEG-4/"+name, data);
  881. }, cfg, strlen(cfg), buf, len);
  882. return 1;
  883. }
  884. /**
  885. * Load a config file
  886. */
  887. uint8_t *main_cfgload(char *cfg, int *len)
  888. {
  889. uint8_t *buf = EM_ASM_PTR({
  890. var name = new TextDecoder("utf-8").decode(new Uint8Array(Module.HEAPU8.buffer,$0,$1));
  891. var data = localStorage.getItem("MEG-4/"+name);
  892. if(data != undefined) {
  893. const buf = Module._malloc(data.length + 1);
  894. Module.HEAPU8.set(new TextEncoder("utf-8").encode(data + String.fromCharCode(0)), buf);
  895. return buf;
  896. }
  897. return 0;
  898. }, cfg, strlen(cfg));
  899. if(buf) *len = strlen((const char*)buf);
  900. return buf;
  901. }
  902. #endif
  903. /**
  904. * Log messages
  905. */
  906. void main_log(int lvl, const char* fmt, ...)
  907. {
  908. __builtin_va_list args;
  909. __builtin_va_start(args, fmt);
  910. if(verbose >= lvl) { printf("meg4: "); vprintf(fmt, args); printf("\r\n"); }
  911. __builtin_va_end(args);
  912. }
  913. /**
  914. * Parse the command line
  915. */
  916. void main_parsecommandline(int argc, char **argv, char **lng, char ***infile)
  917. {
  918. int i, j;
  919. #ifdef __ANDROID__
  920. char *tmp = SDL_AndroidGetExternalStoragePath();
  921. main_floppydir = (char*)malloc(strlen(tmp) + 32);
  922. if(main_floppydir) {
  923. strcpy(main_floppydir, tmp);
  924. strcat(main_floppydir, "/../../../../Download/MEG-4");
  925. mkdir(main_floppydir, 0777);
  926. }
  927. #endif
  928. *infile = NULL;
  929. for(i = 1; i < argc && argv && argv[i]; i++) {
  930. if(!memcmp(argv[i], "--help", 6) || !strcmp(argv[i], CLIFLAG "h") || !strcmp(argv[i], CLIFLAG "?")) goto usage;
  931. if(argv[i][0] == CLIFLAG[0]) {
  932. for(j = 1; j < 16 && argv[i][j]; j++)
  933. switch(argv[i][j]) {
  934. case 'L': if(j == 1 && argv[i + 1]) { *lng = argv[++i]; j = 16; } else goto usage; break;
  935. case 'd': if(j == 1 && argv[i + 1]) { main_floppydir = argv[++i]; j = 16; } else goto usage; break;
  936. case 'v': verbose++; break;
  937. #ifdef DEBUG
  938. case 's': strace++; break;
  939. #endif
  940. #ifndef __WIN32__
  941. case 'z': zenity++; break;
  942. #endif
  943. case 'n': nearest++; break;
  944. case 'w': windowed++; break;
  945. default:
  946. usage: main_hdr();
  947. printf(" meg4 [" CLIFLAG "L <xx>] "
  948. #ifndef __WIN32__
  949. "[" CLIFLAG "z] "
  950. #endif
  951. "[" CLIFLAG "n] [" CLIFLAG "w] [" CLIFLAG "v|" CLIFLAG "vv|" CLIFLAG "vvv] "
  952. #ifdef DEBUG
  953. "[" CLIFLAG "s]"
  954. #endif
  955. #ifndef EMBED
  956. "["
  957. #endif
  958. CLIFLAG "d <dir>"
  959. #ifndef EMBED
  960. "]"
  961. #endif
  962. " [floppy]\r\n\r\n");
  963. exit(0);
  964. break;
  965. }
  966. } else
  967. if(!*infile) *infile = &argv[i];
  968. }
  969. #ifdef EMBED
  970. if(!main_floppydir) goto usage;
  971. #endif
  972. if(!*lng) *lng = "en";
  973. }