#include <sys/types.h>

#include <libretro.h>
#include <retro_math.h>

#include "libretro_core_options.h"

#include "mednafen/settings.h"
#include "mednafen/git.h"
#include "mednafen/wswan/wswan.h"
#include "mednafen/mempatcher.h"
#include "mednafen/wswan/gfx.h"
#include "mednafen/wswan/interrupt.h"
#include "mednafen/wswan/wswan-memory.h"
#include "mednafen/wswan/start.inc"
#include "mednafen/wswan/sound.h"
#include "mednafen/wswan/v30mz.h"
#include "mednafen/wswan/rtc.h"
#include "mednafen/wswan/eeprom.h"

#if defined(_3DS)
void* linearMemAlign(size_t size, size_t alignment);
void linearFree(void* mem);
#endif

#define MEDNAFEN_CORE_NAME_MODULE "wswan"
#define MEDNAFEN_CORE_NAME "Beetle WonderSwan"
#define MEDNAFEN_CORE_VERSION "v0.9.35.1"
#define MEDNAFEN_CORE_EXTENSIONS "ws|wsc|pc2"
#define MEDNAFEN_CORE_TIMING_FPS (3072000.0 / (159.0 * 256.0))
#define MEDNAFEN_CORE_GEOMETRY_BASE_W 224
#define MEDNAFEN_CORE_GEOMETRY_BASE_H 144
#define MEDNAFEN_CORE_GEOMETRY_MAX_W 224
#define MEDNAFEN_CORE_GEOMETRY_MAX_H 144
#define MEDNAFEN_CORE_GEOMETRY_ASPECT_RATIO (14.0 / 9.0)
#define FB_WIDTH 224
#define FB_HEIGHT 144
#define FB_MAX_HEIGHT FB_HEIGHT

/* Forward declarations */
void MDFN_LoadGameCheats(void *override_ptr);
void MDFN_FlushGameCheats(int nosave);

/* core options */
static int RETRO_SAMPLE_RATE = 44100;

static int RETRO_PIX_BYTES = 2;
static int RETRO_PIX_DEPTH = 15;

struct retro_perf_callback perf_cb;
retro_log_printf_t log_cb;
static retro_video_refresh_t video_cb;
static retro_audio_sample_t audio_cb;
static retro_audio_sample_batch_t audio_batch_cb;
static retro_environment_t environ_cb;
static retro_input_poll_t input_poll_cb;
static retro_input_state_t input_state_cb;

static bool libretro_supports_bitmasks = false;

typedef enum
{
   DISPLAY_ROTATION_MANUAL = 0,
   DISPLAY_ROTATION_PORTRAIT,
   DISPLAY_ROTATION_LANDSCAPE
} retro_display_rotation_t;

static retro_display_rotation_t retro_display_rotation = DISPLAY_ROTATION_MANUAL;

static bool rotate_tall               = false;
static bool select_pressed_last_frame = false;
static bool hw_rotate_enabled         = false;

static unsigned rotate_joymap = 0;

static MDFN_Surface *surf   = NULL;
static uint16_t *rotate_buf = NULL;

#define ROTATE_PIXEL_BUF(typename_t, src, width, height, dst)                            \
   {                                                                                     \
      typename_t *in_ptr  = (typename_t*)src;                                            \
      typename_t *out_ptr = (typename_t*)dst;                                            \
      size_t x, y;                                                                       \
      for (x = 0; x < width; x++)                                                        \
         for (y = 0; y < height; y++)                                                    \
            *(out_ptr + y + (((width - 1) - x) * height)) = *(in_ptr + x + (y * width)); \
   }

static int16_t *audio_samples_buf     = NULL;
static int32_t audio_samples_buf_size = 0;

#define RETRO_60HZ_FPS         ((4.0 * MEDNAFEN_CORE_TIMING_FPS) / 5.0)
#define RETRO_60HZ_CYCLE_INDEX 4

typedef struct
{
   int16_t *samples_buf;
   int32_t samples_buf_size;
   int32_t samples_buf_pos;
   int32_t samples_per_frame;
} retro_60hz_audio_t;

static bool retro_60hz_enabled             = false;
static uint16_t retro_60hz_counter         = 0;
static retro_60hz_audio_t retro_60hz_audio = {0};

static void retro_60hz_deinit(void)
{
   if (retro_60hz_audio.samples_buf)
      free(retro_60hz_audio.samples_buf);

   retro_60hz_audio.samples_buf       = NULL;
   retro_60hz_audio.samples_buf_size  = 0;
   retro_60hz_audio.samples_buf_pos   = 0;
   retro_60hz_audio.samples_per_frame = 0;

   retro_60hz_counter = 0;
}

static void retro_60hz_init(void)
{
   retro_60hz_deinit();

   if (retro_60hz_enabled)
   {
      /* Get expected number of samples per 60Hz
       * frame
       * > Round down - any excess samples will be read
       *   out at the end of each 4/5 frame cycle */
      retro_60hz_audio.samples_per_frame = ((int32_t)(RETRO_SAMPLE_RATE /
            RETRO_60HZ_FPS));

      /* Initial buffer size should be *twice* that of
       * the expected number of samples per 75Hz frame */
      retro_60hz_audio.samples_buf_size  = ((int32_t)(RETRO_SAMPLE_RATE /
            MEDNAFEN_CORE_TIMING_FPS) + 1) << 2;
      retro_60hz_audio.samples_buf       = (int16_t*)malloc(
            retro_60hz_audio.samples_buf_size * sizeof(int16_t));

      if (!retro_60hz_audio.samples_buf)
      {
         retro_60hz_deinit();
         retro_60hz_enabled = false;
      }
   }
}

