cdrom: change pause timing again
[pcsx_rearmed.git] / frontend / libretro.c
index 42f6151..6a719ae 100644 (file)
@@ -15,6 +15,7 @@
 #include <sys/syscall.h>
 #endif
 
+#include "retro_miscellaneous.h"
 #ifdef SWITCH
 #include <switch.h>
 #endif
 #include "../libpcsxcore/cdriso.h"
 #include "../libpcsxcore/cheat.h"
 #include "../libpcsxcore/r3000a.h"
+#include "../libpcsxcore/gpu.h"
+#include "../libpcsxcore/database.h"
 #include "../plugins/dfsound/out.h"
 #include "../plugins/dfsound/spu_config.h"
-#include "../plugins/dfinput/externals.h"
 #include "cspace.h"
 #include "main.h"
 #include "menu.h"
@@ -75,14 +77,21 @@ static retro_set_rumble_state_t rumble_cb;
 static struct retro_log_callback logging;
 static retro_log_printf_t log_cb;
 
+#define LogWarn(fmt, ...) do { \
+   if (log_cb) log_cb(RETRO_LOG_WARN, fmt, ##__VA_ARGS__); \
+} while (0)
+#define LogErr(fmt, ...) do { \
+   if (log_cb) log_cb(RETRO_LOG_ERROR, fmt, ##__VA_ARGS__); \
+} while (0)
+
 static unsigned msg_interface_version = 0;
 
 static void *vout_buf;
 static void *vout_buf_ptr;
-static int vout_width, vout_height;
-static int vout_doffs_old, vout_fb_dirty;
+static int vout_width = 256, vout_height = 240, vout_pitch = 256;
+static int vout_fb_dirty;
+static int psx_w, psx_h;
 static bool vout_can_dupe;
-static bool duping_enable;
 static bool found_bios;
 static bool display_internal_fps = false;
 static unsigned frame_count = 0;
@@ -96,6 +105,7 @@ static bool show_advanced_gpu_peops_settings = true;
 static bool show_advanced_gpu_unai_settings = true;
 #endif
 static float mouse_sensitivity = 1.0f;
+static unsigned int disk_current_index;
 
 typedef enum
 {
@@ -121,7 +131,8 @@ static unsigned previous_width = 0;
 static unsigned previous_height = 0;
 
 static int plugins_opened;
-static int is_pal_mode;
+
+#define is_pal_mode Config.PsxType
 
 /* memory card data */
 extern char Mcd1Data[MCD_SIZE];
@@ -142,6 +153,9 @@ int in_mouse[8][2];
 int multitap1 = 0;
 int multitap2 = 0;
 int in_enable_vibration = 1;
+static int in_enable_crosshair[2] = { 0, 0 };
+static int in_dualshock_analog_combo = 0;
+static bool in_dualshock_toggling = false;
 
 // NegCon adjustment parameters
 // > The NegCon 'twist' action is somewhat awkward when mapped
@@ -230,8 +244,16 @@ static void set_vout_fb()
    fb.height         = vout_height;
    fb.access_flags   = RETRO_MEMORY_ACCESS_WRITE;
 
-   if (environ_cb(RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER, &fb) && fb.format == RETRO_PIXEL_FORMAT_RGB565)
-      vout_buf_ptr = (uint16_t *)fb.data;
+   vout_pitch = vout_width;
+   if (environ_cb(RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER, &fb)
+         && fb.format == RETRO_PIXEL_FORMAT_RGB565
+         && vout_can_dupe)
+   {
+      vout_buf_ptr = fb.data;
+      if (fb.pitch / 2 != vout_pitch && fb.pitch != vout_width * 2)
+         LogWarn("got unusual pitch %zd for resolution %dx%d\n", fb.pitch, vout_width, vout_height);
+      vout_pitch = fb.pitch / 2;
+   }
    else
       vout_buf_ptr = vout_buf;
 }
@@ -240,6 +262,8 @@ static void vout_set_mode(int w, int h, int raw_w, int raw_h, int bpp)
 {
    vout_width = w;
    vout_height = h;
+   psx_w = raw_w;
+   psx_h = raw_h;
 
    if (previous_width != vout_width || previous_height != vout_height)
    {
@@ -267,29 +291,63 @@ static void convert(void *buf, size_t bytes)
 }
 #endif
 
-static void vout_flip(const void *vram, int stride, int bgr24, int w, int h)
+// Function to add crosshairs
+static void addCrosshair(int port, int crosshair_color, unsigned short *buffer, int bufferStride, int pos_x, int pos_y, int thickness, int size_x, int size_y) {
+   for (port = 0; port < 2; port++) {
+      // Draw the horizontal line of the crosshair
+      int i, j;
+      for (i = pos_y - thickness / 2; i <= pos_y + thickness / 2; i++) {
+         for (j = pos_x - size_x / 2; j <= pos_x + size_x / 2; j++) {
+            if ((i + vout_height) >= 0 && (i + vout_height) < bufferStride && j >= 0 && j < bufferStride && in_enable_crosshair[port] > 0)
+               buffer[i * bufferStride + j] = crosshair_color;
+         }
+      }
+
+      // Draw the vertical line of the crosshair
+      for (i = pos_x - thickness / 2; i <= pos_x + thickness / 2; i++) {
+         for (j = pos_y - size_y / 2; j <= pos_y + size_y / 2; j++) {
+            if (i >= 0 && i < bufferStride && (j + vout_height) >= 0 && (j + vout_height) < bufferStride && in_enable_crosshair[port] > 0)
+               buffer[j * bufferStride + i] = crosshair_color;
+         }
+      }
+   }
+}
+
+struct CrosshairInfo {
+   int pos_x, pos_y, thickness, size_x, size_y;
+};
+
+// Calculate size and position of crosshairs
+static void CrosshairDimensions(int port, struct CrosshairInfo *info) {
+   int gunx = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X);
+   int guny = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y);
+   if (gunx == 32767) // Prevent crosshairs from wrapping around right side of screen to left
+      info->pos_x = (gunx + 32767.0f) * vout_width / 65534.0f - 0.5f;
+   else
+      info->pos_x = (gunx + 32767.0f) * vout_width / 65534.0f;
+   info->pos_y = (guny + 32767.0f) * vout_height / 65534.0f - vout_height;
+   info->thickness = pl_rearmed_cbs.gpu_neon.enhancement_enable ? 4 : 2;
+   info->size_x = psx_w * (pl_rearmed_cbs.gpu_neon.enhancement_enable ? 2 : 1) / 40.0f;
+   info->size_y = psx_h * (pl_rearmed_cbs.gpu_neon.enhancement_enable ? 2 : 1) * (4.0f / 3.0f) / 40.0f;
+}
+
+static void vout_flip(const void *vram, int stride, int bgr24,
+      int x, int y, int w, int h, int dims_changed)
 {
    unsigned short *dest = vout_buf_ptr;
    const unsigned short *src = vram;
-   int dstride = vout_width, h1 = h;
-   int doffs;
+   int dstride = vout_pitch, h1 = h;
+   int port = 0;
 
-   if (vram == NULL)
+   if (vram == NULL || dims_changed || (in_enable_crosshair[0] + in_enable_crosshair[1]) > 0)
    {
+      memset(vout_buf_ptr, 0, dstride * vout_height * 2);
       // blanking
-      memset(vout_buf_ptr, 0, dstride * h * 2);
-      goto out;
+      if (vram == NULL)
+         goto out;
    }
 
-   doffs = (vout_height - h) * dstride;
-   doffs += (dstride - w) / 2 & ~1;
-   if (doffs != vout_doffs_old)
-   {
-      // clear borders
-      memset(vout_buf_ptr, 0, dstride * h * 2);
-      vout_doffs_old = doffs;
-   }
-   dest += doffs;
+   dest += x + y * dstride;
 
    if (bgr24)
    {
@@ -307,9 +365,18 @@ static void vout_flip(const void *vram, int stride, int bgr24, int w, int h)
       }
    }
 
+   for (port = 0; port < 2; port++) {
+      if (in_enable_crosshair[port] > 0 && (in_type[port] == PSE_PAD_TYPE_GUNCON || in_type[port] == PSE_PAD_TYPE_GUN))
+      {
+         struct CrosshairInfo crosshairInfo;
+         CrosshairDimensions(port, &crosshairInfo);
+         addCrosshair(port, in_enable_crosshair[port], dest, dstride, crosshairInfo.pos_x, crosshairInfo.pos_y, crosshairInfo.thickness, crosshairInfo.size_x, crosshairInfo.size_y);
+      }
+   }
+
 out:
 #ifndef FRONTEND_SUPPORTS_RGB565
-   convert(vout_buf_ptr, vout_width * vout_height * 2);
+   convert(vout_buf_ptr, vout_pitch * vout_height * 2);
 #endif
    vout_fb_dirty = 1;
    pl_rearmed_cbs.flip_cnt++;
