cdrom: change pause timing again
[pcsx_rearmed.git] / frontend / libretro.c
index 7f33157..6a719ae 100644 (file)
@@ -15,6 +15,7 @@
 #include <sys/syscall.h>
 #endif
 
+#include "retro_miscellaneous.h"
 #ifdef SWITCH
 #include <switch.h>
 #endif
@@ -76,6 +77,13 @@ 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;
@@ -84,7 +92,6 @@ 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;
@@ -124,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];
@@ -146,8 +154,8 @@ int multitap1 = 0;
 int multitap2 = 0;
 int in_enable_vibration = 1;
 static int in_enable_crosshair[2] = { 0, 0 };
-static bool in_dualshock_toggle_enable = 0;
-static bool in_dualshock_toggling = 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
@@ -237,10 +245,13 @@ static void set_vout_fb()
    fb.access_flags   = RETRO_MEMORY_ACCESS_WRITE;
 
    vout_pitch = vout_width;
-   if (environ_cb(RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER, &fb) && fb.format == RETRO_PIXEL_FORMAT_RGB565) {
+   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)
-         SysPrintf("got unusual pitch %zd for resolution %dx%d\n", fb.pitch, vout_width, vout_height);
+         LogWarn("got unusual pitch %zd for resolution %dx%d\n", fb.pitch, vout_width, vout_height);
       vout_pitch = fb.pitch / 2;
    }
    else
@@ -284,16 +295,17 @@ static void convert(void *buf, size_t bytes)
 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
-      for (int i = pos_y - thickness / 2; i <= pos_y + thickness / 2; i++) {
-         for (int j = pos_x - size_x / 2; j <= pos_x + size_x / 2; j++) {
+      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 (int i = pos_x - thickness / 2; i <= pos_x + thickness / 2; i++) {
-         for (int j = pos_y - size_y / 2; j <= pos_y + size_y / 2; j++) {
+      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;
          }
@@ -356,8 +368,8 @@ static void vout_flip(const void *vram, int stride, int bgr24,
    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);
+         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);
       }
    }
@@ -409,7 +421,7 @@ void *pl_3ds_mmap(unsigned long addr, size_t size, int is_fixed,
 
             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);
             }
 
@@ -575,7 +587,6 @@ void pl_frame_limit(void)
 
 void pl_timing_prepare(int is_pal)
 {
-   is_pal_mode = is_pal;
 }
 
 void plat_trigger_vibrate(int pad, int low, int high)
@@ -717,8 +728,8 @@ 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_crosshair1",
+            "pcsx_rearmed_crosshair2",
             "pcsx_rearmed_konamigunadjustx",
             "pcsx_rearmed_konamigunadjusty",
             "pcsx_rearmed_gunconadjustx",
@@ -980,7 +991,7 @@ 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->library_version  = "r24l" GIT_VERSION;
    info->valid_extensions = "bin|cue|img|mdf|pbp|toc|cbn|m3u|chd|iso|exe";
    info->need_fullpath    = true;
 }
@@ -991,7 +1002,7 @@ void retro_get_system_av_info(struct retro_system_av_info *info)
    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;
@@ -1082,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);
@@ -1153,7 +1164,7 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code)
 
 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);
@@ -1228,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;
 }
@@ -1259,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,
@@ -1268,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;
@@ -1488,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 */
 
@@ -1677,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;
    }
 
@@ -1697,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;
       }
    }
@@ -1730,16 +2061,36 @@ 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;
    NetOpened = 0;
 
    if (OpenPlugins() == -1)
    {
-      log_cb(RETRO_LOG_INFO, "failed to open plugins\n");
+      LogErr("failed to open plugins\n");
       return false;
    }
 
@@ -1798,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;
          }
       }
@@ -1818,7 +2169,7 @@ bool retro_load_game(const struct retro_game_info *info)
 
    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;
    }
 
@@ -1830,7 +2181,7 @@ bool retro_load_game(const struct retro_game_info *info)
       ret = LoadCdrom();
    if (ret != 0)
    {
-      log_cb(RETRO_LOG_INFO, "could not load %s (%d)\n", is_exe ? "exe" : "CD", ret);
+      LogErr("could not load %s (%d)\n", is_exe ? "exe" : "CD", ret);
       return false;
    }
    emu_on_new_cd(0);
@@ -1914,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";
@@ -2014,11 +2366,27 @@ static void update_variables(bool in_flight)
    }
 
    var.value = NULL;
-   var.key = "pcsx_rearmed_analog_toggle";
+   var.key = "pcsx_rearmed_analog_combo";
 
    if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
    {
-      in_dualshock_toggle_enable = (strcmp(var.value, "enabled") == 0);
+      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;
@@ -2076,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";
@@ -2105,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
@@ -2319,6 +2707,18 @@ static void update_variables(bool in_flight)
          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)
@@ -2618,6 +3018,13 @@ static void update_variables(bool in_flight)
    }
 
    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
@@ -2873,17 +3280,13 @@ static void update_input_mouse(int port, int ret)
 
 static void update_input(void)
 {
-   int16_t analog_combo =
-      (1 << RETRO_DEVICE_ID_JOYPAD_L) |
-      (1 << RETRO_DEVICE_ID_JOYPAD_R) |
-      (1 << RETRO_DEVICE_ID_JOYPAD_SELECT);
    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;
@@ -2892,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++)
@@ -2918,8 +3325,8 @@ static void update_input(void)
          break;      
       default:
          // dualshock ANALOG toggle?
-         if (type == PSE_PAD_TYPE_ANALOGPAD && in_dualshock_toggle_enable
-             && (ret & analog_combo) == analog_combo)
+         if (type == PSE_PAD_TYPE_ANALOGPAD && in_dualshock_analog_combo != 0
+             && ret == in_dualshock_analog_combo)
          {
             if (!in_dualshock_toggling)
             {
@@ -3067,9 +3474,14 @@ void retro_run(void)
          frameskip_counter = 0;
    }
 
-   video_cb((vout_fb_dirty || !vout_can_dupe || !duping_enable) ? vout_buf_ptr : NULL,
+   video_cb((vout_fb_dirty || !vout_can_dupe) ? vout_buf_ptr : NULL,
        vout_width, vout_height, vout_pitch * 2);
    vout_fb_dirty = 0;
+
+#ifdef HAVE_CDROM
+   if (CDR_open == rcdrom_open)
+      rcdrom_check_eject();
+#endif
 }
 
 static bool try_use_bios(const char *path, bool preferred_only)
@@ -3191,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
@@ -3203,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;
          }
       }
@@ -3318,7 +3730,7 @@ void retro_init(void)
    ret |= emu_core_init();
    if (ret != 0)
    {
-      SysPrintf("PCSX init failed.\n");
+      LogErr("PCSX init failed.\n");
       exit(1);
    }
 
@@ -3338,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';