static void retro_60hz_cache_audio_samples(int16_t *samples, int32_t frames)
{
   int32_t buffer_capacity  = retro_60hz_audio.samples_buf_size -
         retro_60hz_audio.samples_buf_pos;
   int32_t samples_to_write = frames << 1;

   /* Resize buffer if necessary */
   if (buffer_capacity < samples_to_write)
   {
      int16_t *tmp_buffer = NULL;
      int32_t tmp_buffer_size;

      tmp_buffer_size = retro_60hz_audio.samples_buf_size +
            (samples_to_write - buffer_capacity);
      tmp_buffer_size = (tmp_buffer_size << 1) - (tmp_buffer_size >> 1);
      tmp_buffer      = (int16_t *)malloc(tmp_buffer_size * sizeof(int16_t));

      memcpy(tmp_buffer, retro_60hz_audio.samples_buf,
            retro_60hz_audio.samples_buf_pos * sizeof(int16_t));

      free(retro_60hz_audio.samples_buf);
      retro_60hz_audio.samples_buf      = tmp_buffer;
      retro_60hz_audio.samples_buf_size = tmp_buffer_size;
   }

   /* Copy samples */
   memcpy(retro_60hz_audio.samples_buf +
               retro_60hz_audio.samples_buf_pos,
         samples, samples_to_write * sizeof(int16_t));
   retro_60hz_audio.samples_buf_pos += samples_to_write;
}

static bool audio_low_pass_enabled      = false;
static int64_t audio_low_pass_acc_left  = 0;
static int64_t audio_low_pass_acc_right = 0;

static void audio_low_pass_apply(int16_t *samples, int32_t frames)
{
   while (frames-- > 0)
   {
      int64_t drop_current_left;
      int64_t drop_current_right;

      /* Left channel */
      drop_current_left        = ((*samples << 16) - audio_low_pass_acc_left) >> 3;
      audio_low_pass_acc_left += drop_current_left;
      *samples++               = (int16_t)((audio_low_pass_acc_left >> 16) & 0xFFFF);

      /* Right channel */
      drop_current_right        = ((*samples << 16) - audio_low_pass_acc_right) >> 3;
      audio_low_pass_acc_right += drop_current_right;
      *samples++                = (int16_t)((audio_low_pass_acc_right >> 16) & 0xFFFF);
   }
}

/* Mono palettes */

struct ws_mono_palette
{
   const char *name;
   uint32 start;
   uint32 end;
};

static struct ws_mono_palette ws_mono_palettes[] = {
   { "default",                0x000000, 0xFFFFFF },
   { "wonderswan",             0x3E3D20, 0x9B9D66 },
   { "wondeswan_color",        0x1B201E, 0xD7D49D },
   { "swancrystal",            0x151108, 0xFFFCCA },
   { "gb_dmg",                 0x00420C, 0x578200 },
   { "gb_pocket",              0x2A3325, 0xA7B19A },
   { "gb_light",               0x00778D, 0x01CBDF },
   { "blossom_pink",           0x180F0F, 0xF09898 },
   { "bubbles_blue",           0x0D1418, 0x88D0F0 },
   { "buttercup_green",        0x12160D, 0xB8E088 },
   { "digivice",               0x000000, 0x8C8C73 },
   { "game_com",               0x000000, 0xA7BF6B },
   { "gameking",               0x184221, 0x8CCE94 },
   { "game_master",            0x2D2D2B, 0x829FA6 },
   { "golden_wild",            0x120F0A, 0xB99F65 },
   { "greenscale",             0x0C360C, 0x9CBE0C },
   { "hokage_orange",          0x170D08, 0xEA8352 },
   { "labo_fawn",              0x15110B, 0xD7AA73 },
   { "legendary_super_saiyan", 0x101509, 0xA5DB5A },
   { "microvision",            0x303030, 0xA0A0A0 },
   { "million_live_gold",      0x141109, 0xCDB261 },
   { "odyssey_gold",           0x131000, 0xC2A000 },
   { "shiny_sky_blue",         0x0E1216, 0x8CB6DF },
   { "slime_blue",             0x040E14, 0x2F8CCC },
   { "ti_83",                  0x181810, 0x9CA684 },
   { "travel_wood",            0x482810, 0xF8D8B0 },
   { "virtual_boy",            0x000000, 0xE30000 },
   { NULL, 0, 0 },
};

static uint32 mono_pal_start = 0x000000;
static uint32 mono_pal_end   = 0xFFFFFF;

static bool find_mono_palette(const char* name,
      uint32 *start, uint32 *end)
{
   bool palette_found = false;
   unsigned i;

   for (i = 0; ws_mono_palettes[i].name; i++)
   {
      if (!strcmp(ws_mono_palettes[i].name, name))
      {
         *start        = ws_mono_palettes[i].start;
         *end          = ws_mono_palettes[i].end;
         palette_found = true;
         break;
      }
   }

   if (!palette_found)
   {
      *start = 0x000000;
      *end   = 0xFFFFFF;
   }

   return palette_found;
}

/* Frameskipping */

static unsigned frameskip_type             = 0;
static unsigned frameskip_threshold        = 0;
static uint16_t frameskip_counter          = 0;

static bool retro_audio_buff_active        = false;
static unsigned retro_audio_buff_occupancy = 0;
static bool retro_audio_buff_underrun      = false;
/* Maximum number of consecutive frames that
 * can be skipped */
#define FRAMESKIP_MAX 30

static unsigned audio_latency              = 0;
static bool update_audio_latency           = false;

static void retro_audio_buff_status_cb(
      bool active, unsigned occupancy, bool underrun_likely)
{
   retro_audio_buff_active    = active;
   retro_audio_buff_occupancy = occupancy;
   retro_audio_buff_underrun  = underrun_likely;
}