@@ -329,7 +396,7 @@ psx_map_t custom_psx_maps[] = {
    { NULL, 0x12800000, 0x010000, MAP_TAG_OTHER }, // 0x1f800000
    { NULL, 0x12c00000, 0x080000, MAP_TAG_OTHER }, // 0x1fc00000
    { NULL, 0x11000000, 0x800000, MAP_TAG_LUTS }, // 0x08000000
-   { NULL, 0x12000000, 0x200000, MAP_TAG_VRAM }, // 0x00000000
+   { NULL, 0x12000000, 0x201000, MAP_TAG_VRAM }, // 0x00000000
 };
 
 void *pl_3ds_mmap(unsigned long addr, size_t size, int is_fixed,
@@ -347,22 +414,25 @@ void *pl_3ds_mmap(unsigned long addr, size_t size, int is_fixed,
          if ((custom_map->size == size) && (custom_map->tag == tag))
          {
             uint32_t ptr_aligned, tmp;
+            void *ret;
 
             custom_map->buffer = malloc(size + 0x1000);
             ptr_aligned = (((u32)custom_map->buffer) + 0xFFF) & ~0xFFF;
 
             if (svcControlMemory(&tmp, (void *)custom_map->target_map, (void *)ptr_aligned, size, MEMOP_MAP, 0x3) < 0)
             {
-               SysPrintf("could not map memory @0x%08X\n", custom_map->target_map);
+               LogErr("could not map memory @0x%08X\n", custom_map->target_map);
                exit(1);
             }
 
-            return (void *)custom_map->target_map;
+            ret = (void *)custom_map->target_map;
+            memset(ret, 0, size);
+            return ret;
          }
       }
    }
 
-   return malloc(size);
+   return calloc(size, 1);
 }
 
 void pl_3ds_munmap(void *ptr, size_t size, enum psxMapTag tag)
@@ -398,19 +468,20 @@ void pl_3ds_munmap(void *ptr, size_t size, enum psxMapTag tag)
 typedef struct
 {
    void *buffer;
-   uint32_t target_map;
    size_t size;
    enum psxMapTag tag;
+   int used;
 } psx_map_t;
 
-void *addr = NULL;
+static void *addr = NULL;
 
 psx_map_t custom_psx_maps[] = {
-   { NULL, NULL, 0x210000, MAP_TAG_RAM }, // 0x80000000
-   { NULL, NULL, 0x010000, MAP_TAG_OTHER }, // 0x1f800000
-   { NULL, NULL, 0x080000, MAP_TAG_OTHER }, // 0x1fc00000
-   { NULL, NULL, 0x800000, MAP_TAG_LUTS }, // 0x08000000
-   { NULL, NULL, 0x200000, MAP_TAG_VRAM }, // 0x00000000
+   { NULL, 0x800000, MAP_TAG_LUTS },
+   { NULL, 0x080000, MAP_TAG_OTHER },
+   { NULL, 0x010000, MAP_TAG_OTHER },
+   { NULL, 0x201000, MAP_TAG_VRAM },
+   { NULL, 0x802000, MAP_TAG_VRAM }, // enhanced renderer
+   { NULL, 0x210000, MAP_TAG_RAM },
 };
 
 int init_vita_mmap()
@@ -420,12 +491,14 @@ int init_vita_mmap()
    addr = malloc(64 * 1024 * 1024);
    if (addr == NULL)
       return -1;
-   tmpaddr = ((u32)(addr + 0xFFFFFF)) & ~0xFFFFFF;
-   custom_psx_maps[0].buffer = tmpaddr + 0x2000000;
-   custom_psx_maps[1].buffer = tmpaddr + 0x1800000;
-   custom_psx_maps[2].buffer = tmpaddr + 0x1c00000;
-   custom_psx_maps[3].buffer = tmpaddr + 0x0000000;
+   tmpaddr = (void *)(((size_t)addr + 0xFFFFFF) & ~0xFFFFFF);
+   custom_psx_maps[0].buffer = tmpaddr + 0x0000000;
+   custom_psx_maps[1].buffer = tmpaddr + 0x0800000;
+   custom_psx_maps[2].buffer = tmpaddr + 0x0880000;
+   custom_psx_maps[3].buffer = tmpaddr + 0x0900000;
    custom_psx_maps[4].buffer = tmpaddr + 0x1000000;
+   custom_psx_maps[5].buffer = tmpaddr + 0x2000000;
+   memset(tmpaddr, 0, 0x2210000);
 #if 0
    for(n = 0; n < 5; n++){
    sceClibPrintf("addr reserved %x\n",custom_psx_maps[n].buffer);
@@ -436,6 +509,11 @@ int init_vita_mmap()
 
 void deinit_vita_mmap()
 {
+   size_t i;
+   for (i = 0; i < sizeof(custom_psx_maps) / sizeof(custom_psx_maps[0]); i++) {
+      custom_psx_maps[i].buffer = NULL;
+      custom_psx_maps[i].used = 0;
+   }
    free(addr);
 }
 
@@ -449,13 +527,14 @@ void *pl_vita_mmap(unsigned long addr, size_t size, int is_fixed,
 
    for (; custom_map->size; custom_map++)
    {
-      if ((custom_map->size == size) && (custom_map->tag == tag))
+      if (custom_map->size == size && custom_map->tag == tag && !custom_map->used)
       {
+         custom_map->used = 1;
          return custom_map->buffer;
       }
    }
 
-   return malloc(size);
+   return calloc(size, 1);
 }
 
 void pl_vita_munmap(void *ptr, size_t size, enum psxMapTag tag)
@@ -468,6 +547,7 @@ void pl_vita_munmap(void *ptr, size_t size, enum psxMapTag tag)
    {
       if ((custom_map->buffer == ptr))
       {
+         custom_map->used = 0;
          return;
       }
    }
@@ -493,6 +573,7 @@ struct rearmed_cbs pl_rearmed_cbs = {
    .pl_vout_close    = vout_close,
    .mmap             = pl_mmap,
    .munmap           = pl_munmap,
+   .gpu_state_change = gpu_state_change,
    /* from psxcounters */
    .gpu_hcnt         = &hSyncCount,
    .gpu_frame_count  = &frame_counter,
@@ -501,12 +582,11 @@ struct rearmed_cbs pl_rearmed_cbs = {
 void pl_frame_limit(void)
 {
    /* called once per frame, make psxCpu->Execute() above return */
-   stop = 1;
+   stop++;
 }
 
 void pl_timing_prepare(int is_pal)
 {
-   is_pal_mode = is_pal;
 }
 
 void plat_trigger_vibrate(int pad, int low, int high)
@@ -521,8 +601,41 @@ void plat_trigger_vibrate(int pad, int low, int high)
    }
 }
 
-void pl_update_gun(int *xn, int *yn, int *xres, int *yres, int *in)
+//Percentage distance of screen to adjust for Konami Gun
+static float KonamiGunAdjustX = 0;
+static float KonamiGunAdjustY = 0;
+
+void pl_gun_byte2(int port, unsigned char byte)
 {
+   int irq_count = 4;
+   float justifier_multiplier = 0;
+   int justifier_width = psx_w;
+   int justifier_height = psx_h;
+   int justifier_offscreen = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN);
+   int justifier_reload = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD);
+
+   if (justifier_width == 256)
+      justifier_multiplier = is_pal_mode ? .157086f : .158532f;
+   else if (justifier_width == 320)
+      justifier_multiplier = is_pal_mode ? .196358f : .198166f;
+   else if (justifier_width == 384)
+      justifier_multiplier = is_pal_mode ? .224409f : .226475f;
+   else if (justifier_width == 512)
+      justifier_multiplier = is_pal_mode ? .314173f : .317065f;
+   else // (justifier_width == 640)
+      justifier_multiplier = is_pal_mode ? .392717f : .396332f;
+
+   int gunx = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X);
+   int guny = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y);
+
+   //Default offset of +105 for X and -12 for Y is chosen to obtain alignment in Die Hard Trilogy, which has no calibration feature
+   int gunx_scaled = ((gunx + 32767.0f) / 65534.0f + KonamiGunAdjustX) * justifier_width / justifier_multiplier + 105.0f;
+   int guny_scaled = ((guny + 32767.0f) / 65534.0f + KonamiGunAdjustY) * justifier_height - 12.0f;
+
+   if ((byte & 0x10) && !justifier_offscreen && !justifier_reload)
+   {
+      psxScheduleIrq10(irq_count, gunx_scaled, guny_scaled);
+   }
 }
 
 /* sound calls */
@@ -541,25 +654,27 @@ void out_register_libretro(struct out_driver *drv)
    drv->feed   = snd_feed;
 }
 
-#define RETRO_DEVICE_PSE_STANDARD   RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD,   0)
-#define RETRO_DEVICE_PSE_ANALOG     RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG,   0)
-#define RETRO_DEVICE_PSE_DUALSHOCK  RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG,   1)
-#define RETRO_DEVICE_PSE_NEGCON     RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG,   2)
-#define RETRO_DEVICE_PSE_GUNCON     RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_LIGHTGUN, 0)
-#define RETRO_DEVICE_PSE_MOUSE      RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE,    0)
+#define RETRO_DEVICE_PSE_STANDARD         RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD,   0)
+#define RETRO_DEVICE_PSE_ANALOG           RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG,   0)
+#define RETRO_DEVICE_PSE_DUALSHOCK        RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG,   1)
+#define RETRO_DEVICE_PSE_NEGCON           RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_ANALOG,   2)
+#define RETRO_DEVICE_PSE_GUNCON           RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_LIGHTGUN, 0)
+#define RETRO_DEVICE_PSE_JUSTIFIER        RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_LIGHTGUN, 1)
+#define RETRO_DEVICE_PSE_MOUSE            RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE,    0)
 
 static char *get_pse_pad_label[] = {
    "none", "mouse", "negcon", "konami gun", "standard", "analog", "guncon", "dualshock"
 };
 
