frontend: support ingame actions (state load and such)
[pcsx_rearmed.git] / frontend / main.c
1 /*
2  * (C) notaz, 2010-2011
3  *
4  * This work is licensed under the terms of the GNU GPLv2 or later.
5  * See the COPYING file in the top-level directory.
6  */
7
8 #include <stdio.h>
9 #include <string.h>
10 #include <stdarg.h>
11 #include <dlfcn.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15 #include <signal.h>
16
17 #include "main.h"
18 #include "plugin.h"
19 #include "plugin_lib.h"
20 #include "pcnt.h"
21 #include "menu.h"
22 #include "../libpcsxcore/misc.h"
23 #include "../libpcsxcore/new_dynarec/new_dynarec.h"
24 #include "../plugins/cdrcimg/cdrcimg.h"
25 #include "common/plat.h"
26 #include "common/input.h"
27
28 int ready_to_go;
29 unsigned long gpuDisp;
30 char cfgfile_basename[MAXPATHLEN];
31 static char *(*real_getenv)(const char *name);
32 int state_slot;
33 enum sched_action emu_action, emu_action_old;
34 char hud_msg[64];
35 int hud_new_msg;
36
37 // from softgpu plugin
38 extern int UseFrameSkip;
39
40 static void make_path(char *buf, size_t size, const char *dir, const char *fname)
41 {
42         if (fname)
43                 snprintf(buf, size, ".%s%s", dir, fname);
44         else
45                 snprintf(buf, size, ".%s", dir);
46 }
47 #define MAKE_PATH(buf, dir, fname) \
48         make_path(buf, sizeof(buf), dir, fname)
49
50 static void create_profile_dir(const char *directory) {
51         char path[MAXPATHLEN];
52
53         MAKE_PATH(path, directory, NULL);
54         mkdir(path, S_IRWXU | S_IRWXG);
55 }
56
57 static void CheckSubDir() {
58         // make sure that ~/.pcsx exists
59         create_profile_dir(PCSX_DOT_DIR);
60
61         create_profile_dir(BIOS_DIR);
62         create_profile_dir(MEMCARD_DIR);
63         create_profile_dir(STATES_DIR);
64         create_profile_dir(PLUGINS_DIR);
65         create_profile_dir(PLUGINS_CFG_DIR);
66         create_profile_dir(CHEATS_DIR);
67         create_profile_dir(PATCHES_DIR);
68         create_profile_dir(PCSX_DOT_DIR "cfg");
69 }
70
71 void set_cd_image(const char *fname)
72 {
73         const char *ext = NULL;
74         
75         if (fname != NULL)
76                 ext = strrchr(fname, '.');
77
78         if (ext && (
79             strcasecmp(ext, ".z") == 0 || strcasecmp(ext, ".bz") == 0 ||
80             strcasecmp(ext, ".znx") == 0 || strcasecmp(ext, ".pbp") == 0)) {
81                 SetIsoFile(NULL);
82                 cdrcimg_set_fname(fname);
83                 strcpy(Config.Cdr, "builtin_cdrcimg");
84         } else {
85                 SetIsoFile(fname);
86                 strcpy(Config.Cdr, "builtin_cdr");
87         }
88 }
89
90 static void set_default_paths(void)
91 {
92         MAKE_PATH(Config.Mcd1, MEMCARD_DIR, "card1.mcd");
93         MAKE_PATH(Config.Mcd2, MEMCARD_DIR, "card2.mcd");
94         strcpy(Config.BiosDir, "bios");
95
96         strcpy(Config.PluginsDir, "plugins");
97         strcpy(Config.Gpu, "builtin_gpu");
98         strcpy(Config.Spu, "builtin_spu");
99         strcpy(Config.Cdr, "builtin_cdr");
100         strcpy(Config.Pad1, "builtin_pad");
101         strcpy(Config.Pad2, "builtin_pad");
102         strcpy(Config.Net, "Disabled");
103         Config.PsxAuto = 1;
104
105         snprintf(Config.PatchesDir, sizeof(Config.PatchesDir), "." PATCHES_DIR);
106 }
107
108 void do_emu_action(void)
109 {
110         int ret;
111
112         emu_action_old = emu_action;
113
114         switch (emu_action) {
115         case SACTION_NONE:
116                 return;
117         case SACTION_ENTER_MENU:
118                 menu_loop();
119                 return;
120         case SACTION_LOAD_STATE:
121                 ret = emu_load_state(state_slot);
122                 snprintf(hud_msg, sizeof(hud_msg), ret == 0 ? "LOADED" : "FAIL!");
123                 break;
124         case SACTION_SAVE_STATE:
125                 ret = emu_save_state(state_slot);
126                 snprintf(hud_msg, sizeof(hud_msg), ret == 0 ? "SAVED" : "FAIL!");
127                 break;
128         case SACTION_NEXT_SSLOT:
129                 state_slot++;
130                 if (state_slot > 9)
131                         state_slot = 0;
132                 goto do_state_slot;
133         case SACTION_PREV_SSLOT:
134                 state_slot--;
135                 if (state_slot < 0)
136                         state_slot = 9;
137                 goto do_state_slot;
138         case SACTION_TOGGLE_FSKIP:
139                 UseFrameSkip ^= 1;
140                 snprintf(hud_msg, sizeof(hud_msg), "FRAMESKIP %s",
141                         UseFrameSkip ? "ON" : "OFF");
142                 break;
143         }
144         hud_new_msg = 3;
145         return;
146
147 do_state_slot:
148         snprintf(hud_msg, sizeof(hud_msg), "STATE SLOT %d [%s]", state_slot,
149                 emu_check_state(state_slot) == 0 ? "USED" : "FREE");
150         hud_new_msg = 3;
151 }
152
153 int main(int argc, char *argv[])
154 {
155         void *tmp;
156
157         tmp = dlopen("/lib/libdl.so.2", RTLD_LAZY);
158         if (tmp == NULL)
159                 tmp = dlopen("/lib32/libdl.so.2", RTLD_LAZY);
160         if (tmp != NULL)
161                 real_getenv = dlsym(tmp, "getenv");
162         if (real_getenv == NULL) {
163                 fprintf(stderr, "%s\n", dlerror());
164                 return 1;
165         }
166         dlclose(tmp);
167
168         // what is the name of the config file?
169         // it may be redefined by -cfg on the command line
170         strcpy(cfgfile_basename, "pcsx.cfg");
171
172         emuLog = stdout;
173         SetIsoFile(NULL);
174
175         memset(&Config, 0, sizeof(Config));
176
177         CheckSubDir();
178         set_default_paths();
179         strcpy(Config.Bios, "HLE");
180
181 #ifdef MAEMO
182         extern int maemo_main(int argc, char **argv);
183         return maemo_main(argc, argv);
184 #else
185         char file[MAXPATHLEN] = "";
186         char path[MAXPATHLEN];
187         const char *cdfile = NULL;
188         int loadst = 0;
189         int i;
190
191         // read command line options
192         for (i = 1; i < argc; i++) {
193                      if (!strcmp(argv[i], "-psxout")) Config.PsxOut = 1;
194                 else if (!strcmp(argv[i], "-load")) loadst = atol(argv[++i]);
195                 else if (!strcmp(argv[i], "-cfg")) {
196                         if (i+1 >= argc) break;
197                         strncpy(cfgfile_basename, argv[++i], MAXPATHLEN-100);   /* TODO buffer overruns */
198                         printf("Using config file %s.\n", cfgfile_basename);
199                 }
200                 else if (!strcmp(argv[i], "-cdfile")) {
201                         char isofilename[MAXPATHLEN];
202
203                         if (i+1 >= argc) break;
204                         strncpy(isofilename, argv[++i], MAXPATHLEN);
205                         if (isofilename[0] != '/') {
206                                 getcwd(path, MAXPATHLEN);
207                                 if (strlen(path) + strlen(isofilename) + 1 < MAXPATHLEN) {
208                                         strcat(path, "/");
209                                         strcat(path, isofilename);
210                                         strcpy(isofilename, path);
211                                 } else
212                                         isofilename[0] = 0;
213                         }
214
215                         cdfile = isofilename;
216                 }
217                 else if (!strcmp(argv[i], "-h") ||
218                          !strcmp(argv[i], "-help") ||
219                          !strcmp(argv[i], "--help")) {
220                          printf(PACKAGE_NAME " " PACKAGE_VERSION "\n");
221                          printf("%s\n", _(
222                                                         " pcsx [options] [file]\n"
223                                                         "\toptions:\n"
224                                                         "\t-cdfile FILE\tRuns a CD image file\n"
225                                                         "\t-nogui\t\tDon't open the GTK GUI\n"
226                                                         "\t-cfg FILE\tLoads desired configuration file (default: ~/.pcsx/pcsx.cfg)\n"
227                                                         "\t-psxout\t\tEnable PSX output\n"
228                                                         "\t-load STATENUM\tLoads savestate STATENUM (1-5)\n"
229                                                         "\t-h -help\tDisplay this message\n"
230                                                         "\tfile\t\tLoads file\n"));
231                          return 0;
232                 } else {
233                         strncpy(file, argv[i], MAXPATHLEN);
234                         if (file[0] != '/') {
235                                 getcwd(path, MAXPATHLEN);
236                                 if (strlen(path) + strlen(file) + 1 < MAXPATHLEN) {
237                                         strcat(path, "/");
238                                         strcat(path, file);
239                                         strcpy(file, path);
240                                 } else
241                                         file[0] = 0;
242                         }
243                 }
244         }
245
246         if (cdfile)
247                 set_cd_image(cdfile);
248
249         if (SysInit() == -1)
250                 return 1;
251
252         // frontend stuff
253         in_init();
254         in_probe();
255         plat_init();
256         menu_init();
257
258         if (LoadPlugins() == -1) {
259                 // FIXME: this recovery doesn't work, just delete bad config and bail out
260                 // SysMessage("could not load plugins, retrying with defaults\n");
261                 set_default_paths();
262                 snprintf(path, sizeof(path), "." PCSX_DOT_DIR "%s", cfgfile_basename);
263                 remove(path);
264                 SysMessage("Failed loading plugins!");
265                 return 1;
266         }
267         pcnt_hook_plugins();
268
269         if (OpenPlugins() == -1) {
270                 return 1;
271         }
272         plugin_call_rearmed_cbs();
273
274         CheckCdrom();
275         SysReset();
276
277         if (file[0] != '\0') {
278                 if (Load(file) != -1)
279                         ready_to_go = 1;
280         } else {
281                 if (cdfile) {
282                         if (LoadCdrom() == -1) {
283                                 ClosePlugins();
284                                 printf(_("Could not load CD-ROM!\n"));
285                                 return -1;
286                         }
287                         ready_to_go = 1;
288                 }
289         }
290
291         // If a state has been specified, then load that
292         if (loadst) {
293                 int ret = emu_load_state(loadst - 1);
294                 printf("%s state %d\n", ret ? "failed to load" : "loaded", loadst);
295         }
296
297         if (ready_to_go)
298                 menu_prepare_emu();
299         else
300                 menu_loop();
301
302         pl_start_watchdog();
303
304         while (1)
305         {
306                 stop = 0;
307                 emu_action = SACTION_NONE;
308
309                 psxCpu->Execute();
310                 if (emu_action != SACTION_NONE)
311                         do_emu_action();
312         }
313
314         return 0;
315 #endif
316 }
317
318 int SysInit() {
319         if (EmuInit() == -1) {
320                 printf("PSX emulator couldn't be initialized.\n");
321                 return -1;
322         }
323
324         LoadMcds(Config.Mcd1, Config.Mcd2);     /* TODO Do we need to have this here, or in the calling main() function?? */
325
326         if (Config.Debug) {
327                 StartDebugger();
328         }
329
330         return 0;
331 }
332
333 void SysRunGui() {
334         printf("SysRunGui\n");
335 }
336
337 void StartGui() {
338         printf("StartGui\n");
339 }
340
341 static void dummy_lace()
342 {
343 }
344
345 void SysReset() {
346         // rearmed hack: EmuReset() runs some code when real BIOS is used,
347         // but we usually do reset from menu while GPU is not open yet,
348         // so we need to prevent updateLace() call..
349         void *real_lace = GPU_updateLace;
350         GPU_updateLace = dummy_lace;
351
352         EmuReset();
353
354         // hmh core forgets this
355         CDR_stop();
356
357         GPU_updateLace = real_lace;
358 }
359
360 void SysClose() {
361         EmuShutdown();
362         ReleasePlugins();
363
364         StopDebugger();
365
366         if (emuLog != NULL) fclose(emuLog);
367 }
368
369 void SysUpdate() {
370 }
371
372 void OnFile_Exit() {
373         printf("OnFile_Exit\n");
374 #ifndef MAEMO
375         menu_finish();
376 #endif
377         plat_finish();
378         SysClose();
379         exit(0);
380 }
381
382 int get_state_filename(char *buf, int size, int i) {
383         char trimlabel[33];
384         int j;
385
386         strncpy(trimlabel, CdromLabel, 32);
387         trimlabel[32] = 0;
388         for (j = 31; j >= 0; j--)
389                 if (trimlabel[j] == ' ')
390                         trimlabel[j] = 0;
391                 else
392                         continue;
393
394         snprintf(buf, size, "." STATES_DIR "%.32s-%.9s.%3.3d",
395                 trimlabel, CdromId, i);
396
397         return 0;
398 }
399
400 int emu_check_state(int slot)
401 {
402         char fname[MAXPATHLEN];
403         int ret;
404
405         ret = get_state_filename(fname, sizeof(fname), slot);
406         if (ret != 0)
407                 return ret;
408
409         return CheckState(fname);
410 }
411
412 int emu_save_state(int slot)
413 {
414         char fname[MAXPATHLEN];
415         int ret;
416
417         ret = get_state_filename(fname, sizeof(fname), slot);
418         if (ret != 0)
419                 return ret;
420
421         return SaveState(fname);
422 }
423
424 int emu_load_state(int slot)
425 {
426         char fname[MAXPATHLEN];
427         int ret;
428
429         ret = get_state_filename(fname, sizeof(fname), slot);
430         if (ret != 0)
431                 return ret;
432
433         return LoadState(fname);
434 }
435
436 void SysPrintf(const char *fmt, ...) {
437         va_list list;
438         char msg[512];
439
440         va_start(list, fmt);
441         vsprintf(msg, fmt, list);
442         va_end(list);
443
444         fprintf(emuLog, "%s", msg);
445 }
446
447 void SysMessage(const char *fmt, ...) {
448         va_list list;
449         char msg[512];
450
451         va_start(list, fmt);
452         vsprintf(msg, fmt, list);
453         va_end(list);
454
455         if (msg[strlen(msg) - 1] == '\n')
456                 msg[strlen(msg) - 1] = 0;
457
458         fprintf(stderr, "%s\n", msg);
459 }
460
461 static void SignalExit(int sig) {
462         ClosePlugins();
463         OnFile_Exit();
464 }
465
466 #define PARSEPATH(dst, src) \
467         ptr = src + strlen(src); \
468         while (*ptr != '\\' && ptr != src) ptr--; \
469         if (ptr != src) { \
470                 strcpy(dst, ptr+1); \
471         }
472
473 static int _OpenPlugins(void) {
474         int ret;
475
476         signal(SIGINT, SignalExit);
477         signal(SIGPIPE, SignalExit);
478
479         GPU_clearDynarec(clearDynarec);
480
481         ret = CDR_open();
482         if (ret < 0) { SysMessage(_("Error opening CD-ROM plugin!")); return -1; }
483         ret = SPU_open();
484         if (ret < 0) { SysMessage(_("Error opening SPU plugin!")); return -1; }
485         SPU_registerCallback(SPUirq);
486         // pcsx-rearmed: we handle gpu elsewhere
487         //ret = GPU_open(&gpuDisp, "PCSX", NULL);
488         //if (ret < 0) { SysMessage(_("Error opening GPU plugin!")); return -1; }
489         ret = PAD1_open(&gpuDisp);
490         if (ret < 0) { SysMessage(_("Error opening Controller 1 plugin!")); return -1; }
491         ret = PAD2_open(&gpuDisp);
492         if (ret < 0) { SysMessage(_("Error opening Controller 2 plugin!")); return -1; }
493
494         if (Config.UseNet && !NetOpened) {
495                 netInfo info;
496                 char path[MAXPATHLEN];
497                 char dotdir[MAXPATHLEN];
498
499                 MAKE_PATH(dotdir, "/.pcsx/plugins/", NULL);
500
501                 strcpy(info.EmuName, "PCSX " PACKAGE_VERSION);
502                 strncpy(info.CdromID, CdromId, 9);
503                 strncpy(info.CdromLabel, CdromLabel, 9);
504                 info.psxMem = psxM;
505                 info.GPU_showScreenPic = GPU_showScreenPic;
506                 info.GPU_displayText = GPU_displayText;
507                 info.GPU_showScreenPic = GPU_showScreenPic;
508                 info.PAD_setSensitive = PAD1_setSensitive;
509                 sprintf(path, "%s%s", Config.BiosDir, Config.Bios);
510                 strcpy(info.BIOSpath, path);
511                 strcpy(info.MCD1path, Config.Mcd1);
512                 strcpy(info.MCD2path, Config.Mcd2);
513                 sprintf(path, "%s%s", dotdir, Config.Gpu);
514                 strcpy(info.GPUpath, path);
515                 sprintf(path, "%s%s", dotdir, Config.Spu);
516                 strcpy(info.SPUpath, path);
517                 sprintf(path, "%s%s", dotdir, Config.Cdr);
518                 strcpy(info.CDRpath, path);
519                 NET_setInfo(&info);
520
521                 ret = NET_open(&gpuDisp);
522                 if (ret < 0) {
523                         if (ret == -2) {
524                                 // -2 is returned when something in the info
525                                 // changed and needs to be synced
526                                 char *ptr;
527
528                                 PARSEPATH(Config.Bios, info.BIOSpath);
529                                 PARSEPATH(Config.Gpu,  info.GPUpath);
530                                 PARSEPATH(Config.Spu,  info.SPUpath);
531                                 PARSEPATH(Config.Cdr,  info.CDRpath);
532
533                                 strcpy(Config.Mcd1, info.MCD1path);
534                                 strcpy(Config.Mcd2, info.MCD2path);
535                                 return -2;
536                         } else {
537                                 Config.UseNet = FALSE;
538                         }
539                 } else {
540                         if (NET_queryPlayer() == 1) {
541                                 if (SendPcsxInfo() == -1) Config.UseNet = FALSE;
542                         } else {
543                                 if (RecvPcsxInfo() == -1) Config.UseNet = FALSE;
544                         }
545                 }
546                 NetOpened = TRUE;
547         } else if (Config.UseNet) {
548                 NET_resume();
549         }
550
551         return 0;
552 }
553
554 int OpenPlugins() {
555         int ret;
556
557         while ((ret = _OpenPlugins()) == -2) {
558                 ReleasePlugins();
559                 LoadMcds(Config.Mcd1, Config.Mcd2);
560                 if (LoadPlugins() == -1) return -1;
561         }
562         return ret;
563 }
564
565 void ClosePlugins() {
566         int ret;
567
568         signal(SIGINT, SIG_DFL);
569         signal(SIGPIPE, SIG_DFL);
570         ret = CDR_close();
571         if (ret < 0) { SysMessage(_("Error closing CD-ROM plugin!")); return; }
572         ret = SPU_close();
573         if (ret < 0) { SysMessage(_("Error closing SPU plugin!")); return; }
574         ret = PAD1_close();
575         if (ret < 0) { SysMessage(_("Error closing Controller 1 Plugin!")); return; }
576         ret = PAD2_close();
577         if (ret < 0) { SysMessage(_("Error closing Controller 2 plugin!")); return; }
578         // pcsx-rearmed: we handle gpu elsewhere
579         //ret = GPU_close();
580         //if (ret < 0) { SysMessage(_("Error closing GPU plugin!")); return; }
581
582         if (Config.UseNet) {
583                 NET_pause();
584         }
585 }
586
587 #if 1
588 /* this is to avoid having to hack every plugin to stop using $HOME */
589 char *getenv(const char *name)
590 {
591         static char ret[8] = ".";
592
593         if (name && strcmp(name, "HOME") == 0 &&
594                         ((int)name >> 28) == 0) // HACK: let libs find home
595                 return ret;
596
597         return real_getenv(name);
598 }
599 #endif
600
601 /* we hook statically linked plugins here */
602 static const char *builtin_plugins[] = {
603         "builtin_gpu", "builtin_spu", "builtin_cdr", "builtin_pad",
604         "builtin_cdrcimg",
605 };
606
607 static const int builtin_plugin_ids[] = {
608         PLUGIN_GPU, PLUGIN_SPU, PLUGIN_CDR, PLUGIN_PAD,
609         PLUGIN_CDRCIMG,
610 };
611
612 void *SysLoadLibrary(const char *lib) {
613         const char *tmp = strrchr(lib, '/');
614         void *ret;
615         int i;
616
617         printf("plugin: %s\n", lib);
618
619         if (tmp != NULL) {
620                 tmp++;
621                 for (i = 0; i < ARRAY_SIZE(builtin_plugins); i++)
622                         if (strcmp(tmp, builtin_plugins[i]) == 0)
623                                 return (void *)(long)(PLUGIN_DL_BASE + builtin_plugin_ids[i]);
624         }
625
626 #if defined(__x86_64__) || defined(__i386__)
627         // convenience hack
628         char name[MAXPATHLEN];
629         snprintf(name, sizeof(name), "%s.x86", lib);
630         lib = name;
631 #endif
632
633         ret = dlopen(lib, RTLD_NOW);
634         if (ret == NULL)
635                 fprintf(stderr, "dlopen: %s\n", dlerror());
636         return ret;
637 }
638
639 void *SysLoadSym(void *lib, const char *sym) {
640         unsigned int plugid = (unsigned int)(long)lib;
641
642         if (PLUGIN_DL_BASE <= plugid && plugid < PLUGIN_DL_BASE + ARRAY_SIZE(builtin_plugins))
643                 return plugin_link(plugid - PLUGIN_DL_BASE, sym);
644
645         return dlsym(lib, sym);
646 }
647
648 const char *SysLibError() {
649         return dlerror();
650 }
651
652 void SysCloseLibrary(void *lib) {
653         unsigned int plugid = (unsigned int)(long)lib;
654
655         if (PLUGIN_DL_BASE <= plugid && plugid < PLUGIN_DL_BASE + ARRAY_SIZE(builtin_plugins))
656                 return;
657
658         dlclose(lib);
659 }
660