static void init_frameskip(void)
{
   if (frameskip_type > 0)
   {
      struct retro_audio_buffer_status_callback buf_status_cb;

      buf_status_cb.callback = retro_audio_buff_status_cb;
      if (!environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK,
            &buf_status_cb))
      {
         if (log_cb)
            log_cb(RETRO_LOG_WARN, "Frameskip disabled - frontend does not support audio buffer status monitoring.\n");

         retro_audio_buff_active    = false;
         retro_audio_buff_occupancy = 0;
         retro_audio_buff_underrun  = false;
         audio_latency              = 0;
      }
      else
      {
         float fps;
         float latency_factor;
         float frame_time_msec;

         if (retro_60hz_enabled)
         {
            fps            = (float)RETRO_60HZ_FPS;
            latency_factor = 6.0f;
         }
         else
         {
            fps            = (float)MEDNAFEN_CORE_TIMING_FPS;
            latency_factor = 8.0f;
         }

         /* Frameskip is enabled - increase frontend
          * audio latency to minimise potential
          * buffer underruns */
         frame_time_msec = 1000.0f / fps;

         /* Set latency to an integer multiple of
          * the current frame time...
          * > At 60Hz we normally use a 6x multiplier
          * > The native (unusually high) 75Hz of the
          *   WonderSwan seems to place greater demands
          *   on the frontend, so we increase the
          *   multiplier to 8x; this gives the frontend
          *   more room to manoeuvre, and improves the
          *   efficacy of the 'Auto' frameskip setting */
         audio_latency = (unsigned)((latency_factor * frame_time_msec) + 0.5f);

         /* ...then round up to nearest multiple of 32 */
         audio_latency = (audio_latency + 0x1F) & ~0x1F;
      }
   }
   else
   {
      environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, NULL);
      audio_latency = 0;
   }

   update_audio_latency = true;
}

/* Cygne
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Dox dox@space.pl
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

int 		wsc = 1;			/*color/mono*/
uint32		rom_size;

uint16 WSButtonStatus;

static void Reset(void)
{
   int u0;

   v30mz_reset();				/* Reset CPU */
   WSwan_MemoryReset();
   WSwan_GfxReset();
   WSwan_SoundReset();
   WSwan_InterruptReset();
   WSwan_RTCReset();
   WSwan_EEPROMReset();

   for(u0=0;u0<0xc9;u0++)
   {
      if(u0 != 0xC4 && u0 != 0xC5 && u0 != 0xBA && u0 != 0xBB)
         WSwan_writeport(u0,startio[u0]);
   }

   v30mz_set_reg(NEC_SS,0);
   v30mz_set_reg(NEC_SP,0x2000);
}

static uint8 *chee = NULL;

static void Emulate(EmulateSpecStruct *espec,
      int skip_frame, int update_sample_rate)
{
   espec->surface          = surf;
   espec->DisplayRect.w    = 224;
   espec->DisplayRect.h    = 144;
   espec->skip             = skip_frame;
   espec->SoundBufSize     = 0;

   if (update_sample_rate)
      WSwan_SetSoundRate(RETRO_SAMPLE_RATE);

   WSButtonStatus          = chee[0] | (chee[1] << 8);

   MDFNMP_ApplyPeriodicCheats();

   while (!wsExecuteLine(espec->surface, espec->skip));

   espec->SoundBufSize = WSwan_SoundFlush(&audio_samples_buf,
         &audio_samples_buf_size);

   if (audio_low_pass_enabled)
      audio_low_pass_apply(audio_samples_buf,
            espec->SoundBufSize);

   v30mz_timestamp = 0;
}

#if 0
typedef struct
{
 const uint8 id;
 const char *name;
} DLEntry;

static const DLEntry Developers[] =
{
 { 0x01, "Bandai" },
 { 0x02, "Taito" },
 { 0x03, "Tomy" },
 { 0x04, "Koei" },
 { 0x05, "Data East" },
 { 0x06, "Asmik" }, /* Asmik Ace? */
 { 0x07, "Media Entertainment" },
 { 0x08, "Nichibutsu" },
 { 0x0A, "Coconuts Japan" },
 { 0x0B, "Sammy" },
 { 0x0C, "Sunsoft" },
 { 0x0D, "Mebius" },
 { 0x0E, "Banpresto" },
 { 0x10, "Jaleco" },
 { 0x11, "Imagineer" },
 { 0x12, "Konami" },
 { 0x16, "Kobunsha" },
 { 0x17, "Bottom Up" },
 { 0x18, "Kaga Tech" },
 { 0x19, "Sunrise" },
 { 0x1A, "Cyber Front" },
 { 0x1B, "Mega House" },
 { 0x1D, "Interbec" },
 { 0x1E, "Nihon Application" },
 { 0x1F, "Bandai Visual" },
 { 0x20, "Athena" },
 { 0x21, "KID" },
 { 0x22, "HAL Corporation" },
 { 0x23, "Yuki Enterprise" },
 { 0x24, "Omega Micott" },
 { 0x25, "Layup" },
 { 0x26, "Kadokawa Shoten" },
 { 0x27, "Shall Luck" },
 { 0x28, "Squaresoft" },
 { 0x2B, "Tom Create" },
 { 0x2D, "Namco" },
 { 0x2E, "Movic" }, /* ???? */
 { 0x2F, "E3 Staff" }, /* ???? */
 { 0x31, "Vanguard" },
 { 0x32, "Megatron" },
 { 0x33, "Wiz" },
 { 0x36, "Capcom" }
};
#endif

static uint32 SRAMSize;