-static const struct retro_controller_description pads[7] =
+static const struct retro_controller_description pads[8] =
 {
-   { "standard",  RETRO_DEVICE_JOYPAD },
-   { "analog",    RETRO_DEVICE_PSE_ANALOG },
-   { "dualshock", RETRO_DEVICE_PSE_DUALSHOCK },
-   { "negcon",    RETRO_DEVICE_PSE_NEGCON },
-   { "guncon",    RETRO_DEVICE_PSE_GUNCON },
-   { "mouse",     RETRO_DEVICE_PSE_MOUSE },
+   { "standard",   RETRO_DEVICE_JOYPAD },
+   { "analog",     RETRO_DEVICE_PSE_ANALOG },
+   { "dualshock",  RETRO_DEVICE_PSE_DUALSHOCK },
+   { "negcon",     RETRO_DEVICE_PSE_NEGCON },
+   { "guncon",     RETRO_DEVICE_PSE_GUNCON },
+   { "konami gun", RETRO_DEVICE_PSE_JUSTIFIER },
+   { "mouse",      RETRO_DEVICE_PSE_MOUSE },
    { NULL, 0 },
 };
 
@@ -613,6 +728,10 @@ static bool update_option_visibility(void)
             "pcsx_rearmed_negcon_deadzone",
             "pcsx_rearmed_negcon_response",
             "pcsx_rearmed_input_sensitivity",
+            "pcsx_rearmed_crosshair1",
+            "pcsx_rearmed_crosshair2",
+            "pcsx_rearmed_konamigunadjustx",
+            "pcsx_rearmed_konamigunadjusty",
             "pcsx_rearmed_gunconadjustx",
             "pcsx_rearmed_gunconadjusty",
             "pcsx_rearmed_gunconadjustratiox",
@@ -853,6 +972,9 @@ void retro_set_controller_port_device(unsigned port, unsigned device)
    case RETRO_DEVICE_PSE_GUNCON:
       in_type[port] = PSE_PAD_TYPE_GUNCON;
       break;
+   case RETRO_DEVICE_PSE_JUSTIFIER:
+      in_type[port] = PSE_PAD_TYPE_GUN;
+      break;
    case RETRO_DEVICE_NONE:
    default:
       in_type[port] = PSE_PAD_TYPE_NONE;
@@ -869,18 +991,18 @@ void retro_get_system_info(struct retro_system_info *info)
 #endif
    memset(info, 0, sizeof(*info));
    info->library_name     = "PCSX-ReARMed";
-   info->library_version  = "r23l" GIT_VERSION;
-   info->valid_extensions = "bin|cue|img|mdf|pbp|toc|cbn|m3u|chd";
+   info->library_version  = "r24l" GIT_VERSION;
+   info->valid_extensions = "bin|cue|img|mdf|pbp|toc|cbn|m3u|chd|iso|exe";
    info->need_fullpath    = true;
 }
 
 void retro_get_system_av_info(struct retro_system_av_info *info)
 {
-   unsigned geom_height          = vout_height > 0 ? vout_height : 240;
-   unsigned geom_width           = vout_width > 0 ? vout_width : 320;
+   unsigned geom_height          = vout_height;
+   unsigned geom_width           = vout_width;
 
    memset(info, 0, sizeof(*info));
-   info->timing.fps              = is_pal_mode ? 50.0 : 60.0;
+   info->timing.fps              = psxGetFps();
    info->timing.sample_rate      = 44100.0;
    info->geometry.base_width     = geom_width;
    info->geometry.base_height    = geom_height;
@@ -971,7 +1093,7 @@ static void save_close(void *file)
       return;
 
    if (fp->pos > r_size)
-      SysPrintf("ERROR: save buffer overflow detected\n");
+      LogErr("ERROR: save buffer overflow detected\n");
    else if (fp->is_write && fp->pos < r_size)
       // make sure we don't save trash in leftover space
       memset(fp->buf + fp->pos, 0, r_size - fp->pos);
@@ -980,14 +1102,24 @@ static void save_close(void *file)
 
 bool retro_serialize(void *data, size_t size)
 {
-   int ret = SaveState(data);
+   int ret;
+   CdromFrontendId = disk_current_index;
+   ret = SaveState(data);
    return ret == 0 ? true : false;
 }
 
+static bool disk_set_image_index(unsigned int index);
+
 bool retro_unserialize(const void *data, size_t size)
 {
-   int ret = LoadState(data);
-   return ret == 0 ? true : false;
+   int ret;
+   CdromFrontendId = -1;
+   ret = LoadState(data);
+   if (ret)
+      return false;
+   if (CdromFrontendId != -1 && CdromFrontendId != disk_current_index)
+      disk_set_image_index(CdromFrontendId);
+   return true;
 }
 
 /* cheats */
@@ -998,12 +1130,13 @@ void retro_cheat_reset(void)
 
 void retro_cheat_set(unsigned index, bool enabled, const char *code)
 {
-   char buf[256];
-   int ret;
+   int ret = -1;
+   char *buf;
 
-   // cheat funcs are destructive, need a copy..
-   strncpy(buf, code, sizeof(buf));
-   buf[sizeof(buf) - 1] = 0;
+   // cheat funcs are destructive, need a copy...
+   buf = strdup(code);
+   if (buf == NULL)
+      goto finish;
 
    //Prepare buffered cheat for PCSX's AddCheat fucntion.
    int cursor = 0;
@@ -1029,10 +1162,12 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code)
    else
       ret = AddCheat("", buf);
 
+finish:
    if (ret != 0)
-      SysPrintf("Failed to set cheat %#u\n", index);
+      LogErr("Failed to set cheat %#u\n", index);
    else if (index < NumCheats)
       Cheats[index].Enabled = enabled;
+   free(buf);
 }
 
 // just in case, maybe a win-rt port in the future?
@@ -1050,7 +1185,6 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code)
 static unsigned int disk_initial_index;
 static char disk_initial_path[PATH_MAX];
 static bool disk_ejected;
-static unsigned int disk_current_index;
 static unsigned int disk_count;
 static struct disks_state
 {
@@ -1105,12 +1239,31 @@ static void disk_init(void)
    }
 }
 
+#ifdef HAVE_CDROM
+static long CALLBACK rcdrom_open(void);
+static long CALLBACK rcdrom_close(void);
+static void rcdrom_stop_thread(void);
+#endif
+
 static bool disk_set_eject_state(bool ejected)
 {
-   // weird PCSX API..
+   if (ejected != disk_ejected)
+      SysPrintf("new eject_state: %d\n", ejected);
+
+   // weird PCSX API...
    SetCdOpenCaseTime(ejected ? -1 : (time(NULL) + 2));
    LidInterrupt();
 
+#ifdef HAVE_CDROM
+   if (CDR_open == rcdrom_open && ejected != disk_ejected) {
+      rcdrom_stop_thread();
+      if (!ejected) {
+         // likely the real cd was also changed - rescan
+         rcdrom_close();
+         rcdrom_open();
+      }
+   }
+#endif
    disk_ejected = ejected;
    return true;
 }
@@ -1136,7 +1289,7 @@ static bool disk_set_image_index(unsigned int index)
 
    if (disks[index].fname == NULL)
    {
-      SysPrintf("missing disk #%u\n", index);
+      LogErr("missing disk #%u\n", index);
       CDR_shutdown();
 
       // RetroArch specifies "no disk" with index == count,
@@ -1145,26 +1298,25 @@ static bool disk_set_image_index(unsigned int index)
       return true;
    }
 
-   SysPrintf("switching to disk %u: \"%s\" #%d\n", index,
+   LogErr("switching to disk %u: \"%s\" #%d\n", index,
        disks[index].fname, disks[index].internal_index);
 
    cdrIsoMultidiskSelect = disks[index].internal_index;
    set_cd_image(disks[index].fname);
    if (ReloadCdromPlugin() < 0)
    {
-      SysPrintf("failed to load cdr plugin\n");
+      LogErr("failed to load cdr plugin\n");
       return false;
    }
    if (CDR_open() < 0)
    {
-      SysPrintf("failed to open cdr plugin\n");
+      LogErr("failed to open cdr plugin\n");
       return false;
    }
 
    if (!disk_ejected)
    {
-      SetCdOpenCaseTime(time(NULL) + 2);
-      LidInterrupt();
+      disk_set_eject_state(disk_ejected);
    }
 
    disk_current_index = index;
@@ -1365,6 +1517,308 @@ static void extract_directory(char *buf, const char *path, size_t size)
    }
 }
 
