psxbios: completely rework exception handling
[pcsx_rearmed.git] / libpcsxcore / psxbios.c
index 928f0af..6624207 100644 (file)
@@ -18,7 +18,7 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA 02111-1307 USA.           *
  ***************************************************************************/
 
-/* Gameblabla 2018-2019 : 
+/* Gameblabla 2018-2019 :
  * Numerous changes to bios calls as well as improvements in order to conform to nocash's findings
  * for the PSX bios calls. Thanks senquack for helping out with some of the changes
  * and helping to spot issues and refine my patches.
 #include "psxbios.h"
 #include "psxhw.h"
 #include "gpu.h"
+#include "sio.h"
+#include "psxhle.h"
 #include <zlib.h>
 
+#if (defined(__GNUC__) && __GNUC__ >= 5) || defined(__clang__)
+#pragma GCC diagnostic ignored "-Wpointer-sign"
+#endif
+
 #undef SysPrintf
 #define SysPrintf if (Config.PsxOut) printf
 
@@ -75,7 +81,7 @@ char *biosA0n[256] = {
        "dev_card_close",       "dev_card_firstfile",   "dev_card_nextfile","dev_card_erase",
        "dev_card_undelete","dev_card_format",          "dev_card_rename",      "dev_card_6f",
 // 0x70
-       "_bu_init",                     "_96_init",             "_96_remove",           "sys_a0_73",
+       "_bu_init",                     "_96_init",             "CdRemove",             "sys_a0_73",
        "sys_a0_74",            "sys_a0_75",    "sys_a0_76",            "sys_a0_77",
        "_96_CdSeekL",          "sys_a0_79",    "sys_a0_7a",            "sys_a0_7b",
        "_96_CdGetStatus",      "sys_a0_7d",    "_96_CdRead",           "sys_a0_7f",
@@ -211,13 +217,16 @@ typedef struct {
 */
 
 typedef struct {
-       s32 status;
-       s32 mode;
+       u32 status;
+       u32 mode;
        u32 reg[32];
-       u32 func;
+       u32 epc;
+       u32 hi, lo;
+       u32 sr, cause;
+       u32 unused[9];
 } TCB;
 