static int Load(const uint8_t *data, size_t size)
{
   uint32 pow_size      = 0;
   uint32 real_rom_size = 0;
   uint8 header[10];

   if(size < 65536)
      return 0;

   real_rom_size = (size + 0xFFFF) & ~0xFFFF;
   pow_size      = next_pow2(real_rom_size);
   rom_size      = pow_size + (pow_size == 0);

   wsCartROM     = (uint8 *)calloc(1, rom_size);

   /* This real_rom_size vs rom_size funny business 
    * is intended primarily for handling
    * WSR files. */
   if(real_rom_size < rom_size)
      memset(wsCartROM, 0xFF, rom_size - real_rom_size);

   memcpy(wsCartROM + (rom_size - real_rom_size), data, size);

   memcpy(header, wsCartROM + rom_size - 10, 10);

   SRAMSize = 0;
   eeprom_size = 0;

   switch(header[5])
   {
      case 0x01: SRAMSize =   8 * 1024; break;
      case 0x02: SRAMSize =  32 * 1024; break;
      case 0x03: SRAMSize = 128 * 1024; break;
      case 0x04: SRAMSize = 256 * 1024; break; /* Dicing Knight!, Judgement Silver */
      case 0x05: SRAMSize = 512 * 1024; break; /* Wonder Gate */

      case 0x10: eeprom_size = 128; break;
      case 0x20: eeprom_size = 2 *1024; break;
      case 0x50: eeprom_size = 1024; break;
   }

   if((header[8] | (header[9] << 8)) == 0x8de1 && (header[0]==0x01)&&(header[2]==0x27)) /* Detective Conan */
   {
      /* WS cpu is using cache/pipeline or there's protected ROM bank where pointing CS */
      wsCartROM[0xfffe8]=0xea;
      wsCartROM[0xfffe9]=0x00;
      wsCartROM[0xfffea]=0x00;
      wsCartROM[0xfffeb]=0x00;
      wsCartROM[0xfffec]=0x20;
   }

#if 0
   if(header[6] & 0x1)
      EmulatedWSwan.rotated = MDFN_ROTATE90;
#endif

   MDFNMP_Init(16384, (1 << 20) / 1024);

   v30mz_init(WSwan_readmem20, WSwan_writemem20, WSwan_readport, WSwan_writeport);
   WSwan_MemoryInit(MDFN_GetSettingB("wswan.language"), wsc, SRAMSize, false); /* EEPROM and SRAM are loaded in this func. */
   WSwan_GfxInit();

   WSwan_SoundInit();

   wsMakeTiles();

   Reset();

   return 1;
}

static void CloseGame(void)
{
   WSwan_MemoryKill();

   WSwan_SoundKill();

   if(wsCartROM)
   {
      free(wsCartROM);
      wsCartROM = NULL;
   }
}

int StateAction(StateMem *sm, int load, int data_only)
{
   if(!v30mz_StateAction(sm, load, data_only))
      return 0;
   /* Call MemoryStateAction before others StateActions... */
   if(!WSwan_MemoryStateAction(sm, load, data_only))
      return 0;
   if(!WSwan_GfxStateAction(sm, load, data_only))
      return 0;
   if(!WSwan_RTCStateAction(sm, load, data_only))
      return 0;
   if(!WSwan_InterruptStateAction(sm, load, data_only))
      return 0;
   if(!WSwan_SoundStateAction(sm, load, data_only))
      return 0;
   if(!WSwan_EEPROMStateAction(sm, load, data_only))
      return 0;
   return 1;
}

static void DoSimpleCommand(int cmd)
{
   switch(cmd)
   {
      case MDFN_MSC_POWER:
      case MDFN_MSC_RESET:
         Reset();
         break;
   }
}

static bool update_audio = false;
static bool update_video = false;

static bool MDFNI_LoadGame(
      const char *force_module, const uint8_t *data,
      size_t size)
{
   if(Load(data, size) <= 0)
      return false;

	MDFN_LoadGameCheats(NULL);
	MDFNMP_InstallReadPatches();

   return true;
}

static void MDFNI_CloseGame(void)
{
   MDFN_FlushGameCheats(0);

   CloseGame();

   MDFNMP_Kill();
}

static void check_system_specs(void)
{
   /* TODO - should theoretically be level 4, but apparently
    * doesn't run at fullspeed on PSP (yet) */
   unsigned level = 4;
   environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level);
}

static void check_depth(void)
{
   if (RETRO_PIX_DEPTH == 24)
   {
      enum retro_pixel_format rgb888 = RETRO_PIXEL_FORMAT_XRGB8888;

      if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &rgb888))
      {
         if (log_cb) log_cb(RETRO_LOG_ERROR, "Pixel format XRGB8888 not supported by platform.\n");

         RETRO_PIX_BYTES = 2;
         RETRO_PIX_DEPTH = 15;
      }
   }

#if defined(FRONTEND_SUPPORTS_RGB565)
   if (RETRO_PIX_BYTES == 2)
   {
      enum retro_pixel_format rgb565 = RETRO_PIXEL_FORMAT_RGB565;

      if (environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &rgb565))
      {
         if (log_cb) log_cb(RETRO_LOG_INFO, "Frontend supports RGB565 - will use that instead of XRGB1555.\n");

         RETRO_PIX_DEPTH = 16;
      }
   }
#endif
}

static void rotate_display(void)
{
   if (hw_rotate_enabled)
   {
      struct retro_game_geometry new_geom = {
            FB_WIDTH,
            FB_HEIGHT,
            FB_WIDTH,
            FB_HEIGHT,
            rotate_tall ? (9.0 / 14.0) : (14.0 / 9.0)
      };
      unsigned rot_angle = rotate_tall ?
            1 : /* 90 degrees */
            0;  /* 0 degrees  */

      environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, (void*)&new_geom);
      environ_cb(RETRO_ENVIRONMENT_SET_ROTATION, (void*)&rot_angle);
   }
   else
   {
      struct retro_game_geometry new_geom = {
            rotate_tall ? FB_HEIGHT : FB_WIDTH,
            rotate_tall ? FB_WIDTH  : FB_HEIGHT,
            FB_WIDTH,
            FB_WIDTH,
            rotate_tall ? (9.0 / 14.0) : (14.0 / 9.0)
      };

      environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, (void*)&new_geom);
   }
}