+// raw cdrom support
+#ifdef HAVE_CDROM
+#include "vfs/vfs_implementation.h"
+#include "vfs/vfs_implementation_cdrom.h"
+#include "libretro-cdrom.h"
+#include "rthreads/rthreads.h"
+#include "retro_timers.h"
+struct cached_buf {
+   unsigned char buf[2352];
+   unsigned int lba;
+};
+static struct {
+   libretro_vfs_implementation_file *h;
+   sthread_t *thread;
+   slock_t *read_lock;
+   slock_t *buf_lock;
+   scond_t *cond;
+   struct cached_buf *buf;
+   unsigned int buf_cnt, thread_exit, do_prefetch;
+   unsigned int total_lba, prefetch_lba;
+   int check_eject_delay;
+} rcdrom;
+
+static void lbacache_do(unsigned int lba)
+{
+   unsigned char m, s, f, buf[2352];
+   unsigned int i = lba % rcdrom.buf_cnt;
+   int ret;
+
+   cdrom_lba_to_msf(lba + 150, &m, &s, &f);
+   slock_lock(rcdrom.read_lock);
+   ret = cdrom_read_sector(rcdrom.h, lba, buf);
+   slock_lock(rcdrom.buf_lock);
+   slock_unlock(rcdrom.read_lock);
+   //printf("%d:%02d:%02d m%d f%d\n", m, s, f, buf[12+3], ((buf[12+4+2] >> 5) & 1) + 1);
+   if (ret) {
+      rcdrom.do_prefetch = 0;
+      slock_unlock(rcdrom.buf_lock);
+      LogErr("prefetch: cdrom_read_sector failed for lba %d\n", lba);
+      return;
+   }
+   rcdrom.check_eject_delay = 100;
+
+   if (lba != rcdrom.buf[i].lba) {
+      memcpy(rcdrom.buf[i].buf, buf, sizeof(rcdrom.buf[i].buf));
+      rcdrom.buf[i].lba = lba;
+   }
+   slock_unlock(rcdrom.buf_lock);
+   retro_sleep(0); // why does the main thread stall without this?
+}
+
+static int lbacache_get(unsigned int lba, void *buf)
+{
+   unsigned int i;
+   int ret = 0;
+
+   i = lba % rcdrom.buf_cnt;
+   slock_lock(rcdrom.buf_lock);
+   if (lba == rcdrom.buf[i].lba) {
+      memcpy(buf, rcdrom.buf[i].buf, 2352);
+      ret = 1;
+   }
+   slock_unlock(rcdrom.buf_lock);
+   return ret;
+}
+
+static void rcdrom_prefetch_thread(void *unused)
+{
+   unsigned int buf_cnt, lba, lba_to;
+
+   slock_lock(rcdrom.buf_lock);
+   while (!rcdrom.thread_exit)
+   {
+#ifdef __GNUC__
+      __asm__ __volatile__("":::"memory"); // barrier
+#endif
+      if (!rcdrom.do_prefetch)
+         scond_wait(rcdrom.cond, rcdrom.buf_lock);
+      if (!rcdrom.do_prefetch || !rcdrom.h || rcdrom.thread_exit)
+         continue;
+
+      buf_cnt = rcdrom.buf_cnt;
+      lba = rcdrom.prefetch_lba;
+      lba_to = lba + buf_cnt;
+      if (lba_to > rcdrom.total_lba)
+         lba_to = rcdrom.total_lba;
+      for (; lba < lba_to; lba++) {
+         if (lba != rcdrom.buf[lba % buf_cnt].lba)
+            break;
+      }
+      if (lba == lba_to) {
+         // caching complete
+         rcdrom.do_prefetch = 0;
+         continue;
+      }
+
+      slock_unlock(rcdrom.buf_lock);
+      lbacache_do(lba);
+      slock_lock(rcdrom.buf_lock);
+   }
+   slock_unlock(rcdrom.buf_lock);
+}
+
+static void rcdrom_stop_thread(void)
+{
+   rcdrom.thread_exit = 1;
+   if (rcdrom.buf_lock) {
+      slock_lock(rcdrom.buf_lock);
+      rcdrom.do_prefetch = 0;
+      if (rcdrom.cond)
+         scond_signal(rcdrom.cond);
+      slock_unlock(rcdrom.buf_lock);
+   }
+   if (rcdrom.thread) {
+      sthread_join(rcdrom.thread);
+      rcdrom.thread = NULL;
+   }
+   if (rcdrom.cond) { scond_free(rcdrom.cond); rcdrom.cond = NULL; }
+   if (rcdrom.buf_lock) { slock_free(rcdrom.buf_lock); rcdrom.buf_lock = NULL; }
+   if (rcdrom.read_lock) { slock_free(rcdrom.read_lock); rcdrom.read_lock = NULL; }
+   free(rcdrom.buf);
+   rcdrom.buf = NULL;
+}
+
+// the thread is optional, if anything fails we can do direct reads
+static void rcdrom_start_thread(void)
+{
+   rcdrom_stop_thread();
+   rcdrom.thread_exit = rcdrom.prefetch_lba = rcdrom.do_prefetch = 0;
+   if (rcdrom.buf_cnt == 0)
+      return;
+   rcdrom.buf = calloc(rcdrom.buf_cnt, sizeof(rcdrom.buf[0]));
+   rcdrom.buf_lock = slock_new();
+   rcdrom.read_lock = slock_new();
+   rcdrom.cond = scond_new();
+   if (rcdrom.buf && rcdrom.buf_lock && rcdrom.read_lock && rcdrom.cond) {
+      rcdrom.thread = sthread_create(rcdrom_prefetch_thread, NULL);
+      rcdrom.buf[0].lba = ~0;
+   }
+   if (!rcdrom.thread) {
+      LogErr("cdrom precache thread init failed.\n");
+      rcdrom_stop_thread();
+   }
+}
+
+static long CALLBACK rcdrom_open(void)
+{
+   const char *name = GetIsoFile();
+   //printf("%s %s\n", __func__, name);
+   rcdrom.h = retro_vfs_file_open_impl(name, RETRO_VFS_FILE_ACCESS_READ,
+      RETRO_VFS_FILE_ACCESS_HINT_NONE);
+   if (rcdrom.h) {
+      int ret = cdrom_set_read_speed_x(rcdrom.h, 4);
+      if (ret) LogErr("CD speed set failed\n");
+      const cdrom_toc_t *toc = retro_vfs_file_get_cdrom_toc();
+      const cdrom_track_t *last = &toc->track[toc->num_tracks - 1];
+      unsigned int lba = cdrom_msf_to_lba(last->min, last->sec, last->frame) - 150;
+      rcdrom.total_lba = lba + last->track_size;
+      //cdrom_get_current_config_random_readable(rcdrom.h);
+      //cdrom_get_current_config_multiread(rcdrom.h);
+      //cdrom_get_current_config_cdread(rcdrom.h);
+      //cdrom_get_current_config_profiles(rcdrom.h);
+      rcdrom_start_thread();
+      return 0;
+   }
+   LogErr("retro_vfs_file_open failed for '%s'\n", name);
+   return -1;
+}
+
+static long CALLBACK rcdrom_close(void)
+{
+   //printf("%s\n", __func__);
+   if (rcdrom.h) {
+      rcdrom_stop_thread();
+      retro_vfs_file_close_impl(rcdrom.h);
+      rcdrom.h = NULL;
+   }
+   return 0;
+}
+
+static long CALLBACK rcdrom_getTN(unsigned char *tn)
+{
+   const cdrom_toc_t *toc = retro_vfs_file_get_cdrom_toc();
+   tn[0] = 1;
+   tn[1] = toc->num_tracks;
+   //printf("%s -> %d %d\n", __func__, tn[0], tn[1]);
+   return 0;
+}
+
+static long CALLBACK rcdrom_getTD(unsigned char track, unsigned char *rt)
+{
+   const cdrom_toc_t *toc = retro_vfs_file_get_cdrom_toc();
+   rt[0] = 0, rt[1] = 2, rt[2] = 0;
+   if (track == 0) {
+      cdrom_lba_to_msf(rcdrom.total_lba + 150, &rt[2], &rt[1], &rt[0]);
+   }
+   else if (track <= toc->num_tracks) {
+      int i = track - 1;
+      rt[2] = toc->track[i].min;
+      rt[1] = toc->track[i].sec;
+      rt[0] = toc->track[i].frame;
+   }
+   //printf("%s %d -> %d:%02d:%02d\n", __func__, track, rt[2], rt[1], rt[0]);
+   return 0;
+}
+
+static long CALLBACK rcdrom_prefetch(unsigned char m, unsigned char s, unsigned char f)
+{
+   unsigned int lba = cdrom_msf_to_lba(m, s, f) - 150;
+   if (rcdrom.cond && rcdrom.h) {
+      rcdrom.prefetch_lba = lba;
+      rcdrom.do_prefetch = 1;
+      scond_signal(rcdrom.cond);
+   }
+   if (rcdrom.buf) {
+     unsigned int c = rcdrom.buf_cnt;
+     if (c)
+        return rcdrom.buf[lba % c].lba == lba;
+   }
+   return 1;
+}
+
+static int rcdrom_read_msf(unsigned char m, unsigned char s, unsigned char f,
+      void *buf, const char *func)
+{
+   unsigned int lba = cdrom_msf_to_lba(m, s, f) - 150;
+   int hit = 0, ret = -1;
+   if (rcdrom.buf_lock)
+      hit = lbacache_get(lba, buf);
+   if (!hit && rcdrom.read_lock) {
+      // maybe still prefetching
+      slock_lock(rcdrom.read_lock);
+      slock_unlock(rcdrom.read_lock);
+      hit = lbacache_get(lba, buf);
+      if (hit)
+         hit = 2;
+   }
+   if (!hit) {
+      slock_t *lock = rcdrom.read_lock;
+      rcdrom.do_prefetch = 0;
+      if (lock)
+         slock_lock(lock);
+      if (rcdrom.h) {
+         ret = cdrom_read_sector(rcdrom.h, lba, buf);
+         if (ret)
+            LogErr("cdrom_read_sector failed for lba %d\n", lba);
+      }
+      if (lock)
+         slock_unlock(lock);
+   }
+   else
+      ret = 0;
+   rcdrom.check_eject_delay = ret ? 0 : 100;
+   //printf("%s %d:%02d:%02d -> %d hit %d\n", func, m, s, f, ret, hit);
+   return ret;
+}
+
+static boolean CALLBACK rcdrom_readTrack(unsigned char *time)
+{
+   unsigned char m = btoi(time[0]), s = btoi(time[1]), f = btoi(time[2]);
+   return !rcdrom_read_msf(m, s, f, ISOgetBuffer() - 12, __func__);
+}
+
+static long CALLBACK rcdrom_readCDDA(unsigned char m, unsigned char s, unsigned char f,
+      unsigned char *buffer)
+{
+   return rcdrom_read_msf(m, s, f, buffer, __func__);
+}
+
+static unsigned char * CALLBACK rcdrom_getBuffer(void)
+{
+   //printf("%s\n", __func__);
+   return ISOgetBuffer();
+}
+
+static unsigned char * CALLBACK rcdrom_getBufferSub(int sector)
+{
+   //printf("%s %d %d\n", __func__, sector, rcdrom_h->cdrom.last_frame_lba);
+   return NULL;
+}
+
+static long CALLBACK rcdrom_getStatus(struct CdrStat *stat)
+{
+   const cdrom_toc_t *toc = retro_vfs_file_get_cdrom_toc();
+   //printf("%s %p\n", __func__, stat);
+   CDR__getStatus(stat);
+   stat->Type = toc->track[0].audio ? 2 : 1;
+   return 0;
+}
+
+static void rcdrom_check_eject(void)
+{
+   bool media_inserted;
+   if (!rcdrom.h || rcdrom.do_prefetch || rcdrom.check_eject_delay-- > 0)
+      return;
+   rcdrom.check_eject_delay = 100;
+   media_inserted = cdrom_is_media_inserted(rcdrom.h); // 1-2ms
+   if (!media_inserted != disk_ejected)
+      disk_set_eject_state(!media_inserted);
+}
+#endif // HAVE_CDROM
+
 #if defined(__QNX__) || defined(_WIN32)
 /* Blackberry QNX doesn't have strcasestr */
 