-typedef struct {                   
+typedef struct {
        u32 _pc0;
        u32 gp0;
        u32 t_addr;
@@ -248,14 +257,13 @@ typedef struct {
        u32  mcfile;
 } FileDesc;
 
-static u32 *jmp_int = NULL;
 static int *pad_buf = NULL;
 static char *pad_buf1 = NULL, *pad_buf2 = NULL;
 static int pad_buf1len, pad_buf2len;
 static int pad_stopped = 0;
 
 static u32 regs[35];
-static EvCB *Event;
+static EvCB *EventCB;
 static EvCB *HwEV; // 0xf0
 static EvCB *EvEV; // 0xf1
 static EvCB *RcEV; // 0xf2
@@ -265,61 +273,114 @@ static EvCB *ThEV; // 0xff
 static u32 heap_size = 0;
 static u32 *heap_addr = NULL;
 static u32 *heap_end = NULL;
-static u32 SysIntRP[8];
 static int CardState = -1;
-static TCB Thread[8];
 static int CurThread = 0;
 static FileDesc FDesc[32];
-static u32 card_active_chan;
+static u32 card_active_chan = 0;
+
+// fixed RAM offsets, SCPH1001 compatible
+#define A_TT_ExCB       0x0100
+#define A_TT_PCB        0x0108
+#define A_TT_TCB        0x0110
+#define A_A0_TABLE      0x0200
+#define A_B0_TABLE      0x0874
+#define A_C0_TABLE      0x0674
+#define A_SYSCALL       0x0650
+#define A_EXCEPTION     0x0c80
+#define A_EXC_SP        0x6cf0
+#define A_EEXIT_DEF     0x6cf4
+#define A_EEXIT_PTR     0x75d0
+#define A_EXC_STACK     0x85d8  // exception stack top
+#define A_RCNT_VBL_ACK  0x8600
+#define A_EXC_GP        0xf450
+
+#define HLEOP(n) SWAPu32((0x3b << 26) | (n));
+
+static u32 loadRam32(u32 addr)
+{
+       assert(!(addr & 0x5f800000));
+       return SWAP32(*((u32 *)psxM + ((addr & 0x1fffff) >> 2)));
+}
+
+static void *castRam32ptr(u32 addr)
+{
+       assert(!(addr & 0x5f800003));
+       return psxM + (addr & 0x1ffffc);
+}
 
-boolean hleSoftCall = FALSE;
+static void *loadRam32ptr(u32 addr)
+{
+       return castRam32ptr(loadRam32(addr));
+}
 
-static inline void softCall(u32 pc) {
-       pc0 = pc;
-       ra = 0x80001000;
+static void storeRam32(u32 addr, u32 d)
+{
+       assert(!(addr & 0x5f800000));
+       *((u32 *)psxM + ((addr & 0x1fffff) >> 2)) = SWAP32(d);
+}
+
+static void mips_return(u32 val)
+{
+       v0 = val;
+       pc0 = ra;
+}
 
-       hleSoftCall = TRUE;
+static void use_cycles(u32 cycle)
+{
+       psxRegs.cycle += cycle * 2;
+}
 
-       while (pc0 != 0x80001000) psxCpu->ExecuteBlock();
+static void mips_return_c(u32 val, u32 cycle)
+{
+       use_cycles(cycle);
+       mips_return(val);
+}
+
+static void mips_return_void_c(u32 cycle)
+{
+       use_cycles(cycle);
+       pc0 = ra;
+}
 
-       hleSoftCall = FALSE;
+static int returned_from_exception(void)
+{
+       // 0x80000080 means it took another exception just after return
+       return pc0 == k0 || pc0 == 0x80000080;
 }
 
-static inline void softCall2(u32 pc) {
+static inline void softCall(u32 pc) {
        u32 sra = ra;
+       u32 ssr = psxRegs.CP0.n.SR;
        pc0 = pc;
        ra = 0x80001000;
+       psxRegs.CP0.n.SR &= ~0x404; // disable interrupts
 
-       hleSoftCall = TRUE;
+       while (pc0 != 0x80001000)
+               psxCpu->ExecuteBlock(EXEC_CALLER_HLE);
 
-       while (pc0 != 0x80001000) psxCpu->ExecuteBlock();
        ra = sra;
-
-       hleSoftCall = FALSE;
+       psxRegs.CP0.n.SR = ssr;
 }
 
-static inline void DeliverEvent(u32 ev, u32 spec) {
-       if (Event[ev][spec].status != EvStACTIVE) return;
-
-//     Event[ev][spec].status = EvStALREADY;
-       if (Event[ev][spec].mode == EvMdINTR) {
-               softCall2(Event[ev][spec].fhandler);
-       } else Event[ev][spec].status = EvStALREADY;
-}
+static inline void softCallInException(u32 pc) {
+       u32 sra = ra;
+       pc0 = pc;
+       ra = 0x80001000;
 
-static unsigned interrupt_r26=0x8004E8B0;
+       while (!returned_from_exception() && pc0 != 0x80001000)
+               psxCpu->ExecuteBlock(EXEC_CALLER_HLE);
 
-static inline void SaveRegs() {
-       memcpy(regs, psxRegs.GPR.r, 32*4);
-       regs[32] = psxRegs.GPR.n.lo;
-       regs[33] = psxRegs.GPR.n.hi;
-       regs[34] = psxRegs.pc;
+       if (pc0 == 0x80001000)
+               ra = sra;
 }
 
-static inline void LoadRegs() {
-       memcpy(psxRegs.GPR.r, regs, 32*4);
-       psxRegs.GPR.n.lo = regs[32];
-       psxRegs.GPR.n.hi = regs[33];
+static inline void DeliverEvent(u32 ev, u32 spec) {
+       if (EventCB[ev][spec].status != EvStACTIVE) return;
+
+//     EventCB[ev][spec].status = EvStALREADY;
+       if (EventCB[ev][spec].mode == EvMdINTR) {
+               softCall(EventCB[ev][spec].fhandler);
+       } else EventCB[ev][spec].status = EvStALREADY;
 }
 
 /*                                           *
@@ -346,6 +407,7 @@ static inline void LoadRegs() {
        ptr = Mcd##mcd##Data + offset; \
        memcpy(ptr, Ra1, length); \
        FDesc[1 + mcd].offset += length; \
+       SaveMcd(Config.Mcd##mcd, Mcd##mcd##Data, offset, length); \
        if (FDesc[1 + mcd].mode & 0x8000) { \
        DeliverEvent(0x11, 0x2); /* 0xf0000011, 0x0004 */ \
        DeliverEvent(0x81, 0x2); /* 0xf4000001, 0x0004 */ \
@@ -353,6 +415,10 @@ static inline void LoadRegs() {
        else v0 = length; \
 }
 
+#ifndef PSXBIOS_LOG
+//#define PSXBIOS_LOG printf
+#define PSXBIOS_LOG(...)
+#endif
 
 /* Internally redirects to "FileRead(fd,tempbuf,1)".*/
 /* For some strange reason, the returned character is sign-expanded; */
@@ -367,7 +433,7 @@ void psxBios_getc(void) // 0x03, 0x35
 #endif
        v0 = -1;
 
-       if (pa1) {
+       if (pa1 != INVALID_PTR) {
                switch (a0) {
                        case 2: buread(pa1, 1, 1); break;
                        case 3: buread(pa1, 2, 1); break;
@@ -386,7 +452,7 @@ void psxBios_putc(void) // 0x09, 0x3B
        PSXBIOS_LOG("psxBios_%s\n", biosA0n[0x09]);
 #endif
        v0 = -1;
-       if (!pa1) {
+       if (pa1 == INVALID_PTR) {
                pc0 = ra;
                return;
        }
@@ -471,40 +537,46 @@ void psxBios_atol() { // 0x11
        psxBios_atoi();
 }
 
-void psxBios_setjmp() { // 0x13
-       u32 *jmp_buf = (u32 *)Ra0;
+struct jmp_buf_ {
+       u32 ra_, sp_, fp_;
+       u32 s[8];
+       u32 gp_;
+};
+
+static void psxBios_setjmp() { // 0x13
+       struct jmp_buf_ *jmp_buf = castRam32ptr(a0);
        int i;
 
-#ifdef PSXBIOS_LOG
-       PSXBIOS_LOG("psxBios_%s\n", biosA0n[0x13]);
-#endif
+       PSXBIOS_LOG("psxBios_%s %x\n", biosA0n[0x13], a0);
 
-       jmp_buf[0] = ra;
-       jmp_buf[1] = sp;
-       jmp_buf[2] = fp;
+       jmp_buf->ra_ = SWAP32(ra);
+       jmp_buf->sp_ = SWAP32(sp);
+       jmp_buf->fp_ = SWAP32(fp);
        for (i = 0; i < 8; i++) // s0-s7
-               jmp_buf[3 + i] = psxRegs.GPR.r[16 + i];
-       jmp_buf[11] = gp;
+               jmp_buf->s[i] = SWAP32(psxRegs.GPR.r[16 + i]);
+       jmp_buf->gp_ = SWAP32(gp);
 
-       v0 = 0; pc0 = ra;
+       mips_return_c(0, 15);
 }
 
-void psxBios_longjmp() { // 0x14
-       u32 *jmp_buf = (u32 *)Ra0;
+static void longjmp_load(const struct jmp_buf_ *jmp_buf)
+{
        int i;
 
-#ifdef PSXBIOS_LOG
-       PSXBIOS_LOG("psxBios_%s\n", biosA0n[0x14]);
-#endif
-
-       ra = jmp_buf[0]; /* ra */
-       sp = jmp_buf[1]; /* sp */
-       fp = jmp_buf[2]; /* fp */
+       ra = SWAP32(jmp_buf->ra_);
+       sp = SWAP32(jmp_buf->sp_);
+       fp = SWAP32(jmp_buf->fp_);
        for (i = 0; i < 8; i++) // s0-s7
-               psxRegs.GPR.r[16 + i] = jmp_buf[3 + i];
-       gp = jmp_buf[11]; /* gp */
+               psxRegs.GPR.r[16 + i] = SWAP32(jmp_buf->s[i]);
+       gp = SWAP32(jmp_buf->gp_);;
+}
+
+void psxBios_longjmp() { // 0x14
+       struct jmp_buf_ *jmp_buf = castRam32ptr(a0);
 
-       v0 = a1; pc0 = ra;
+       PSXBIOS_LOG("psxBios_%s\n", biosA0n[0x14]);
+       longjmp_load(jmp_buf);
+       mips_return_c(a1, 15);
 }
 
 void psxBios_strcat() { // 0x15
@@ -695,7 +767,7 @@ void psxBios_index() { // 0x1c
                pc0 = ra;
                return;
        }
-       
+
        do {
                if (*p == a1) {
                        v0 = a0 + (p - (char *)Ra0);
@@ -924,7 +996,7 @@ void psxBios_memcmp() { // 0x2d
 
 void psxBios_memchr() { // 0x2e
        char *p = (char *)Ra0;
-       
+
        if (a0 == 0 || a2 > 0x7FFFFFFF)
        {
                pc0 = ra;
@@ -961,7 +1033,7 @@ static inline int qscmp(char *a, char *b) {
        a0 = sa0 + (a - (char *)PSXM(sa0));
        a1 = sa0 + (b - (char *)PSXM(sa0));
 
-       softCall2(qscmpfunc);
+       softCall(qscmpfunc);
 
        a0 = sa0;
        return (s32)v0;
@@ -1060,7 +1132,7 @@ void psxBios_qsort() { // 0x31
 }
 
 void psxBios_malloc() { // 0x33
-       unsigned int *chunk, *newchunk = NULL;
+       u32 *chunk, *newchunk = NULL;
        unsigned int dsize = 0, csize, cstat;
        int colflag;
 #ifdef PSXBIOS_LOG
@@ -1246,7 +1318,7 @@ void psxBios_getchar() { //0x3b
        v0 = getchar(); pc0 = ra;
 }
 
-void psxBios_printf() { // 0x3f
+static void psxBios_printf_psxout() { // 0x3f
        char tmp[1024];
        char tmp2[1024];
        u32 save[4];
@@ -1255,7 +1327,7 @@ void psxBios_printf() { // 0x3f
        void *psp;
 
        psp = PSXM(sp);
-       if (psp) {
+       if (psp != INVALID_PTR) {
                memcpy(save, psp, 4 * 4);
                psxMu32ref(sp) = SWAP32((u32)a0);
                psxMu32ref(sp + 4) = SWAP32((u32)a1);
@@ -1311,11 +1383,14 @@ _start:
        }
        *ptmp = 0;
 
-       if (psp)
+       if (psp != INVALID_PTR)
                memcpy(psp, save, 4 * 4);
 
        SysPrintf("%s", tmp);
+}
 
+void psxBios_printf() { // 0x3f
+       psxBios_printf_psxout();
        pc0 = ra;
 }
 
@@ -1339,6 +1414,14 @@ void psxBios_format() { // 0x41
        pc0 = ra;
 }
 
+static void psxBios_SystemErrorUnresolvedException() {
+       if (loadRam32(0xfffc) != 0x12345678) { // prevent log flood
+               SysPrintf("psxBios_%s\n", biosA0n[0x40]);
+               storeRam32(0xfffc, 0x12345678);
+       }
+       mips_return_void_c(1000);
+}
+
 /*
  *     long Load(char *name, struct EXEC *header);
  */
@@ -1400,43 +1483,44 @@ void psxBios_FlushCache() { // 44
 #ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s\n", biosA0n[0x44]);
 #endif
-
+       psxCpu->Notify(R3000ACPU_NOTIFY_CACHE_ISOLATED, NULL);
+       psxCpu->Notify(R3000ACPU_NOTIFY_CACHE_UNISOLATED, NULL);
        pc0 = ra;
 }
 
 void psxBios_GPU_dw() { // 0x46
        int size;
-       s32 *ptr;
+       u32 *ptr;
 
 #ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s\n", biosA0n[0x46]);
 #endif
 
        GPU_writeData(0xa0000000);
-       GPU_writeData((a1<<16)|(a0&0xffff));
-       GPU_writeData((a3<<16)|(a2&0xffff));
-       size = (a2*a3+1)/2;
-       ptr = (s32*)PSXM(Rsp[4]);  //that is correct?
-       do {
-               GPU_writeData(SWAP32(*ptr));
-               ptr++;
-       } while(--size);
+       GPU_writeData((a1<<0x10)|(a0&0xffff));
+       GPU_writeData((a3<<0x10)|(a2&0xffff));
+       size = (a2*a3)/2;
+       ptr = (u32*)PSXM(Rsp[4]);  //that is correct?
+       while(size--)
+       {
+               GPU_writeData(SWAPu32(*ptr++));
+       } 
 
        pc0 = ra;
-}  
+}
 
 void psxBios_mem2vram() { // 0x47
        int size;
-
+       gpuSyncPluginSR();
        GPU_writeData(0xa0000000);
-       GPU_writeData((a1<<16)|(a0&0xffff));
-       GPU_writeData((a3<<16)|(a2&0xffff));
-       size = (a2*a3+1)/2;
+       GPU_writeData((a1<<0x10)|(a0&0xffff));
+       GPU_writeData((a3<<0x10)|(a2&0xffff));
+       size = ((((a2 * a3) / 2) >> 4) << 16);
        GPU_writeStatus(0x04000002);
        psxHwWrite32(0x1f8010f4,0);
        psxHwWrite32(0x1f8010f0,psxHwRead32(0x1f8010f0)|0x800);
        psxHwWrite32(0x1f8010a0,Rsp[4]);//might have a buggy...
-       psxHwWrite32(0x1f8010a4,((size/16)<<16)|16);
+       psxHwWrite32(0x1f8010a4, size | 0x10);
        psxHwWrite32(0x1f8010a8,0x01000201);
 
        pc0 = ra;
@@ -1449,22 +1533,26 @@ void psxBios_SendGPU() { // 0x48
 }
 
 void psxBios_GPU_cw() { // 0x49
+       gpuSyncPluginSR();
        GPU_writeData(a0);
+       v0 = HW_GPU_STATUS;
        pc0 = ra;
 }
 
 void psxBios_GPU_cwb() { // 0x4a
-       s32 *ptr = (s32*)Ra0;
+       u32 *ptr = (u32*)Ra0;
        int size = a1;
-       while(size--) {
-               GPU_writeData(SWAP32(*ptr));
-               ptr++;
+       gpuSyncPluginSR();
+       while(size--)
+       {
+               GPU_writeData(SWAPu32(*ptr++));
        }
 
        pc0 = ra;
 }
    
 void psxBios_GPU_SendPackets() { //4b: 
+       gpuSyncPluginSR();
        GPU_writeStatus(0x04000002);
        psxHwWrite32(0x1f8010f4,0);
        psxHwWrite32(0x1f8010f0,psxHwRead32(0x1f8010f0)|0x800);
@@ -1499,7 +1587,7 @@ void psxBios_LoadExec() { // 51
 #endif
        s_addr = a1; s_size = a2;
 
-       a1 = 0xf000;    
+       a1 = 0xf000;
        psxBios_Load();
 
        header->S_addr = s_addr;
@@ -1528,12 +1616,36 @@ void psxBios__96_init() { // 71
        pc0 = ra;
 }
 
-void psxBios__96_remove() { // 72
-#ifdef PSXBIOS_LOG
+static void psxBios_SysDeqIntRP_();
+
+static void psxBios_DequeueCdIntr_() {
+       a0 = 0; a1 = 0x91d0;
+       psxBios_SysDeqIntRP_();
+       a0 = 0; a1 = 0x91e0;
+       psxBios_SysDeqIntRP_();
+       use_cycles(16);
+}
+
+static void psxBios_DequeueCdIntr() { // a3
+       PSXBIOS_LOG("psxBios_%s\n", biosA0n[0xa3]);
+       psxBios_DequeueCdIntr_();
+}
+
+static void psxBios_CdRemove() { // 56, 72
        PSXBIOS_LOG("psxBios_%s\n", biosA0n[0x72]);
-#endif
 
-       pc0 = ra;
+       // CloseEvent 0xf1000000
+       // CloseEvent 0xf1000001
+       // CloseEvent 0xf1000002
+       // CloseEvent 0xf1000003
+       // CloseEvent 0xf1000004
+       psxBios_DequeueCdIntr_();
+
+       // EnterCriticalSection - should be done at the beginning,
+       // but this way is much easier to implement
+       a0 = 1;
+       pc0 = A_SYSCALL;
+       use_cycles(30);
 }
 
 void psxBios_SetMem() { // 9f
@@ -1554,7 +1666,7 @@ void psxBios_SetMem() { // 9f
                        psxHu32ref(0x1060) = SWAP32(new | 0x300);
                        psxMu32ref(0x060) = a0;
                        SysPrintf("Change effective memory : %d MBytes\n",a0);
-       
+
                default:
                        SysPrintf("Effective memory must be 2/8 MBytes\n");
                break;
@@ -1574,16 +1686,16 @@ void psxBios__card_info() { // ab
 #ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s: %x\n", biosA0n[0xab], a0);
 #endif
-       u32 ret;
+       u32 ret, port;
        card_active_chan = a0;
-
-       switch (card_active_chan) 
-       {
-       case 0x00: case 0x01: case 0x02: case 0x03:
-               ret = Config.Mcd1[0] ? 0x2 : 0x8;
-               break;
-       case 0x10: case 0x11: case 0x12: case 0x13:
-               ret = Config.Mcd2[0] ? 0x2 : 0x8;
+       port = card_active_chan >> 4;
+
+       switch (port) {
+       case 0x0:
+       case 0x1:
+               ret = 0x2;
+               if (McdDisable[port & 1])
+                       ret = 0x8;
                break;
        default:
 #ifdef PSXBIOS_LOG
@@ -1592,8 +1704,12 @@ void psxBios__card_info() { // ab
                ret = 0x11;
                break;
        }
-       
-       DeliverEvent(0x11, 0x2); // 0xf4000001, 0x0004
+
+       if (McdDisable[0] && McdDisable[1])
+               ret = 0x8;
+
+       DeliverEvent(0x11, 0x2); // 0xf0000011, 0x0004
+//     DeliverEvent(0x81, 0x2); // 0xf4000001, 0x0004
        DeliverEvent(0x81, ret); // 0xf4000001, 0x0004
        v0 = 1; pc0 = ra;
 }
@@ -1682,14 +1798,14 @@ void psxBios_ResetRCnt() { // 06
 }
 
 
-/* gets ev for use with Event */
+/* gets ev for use with EventCB */
 #define GetEv() \
        ev = (a0 >> 24) & 0xf; \
        if (ev == 0xf) ev = 0x5; \
        ev*= 32; \
        ev+= a0&0x1f;
 
-/* gets spec for use with Event */
+/* gets spec for use with EventCB */
 #define GetSpec() \
        spec = 0; \
        switch (a1) { \
@@ -1727,9 +1843,9 @@ void psxBios_OpenEvent() { // 08
        PSXBIOS_LOG("psxBios_%s %x,%x (class:%x, spec:%x, mode:%x, func:%x)\n", biosB0n[0x08], ev, spec, a0, a1, a2, a3);
 #endif
 
-       Event[ev][spec].status = EvStWAIT;
-       Event[ev][spec].mode = a2;
-       Event[ev][spec].fhandler = a3;
+       EventCB[ev][spec].status = EvStWAIT;
+       EventCB[ev][spec].mode = a2;
+       EventCB[ev][spec].fhandler = a3;
 
        v0 = ev | (spec << 8);
        pc0 = ra;
@@ -1745,7 +1861,7 @@ void psxBios_CloseEvent() { // 09
        PSXBIOS_LOG("psxBios_%s %x,%x\n", biosB0n[0x09], ev, spec);
 #endif
 
-       Event[ev][spec].status = EvStUNUSED;
+       EventCB[ev][spec].status = EvStUNUSED;
 
        v0 = 1; pc0 = ra;
 }
@@ -1758,17 +1874,17 @@ void psxBios_WaitEvent() { // 0a
 #ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s %x,%x\n", biosB0n[0x0a], ev, spec);
 #endif
-       if (Event[ev][spec].status == EvStUNUSED)
+       if (EventCB[ev][spec].status == EvStUNUSED)
        {
                v0 = 0;
-               pc0 = ra;       
+               pc0 = ra;
                return;
        }
 
-       if (Event[ev][spec].status == EvStALREADY) 
+       if (EventCB[ev][spec].status == EvStALREADY)
        {
                /* Callback events (mode=EvMdINTR) do never set the ready flag (and thus WaitEvent would hang forever). */
-               if (!(Event[ev][spec].mode == EvMdINTR)) Event[ev][spec].status = EvStACTIVE;
+               if (!(EventCB[ev][spec].mode == EvMdINTR)) EventCB[ev][spec].status = EvStACTIVE;
                v0 = 1;
                pc0 = ra;
                return;
@@ -1784,12 +1900,12 @@ void psxBios_TestEvent() { // 0b
        ev   = a0 & 0xff;
        spec = (a0 >> 8) & 0xff;
 
-       if (Event[ev][spec].status == EvStALREADY) 
+       if (EventCB[ev][spec].status == EvStALREADY)
        {
-               if (!(Event[ev][spec].mode == EvMdINTR)) Event[ev][spec].status = EvStACTIVE;
+               if (!(EventCB[ev][spec].mode == EvMdINTR)) EventCB[ev][spec].status = EvStACTIVE;
                v0 = 1;
-       } 
-       else 
+       }
+       else
        {
                v0 = 0;
        }
@@ -1811,7 +1927,7 @@ void psxBios_EnableEvent() { // 0c
        PSXBIOS_LOG("psxBios_%s %x,%x\n", biosB0n[0x0c], ev, spec);
 #endif
 
-       Event[ev][spec].status = EvStACTIVE;
+       EventCB[ev][spec].status = EvStACTIVE;
 
        v0 = 1; pc0 = ra;
 }
@@ -1826,7 +1942,7 @@ void psxBios_DisableEvent() { // 0d
        PSXBIOS_LOG("psxBios_%s %x,%x\n", biosB0n[0x0d], ev, spec);
 #endif
 
-       Event[ev][spec].status = EvStWAIT;
+       EventCB[ev][spec].status = EvStWAIT;
 
        v0 = 1; pc0 = ra;
 }
@@ -1836,33 +1952,34 @@ void psxBios_DisableEvent() { // 0d
  */
 
 void psxBios_OpenTh() { // 0e
+       TCB *tcb = loadRam32ptr(A_TT_TCB);
+       u32 limit = loadRam32(A_TT_TCB + 4) / 0xc0u;
        int th;
 
-       for (th=1; th<8; th++)
+       for (th = 1; th < limit; th++)
        {
-               if (Thread[th].status == 0) break;
+               if (tcb[th].status != SWAP32(0x4000)) break;
 
        }
-       if (th == 8) {
+       if (th == limit) {
                // Feb 2019 - Added out-of-bounds fix caught by cppcheck:
                // When no free TCB is found, return 0xffffffff according to Nocash doc.
 #ifdef PSXBIOS_LOG
                PSXBIOS_LOG("\t%s() WARNING! No Free TCBs found!\n", __func__);
 #endif
-               v0 = 0xffffffff;
-               pc0 = ra;
+               mips_return_c(0xffffffff, 20);
                return;
        }
-#ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s: %x\n", biosB0n[0x0e], th);
-#endif
 
-       Thread[th].status = 1;
-       Thread[th].func    = a0;
-       Thread[th].reg[29] = a1;
-       Thread[th].reg[28] = a2;
+       tcb[th].status  = SWAP32(0x4000);
+       tcb[th].mode    = SWAP32(0x1000);
+       tcb[th].epc     = SWAP32(a0);
+       tcb[th].reg[30] = SWAP32(a1); // fp
+       tcb[th].reg[29] = SWAP32(a1); // sp
+       tcb[th].reg[28] = SWAP32(a2); // gp
 
-       v0 = th; pc0 = ra;
+       mips_return_c(0xff000000 + th, 34);
 }
 
 /*
@@ -1870,15 +1987,17 @@ void psxBios_OpenTh() { // 0e
  */
 
 void psxBios_CloseTh() { // 0f
-       int th = a0 & 0xff;
+       TCB *tcb = loadRam32ptr(A_TT_TCB);
+       u32 limit = loadRam32(A_TT_TCB + 4) / 0xc0u;
+       u32 th = a0 & 0xff;
 
 #ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s: %x\n", biosB0n[0x0f], th);
 #endif
        /* The return value is always 1 (even if the handle was already closed). */
        v0 = 1;
-       if (Thread[th].status != 0) {
-               Thread[th].status = 0;
+       if (th < limit && tcb[th].status == SWAP32(0x4000)) {
+               tcb[th].status = SWAP32(0x1000);
        }
 
        pc0 = ra;
@@ -1889,27 +2008,18 @@ void psxBios_CloseTh() { // 0f
  */
 
 void psxBios_ChangeTh() { // 10
-       int th = a0 & 0xff;
+       u32 tcbBase = loadRam32(A_TT_TCB);
+       u32 th = a0 & 0xffff;
 
 #ifdef PSXBIOS_LOG
 //     PSXBIOS_LOG("psxBios_%s: %x\n", biosB0n[0x10], th);
 #endif
-       /* The return value is always 1. */
-       v0 = 1;
-       if (Thread[th].status == 0 || CurThread == th) {
-               pc0 = ra;
-       } else {
-               if (Thread[CurThread].status == 2) {
-                       Thread[CurThread].status = 1;
-                       Thread[CurThread].func = ra;
-                       memcpy(Thread[CurThread].reg, psxRegs.GPR.r, 32*4);
-               }
-
-               memcpy(psxRegs.GPR.r, Thread[th].reg, 32*4);
-               pc0 = Thread[th].func;
-               Thread[th].status = 2;
-               CurThread = th;
-       }
+       // without doing any argument checks, just issue a syscall
+       // (like the real bios does)
+       a0 = 3;
+       a1 = tcbBase + th * sizeof(TCB);
+       pc0 = A_SYSCALL;
+       use_cycles(15);
 }
 
 void psxBios_InitPAD() { // 0x12
@@ -1931,7 +2041,7 @@ void psxBios_StartPAD() { // 13
 #endif
        pad_stopped = 0;
        psxHwWrite16(0x1f801074, (unsigned short)(psxHwRead16(0x1f801074) | 0x1));
-       psxRegs.CP0.n.Status |= 0x401;
+       psxRegs.CP0.n.SR |= 0x401;
        pc0 = ra;
 }
 
@@ -1958,7 +2068,7 @@ void psxBios_PAD_init() { // 15
        psxHwWrite16(0x1f801074, (u16)(psxHwRead16(0x1f801074) | 0x1));
        pad_buf = (int *)Ra1;
        *pad_buf = -1;
-       psxRegs.CP0.n.Status |= 0x401;
+       psxRegs.CP0.n.SR |= 0x401;
        v0 = 2;
        pc0 = ra;
 }
@@ -1971,33 +2081,37 @@ void psxBios_PAD_dr() { // 16
        v0 = -1; pc0 = ra;
 }
 
-void psxBios_ReturnFromException() { // 17
-       LoadRegs();
+static void psxBios_ReturnFromException() { // 17
+       u32 tcbPtr = loadRam32(A_TT_PCB);
+       const TCB *tcb = loadRam32ptr(tcbPtr);
+       int i;
+
+       for (i = 1; i < 32; i++)
+               psxRegs.GPR.r[i] = SWAP32(tcb->reg[i]);
+       psxRegs.GPR.n.lo = SWAP32(tcb->lo);
+       psxRegs.GPR.n.hi = SWAP32(tcb->hi);
+       psxRegs.CP0.n.SR = SWAP32(tcb->sr);
 
-       pc0 = psxRegs.CP0.n.EPC;
-       k0 = interrupt_r26;
-       if (psxRegs.CP0.n.Cause & 0x80000000) pc0 += 4;
+       //printf("%s %08x->%08x %u\n", __func__, pc0, tcb->epc, psxRegs.cycle);
+       pc0 = k0 = SWAP32(tcb->epc);
 
-       psxRegs.CP0.n.Status = (psxRegs.CP0.n.Status & 0xfffffff0) |
-                                                 ((psxRegs.CP0.n.Status & 0x3c) >> 2);
+       psxRegs.CP0.n.SR = (psxRegs.CP0.n.SR & ~0x0f) | ((psxRegs.CP0.n.SR & 0x3c) >> 2);
+       use_cycles(53);
+       psxBranchTest();
 }
 
 void psxBios_ResetEntryInt() { // 18
-#ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s\n", biosB0n[0x18]);
-#endif
 
-       jmp_int = NULL;
-       pc0 = ra;
+       storeRam32(A_EEXIT_PTR, A_EEXIT_DEF);
+       mips_return_void_c(5);
 }
 
 void psxBios_HookEntryInt() { // 19
-#ifdef PSXBIOS_LOG
-       PSXBIOS_LOG("psxBios_%s\n", biosB0n[0x19]);
-#endif
+       PSXBIOS_LOG("psxBios_%s %x\n", biosB0n[0x19], a0);
 
-       jmp_int = (u32*)Ra0;
-       pc0 = ra;
+       storeRam32(A_EEXIT_PTR, a0);
+       mips_return_void_c(3);
 }
 
 void psxBios_UnDeliverEvent() { // 0x20
@@ -2011,26 +2125,27 @@ void psxBios_UnDeliverEvent() { // 0x20
        PSXBIOS_LOG("psxBios_%s %x,%x\n", biosB0n[0x20], ev, spec);
 #endif
 
-       if (Event[ev][spec].status == EvStALREADY &&
-               Event[ev][spec].mode == EvMdNOINTR)
-               Event[ev][spec].status = EvStACTIVE;
+       if (EventCB[ev][spec].status == EvStALREADY &&
+               EventCB[ev][spec].mode == EvMdNOINTR)
+               EventCB[ev][spec].status = EvStACTIVE;
 
        pc0 = ra;
 }
 
 char ffile[64], *pfile;
 int nfile;
-static void buopen(int mcd, u8 *ptr, u8 *cfg)
+
+static void buopen(int mcd, char *ptr, char *cfg)
 {
        int i;
-       u8 *fptr = ptr;
+       char *mcd_data = ptr;
 
        strcpy(FDesc[1 + mcd].name, Ra0+5);
        FDesc[1 + mcd].offset = 0;
        FDesc[1 + mcd].mode   = a1;
 
        for (i=1; i<16; i++) {
-               fptr += 128;
+               const char *fptr = mcd_data + 128 * i;
                if ((*fptr & 0xF0) != 0x50) continue;
                if (strcmp(FDesc[1 + mcd].name, fptr+0xa)) continue;
                FDesc[1 + mcd].mcfile = i;
@@ -2039,12 +2154,11 @@ static void buopen(int mcd, u8 *ptr, u8 *cfg)
                break;
        }
        if (a1 & 0x200 && v0 == -1) { /* FCREAT */
-               fptr = ptr;
                for (i=1; i<16; i++) {
                        int j, xor, nblk = a1 >> 16;
-                       u8 *pptr, *fptr2;
+                       char *pptr, *fptr2;
+                       char *fptr = mcd_data + 128 * i;
 
-                       fptr += 128;
                        if ((*fptr & 0xF0) != 0xa0) continue;
 
                        FDesc[1 + mcd].mcfile = i;
@@ -2059,7 +2173,7 @@ static void buopen(int mcd, u8 *ptr, u8 *cfg)
                                int k;
                                for(i++; i<16; i++) {
                                        fptr2 += 128;
-                                       
+
                                        memset(fptr2, 0, 128);
                                        fptr2[0] = j < nblk ? 0x52 : 0x53;
                                        pptr[8] = i - 1;
@@ -2089,8 +2203,6 @@ static void buopen(int mcd, u8 *ptr, u8 *cfg)
  */
 
 void psxBios_open() { // 0x32
-       int i;
-       char *ptr;
        void *pa0 = Ra0;
 
 #ifdef PSXBIOS_LOG
@@ -2099,7 +2211,7 @@ void psxBios_open() { // 0x32
 
        v0 = -1;
 
-       if (pa0) {
+       if (pa0 != INVALID_PTR) {
                if (!strncmp(pa0, "bu00", 4)) {
                        buopen(1, Mcd1Data, Config.Mcd1);
                }
@@ -2153,13 +2265,13 @@ void psxBios_read() { // 0x34
 
        v0 = -1;
 
-       if (pa1) {
+       if (pa1 != INVALID_PTR) {
                switch (a0) {
                        case 2: buread(pa1, 1, a2); break;
                        case 3: buread(pa1, 2, a2); break;
                }
        }
-               
+
        pc0 = ra;
 }
 
@@ -2176,7 +2288,7 @@ void psxBios_write() { // 0x35/0x03
 #endif
 
        v0 = -1;
-       if (!pa1) {
+       if (pa1 == INVALID_PTR) {
                pc0 = ra;
                return;
        }
@@ -2199,6 +2311,25 @@ void psxBios_write() { // 0x35/0x03
        pc0 = ra;
 }
 
+static void psxBios_write_psxout() {
+       if (a0 == 1) { // stdout
+               const char *ptr = Ra1;
+               int len = a2;
+
+               if (ptr != INVALID_PTR)
+                       while (len-- > 0)
+                               SysPrintf("%c", *ptr++);
+       }
+}
+
+static void psxBios_putchar_psxout() { // 3d
+       SysPrintf("%c", (char)a0);
+}
+
+static void psxBios_puts_psxout() { // 3e/3f
+       SysPrintf("%s", Ra0);
+}
+
 /*
  *     int close(int fd);
  */
@@ -2225,7 +2356,7 @@ void psxBios_puts() { // 3e/3f
 
 /* To avoid any issues with different behaviour when using the libc's own strlen instead.
  * We want to mimic the PSX's behaviour in this case for bufile. */
-static size_t strlen_internal(char* p) 
+static size_t strlen_internal(char* p)
 {
        size_t size_of_array = 0;
        while (*p++) size_of_array++;
@@ -2266,7 +2397,7 @@ static size_t strlen_internal(char* p)
 /*
  *     struct DIRENTRY* firstfile(char *name,struct DIRENTRY *dir);
  */
+
 void psxBios_firstfile() { // 42
        struct DIRENTRY *dir = (struct DIRENTRY *)Ra1;
        void *pa0 = Ra0;
@@ -2280,7 +2411,7 @@ void psxBios_firstfile() { // 42
 
        v0 = 0;
 
-       if (pa0) {
+       if (pa0 != INVALID_PTR) {
                strcpy(ffile, pa0);
                pfile = ffile+5;
                nfile = 0;
@@ -2358,7 +2489,7 @@ void psxBios_rename() { // 44
 
        v0 = 0;
 
-       if (pa0 && pa1) {
+       if (pa0 != INVALID_PTR && pa1 != INVALID_PTR) {
                if (!strncmp(pa0, "bu00", 4) && !strncmp(pa1, "bu00", 4)) {
                        burename(1);
                }
@@ -2400,7 +2531,7 @@ void psxBios_delete() { // 45
 
        v0 = 0;
 
-       if (pa0) {
+       if (pa0 != INVALID_PTR) {
                if (!strncmp(pa0, "bu00", 4)) {
                        budelete(1);
                }
@@ -2463,7 +2594,7 @@ void psxBios__card_write() { // 0x4e
        card_active_chan = a0;
        port = a0 >> 4;
 
-       if (pa2) {
+       if (pa2 != INVALID_PTR) {
                if (port == 0) {
                        memcpy(Mcd1Data + a1 * 128, pa2, 128);
                        SaveMcd(Config.Mcd1, Mcd1Data, a1 * 128, 128);
@@ -2499,7 +2630,7 @@ void psxBios__card_read() { // 0x4f
        card_active_chan = a0;
        port = a0 >> 4;
 
-       if (pa2) {
+       if (pa2 != INVALID_PTR) {
                if (port == 0) {
                        memcpy(pa2, Mcd1Data + a1 * 128, 128);
                } else {
@@ -2523,7 +2654,8 @@ void psxBios__new_card() { // 0x50
 
 /* According to a user, this allows Final Fantasy Tactics to save/load properly */
 void psxBios__get_error(void) // 55
-{ 
+{
+       PSXBIOS_LOG("psxBios_%s\n", biosB0n[0x55]);
        v0 = 0;
        pc0 = ra;
 }
@@ -2531,6 +2663,7 @@ void psxBios__get_error(void) // 55
 void psxBios_Krom2RawAdd() { // 0x51
        int i = 0;
 
+       PSXBIOS_LOG("psxBios_%s\n", biosB0n[0x51]);
        const u32 table_8140[][2] = {
                {0x8140, 0x0000}, {0x8180, 0x0762}, {0x81ad, 0x0cc6}, {0x81b8, 0x0ca8},
                {0x81c0, 0x0f00}, {0x81c8, 0x0d98}, {0x81cf, 0x10c2}, {0x81da, 0x0e6a},
@@ -2572,19 +2705,17 @@ void psxBios_Krom2RawAdd() { // 0x51
 }
 
 void psxBios_GetC0Table() { // 56
-#ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s\n", biosB0n[0x56]);
-#endif
+       log_unhandled("GetC0Table @%08x\n", ra);
 
-       v0 = 0x674; pc0 = ra;
+       mips_return_c(A_C0_TABLE, 3);
 }
 
 void psxBios_GetB0Table() { // 57
-#ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s\n", biosB0n[0x57]);
-#endif
+       log_unhandled("GetB0Table @%08x\n", ra);
 
-       v0 = 0x874; pc0 = ra;
+       mips_return_c(A_B0_TABLE, 3);
 }
 
 void psxBios__card_chan() { // 0x58
@@ -2599,7 +2730,7 @@ void psxBios__card_chan() { // 0x58
 void psxBios_ChangeClearPad() { // 5b
 #ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s: %x\n", biosB0n[0x5b], a0);
-#endif 
+#endif
 
        pc0 = ra;
 }
@@ -2629,73 +2760,169 @@ void psxBios__card_wait() { // 5d
  */
 
 void psxBios_SysEnqIntRP() { // 02
-#ifdef PSXBIOS_LOG
-       PSXBIOS_LOG("psxBios_%s: %x\n", biosC0n[0x02] ,a0);
-#endif
-
-       SysIntRP[a0] = a1;
+       u32 old, base = loadRam32(A_TT_ExCB);
+       PSXBIOS_LOG("psxBios_%s %x %x\n", biosC0n[0x02], a0, a1);
 
-       v0 = 0; pc0 = ra;
+       old = loadRam32(base + (a0 << 3));
+       storeRam32(base + (a0 << 3), a1);
+       storeRam32(a1, old);
+       mips_return_c(0, 9);
 }
 
 /*
  * int SysDeqIntRP(int index , long *queue);
  */
 
-void psxBios_SysDeqIntRP() { // 03
-#ifdef PSXBIOS_LOG
-       PSXBIOS_LOG("psxBios_%s: %x\n", biosC0n[0x03], a0);
-#endif
+static void psxBios_SysDeqIntRP_() { // 03
+       u32 ptr, next, base = loadRam32(A_TT_ExCB);
+       u32 lim = 0, ret = 0;
+
+       // as in original: no arg checks of any kind, bug if a1 == 0
+       ptr = loadRam32(base + (a0 << 3));
+       while (ptr) {
+               next = loadRam32(ptr);
+               if (ptr == a1) {
+                       storeRam32(base + (a0 << 3), next);
+                       ret = ptr;
+                       use_cycles(6);
+                       break;
+               }
+               while (next && next != a1 && lim++ < 100) {
+                       ptr = next;
+                       next = loadRam32(ptr);
+                       use_cycles(8);
+               }
+               if (next == a1) {
+                       next = loadRam32(next);
+                       storeRam32(ptr, next);
+                       ret = ptr;
+                       use_cycles(6);
+               }
+               break;
+       }
+       if (lim == 100)
+               PSXBIOS_LOG("bad chain %u %x\n", a0, base);
 
-       SysIntRP[a0] = 0;
+       mips_return_c(ret, 12);
+}
 
-       v0 = 0; pc0 = ra;
+static void psxBios_SysDeqIntRP() { // 03
+       PSXBIOS_LOG("psxBios_%s %x %x\n", biosC0n[0x03], a0, a1);
+       psxBios_SysDeqIntRP_();
 }
 
 void psxBios_ChangeClearRCnt() { // 0a
-       u32 *ptr;
+       u32 ret;
 
-#ifdef PSXBIOS_LOG
        PSXBIOS_LOG("psxBios_%s: %x, %x\n", biosC0n[0x0a], a0, a1);
-#endif
-
-       ptr = (u32*)PSXM((a0 << 2) + 0x8600);
-       v0 = *ptr;
-       *ptr = a1;
 
-//     psxRegs.CP0.n.Status|= 0x404;
-       pc0 = ra;
+       ret = loadRam32(A_RCNT_VBL_ACK + (a0 << 2));
+       storeRam32(A_RCNT_VBL_ACK + (a0 << 2), a1);
+       mips_return_c(ret, 8);
 }
 
-void psxBios_dummy() { 
-#ifdef PSXBIOS_LOG
-       PSXBIOS_LOG("unk %x call: %x\n", pc0 & 0x1fffff, t1);
-#endif
-       pc0 = ra; 
+void psxBios_dummy() {
+       u32 pc = (pc0 & 0x1fffff) - 4;
+       char **ntab = pc == 0xa0 ? biosA0n : pc == 0xb0 ? biosB0n
+               : pc == 0xc0 ? biosC0n : NULL;
+       PSXBIOS_LOG("unk %x call: %x ra=%x (%s)\n",
+               pc, t1, ra, ntab ? ntab[t1 & 0xff] : "???");
+       (void)pc; (void)ntab;
+       mips_return_c(0, 100);
 }
 
 void (*biosA0[256])();
-void (*biosB0[256])();
-void (*biosC0[256])();
+// C0 and B0 overlap (end of C0 is start of B0)
+void (*biosC0[256+128])();
+void (**biosB0)() = biosC0 + 128;
 
 #include "sjisfont.h"
 
+void setup_mips_code()
+{
+       u32 *ptr;
+       ptr = (u32 *)&psxM[A_SYSCALL];
+       ptr[0x00/4] = SWAPu32(0x0000000c); // syscall 0
+       ptr[0x04/4] = SWAPu32(0x03e00008); // jr    $ra
+       ptr[0x08/4] = SWAPu32(0x00000000); // nop
+
+       ptr = (u32 *)&psxM[A_EXCEPTION];
+       memset(ptr, 0, 0xc0);              // nops (to be patched by games sometimes)
+       ptr[0x10/4] = SWAPu32(0x8c1a0108); // lw    $k0, (0x108)   // PCB
+       ptr[0x14/4] = SWAPu32(0x00000000); // nop
+       ptr[0x18/4] = SWAPu32(0x8f5a0000); // lw    $k0, ($k0)     // TCB
+       ptr[0x1c/4] = SWAPu32(0x00000000); // nop
+       ptr[0x20/4] = SWAPu32(0x275a0008); // addiu $k0, $k0, 8    // regs
+       ptr[0x24/4] = SWAPu32(0xaf5f007c); // sw    $ra, 0x7c($k0)
+       ptr[0x28/4] = SWAPu32(0xaf410004); // sw    $at, 0x04($k0)
+       ptr[0x2c/4] = SWAPu32(0xaf420008); // sw    $v0, 0x08($k0)
+       ptr[0x30/4] = SWAPu32(0xaf43000c); // sw    $v1, 0x0c($k0)
+
+       ptr[0x60/4] = SWAPu32(0x40037000); // mfc0  $v1, EPC
+       ptr[0x64/4] = SWAPu32(0x40026800); // mfc0  $v0, Cause
+       ptr[0x68/4] = SWAPu32(0x24630004); // addiu $v1, $v1, 4
+       ptr[0x6c/4] = SWAPu32(0xaf430080); // sw    $v1, 0x80($k0)
+
+       ptr[0xb0/4] = HLEOP(hleop_exception);
+}
+
+static const struct {
+       u32 addr;
+       enum hle_op op;
+} chainfns[] = {
+       { 0xbfc050a4, hleop_exc0_0_1 },
+       { 0xbfc04fbc, hleop_exc0_0_2 },
+       { 0xbfc0506c, hleop_exc0_1_1 },
+       { 0xbfc04dec, hleop_exc0_1_2 },
+       {     0x1a00, hleop_exc0_2_2 },
+       {     0x19c8, hleop_exc1_0_1 },
+       {     0x18bc, hleop_exc1_0_2 },
+       {     0x1990, hleop_exc1_1_1 },
+       {     0x1858, hleop_exc1_1_2 },
+       {     0x1958, hleop_exc1_2_1 },
+       {     0x17f4, hleop_exc1_2_2 },
+       {     0x1920, hleop_exc1_3_1 },
+       {     0x1794, hleop_exc1_3_2 },
+       {     0x2458, hleop_exc3_0_2 },
+};
+
+static int chain_hle_op(u32 handler)
+{
+       size_t i;
+
+       for (i = 0; i < sizeof(chainfns) / sizeof(chainfns[0]); i++)
+               if (chainfns[i].addr == handler)
+                       return chainfns[i].op;
+       return hleop_dummy;
+}
+
+static void write_chain(u32 *d, u32 next, u32 handler1, u32 handler2)
+{
+       d[0] = SWAPu32(next);
+       d[1] = SWAPu32(handler1);
+       d[2] = SWAPu32(handler2);
+
+       // install hle traps
+       PSXMu32ref(handler1) = HLEOP(chain_hle_op(handler1));
+       PSXMu32ref(handler2) = HLEOP(chain_hle_op(handler2));
+}
+
 void psxBiosInit() {
        u32 base, size;
-       u32 *ptr
+       u32 *ptr, *ram32;
        int i;
        uLongf len;
 
+       memset(psxM, 0, 0x10000);
        for(i = 0; i < 256; i++) {
                biosA0[i] = NULL;
                biosB0[i] = NULL;
                biosC0[i] = NULL;
        }
-       biosA0[0x3e] = psxBios_puts;
-       biosA0[0x3f] = psxBios_printf;
-
-       biosB0[0x3d] = psxBios_putchar;
-       biosB0[0x3f] = psxBios_puts;
+       biosA0[0x03] = biosB0[0x35] = psxBios_write_psxout;
+       biosA0[0x3c] = biosB0[0x3d] = psxBios_putchar_psxout;
+       biosA0[0x3e] = biosB0[0x3f] = psxBios_puts_psxout;
+       biosA0[0x3f] = psxBios_printf_psxout;
 
        if (!Config.HLE) return;
 
@@ -2765,9 +2992,9 @@ void psxBiosInit() {
        biosA0[0x39] = psxBios_InitHeap;
        //biosA0[0x3a] = psxBios__exit;
        biosA0[0x3b] = psxBios_getchar;
-       biosA0[0x3c] = psxBios_putchar; 
+       biosA0[0x3c] = psxBios_putchar;
        //biosA0[0x3d] = psxBios_gets;
-       //biosA0[0x40] = psxBios_sys_a0_40;
+       biosA0[0x40] = psxBios_SystemErrorUnresolvedException;
        //biosA0[0x41] = psxBios_LoadTest;
        biosA0[0x42] = psxBios_Load;
        biosA0[0x43] = psxBios_Exec;
@@ -2781,7 +3008,7 @@ void psxBiosInit() {
        biosA0[0x4b] = psxBios_GPU_SendPackets;
        biosA0[0x4c] = psxBios_sys_a0_4c;
        biosA0[0x4d] = psxBios_GPU_GetGPUStatus;
-       //biosA0[0x4e] = psxBios_GPU_sync;      
+       //biosA0[0x4e] = psxBios_GPU_sync;
        //biosA0[0x4f] = psxBios_sys_a0_4f;
        //biosA0[0x50] = psxBios_sys_a0_50;
        biosA0[0x51] = psxBios_LoadExec;
@@ -2789,7 +3016,7 @@ void psxBiosInit() {
        //biosA0[0x53] = psxBios_sys_a0_53;
        //biosA0[0x54] = psxBios__96_init_a54;
        //biosA0[0x55] = psxBios__bu_init_a55;
-       //biosA0[0x56] = psxBios__96_remove_a56;
+       biosA0[0x56] = psxBios_CdRemove;
        //biosA0[0x57] = psxBios_sys_a0_57;
        //biosA0[0x58] = psxBios_sys_a0_58;
        //biosA0[0x59] = psxBios_sys_a0_59;
@@ -2817,7 +3044,7 @@ void psxBiosInit() {
        //biosA0[0x6f] = psxBios_dev_card_6f;
        biosA0[0x70] = psxBios__bu_init;
        biosA0[0x71] = psxBios__96_init;
-       biosA0[0x72] = psxBios__96_remove;
+       biosA0[0x72] = psxBios_CdRemove;
        //biosA0[0x73] = psxBios_sys_a0_73;
        //biosA0[0x74] = psxBios_sys_a0_74;
        //biosA0[0x75] = psxBios_sys_a0_75;
@@ -2833,10 +3060,10 @@ void psxBiosInit() {
        //biosA0[0x7f] = psxBios_sys_a0_7f;
        //biosA0[0x80] = psxBios_sys_a0_80;
        //biosA0[0x81] = psxBios_sys_a0_81;
-       //biosA0[0x82] = psxBios_sys_a0_82;             
+       //biosA0[0x82] = psxBios_sys_a0_82;
        //biosA0[0x83] = psxBios_sys_a0_83;
        //biosA0[0x84] = psxBios_sys_a0_84;
-       //biosA0[0x85] = psxBios__96_CdStop;    
+       //biosA0[0x85] = psxBios__96_CdStop;
        //biosA0[0x86] = psxBios_sys_a0_86;
        //biosA0[0x87] = psxBios_sys_a0_87;
        //biosA0[0x88] = psxBios_sys_a0_88;
@@ -2847,10 +3074,10 @@ void psxBiosInit() {
        //biosA0[0x8d] = psxBios_sys_a0_8d;
        //biosA0[0x8e] = psxBios_sys_a0_8e;
        //biosA0[0x8f] = psxBios_sys_a0_8f;
-       //biosA0[0x90] = psxBios_sys_a0_90;
-       //biosA0[0x91] = psxBios_sys_a0_91;
-       //biosA0[0x92] = psxBios_sys_a0_92;
-       //biosA0[0x93] = psxBios_sys_a0_93;
+       biosA0[0x90] = hleExc0_1_2;
+       biosA0[0x91] = hleExc0_0_2;
+       biosA0[0x92] = hleExc0_1_1;
+       biosA0[0x93] = hleExc0_0_1;
        //biosA0[0x94] = psxBios_sys_a0_94;
        //biosA0[0x95] = psxBios_sys_a0_95;
        //biosA0[0x96] = psxBios_AddCDROMDevice;
@@ -2866,7 +3093,7 @@ void psxBiosInit() {
        //biosA0[0xa0] = psxBios__boot;
        //biosA0[0xa1] = psxBios_SystemError;
        //biosA0[0xa2] = psxBios_EnqueueCdIntr;
-       //biosA0[0xa3] = psxBios_DequeueCdIntr;
+       biosA0[0xa3] = psxBios_DequeueCdIntr;
        //biosA0[0xa4] = psxBios_sys_a0_a4;
        //biosA0[0xa5] = psxBios_ReadSector;
        biosA0[0xa6] = psxBios_get_cd_status;
@@ -2988,7 +3215,7 @@ void psxBiosInit() {
        //biosC0[0x07] = psxBios_InstallExeptionHandler;
        //biosC0[0x08] = psxBios_SysInitMemory;
        //biosC0[0x09] = psxBios_SysInitKMem;
-       biosC0[0x0a] = psxBios_ChangeClearRCnt; 
+       biosC0[0x0a] = psxBios_ChangeClearRCnt;
        //biosC0[0x0b] = psxBios_SystemError;
        //biosC0[0x0c] = psxBios_InitDefInt;
        //biosC0[0x0d] = psxBios_sys_c0_0d;
@@ -3011,27 +3238,16 @@ void psxBiosInit() {
 /**/
        base = 0x1000;
        size = sizeof(EvCB) * 32;
-       Event = (void *)&psxR[base]; base += size * 6;
-       memset(Event, 0, size * 6);
-       HwEV = Event;
-       EvEV = Event + 32;
-       RcEV = Event + 32 * 2;
-       UeEV = Event + 32 * 3;
-       SwEV = Event + 32 * 4;
-       ThEV = Event + 32 * 5;
-
-       ptr = (u32 *)&psxM[0x0874]; // b0 table
-       ptr[0] = SWAPu32(0x4c54 - 0x884);
-
-       ptr = (u32 *)&psxM[0x0674]; // c0 table
-       ptr[6] = SWAPu32(0xc80);
-
-       memset(SysIntRP, 0, sizeof(SysIntRP));
-       memset(Thread, 0, sizeof(Thread));
-       Thread[0].status = 2; // main thread
+       EventCB = (void *)&psxR[base]; base += size * 6;
+       memset(EventCB, 0, size * 6);
+       HwEV = EventCB;
+       EvEV = EventCB + 32;
+       RcEV = EventCB + 32 * 2;
+       UeEV = EventCB + 32 * 3;
+       SwEV = EventCB + 32 * 4;
+       ThEV = EventCB + 32 * 5;
 
        pad_stopped = 1;
-       jmp_int = NULL;
        pad_buf = NULL;
        pad_buf1 = NULL;
        pad_buf2 = NULL;
@@ -3044,33 +3260,6 @@ void psxBiosInit() {
        memset(FDesc, 0, sizeof(FDesc));
        card_active_chan = 0;
 
-       psxMu32ref(0x0150) = SWAPu32(0x160);
-       psxMu32ref(0x0154) = SWAPu32(0x320);
-       psxMu32ref(0x0160) = SWAPu32(0x248);
-       strcpy((char *)&psxM[0x248], "bu");
-/*     psxMu32ref(0x0ca8) = SWAPu32(0x1f410004);
-       psxMu32ref(0x0cf0) = SWAPu32(0x3c020000);
-       psxMu32ref(0x0cf4) = SWAPu32(0x2442641c);
-       psxMu32ref(0x09e0) = SWAPu32(0x43d0);
-       psxMu32ref(0x4d98) = SWAPu32(0x946f000a);
-*/
-       // opcode HLE
-       psxRu32ref(0x0000) = SWAPu32((0x3b << 26) | 4);
-       /* Whatever this does, it actually breaks CTR, even without the uninitiliazed memory patch. 
-       Normally games shouldn't read from address 0 yet they do. See explanation below in details. */
-       //psxMu32ref(0x0000) = SWAPu32((0x3b << 26) | 0);
-       psxMu32ref(0x00a0) = SWAPu32((0x3b << 26) | 1);
-       psxMu32ref(0x00b0) = SWAPu32((0x3b << 26) | 2);
-       psxMu32ref(0x00c0) = SWAPu32((0x3b << 26) | 3);
-       psxMu32ref(0x4c54) = SWAPu32((0x3b << 26) | 0);
-       psxMu32ref(0x8000) = SWAPu32((0x3b << 26) | 5);
-       psxMu32ref(0x07a0) = SWAPu32((0x3b << 26) | 0);
-       psxMu32ref(0x0884) = SWAPu32((0x3b << 26) | 0);
-       psxMu32ref(0x0894) = SWAPu32((0x3b << 26) | 0);
-
-       // initial stack pointer for BIOS interrupt
-       psxMu32ref(0x6c80) = SWAPu32(0x000085c8);
-
        // initial RNG seed
        psxMu32ref(0x9010) = SWAPu32(0xac20cc00);
 
@@ -3083,28 +3272,121 @@ void psxBiosInit() {
        // memory size 2 MB
        psxHu32ref(0x1060) = SWAPu32(0x00000b88);
 
-       hleSoftCall = FALSE;
-       
        /*      Some games like R-Types, CTR, Fade to Black read from adress 0x00000000 due to uninitialized pointers.
                See Garbage Area at Address 00000000h in Nocash PSX Specfications for more information.
                Here are some examples of games not working with this fix in place :
                R-type won't get past the Irem logo if not implemented.
                Crash Team Racing will softlock after the Sony logo.
        */
-       
-       psxMu32ref(0x0000) = SWAPu32(0x00000003);
-       /*
-       But overwritten by 00000003h after soon.
-       psxMu32ref(0x0000) = SWAPu32(0x00001A3C);
-       */
-       psxMu32ref(0x0004) = SWAPu32(0x800C5A27);
-       psxMu32ref(0x0008) = SWAPu32(0x08000403);
-       psxMu32ref(0x000C) = SWAPu32(0x00000000);
+
+       ram32 = (u32 *)psxM;
+       ram32[0x0000/4] = SWAPu32(0x00000003); // lui   $k0, 0  (overwritten by 3)
+       ram32[0x0004/4] = SWAPu32(0x275a0000 + A_EXCEPTION); // addiu $k0, $k0, 0xc80
+       ram32[0x0008/4] = SWAPu32(0x03400008); // jr    $k0
+       ram32[0x000c/4] = SWAPu32(0x00000000); // nop
+
+       ram32[0x0060/4] = SWAPu32(0x00000002); // ram size?
+       ram32[0x0068/4] = SWAPu32(0x000000ff); // unknown
+
+       ram32[0x0080/4] = SWAPu32(0x3c1a0000); // lui   $k0, 0  // exception vector
+       ram32[0x0084/4] = SWAPu32(0x275a0000 + A_EXCEPTION); // addiu $k0, $k0, 0xc80
+       ram32[0x0088/4] = SWAPu32(0x03400008); // jr    $k0
+       ram32[0x008c/4] = SWAPu32(0x00000000); // nop
+
+       ram32[0x00a0/4] = HLEOP(hleop_a0);
+       ram32[0x00b0/4] = HLEOP(hleop_b0);
+       ram32[0x00c0/4] = HLEOP(hleop_c0);
+
+       // "table of tables". Some games modify it
+       assert(A_TT_ExCB == 0x0100);
+       ram32[0x0100/4] = SWAPu32(0x0000e004); // ExCB - exception chains
+       ram32[0x0104/4] = SWAPu32(0x00000020); // ExCB size
+       ram32[0x0108/4] = SWAPu32(0x0000e1ec); // PCB - process control
+       ram32[0x010c/4] = SWAPu32(0x00000004); // PCB size
+       ram32[0x0110/4] = SWAPu32(0x0000e1f4); // TCB - thread control
+       ram32[0x0114/4] = SWAPu32(0x00000300); // TCB size
+       ram32[0x0120/4] = SWAPu32(0x0000e028); // EvCB - event control
+       ram32[0x0124/4] = SWAPu32(0x000001c0); // EvCB size
+       ram32[0x0140/4] = SWAPu32(0x00008648); // FCB - file control
+       ram32[0x0144/4] = SWAPu32(0x000002c0); // FCB size
+       ram32[0x0150/4] = SWAPu32(0x00006ee0); // DCB - device control
+       ram32[0x0154/4] = SWAPu32(0x00000320); // DCB size
+
+       ram32[0xe000/4] = SWAPu32(0x00000020); // SysMalloc block size
+       ram32[0xe004/4] = SWAPu32(0x000091e0); // chain0
+       ram32[0xe00c/4] = SWAPu32(0x00006d88); // chain1
+       ram32[0xe014/4] = SWAPu32(0x00000000); // chain2
+       ram32[0xe01c/4] = SWAPu32(0x00006d98); // chain3
+
+       ram32[0xe1ec/4] = SWAPu32(0x0000e1f4); // TCB
+       ram32[0xe1f0/4] = SWAPu32(0x00000300); // SysMalloc block size
+       ram32[0xe1f4/4] = SWAPu32(0x00004000); // first TCB
+
+       ram32[0x6ee0/4] = SWAPu32(0x0000eff0); // DCB
+       strcpy((char *)&ram32[0xeff0/4], "bu");
+
+       // default exception handler chains
+       write_chain(&ram32[0x91e0/4], 0x91d0, 0xbfc050a4, 0xbfc04fbc); // chain0.e0
+       write_chain(&ram32[0x91d0/4], 0x6da8, 0xbfc0506c, 0xbfc04dec); // chain0.e1
+       write_chain(&ram32[0x6da8/4],      0,          0,     0x1a00); // chain0.e2
+       write_chain(&ram32[0x6d88/4], 0x6d78,     0x19c8,     0x18bc); // chain1.e0
+       write_chain(&ram32[0x6d78/4], 0x6d68,     0x1990,     0x1858); // chain1.e1
+       write_chain(&ram32[0x6d68/4], 0x6d58,     0x1958,     0x17f4); // chain1.e2
+       write_chain(&ram32[0x6d58/4],      0,     0x1920,     0x1794); // chain1.e3
+       write_chain(&ram32[0x6d98/4],      0,          0,     0x2458); // chain3.e0
+
+       setup_mips_code();
+
+       // fill the api jumptables with fake entries as some games patch them
+       // (or rather the funcs listed there)
+       ptr = (u32 *)&psxM[A_A0_TABLE];
+       for (i = 0; i < 256; i++)
+               ptr[i] = SWAP32(0x1000);
+
+       ptr = (u32 *)&psxM[A_B0_TABLE];
+       for (i = 0; i < 256; i++)
+               ptr[i] = SWAP32(0x2000);
+       // B(5b) is special because games patch (sometimes even jump to)
+       // code at fixed offsets from it, nocash lists offsets:
+       //  patch: +3d8, +4dc, +594, +62c, +9c8, +1988
+       //  call:  +7a0=4b70, +884=4c54, +894=4c64
+       ptr[0x5b] = SWAP32(0x43d0);
+       ram32[0x4b70/4] = SWAP32(0x03e00008); // jr $ra
+       ram32[0x4c54/4] = SWAP32(0x03e00008); // jr $ra
+       ram32[0x4c64/4] = SWAP32(0x03e00008); // jr $ra
+
+       ptr = (u32 *)&psxM[A_C0_TABLE];
+       for (i = 0; i < 256/2; i++)
+               ptr[i] = SWAP32(0x3000);
+       ptr[6] = SWAP32(A_EXCEPTION);
+
+       // more HLE traps
+       ram32[0x1000/4] = HLEOP(hleop_dummy);
+       ram32[0x2000/4] = HLEOP(hleop_dummy);
+       ram32[0x3000/4] = HLEOP(hleop_dummy);
+       ram32[0x4c54/4] = HLEOP(hleop_dummy);   // for B12_InitPad?
+       ram32[0x8000/4] = HLEOP(hleop_execret);
+
+       ram32[A_EEXIT_PTR/4] = SWAP32(A_EEXIT_DEF);
+       ram32[A_EXC_SP/4] = SWAP32(A_EXC_STACK);
+       ram32[A_RCNT_VBL_ACK/4 + 0] = SWAP32(1);
+       ram32[A_RCNT_VBL_ACK/4 + 1] = SWAP32(1);
+       ram32[A_RCNT_VBL_ACK/4 + 2] = SWAP32(1);
+       ram32[A_RCNT_VBL_ACK/4 + 3] = SWAP32(1);
+
+       psxRegs.CP0.n.SR &= ~0x400000; // use ram vector
 }
 
 void psxBiosShutdown() {
 }
 
+void psxBiosCnfLoaded(u32 tcbs, u32 events) {
+       if (tcbs > 4)
+               log_unhandled("FIXME: TCB = %x\n", tcbs);
+       if (events > 16)
+               log_unhandled("FIXME: EVENT = %x\n", tcbs);
+}
+
 #define psxBios_PADpoll(pad) { \
        PAD##pad##_startPoll(pad); \
        pad_buf##pad[0] = 0; \
@@ -3121,181 +3403,293 @@ void psxBiosShutdown() {
        } \
 }
 
-void biosInterrupt() {
+static void biosPadHLE() {
        int i, bufcount;
 
-//     if (psxHu32(0x1070) & 0x1) { // Vsync
-               if (pad_buf != NULL) {
-                       u32 *buf = (u32*)pad_buf;
-
-                       if (!Config.UseNet) {
-                               PAD1_startPoll(1);
-                               if (PAD1_poll(0x42) == 0x23) {
-                                       PAD1_poll(0);
-                                       *buf = PAD1_poll(0) << 8;
-                                       *buf |= PAD1_poll(0);
-                                       PAD1_poll(0);
-                                       *buf &= ~((PAD1_poll(0) > 0x20) ? 1 << 6 : 0);
-                                       *buf &= ~((PAD1_poll(0) > 0x20) ? 1 << 7 : 0);
-                               } else {
-                                       PAD1_poll(0);
-                                       *buf = PAD1_poll(0) << 8;
-                                       *buf|= PAD1_poll(0);
-                               }
+       if (pad_buf != NULL) {
+               u32 *buf = (u32*)pad_buf;
+
+               PAD1_startPoll(1);
+               if (PAD1_poll(0x42) == 0x23) {
+                       PAD1_poll(0);
+                       *buf = PAD1_poll(0) << 8;
+                       *buf |= PAD1_poll(0);
+                       PAD1_poll(0);
+                       *buf &= ~((PAD1_poll(0) > 0x20) ? 1 << 6 : 0);
+                       *buf &= ~((PAD1_poll(0) > 0x20) ? 1 << 7 : 0);
+               } else {
+                       PAD1_poll(0);
+                       *buf = PAD1_poll(0) << 8;
+                       *buf|= PAD1_poll(0);
+               }
 
-                               PAD2_startPoll(2);
-                               if (PAD2_poll(0x42) == 0x23) {
-                                       PAD2_poll(0);
-                                       *buf |= PAD2_poll(0) << 24;
-                                       *buf |= PAD2_poll(0) << 16;
-                                       PAD2_poll(0);
-                                       *buf &= ~((PAD2_poll(0) > 0x20) ? 1 << 22 : 0);
-                                       *buf &= ~((PAD2_poll(0) > 0x20) ? 1 << 23 : 0);
-                               } else {
-                                       PAD2_poll(0);
-                                       *buf |= PAD2_poll(0) << 24;
-                                       *buf |= PAD2_poll(0) << 16;
-                               }
-                       } else {
-                               u16 data;
+               PAD2_startPoll(2);
+               if (PAD2_poll(0x42) == 0x23) {
+                       PAD2_poll(0);
+                       *buf |= PAD2_poll(0) << 24;
+                       *buf |= PAD2_poll(0) << 16;
+                       PAD2_poll(0);
+                       *buf &= ~((PAD2_poll(0) > 0x20) ? 1 << 22 : 0);
+                       *buf &= ~((PAD2_poll(0) > 0x20) ? 1 << 23 : 0);
+               } else {
+                       PAD2_poll(0);
+                       *buf |= PAD2_poll(0) << 24;
+                       *buf |= PAD2_poll(0) << 16;
+               }
+       }
+       if (!pad_stopped)  {
+               if (pad_buf1) {
+                       psxBios_PADpoll(1);
+               }
 
-                               PAD1_startPoll(1);
-                               PAD1_poll(0x42);
-                               PAD1_poll(0);
-                               data = PAD1_poll(0) << 8;
-                               data |= PAD1_poll(0);
+               if (pad_buf2) {
+                       psxBios_PADpoll(2);
+               }
+       }
+}
 
-                               if (NET_sendPadData(&data, 2) == -1)
-                                       netError();
+static void handle_chain_x_x_1(u32 enable, u32 irqbit)
+{
+       use_cycles(10);
+       if (enable) {
+               psxHwWrite16(0x1f801070, ~(1u << irqbit));
+               psxBios_ReturnFromException();
+       }
+       else
+               pc0 = ra;
+}
 
-                               if (NET_recvPadData(&((u16*)buf)[0], 1) == -1)
-                                       netError();
-                               if (NET_recvPadData(&((u16*)buf)[1], 2) == -1)
-                                       netError();
-                       }
-               }
-               if (Config.UseNet && pad_buf1 != NULL && pad_buf2 != NULL) {
-                       psxBios_PADpoll(1);
+// hleExc0_{0,1}* are usually removed by A(56)/A(72) on the game's startup,
+// so this is only partially implemented
+void hleExc0_0_1() // A(93h) - CdromDmaIrqFunc2
+{
+       u32 cdrom_dma_ack_enable = 1; // a000b93c
+       handle_chain_x_x_1(cdrom_dma_ack_enable, 3); // IRQ3 DMA
+}
 
-                       if (NET_sendPadData(pad_buf1, i) == -1)
-                               netError();
+void hleExc0_0_2() // A(91h) - CdromDmaIrqFunc1
+{
+       u32 ret = 0;
+       //PSXBIOS_LOG("%s\n", __func__);
 
-                       if (NET_recvPadData(pad_buf1, 1) == -1)
-                               netError();
-                       if (NET_recvPadData(pad_buf2, 2) == -1)
-                               netError();
-               } else {
-                       if (!pad_stopped)  {
-                               if (pad_buf1) {
-                                       psxBios_PADpoll(1);
-                               }
+       if (psxHu32(0x1074) & psxHu32(0x1070) & 8) { // IRQ3 DMA
+               psxHwWrite32(0x1f8010f4, (psxHu32(0x10f4) & 0xffffff) | 0x88000000);
+               //if (--cdrom_irq_counter == 0) // 0xa0009180
+               //      DeliverEvent();         // 0xf0000003, 0x10
+               use_cycles(22);
+               ret = 1;
+       }
+       mips_return_c(ret, 20);
+}
 
-                               if (pad_buf2) {
-                                       psxBios_PADpoll(2);
-                               }
-                       }
-               }
+void hleExc0_1_1() // A(92h) - CdromIoIrqFunc2
+{
+       u32 cdrom_irq_ack_enable = 1; // a000b938
+       handle_chain_x_x_1(cdrom_irq_ack_enable, 2); // IRQ2 cdrom
+}
+
+void hleExc0_1_2() // A(90h) - CdromIoIrqFunc1
+{
+       u32 ret = 0;
+       if (psxHu32(0x1074) & psxHu32(0x1070) & 4) { // IRQ2 cdrom
+               PSXBIOS_LOG("%s TODO\n", __func__);
+               ret = 1;
+       }
+       mips_return_c(ret, 20);
+}
 
-       if (psxHu32(0x1070) & 0x1) { // Vsync
-               if (RcEV[3][1].status == EvStACTIVE) {
-                       softCall(RcEV[3][1].fhandler);
-//                     hwWrite32(0x1f801070, ~(1));
+void hleExc0_2_2_syscall() // not in any A/B/C table
+{
+       u32 code = (psxRegs.CP0.n.Cause & 0x3c) >> 2;
+       u32 tcbPtr = loadRam32(A_TT_PCB);
+       TCB *tcb = loadRam32ptr(tcbPtr);
+
+       if (code != R3000E_Syscall) {
+               if (code != 0) {
+                       // DeliverEvent(); // 0xf0000010, 0x1000
+                       psxBios_SystemErrorUnresolvedException();
                }
+               mips_return_c(0, 17);
+               return;
        }
 
-       if (psxHu32(0x1070) & 0x70) { // Rcnt 0,1,2
-               int i;
+       //printf("%s c=%d a0=%d\n", __func__, code, a0);
+       tcb->epc += SWAP32(4);
+       switch (a0) {
+               case 0: // noop
+                       break;
+
+               case 1: { // EnterCritical - disable irqs
+                       u32 was_enabled = ((SWAP32(tcb->sr) & 0x404) == 0x404);
+                       tcb->reg[2] = SWAP32(was_enabled);
+                       tcb->sr &= SWAP32(~0x404);
+                       break;
+               }
+               case 2: // ExitCritical - enable irqs
+                       tcb->sr |= SWAP32(0x404);
+                       break;
 
-               for (i = 0; i < 3; i++) {
-                       if (psxHu32(0x1070) & (1 << (i + 4))) {
-                               if (RcEV[i][1].status == EvStACTIVE) {
-                                       softCall(RcEV[i][1].fhandler);
-                               }
-                               psxHwWrite32(0x1f801070, ~(1 << (i + 4)));
-                       }
+               case 3: { // ChangeThreadSubFunction
+                       u32 tcbPtr = loadRam32(A_TT_PCB);
+                       storeRam32(tcbPtr, a1);
+                       break;
                }
+               default:
+                       // DeliverEvent(); // 0xf0000010, 0x4000
+                       break;
        }
+       use_cycles(30);
+       psxBios_ReturnFromException();
 }
 
-void psxBiosException() {
-       int i;
+void hleExc1_0_1(void)
+{
+       u32 vbl_irq_ack_enable = loadRam32(A_RCNT_VBL_ACK + 0x0c); // 860c
+       handle_chain_x_x_1(vbl_irq_ack_enable, 0); // IRQ0 vblank
+}
 
-       switch (psxRegs.CP0.n.Cause & 0x3c) {
-               case 0x00: // Interrupt
-                       interrupt_r26=psxRegs.CP0.n.EPC;
-#ifdef PSXCPU_LOG
-//                     PSXCPU_LOG("interrupt\n");
-#endif
-                       SaveRegs();
+static void handle_chain_1_x_2(u32 ev_index, u32 irqbit)
+{
+       u32 ret = 0;
+       if (psxHu32(0x1074) & psxHu32(0x1070) & (1u << irqbit)) {
+               // DeliverEvent 0xf2000000 + ev_index, 2
+               if (RcEV[ev_index][1].status == EvStACTIVE) {
+                       softCall(RcEV[ev_index][1].fhandler);
+               }
+               ret = 1;
+       }
+       mips_return_c(ret, 22);
+}
 
-                       sp = psxMu32(0x6c80); // create new stack for interrupt handlers
+void hleExc1_0_2(void)
+{
+       handle_chain_1_x_2(3, 0); // IRQ0 vblank
+}
 
-                       biosInterrupt();
+void hleExc1_1_1(void)
+{
+       u32 rcnt_irq_ack_enable = loadRam32(A_RCNT_VBL_ACK + 0x08); // 8608
+       handle_chain_x_x_1(rcnt_irq_ack_enable, 6); // IRQ6 rcnt2
+}
 
-                       for (i = 0; i < 8; i++) {
-                               if (SysIntRP[i]) {
-                                       u32 *queue = (u32 *)PSXM(SysIntRP[i]);
+void hleExc1_1_2(void)
+{
+       handle_chain_1_x_2(2, 6); // IRQ6 rcnt2
+}
 
-                                       s0 = queue[2];
-                                       softCall(queue[1]);
-                               }
-                       }
+void hleExc1_2_1(void)
+{
+       u32 rcnt_irq_ack_enable = loadRam32(A_RCNT_VBL_ACK + 0x04); // 8604
+       handle_chain_x_x_1(rcnt_irq_ack_enable, 5); // IRQ5 rcnt1
+}
 
-                       if (jmp_int != NULL) {
-                               int i;
+void hleExc1_2_2(void)
+{
+       handle_chain_1_x_2(1, 5); // IRQ5 rcnt1
+}
 
-                               psxHwWrite32(0x1f801070, 0xffffffff);
+void hleExc1_3_1(void)
+{
+       u32 rcnt_irq_ack_enable = loadRam32(A_RCNT_VBL_ACK + 0x00); // 8600
+       handle_chain_x_x_1(rcnt_irq_ack_enable, 4); // IRQ4 rcnt0
+}
 
-                               ra = jmp_int[0];
-                               sp = jmp_int[1];
-                               fp = jmp_int[2];
-                               for (i = 0; i < 8; i++) // s0-s7
-                                        psxRegs.GPR.r[16 + i] = jmp_int[3 + i];
-                               gp = jmp_int[11];
+void hleExc1_3_2(void)
+{
+       handle_chain_1_x_2(0, 4); // IRQ4 rcnt0
+}
 
-                               v0 = 1;
-                               pc0 = ra;
-                               return;
-                       }
-                       psxHwWrite16(0x1f801070, 0);
-                       break;
+void hleExc3_0_2_defint(void)
+{
+       static const struct {
+               u8 ev, irqbit;
+       } tab[] = {
+               {  3,  2 }, // cdrom
+               {  9,  9 }, // spu
+               {  2,  1 }, // gpu
+               { 10, 10 }, // io
+               { 11,  8 }, // sio
+               {  1,  0 }, // vbl
+               {  5,  4 }, // rcnt0
+               {  6,  5 }, // rcnt1
+               {  6,  6 }, // rcnt2 (bug)
+               {  8,  7 }, // sio rx
+               {  4,  3 }, // sio
+       };
+       size_t i;
+       for (i = 0; i < sizeof(tab) / sizeof(tab[0]); i++) {
+               if (psxHu32(0x1074) & psxHu32(0x1070) & (1u << tab[i].irqbit)) {
+                       // DeliverEvent 0xf0000000 + ev, 0x1000
+                       use_cycles(7);
+               }
 
-               case 0x20: // Syscall
-#ifdef PSXCPU_LOG
-                       PSXCPU_LOG("syscall exp %x\n", a0);
-#endif
-                       switch (a0) {
-                               case 1: // EnterCritical - disable irq's
-                                       /* Fixes Medievil 2 not loading up new game, Digimon World not booting up and possibly others */
-                                       v0 = (psxRegs.CP0.n.Status & 0x404) == 0x404;
-                                       psxRegs.CP0.n.Status &= ~0x404;
-                                       break;
+       }
+       mips_return_c(0, 11 + 7*11 + 7*11 + 12);
+}
 
-                               case 2: // ExitCritical - enable irq's
-                                       psxRegs.CP0.n.Status |= 0x404; 
-                                       break;
-                               /* Normally this should cover SYS(00h, SYS(04h but they don't do anything relevant so... */
-                               default:
-                                       break;
-                       }
-                       pc0 = psxRegs.CP0.n.EPC + 4;
+void psxBiosException() {
+       u32 tcbPtr = loadRam32(A_TT_PCB);
+       u32 *chains = loadRam32ptr(A_TT_ExCB);
+       TCB *tcb = loadRam32ptr(tcbPtr);
+       u32 ptr, *chain;
+       int c, lim;
+       int i;
 
-                       psxRegs.CP0.n.Status = (psxRegs.CP0.n.Status & 0xfffffff0) |
-                                                                 ((psxRegs.CP0.n.Status & 0x3c) >> 2);
-                       return;
+       // save the regs
+       // $at, $v0, $v1 already saved by the mips code at A_EXCEPTION
+       for (i = 4; i < 32; i++) {
+               if (i == 26) // $k0
+                       continue;
+               tcb->reg[i] = SWAP32(psxRegs.GPR.r[i]);
+       }
+       tcb->lo = SWAP32(psxRegs.GPR.n.lo);
+       tcb->hi = SWAP32(psxRegs.GPR.n.hi);
+       tcb->epc = SWAP32(psxRegs.CP0.n.EPC);
+       tcb->sr = SWAP32(psxRegs.CP0.n.SR);
+       tcb->cause = SWAP32(psxRegs.CP0.n.Cause);
+       sp = fp = loadRam32(A_EXC_SP);
+       gp = A_EXC_GP;
+       use_cycles(46);
+
+       // do the chains (always 4)
+       for (c = lim = 0; c < 4; c++) {
+               if (chains[c * 2] == 0)
+                       continue;
+               ptr = SWAP32(chains[c * 2]);
+               for (; ptr && lim < 100; ptr = SWAP32(chain[0])) {
+                       chain = castRam32ptr(ptr);
+                       use_cycles(14);
+                       lim++;
+                       if (chain[2] == 0)
+                               continue;
+                       softCallInException(SWAP32(chain[2]));
+                       if (returned_from_exception())
+                               return;
 
-               default:
-#ifdef PSXCPU_LOG
-                       PSXCPU_LOG("unknown bios exception!\n");
-#endif
-                       break;
+                       if (v0 == 0 || chain[1] == 0)
+                               continue;
+                       softCallInException(SWAP32(chain[1]));
+                       if (returned_from_exception())
+                               return;
+               }
        }
+       assert(lim < 100);
 
-       pc0 = psxRegs.CP0.n.EPC;
-       if (psxRegs.CP0.n.Cause & 0x80000000) pc0+=4;
+       // TODO make this a chain entry
+       if (psxHu32(0x1070) & 1)
+               biosPadHLE();
 
-       psxRegs.CP0.n.Status = (psxRegs.CP0.n.Status & 0xfffffff0) |
-                                                 ((psxRegs.CP0.n.Status & 0x3c) >> 2);
+       // return from exception (custom or default)
+       use_cycles(23);
+       ptr = loadRam32(A_EEXIT_PTR);
+       if (ptr != A_EEXIT_DEF) {
+               const struct jmp_buf_ *jmp_buf = castRam32ptr(ptr);
+               longjmp_load(jmp_buf);
+               v0 = 1;
+               pc0 = ra;
+               return;
+       }
+       psxBios_ReturnFromException();
 }
 
 #define bfreeze(ptr, size) { \
@@ -3321,7 +3715,6 @@ void psxBiosException() {
 void psxBiosFreeze(int Mode) {
        u32 base = 0x40000;
 
-       bfreezepsxMptr(jmp_int, u32);
        bfreezepsxMptr(pad_buf, int);
        bfreezepsxMptr(pad_buf1, char);
        bfreezepsxMptr(pad_buf2, char);
@@ -3329,9 +3722,7 @@ void psxBiosFreeze(int Mode) {
        bfreezel(&pad_buf1len);
        bfreezel(&pad_buf2len);
        bfreezes(regs);
-       bfreezes(SysIntRP);
        bfreezel(&CardState);
-       bfreezes(Thread);
        bfreezel(&CurThread);
        bfreezes(FDesc);
        bfreezel(&card_active_chan);