static void check_variables(int startup)
{
   struct retro_variable var = {0};
   uint32 prev_mono_pal_start;
   uint32 prev_mono_pal_end;
   unsigned prev_frameskip_type;
   bool update_60hz_mode = false;

   var.key = "wswan_rotate_display",
   var.value = NULL;

   retro_display_rotation = DISPLAY_ROTATION_MANUAL;

   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
   {
      if (!strcmp(var.value, "portrait"))
         retro_display_rotation = DISPLAY_ROTATION_PORTRAIT;
      else if (!strcmp(var.value, "landscape"))
         retro_display_rotation = DISPLAY_ROTATION_LANDSCAPE;
   }

   switch (retro_display_rotation)
   {
      case DISPLAY_ROTATION_PORTRAIT:
         if (!rotate_tall)
         {
            rotate_tall = true;
            rotate_display();
         }
         break;
      case DISPLAY_ROTATION_LANDSCAPE:
         if (rotate_tall)
         {
            rotate_tall = false;
            rotate_display();
         }
         break;
      default:
         break;
   }

   var.key = "wswan_rotate_keymap",
   var.value = NULL;

   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
   {
      if (!strcmp(var.value, "disabled"))
         rotate_joymap = 0;
      else if (!strcmp(var.value, "enabled"))
         rotate_joymap = 1;
      else if (!strcmp(var.value, "auto"))
         rotate_joymap = 2;
   }

   var.key = "wswan_mono_palette",
   var.value = NULL;

   prev_mono_pal_start = mono_pal_start;
   prev_mono_pal_end   = mono_pal_end;

   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
      find_mono_palette(var.value, &mono_pal_start, &mono_pal_end);

   if ((mono_pal_start != prev_mono_pal_start) ||
       (mono_pal_end   != prev_mono_pal_end))
      WSwan_SetMonoPalette(RETRO_PIX_DEPTH, mono_pal_start, mono_pal_end);

   var.key = "wswan_frameskip";
   var.value = NULL;

   prev_frameskip_type = frameskip_type;
   frameskip_type      = 0;

   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
   {
      if (strcmp(var.value, "auto") == 0)
         frameskip_type = 1;
      else if (strcmp(var.value, "manual") == 0)
         frameskip_type = 2;
   }

   var.key = "wswan_frameskip_threshold";
   var.value = NULL;

   frameskip_threshold = 33;

   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
      frameskip_threshold = strtol(var.value, NULL, 10);

   var.key = "wswan_60hz_mode";
   var.value = NULL;

   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
   {
      bool old_value = retro_60hz_enabled;

      if (!strcmp(var.value, "disabled"))
         retro_60hz_enabled = false;
      else if (!strcmp(var.value, "enabled"))
         retro_60hz_enabled = true;

      if (!startup && (old_value != retro_60hz_enabled))
      {
         update_video = true;
         update_60hz_mode = true;
      }
   }

   var.key = "wswan_sound_sample_rate";
   var.value = NULL;

   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
   {
      int old_value = RETRO_SAMPLE_RATE;

      RETRO_SAMPLE_RATE = atoi(var.value);
      RETRO_SAMPLE_RATE = (RETRO_SAMPLE_RATE < 11025) ?
            11025 : RETRO_SAMPLE_RATE;
      RETRO_SAMPLE_RATE = (RETRO_SAMPLE_RATE > 48000) ?
            48000 : RETRO_SAMPLE_RATE;

      if (!startup && (old_value != RETRO_SAMPLE_RATE))
      {
         update_audio = true;
         /* If audio sample rate changes, must reinitialise
          * 60Hz mode (will be a no-op if 60Hz mode is
          * currently inactive) */
         update_60hz_mode = true;
      }
   }

   var.key = "wswan_sound_low_pass";
   var.value = NULL;

   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
   {
      if (!strcmp(var.value, "disabled"))
         audio_low_pass_enabled = false;
      else if (!strcmp(var.value, "enabled"))
         audio_low_pass_enabled = true;
   }

   if (startup)
   {
      var.key = "wswan_gfx_colors";
      var.value = NULL;

      if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
      {
         if (!strcmp(var.value, "16bit"))
         {
            RETRO_PIX_BYTES = 2;
            RETRO_PIX_DEPTH = 16;
         }
         else if (!strcmp(var.value, "24bit"))
         {
            RETRO_PIX_BYTES = 4;
            RETRO_PIX_DEPTH = 24;
         }
      }
   }

   if (update_60hz_mode)
      retro_60hz_init();

   /* (Re)Initialise frameskipping, if required */
   if (startup ||
       (frameskip_type != prev_frameskip_type) ||
       update_60hz_mode)
      init_frameskip();
}

void retro_init(void)
{
   struct retro_log_callback log;
   if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
      log_cb = log.log;
   else 
      log_cb = NULL;

   frameskip_type             = 0;
   frameskip_threshold        = 0;
   frameskip_counter          = 0;
   retro_audio_buff_active    = false;
   retro_audio_buff_occupancy = 0;
   retro_audio_buff_underrun  = false;
   audio_latency              = 0;
   update_audio_latency       = false;
   audio_low_pass_enabled     = false;
   audio_low_pass_acc_left    = 0;
   audio_low_pass_acc_right   = 0;

   check_system_specs();
   check_variables(true);
   check_depth();

   if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
      libretro_supports_bitmasks = true;
}

void retro_reset(void)
{
   DoSimpleCommand(MDFN_MSC_RESET);
}

bool retro_load_game_special(unsigned a, const struct retro_game_info *b, size_t c)
{
   return false;
}

#define MAX_PLAYERS 1
#define MAX_BUTTONS 11
static uint16_t input_buf;