@@ -1410,6 +1864,32 @@ static void set_retro_memmap(void)
 #endif
 }
 
+static void show_notification(const char *msg_str,
+      unsigned duration_ms, unsigned priority)
+{
+   if (msg_interface_version >= 1)
+   {
+      struct retro_message_ext msg = {
+         msg_str,
+         duration_ms,
+         3,
+         RETRO_LOG_WARN,
+         RETRO_MESSAGE_TARGET_ALL,
+         RETRO_MESSAGE_TYPE_NOTIFICATION,
+         -1
+      };
+      environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg);
+   }
+   else
+   {
+      struct retro_message msg = {
+         msg_str,
+         180
+      };
+      environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg);
+   }
+}
+
 static void retro_audio_buff_status_cb(
    bool active, unsigned occupancy, bool underrun_likely)
 {
@@ -1471,6 +1951,8 @@ bool retro_load_game(const struct retro_game_info *info)
    size_t i;
    unsigned int cd_index = 0;
    bool is_m3u = (strcasestr(info->path, ".m3u") != NULL);
+   bool is_exe = (strcasestr(info->path, ".exe") != NULL);
+   int ret;
 
    struct retro_input_descriptor desc[] = {
 #define JOYP(port)                                                                                                \
@@ -1497,7 +1979,8 @@ bool retro_load_game(const struct retro_game_info *info)
       { port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER, "Gun Trigger" },                        \
       { port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD,  "Gun Reload" },                         \
       { port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_A,   "Gun Aux A" },                          \
-      { port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_B,   "Gun Aux B" },
+      { port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_B,   "Gun Aux B" },                          \
+      { port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_START,   "Gun Start" },
       
       JOYP(0)
       JOYP(1)
@@ -1525,7 +2008,7 @@ bool retro_load_game(const struct retro_game_info *info)
 
    if (info == NULL || info->path == NULL)
    {
-      SysPrintf("info->path required\n");
+      LogErr("info->path required\n");
       return false;
    }
 
@@ -1545,7 +2028,7 @@ bool retro_load_game(const struct retro_game_info *info)
    {
       if (!read_m3u(info->path))
       {
-         log_cb(RETRO_LOG_INFO, "failed to read m3u file\n");
+         LogErr("failed to read m3u file\n");
          return false;
       }
    }
@@ -1578,8 +2061,28 @@ bool retro_load_game(const struct retro_game_info *info)
    /* have to reload after set_cd_image for correct cdr plugin */
    if (LoadPlugins() == -1)
    {
-      log_cb(RETRO_LOG_INFO, "failed to load plugins\n");
+      LogErr("failed to load plugins\n");
+      return false;
+   }
+   if (!strncmp(info->path, "cdrom:", 6))
+   {
+#ifdef HAVE_CDROM
+      CDR_open = rcdrom_open;
+      CDR_close = rcdrom_close;
+      CDR_getTN = rcdrom_getTN;
+      CDR_getTD = rcdrom_getTD;
+      CDR_readTrack = rcdrom_readTrack;
+      CDR_getBuffer = rcdrom_getBuffer;
+      CDR_getBufferSub = rcdrom_getBufferSub;
+      CDR_getStatus = rcdrom_getStatus;
+      CDR_readCDDA = rcdrom_readCDDA;
+      CDR_prefetch = rcdrom_prefetch;
+#elif !defined(USE_LIBRETRO_VFS)
+      ReleasePlugins();
+      LogErr("%s\n", "Physical CD-ROM support is not compiled in.");
+      show_notification("Physical CD-ROM support is not compiled in.", 6000, 3);
       return false;
+#endif
    }
 
    plugins_opened = 1;
@@ -1587,7 +2090,7 @@ bool retro_load_game(const struct retro_game_info *info)
 
    if (OpenPlugins() == -1)
    {
-      log_cb(RETRO_LOG_INFO, "failed to open plugins\n");
+      LogErr("failed to open plugins\n");
       return false;
    }
 
@@ -1646,12 +2149,12 @@ bool retro_load_game(const struct retro_game_info *info)
 
          if (ReloadCdromPlugin() < 0)
          {
-            log_cb(RETRO_LOG_INFO, "failed to reload cdr plugins\n");
+            LogErr("failed to reload cdr plugins\n");
             return false;
          }
          if (CDR_open() < 0)
          {
-            log_cb(RETRO_LOG_INFO, "failed to open cdr plugin\n");
+            LogErr("failed to open cdr plugin\n");
             return false;
          }
       }
@@ -1664,17 +2167,21 @@ bool retro_load_game(const struct retro_game_info *info)
    plugin_call_rearmed_cbs();
    /* dfinput_activate(); */
 
-   if (CheckCdrom() == -1)
+   if (!is_exe && CheckCdrom() == -1)
    {
-      log_cb(RETRO_LOG_INFO, "unsupported/invalid CD image: %s\n", info->path);
+      LogErr("unsupported/invalid CD image: %s\n", info->path);
       return false;
    }
 
    SysReset();
 
-   if (LoadCdrom() == -1)
+   if (is_exe)
+      ret = Load(info->path);
+   else
+      ret = LoadCdrom();
+   if (ret != 0)
    {
-      log_cb(RETRO_LOG_INFO, "could not load CD\n");
+      LogErr("could not load %s (%d)\n", is_exe ? "exe" : "CD", ret);
       return false;
    }
    emu_on_new_cd(0);
@@ -1682,6 +2189,9 @@ bool retro_load_game(const struct retro_game_info *info)
    set_retro_memmap();
    retro_set_audio_buff_status_cb();
 
+   if (check_unsatisfied_libcrypt())
+      show_notification("LibCrypt protected game with missing SBI detected", 3000, 3);
+
    return true;
 }
 
@@ -1737,11 +2247,11 @@ static const unsigned short retro_psx_map[] = {
 };
 #define RETRO_PSX_MAP_LEN (sizeof(retro_psx_map) / sizeof(retro_psx_map[0]))
 
-//Percentage distance of screen to adjust
+//Percentage distance of screen to adjust for Guncon
 static int GunconAdjustX = 0;
 static int GunconAdjustY = 0;
 
