frontend: update libpicofe, fix missed callbacks
[pcsx_rearmed.git] / libpcsxcore / misc.c
index ef49244..e0445e7 100644 (file)
 * Miscellaneous functions, including savestates and CD-ROM loading.
 */
 
+#include <stddef.h>
+#include <errno.h>
+#include <assert.h>
 #include "misc.h"
 #include "cdrom.h"
+#include "cdrom-async.h"
 #include "mdec.h"
+#include "gpu.h"
 #include "ppf.h"
+#include "psxbios.h"
+#include "database.h"
+#include <zlib.h>
+#include "revision.h"
 
 char CdromId[10] = "";
 char CdromLabel[33] = "";
+int  CdromFrontendId; // for frontend use
+
+static u32 save_counter;
 
 // PSX Executable types
 #define PSX_EXE     1
@@ -51,14 +63,11 @@ struct iso_directory_record {
        char name                       [1];
 };
 
-void mmssdd( char *b, char *p )
+static void mmssdd(const char *b, char *p)
 {
+       const unsigned char *ub = (void *)b;
+       int block = (ub[3] << 24) | (ub[2] << 16) | (ub[1] << 8) | ub[0];
        int m, s, d;
-#if defined(__BIGENDIAN__)
-       int block = (b[0] & 0xff) | ((b[1] & 0xff) << 8) | ((b[2] & 0xff) << 16) | (b[3] << 24);
-#else
-       int block = *((int*)b);
-#endif
 
        block += 150;
        m = block / 4500;                       // minutes
@@ -66,17 +75,12 @@ void mmssdd( char *b, char *p )
        s = block / 75;                         // seconds
        d = block - s * 75;                     // seconds rest
 
-       m = ((m / 10) << 4) | m % 10;
-       s = ((s / 10) << 4) | s % 10;
-       d = ((d / 10) << 4) | d % 10;   
-
        p[0] = m;
        p[1] = s;
        p[2] = d;
 }
 
 #define incTime() \
-       time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
        time[2]++; \
        if(time[2] == 75) { \
                time[2] = 0; \
@@ -86,12 +90,12 @@ void mmssdd( char *b, char *p )
                        time[0]++; \
                } \
        } \
-       time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
 
 #define READTRACK() \
-       if (CDR_readTrack(time) == -1) return -1; \
-       buf = CDR_getBuffer(); \
-       if (buf == NULL) return -1; else CheckPPFCache(buf, time[0], time[1], time[2]);
+       if (cdra_readTrack(time)) return -1; \
+       buf = cdra_getBuffer(); \
+       if (buf == NULL) return -1; \
+       else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
 
 #define READDIR(_dir) \
        READTRACK(); \
@@ -101,14 +105,15 @@ void mmssdd( char *b, char *p )
        READTRACK(); \
        memcpy(_dir + 2048, buf + 12, 2048);
 