bool retro_load_game(const struct retro_game_info *info)
{
   const unsigned rot_angle = 0;

   struct retro_input_descriptor desc[] = {
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "X Cursor Left" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "X Cursor Up" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "X Cursor Down" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "X Cursor Right" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "Y Cursor Left" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "Y Cursor Up" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "Y Cursor Down" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "Y Cursor Right" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
      { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Rotate screen + active D-Pad" },

      { 0 },
   };

   if (!info)
      goto error;

   environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);

   if (!MDFNI_LoadGame(MEDNAFEN_CORE_NAME_MODULE,
         (const uint8_t*)info->data, info->size))
      goto error;

   chee = (uint8 *)&input_buf;
   surf = (MDFN_Surface*)calloc(1, sizeof(*surf));
   
   if (!surf)
      goto error;
   
   surf->width  = FB_WIDTH;
   surf->height = FB_HEIGHT;
   surf->pitch  = FB_WIDTH;
   surf->depth  = RETRO_PIX_DEPTH;

#if defined(_3DS)
   surf->pixels = (uint16_t*)linearMemAlign(FB_WIDTH * FB_HEIGHT * sizeof(uint32_t), 128);
#else
   surf->pixels = (uint16_t*)calloc(1, FB_WIDTH * FB_HEIGHT * sizeof(uint32_t));
#endif

   if (!surf->pixels)
      goto error;

   /* Check whether 'hardware' rotation (via frontend
    * gfx driver) is supported */
   hw_rotate_enabled = environ_cb(RETRO_ENVIRONMENT_SET_ROTATION, (void*)&rot_angle);

   if (!hw_rotate_enabled && !rotate_buf)
   {
#if defined(_3DS)
      rotate_buf = (uint16_t*)linearMemAlign(FB_WIDTH * FB_HEIGHT * sizeof(uint32_t), 128);
#else
      rotate_buf = (uint16_t*)calloc(1, FB_WIDTH * FB_HEIGHT * sizeof(uint32_t));
#endif

      if (!rotate_buf)
         goto error;
   }

   rotate_tall = false;
   select_pressed_last_frame = false;
   rotate_joymap = 0;

   check_variables(false);

   /* Allocate an audio buffer of sufficient size
    * for the expected number of samples per frame
    * (size will be increased automatically if
    * configuration changes) */
   audio_samples_buf_size = ((int32_t)(RETRO_SAMPLE_RATE /
         MEDNAFEN_CORE_TIMING_FPS) + 1) << 1;
   audio_samples_buf      = (int16_t*)malloc(audio_samples_buf_size * sizeof(int16_t));

   if (!audio_samples_buf)
      goto error;

   retro_60hz_init();

   WSwan_SetPixelFormat(RETRO_PIX_DEPTH,
         mono_pal_start, mono_pal_end);
   WSwan_SetSoundRate(RETRO_SAMPLE_RATE);

   return true;

error:
   if (surf)
   {
      if (surf->pixels)
#if defined(_3DS)
         linearFree(surf->pixels);
#else
         free(surf->pixels);
#endif
      free(surf);
   }
   surf = NULL;

   if (rotate_buf)
#if defined(_3DS)
      linearFree(rotate_buf);
#else
      free(rotate_buf);
#endif
   rotate_buf = NULL;

   if (audio_samples_buf)
      free(audio_samples_buf);
   audio_samples_buf      = NULL;
   audio_samples_buf_size = 0;

   return false;
}

void retro_unload_game(void)
{
   MDFNI_CloseGame();

   if (surf)
   {
      if (surf->pixels)
#if defined(_3DS)
         linearFree(surf->pixels);
#else
         free(surf->pixels);
#endif
      free(surf);
   }
   surf = NULL;

   if (rotate_buf)
#if defined(_3DS)
      linearFree(rotate_buf);
#else
      free(rotate_buf);
#endif
   rotate_buf = NULL;

   if (audio_samples_buf)
      free(audio_samples_buf);
   audio_samples_buf      = NULL;
   audio_samples_buf_size = 0;

   retro_60hz_deinit();
}

static void update_input(void)
{
   static unsigned map[2][11] = {
      {
         RETRO_DEVICE_ID_JOYPAD_UP,    /* X Cursor horizontal-layout games */
         RETRO_DEVICE_ID_JOYPAD_RIGHT, /* X Cursor horizontal-layout games */
         RETRO_DEVICE_ID_JOYPAD_DOWN,  /* X Cursor horizontal-layout games */
         RETRO_DEVICE_ID_JOYPAD_LEFT,  /* X Cursor horizontal-layout games */
         RETRO_DEVICE_ID_JOYPAD_R2,    /* Y Cursor UP vertical-layout games */
         RETRO_DEVICE_ID_JOYPAD_R,     /* Y Cursor RIGHT vertical-layout games */
         RETRO_DEVICE_ID_JOYPAD_L2,    /* Y Cursor DOWN vertical-layout games */
         RETRO_DEVICE_ID_JOYPAD_L,     /* Y Cursor LEFT vertical-layout games */
         RETRO_DEVICE_ID_JOYPAD_START,
         RETRO_DEVICE_ID_JOYPAD_A,
         RETRO_DEVICE_ID_JOYPAD_B,
      },
      {
         RETRO_DEVICE_ID_JOYPAD_Y,     /* X Cursor horizontal-layout games */
         RETRO_DEVICE_ID_JOYPAD_X,     /* X Cursor horizontal-layout games */
         RETRO_DEVICE_ID_JOYPAD_A,     /* X Cursor horizontal-layout games */
         RETRO_DEVICE_ID_JOYPAD_B,     /* X Cursor horizontal-layout games */
         RETRO_DEVICE_ID_JOYPAD_LEFT,  /* Y Cursor UP vertical-layout games */
         RETRO_DEVICE_ID_JOYPAD_UP,    /* Y Cursor RIGHT vertical-layout games */
         RETRO_DEVICE_ID_JOYPAD_RIGHT, /* Y Cursor DOWN vertical-layout games */
         RETRO_DEVICE_ID_JOYPAD_DOWN,  /* Y Cursor LEFT vertical-layout games */
         RETRO_DEVICE_ID_JOYPAD_START,
         RETRO_DEVICE_ID_JOYPAD_L,
         RETRO_DEVICE_ID_JOYPAD_R,
      }
   };
   unsigned i;
   int16_t bitmask = 0;
   bool select_button;
   bool joymap;
#ifdef MSB_FIRST
   union {
      uint8_t b[2];
      uint16_t s;
   } u;
#endif

   input_buf = 0;

   if (libretro_supports_bitmasks)
      bitmask = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK);
   else
   {
      for (i = 0; i < (RETRO_DEVICE_ID_JOYPAD_R3+1); i++)
         bitmask |= input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, i) ? 1 << i : 0;
   }

   select_button = bitmask & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT);
   if ((retro_display_rotation == DISPLAY_ROTATION_MANUAL) &&
       select_button &&
       !select_pressed_last_frame)
   {
      rotate_tall = !rotate_tall;
      rotate_display();
   }
   select_pressed_last_frame = select_button;

   joymap = (rotate_joymap == 2) ? rotate_tall : (rotate_joymap ? true : false);

   for (i = 0; i < MAX_BUTTONS; i++)
      input_buf |= map[joymap][i] != -1u && ((1 << map[joymap][i]) & bitmask) ? (1 << i) : 0;