-//Used when out by a percentage
+//Used when out by a percentage with Guncon
 static float GunconAdjustRatioX = 1;
 static float GunconAdjustRatioY = 1;
 
@@ -1755,6 +2265,7 @@ static void update_variables(bool in_flight)
    int gpu_peops_fix = GPU_PEOPS_OLD_FRAME_SKIP;
 #endif
    frameskip_type_t prev_frameskip_type;
+   double old_fps = psxGetFps();
 
    var.value = NULL;
    var.key = "pcsx_rearmed_frameskip_type";
@@ -1854,6 +2365,30 @@ static void update_variables(bool in_flight)
          in_enable_vibration = 1;
    }
 
+   var.value = NULL;
+   var.key = "pcsx_rearmed_analog_combo";
+
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      if (strcmp(var.value, "l1+r1+select") == 0)
+         in_dualshock_analog_combo = (1 << RETRO_DEVICE_ID_JOYPAD_L) |
+           (1 << RETRO_DEVICE_ID_JOYPAD_R) | (1 << RETRO_DEVICE_ID_JOYPAD_SELECT);
+      else if (strcmp(var.value, "l1+r1+start") == 0)
+         in_dualshock_analog_combo = (1 << RETRO_DEVICE_ID_JOYPAD_L) |
+           (1 << RETRO_DEVICE_ID_JOYPAD_R) | (1 << RETRO_DEVICE_ID_JOYPAD_START);
+      else if (strcmp(var.value, "l1+r1+l3") == 0)
+         in_dualshock_analog_combo = (1 << RETRO_DEVICE_ID_JOYPAD_L) |
+           (1 << RETRO_DEVICE_ID_JOYPAD_R) | (1 << RETRO_DEVICE_ID_JOYPAD_L3);
+      else if (strcmp(var.value, "l1+r1+r3") == 0)
+         in_dualshock_analog_combo = (1 << RETRO_DEVICE_ID_JOYPAD_L) |
+           (1 << RETRO_DEVICE_ID_JOYPAD_R) | (1 << RETRO_DEVICE_ID_JOYPAD_R3);
+      else if (strcmp(var.value, "l3+r3") == 0)
+         in_dualshock_analog_combo = (1 << RETRO_DEVICE_ID_JOYPAD_L3) |
+           (1 << RETRO_DEVICE_ID_JOYPAD_R3);
+      else
+         in_dualshock_analog_combo = 0;
+   }
+
    var.value = NULL;
    var.key = "pcsx_rearmed_dithering";
 
@@ -1881,7 +2416,7 @@ static void update_variables(bool in_flight)
 
 #ifdef GPU_NEON
    var.value = NULL;
-   var.key = "pcsx_rearmed_neon_interlace_enable";
+   var.key = "pcsx_rearmed_neon_interlace_enable_v2";
 
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
    {
@@ -1889,6 +2424,8 @@ static void update_variables(bool in_flight)
          pl_rearmed_cbs.gpu_neon.allow_interlace = 0;
       else if (strcmp(var.value, "enabled") == 0)
          pl_rearmed_cbs.gpu_neon.allow_interlace = 1;
+      else // auto
+         pl_rearmed_cbs.gpu_neon.allow_interlace = 2;
    }
 
    var.value = NULL;
@@ -1907,23 +2444,23 @@ static void update_variables(bool in_flight)
 
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
    {
-      if (strcmp(var.value, "disabled") == 0)
-         pl_rearmed_cbs.gpu_neon.enhancement_no_main = 0;
-      else if (strcmp(var.value, "enabled") == 0)
+      if (strcmp(var.value, "enabled") == 0)
          pl_rearmed_cbs.gpu_neon.enhancement_no_main = 1;
+      else
+         pl_rearmed_cbs.gpu_neon.enhancement_no_main = 0;
    }
-#endif
 
    var.value = NULL;
-   var.key = "pcsx_rearmed_duping_enable";
+   var.key = "pcsx_rearmed_neon_enhancement_tex_adj";
 
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
    {
-      if (strcmp(var.value, "disabled") == 0)
-         duping_enable = false;
-      else if (strcmp(var.value, "enabled") == 0)
-         duping_enable = true;
+      if (strcmp(var.value, "enabled") == 0)
+         pl_rearmed_cbs.gpu_neon.enhancement_tex_adj = 1;
+      else
+         pl_rearmed_cbs.gpu_neon.enhancement_tex_adj = 0;
    }
+#endif
 
    var.value = NULL;
    var.key = "pcsx_rearmed_display_internal_fps";
@@ -1936,6 +2473,26 @@ static void update_variables(bool in_flight)
          display_internal_fps = true;
    }
 
+#ifdef HAVE_CDROM
+   var.value = NULL;
+   var.key = "pcsx_rearmed_phys_cd_readahead";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      long newval = strtol(var.value, NULL, 10);
+      bool changed = rcdrom.buf_cnt != newval;
+      if (rcdrom.h && changed)
+         rcdrom_stop_thread();
+      rcdrom.buf_cnt = newval;
+      if (rcdrom.h && changed) {
+         rcdrom_start_thread();
+         if (rcdrom.cond && rcdrom.prefetch_lba) {
+            rcdrom.do_prefetch = 1;
+            scond_signal(rcdrom.cond);
+         }
+      }
+   }
+#endif
+
    //
    // CPU emulation related config
 #ifndef DRC_DISABLE
@@ -1947,18 +2504,13 @@ static void update_variables(bool in_flight)
 
    {
       R3000Acpu *prev_cpu = psxCpu;
-#if defined(LIGHTREC)
-      bool can_use_dynarec = found_bios;
-#else
-      bool can_use_dynarec = 1;
-#endif
 
 #ifdef _3DS
       if (!__ctr_svchax)
          Config.Cpu = CPU_INTERPRETER;
       else
 #endif
-      if (strcmp(var.value, "disabled") == 0 || !can_use_dynarec)
+      if (strcmp(var.value, "disabled") == 0)
          Config.Cpu = CPU_INTERPRETER;
       else if (strcmp(var.value, "enabled") == 0)
          Config.Cpu = CPU_DYNAREC;
@@ -1966,9 +2518,11 @@ static void update_variables(bool in_flight)
       psxCpu = (Config.Cpu == CPU_INTERPRETER) ? &psxInt : &psxRec;
       if (psxCpu != prev_cpu)
       {
+         prev_cpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
          prev_cpu->Shutdown();
          psxCpu->Init();
-         psxCpu->Reset(); // not really a reset..
+         psxCpu->Reset();
+         psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
       }
    }
 #endif /* !DRC_DISABLE */
@@ -2043,6 +2597,16 @@ static void update_variables(bool in_flight)
          Config.icache_emulation = 1;
    }
 
+   var.value = NULL;
+   var.key = "pcsx_rearmed_exception_emulation";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      if (strcmp(var.value, "enabled") == 0)
+         Config.PreciseExceptions = 1;
+      else
+         Config.PreciseExceptions = 0;
+   }
+
    psxCpu->ApplyConfig();
 
    // end of CPU emu config
@@ -2074,26 +2638,40 @@ static void update_variables(bool in_flight)
          spu_config.iUseInterpolation = 0;
    }
 
-#ifndef _WIN32
+#if P_HAVE_PTHREAD
    var.value = NULL;
-   var.key = "pcsx_rearmed_async_cd";
+   var.key = "pcsx_rearmed_spu_thread";
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
    {
-      if (strcmp(var.value, "async") == 0)
-      {
-         Config.AsyncCD = 1;
-         Config.CHD_Precache = 0;
-      }
-      else if (strcmp(var.value, "sync") == 0)
-      {
-         Config.AsyncCD = 0;
-         Config.CHD_Precache = 0;
-      }
-      else if (strcmp(var.value, "precache") == 0)
-      {
-         Config.AsyncCD = 0;
-         Config.CHD_Precache = 1;
-      }
+      if (strcmp(var.value, "enabled") == 0)
+         spu_config.iUseThread = 1;
+      else
+         spu_config.iUseThread = 0;
+   }
+#endif
+
+#if 0 // currently disabled, see USE_READ_THREAD in libpcsxcore/cdriso.c
+   if (P_HAVE_PTHREAD) {
+          var.value = NULL;
+          var.key = "pcsx_rearmed_async_cd";
+          if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+          {
+                 if (strcmp(var.value, "async") == 0)
+                 {
+                        Config.AsyncCD = 1;
+                        Config.CHD_Precache = 0;
+                 }
+                 else if (strcmp(var.value, "sync") == 0)
+                 {
+                        Config.AsyncCD = 0;
+                        Config.CHD_Precache = 0;
+                 }
+                 else if (strcmp(var.value, "precache") == 0)
+                 {
+                        Config.AsyncCD = 0;
+                        Config.CHD_Precache = 1;
+                 }
+       }
    }
 #endif
 
@@ -2117,6 +2695,58 @@ static void update_variables(bool in_flight)
          Config.Cdda = 0;
    }
 