-int GetCdromFile(u8 *mdir, u8 *time, s8 *filename) {
+int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
        struct iso_directory_record *dir;
-       char ddir[4096];
+       int retval = -1;
+       u8 ddir[4096];
        u8 *buf;
        int i;
 
        // only try to scan if a filename is given
-       if (!strlen(filename)) return -1;
+       if (filename == INVALID_PTR || !strlen(filename)) return -1;
 
        i = 0;
        while (i < 4096) {
@@ -116,7 +121,7 @@ int GetCdromFile(u8 *mdir, u8 *time, s8 *filename) {
                if (dir->length[0] == 0) {
                        return -1;
                }
-               i += dir->length[0];
+               i += (u8)dir->length[0];
 
                if (dir->flags[0] & 0x2) { // it's a dir
                        if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
@@ -132,31 +137,91 @@ int GetCdromFile(u8 *mdir, u8 *time, s8 *filename) {
                } else {
                        if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
                                mmssdd(dir->extent, (char *)time);
+                               retval = 0;
                                break;
                        }
                }
        }
-       return 0;
+       return retval;
+}
+
+static void SetBootRegs(u32 pc, u32 gp, u32 sp)
+{
+       //printf("%s %08x %08x %08x\n", __func__, pc, gp, sp);
+       psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
+
+       psxRegs.pc = pc;
+       psxRegs.GPR.n.gp = gp;
+       psxRegs.GPR.n.sp = sp ? sp : 0x801fff00;
+       psxRegs.GPR.n.fp = psxRegs.GPR.n.sp;
+
+       psxRegs.GPR.n.t0 = psxRegs.GPR.n.sp; // mimic A(43)
+       psxRegs.GPR.n.t3 = pc;
+
+       psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
+}
+
+int BiosBootBypass() {
+       struct CdrStat stat = { 0, 0, };
+       assert(psxRegs.pc == 0x80030000);
+
+       // no bypass if the lid is open
+       CDR__getStatus(&stat);
+       if (stat.Status & 0x10)
+               return 0;
+
+       // skip BIOS logos and region check
+       psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
+       psxRegs.pc = psxRegs.GPR.n.ra;
+       return 1;
+}
+
+static void getFromCnf(char *buf, const char *key, u32 *val)
+{
+       buf = strstr(buf, key);
+       if (buf)
+               buf = strchr(buf, '=');
+       if (buf) {
+               unsigned long v;
+               errno = 0;
+               v = strtoul(buf + 1, NULL, 16);
+               if (errno == 0)
+                       *val = v;
+       }
 }
 
 int LoadCdrom() {
-       EXE_HEADER tmpHead;
+       union {
+               EXE_HEADER h;
+               u32 d[sizeof(EXE_HEADER) / sizeof(u32)];
+       } tmpHead;
        struct iso_directory_record *dir;
        u8 time[4], *buf;
        u8 mdir[4096];
-       s8 exename[256];
+       char exename[256];
+       u32 cnf_tcb = 4;
+       u32 cnf_event = 16;
+       u32 cnf_stack = 0;
+       u32 t_addr;
+       u32 t_size;
+       u32 sp = 0;
+       int i, ret;
+
+       save_counter = 0;
 
        if (!Config.HLE) {
-               psxRegs.pc = psxRegs.GPR.n.ra;
-               return 0;
+               if (psxRegs.pc != 0x80030000) // BiosBootBypass'ed or custom BIOS?
+                       return 0;
+               if (Config.SlowBoot)
+                       return 0;
        }
 
-       time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
+       time[0] = 0; time[1] = 2; time[2] = 0x10;
 
        READTRACK();
 
        // skip head and sub, and go to the root directory record
-       dir = (struct iso_directory_record*) &buf[12+156]; 
+       dir = (struct iso_directory_record*) &buf[12+156];
 
        mmssdd(dir->extent, (char*)time);
 
@@ -166,18 +231,20 @@ int LoadCdrom() {
        if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
                // if SYSTEM.CNF is missing, start an existing PSX.EXE
                if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
+               strcpy(exename, "PSX.EXE;1");
 
                READTRACK();
        }
        else {
                // read the SYSTEM.CNF
                READTRACK();
+               buf[1023] = 0;
 
-               sscanf((char *)buf + 12, "BOOT = cdrom:\\%256s", exename);
-               if (GetCdromFile(mdir, time, exename) == -1) {
-                       sscanf((char *)buf + 12, "BOOT = cdrom:%256s", exename);
-                       if (GetCdromFile(mdir, time, exename) == -1) {
-                               char *ptr = strstr(buf + 12, "cdrom:");
+               ret = sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
+               if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
+                       ret = sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
+                       if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
+                               char *ptr = strstr((char *)buf + 12, "cdrom:");
                                if (ptr != NULL) {
                                        ptr += 6;
                                        while (*ptr == '\\' || *ptr == '/') ptr++;
@@ -192,51 +259,73 @@ int LoadCdrom() {
                                        return -1;
                        }
                }
+               getFromCnf((char *)buf + 12, "TCB", &cnf_tcb);
+               getFromCnf((char *)buf + 12, "EVENT", &cnf_event);
+               getFromCnf((char *)buf + 12, "STACK", &cnf_stack);
+               if (Config.HLE)
+                       psxBiosCnfLoaded(cnf_tcb, cnf_event, cnf_stack);
 
                // Read the EXE-Header
                READTRACK();
        }
 
        memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
+       for (i = 2; i < sizeof(tmpHead.d) / sizeof(tmpHead.d[0]); i++)
+               tmpHead.d[i] = SWAP32(tmpHead.d[i]);
 
-       psxRegs.pc = SWAP32(tmpHead.pc0);
-       psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
-       psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr); 
-       if (psxRegs.GPR.n.sp == 0) psxRegs.GPR.n.sp = 0x801fff00;
-
-       tmpHead.t_size = SWAP32(tmpHead.t_size);
-       tmpHead.t_addr = SWAP32(tmpHead.t_addr);
+       SysPrintf("manual booting '%s' pc=%x\n", exename, tmpHead.h.pc0);
+       sp = tmpHead.h.s_addr;
+       if (cnf_stack)
+               sp = cnf_stack;
+       SetBootRegs(tmpHead.h.pc0, tmpHead.h.gp0, sp);
 
        // Read the rest of the main executable
-       while (tmpHead.t_size) {
-               void *ptr = (void *)PSXM(tmpHead.t_addr);
+       for (t_addr = tmpHead.h.t_addr, t_size = tmpHead.h.t_size; t_size & ~2047; ) {
+               void *ptr = (void *)PSXM(t_addr);
 
                incTime();
                READTRACK();
 
-               if (ptr != NULL) memcpy(ptr, buf+12, 2048);
+               if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
 
-               tmpHead.t_size -= 2048;
-               tmpHead.t_addr += 2048;
+               t_addr += 2048;
+               t_size -= 2048;
        }
 
+       psxCpu->Clear(tmpHead.h.t_addr, tmpHead.h.t_size / 4);
+       //psxCpu->Reset();
+
+       if (Config.HLE)
+               psxBiosCheckExe(tmpHead.h.t_addr, tmpHead.h.t_size, 0);
+
        return 0;
 }
 
-int LoadCdromFile(const char *filename, EXE_HEADER *head) {
+int LoadCdromFile(const char *filename, EXE_HEADER *head, u8 *time_bcd_out) {
        struct iso_directory_record *dir;
        u8 time[4],*buf;
-       u8 mdir[4096], exename[256];
+       u8 mdir[4096];
+       char exename[256];
+       const char *p1, *p2;
        u32 size, addr;
+       void *mem;
 
-       sscanf(filename, "cdrom:\\%256s", exename);
+       if (filename == INVALID_PTR)
+               return -1;
+
+       p1 = filename;
+       if ((p2 = strchr(p1, ':')))
+               p1 = p2 + 1;
+       while (*p1 == '\\')
+               p1++;
+       snprintf(exename, sizeof(exename), "%s", p1);
 
-       time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
+       time[0] = 0; time[1] = 2; time[2] = 0x10;
 
        READTRACK();
 
        // skip head and sub, and go to the root directory record
-       dir = (struct iso_directory_record *)&buf[12 + 156]; 
+       dir = (struct iso_directory_record *)&buf[12 + 156];
 
        mmssdd(dir->extent, (char*)time);
 
@@ -245,46 +334,75 @@ int LoadCdromFile(const char *filename, EXE_HEADER *head) {
        if (GetCdromFile(mdir, time, exename) == -1) return -1;
 
        READTRACK();
+       incTime();
 
        memcpy(head, buf + 12, sizeof(EXE_HEADER));
-       size = head->t_size;
-       addr = head->t_addr;
+       size = SWAP32(head->t_size);
+       addr = SWAP32(head->t_addr);
 
-       while (size) {
-               incTime();
+       psxCpu->Clear(addr, size / 4);
+       //psxCpu->Reset();
+
+       while (size & ~2047) {
                READTRACK();
+               incTime();
 
-               memcpy((void *)PSXM(addr), buf + 12, 2048);
+               mem = PSXM(addr);
+               if (mem != INVALID_PTR)
+                       memcpy(mem, buf + 12, 2048);
 
                size -= 2048;
                addr += 2048;
        }
+       if (time_bcd_out) {
+               time_bcd_out[0] = itob(time[0]);
+               time_bcd_out[1] = itob(time[1]);
+               time_bcd_out[2] = itob(time[2]);
+       }
 
        return 0;
 }
 
 int CheckCdrom() {
        struct iso_directory_record *dir;
-       unsigned char time[4], *buf;
+       struct CdrStat stat = { 0, 0, };
+       unsigned char time[4] = { 0, 2, 4 };
+       char *buf;
        unsigned char mdir[4096];
        char exename[256];
-       int i, c;
+       int lic_region_detected = -1;
+       int i, len, c;
 
        FreePPFCache();
+       memset(CdromLabel, 0, sizeof(CdromLabel));
+       memset(CdromId, 0, sizeof(CdromId));
+       memset(exename, 0, sizeof(exename));
+
+       if (!Config.HLE && Config.SlowBoot) {
+               // boot to BIOS in case of CDDA or lid is open
+               cdra_getStatus(&stat);
+               if ((stat.Status & 0x10) || stat.Type == 2 || cdra_readTrack(time))
+                       return 0;
+       }
+       if (Config.PsxAuto) {
+               time[0] = 0;
+               time[1] = 2;
+               time[2] = 4;
+               READTRACK();
+               if (strcmp((char *)buf + 12 + 46, "Entertainment Euro pe   ") == 0)
+                       lic_region_detected = PSX_TYPE_PAL;
+               // else it'll default to NTSC anyway
+       }
 
-       time[0] = itob(0);
-       time[1] = itob(2);
-       time[2] = itob(0x10);
-
+       time[0] = 0;
+       time[1] = 2;
+       time[2] = 0x10;
        READTRACK();
 
-       CdromLabel[0] = '\0';
-       CdromId[0] = '\0';
-
        strncpy(CdromLabel, buf + 52, 32);
 
        // skip head and sub, and go to the root directory record
-       dir = (struct iso_directory_record *)&buf[12 + 156]; 
+       dir = (struct iso_directory_record *)&buf[12 + 156];
 
        mmssdd(dir->extent, (char *)time);
 
@@ -293,9 +411,9 @@ int CheckCdrom() {
        if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
                READTRACK();
 
-               sscanf((char *)buf + 12, "BOOT = cdrom:\\%256s", exename);
+               sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
                if (GetCdromFile(mdir, time, exename) == -1) {
-                       sscanf((char *)buf + 12, "BOOT = cdrom:%256s", exename);
+                       sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
                        if (GetCdromFile(mdir, time, exename) == -1) {
                                char *ptr = strstr(buf + 12, "cdrom:");                 // possibly the executable is in some subdir
                                if (ptr != NULL) {
@@ -312,6 +430,14 @@ int CheckCdrom() {
                                        return -1;
                        }
                }
+               /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
+               if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
+                       size_t offset = 4;
+                       size_t i, len = strlen(exename) - offset;
+                       for (i = 0; i < len; i++)
+                               exename[i] = exename[i + offset];
+                       exename[i] = '\0';
+               }
        } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
                strcpy(exename, "PSX.EXE;1");
                strcpy(CdromId, "SLUS99999");
@@ -319,19 +445,29 @@ int CheckCdrom() {
                return -1;              // SYSTEM.CNF and PSX.EXE not found
 
        if (CdromId[0] == '\0') {
-               i = strlen(exename);
-               if (i >= 2) {
-                       if (exename[i - 2] == ';') i-= 2;
-                       c = 8; i--;
-                       while (i >= 0 && c >= 0) {
-                               if (isalnum(exename[i])) CdromId[c--] = exename[i];
-                               i--;
-                       }
+               len = strlen(exename);
+               c = 0;
+               for (i = 0; i < len; ++i) {
+                       if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
+                               break;
+                       if (isalnum((int)exename[i]))
+                               CdromId[c++] = exename[i];
                }
        }
 
+       if (CdromId[0] == '\0')
+               strcpy(CdromId, "SLUS99999");
+
        if (Config.PsxAuto) { // autodetect system (pal or ntsc)
-               if (strstr(exename, "ES") != NULL)
+               if (lic_region_detected >= 0)
+                       Config.PsxType = lic_region_detected;
+               else if (
+                       /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
+                       ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
+                       !strncmp(CdromId, "DTLS3035", 8) ||
+                       !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
+                       !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
+                       !strncmp(CdromId, "PBPX95008", 9))   // add more serials if they are discovered.
                        Config.PsxType = PSX_TYPE_PAL; // pal
                else Config.PsxType = PSX_TYPE_NTSC; // ntsc
        }
@@ -341,8 +477,11 @@ int CheckCdrom() {
        }
        SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
        SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
+       SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
+       
+       Apply_Hacks_Cdrom();
 
-       BuildPPFCache();
+       BuildPPFCache(NULL);
 
        return 0;
 }
@@ -355,7 +494,9 @@ static int PSXGetFileType(FILE *f) {
 
        current = ftell(f);
        fseek(f, 0L, SEEK_SET);
-       fread(mybuf, 2048, 1, f);
+       if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
+               goto io_fail;
+       
        fseek(f, current, SEEK_SET);
 
        exe_hdr = (EXE_HEADER *)mybuf;
@@ -370,6 +511,30 @@ static int PSXGetFileType(FILE *f) {
                return COFF_EXE;
 
        return INVALID_EXE;
+
+io_fail:
+#ifndef NDEBUG
+       SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
+#endif
+       return INVALID_EXE;
+}
+
+// temporary pandora workaround..
+// FIXME: remove
+size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+       void *tmp;
+       size_t ret = 0;
+
+       tmp = malloc(size * nmemb);
+       if (tmp) {
+               ret = fread(tmp, size, nmemb, stream);
+               memcpy(ptr, tmp, size * nmemb);
+               free(tmp);
+       }
+       else
+               ret = fread(ptr, size, nmemb, stream);
+       return ret;
 }
 
 int Load(const char *ExePath) {
@@ -379,9 +544,10 @@ int Load(const char *ExePath) {
        int retval = 0;
        u8 opcode;
        u32 section_address, section_size;
+       void *mem;
 
-       strncpy(CdromId, "SLUS99999", 9);
-       strncpy(CdromLabel, "SLUS_999.99", 11);
+       strcpy(CdromId, "SLUS99999");
+       strcpy(CdromLabel, "SLUS_999.99");
 
        tmpFile = fopen(ExePath, "rb");
        if (tmpFile == NULL) {
@@ -391,41 +557,52 @@ int Load(const char *ExePath) {
                type = PSXGetFileType(tmpFile);
                switch (type) {
                        case PSX_EXE:
-                               fread(&tmpHead,sizeof(EXE_HEADER),1,tmpFile);
-                               fseek(tmpFile, 0x800, SEEK_SET);                
-                               fread((void *)PSXM(SWAP32(tmpHead.t_addr)), SWAP32(tmpHead.t_size),1,tmpFile);
-                               fclose(tmpFile);
-                               psxRegs.pc = SWAP32(tmpHead.pc0);
-                               psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
-                               psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr); 
-                               if (psxRegs.GPR.n.sp == 0)
-                                       psxRegs.GPR.n.sp = 0x801fff00;
+                               if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
+                                       goto fail_io;
+                               section_address = SWAP32(tmpHead.t_addr);
+                               section_size = SWAP32(tmpHead.t_size);
+                               mem = PSXM(section_address);
+                               if (mem != INVALID_PTR) {
+                                       fseek(tmpFile, 0x800, SEEK_SET);
+                                       fread_to_ram(mem, section_size, 1, tmpFile);
+                                       psxCpu->Clear(section_address, section_size / 4);
+                               }
+                               SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
+                                       SWAP32(tmpHead.s_addr));
                                retval = 0;
                                break;
                        case CPE_EXE:
                                fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
                                do {
-                                       fread(&opcode, 1, 1, tmpFile);
+                                       if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
+                                               goto fail_io;
                                        switch (opcode) {
                                                case 1: /* Section loading */
-                                                       fread(&section_address, 4, 1, tmpFile);
-                                                       fread(&section_size, 4, 1, tmpFile);
+                                                       if (fread(&section_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
+                                                               goto fail_io;
+                                                       if (fread(&section_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
+                                                               goto fail_io;
                                                        section_address = SWAPu32(section_address);
                                                        section_size = SWAPu32(section_size);
 #ifdef EMU_LOG
                                                        EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
 #endif
-                                                       fread(PSXM(section_address), section_size, 1, tmpFile);
+                                                       mem = PSXM(section_address);
+                                                       if (mem != INVALID_PTR) {
+                                                               fread_to_ram(mem, section_size, 1, tmpFile);
+                                                               psxCpu->Clear(section_address, section_size / 4);
+                                                       }
                                                        break;
                                                case 3: /* register loading (PC only?) */
                                                        fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
-                                                       fread(&psxRegs.pc, 4, 1, tmpFile);
+                                                       if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
+                                                               goto fail_io;
                                                        psxRegs.pc = SWAPu32(psxRegs.pc);
                                                        break;
                                                case 0: /* End of file */
                                                        break;
                                                default:
-                                                       SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
+                                                       SysPrintf(_("Unknown CPE opcode %02x at position %08lx.\n"), opcode, ftell(tmpFile) - 1);
                                                        retval = -1;
                                                        break;
                                        }
@@ -436,7 +613,8 @@ int Load(const char *ExePath) {
                                retval = -1;
                                break;
                        case INVALID_EXE:
-                               SysPrintf(_("This file does not appear to be a valid PSX file.\n"));
+                               SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
+                               SysPrintf(_("(did you forget -cdfile ?)\n"));
                                retval = -1;
                                break;
                }
@@ -447,116 +625,261 @@ int Load(const char *ExePath) {
                CdromLabel[0] = '\0';
        }
 
+       if (tmpFile)
+               fclose(tmpFile);
        return retval;
+
+fail_io:
+#ifndef NDEBUG
+       SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
+#endif
+       fclose(tmpFile);
+       return -1;
 }
 
 // STATES
 
-static const char PcsxHeader[32] = "STv4 PCSX v" PACKAGE_VERSION;
+static void *zlib_open(const char *name, const char *mode)
+{
+       return gzopen(name, mode);
+}
+
+static int zlib_read(void *file, void *buf, u32 len)
+{
+       return gzread(file, buf, len);
+}
+
+static int zlib_write(void *file, const void *buf, u32 len)
+{
+       return gzwrite(file, buf, len);
+}
+
+static long zlib_seek(void *file, long offs, int whence)
+{
+       return gzseek(file, offs, whence);
+}
+
+static void zlib_close(void *file)
+{
+       gzclose(file);
+}
+
+struct PcsxSaveFuncs SaveFuncs = {
+       zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
+};
+
+static const char PcsxHeader[32] = "STv4 PCSXra " REV;
 
 // Savestate Versioning!
 // If you make changes to the savestate version, please increment the value below.
-static const u32 SaveVersion = 0x8b410004;
+static const u32 SaveVersion = 0x8b410006;
+
+struct origin_info {
+       boolean icache_emulation;
+       boolean DisableStalls;
+       boolean PreciseExceptions;
+       boolean TurboCD;
+       s8 GpuListWalking;
+       s8 FractionalFramerate;
+       u8 Cpu;
+       u8 PsxType;
+       char build_info[64];
+};
+
+#define MISC_MAGIC 0x4353494d
+struct misc_save_data {
+       u32 magic;
+       u32 gteBusyCycle;
+       u32 muldivBusyCycle;
+       u32 biuReg;
+       u32 biosBranchCheck;
+       u32 gpuIdleAfter;
+       u32 gpuSr;
+       u32 frame_counter;
+       int CdromFrontendId;
+       u32 save_counter;
+};
+
+#define EX_SCREENPIC_SIZE (128 * 96 * 3)
 
 int SaveState(const char *file) {
-       gzFile f;
-       GPUFreeze_t *gpufP;
-       SPUFreeze_t *spufP;
+       struct misc_save_data *misc = (void *)(psxH + 0xf000);
+       struct origin_info oi = { 0, };
+       GPUFreeze_t *gpufP = NULL;
+       SPUFreezeHdr_t spufH;
+       SPUFreeze_t *spufP = NULL;
+       u8 buf[EX_SCREENPIC_SIZE];
+       int result = -1;
        int Size;
-       unsigned char *pMem;
+       void *f;
 
-       f = gzopen(file, "wb");
-       if (f == NULL) return -1;
+       assert(!psxRegs.branching);
+       assert(!psxRegs.cpuInRecursion);
+       assert(!misc->magic);
 
-       gzwrite(f, (void *)PcsxHeader, 32);
-       gzwrite(f, (void *)&SaveVersion, sizeof(u32));
-       gzwrite(f, (void *)&Config.HLE, sizeof(boolean));
+       f = SaveFuncs.open(file, "wb");
+       if (f == NULL) return -1;
 
-       pMem = (unsigned char *)malloc(128 * 96 * 3);
-       if (pMem == NULL) return -1;
-       GPU_getScreenPic(pMem);
-       gzwrite(f, pMem, 128 * 96 * 3);
-       free(pMem);
+       misc->magic = MISC_MAGIC;
+       misc->gteBusyCycle = psxRegs.gteBusyCycle;
+       misc->muldivBusyCycle = psxRegs.muldivBusyCycle;
+       misc->biuReg = psxRegs.biuReg;
+       misc->biosBranchCheck = psxRegs.biosBranchCheck;
+       misc->gpuIdleAfter = psxRegs.gpuIdleAfter;
+       misc->gpuSr = HW_GPU_STATUS;
+       misc->frame_counter = frame_counter;
+       misc->CdromFrontendId = CdromFrontendId;
+       misc->save_counter = ++save_counter;
+
+       psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
+
+       SaveFuncs.write(f, (void *)PcsxHeader, 32);
+       SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
+       SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
+
+       oi.icache_emulation = Config.icache_emulation;
+       oi.DisableStalls = Config.DisableStalls;
+       oi.PreciseExceptions = Config.PreciseExceptions;
+       oi.TurboCD = Config.TurboCD;
+       oi.GpuListWalking = Config.GpuListWalking;
+       oi.FractionalFramerate = Config.FractionalFramerate;
+       oi.Cpu = Config.Cpu;
+       oi.PsxType = Config.PsxType;
+       snprintf(oi.build_info, sizeof(oi.build_info), "%s", get_build_info());
+
+       // this was space for ScreenPic
+       assert(sizeof(buf) >= EX_SCREENPIC_SIZE);
+       assert(sizeof(oi) - 3 <= EX_SCREENPIC_SIZE);
+       memset(buf, 0, sizeof(buf));
+       memcpy(buf + 3, &oi, sizeof(oi));
+       SaveFuncs.write(f, buf, EX_SCREENPIC_SIZE);
 
        if (Config.HLE)
                psxBiosFreeze(1);
 
-       gzwrite(f, psxM, 0x00200000);
-       gzwrite(f, psxR, 0x00080000);
-       gzwrite(f, psxH, 0x00010000);
-       gzwrite(f, (void *)&psxRegs, sizeof(psxRegs));
+       SaveFuncs.write(f, psxM, 0x00200000);
+       SaveFuncs.write(f, psxR, 0x00080000);
+       SaveFuncs.write(f, psxH, 0x00010000);
+       // only partial save of psxRegisters to maintain savestate compat
+       SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
 
        // gpu
        gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
+       if (gpufP == NULL) goto cleanup;
        gpufP->ulFreezeVersion = 1;
        GPU_freeze(1, gpufP);
-       gzwrite(f, gpufP, sizeof(GPUFreeze_t));
-       free(gpufP);
+       SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
+       free(gpufP); gpufP = NULL;
 
        // spu
-       spufP = (SPUFreeze_t *) malloc(16);
-       SPU_freeze(2, spufP);
-       Size = spufP->Size; gzwrite(f, &Size, 4);
-       free(spufP);
+       SPU_freeze(2, (SPUFreeze_t *)&spufH, psxRegs.cycle);
+       Size = spufH.Size; SaveFuncs.write(f, &Size, 4);
        spufP = (SPUFreeze_t *) malloc(Size);
-       SPU_freeze(1, spufP);
-       gzwrite(f, spufP, Size);
-       free(spufP);
+       if (spufP == NULL) goto cleanup;
+       SPU_freeze(1, spufP, psxRegs.cycle);
+       SaveFuncs.write(f, spufP, Size);
+       free(spufP); spufP = NULL;
 
        sioFreeze(f, 1);
        cdrFreeze(f, 1);
        psxHwFreeze(f, 1);
        psxRcntFreeze(f, 1);
        mdecFreeze(f, 1);
-
-       gzclose(f);
-
-       return 0;
+       ndrc_freeze(f, 1);
+       padFreeze(f, 1);
+
+       result = 0;
+cleanup:
+       memset(misc, 0, sizeof(*misc));
+       SaveFuncs.close(f);
+       return result;
 }
 
 int LoadState(const char *file) {
-       gzFile f;
-       GPUFreeze_t *gpufP;
-       SPUFreeze_t *spufP;
+       struct misc_save_data *misc = (void *)(psxH + 0xf000);
+       u32 biosBranchCheckOld = psxRegs.biosBranchCheck;
+       void *f;
+       GPUFreeze_t *gpufP = NULL;
+       SPUFreeze_t *spufP = NULL;
+       boolean hle, oldhle;
        int Size;
        char header[32];
        u32 version;
-       boolean hle;
+       int result = -1;
 
-       f = gzopen(file, "rb");
+       f = SaveFuncs.open(file, "rb");
        if (f == NULL) return -1;
 
-       gzread(f, header, sizeof(header));
-       gzread(f, &version, sizeof(u32));
-       gzread(f, &hle, sizeof(boolean));
-
-       if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion || hle != Config.HLE) {
-               gzclose(f);
-               return -1;
+       if (!file)
+               file = "(stream)";
+       memset(header, 0, sizeof(header));
+       SaveFuncs.read(f, header, 16);
+       if (strncmp("RASTATE", header, 7) == 0) {
+               // looks like RA header, normal savestate should follow
+               SysPrintf("%s: trying to skip RASTATE header\n", file);
+               SaveFuncs.read(f, header, 16);
        }
+       SaveFuncs.read(f, header + 16, 16);
+       SaveFuncs.read(f, &version, sizeof(u32));
+       SaveFuncs.read(f, &hle, sizeof(boolean));
 
-       psxCpu->Reset();
-       gzseek(f, 128 * 96 * 3, SEEK_CUR);
+       if (strncmp("STv4 PCSX", header, 9) != 0) {
+               SysPrintf("%s: is not a savestate?\n", file);
+               goto cleanup;
+       }
+       if (version != SaveVersion) {
+               SysPrintf("%s: incompatible savestate version %x\n", file, version);
+               goto cleanup;
+       }
+       oldhle = Config.HLE;
+       Config.HLE = hle;
 
-       gzread(f, psxM, 0x00200000);
-       gzread(f, psxR, 0x00080000);
-       gzread(f, psxH, 0x00010000);
-       gzread(f, (void *)&psxRegs, sizeof(psxRegs));
+       if (Config.HLE)
+               psxBiosInit();
+
+       // ex-ScreenPic space
+       SaveFuncs.seek(f, EX_SCREENPIC_SIZE, SEEK_CUR);
+
+       SaveFuncs.read(f, psxM, 0x00200000);
+       SaveFuncs.read(f, psxR, 0x00080000);
+       SaveFuncs.read(f, psxH, 0x00010000);
+       SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
+       psxRegs.gteBusyCycle = psxRegs.cycle;
+       psxRegs.branching = 0;
+       psxRegs.biosBranchCheck = ~0;
+       psxRegs.cpuInRecursion = 0;
+       psxRegs.gpuIdleAfter = psxRegs.cycle - 1;
+       HW_GPU_STATUS &= SWAP32(~PSXGPU_nBUSY);
+       if (misc->magic == MISC_MAGIC) {
+               psxRegs.gteBusyCycle = misc->gteBusyCycle;
+               psxRegs.muldivBusyCycle = misc->muldivBusyCycle;
+               psxRegs.biuReg = misc->biuReg;
+               psxRegs.biosBranchCheck = misc->biosBranchCheck;
+               psxRegs.gpuIdleAfter = misc->gpuIdleAfter;
+               HW_GPU_STATUS = misc->gpuSr;
+               frame_counter = misc->frame_counter;
+               CdromFrontendId = misc->CdromFrontendId;
+               if (misc->save_counter)
+                       save_counter = misc->save_counter;
+       }
 
        if (Config.HLE)
                psxBiosFreeze(0);
 
        // gpu
        gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
-       gzread(f, gpufP, sizeof(GPUFreeze_t));
+       if (gpufP == NULL) goto cleanup;
+       SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
        GPU_freeze(0, gpufP);
        free(gpufP);
+       gpuSyncPluginSR();
 
        // spu
-       gzread(f, &Size, 4);
+       SaveFuncs.read(f, &Size, 4);
        spufP = (SPUFreeze_t *)malloc(Size);
-       gzread(f, spufP, Size);
-       SPU_freeze(0, spufP);
+       if (spufP == NULL) goto cleanup;
+       SaveFuncs.read(f, spufP, Size);
+       SPU_freeze(0, spufP, psxRegs.cycle);
        free(spufP);
 
        sioFreeze(f, 0);
@@ -565,81 +888,52 @@ int LoadState(const char *file) {
        psxRcntFreeze(f, 0);
        mdecFreeze(f, 0);
 
-       gzclose(f);
+       if (Config.HLE != oldhle) {
+               // at least ari64 drc compiles differently so hard reset
+               psxCpu->Shutdown();
+               psxCpu->Init();
+       }
+       ndrc_freeze(f, 0);
+       padFreeze(f, 0);
 
-       return 0;
+       events_restore();
+       if (Config.HLE)
+               psxBiosCheckExe(biosBranchCheckOld, 0x60, 1);
+
+       psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
+
+       result = 0;
+cleanup:
+       memset(misc, 0, sizeof(*misc));
+       SaveFuncs.close(f);
+       return result;
 }
 
 int CheckState(const char *file) {
-       gzFile f;
+       void *f;
        char header[32];
        u32 version;
        boolean hle;
 
-       f = gzopen(file, "rb");
+       f = SaveFuncs.open(file, "rb");
        if (f == NULL) return -1;
 
-       gzread(f, header, sizeof(header));
-       gzread(f, &version, sizeof(u32));
-       gzread(f, &hle, sizeof(boolean));
+       memset(header, 0, sizeof(header));
+       SaveFuncs.read(f, header, 16);
+       if (strncmp("RASTATE", header, 7) == 0)
+               SaveFuncs.read(f, header, 16);
+       SaveFuncs.read(f, header + 16, 16);
+       SaveFuncs.read(f, &version, sizeof(u32));
+       SaveFuncs.read(f, &hle, sizeof(boolean));
 
-       gzclose(f);
+       SaveFuncs.close(f);
 
-       if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion || hle != Config.HLE)
+       if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
                return -1;
 
        return 0;
 }
 
-// NET Function Helpers
-
-int SendPcsxInfo() {
-       if (NET_recvData == NULL || NET_sendData == NULL)
-               return 0;
-
-       NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
-       NET_sendData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
-       NET_sendData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
-       NET_sendData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
-       NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
-       NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
-
-       return 0;
-}
-
-int RecvPcsxInfo() {
-       int tmp;
-
-       if (NET_recvData == NULL || NET_sendData == NULL)
-               return 0;
-
-       NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
-       NET_recvData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
-       NET_recvData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
-       NET_recvData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
-       NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
-
-       SysUpdate();
-
-       tmp = Config.Cpu;
-       NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
-       if (tmp != Config.Cpu) {
-               psxCpu->Shutdown();
-#ifdef PSXREC
-               if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
-               else psxCpu = &psxRec;
-#else
-               psxCpu = &psxInt;
-#endif
-               if (psxCpu->Init() == -1) {
-                       SysClose(); return -1;
-               }
-               psxCpu->Reset();
-       }
-
-       return 0;
-}
-
 // remove the leading and trailing spaces in a string
 void trim(char *str) {
        int pos = 0;
@@ -694,7 +988,7 @@ static unsigned short crctab[256] = {
        0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
 };
 
-u16 calcCrc(u8 *d, int len) {
+u16 calcCrc(const u8 *d, int len) {
        u16 crc = 0;
        int i;
 
@@ -704,3 +998,54 @@ u16 calcCrc(u8 *d, int len) {
 
        return ~crc;
 }
+
+#define MKSTR2(x) #x
+#define MKSTR(x) MKSTR2(x)
+const char *get_build_info(void)
+{
+       return ""
+#ifdef __VERSION__
+               "cc " __VERSION__ " "
+#endif
+#if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 8
+               "64bit "
+#elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ == 4
+               "32bit "
+#endif
+#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+               "be "
+#endif
+#if defined(__PIE__) || defined(__pie__)
+               "pie "
+#endif
+#if defined(__PIC__) || defined(__pic__)
+               "pic "
+#endif
+#if defined(__aarch64__)
+               "arm64"
+#elif defined(__arm__)
+               "arm"
+#endif
+#ifdef __ARM_ARCH
+               "v" MKSTR(__ARM_ARCH) " "
+#endif
+#ifdef __thumb__
+               "thumb "
+#endif
+#if defined(__AVX__)
+               "avx "
+#elif defined(__SSSE3__)
+               "ssse3 "
+#elif defined(__ARM_NEON) || defined(__ARM_NEON__)
+               "neon "
+#endif
+#if defined(__ARM_FEATURE_SVE) && __ARM_FEATURE_SVE
+               "sve "
+#endif
+#if defined(LIGHTREC)
+               "lightrec "
+#elif !defined(DRC_DISABLE)
+               "ari64 "
+#endif
+               "gpu=" MKSTR(BUILTIN_GPU);
+}