#ifdef MSB_FIRST
   u.s = input_buf;
   input_buf = u.b[0] | u.b[1] << 8;
#endif
}

void retro_run(void)
{
   int total;
   unsigned width, height;
   int32 SoundBufSize;
   EmulateSpecStruct spec;
   bool updated   = false;
   int skip_frame = 0;
   int update_sample_rate;

   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
      check_variables(false);
   update_sample_rate = update_audio;

   input_poll_cb();

   update_input();

   /* Check whether current frame should
    * be skipped */
   if ((frameskip_type > 0) && retro_audio_buff_active)
   {
      switch (frameskip_type)
      {
         case 1: /* auto */
            skip_frame = retro_audio_buff_underrun ? 1 : 0;
            break;
         case 2: /* manual */
            skip_frame = (retro_audio_buff_occupancy < frameskip_threshold) ? 1 : 0;
            break;
         default:
            skip_frame = 0;
            break;
      }

      if (!skip_frame ||
          (frameskip_counter >= FRAMESKIP_MAX))
      {
         skip_frame        = 0;
         frameskip_counter = 0;
      }
      else
         frameskip_counter++;
   }

   /* If frameskip settings have changed, update
    * frontend audio latency */
   if (update_audio_latency)
   {
      environ_cb(RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY,
            &audio_latency);
      update_audio_latency = false;
   }

   if (retro_60hz_enabled)
   {
      /* If we are running in 60Hz mode, then:
       * - Audio data must be buffered
       * - On every 4th call of retro_run(), an
       *   extra frame must be emulated */

      /* Emulate 'force skipped frame' */
      if (retro_60hz_counter == 0)
      {
         Emulate(&spec, 1, update_sample_rate);
         update_sample_rate = 0;
         retro_60hz_cache_audio_samples(audio_samples_buf,
               spec.SoundBufSize);
      }

      /* Run 'regular' frame */
      Emulate(&spec, skip_frame, update_sample_rate);
      retro_60hz_cache_audio_samples(audio_samples_buf,
            spec.SoundBufSize);

      retro_60hz_counter++;
   }
   else
      Emulate(&spec, skip_frame, update_sample_rate);

   if (update_audio || update_video)
   {
      struct retro_system_av_info system_av_info;
      retro_get_system_av_info(&system_av_info);
      environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &system_av_info);
      update_audio = false;
      update_video = false;
   }

   width  = spec.DisplayRect.w;
   height = spec.DisplayRect.h;

   if (hw_rotate_enabled || !rotate_tall)
   {
      if (!skip_frame)
         video_cb(surf->pixels, width, height, FB_WIDTH * RETRO_PIX_BYTES);
      else
         video_cb(NULL, width, height, FB_WIDTH * RETRO_PIX_BYTES);
   }
   else
   {
      if (!skip_frame)
      {
         /* Perform software-based display rotation */
         if (RETRO_PIX_BYTES == 4)
            ROTATE_PIXEL_BUF(
                  uint32_t, surf->pixels, width, height, rotate_buf)
         else
            ROTATE_PIXEL_BUF(
                  uint16_t, surf->pixels, width, height, rotate_buf)

         video_cb(rotate_buf, height, width, FB_HEIGHT * RETRO_PIX_BYTES);
      }
      else
         video_cb(NULL, height, width, FB_HEIGHT * RETRO_PIX_BYTES);
   }

   if (retro_60hz_enabled)
   {
      /* If we are running in 60Hz mode, then
       * read out the expected number of samples
       * on each frame plus empty the buffer at
       * the end of each 4/5 frame cycle */
      int32_t frames_available = retro_60hz_audio.samples_buf_pos >> 1;
      int32_t frames_to_read   = retro_60hz_audio.samples_per_frame;
      int32_t samples_to_read;

      frames_to_read = (frames_to_read > frames_available) ?
            frames_available : frames_to_read;

      if (retro_60hz_counter >= RETRO_60HZ_CYCLE_INDEX)
      {
         frames_to_read     = frames_available;
         retro_60hz_counter = 0;
      }
      samples_to_read  = frames_to_read << 1;

      for (total = 0; total < frames_to_read; )
         total += audio_batch_cb(
               retro_60hz_audio.samples_buf + (total << 1),
               frames_to_read - total);

      /* Remove uploaded samples from the buffer */
      if (frames_to_read < frames_available)
         memmove(retro_60hz_audio.samples_buf,
               retro_60hz_audio.samples_buf + samples_to_read,
               (retro_60hz_audio.samples_buf_pos - samples_to_read) *
                     sizeof(int16_t));

      retro_60hz_audio.samples_buf_pos -= samples_to_read;
   }
   else
      for (total = 0; total < spec.SoundBufSize; )
         total += audio_batch_cb(
               audio_samples_buf + (total << 1),
               spec.SoundBufSize - total);
}