+   var.value = NULL;
+   var.key = "pcsx_rearmed_gpu_slow_llists";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      if (strcmp(var.value, "disabled") == 0)
+         Config.GpuListWalking = 0;
+      else if (strcmp(var.value, "enabled") == 0)
+         Config.GpuListWalking = 1;
+      else // auto
+         Config.GpuListWalking = -1;
+   }
+
+   var.value = NULL;
+   var.key = "pcsx_rearmed_fractional_framerate";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      if (strcmp(var.value, "disabled") == 0)
+         Config.FractionalFramerate = 0;
+      else if (strcmp(var.value, "enabled") == 0)
+         Config.FractionalFramerate = 1;
+      else // auto
+         Config.FractionalFramerate = -1;
+   }
+
+   var.value = NULL;
+   var.key = "pcsx_rearmed_screen_centering";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      if (strcmp(var.value, "game") == 0)
+         pl_rearmed_cbs.screen_centering_type = 1;
+      else if (strcmp(var.value, "borderless") == 0)
+         pl_rearmed_cbs.screen_centering_type = 2;
+      else if (strcmp(var.value, "manual") == 0)
+         pl_rearmed_cbs.screen_centering_type = 3;
+      else // auto
+         pl_rearmed_cbs.screen_centering_type = 0;
+   }
+
+   var.value = NULL;
+   var.key = "pcsx_rearmed_screen_centering_x";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      pl_rearmed_cbs.screen_centering_x = atoi(var.value);
+   }
+
+   var.value = NULL;
+   var.key = "pcsx_rearmed_screen_centering_y";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      pl_rearmed_cbs.screen_centering_y = atoi(var.value);
+   }
+
 #ifdef THREAD_RENDERING
    var.key = "pcsx_rearmed_gpu_thread_rendering";
    var.value = NULL;
@@ -2265,9 +2895,59 @@ static void update_variables(bool in_flight)
    }
 #endif // GPU_UNAI
 
+   var.value = NULL;
+   var.key = "pcsx_rearmed_crosshair1";
+
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      if (strcmp(var.value, "disabled") == 0)
+         in_enable_crosshair[0] = 0;
+      else if (strcmp(var.value, "blue") == 0)
+         in_enable_crosshair[0] = 0x1F;
+      else if (strcmp(var.value, "green") == 0)
+         in_enable_crosshair[0] = 0x7E0;
+      else if (strcmp(var.value, "red") == 0)
+         in_enable_crosshair[0] = 0xF800;
+      else if (strcmp(var.value, "white") == 0)
+         in_enable_crosshair[0] = 0xFFFF;
+   }
+
+   var.value = NULL;
+   var.key = "pcsx_rearmed_crosshair2";
+
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      if (strcmp(var.value, "disabled") == 0)
+         in_enable_crosshair[1] = 0;
+      else if (strcmp(var.value, "blue") == 0)
+         in_enable_crosshair[1] = 0x1F;
+      else if (strcmp(var.value, "green") == 0)
+         in_enable_crosshair[1] = 0x7E0;
+      else if (strcmp(var.value, "red") == 0)
+         in_enable_crosshair[1] = 0xF800;
+      else if (strcmp(var.value, "white") == 0)
+         in_enable_crosshair[1] = 0xFFFF;
+   }
+
    //This adjustment process gives the user the ability to manually align the mouse up better
    //with where the shots are in the emulator.
 
+   var.value = NULL;
+   var.key = "pcsx_rearmed_konamigunadjustx";
+
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      KonamiGunAdjustX = atof(var.value) / 100.0f;
+   }
+
+   var.value = NULL;
+   var.key = "pcsx_rearmed_konamigunadjusty";
+
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+   {
+      KonamiGunAdjustY = atof(var.value) / 100.0f;
+   }
+
    var.value = NULL;
    var.key = "pcsx_rearmed_gunconadjustx";
 
@@ -2307,6 +2987,18 @@ static void update_variables(bool in_flight)
       mouse_sensitivity = atof(var.value);
    }
 
+   if (found_bios)
+   {
+      var.value = NULL;
+      var.key = "pcsx_rearmed_show_bios_bootlogo";
+      if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+      {
+         Config.SlowBoot = 0;
+         if (strcmp(var.value, "enabled") == 0)
+            Config.SlowBoot = 1;
+      }
+   }
+
    if (in_flight)
    {
       // inform core things about possible config changes
@@ -2324,29 +3016,15 @@ static void update_variables(bool in_flight)
 
       /* dfinput_activate(); */
    }
-   else
-   {
-      //not yet running
-
-      //bootlogo display hack
-      if (found_bios)
-      {
-         var.value = NULL;
-         var.key = "pcsx_rearmed_show_bios_bootlogo";
-         if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
-         {
-            Config.SlowBoot = 0;
-            rebootemu = 0;
-            if (strcmp(var.value, "enabled") == 0)
-            {
-               Config.SlowBoot = 1;
-               rebootemu = 1;
-            }
-         }
-      }
-   }
 
    update_option_visibility();
+
+   if (old_fps != psxGetFps())
+   {
+      struct retro_system_av_info info;
+      retro_get_system_av_info(&info);
+      environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &info);
+   }
 }
 
 // Taken from beetle-psx-libretro
@@ -2403,25 +3081,26 @@ unsigned char axis_range_modifier(int16_t axis_value, bool is_square)
 static void update_input_guncon(int port, int ret)
 {
    //ToDo:
-   //Core option for cursors for both players
    //Separate pointer and lightgun control types
 
    //Mouse range is -32767 -> 32767
    //1% is about 655
    //Use the left analog stick field to store the absolute coordinates
-   //Fix cursor to top-left when gun is detected as "offscreen"
+
+   int gunx = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X);
+   int guny = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y);
+
+   //Have the Libretro API let /libpcsxcore/plugins.c know when the lightgun is pointed offscreen
+   //Offscreen value is chosen to be well out of range of any possible scaling done via core options
    if (input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN) || input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD))
    {
-      in_analog_left[port][0] = -32767;
-      in_analog_left[port][1] = -32767;
+      in_analog_left[port][0] = 65536;
+      in_analog_left[port][1] = 65536;
    }
    else
    {
-      int gunx = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X);
-      int guny = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y);
-          
-      in_analog_left[port][0] = (gunx * GunconAdjustRatioX) + (GunconAdjustX * 655);
-      in_analog_left[port][1] = (guny * GunconAdjustRatioY) + (GunconAdjustY * 655);
+      in_analog_left[port][0] = ((gunx * GunconAdjustRatioX) + (GunconAdjustX * 655)) / 64 + 512;
+      in_analog_left[port][1] = ((guny * GunconAdjustRatioY) + (GunconAdjustY * 655)) / 64 + 512;
    }
        
    //GUNCON has 3 controls, Trigger,A,B which equal Circle,Start,Cross
@@ -2440,6 +3119,29 @@ static void update_input_guncon(int port, int ret)
           
 }
 