void retro_get_system_info(struct retro_system_info *info)
{
   memset(info, 0, sizeof(*info));
   info->library_name     = MEDNAFEN_CORE_NAME;
#ifndef GIT_VERSION
#define GIT_VERSION ""
#endif
   info->library_version  = MEDNAFEN_CORE_VERSION GIT_VERSION;
   info->need_fullpath    = false;
   info->valid_extensions = MEDNAFEN_CORE_EXTENSIONS;
   info->block_extract    = false;
}

void retro_get_system_av_info(struct retro_system_av_info *info)
{
   memset(info, 0, sizeof(*info));

   info->timing.fps               = retro_60hz_enabled ?
         RETRO_60HZ_FPS : MEDNAFEN_CORE_TIMING_FPS;
   info->timing.sample_rate       = RETRO_SAMPLE_RATE;

   if (hw_rotate_enabled || !rotate_tall)
   {
      info->geometry.base_width   = MEDNAFEN_CORE_GEOMETRY_BASE_W;
      info->geometry.base_height  = MEDNAFEN_CORE_GEOMETRY_BASE_H;
   }
   else
   {
      info->geometry.base_width   = MEDNAFEN_CORE_GEOMETRY_BASE_H;
      info->geometry.base_height  = MEDNAFEN_CORE_GEOMETRY_BASE_W;
   }

   info->geometry.max_width       = MEDNAFEN_CORE_GEOMETRY_MAX_W;
   if (hw_rotate_enabled)
      info->geometry.max_height   = MEDNAFEN_CORE_GEOMETRY_MAX_H;
   else
      info->geometry.max_height   = MEDNAFEN_CORE_GEOMETRY_MAX_W;

   if (rotate_tall)
      info->geometry.aspect_ratio = 1.0f / MEDNAFEN_CORE_GEOMETRY_ASPECT_RATIO;
   else
      info->geometry.aspect_ratio = MEDNAFEN_CORE_GEOMETRY_ASPECT_RATIO;
}

void retro_deinit(void)
{
   if (surf)
   {
      if (surf->pixels)
#if defined(_3DS)
         linearFree(surf->pixels);
#else
         free(surf->pixels);
#endif
      free(surf);
   }
   surf = NULL;

   if (rotate_buf)
#if defined(_3DS)
      linearFree(rotate_buf);
#else
      free(rotate_buf);
#endif
   rotate_buf = NULL;

   if (audio_samples_buf)
      free(audio_samples_buf);
   audio_samples_buf      = NULL;
   audio_samples_buf_size = 0;

   retro_60hz_deinit();
   retro_60hz_enabled = false;

   libretro_supports_bitmasks = false;
}

unsigned retro_get_region(void)
{
   return RETRO_REGION_NTSC; /* No real regions of sorts for this handheld, so just set to NTSC. */
}

unsigned retro_api_version(void)
{
   return RETRO_API_VERSION;
}

void retro_set_controller_port_device(unsigned in_port, unsigned device) { }

void retro_set_environment(retro_environment_t cb)
{
   bool option_cats_supported = false;

   environ_cb = cb;

   libretro_set_core_options(environ_cb,
      &option_cats_supported);
}

void retro_set_audio_sample(retro_audio_sample_t cb)
{
}

void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
{
   audio_batch_cb = cb;
}

void retro_set_input_poll(retro_input_poll_t cb)
{
   input_poll_cb = cb;
}

void retro_set_input_state(retro_input_state_t cb)
{
   input_state_cb = cb;
}

void retro_set_video_refresh(retro_video_refresh_t cb)
{
   video_cb = cb;
}

size_t retro_serialize_size(void)
{
   StateMem st;

   st.data           = NULL;
   st.loc            = 0;
   st.len            = 0;
   st.malloced       = 0;
   st.initial_malloc = 0;

   if (!MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL))
      return 0;

   free(st.data);

   return st.len;
}

bool retro_serialize(void *data, size_t size)
{
   StateMem st;
   bool ret          = false;
   uint8_t *_dat     = (uint8_t*)malloc(size);

   if (!_dat)
      return false;

   /* Mednafen can realloc the buffer so we need to ensure this is safe. */
   st.data           = _dat;
   st.loc            = 0;
   st.len            = 0;
   st.malloced       = size;
   st.initial_malloc = 0;

   ret = MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL);

   memcpy(data, st.data, size);
   free(st.data);

   return ret;
}

bool retro_unserialize(const void *data, size_t size)
{
   StateMem st;

   st.data           = (uint8_t*)data;
   st.loc            = 0;
   st.len            = size;
   st.malloced       = 0;
   st.initial_malloc = 0;

   return MDFNSS_LoadSM(&st, 0, 0);
}

void *retro_get_memory_data(unsigned type)
{
   switch (type)
   {
      case RETRO_MEMORY_SAVE_RAM:
         if (eeprom_size)
            return (uint8_t*)wsEEPROM;
         else if (SRAMSize)
            return wsSRAM;
         break;
      case RETRO_MEMORY_SYSTEM_RAM:
         return (uint8_t*)wsRAM;
      default:
         break;
   }

   return NULL;
}

size_t retro_get_memory_size(unsigned type)
{
   switch (type)
   {
      case RETRO_MEMORY_SAVE_RAM:
         if (eeprom_size)
            return eeprom_size;
         else if (SRAMSize)
            return SRAMSize;
         break;
      case RETRO_MEMORY_SYSTEM_RAM:
         return wsRAMSize;
      default:
         break;
   }

   return 0;
}

void retro_cheat_reset(void) { }
void retro_cheat_set(unsigned a, bool b, const char *c) { }