+static void update_input_justifier(int port, int ret)
+{
+   //ToDo:
+   //Separate pointer and lightgun control types
+
+   //RetroArch lightgun range is -32767 -> 32767 on both axes (positive Y is down)
+
+   //JUSTIFIER has 3 controls, Trigger,Special,Start which equal Square,Cross,Start
+
+   // Trigger
+   if (input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER) || input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD))
+      in_keystate[port] |= (1 << DKEY_SQUARE);
+
+   // Special
+   if (input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_A))
+      in_keystate[port] |= (1 << DKEY_CROSS);
+
+   // Start
+   if (input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_START))
+      in_keystate[port] |= (1 << DKEY_START);
+          
+}
+
 static void update_input_negcon(int port, int ret)
 {
    int lsx;
@@ -2578,13 +3280,13 @@ static void update_input_mouse(int port, int ret)
 
 static void update_input(void)
 {
-   // reset all keystate, query libretro for keystate
    int i;
    int j;
 
+   // reset all keystate, query libretro for keystate
    for (i = 0; i < PORTS_NUMBER; i++)
    {
-      int16_t ret = 0;
+      int32_t ret = 0;
       int type = in_type[i];
 
       in_keystate[i] = 0;
@@ -2593,7 +3295,11 @@ static void update_input(void)
          continue;
 
       if (libretro_supports_bitmasks)
+      {
          ret = input_state_cb(i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK);
+         // undo int16 sign extension (why input_state_cb returns int16 in the first place?)
+         ret &= (1 << (RETRO_DEVICE_ID_JOYPAD_R3 + 1)) - 1;
+      }
       else
       {
          for (j = 0; j < (RETRO_DEVICE_ID_JOYPAD_R3 + 1); j++)
@@ -2608,6 +3314,9 @@ static void update_input(void)
       case PSE_PAD_TYPE_GUNCON:
          update_input_guncon(i, ret);
          break;
+      case PSE_PAD_TYPE_GUN:
+         update_input_justifier(i, ret);
+         break;
       case PSE_PAD_TYPE_NEGCON:
          update_input_negcon(i, ret);
          break;
@@ -2615,7 +3324,23 @@ static void update_input(void)
          update_input_mouse(i, ret);
          break;      
       default:
-         // Query digital inputs
+         // dualshock ANALOG toggle?
+         if (type == PSE_PAD_TYPE_ANALOGPAD && in_dualshock_analog_combo != 0
+             && ret == in_dualshock_analog_combo)
+         {
+            if (!in_dualshock_toggling)
+            {
+               int state = padToggleAnalog(i);
+               char msg[32];
+               snprintf(msg, sizeof(msg), "ANALOG %s", state ? "ON" : "OFF");
+               show_notification(msg, 800, 1);
+               in_dualshock_toggling = true;
+            }
+            return;
+         }
+         in_dualshock_toggling = false;
+
+         // Set digital inputs
          for (j = 0; j < RETRO_PSX_MAP_LEN; j++)
             if (ret & (1 << j))
                in_keystate[i] |= retro_psx_map[j];
@@ -2684,14 +3409,11 @@ void retro_run(void)
    {
       rebootemu = 0;
       SysReset();
-      if (!Config.HLE && !Config.SlowBoot)
-      {
-         // skip BIOS logos
-         psxRegs.pc = psxRegs.GPR.n.ra;
-      }
-      return;
+      if (Config.HLE)
+         LoadCdrom();
    }
 
+   set_vout_fb();
    print_internal_fps();
 
    /* Check whether current frame should
@@ -2752,14 +3474,17 @@ void retro_run(void)
          frameskip_counter = 0;
    }
 
-   video_cb((vout_fb_dirty || !vout_can_dupe || !duping_enable) ? vout_buf_ptr : NULL,
-       vout_width, vout_height, vout_width * 2);
+   video_cb((vout_fb_dirty || !vout_can_dupe) ? vout_buf_ptr : NULL,
+       vout_width, vout_height, vout_pitch * 2);
    vout_fb_dirty = 0;
 
-   set_vout_fb();
+#ifdef HAVE_CDROM
+   if (CDR_open == rcdrom_open)
+      rcdrom_check_eject();
+#endif
 }
 
-static bool try_use_bios(const char *path)
+static bool try_use_bios(const char *path, bool preferred_only)
 {
    long size;
    const char *name;
@@ -2771,12 +3496,20 @@ static bool try_use_bios(const char *path)
    size = ftell(fp);
    fclose(fp);
 
-   if (size != 512 * 1024)
-      return false;
-
    name = strrchr(path, SLASH);
    if (name++ == NULL)
       name = path;
+
+   if (preferred_only && size != 512 * 1024)
+      return false;
+   if (size != 512 * 1024 && size != 4 * 1024 * 1024)
+      return false;
+   if (strstr(name, "unirom"))
+      return false;
+   // jp bios have an addidional region check
+   if (preferred_only && (strcasestr(name, "00.") || strcasestr(name, "j.bin")))
+      return false;
+
    snprintf(Config.Bios, sizeof(Config.Bios), "%s", name);
    return true;
 }
@@ -2787,24 +3520,50 @@ static bool try_use_bios(const char *path)
 
 static bool find_any_bios(const char *dirpath, char *path, size_t path_size)
 {
+   static const char *substr_pref[] = { "scph", "ps" };
+   static const char *substr_alt[] = { "scph", "ps", "openbios" };
    DIR *dir;
    struct dirent *ent;
    bool ret = false;
+   size_t i;
 
    dir = opendir(dirpath);
    if (dir == NULL)
       return false;
 
+   // try to find a "better" bios
    while ((ent = readdir(dir)))
    {
-      if ((strncasecmp(ent->d_name, "scph", 4) != 0) && (strncasecmp(ent->d_name, "psx", 3) != 0))
-         continue;
+      for (i = 0; i < sizeof(substr_pref) / sizeof(substr_pref[0]); i++)
+      {
+         const char *substr = substr_pref[i];
+         if ((strncasecmp(ent->d_name, substr, strlen(substr)) != 0))
+            continue;
+         snprintf(path, path_size, "%s%c%s", dirpath, SLASH, ent->d_name);
+         ret = try_use_bios(path, true);
+         if (ret)
+            goto finish;
+      }
+   }
 
-      snprintf(path, path_size, "%s%c%s", dirpath, SLASH, ent->d_name);
-      ret = try_use_bios(path);
-      if (ret)
-         break;
+   // another pass to look for anything fitting, even ps2 bios
+   rewinddir(dir);
+   while ((ent = readdir(dir)))
+   {
+      for (i = 0; i < sizeof(substr_alt) / sizeof(substr_alt[0]); i++)
+      {
+         const char *substr = substr_alt[i];
+         if ((strncasecmp(ent->d_name, substr, strlen(substr)) != 0))
+            continue;
+         snprintf(path, path_size, "%s%c%s", dirpath, SLASH, ent->d_name);
+         ret = try_use_bios(path, false);
+         if (ret)
+            goto finish;
+      }
    }
+
+
+finish:
    closedir(dir);
    return ret;
 }
@@ -2844,7 +3603,7 @@ static int init_memcards(void)
          {
             if (strlen(dir) + strlen(CARD2_FILE) + 2 > sizeof(Config.Mcd2))
             {
-               SysPrintf("Path '%s' is too long. Cannot use memcard 2. Use a shorter path.\n", dir);
+               LogErr("Path '%s' is too long. Cannot use memcard 2. Use a shorter path.\n", dir);
                ret = -1;
             }
             else
@@ -2856,7 +3615,7 @@ static int init_memcards(void)
          }
          else
          {
-            SysPrintf("Could not get save directory! Could not create memcard 2.");
+            LogErr("Could not get save directory! Could not create memcard 2.");
             ret = -1;
          }
       }
@@ -2901,7 +3660,7 @@ static void loadPSXBios(void)
          for (i = 0; i < sizeof(bios) / sizeof(bios[0]); i++)
          {
             snprintf(path, sizeof(path), "%s%c%s.bin", dir, SLASH, bios[i]);
-            found_bios = try_use_bios(path);
+            found_bios = try_use_bios(path, true);
             if (found_bios)
                break;
          }
@@ -2918,38 +3677,21 @@ static void loadPSXBios(void)
    if (!found_bios)
    {
       const char *msg_str;
+      unsigned duration;
       if (useHLE)
       {
-         msg_str = "BIOS set to \'hle\' in core options - real BIOS will be ignored";
+         msg_str = "BIOS set to \'hle\'";
          SysPrintf("Using HLE BIOS.\n");
+         // shorter as the user probably intentionally wants to use HLE
+         duration = 700;
       }
       else
       {
          msg_str = "No PlayStation BIOS file found - add for better compatibility";
          SysPrintf("No BIOS files found.\n");
+         duration = 3000;
       }
-
-      if (msg_interface_version >= 1)
-      {
-         struct retro_message_ext msg = {
-            msg_str,
-            3000,
-            3,
-            RETRO_LOG_WARN,
-            RETRO_MESSAGE_TARGET_ALL,
-            RETRO_MESSAGE_TYPE_NOTIFICATION,
-            -1
-         };
-         environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg);
-      }
-      else
-      {
-         struct retro_message msg = {
-            msg_str,
-            180
-         };
-         environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg);
-      }
+      show_notification(msg_str, duration, 2);
    }
 }
 
@@ -2988,17 +3730,19 @@ void retro_init(void)
    ret |= emu_core_init();
    if (ret != 0)
    {
-      SysPrintf("PCSX init failed.\n");
+      LogErr("PCSX init failed.\n");
       exit(1);
    }
 
 #ifdef _3DS
    vout_buf = linearMemAlign(VOUT_MAX_WIDTH * VOUT_MAX_HEIGHT * 2, 0x80);
-#elif defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L) && !defined(VITA) && !defined(__SWITCH__)
+#elif defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L) && P_HAVE_POSIX_MEMALIGN
    if (posix_memalign(&vout_buf, 16, VOUT_MAX_WIDTH * VOUT_MAX_HEIGHT * 2) != 0)
       vout_buf = (void *) 0;
+   else
+      memset(vout_buf, 0, VOUT_MAX_WIDTH * VOUT_MAX_HEIGHT * 2);
 #else
-   vout_buf = malloc(VOUT_MAX_WIDTH * VOUT_MAX_HEIGHT * 2);
+   vout_buf = calloc(VOUT_MAX_WIDTH * VOUT_MAX_HEIGHT, 2);
 #endif
 
    vout_buf_ptr = vout_buf;
@@ -3006,6 +3750,8 @@ void retro_init(void)
    loadPSXBios();
 
    environ_cb(RETRO_ENVIRONMENT_GET_CAN_DUPE, &vout_can_dupe);
+   if (!vout_can_dupe)
+      LogWarn("CAN_DUPE reports false\n");
 
    disk_initial_index = 0;
    disk_initial_path[0] = '\0';
@@ -3027,7 +3773,6 @@ void retro_init(void)
 #endif
    pl_rearmed_cbs.gpu_peops.iUseDither = 1;
    pl_rearmed_cbs.gpu_peops.dwActFixes = GPU_PEOPS_OLD_FRAME_SKIP;
-   spu_config.iUseFixedUpdates = 1;
 
    SaveFuncs.open = save_open;
    SaveFuncs.read = save_read;
@@ -3118,3 +3863,5 @@ void SysDLog(const char *fmt, ...)
    if (log_cb)
       log_cb(RETRO_LOG_DEBUG, "%s", msg);
 }
+
+// vim:sw=3:ts=3:expandtab