#include #include #include #include #include #include #ifdef _MSC_VER #include #endif #include #include #include #include #include #include #include #include "../../fceu.h" #include "../../fceu-endian.h" #include "../../input.h" #include "../../state.h" #include "../../ppu.h" #include "../../cart.h" #include "../../x6502.h" #include "../../git.h" #include "../../palette.h" #include "../../sound.h" #include "../../file.h" #include "../../cheat.h" #include "../../ines.h" #include "../../unif.h" #include "../../fds.h" #include "../../vsuni.h" #include "../../video.h" #ifdef PSP #include "pspgu.h" #endif #if defined(RENDER_GSKIT_PS2) #include "libretro-common/include/libretro_gskit_ps2.h" #endif #if defined (PSP) #define RED_SHIFT 0 #define GREEN_SHIFT 5 #define BLUE_SHIFT 11 #define RED_EXPAND 3 #define GREEN_EXPAND 2 #define BLUE_EXPAND 3 typedef uint16_t bpp_t; #elif defined (FRONTEND_SUPPORTS_ABGR1555) #define RED_SHIFT 0 #define GREEN_SHIFT 5 #define BLUE_SHIFT 10 #define RED_EXPAND 3 #define GREEN_EXPAND 3 #define BLUE_EXPAND 3 #define RED_MASK 0x1F #define GREEN_MASK 0x3E0 #define BLUE_MASK 0x7C00 typedef uint16_t bpp_t; #elif defined (FRONTEND_SUPPORTS_RGB888) #define RED_SHIFT 16 #define GREEN_SHIFT 8 #define BLUE_SHIFT 0 #define RED_EXPAND 0 #define GREEN_EXPAND 0 #define BLUE_EXPAND 0 #define RED_MASK 0xFF0000 #define GREEN_MASK 0x00FF00 #define BLUE_MASK 0x0000FF typedef uint32_t bpp_t; #elif defined (FRONTEND_SUPPORTS_RGB565) #define RED_SHIFT 11 #define GREEN_SHIFT 5 #define BLUE_SHIFT 0 #define RED_EXPAND 3 #define GREEN_EXPAND 2 #define BLUE_EXPAND 3 #define RED_MASK 0xF800 #define GREEN_MASK 0x7e0 #define BLUE_MASK 0x1f typedef uint16_t bpp_t; #else #define RED_SHIFT 10 #define GREEN_SHIFT 5 #define BLUE_SHIFT 0 #define RED_EXPAND 3 #define GREEN_EXPAND 3 #define BLUE_EXPAND 3 typedef uint16_t bpp_t; #endif #define MAX_PLAYERS 4 /* max supported players */ #define MAX_PORTS 2 /* max controller ports, * port 0 for player 1/3, port 1 for player 2/4 */ #define RETRO_DEVICE_AUTO RETRO_DEVICE_JOYPAD #define RETRO_DEVICE_GAMEPAD RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1) #define RETRO_DEVICE_ZAPPER RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 0) #define RETRO_DEVICE_ARKANOID RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 1) #define RETRO_DEVICE_POWERPADA RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 0) #define RETRO_DEVICE_POWERPADB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 1) #define RETRO_DEVICE_FC_ARKANOID RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 2) #define RETRO_DEVICE_FC_OEKAKIDS RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 3) #define RETRO_DEVICE_FC_SHADOW RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 4) #define RETRO_DEVICE_FC_4PLAYERS RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 2) #define RETRO_DEVICE_FC_HYPERSHOT RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 3) #define RETRO_DEVICE_FC_FTRAINERA RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 2) #define RETRO_DEVICE_FC_FTRAINERB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 3) #define RETRO_DEVICE_FC_FKB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 4) #define RETRO_DEVICE_FC_SUBORKB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 5) #define RETRO_DEVICE_FC_PEC586KB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 6) #define RETRO_DEVICE_FC_AUTO RETRO_DEVICE_JOYPAD #define NES_WIDTH 256 #define NES_HEIGHT 240 #define NES_8_7_PAR ((width * (8.0 / 7.0)) / height) #define NES_4_3 ((width / (height * (256.0 / 240.0))) * 4.0 / 3.0) #define NES_PP ((width / (height * (256.0 / 240.0))) * 16.0 / 15.0) #define NES_PAL_FPS (838977920.0 / 16777215.0) #define NES_NTSC_FPS (1008307711.0 / 16777215.0) #if defined(_3DS) void* linearMemAlign(size_t size, size_t alignment); void linearFree(void* mem); #endif #if defined(RENDER_GSKIT_PS2) RETRO_HW_RENDER_INTEFACE_GSKIT_PS2 *ps2 = NULL; #endif extern void FCEU_ZapperSetTolerance(int t); static retro_video_refresh_t video_cb = NULL; static retro_input_poll_t poll_cb = NULL; static retro_input_state_t input_cb = NULL; static retro_audio_sample_batch_t audio_batch_cb = NULL; retro_environment_t environ_cb = NULL; #ifdef PSP static bool crop_overscan; #endif static int crop_overscan_h_left; static int crop_overscan_h_right; static int crop_overscan_v_top; static int crop_overscan_v_bottom; static bool use_raw_palette; static int aspect_ratio_par; /* Pixel format negotiated with the frontend at retro_load_game. Cached * here for retro_run_blit so it can validate that * GET_CURRENT_SOFTWARE_FRAMEBUFFER returned a format we can write into * directly. */ static enum retro_pixel_format active_pixformat = RETRO_PIXEL_FORMAT_UNKNOWN; /* * Flags to keep track of whether turbo * buttons toggled on or off. * * There are two values in array * for Turbo A and Turbo B for * each player */ #define MAX_BUTTONS 9 #define TURBO_BUTTONS 2 unsigned char turbo_button_toggle[MAX_PLAYERS][TURBO_BUTTONS] = { {0} }; typedef struct { unsigned retro; unsigned nes; } keymap; static const keymap turbomap[] = { { RETRO_DEVICE_ID_JOYPAD_X, JOY_A }, { RETRO_DEVICE_ID_JOYPAD_Y, JOY_B }, }; static const keymap bindmap[] = { { RETRO_DEVICE_ID_JOYPAD_A, JOY_A }, { RETRO_DEVICE_ID_JOYPAD_B, JOY_B }, { RETRO_DEVICE_ID_JOYPAD_L3, JOY_A | JOY_B }, { RETRO_DEVICE_ID_JOYPAD_SELECT, JOY_SELECT }, { RETRO_DEVICE_ID_JOYPAD_START, JOY_START }, { RETRO_DEVICE_ID_JOYPAD_UP, JOY_UP }, { RETRO_DEVICE_ID_JOYPAD_DOWN, JOY_DOWN }, { RETRO_DEVICE_ID_JOYPAD_LEFT, JOY_LEFT }, { RETRO_DEVICE_ID_JOYPAD_RIGHT, JOY_RIGHT }, }; static const uint32_t powerpadmap[] = { RETROK_q, RETROK_w, RETROK_e, RETROK_r, RETROK_a, RETROK_s, RETROK_d, RETROK_f, RETROK_z, RETROK_x, RETROK_c, RETROK_v, }; static const uint32_t fkbmap[0x48] = { RETROK_F1,RETROK_F2,RETROK_F3,RETROK_F4,RETROK_F5,RETROK_F6,RETROK_F7,RETROK_F8, RETROK_1,RETROK_2,RETROK_3,RETROK_4,RETROK_5,RETROK_6,RETROK_7,RETROK_8,RETROK_9,RETROK_0,RETROK_MINUS,RETROK_EQUALS,RETROK_BACKSLASH,RETROK_BACKSPACE, RETROK_ESCAPE,RETROK_q,RETROK_w,RETROK_e,RETROK_r,RETROK_t,RETROK_y,RETROK_u,RETROK_i,RETROK_o,RETROK_p,RETROK_TILDE,RETROK_LEFTBRACKET,RETROK_RETURN, RETROK_LCTRL,RETROK_a,RETROK_s,RETROK_d,RETROK_f,RETROK_g,RETROK_h,RETROK_j,RETROK_k,RETROK_l,RETROK_SEMICOLON,RETROK_QUOTE,RETROK_RIGHTBRACKET,RETROK_INSERT, RETROK_LSHIFT,RETROK_z,RETROK_x,RETROK_c,RETROK_v,RETROK_b,RETROK_n,RETROK_m,RETROK_COMMA,RETROK_PERIOD,RETROK_SLASH,RETROK_RALT,RETROK_RSHIFT,RETROK_LALT,RETROK_SPACE, RETROK_DELETE,RETROK_END,RETROK_PAGEDOWN, RETROK_UP,RETROK_LEFT,RETROK_RIGHT,RETROK_DOWN }; static const uint32_t suborkbmap[0x65] = { RETROK_ESCAPE,RETROK_F1,RETROK_F2,RETROK_F3,RETROK_F4,RETROK_F5,RETROK_F6,RETROK_F7,RETROK_F8,RETROK_F9, RETROK_F10,RETROK_F11,RETROK_F12,RETROK_NUMLOCK,RETROK_CARET,RETROK_1,RETROK_2,RETROK_3,RETROK_4,RETROK_5, RETROK_6,RETROK_7,RETROK_8,RETROK_9,RETROK_0,RETROK_MINUS,RETROK_EQUALS,RETROK_BACKSPACE,RETROK_INSERT,RETROK_HOME, RETROK_PAGEUP,RETROK_PAUSE,RETROK_KP_DIVIDE,RETROK_KP_MULTIPLY,RETROK_KP_MINUS,RETROK_TAB,RETROK_q,RETROK_w,RETROK_e,RETROK_r, RETROK_t,RETROK_y,RETROK_u,RETROK_i,RETROK_o,RETROK_p,RETROK_LEFTBRACKET,RETROK_RIGHTBRACKET,RETROK_RETURN,RETROK_DELETE, RETROK_END,RETROK_PAGEDOWN,RETROK_KP7,RETROK_KP8,RETROK_KP9,RETROK_KP_PLUS,RETROK_CAPSLOCK,RETROK_a,RETROK_s,RETROK_d, RETROK_f,RETROK_g,RETROK_h,RETROK_j,RETROK_k,RETROK_l,RETROK_SEMICOLON,RETROK_QUOTE,RETROK_KP4,RETROK_KP5, RETROK_KP6,RETROK_LSHIFT,RETROK_z,RETROK_x,RETROK_c,RETROK_v,RETROK_b,RETROK_n,RETROK_m,RETROK_COMMA, RETROK_PERIOD,RETROK_SLASH,RETROK_BACKSLASH,RETROK_UP,RETROK_KP1,RETROK_KP2,RETROK_KP3,RETROK_LCTRL,RETROK_LALT,RETROK_SPACE, RETROK_LEFT,RETROK_DOWN,RETROK_RIGHT,RETROK_KP0,RETROK_KP_PERIOD,RETROK_UNKNOWN,RETROK_UNKNOWN,RETROK_UNKNOWN,RETROK_UNKNOWN,RETROK_UNKNOWN, RETROK_UNKNOWN }; typedef struct { bool enable_4player; /* four-score / 4-player adapter used */ bool up_down_allowed; /* disabled simultaneous up+down and left+right dpad combinations */ /* turbo related */ uint32_t turbo_enabler[MAX_PLAYERS]; uint32_t turbo_delay; uint32_t type[MAX_PLAYERS + 1]; /* 4-players + famicom expansion */ /* input data */ uint32_t JSReturn; /* player input data, 1 byte per player (1-4) */ uint32_t MouseData[MAX_PORTS][4]; /* nes mouse data */ uint32_t FamicomData[3]; /* Famicom expansion port data */ uint32_t PowerPadData; uint8_t FamilyKeyboardData[0x48]; uint8_t SuborKeyboardData[0x65]; } NES_INPUT_T; static NES_INPUT_T nes_input = { 0 }; enum RetroZapperInputModes{RetroCLightgun, RetroSTLightgun, RetroMouse, RetroPointer}; enum RetroZapperInputModes zappermode = RetroCLightgun; enum RetroArkanoidInputModes{RetroArkanoidMouse, RetroArkanoidPointer, RetroArkanoidAbsMouse, RetroArkanoidStelladaptor}; enum RetroArkanoidInputModes arkanoidmode = RetroArkanoidMouse; static int mouseSensitivity = 100; extern int switchZapper; static bool libretro_supports_bitmasks = false; static bool libretro_supports_option_categories = false; static unsigned libretro_msg_interface_version = 0; /* emulator-specific variables */ const size_t PPU_BIT = 1ULL << 31ULL; extern uint8_t NTARAM[0x800], PALRAM[0x20], SPRAM[0x100], PPU[4]; /* overclock the console by adding dummy scanlines to PPU loop * disables DMC DMA and WaveHi filling for these dummies * doesn't work with new PPU */ unsigned overclock_enabled = -1; unsigned overclocked = 0; unsigned skip_7bit_overclocking = 1; /* 7-bit samples have priority over overclocking */ unsigned totalscanlines = 0; unsigned normal_scanlines = 240; unsigned extrascanlines = 0; unsigned vblankscanlines = 0; unsigned dendy = 0; static unsigned systemRegion = 0; static unsigned opt_region = 0; static bool opt_showAdvSoundOptions = true; static bool opt_showAdvSystemOptions = true; #if defined(PSP) || defined(PS2) static __attribute__((aligned(16))) uint16_t retro_palette[256]; #else static uint32_t retro_palette[1024]; #endif #if defined(RENDER_GSKIT_PS2) static uint8_t* fceu_video_out; #else static bpp_t* fceu_video_out; #endif /* Some timing-related variables. */ static unsigned sndsamplerate; static unsigned sndquality; static unsigned sndvolume; unsigned swapDuty; static int32_t *sound = 0; static uint32_t Dummy = 0; static uint32_t current_palette = 0; static unsigned serialize_size; /* extern forward decls.*/ extern FCEUGI *GameInfo; extern uint8_t *XBuf; extern CartInfo iNESCart; extern CartInfo UNIFCart; extern int show_crosshair; extern int option_ramstate; extern int zapper_trigger_invert_option; extern int zapper_sensor_invert_option; /* emulator-specific callback functions */ const char *GetKeyboard(void); /* used by src/boards/transformer.c */ const char * GetKeyboard(void) { return ""; } #define BUILD_PIXEL_RGB565(R,G,B) (((int) ((R)&0x1f) << RED_SHIFT) | ((int) ((G)&0x3f) << GREEN_SHIFT) | ((int) ((B)&0x1f) << BLUE_SHIFT)) void FCEUD_SetPalette(uint16_t index, uint8_t r, uint8_t g, uint8_t b) { uint16_t index_to_write = index; #if defined(RENDER_GSKIT_PS2) /* Index correction for PS2 GS */ int modi = index & 63; if ((modi >= 8 && modi < 16) || (modi >= 40 && modi < 48)) { index_to_write += 8; } else if ((modi >= 16 && modi < 24) || (modi >= 48 && modi < 56)) { index_to_write -= 8; } #endif #if defined(PSP) || defined(PS2) /* PS2 / PSP will only have 256 colors */ if (index >= 256) return; #endif #ifdef FRONTEND_SUPPORTS_RGB565 retro_palette[index_to_write] = BUILD_PIXEL_RGB565(r >> RED_EXPAND, g >> GREEN_EXPAND, b >> BLUE_EXPAND); #else retro_palette[index_to_write] = ((r >> RED_EXPAND) << RED_SHIFT) | ((g >> GREEN_EXPAND) << GREEN_SHIFT) | ((b >> BLUE_EXPAND) << BLUE_SHIFT); #endif } static struct retro_log_callback log_cb; static void default_logger(enum retro_log_level level, const char *fmt, ...) {} void FCEUD_PrintError(const char *c) { log_cb.log(RETRO_LOG_WARN, "%s", c); } void FCEUD_Message(const char *s) { log_cb.log(RETRO_LOG_INFO, "%s", s); } void FCEUD_DispMessage(enum retro_log_level level, unsigned duration, const char *str) { if (!environ_cb) return; if (libretro_msg_interface_version >= 1) { struct retro_message_ext msg; unsigned priority; switch (level) { case RETRO_LOG_ERROR: priority = 5; break; case RETRO_LOG_WARN: priority = 4; break; case RETRO_LOG_INFO: priority = 3; break; case RETRO_LOG_DEBUG: default: priority = 1; break; } msg.msg = str; msg.duration = duration; msg.priority = priority; msg.level = level; msg.target = RETRO_MESSAGE_TARGET_OSD; msg.type = RETRO_MESSAGE_TYPE_NOTIFICATION_ALT; msg.progress = -1; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg); } else { float fps = (FSettings.PAL || dendy) ? NES_PAL_FPS : NES_NTSC_FPS; unsigned frames = (unsigned)(((float)duration * fps / 1000.0f) + 0.5f); struct retro_message msg; msg.msg = str; msg.frames = frames; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg); } } static void FCEUD_SoundToggle (void) { FCEUI_SetSoundVolume(sndvolume); } /*palette for FCEU*/ #define PAL_INTERNAL sizeof(palettes) / sizeof(palettes[0]) /* Number of palettes in palettes[] */ #define PAL_DEFAULT (PAL_INTERNAL + 1) #define PAL_RAW (PAL_INTERNAL + 2) #define PAL_CUSTOM (PAL_INTERNAL + 3) #define PAL_TOTAL PAL_CUSTOM static uint8_t external_palette_exist = false; /* table for currently loaded palette */ static uint8_t base_palette[192]; struct st_palettes { char name[32]; char desc[32]; unsigned int data[64]; }; struct st_palettes palettes[] = { { "asqrealc", "AspiringSquire's Real palette", { 0x6c6c6c, 0x00268e, 0x0000a8, 0x400094, 0x700070, 0x780040, 0x700000, 0x621600, 0x442400, 0x343400, 0x005000, 0x004444, 0x004060, 0x000000, 0x101010, 0x101010, 0xbababa, 0x205cdc, 0x3838ff, 0x8020f0, 0xc000c0, 0xd01474, 0xd02020, 0xac4014, 0x7c5400, 0x586400, 0x008800, 0x007468, 0x00749c, 0x202020, 0x101010, 0x101010, 0xffffff, 0x4ca0ff, 0x8888ff, 0xc06cff, 0xff50ff, 0xff64b8, 0xff7878, 0xff9638, 0xdbab00, 0xa2ca20, 0x4adc4a, 0x2ccca4, 0x1cc2ea, 0x585858, 0x101010, 0x101010, 0xffffff, 0xb0d4ff, 0xc4c4ff, 0xe8b8ff, 0xffb0ff, 0xffb8e8, 0xffc4c4, 0xffd4a8, 0xffe890, 0xf0f4a4, 0xc0ffc0, 0xacf4f0, 0xa0e8ff, 0xc2c2c2, 0x202020, 0x101010 } }, { "restored-wii-vc", "Restored Wii VC palette", { 0x666666, 0x000095, 0x10008B, 0x39007D, 0x5C0068, 0x660000, 0x5C0000, 0x391800, 0x223700, 0x004316, 0x004300, 0x003916, 0x022D5E, 0x000000, 0x000000, 0x000000, 0xA39EA3, 0x0043B9, 0x4502F1, 0x6902CF, 0x8C00AC, 0x960050, 0x962E02, 0x7E4200, 0x5C6600, 0x227D02, 0x167D02, 0x027D46, 0x02667E, 0x161616, 0x000000, 0x000000, 0xF2F2F2, 0x689EFF, 0x8C7BFF, 0xB970FF, 0xE671F2, 0xF266B9, 0xFE8968, 0xCF9E46, 0xACA03B, 0x7EBC02, 0x4EC745, 0x45C77E, 0x50C7C6, 0x4E4E4E, 0x000000, 0x000000, 0xFFFFFF, 0xC4DCFE, 0xC6C7F4, 0xDBC7FF, 0xE9BDFF, 0xF2C6DC, 0xF4D2C4, 0xDBC8AE, 0xDBDDA0, 0xCFE9AE, 0xB9EAAC, 0xAEDCB9, 0xA1D2C6, 0xDEDEDE, 0x000000, 0x000000 } }, { "wii-vc", "Wii Virtual Console palette", { 0x494949, 0x00006a, 0x090063, 0x290059, 0x42004a, 0x490000, 0x420000, 0x291100, 0x182700, 0x003010, 0x003000, 0x002910, 0x012043, 0x000000, 0x000000, 0x000000, 0x747174, 0x003084, 0x3101ac, 0x4b0194, 0x64007b, 0x6b0039, 0x6b2101, 0x5a2f00, 0x424900, 0x185901, 0x105901, 0x015932, 0x01495a, 0x101010, 0x000000, 0x000000, 0xadadad, 0x4a71b6, 0x6458d5, 0x8450e6, 0xa451ad, 0xad4984, 0xb5624a, 0x947132, 0x7b722a, 0x5a8601, 0x388e31, 0x318e5a, 0x398e8d, 0x383838, 0x000000, 0x000000, 0xb6b6b6, 0x8c9db5, 0x8d8eae, 0x9c8ebc, 0xa687bc, 0xad8d9d, 0xae968c, 0x9c8f7c, 0x9c9e72, 0x94a67c, 0x84a77b, 0x7c9d84, 0x73968d, 0xdedede, 0x000000, 0x000000 } }, { "rgb", "Nintendo RGB PPU palette", { 0x6D6D6D, 0x002492, 0x0000DB, 0x6D49DB, 0x92006D, 0xB6006D, 0xB62400, 0x924900, 0x6D4900, 0x244900, 0x006D24, 0x009200, 0x004949, 0x000000, 0x000000, 0x000000, 0xB6B6B6, 0x006DDB, 0x0049FF, 0x9200FF, 0xB600FF, 0xFF0092, 0xFF0000, 0xDB6D00, 0x926D00, 0x249200, 0x009200, 0x00B66D, 0x009292, 0x242424, 0x000000, 0x000000, 0xFFFFFF, 0x6DB6FF, 0x9292FF, 0xDB6DFF, 0xFF00FF, 0xFF6DFF, 0xFF9200, 0xFFB600, 0xDBDB00, 0x6DDB00, 0x00FF00, 0x49FFDB, 0x00FFFF, 0x494949, 0x000000, 0x000000, 0xFFFFFF, 0xB6DBFF, 0xDBB6FF, 0xFFB6FF, 0xFF92FF, 0xFFB6B6, 0xFFDB92, 0xFFFF49, 0xFFFF6D, 0xB6FF49, 0x92FF6D, 0x49FFDB, 0x92DBFF, 0x929292, 0x000000, 0x000000 } }, { "yuv-v3", "FBX's YUV-V3 palette", { 0x666666, 0x002A88, 0x1412A7, 0x3B00A4, 0x5C007E, 0x6E0040, 0x6C0700, 0x561D00, 0x333500, 0x0C4800, 0x005200, 0x004C18, 0x003E5B, 0x000000, 0x000000, 0x000000, 0xADADAD, 0x155FD9, 0x4240FF, 0x7527FE, 0xA01ACC, 0xB71E7B, 0xB53120, 0x994E00, 0x6B6D00, 0x388700, 0x0D9300, 0x008C47, 0x007AA0, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x64B0FF, 0x9290FF, 0xC676FF, 0xF26AFF, 0xFF6ECC, 0xFF8170, 0xEA9E22, 0xBCBE00, 0x88D800, 0x5CE430, 0x45E082, 0x48CDDE, 0x4F4F4F, 0x000000, 0x000000, 0xFFFFFF, 0xC0DFFF, 0xD3D2FF, 0xE8C8FF, 0xFAC2FF, 0xFFC4EA, 0xFFCCC5, 0xF7D8A5, 0xE4E594, 0xCFEF96, 0xBDF4AB, 0xB3F3CC, 0xB5EBF2, 0xB8B8B8, 0x000000, 0x000000 } }, { "unsaturated-final", "FBX's Unsaturated-Final palette", { 0x676767, 0x001F8E, 0x23069E, 0x40008E, 0x600067, 0x67001C, 0x5B1000, 0x432500, 0x313400, 0x074800, 0x004F00, 0x004622, 0x003A61, 0x000000, 0x000000, 0x000000, 0xB3B3B3, 0x205ADF, 0x5138FB, 0x7A27EE, 0xA520C2, 0xB0226B, 0xAD3702, 0x8D5600, 0x6E7000, 0x2E8A00, 0x069200, 0x008A47, 0x037B9B, 0x101010, 0x000000, 0x000000, 0xFFFFFF, 0x62AEFF, 0x918BFF, 0xBC78FF, 0xE96EFF, 0xFC6CCD, 0xFA8267, 0xE29B26, 0xC0B901, 0x84D200, 0x58DE38, 0x46D97D, 0x49CED2, 0x494949, 0x000000, 0x000000, 0xFFFFFF, 0xC1E3FF, 0xD5D4FF, 0xE7CCFF, 0xFBC9FF, 0xFFC7F0, 0xFFD0C5, 0xF8DAAA, 0xEBE69A, 0xD1F19A, 0xBEF7AF, 0xB6F4CD, 0xB7F0EF, 0xB2B2B2, 0x000000, 0x000000 } }, { "sony-cxa2025as-us", "Sony CXA2025AS US palette", { 0x585858, 0x00238C, 0x00139B, 0x2D0585, 0x5D0052, 0x7A0017, 0x7A0800, 0x5F1800, 0x352A00, 0x093900, 0x003F00, 0x003C22, 0x00325D, 0x000000, 0x000000, 0x000000, 0xA1A1A1, 0x0053EE, 0x153CFE, 0x6028E4, 0xA91D98, 0xD41E41, 0xD22C00, 0xAA4400, 0x6C5E00, 0x2D7300, 0x007D06, 0x007852, 0x0069A9, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x1FA5FE, 0x5E89FE, 0xB572FE, 0xFE65F6, 0xFE6790, 0xFE773C, 0xFE9308, 0xC4B200, 0x79CA10, 0x3AD54A, 0x11D1A4, 0x06BFFE, 0x424242, 0x000000, 0x000000, 0xFFFFFF, 0xA0D9FE, 0xBDCCFE, 0xE1C2FE, 0xFEBCFB, 0xFEBDD0, 0xFEC5A9, 0xFED18E, 0xE9DE86, 0xC7E992, 0xA8EEB0, 0x95ECD9, 0x91E4FE, 0xACACAC, 0x000000, 0x000000 } }, { "pal", "PAL palette", { 0x808080, 0x0000BA, 0x3700BF, 0x8400A6, 0xBB006A, 0xB7001E, 0xB30000, 0x912600, 0x7B2B00, 0x003E00, 0x00480D, 0x003C22, 0x002F66, 0x000000, 0x050505, 0x050505, 0xC8C8C8, 0x0059FF, 0x443CFF, 0xB733CC, 0xFE33AA, 0xFE375E, 0xFE371A, 0xD54B00, 0xC46200, 0x3C7B00, 0x1D8415, 0x009566, 0x0084C4, 0x111111, 0x090909, 0x090909, 0xFEFEFE, 0x0095FF, 0x6F84FF, 0xD56FFF, 0xFE77CC, 0xFE6F99, 0xFE7B59, 0xFE915F, 0xFEA233, 0xA6BF00, 0x51D96A, 0x4DD5AE, 0x00D9FF, 0x666666, 0x0D0D0D, 0x0D0D0D, 0xFEFEFE, 0x84BFFF, 0xBBBBFF, 0xD0BBFF, 0xFEBFEA, 0xFEBFCC, 0xFEC4B7, 0xFECCAE, 0xFED9A2, 0xCCE199, 0xAEEEB7, 0xAAF8EE, 0xB3EEFF, 0xDDDDDD, 0x111111, 0x111111 } }, { "bmf-final2", "BMF's Final 2 palette", { 0x525252, 0x000080, 0x08008A, 0x2C007E, 0x4A004E, 0x500006, 0x440000, 0x260800, 0x0A2000, 0x002E00, 0x003200, 0x00260A, 0x001C48, 0x000000, 0x000000, 0x000000, 0xA4A4A4, 0x0038CE, 0x3416EC, 0x5E04DC, 0x8C00B0, 0x9A004C, 0x901800, 0x703600, 0x4C5400, 0x0E6C00, 0x007400, 0x006C2C, 0x005E84, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x4C9CFF, 0x7C78FF, 0xA664FF, 0xDA5AFF, 0xF054C0, 0xF06A56, 0xD68610, 0xBAA400, 0x76C000, 0x46CC1A, 0x2EC866, 0x34C2BE, 0x3A3A3A, 0x000000, 0x000000, 0xFFFFFF, 0xB6DAFF, 0xC8CAFF, 0xDAC2FF, 0xF0BEFF, 0xFCBCEE, 0xFAC2C0, 0xF2CCA2, 0xE6DA92, 0xCCE68E, 0xB8EEA2, 0xAEEABE, 0xAEE8E2, 0xB0B0B0, 0x000000, 0x000000 } }, { "bmf-final3", "BMF's Final 3 palette", { 0x686868, 0x001299, 0x1A08AA, 0x51029A, 0x7E0069, 0x8E001C, 0x7E0301, 0x511800, 0x1F3700, 0x014E00, 0x005A00, 0x00501C, 0x004061, 0x000000, 0x000000, 0x000000, 0xB9B9B9, 0x0C5CD7, 0x5035F0, 0x8919E0, 0xBB0CB3, 0xCE0C61, 0xC02B0E, 0x954D01, 0x616F00, 0x1F8B00, 0x01980C, 0x00934B, 0x00819B, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x63B4FF, 0x9B91FF, 0xD377FF, 0xEF6AFF, 0xF968C0, 0xF97D6C, 0xED9B2D, 0xBDBD16, 0x7CDA1C, 0x4BE847, 0x35E591, 0x3FD9DD, 0x606060, 0x000000, 0x000000, 0xFFFFFF, 0xACE7FF, 0xD5CDFF, 0xEDBAFF, 0xF8B0FF, 0xFEB0EC, 0xFDBDB5, 0xF9D28E, 0xE8EB7C, 0xBBF382, 0x99F7A2, 0x8AF5D0, 0x92F4F1, 0xBEBEBE, 0x000000, 0x000000 } }, { "smooth-fbx", "FBX's Smooth palette", { 0x6A6D6A, 0x001380, 0x1E008A, 0x39007A, 0x550056, 0x5A0018, 0x4F1000, 0x3D1C00, 0x253200, 0x003D00, 0x004000, 0x003924, 0x002E55, 0x000000, 0x000000, 0x000000, 0xB9BCB9, 0x1850C7, 0x4B30E3, 0x7322D6, 0x951FA9, 0x9D285C, 0x983700, 0x7F4C00, 0x5E6400, 0x227700, 0x027E02, 0x007645, 0x006E8A, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x68A6FF, 0x8C9CFF, 0xB586FF, 0xD975FD, 0xE377B9, 0xE58D68, 0xD49D29, 0xB3AF0C, 0x7BC211, 0x55CA47, 0x46CB81, 0x47C1C5, 0x4A4D4A, 0x000000, 0x000000, 0xFFFFFF, 0xCCEAFF, 0xDDDEFF, 0xECDAFF, 0xF8D7FE, 0xFCD6F5, 0xFDDBCF, 0xF9E7B5, 0xF1F0AA, 0xDAFAA9, 0xC9FFBC, 0xC3FBD7, 0xC4F6F6, 0xBEC1BE, 0x000000, 0x000000 } }, { "composite-direct-fbx", "FBX's Composite Direct palette", { 0x656565, 0x00127D, 0x18008E, 0x360082, 0x56005D, 0x5A0018, 0x4F0500, 0x381900, 0x1D3100, 0x003D00, 0x004100, 0x003B17, 0x002E55, 0x000000, 0x000000, 0x000000, 0xAFAFAF, 0x194EC8, 0x472FE3, 0x6B1FD7, 0x931BAE, 0x9E1A5E, 0x993200, 0x7B4B00, 0x5B6700, 0x267A00, 0x008200, 0x007A3E, 0x006E8A, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x64A9FF, 0x8E89FF, 0xB676FF, 0xE06FFF, 0xEF6CC4, 0xF0806A, 0xD8982C, 0xB9B40A, 0x83CB0C, 0x5BD63F, 0x4AD17E, 0x4DC7CB, 0x4C4C4C, 0x000000, 0x000000, 0xFFFFFF, 0xC7E5FF, 0xD9D9FF, 0xE9D1FF, 0xF9CEFF, 0xFFCCF1, 0xFFD4CB, 0xF8DFB1, 0xEDEAA4, 0xD6F4A4, 0xC5F8B8, 0xBEF6D3, 0xBFF1F1, 0xB9B9B9, 0x000000, 0x000000 } }, { "pvm-style-d93-fbx", "FBX's PVM Style D93 palette", { 0x696B63, 0x001774, 0x1E0087, 0x340073, 0x560057, 0x5E0013, 0x531A00, 0x3B2400, 0x243000, 0x063A00, 0x003F00, 0x003B1E, 0x00334E, 0x000000, 0x000000, 0x000000, 0xB9BBB3, 0x1453B9, 0x4D2CDA, 0x671EDE, 0x98189C, 0x9D2344, 0xA03E00, 0x8D5500, 0x656D00, 0x2C7900, 0x008100, 0x007D42, 0x00788A, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x69A8FF, 0x9691FF, 0xB28AFA, 0xEA7DFA, 0xF37BC7, 0xF28E59, 0xE6AD27, 0xD7C805, 0x90DF07, 0x64E53C, 0x45E27D, 0x48D5D9, 0x4E5048, 0x000000, 0x000000, 0xFFFFFF, 0xD2EAFF, 0xE2E2FF, 0xE9D8FF, 0xF5D2FF, 0xF8D9EA, 0xFADEB9, 0xF9E89B, 0xF3F28C, 0xD3FA91, 0xB8FCA8, 0xAEFACA, 0xCAF3F3, 0xBEC0B8, 0x000000, 0x000000 } }, { "ntsc-hardware-fbx", "FBX's NTSC Hardware palette", { 0x6A6D6A, 0x001380, 0x1E008A, 0x39007A, 0x550056, 0x5A0018, 0x4F1000, 0x382100, 0x213300, 0x003D00, 0x004000, 0x003924, 0x002E55, 0x000000, 0x000000, 0x000000, 0xB9BCB9, 0x1850C7, 0x4B30E3, 0x7322D6, 0x951FA9, 0x9D285C, 0x963C00, 0x7A5100, 0x5B6700, 0x227700, 0x027E02, 0x007645, 0x006E8A, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x68A6FF, 0x9299FF, 0xB085FF, 0xD975FD, 0xE377B9, 0xE58D68, 0xCFA22C, 0xB3AF0C, 0x7BC211, 0x55CA47, 0x46CB81, 0x47C1C5, 0x4A4D4A, 0x000000, 0x000000, 0xFFFFFF, 0xCCEAFF, 0xDDDEFF, 0xECDAFF, 0xF8D7FE, 0xFCD6F5, 0xFDDBCF, 0xF9E7B5, 0xF1F0AA, 0xDAFAA9, 0xC9FFBC, 0xC3FBD7, 0xC4F6F6, 0xBEC1BE, 0x000000, 0x000000 } }, { "nes-classic-fbx-fs", "FBX's NES-Classic FS palette", { 0x60615F, 0x000083, 0x1D0195, 0x340875, 0x51055E, 0x56000F, 0x4C0700, 0x372308, 0x203A0B, 0x0F4B0E, 0x194C16, 0x02421E, 0x023154, 0x000000, 0x000000, 0x000000, 0xA9AAA8, 0x104BBF, 0x4712D8, 0x6300CA, 0x8800A9, 0x930B46, 0x8A2D04, 0x6F5206, 0x5C7114, 0x1B8D12, 0x199509, 0x178448, 0x206B8E, 0x000000, 0x000000, 0x000000, 0xFBFBFB, 0x6699F8, 0x8974F9, 0xAB58F8, 0xD557EF, 0xDE5FA9, 0xDC7F59, 0xC7A224, 0xA7BE03, 0x75D703, 0x60E34F, 0x3CD68D, 0x56C9CC, 0x414240, 0x000000, 0x000000, 0xFBFBFB, 0xBED4FA, 0xC9C7F9, 0xD7BEFA, 0xE8B8F9, 0xF5BAE5, 0xF3CAC2, 0xDFCDA7, 0xD9E09C, 0xC9EB9E, 0xC0EDB8, 0xB5F4C7, 0xB9EAE9, 0xABABAB, 0x000000, 0x000000 } }, { "nescap", "RGBSource's NESCAP palette", { 0x646365, 0x001580, 0x1D0090, 0x380082, 0x56005D, 0x5A001A, 0x4F0900, 0x381B00, 0x1E3100, 0x003D00, 0x004100, 0x003A1B, 0x002F55, 0x000000, 0x000000, 0x000000, 0xAFADAF, 0x164BCA, 0x472AE7, 0x6B1BDB, 0x9617B0, 0x9F185B, 0x963001, 0x7B4800, 0x5A6600, 0x237800, 0x017F00, 0x00783D, 0x006C8C, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x60A6FF, 0x8F84FF, 0xB473FF, 0xE26CFF, 0xF268C3, 0xEF7E61, 0xD89527, 0xBAB307, 0x81C807, 0x57D43D, 0x47CF7E, 0x4BC5CD, 0x4C4B4D, 0x000000, 0x000000, 0xFFFFFF, 0xC2E0FF, 0xD5D2FF, 0xE3CBFF, 0xF7C8FF, 0xFEC6EE, 0xFECEC6, 0xF6D7AE, 0xE9E49F, 0xD3ED9D, 0xC0F2B2, 0xB9F1CC, 0xBAEDED, 0xBAB9BB, 0x000000, 0x000000 } }, { "wavebeam", "nakedarthur's Wavebeam palette", { 0X6B6B6B, 0X001B88, 0X21009A, 0X40008C, 0X600067, 0X64001E, 0X590800, 0X481600, 0X283600, 0X004500, 0X004908, 0X00421D, 0X003659, 0X000000, 0X000000, 0X000000, 0XB4B4B4, 0X1555D3, 0X4337EF, 0X7425DF, 0X9C19B9, 0XAC0F64, 0XAA2C00, 0X8A4B00, 0X666B00, 0X218300, 0X008A00, 0X008144, 0X007691, 0X000000, 0X000000, 0X000000, 0XFFFFFF, 0X63B2FF, 0X7C9CFF, 0XC07DFE, 0XE977FF, 0XF572CD, 0XF4886B, 0XDDA029, 0XBDBD0A, 0X89D20E, 0X5CDE3E, 0X4BD886, 0X4DCFD2, 0X525252, 0X000000, 0X000000, 0XFFFFFF, 0XBCDFFF, 0XD2D2FF, 0XE1C8FF, 0XEFC7FF, 0XFFC3E1, 0XFFCAC6, 0XF2DAAD, 0XEBE3A0, 0XD2EDA2, 0XBCF4B4, 0XB5F1CE, 0XB6ECF1, 0XBFBFBF, 0X000000, 0X000000 } }, { "digital-prime-fbx", "FBX's Digital Prime palette", { 0x616161, 0x000088, 0x1F0D99, 0x371379, 0x561260, 0x5D0010, 0x520E00, 0x3A2308, 0x21350C, 0x0D410E, 0x174417, 0x003A1F, 0x002F57, 0x000000, 0x000000, 0x000000, 0xAAAAAA, 0x0D4DC4, 0x4B24DE, 0x6912CF, 0x9014AD, 0x9D1C48, 0x923404, 0x735005, 0x5D6913, 0x167A11, 0x138008, 0x127649, 0x1C6691, 0x000000, 0x000000, 0x000000, 0xFCFCFC, 0x639AFC, 0x8A7EFC, 0xB06AFC, 0xDD6DF2, 0xE771AB, 0xE38658, 0xCC9E22, 0xA8B100, 0x72C100, 0x5ACD4E, 0x34C28E, 0x4FBECE, 0x424242, 0x000000, 0x000000, 0xFCFCFC, 0xBED4FC, 0xCACAFC, 0xD9C4FC, 0xECC1FC, 0xFAC3E7, 0xF7CEC3, 0xE2CDA7, 0xDADB9C, 0xC8E39E, 0xBFE5B8, 0xB2EBC8, 0xB7E5EB, 0xACACAC, 0x000000, 0x000000 } }, { "magnum-fbx", "FBX's Magnum palette", { 0x696969, 0x00148F, 0x1E029B, 0x3F008A, 0x600060, 0x660017, 0x570D00, 0x451B00, 0x243400, 0x004200, 0x004500, 0x003C1F, 0x00315C, 0x000000, 0x000000, 0x000000, 0xAFAFAF, 0x0F51DD, 0x442FF3, 0x7220E2, 0xA319B3, 0xAE1C51, 0xA43400, 0x884D00, 0x676D00, 0x208000, 0x008B00, 0x007F42, 0x006C97, 0x010101, 0x000000, 0x000000, 0xFFFFFF, 0x65AAFF, 0x8C96FF, 0xB983FF, 0xDD6FFF, 0xEA6FBD, 0xEB8466, 0xDCA21F, 0xBAB403, 0x7ECB07, 0x54D33E, 0x3CD284, 0x3EC7CC, 0x4B4B4B, 0x000000, 0x000000, 0xFFFFFF, 0xBDE2FF, 0xCECFFF, 0xE6C2FF, 0xF6BCFF, 0xF9C2ED, 0xFACFC6, 0xF8DEAC, 0xEEE9A1, 0xD0F59F, 0xBBF5AF, 0xB3F5CD, 0xB9EDF0, 0xB9B9B9, 0x000000, 0x000000 } }, { "smooth-v2-fbx", "FBX's Smooth V2 palette", { 0x6A6A6A, 0x00148F, 0x1E029B, 0x3F008A, 0x600060, 0x660017, 0x570D00, 0x3C1F00, 0x1B3300, 0x004200, 0x004500, 0x003C1F, 0x00315C, 0x000000, 0x000000, 0x000000, 0xB9B9B9, 0x0F4BD4, 0x412DEB, 0x6C1DD9, 0x9C17AB, 0xA71A4D, 0x993200, 0x7C4A00, 0x546400, 0x1A7800, 0x007F00, 0x00763E, 0x00678F, 0x010101, 0x000000, 0x000000, 0xFFFFFF, 0x68A6FF, 0x8C9CFF, 0xB586FF, 0xD975FD, 0xE377B9, 0xE58D68, 0xD49D29, 0xB3AF0C, 0x7BC211, 0x55CA47, 0x46CB81, 0x47C1C5, 0x4A4A4A, 0x000000, 0x000000, 0xFFFFFF, 0xCCEAFF, 0xDDDEFF, 0xECDAFF, 0xF8D7FE, 0xFCD6F5, 0xFDDBCF, 0xF9E7B5, 0xF1F0AA, 0xDAFAA9, 0xC9FFBC, 0xC3FBD7, 0xC4F6F6, 0xBEBEBE, 0x000000, 0x000000 } }, { "nes-classic-fbx", "FBX's NES Classic palette", { 0x616161, 0x000088, 0x1F0D99, 0x371379, 0x561260, 0x5D0010, 0x520E00, 0x3A2308, 0x21350C, 0x0D410E, 0x174417, 0x003A1F, 0x002F57, 0x000000, 0x000000, 0x000000, 0xAAAAAA, 0x0D4DC4, 0x4B24DE, 0x6912CF, 0x9014AD, 0x9D1C48, 0x923404, 0x735005, 0x5D6913, 0x167A11, 0x138008, 0x127649, 0x1C6691, 0x000000, 0x000000, 0x000000, 0xFCFCFC, 0x639AFC, 0x8A7EFC, 0xB06AFC, 0xDD6DF2, 0xE771AB, 0xE38658, 0xCC9E22, 0xA8B100, 0x72C100, 0x5ACD4E, 0x34C28E, 0x4FBECE, 0x424242, 0x000000, 0x000000, 0xFCFCFC, 0xBED4FC, 0xCACAFC, 0xD9C4FC, 0xECC1FC, 0xFAC3E7, 0xF7CEC3, 0xE2CDA7, 0xDADB9C, 0xC8E39E, 0xBFE5B8, 0xB2EBC8, 0xB7E5EB, 0xACACAC, 0x000000, 0x000000 } }, { "royaltea", "Royaltea palette (PVM-2530)", { 0x5A6165, 0x0023A8, 0x0F17B0, 0x28129F, 0x550B61, 0x6B0A11, 0x6E0D00, 0x5E1900, 0x3C2402, 0x003104, 0x003508, 0x00341F, 0x002C55, 0x000000, 0x000000, 0x000000, 0xA7B5BC, 0x0059FF, 0x2A44FF, 0x523CF1, 0x9F34BA, 0xB32846, 0xBB2D09, 0x9E4100, 0x865A00, 0x246D02, 0x007312, 0x007156, 0x0066A6, 0x000000, 0x000000, 0x000000, 0xFFFFFF, 0x4B9FFF, 0x5A91FF, 0x867EFF, 0xD97DFF, 0xFF95CF, 0xFF8E76, 0xF7A247, 0xEFB412, 0x8CC51C, 0x48D04A, 0x10D197, 0x00C9F0, 0x43484B, 0x000000, 0x000000, 0xFFFFFF, 0xB1D9FF, 0xB1CFFF, 0xBCC8FF, 0xE3C8FF, 0xFFD3F7, 0xFFD5CB, 0xFFDEB9, 0xFFE5AD, 0xDBF6AF, 0xB7FBC4, 0x9CFBE6, 0x96F7FF, 0xB1C0C7, 0x000000, 0x000000 } }, { "mugicha", "Mugicha palette", { 0x5c6164, 0x0021a0, 0x00109c, 0x290c91, 0x520a5c, 0x6d000e, 0x590000, 0x430d00, 0x352200, 0x003800, 0x003d00, 0x003621, 0x00294c, 0x000000, 0x000000, 0x000000, 0xaab5ba, 0x0059f0, 0x2945f7, 0x523cf7, 0xac2dc2, 0xbc095d, 0xbd2c08, 0x964200, 0x825400, 0x007600, 0x007a00, 0x007152, 0x0062a0, 0x000000, 0x000000, 0x000000, 0xffffff, 0x2daaff, 0x5a92ff, 0x967eff, 0xeb7dff, 0xff82d0, 0xff8e73, 0xf79b3d, 0xe6b610, 0x73c40f, 0x32d141, 0x05cb88, 0x00c8f0, 0x414548, 0x000000, 0x000000, 0xffffff, 0xb1d9ff, 0xc8d2ff, 0xe2ccff, 0xffccff, 0xffc6fb, 0xffd0cb, 0xffdeb9, 0xffe5ad, 0xdbf6af, 0xb7fbc4, 0x9cfbe6, 0x96f7ff, 0xb4c0c5, 0x000000, 0x000000 } } }; /* ======================================== * Palette switching START * ======================================== */ /* Period in frames between palette switches * when holding RetroPad L2 + Left/Right */ #define PALETTE_SWITCH_PERIOD 30 static bool libretro_supports_set_variable = false; static bool palette_switch_enabled = false; static unsigned palette_switch_counter = 0; struct retro_core_option_value *palette_opt_values = NULL; static const char *palette_labels[PAL_TOTAL] = {0}; static uint32_t palette_switch_get_current_index(void) { if (current_palette < PAL_INTERNAL) return current_palette + 1; switch (current_palette) { case PAL_DEFAULT: return 0; case PAL_RAW: case PAL_CUSTOM: return current_palette - 1; default: break; } /* Cannot happen */ return 0; } static void palette_switch_init(void) { size_t i; struct retro_core_option_v2_definition *opt_defs = option_defs; struct retro_core_option_v2_definition *opt_def = NULL; #ifndef HAVE_NO_LANGEXTRA struct retro_core_option_v2_definition *opt_defs_intl = NULL; struct retro_core_option_v2_definition *opt_def_intl = NULL; unsigned language = 0; #endif libretro_supports_set_variable = false; if (environ_cb(RETRO_ENVIRONMENT_SET_VARIABLE, NULL)) libretro_supports_set_variable = true; palette_switch_enabled = libretro_supports_set_variable; palette_switch_counter = 0; #ifndef HAVE_NO_LANGEXTRA if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) && (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH) && options_intl[language]) opt_defs_intl = options_intl[language]->definitions; #endif /* Find option corresponding to palettes key */ for (opt_def = opt_defs; opt_def->key; opt_def++) if (!strcmp(opt_def->key, "fceumm_palette")) break; /* Cache option values array for fast access * when setting palette index */ palette_opt_values = opt_def->values; /* Loop over all palette values and fetch * palette labels for notification purposes */ for (i = 0; i < PAL_TOTAL; i++) { const char *value = opt_def->values[i].value; const char *value_label = NULL; /* Check if we have a localised palette label */ #ifndef HAVE_NO_LANGEXTRA if (opt_defs_intl) { /* Find localised option corresponding to key */ for (opt_def_intl = opt_defs_intl; opt_def_intl->key; opt_def_intl++) { if (!strcmp(opt_def_intl->key, "fceumm_palette")) { size_t j = 0; /* Search for current option value */ for (;;) { const char *value_intl = opt_def_intl->values[j].value; if (!value_intl) break; if (!strcmp(value, value_intl)) { /* We have a match; fetch localised label */ value_label = opt_def_intl->values[j].label; break; } j++; } break; } } } #endif /* If localised palette label is unset, * use label from option_defs_us or fallback * to value itself */ if (!value_label) value_label = opt_def->values[i].label; if (!value_label) value_label = value; palette_labels[i] = value_label; } } static void palette_switch_deinit(void) { libretro_supports_set_variable = false; palette_switch_enabled = false; palette_switch_counter = 0; palette_opt_values = NULL; } static void palette_switch_set_index(uint32_t palette_index) { struct retro_variable var = {0}; if (palette_index >= PAL_TOTAL) palette_index = PAL_TOTAL - 1; /* Notify frontend of option value changes */ var.key = "fceumm_palette"; var.value = palette_opt_values[palette_index].value; environ_cb(RETRO_ENVIRONMENT_SET_VARIABLE, &var); /* Display notification message */ FCEUD_DispMessage(RETRO_LOG_INFO, 2000, palette_labels[palette_index]); } /* ======================================== * Palette switching END * ======================================== */ /* ======================================== * Stereo Filter START * ======================================== */ enum stereo_filter_type { STEREO_FILTER_NULL = 0, STEREO_FILTER_DELAY }; static enum stereo_filter_type current_stereo_filter = STEREO_FILTER_NULL; #define STEREO_FILTER_DELAY_MS_DEFAULT 15.0f; typedef struct { int32_t *samples; size_t samples_size; size_t samples_pos; size_t delay_count; } stereo_filter_delay_t; static stereo_filter_delay_t stereo_filter_delay; static float stereo_filter_delay_ms = STEREO_FILTER_DELAY_MS_DEFAULT; static void stereo_filter_apply_null(int32_t *sound_buffer, size_t size) { size_t i; /* Each element of sound_buffer is a 16 bit mono sample * stored in a 32 bit value. We convert this to stereo * by copying the mono sample to both the high and low * 16 bit regions of the value and casting sound_buffer * to int16_t when uploading to the frontend */ for (i = 0; i < size; i++) sound_buffer[i] = (sound_buffer[i] << 16) | (sound_buffer[i] & 0xFFFF); } static void stereo_filter_apply_delay(int32_t *sound_buffer, size_t size) { size_t delay_capacity = stereo_filter_delay.samples_size - stereo_filter_delay.samples_pos; size_t i; /* Copy current samples into the delay buffer * (resizing if required) */ if (delay_capacity < size) { int32_t *tmp_buffer = NULL; size_t tmp_buffer_size; tmp_buffer_size = stereo_filter_delay.samples_size + (size - delay_capacity); tmp_buffer_size = (tmp_buffer_size << 1) - (tmp_buffer_size >> 1); tmp_buffer = (int32_t *)malloc(tmp_buffer_size * sizeof(int32_t)); if (!tmp_buffer) { /* On allocation failure, drop this batch rather than dereferencing * a NULL buffer in the memcpy below. The next batch will retry. */ return; } memcpy(tmp_buffer, stereo_filter_delay.samples, stereo_filter_delay.samples_pos * sizeof(int32_t)); free(stereo_filter_delay.samples); stereo_filter_delay.samples = tmp_buffer; stereo_filter_delay.samples_size = tmp_buffer_size; } /* Copy current samples into the delay buffer's tail. The previous * implementation walked the array element-by-element; memcpy is * trivially equivalent for non-overlapping int32_t blocks and * lets the compiler/libc dispatch SIMD where available. */ memcpy(stereo_filter_delay.samples + stereo_filter_delay.samples_pos, sound_buffer, size * sizeof(int32_t)); stereo_filter_delay.samples_pos += size; /* If we have enough samples in the delay * buffer, mix them into the output */ if (stereo_filter_delay.samples_pos > stereo_filter_delay.delay_count) { size_t delay_index = 0; size_t samples_to_mix = stereo_filter_delay.samples_pos - stereo_filter_delay.delay_count; samples_to_mix = (samples_to_mix > size) ? size : samples_to_mix; /* Perform 'null' filtering for any samples for * which a delay buffer entry is unavailable */ if (size > samples_to_mix) for (i = 0; i < size - samples_to_mix; i++) sound_buffer[i] = (sound_buffer[i] << 16) | (sound_buffer[i] & 0xFFFF); /* Each element of sound_buffer is a 16 bit mono sample * stored in a 32 bit value. We convert this to stereo * by copying the mono sample to the high (left channel) * 16 bit region and the delayed sample to the low * (right channel) region, casting sound_buffer * to int16_t when uploading to the frontend */ for (i = size - samples_to_mix; i < size; i++) sound_buffer[i] = (sound_buffer[i] << 16) | (stereo_filter_delay.samples[delay_index++] & 0xFFFF); /* Remove the mixed samples from the delay buffer */ memmove(stereo_filter_delay.samples, stereo_filter_delay.samples + samples_to_mix, (stereo_filter_delay.samples_pos - samples_to_mix) * sizeof(int32_t)); stereo_filter_delay.samples_pos -= samples_to_mix; } /* Otherwise apply the regular 'null' filter */ else for (i = 0; i < size; i++) sound_buffer[i] = (sound_buffer[i] << 16) | (sound_buffer[i] & 0xFFFF); } static void (*stereo_filter_apply)(int32_t *sound_buffer, size_t size) = stereo_filter_apply_null; static void stereo_filter_deinit_delay(void) { if (stereo_filter_delay.samples) free(stereo_filter_delay.samples); stereo_filter_delay.samples = NULL; stereo_filter_delay.samples_size = 0; stereo_filter_delay.samples_pos = 0; stereo_filter_delay.delay_count = 0; } static void stereo_filter_init_delay(void) { size_t initial_samples_size; /* Convert delay (ms) to number of samples */ stereo_filter_delay.delay_count = (size_t)( (stereo_filter_delay_ms / 1000.0f) * (float)sndsamplerate); /* Preallocate delay_count + worst case expected * samples per frame to minimise reallocation of * the samples buffer during runtime */ initial_samples_size = stereo_filter_delay.delay_count + (size_t)((float)sndsamplerate / NES_PAL_FPS) + 1; stereo_filter_delay.samples = (int32_t *)malloc( initial_samples_size * sizeof(int32_t)); if (!stereo_filter_delay.samples) { /* Fall back to the null filter on allocation failure rather than * leaving a NULL samples buffer that the apply path would deref. */ stereo_filter_delay.samples_size = 0; stereo_filter_delay.samples_pos = 0; stereo_filter_apply = stereo_filter_apply_null; return; } stereo_filter_delay.samples_size = initial_samples_size; stereo_filter_delay.samples_pos = 0; /* Assign function pointer */ stereo_filter_apply = stereo_filter_apply_delay; } static void stereo_filter_deinit(void) { /* Clean up */ stereo_filter_deinit_delay(); /* Assign default function pointer */ stereo_filter_apply = stereo_filter_apply_null; } static void stereo_filter_init(void) { stereo_filter_deinit(); /* Use a case statement to simplify matters * if more filter types are added in the * future... */ switch (current_stereo_filter) { case STEREO_FILTER_DELAY: stereo_filter_init_delay(); break; default: break; } } /* ======================================== * Stereo Filter END * ======================================== */ #ifdef HAVE_NTSC_FILTER /* ntsc */ #include "nes_ntsc.h" #define NTSC_NONE 0 #define NTSC_COMPOSITE 1 #define NTSC_SVIDEO 2 #define NTSC_RGB 3 #define NTSC_MONOCHROME 4 #define NES_NTSC_WIDTH (((NES_NTSC_OUT_WIDTH(256) + 3) >> 2) << 2) static unsigned use_ntsc = 0; static unsigned burst_phase = 0; static nes_ntsc_t nes_ntsc; static nes_ntsc_setup_t ntsc_setup; static bpp_t *ntsc_video_out = NULL; /* for ntsc blit buffer */ static void NTSCFilter_Cleanup(void) { if (ntsc_video_out) free(ntsc_video_out); ntsc_video_out = NULL; use_ntsc = 0; burst_phase = 0; } static void NTSCFilter_Init(void) { memset(&nes_ntsc, 0, sizeof(nes_ntsc)); memset(&ntsc_setup, 0, sizeof(ntsc_setup)); ntsc_video_out = (bpp_t *)malloc(NES_NTSC_WIDTH * NES_HEIGHT * sizeof(bpp_t)); } static void NTSCFilter_Setup(void) { if (ntsc_video_out == NULL) NTSCFilter_Init(); switch (use_ntsc) { case NTSC_COMPOSITE: ntsc_setup = nes_ntsc_composite; break; case NTSC_SVIDEO: ntsc_setup = nes_ntsc_svideo; break; case NTSC_RGB: ntsc_setup = nes_ntsc_rgb; break; case NTSC_MONOCHROME: ntsc_setup = nes_ntsc_monochrome; break; default: break; } ntsc_setup.merge_fields = 0; if (GameInfo && (GameInfo->type != GIT_VSUNI) && (current_palette == PAL_DEFAULT || current_palette == PAL_RAW)) /* use ntsc default palette instead of internal default palette for that "identity" effect */ ntsc_setup.palette = NULL; else /* use internal palette, this includes palette presets, external palette and custom palettes * for VS. System games */ ntsc_setup.palette = (unsigned char const *)palo; nes_ntsc_init(&nes_ntsc, &ntsc_setup); } #endif /* HAVE_NTSC_FILTER */ static void ResetPalette(void); static void retro_set_custom_palette(void); static void ResetPalette(void) { retro_set_custom_palette(); #ifdef HAVE_NTSC_FILTER NTSCFilter_Setup(); #endif } unsigned retro_api_version(void) { return RETRO_API_VERSION; } void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; } 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) { poll_cb = cb; } void retro_set_input_state(retro_input_state_t cb) { input_cb = cb; } static void update_nes_controllers(unsigned port, unsigned device) { nes_input.type[port] = device; if (port < 4) { switch (device) { case RETRO_DEVICE_NONE: FCEUI_SetInput(port, SI_NONE, &Dummy, 0); FCEU_printf(" Player %u: None Connected\n", port + 1); break; case RETRO_DEVICE_ZAPPER: FCEUI_SetInput(port, SI_ZAPPER, nes_input.MouseData[port], 1); FCEU_printf(" Player %u: Zapper\n", port + 1); break; case RETRO_DEVICE_ARKANOID: FCEUI_SetInput(port, SI_ARKANOID, nes_input.MouseData[port], 0); FCEU_printf(" Player %u: Arkanoid\n", port + 1); break; case RETRO_DEVICE_POWERPADA: nes_input.type[port] = RETRO_DEVICE_POWERPADA; FCEUI_SetInput(port, SI_POWERPADA, &nes_input.PowerPadData, 0); FCEU_printf(" Player %u: Power Pad\n", port + 1); break; case RETRO_DEVICE_POWERPADB: nes_input.type[port] = RETRO_DEVICE_POWERPADB; FCEUI_SetInput(port, SI_POWERPADB, &nes_input.PowerPadData, 0); FCEU_printf(" Player %u: Power Pad\n", port + 1); break; case RETRO_DEVICE_GAMEPAD: default: nes_input.type[port] = RETRO_DEVICE_GAMEPAD; FCEUI_SetInput(port, SI_GAMEPAD, &nes_input.JSReturn, 0); FCEU_printf(" Player %u: Gamepad\n", port + 1); break; } } if (port == 4) { switch (device) { case RETRO_DEVICE_FC_ARKANOID: FCEUI_SetInputFC(SIFC_ARKANOID, nes_input.FamicomData, 0); FCEU_printf(" Famicom Expansion: Arkanoid\n"); break; case RETRO_DEVICE_FC_SHADOW: FCEUI_SetInputFC(SIFC_SHADOW, nes_input.FamicomData, 1); FCEU_printf(" Famicom Expansion: (Bandai) Hyper Shot\n"); break; case RETRO_DEVICE_FC_OEKAKIDS: FCEUI_SetInputFC(SIFC_OEKAKIDS, nes_input.FamicomData, 1); FCEU_printf(" Famicom Expansion: Oeka Kids Tablet\n"); break; case RETRO_DEVICE_FC_4PLAYERS: FCEUI_SetInputFC(SIFC_4PLAYER, &nes_input.JSReturn, 0); FCEU_printf(" Famicom Expansion: Famicom 4-Player Adapter\n"); break; case RETRO_DEVICE_FC_HYPERSHOT: FCEUI_SetInputFC(SIFC_HYPERSHOT, nes_input.FamicomData, 0); FCEU_printf(" Famicom Expansion: Konami Hyper Shot\n"); break; case RETRO_DEVICE_FC_FTRAINERA: FCEUI_SetInputFC(SIFC_FTRAINERA, &nes_input.PowerPadData, 0); FCEU_printf(" Famicom Expansion: Family Trainer A\n"); break; case RETRO_DEVICE_FC_FTRAINERB: FCEUI_SetInputFC(SIFC_FTRAINERB, &nes_input.PowerPadData, 0); FCEU_printf(" Famicom Expansion: Family Trainer B\n"); break; case RETRO_DEVICE_FC_FKB: FCEUI_SetInputFC(SIFC_FKB, &nes_input.FamilyKeyboardData, 0); FCEU_printf(" Famicom Expansion: Family BASIC Keyboard\n"); break; case RETRO_DEVICE_FC_SUBORKB: FCEUI_SetInputFC(SIFC_SUBORKB, &nes_input.SuborKeyboardData, 0); FCEU_printf(" Famicom Expansion: Subor Keyboard\n"); break; case RETRO_DEVICE_FC_PEC586KB: FCEUI_SetInputFC(SIFC_PEC586KB, &nes_input.SuborKeyboardData, 0); FCEU_printf(" Famicom Expansion: Dongda Keyboard\n"); break; case RETRO_DEVICE_NONE: default: FCEUI_SetInputFC(SIFC_NONE, &Dummy, 0); FCEU_printf(" Famicom Expansion: None Connected\n"); break; } } } static unsigned nes_to_libretro(int d) { switch(d) { case SI_UNSET: case SI_GAMEPAD: return RETRO_DEVICE_GAMEPAD; case SI_NONE: return RETRO_DEVICE_NONE; case SI_ZAPPER: return RETRO_DEVICE_ZAPPER; case SI_ARKANOID: return RETRO_DEVICE_ARKANOID; case SI_POWERPADB: return RETRO_DEVICE_POWERPADB; } return (RETRO_DEVICE_GAMEPAD); } static unsigned fc_to_libretro(int d) { switch(d) { case SIFC_UNSET: case SIFC_NONE: return RETRO_DEVICE_NONE; case SIFC_ARKANOID: return RETRO_DEVICE_FC_ARKANOID; case SIFC_SHADOW: return RETRO_DEVICE_FC_SHADOW; case SIFC_OEKAKIDS: return RETRO_DEVICE_FC_OEKAKIDS; case SIFC_4PLAYER: return RETRO_DEVICE_FC_4PLAYERS; case SIFC_HYPERSHOT: return RETRO_DEVICE_FC_HYPERSHOT; case SIFC_FTRAINERA: return RETRO_DEVICE_FC_FTRAINERA; case SIFC_FTRAINERB: return RETRO_DEVICE_FC_FTRAINERB; case SIFC_FKB: return RETRO_DEVICE_FC_FKB; case SIFC_SUBORKB: return RETRO_DEVICE_FC_SUBORKB; case SIFC_PEC586KB: return RETRO_DEVICE_FC_PEC586KB; } return (RETRO_DEVICE_NONE); } void retro_set_controller_port_device(unsigned port, unsigned device) { if (port < 5) { if (port < 2) /* player 1-2 */ { if (device != RETRO_DEVICE_AUTO) update_nes_controllers(port, device); else if (GameInfo) /* GameInfo may be NULL pre-load */ update_nes_controllers(port, nes_to_libretro(GameInfo->input[port])); } else { if (port < 4) /* player 3-4 */ { /* This section automatically enables 4players support * when player 3 or 4 used */ nes_input.type[port] = RETRO_DEVICE_NONE; if (device == RETRO_DEVICE_AUTO) { if (nes_input.enable_4player) nes_input.type[port] = RETRO_DEVICE_GAMEPAD; } else if (device == RETRO_DEVICE_GAMEPAD) nes_input.type[port] = RETRO_DEVICE_GAMEPAD; FCEU_printf(" Player %u: %s\n", port + 1, (nes_input.type[port] == RETRO_DEVICE_NONE) ? "None Connected" : "Gamepad"); } else /* do famicom controllers here */ { if (device != RETRO_DEVICE_FC_AUTO) update_nes_controllers(4, device); else if (GameInfo) /* GameInfo may be NULL pre-load */ update_nes_controllers(4, fc_to_libretro(GameInfo->inputfc)); } if (nes_input.type[2] == RETRO_DEVICE_GAMEPAD || nes_input.type[3] == RETRO_DEVICE_GAMEPAD) FCEUI_DisableFourScore(0); else FCEUI_DisableFourScore(1); /* check if famicom 4player adapter is used */ if (nes_input.type[4] == RETRO_DEVICE_FC_4PLAYERS) FCEUI_DisableFourScore(1); } } } /* Core options 'update display' callback */ static bool update_option_visibility(void) { struct retro_variable var = {0}; bool updated = false; /* If frontend supports core option categories, * then fceumm_show_adv_system_options and * fceumm_show_adv_sound_options are ignored * and no options should be hidden */ if (libretro_supports_option_categories) return false; var.key = "fceumm_show_adv_system_options"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { bool opt_showAdvSystemOptions_prev = opt_showAdvSystemOptions; opt_showAdvSystemOptions = true; if (strcmp(var.value, "disabled") == 0) opt_showAdvSystemOptions = false; if (opt_showAdvSystemOptions != opt_showAdvSystemOptions_prev) { struct retro_core_option_display option_display; unsigned i; unsigned size; char options_list[][25] = { "fceumm_overclocking", "fceumm_ramstate", "fceumm_nospritelimit", "fceumm_up_down_allowed", "fceumm_show_crosshair" }; option_display.visible = opt_showAdvSystemOptions; size = sizeof(options_list) / sizeof(options_list[0]); for (i = 0; i < size; i++) { option_display.key = options_list[i]; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); } updated = true; } } var.key = "fceumm_show_adv_sound_options"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { bool opt_showAdvSoundOptions_prev = opt_showAdvSoundOptions; opt_showAdvSoundOptions = true; if (strcmp(var.value, "disabled") == 0) opt_showAdvSoundOptions = false; if (opt_showAdvSoundOptions != opt_showAdvSoundOptions_prev) { struct retro_core_option_display option_display; unsigned i; unsigned size; char options_list[][32] = { "fceumm_sndvolume", "fceumm_sndquality", "fceumm_sndlowpass", "fceumm_removetrianglenoise", "fceumm_reducedmcpopping", "fceumm_sndstereodelay", "fceumm_swapduty", "fceumm_apu_1", "fceumm_apu_2", "fceumm_apu_3", "fceumm_apu_4", "fceumm_apu_5" }; option_display.visible = opt_showAdvSoundOptions; size = sizeof(options_list) / sizeof(options_list[0]); for (i = 0; i < size; i++) { option_display.key = options_list[i]; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); } updated = true; } } return updated; } static void set_variables(void) { struct retro_core_option_display option_display; unsigned index = 0; option_display.visible = false; /* Initialize main core option struct */ memset(&option_defs_us, 0, sizeof(option_defs_us)); /* Write common core options to main struct */ while (option_defs[index].key) { memcpy(&option_defs_us[index], &option_defs[index], sizeof(struct retro_core_option_v2_definition)); index++; } /* Append dipswitch settings to core options if available */ set_dipswitch_variables(index, option_defs_us); libretro_supports_option_categories = false; libretro_set_core_options(environ_cb, &libretro_supports_option_categories); /* If frontend supports core option categories, * fceumm_show_adv_system_options and * fceumm_show_adv_sound_options are unused * and should be hidden */ if (libretro_supports_option_categories) { option_display.key = "fceumm_show_adv_system_options"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = "fceumm_show_adv_sound_options"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); } /* If frontend does not support core option * categories, core options may be shown/hidden * at runtime. In this case, register 'update * display' callback, so frontend can update * core options menu without calling retro_run() */ else { struct retro_core_options_update_display_callback update_display_cb; update_display_cb.callback = update_option_visibility; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK, &update_display_cb); } /* VS UNISystem games use internal palette regardless * of user setting, so hide fceumm_palette option */ if (GameInfo && (GameInfo->type == GIT_VSUNI)) { option_display.key = "fceumm_palette"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); /* Additionally disable gamepad palette * switching */ palette_switch_enabled = false; } } /* Game Genie add-on must be enabled before * loading content, so we cannot parse this * option inside check_variables() */ static void check_game_genie_variable(void) { struct retro_variable var = {0}; int game_genie_enabled = 0; var.key = "fceumm_game_genie"; /* Game Genie is only enabled for regular * cartridges (excludes arcade content, * FDS games, etc.) */ if ((GameInfo->type == GIT_CART) && (iNESCart.mapper != 105) && /* Nintendo World Championship cart (Mapper 105)*/ environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "enabled")) game_genie_enabled = 1; FCEUI_SetGameGenie(game_genie_enabled); } /* Callback passed to FCEUI_LoadGame() * > Required since we must set and check * core options immediately after ROM * is loaded, before FCEUI_LoadGame() * returns */ static void frontend_post_load_init() { set_variables(); check_game_genie_variable(); } void retro_set_environment(retro_environment_t cb) { struct retro_vfs_interface_info vfs_iface_info; static const struct retro_controller_description pads1[] = { { "Auto", RETRO_DEVICE_AUTO }, { "Gamepad", RETRO_DEVICE_GAMEPAD }, { "Zapper", RETRO_DEVICE_ZAPPER }, { 0, 0 }, }; static const struct retro_controller_description pads2[] = { { "Auto", RETRO_DEVICE_AUTO }, { "Gamepad", RETRO_DEVICE_GAMEPAD }, { "Arkanoid", RETRO_DEVICE_ARKANOID }, { "Zapper", RETRO_DEVICE_ZAPPER }, { "Power Pad A", RETRO_DEVICE_POWERPADA }, { "Power Pad B", RETRO_DEVICE_POWERPADB }, { 0, 0 }, }; static const struct retro_controller_description pads3[] = { { "Auto", RETRO_DEVICE_AUTO }, { "Gamepad", RETRO_DEVICE_GAMEPAD }, { "Power Pad A", RETRO_DEVICE_POWERPADA }, { "Power Pad B", RETRO_DEVICE_POWERPADB }, { 0, 0 }, }; static const struct retro_controller_description pads4[] = { { "Auto", RETRO_DEVICE_AUTO }, { "Gamepad", RETRO_DEVICE_GAMEPAD }, { 0, 0 }, }; static const struct retro_controller_description pads5[] = { { "Auto", RETRO_DEVICE_FC_AUTO }, { "Arkanoid", RETRO_DEVICE_FC_ARKANOID }, { "(Bandai) Hyper Shot", RETRO_DEVICE_FC_SHADOW }, { "(Konami) Hyper Shot", RETRO_DEVICE_FC_HYPERSHOT }, { "Oeka Kids Tablet", RETRO_DEVICE_FC_OEKAKIDS }, { "4-Player Adapter", RETRO_DEVICE_FC_4PLAYERS }, { "Family Trainer A", RETRO_DEVICE_FC_FTRAINERA }, { "Family Trainer B", RETRO_DEVICE_FC_FTRAINERB }, { 0, 0 }, }; static const struct retro_controller_info ports[] = { { pads1, 3 }, { pads2, 6 }, { pads3, 4 }, { pads4, 2 }, { pads5, 8 }, { 0, 0 }, }; static const struct retro_system_content_info_override content_overrides[] = { { "fds|nes|unf|unif", /* extensions */ false, /* need_fullpath */ false /* persistent_data */ }, { NULL, false, false } }; environ_cb = cb; environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); vfs_iface_info.required_interface_version = 1; vfs_iface_info.iface = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info)) filestream_vfs_init(&vfs_iface_info); environ_cb(RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE, (void*)content_overrides); } void retro_get_system_info(struct retro_system_info *info) { info->need_fullpath = true; info->valid_extensions = "fds|nes|unf|unif"; #ifdef GIT_VERSION info->library_version = "(SVN)" GIT_VERSION; #else info->library_version = "(SVN)"; #endif info->library_name = "FCEUmm"; info->block_extract = false; } static float get_aspect_ratio(unsigned width, unsigned height) { if (aspect_ratio_par == 2) return NES_4_3; else if (aspect_ratio_par == 3) return NES_PP; else return NES_8_7_PAR; } void retro_get_system_av_info(struct retro_system_av_info *info) { unsigned width = NES_WIDTH - crop_overscan_h_left - crop_overscan_h_right; unsigned height = NES_HEIGHT - crop_overscan_v_top - crop_overscan_v_bottom; #ifdef HAVE_NTSC_FILTER info->geometry.base_width = (use_ntsc ? NES_NTSC_OUT_WIDTH(width) : width); info->geometry.max_width = (use_ntsc ? NES_NTSC_WIDTH : NES_WIDTH); #else info->geometry.base_width = width; info->geometry.max_width = NES_WIDTH; #endif info->geometry.base_height = height; info->geometry.max_height = NES_HEIGHT; info->geometry.aspect_ratio = get_aspect_ratio(width, height); info->timing.sample_rate = (float)sndsamplerate; if (FSettings.PAL || dendy) info->timing.fps = NES_PAL_FPS; else info->timing.fps = NES_NTSC_FPS; } static void check_system_specs(void) { /* TODO - when we get it running at fullspeed on PSP, set to 4 */ unsigned level = 5; environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level); } void retro_init(void) { bool achievements = true; log_cb.log=default_logger; environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log_cb); environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements); if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL)) libretro_supports_bitmasks = true; environ_cb(RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION, &libretro_msg_interface_version); palette_switch_init(); } static void retro_set_custom_palette(void) { unsigned i; palette_game_available = 0; use_raw_palette = false; /* VS UNISystem uses internal palette presets regardless of options */ if (GameInfo->type == GIT_VSUNI) FCEU_ResetPalette(); /* Reset and choose between default internal or external custom palette */ else if (current_palette == PAL_DEFAULT || current_palette == PAL_CUSTOM) { palette_game_available = external_palette_exist && (current_palette == PAL_CUSTOM); /* if palette_game_available is set to 1, external palette * is loaded, else it will load default NES palette. * FCEUI_SetPaletteArray() both resets the palette array to * internal default palette and then chooses which one to use. */ FCEUI_SetPaletteArray( NULL, 0 ); } /* setup raw palette */ else if (current_palette == PAL_RAW) { pal color; use_raw_palette = true; for (i = 0; i < 64; i++) { color.r = (((i >> 0) & 0xF) * 255) / 15; color.g = (((i >> 4) & 0x3) * 255) / 3; color.b = 0; FCEUD_SetPalette( i, color.r, color.g, color.b); } #if !defined(PSP) || !defined(PS2) for (i = 0; i < 512; i++) { color.r = (((i >> 0) & 0xF) * 255) / 15; color.g = (((i >> 4) & 0x3) * 255) / 3; color.b = (((i >> 6) & 0x7) * 255 / 7); FCEUD_SetPalette( 256 + i, color.r, color.g, color.b); } #endif } /* setup palette presets */ else { unsigned *palette_data = palettes[current_palette].data; for ( i = 0; i < 64; i++ ) { unsigned data = palette_data[i]; base_palette[ i * 3 + 0 ] = ( data >> 16 ) & 0xff; /* red */ base_palette[ i * 3 + 1 ] = ( data >> 8 ) & 0xff; /* green */ base_palette[ i * 3 + 2 ] = ( data >> 0 ) & 0xff; /* blue */ } FCEUI_SetPaletteArray( base_palette, 64 ); } } /* Set variables for NTSC(1) / PAL(2) / Dendy(3) * Dendy has PAL framerate and resolution, but ~NTSC timings, * and has 50 dummy scanlines to force 50 fps. */ static void FCEUD_RegionOverride(unsigned region) { unsigned is_pal = 0; unsigned d = 0; switch (region) { case 0: /* auto */ d = (systemRegion >> 1) & 1; is_pal = systemRegion & 1; break; case 1: /* ntsc */ FCEUD_DispMessage(RETRO_LOG_INFO, 2000, "System: NTSC"); break; case 2: /* pal */ is_pal = 1; FCEUD_DispMessage(RETRO_LOG_INFO, 2000, "System: PAL"); break; case 3: /* dendy */ d = 1; FCEUD_DispMessage(RETRO_LOG_INFO, 2000, "System: Dendy"); break; } dendy = d; FCEUI_SetVidSystem(is_pal); ResetPalette(); } void retro_deinit (void) { FCEUI_CloseGame(); FCEUI_Sound(0); FCEUI_Kill(); #if defined(_3DS) if (fceu_video_out) linearFree(fceu_video_out); fceu_video_out = NULL; #else if (fceu_video_out) free(fceu_video_out); fceu_video_out = NULL; #endif #if defined(RENDER_GSKIT_PS2) ps2 = NULL; #endif libretro_supports_bitmasks = false; libretro_msg_interface_version = 0; DPSW_Cleanup(); #ifdef HAVE_NTSC_FILTER NTSCFilter_Cleanup(); #endif palette_switch_deinit(); stereo_filter_deinit(); } void retro_reset(void) { ResetNES(); } static void set_apu_channels(int chan) { /* Bitmask layout (set up by check_variables): bit i = fceumm_apu_(i+1) * is enabled. Channel order matches the libretro core options: * bit 0 = Square 1 (apu_1) * bit 1 = Square 2 (apu_2) * bit 2 = Triangle (apu_3) * bit 3 = Noise (apu_4) * bit 4 = PCM / DMC (apu_5) * * The previous masks crossed the SQ1/SQ2 indices and used non-power-of- * two values for the remaining channels, which silently broke the * apu_3..apu_5 toggles entirely (any combination of bits 0/1/2 left * those channels audible) and made apu_1 mute SQ2 and vice versa. */ FSettings.SquareVolume[0] = (chan & 0x01) ? 256 : 0; FSettings.SquareVolume[1] = (chan & 0x02) ? 256 : 0; FSettings.TriangleVolume = (chan & 0x04) ? 256 : 0; FSettings.NoiseVolume = (chan & 0x08) ? 256 : 0; FSettings.PCMVolume = (chan & 0x10) ? 256 : 0; } static void check_variables(bool startup) { struct retro_variable var = {0}; char key[256]; int i, enable_apu; bool stereo_filter_updated = false; /* 1 = Performs only geometry update: e.g. overscans */ /* 2 = Performs video/geometry update when needed and timing changes: e.g. region and filter change */ int audio_video_updated = 0; var.key = "fceumm_ramstate"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "random")) option_ramstate = 2; else if (!strcmp(var.value, "fill $00")) option_ramstate = 1; else option_ramstate = 0; } #ifdef HAVE_NTSC_FILTER var.key = "fceumm_ntsc_filter"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GameInfo && GameInfo->type != GIT_NSF) { unsigned orig_value = use_ntsc; if (strcmp(var.value, "disabled") == 0) use_ntsc = NTSC_NONE; else if (strcmp(var.value, "composite") == 0) use_ntsc = NTSC_COMPOSITE; else if (strcmp(var.value, "svideo") == 0) use_ntsc = NTSC_SVIDEO; else if (strcmp(var.value, "rgb") == 0) use_ntsc = NTSC_RGB; else if (strcmp(var.value, "monochrome") == 0) use_ntsc = NTSC_MONOCHROME; if (use_ntsc != orig_value) { ResetPalette(); audio_video_updated = 2; } } #endif /* HAVE_NTSC_FILTER */ var.key = "fceumm_palette"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { unsigned orig_value = current_palette; if (!strcmp(var.value, "default")) current_palette = PAL_DEFAULT; else if (!strcmp(var.value, "raw")) current_palette = PAL_RAW; else if (!strcmp(var.value, "custom")) current_palette = PAL_CUSTOM; else if (!strcmp(var.value, "asqrealc")) current_palette = 0; else if (!strcmp(var.value, "restored-wii-vc")) current_palette = 1; else if (!strcmp(var.value, "wii-vc")) current_palette = 2; else if (!strcmp(var.value, "rgb")) current_palette = 3; else if (!strcmp(var.value, "yuv-v3")) current_palette = 4; else if (!strcmp(var.value, "unsaturated-final")) current_palette = 5; else if (!strcmp(var.value, "sony-cxa2025as-us")) current_palette = 6; else if (!strcmp(var.value, "pal")) current_palette = 7; else if (!strcmp(var.value, "bmf-final2")) current_palette = 8; else if (!strcmp(var.value, "bmf-final3")) current_palette = 9; else if (!strcmp(var.value, "smooth-fbx")) current_palette = 10; else if (!strcmp(var.value, "composite-direct-fbx")) current_palette = 11; else if (!strcmp(var.value, "pvm-style-d93-fbx")) current_palette = 12; else if (!strcmp(var.value, "ntsc-hardware-fbx")) current_palette = 13; else if (!strcmp(var.value, "nes-classic-fbx-fs")) current_palette = 14; else if (!strcmp(var.value, "nescap")) current_palette = 15; else if (!strcmp(var.value, "wavebeam")) current_palette = 16; else if (!strcmp(var.value, "digital-prime-fbx")) current_palette = 17; else if (!strcmp(var.value, "magnum-fbx")) current_palette = 18; else if (!strcmp(var.value, "smooth-v2-fbx")) current_palette = 19; else if (!strcmp(var.value, "nes-classic-fbx")) current_palette = 20; else if (!strcmp(var.value, "royaltea")) current_palette = 21; else if (!strcmp(var.value, "mugicha")) current_palette = 22; if (current_palette != orig_value) { audio_video_updated = 1; ResetPalette(); } } var.key = "fceumm_up_down_allowed"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) nes_input.up_down_allowed = (!strcmp(var.value, "enabled")) ? true : false; else nes_input.up_down_allowed = false; var.key = "fceumm_nospritelimit"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int no_sprite_limit = (!strcmp(var.value, "enabled")) ? 1 : 0; FCEUI_DisableSpriteLimitation(no_sprite_limit); } var.key = "fceumm_overclocking"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { bool do_reinit = false; if (!strcmp(var.value, "disabled") && overclock_enabled != 0) { skip_7bit_overclocking = 1; extrascanlines = 0; vblankscanlines = 0; overclock_enabled = 0; do_reinit = true; } else if (!strcmp(var.value, "2x-Postrender")) { skip_7bit_overclocking = 1; extrascanlines = 266; vblankscanlines = 0; overclock_enabled = 1; do_reinit = true; } else if (!strcmp(var.value, "2x-VBlank")) { skip_7bit_overclocking = 1; extrascanlines = 0; vblankscanlines = 266; overclock_enabled = 1; do_reinit = true; } normal_scanlines = dendy ? 290 : 240; totalscanlines = normal_scanlines + (overclock_enabled ? extrascanlines : 0); if (do_reinit && startup) { FCEU_KillVirtualVideo(); FCEU_InitVirtualVideo(); } } var.key = "fceumm_zapper_mode"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "mouse")) { zappermode = RetroMouse; switchZapper = 0; } else if (!strcmp(var.value, "touchscreen")) { zappermode = RetroPointer; switchZapper = 0; } else if (!strcmp(var.value, "stlightgun")) { zappermode = RetroSTLightgun; switchZapper = 1; } else { zappermode = RetroCLightgun; /*default setting*/ switchZapper = 0; } } var.key = "fceumm_arkanoid_mode"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "touchscreen")) { arkanoidmode = RetroArkanoidPointer; } else if (!strcmp(var.value, "abs_mouse")) { arkanoidmode = RetroArkanoidAbsMouse; } else if (!strcmp(var.value, "stelladaptor")) { arkanoidmode = RetroArkanoidStelladaptor; } else { arkanoidmode = RetroArkanoidMouse; /*default setting*/ } } var.key = "fceumm_zapper_tolerance"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { FCEU_ZapperSetTolerance(atoi(var.value)); } var.key = "fceumm_mouse_sensitivity"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { mouseSensitivity = atoi(var.value); } var.key = "fceumm_show_crosshair"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) show_crosshair = 1; else if (!strcmp(var.value, "disabled")) show_crosshair = 0; } var.key = "fceumm_zapper_trigger"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) zapper_trigger_invert_option = 1; else if (!strcmp(var.value, "disabled")) zapper_trigger_invert_option = 0; } var.key = "fceumm_zapper_sensor"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) zapper_sensor_invert_option = 1; else if (!strcmp(var.value, "disabled")) zapper_sensor_invert_option = 0; } #ifdef PSP var.key = "fceumm_overscan"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { bool newval = (!strcmp(var.value, "enabled")); if (newval != crop_overscan) { crop_overscan_h_left = (newval == true ? 8 : 0); crop_overscan_h_right = (newval == true ? 8 : 0); crop_overscan_v_top = (newval == true ? 8 : 0); crop_overscan_v_bottom = (newval == true ? 8 : 0); crop_overscan = newval; audio_video_updated = 1; } } #else var.key = "fceumm_overscan_h_left"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int newval = atoi(var.value); if (newval != crop_overscan_h_left) { crop_overscan_h_left = newval; audio_video_updated = 1; } } var.key = "fceumm_overscan_h_right"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int newval = atoi(var.value); if (newval != crop_overscan_h_right) { crop_overscan_h_right = newval; audio_video_updated = 1; } } var.key = "fceumm_overscan_v_top"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int newval = atoi(var.value); if (newval != crop_overscan_v_top) { crop_overscan_v_top = newval; audio_video_updated = 1; } } var.key = "fceumm_overscan_v_bottom"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int newval = atoi(var.value); if (newval != crop_overscan_v_bottom) { crop_overscan_v_bottom = newval; audio_video_updated = 1; } } #endif var.key = "fceumm_aspect"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int oldval = aspect_ratio_par; if (!strcmp(var.value, "8:7 PAR")) { aspect_ratio_par = 1; } else if (!strcmp(var.value, "4:3")) { aspect_ratio_par = 2; } else if (!strcmp(var.value, "PP")) { aspect_ratio_par = 3; } if (aspect_ratio_par != oldval) audio_video_updated = 1; } var.key = "fceumm_turbo_enable"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { nes_input.turbo_enabler[0] = 0; nes_input.turbo_enabler[1] = 0; if (!strcmp(var.value, "Player 1")) nes_input.turbo_enabler[0] = 1; else if (!strcmp(var.value, "Player 2")) nes_input.turbo_enabler[1] = 1; else if (!strcmp(var.value, "Both")) { nes_input.turbo_enabler[0] = 1; nes_input.turbo_enabler[1] = 1; } } var.key = "fceumm_turbo_delay"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) nes_input.turbo_delay = atoi(var.value); var.key = "fceumm_region"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { unsigned oldval = opt_region; if (!strcmp(var.value, "Auto")) opt_region = 0; else if (!strcmp(var.value, "NTSC")) opt_region = 1; else if (!strcmp(var.value, "PAL")) opt_region = 2; else if (!strcmp(var.value, "Dendy")) opt_region = 3; if (opt_region != oldval) { FCEUD_RegionOverride(opt_region); audio_video_updated = 2; } } var.key = "fceumm_sndquality"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { unsigned oldval = sndquality; if (!strcmp(var.value, "Low")) sndquality = 0; else if (!strcmp(var.value, "High")) sndquality = 1; else if (!strcmp(var.value, "Very High")) sndquality = 2; if (sndquality != oldval) FCEUI_SetSoundQuality(sndquality); } var.key = "fceumm_sndlowpass"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int lowpass = (!strcmp(var.value, "enabled")) ? 1 : 0; FCEUI_SetLowPass(lowpass); } var.key = "fceumm_removetrianglenoise"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int newval = (!strcmp(var.value, "enabled")) ? 1 : 0; FCEUI_RemoveTriangleNoise(newval); } var.key = "fceumm_reducedmcpopping"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int newval = (!strcmp(var.value, "enabled")) ? 1 : 0; FCEUI_ReduceDmcPopping(newval); } var.key = "fceumm_sndstereodelay"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { enum stereo_filter_type filter_type = STEREO_FILTER_NULL; float filter_delay_ms = STEREO_FILTER_DELAY_MS_DEFAULT; if (strcmp(var.value, "disabled") && (strlen(var.value) > 1)) { char value_str[3]; value_str[0] = var.value[0]; value_str[1] = var.value[1]; value_str[2] = '\0'; filter_type = STEREO_FILTER_DELAY; filter_delay_ms = (float)atoi(var.value); filter_delay_ms = (filter_delay_ms < 1.0f) ? 1.0f : filter_delay_ms; filter_delay_ms = (filter_delay_ms > 32.0f) ? 32.0f : filter_delay_ms; } if ((filter_type != current_stereo_filter) || ((filter_type == STEREO_FILTER_DELAY) && (filter_delay_ms != stereo_filter_delay_ms))) { current_stereo_filter = filter_type; stereo_filter_delay_ms = filter_delay_ms; stereo_filter_updated = true; } } if ((stereo_filter_updated || (audio_video_updated == 2)) && !startup) stereo_filter_init(); var.key = "fceumm_sndvolume"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int val = (int)(atof(var.value) * 25.6); sndvolume = val; FCEUD_SoundToggle(); } if (audio_video_updated && !startup) { struct retro_system_av_info av_info; retro_get_system_av_info(&av_info); if (audio_video_updated == 2) environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &av_info); else environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &av_info); } var.key = "fceumm_swapduty"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { bool newval = (!strcmp(var.value, "enabled")); if (newval != swapDuty) swapDuty = newval; } var.key = key; enable_apu = 0xff; strlcpy(key, "fceumm_apu_x", sizeof(key)); for (i = 0; i < 5; i++) { /* Replace the trailing 'x' with '1'..'5'. The literal length is * known at compile time so use sizeof-1 to avoid the strlen call * inside the loop. */ key[sizeof("fceumm_apu_") - 1] = '1' + i; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && !strcmp(var.value, "disabled")) enable_apu &= ~(1 << i); } set_apu_channels(enable_apu); update_dipswitch(); update_option_visibility(); } static void add_powerpad_input(unsigned port, uint32_t variant, uint32_t *ppdata) { unsigned k; const uint32_t* map = powerpadmap; for (k = 0 ; k < 12 ; k++) if (input_cb(0, RETRO_DEVICE_KEYBOARD, 0, map[k])) *ppdata |= (1 << k); } static void add_fkb_input(unsigned port, uint32_t variant, uint8_t *fkbkeys) { unsigned k; const uint32_t* map = fkbmap; for (k = 0 ; k < 0x48 ; k++) if (input_cb(0, RETRO_DEVICE_KEYBOARD, 0, map[k])) fkbkeys[k]=1; else fkbkeys[k]=0; } static void add_suborkey_input(unsigned port, uint32_t variant, uint8_t *suborkeys) { unsigned k; const uint32_t* map = suborkbmap; for (k = 0 ; k < 0x65 ; k++) if (input_cb(0, RETRO_DEVICE_KEYBOARD, 0, map[k])) suborkeys[k]=1; else suborkeys[k]=0; } static int mzx = 0, mzy = 0; static void get_mouse_input(unsigned port, uint32_t variant, uint32_t *mousedata) { int min_width, min_height, max_width, max_height; max_width = 256; max_height = 240; mousedata[2] = 0; /* reset click state */ if (variant == RETRO_DEVICE_FC_ARKANOID) variant = RETRO_DEVICE_ARKANOID; if ((variant != RETRO_DEVICE_ARKANOID && zappermode == RetroMouse) || (variant == RETRO_DEVICE_ARKANOID && arkanoidmode == RetroArkanoidMouse)) /* mouse device */ { int mouse_Lbutton; int mouse_Rbutton; min_width = crop_overscan_h_left + 1; min_height = crop_overscan_v_top + 1; max_width -= crop_overscan_h_right; max_height -= crop_overscan_v_bottom; mzx += mouseSensitivity * input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_X) / 100; mzy += mouseSensitivity * input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_Y) / 100; switch(variant) { case RETRO_DEVICE_ARKANOID: if (mzx < 0) mzx = 0; else if (mzx > 240) mzx = 240; if (mzy < min_height) mzy = min_height; else if (mzy > max_height) mzy = max_height; mousedata[1] = mzy; break; case RETRO_DEVICE_ZAPPER: default: /* Set crosshair within the limits of current screen resolution */ if (mzx < min_width) mzx = min_width; else if (mzx > max_width) mzx = max_width; break; } mousedata[0] = mzx; mouse_Lbutton = input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT); mouse_Rbutton = input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_RIGHT); if (mouse_Lbutton) mousedata[2] |= 0x1; if (mouse_Rbutton) mousedata[2] |= 0x2; } else if (variant != RETRO_DEVICE_ARKANOID && zappermode == RetroPointer) { int offset_x = (crop_overscan_h_left * 0x120) - 1; int offset_y = (crop_overscan_v_top * 0x133) + 1; int _x = input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X); int _y = input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y); if (_x == 0 && _y == 0) { mousedata[0] = 0; } else { mousedata[0] = (_x + (0x7FFF + offset_x)) * max_width / ((0x7FFF + offset_x) * 2); mousedata[1] = (_y + (0x7FFF + offset_y)) * max_height / ((0x7FFF + offset_y) * 2); } if (input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_PRESSED)) mousedata[2] |= 0x1; } else if (variant == RETRO_DEVICE_ARKANOID && (arkanoidmode == RetroArkanoidAbsMouse || arkanoidmode == RetroArkanoidPointer)) { int offset_x = (crop_overscan_h_left * 0x120) - 1; int _x = input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X); int _y = input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y); if (_x != 0 || _y != 0) { int32_t raw = (_x + (0x7FFF + offset_x)) * max_width / ((0x7FFF + offset_x) * 2); if (arkanoidmode == RetroArkanoidAbsMouse) { /* remap so full screen movement ends up within the encoder range 0-240 game board: 176 wide paddle: 32 range of movement: 176-32 = 144 left edge: 16 right edge: 64 increase movement by 10 to allow edges to be reached in case of problems */ raw = (raw - 128) * 140 / 128 + 128; if (raw < 0) raw = 0; else if (raw > 255) raw = 255; mousedata[0] = raw * 240 / 255; } else { /* remap so full board movement ends up within the encoder range 0-240 */ if (mousedata[0] < 16+(32/2)) mousedata[0] = 0; else mousedata[0] -= 16+(32/2); if (mousedata[0] > 144) mousedata[0] = 144; mousedata[0] = raw * 240 / 144; } } if (input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_PRESSED)) mousedata[2] |= 0x1; } else if (variant == RETRO_DEVICE_ARKANOID && arkanoidmode == RetroArkanoidStelladaptor) { int x = input_cb(port, RETRO_DEVICE_ANALOG, 0, RETRO_DEVICE_ID_ANALOG_X); mousedata[0] = (x+32768)*240/65535; if (input_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A) || input_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)) mousedata[2] |= 0x1; } else if (zappermode == RetroCLightgun) /* Crosshair lightgun device */ { int offset_x = (crop_overscan_h_left * 0x120) - 1; int offset_y = (crop_overscan_v_top * 0x133) + 1; int offscreen; int offscreen_shot; int trigger; offscreen = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN ); offscreen_shot = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD ); trigger = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER ); if ( offscreen || offscreen_shot ) { mousedata[0] = 0; mousedata[1] = 0; } else { int _x = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X ); int _y = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y ); mousedata[0] = (_x + (0x7FFF + offset_x)) * max_width / ((0x7FFF + offset_x) * 2); mousedata[1] = (_y + (0x7FFF + offset_y)) * max_height / ((0x7FFF + offset_y) * 2); } if ( trigger || offscreen_shot ) mousedata[2] |= 0x1; } else /* Sequential targets lightgun device integration */ { mousedata[2] = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER ); mousedata[3] = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_A ); } } static void FCEUD_UpdateInput(void) { unsigned player, port; bool palette_prev = false; bool palette_next = false; poll_cb(); /* Reset input states */ nes_input.JSReturn = 0; /* nes gamepad */ for (player = 0; player < MAX_PLAYERS; player++) { int i = 0; uint8_t input_buf = 0; int player_enabled = (nes_input.type[player] == RETRO_DEVICE_GAMEPAD) || (nes_input.type[player] == RETRO_DEVICE_JOYPAD); if (player_enabled) { int16_t ret; if (libretro_supports_bitmasks) { bool dpad_enabled = true; ret = input_cb(player, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK); /* If palette switching is enabled, check if * player 1 has the L2 button held down */ if ((player == 0) && palette_switch_enabled && (ret & (1 << RETRO_DEVICE_ID_JOYPAD_L2))) { /* D-Pad left/right are used to switch palettes */ palette_prev = (bool)(ret & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT)); palette_next = (bool)(ret & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT)); /* Regular D-Pad input is disabled */ dpad_enabled = false; } if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_A)) input_buf |= JOY_A; if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_B)) input_buf |= JOY_B; if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_L3)) input_buf |= JOY_A | JOY_B; if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT)) input_buf |= JOY_SELECT; if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_START)) input_buf |= JOY_START; if (dpad_enabled) { if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_UP)) input_buf |= JOY_UP; if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN)) input_buf |= JOY_DOWN; if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT)) input_buf |= JOY_LEFT; if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT)) input_buf |= JOY_RIGHT; } } else { for (i = 0; i < MAX_BUTTONS; i++) input_buf |= input_cb(player, RETRO_DEVICE_JOYPAD, 0, bindmap[i].retro) ? bindmap[i].nes : 0; /* If palette switching is enabled, check if * player 1 has the L2 button held down */ if ((player == 0) && palette_switch_enabled && input_cb(player, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2)) { /* D-Pad left/right are used to switch palettes */ palette_prev = (bool)(input_buf & JOY_LEFT); palette_next = (bool)(input_buf & JOY_RIGHT); /* Regular D-Pad input is disabled */ input_buf &= ~(JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT); } } /* Turbo A and Turbo B buttons are * mapped to Joypad X and Joypad Y * in RetroArch joypad. * Turbo A+B button is mapped to R3 * in RetroArch joypad. * * We achieve this by keeping track of * the number of times it increments * the toggle counter and fire or not fire * depending on whether the delay value has * been reached. * * Each turbo button is activated by * corresponding mapped button * OR mapped Turbo A+B button. * This allows Turbo A+B button to use * the same toggle counters as Turbo A * and Turbo B buttons use separately. */ if (nes_input.turbo_enabler[player]) { /* Handle Turbo A, B & A+B buttons */ for (i = 0; i < TURBO_BUTTONS; i++) { if (input_cb(player, RETRO_DEVICE_JOYPAD, 0, turbomap[i].retro) || input_cb(player, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) { if (!turbo_button_toggle[player][i]) input_buf |= turbomap[i].nes; turbo_button_toggle[player][i]++; turbo_button_toggle[player][i] %= nes_input.turbo_delay + 1; } else /* If the button is not pressed, just reset the toggle */ turbo_button_toggle[player][i] = 0; } } } if (!nes_input.up_down_allowed) { if ((input_buf & JOY_UP) && (input_buf & JOY_DOWN)) input_buf &= ~(JOY_UP | JOY_DOWN); if ((input_buf & JOY_LEFT) && (input_buf & JOY_RIGHT)) input_buf &= ~(JOY_LEFT | JOY_RIGHT); } nes_input.JSReturn |= (input_buf & 0xff) << (player << 3); } /* other inputs*/ for (port = 0; port < MAX_PORTS; port++) { switch (nes_input.type[port]) { case RETRO_DEVICE_ARKANOID: case RETRO_DEVICE_FC_ARKANOID: case RETRO_DEVICE_ZAPPER: get_mouse_input(port, nes_input.type[port], nes_input.MouseData[port]); break; } } nes_input.PowerPadData = 0; for (port = 0; port < MAX_PORTS; port++) { switch (nes_input.type[port]) { case RETRO_DEVICE_POWERPADB: case RETRO_DEVICE_POWERPADA: add_powerpad_input(port, nes_input.type[port], &nes_input.PowerPadData); break; } } /* famicom inputs */ switch (nes_input.type[4]) { case RETRO_DEVICE_FC_ARKANOID: case RETRO_DEVICE_FC_OEKAKIDS: case RETRO_DEVICE_FC_SHADOW: get_mouse_input(0, nes_input.type[4], nes_input.FamicomData); break; case RETRO_DEVICE_FC_HYPERSHOT: { static int toggle; int i; nes_input.FamicomData[0] = 0; toggle ^= 1; for (i = 0; i < 2; i++) { if (input_cb(i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)) nes_input.FamicomData[0] |= 0x02 << (i * 2); else if (input_cb(i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y)) { if (toggle) nes_input.FamicomData[0] |= 0x02 << (i * 2); else nes_input.FamicomData[0] &= ~(0x02 << (i * 2)); } if (input_cb(i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)) nes_input.FamicomData[0] |= 0x04 << (i * 2); else if (input_cb(i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X)) { if (toggle) nes_input.FamicomData[0] |= 0x04 << (i * 2); else nes_input.FamicomData[0] &= ~(0x04 << (i * 2)); } } break; } case RETRO_DEVICE_FC_FTRAINERB: case RETRO_DEVICE_FC_FTRAINERA: add_powerpad_input(4, nes_input.type[4], &nes_input.PowerPadData); break; case RETRO_DEVICE_FC_FKB: add_fkb_input(4, nes_input.type[4], nes_input.FamilyKeyboardData); break; case RETRO_DEVICE_FC_SUBORKB: case RETRO_DEVICE_FC_PEC586KB: add_suborkey_input(4, nes_input.type[4], nes_input.SuborKeyboardData); break; } if (input_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2)) FCEU_VSUniCoin(); /* Insert Coin VS System */ if (GameInfo->type == GIT_FDS) /* Famicom Disk System */ { bool curL = input_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L); bool curR = input_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R); static bool prevL = false, prevR = false; if (curL && !prevL) FCEU_FDSSelect(); /* Swap FDisk side */ prevL = curL; if (curR && !prevR) FCEU_FDSInsert(-1); /* Insert or eject the disk */ prevR = curR; } /* Handle internal palette switching */ if (palette_prev || palette_next) { if (palette_switch_counter == 0) { int new_palette_index = palette_switch_get_current_index(); if (palette_prev) { if (new_palette_index > 0) new_palette_index--; else new_palette_index = PAL_TOTAL - 1; } else /* palette_next */ { if ((unsigned long)new_palette_index < PAL_TOTAL - 1) new_palette_index++; else new_palette_index = 0; } palette_switch_set_index(new_palette_index); } palette_switch_counter++; if (palette_switch_counter >= PALETTE_SWITCH_PERIOD) palette_switch_counter = 0; } else palette_switch_counter = 0; } static void FCEUD_Update(uint8_t *XBuf, int32_t *Buffer, int Count) { } static void retro_run_blit(uint8_t *gfx) { unsigned x, y; #ifdef PSP static unsigned int __attribute__((aligned(16))) d_list[32]; void* texture_vram_p = NULL; #endif unsigned width = 256; unsigned height = 240; unsigned pitch = width * sizeof(bpp_t); #ifdef PSP if (crop_overscan) { width -= 16; height -= 16; } texture_vram_p = (void*) (0x44200000 - (256 * 256)); /* max VRAM address - frame size */ sceKernelDcacheWritebackRange(retro_palette,256 * 2); sceKernelDcacheWritebackRange(XBuf, 256*240 ); sceGuStart(GU_DIRECT, d_list); /* sceGuCopyImage doesnt seem to work correctly with GU_PSM_T8 * so we use GU_PSM_4444 ( 2 Bytes per pixel ) instead * with half the values for pitch / width / x offset */ if (crop_overscan) sceGuCopyImage(GU_PSM_4444, 4, 4, 120, 224, 128, XBuf, 0, 0, 128, texture_vram_p); else sceGuCopyImage(GU_PSM_4444, 0, 0, 128, 240, 128, XBuf, 0, 0, 128, texture_vram_p); sceGuTexSync(); sceGuTexImage(0, 256, 256, 256, texture_vram_p); sceGuTexMode(GU_PSM_T8, 0, 0, GU_FALSE); sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB); sceGuDisable(GU_BLEND); sceGuClutMode(GU_PSM_5650, 0, 0xFF, 0); sceGuClutLoad(32, retro_palette); sceGuFinish(); video_cb(texture_vram_p, width, height, 256); #elif defined(RENDER_GSKIT_PS2) uint32_t *buf = (uint32_t *)RETRO_HW_FRAME_BUFFER_VALID; if (!ps2) { if (!environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void **)&ps2) || !ps2) { FCEU_printf(" Failed to get HW rendering interface!\n"); return; } if (ps2->interface_version != RETRO_HW_RENDER_INTERFACE_GSKIT_PS2_VERSION) { FCEU_printf(" HW render interface mismatch, expected %u, got %u!\n", RETRO_HW_RENDER_INTERFACE_GSKIT_PS2_VERSION, ps2->interface_version); return; } ps2->coreTexture->Width = width; ps2->coreTexture->Height = height; ps2->coreTexture->PSM = GS_PSM_T8; ps2->coreTexture->ClutPSM = GS_PSM_CT16; ps2->coreTexture->Filter = GS_FILTER_LINEAR; ps2->padding = (struct retro_hw_ps2_insets){ (float) crop_overscan_v_top, (float) crop_overscan_h_left, (float) crop_overscan_v_bottom, (float) crop_overscan_h_right }; } ps2->coreTexture->Clut = (u32*)retro_palette; ps2->coreTexture->Mem = (u32*)gfx; video_cb(buf, width, height, pitch); #else #ifdef HAVE_NTSC_FILTER if (use_ntsc) { burst_phase ^= 1; if (ntsc_setup.merge_fields) burst_phase = 0; nes_ntsc_blit(&nes_ntsc, (NES_NTSC_IN_T const*)gfx, (NES_NTSC_IN_T *)XDBuf, NES_WIDTH, burst_phase, NES_WIDTH, NES_HEIGHT, ntsc_video_out, NES_NTSC_WIDTH * sizeof(bpp_t)); width = NES_WIDTH - crop_overscan_h_left - crop_overscan_h_right; width = NES_NTSC_OUT_WIDTH(width); height = NES_HEIGHT - crop_overscan_v_top - crop_overscan_v_bottom; pitch = NES_NTSC_WIDTH * sizeof(bpp_t); /* Pass ntsc_video_out directly to the frontend with the wider * source pitch instead of memcpy'ing into a tightly-packed * fceu_video_out. video_cb's pitch parameter already lets the * frontend skip past the unused right margin per scanline; the * memcpy was redundant. Saves ~580 KB per frame at 32 bpp full * NES_NTSC_OUT_WIDTH * NES_HEIGHT - i.e. ~35 MB/s of bandwidth * at 60 fps. */ { int32_t h_offset = (crop_overscan_h_left ? NES_NTSC_OUT_WIDTH(crop_overscan_h_left) : 0); int32_t v_offset = crop_overscan_v_top; const bpp_t *in = ntsc_video_out + h_offset + NES_NTSC_WIDTH * v_offset; video_cb(in, width, height, pitch); } } else #endif /* HAVE_NTSC_FILTER */ { width -= (crop_overscan_h_left + crop_overscan_h_right); height -= (crop_overscan_v_top + crop_overscan_v_bottom); pitch = width * sizeof(bpp_t); gfx += (crop_overscan_v_top * 256) + crop_overscan_h_left; { /* Try GET_CURRENT_SOFTWARE_FRAMEBUFFER for zero-copy: if the * frontend can hand us its own scanout buffer, we write the * pixel-format conversion directly into it and skip a round * trip through fceu_video_out. */ struct retro_framebuffer fb = {0}; bpp_t *target = fceu_video_out; size_t target_stride = (size_t)width; /* in pixels */ fb.width = width; fb.height = height; fb.access_flags = RETRO_MEMORY_ACCESS_WRITE; if (environ_cb(RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER, &fb) && fb.format == active_pixformat && fb.data && (fb.pitch % sizeof(bpp_t)) == 0) { target = (bpp_t *)fb.data; target_stride = fb.pitch / sizeof(bpp_t); pitch = fb.pitch; } /* Hoist loop-invariant decisions out of the hot per-pixel * loop. The PPU writes a uniform colour_emphasis byte to * every entry of XDBuf for a given scanline (ppu.c:683-685), * so emphasis is constant per row - branch once per row, not * per pixel. NSF and use_raw_palette flags don't change mid- * frame either. */ { const uint8_t *deemp_row = XDBuf + (gfx - XBuf); const bool is_nsf = (GameInfo->type == GIT_NSF); const uint8_t pixel_mask = use_raw_palette ? 0x3F : 0xFF; for (y = 0; y < height; y++) { bpp_t *out_row = target + (size_t)y * target_stride; uint8_t deemp = is_nsf ? 0 : deemp_row[0]; if (deemp != 0) { unsigned base = 256u + ((unsigned)(deemp & 0x07) << 6); for (x = 0; x < width; x++) out_row[x] = retro_palette[base + (gfx[x] & 0x3F)]; } else { for (x = 0; x < width; x++) out_row[x] = retro_palette[gfx[x] & pixel_mask]; } gfx += 256; deemp_row += 256; } } video_cb(target, width, height, pitch); } } #endif } void retro_run(void) { uint8_t *gfx; int32_t ssize = 0; bool updated = false; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(false); FCEUD_UpdateInput(); FCEUI_Emulate(&gfx, &sound, &ssize, 0); retro_run_blit(gfx); stereo_filter_apply(sound, ssize); audio_batch_cb((const int16_t*)sound, ssize); } size_t retro_serialize_size(void) { if (serialize_size == 0) { /* Something arbitrarily big.*/ uint8_t *buffer = (uint8_t*)malloc(1000000); if (!buffer) return 0; memstream_set_buffer(buffer, 1000000); FCEUSS_Save_Mem(); serialize_size = memstream_get_last_size(); free(buffer); } return serialize_size; } bool retro_serialize(void *data, size_t size) { /* Cannot save state while Game Genie * screen is open */ if (geniestage == 1) return false; if (!data || size != retro_serialize_size()) return false; memstream_set_buffer((uint8_t*)data, size); FCEUSS_Save_Mem(); return true; } bool retro_unserialize(const void * data, size_t size) { /* Cannot load state while Game Genie * screen is open */ if (geniestage == 1) return false; if (!data || size != retro_serialize_size()) return false; memstream_set_buffer((uint8_t*)data, size); FCEUSS_Load_Mem(); return true; } static int checkGG(char c) { static const char lets[16] = { 'A', 'P', 'Z', 'L', 'G', 'I', 'T', 'Y', 'E', 'O', 'X', 'U', 'K', 'S', 'V', 'N' }; int x; for (x = 0; x < 16; x++) if (lets[x] == toupper(c)) return 1; return 0; } static int GGisvalid(const char *code) { size_t len = strlen(code); uint32_t i; if (len != 6 && len != 8) return 0; for (i = 0; i < len; i++) if (!checkGG(code[i])) return 0; return 1; } void retro_cheat_reset(void) { FCEU_ResetCheats(); } void retro_cheat_set(unsigned index, bool enabled, const char *code) { char name[256]; char temp[1024]; char *codepart; uint16_t a; uint8_t v; int c; int type = 1; if (code == NULL) return; /* Cheat strings can be arbitrarily long (multiple codes joined by * separators). The previous strcpy into a 256-byte stack buffer was * trivially overflowable. Use strlcpy and a larger buffer; truncate * if necessary rather than overflow. */ strlcpy(name, "N/A", sizeof(name)); strlcpy(temp, code, sizeof(temp)); codepart = strtok(temp, "+,;._ "); while (codepart) { size_t codepart_len = strlen(codepart); if ((codepart_len == 7) && (codepart[4]==':')) { /* raw code in xxxx:xx format */ log_cb.log(RETRO_LOG_DEBUG, "Cheat code added: '%s' (Raw)\n", codepart); codepart[4] = '\0'; a = strtoul(codepart, NULL, 16); v = strtoul(codepart + 5, NULL, 16); c = -1; /* Zero-page addressing modes don't go through the normal read/write handlers in FCEU, so * we must do the old hacky method of RAM cheats. */ if (a < 0x0100) type = 0; FCEUI_AddCheat(name, a, v, c, type); } else if ((codepart_len == 10) && (codepart[4] == '?') && (codepart[7] == ':')) { /* raw code in xxxx?xx:xx */ log_cb.log(RETRO_LOG_DEBUG, "Cheat code added: '%s' (Raw)\n", codepart); codepart[4] = '\0'; codepart[7] = '\0'; a = strtoul(codepart, NULL, 16); v = strtoul(codepart + 8, NULL, 16); c = strtoul(codepart + 5, NULL, 16); /* Zero-page addressing modes don't go through the normal read/write handlers in FCEU, so * we must do the old hacky method of RAM cheats. */ if (a < 0x0100) type = 0; FCEUI_AddCheat(name, a, v, c, type); } else if (GGisvalid(codepart) && FCEUI_DecodeGG(codepart, &a, &v, &c)) { FCEUI_AddCheat(name, a, v, c, type); log_cb.log(RETRO_LOG_DEBUG, "Cheat code added: '%s' (GG)\n", codepart); } else if (FCEUI_DecodePAR(codepart, &a, &v, &c, &type)) { FCEUI_AddCheat(name, a, v, c, type); log_cb.log(RETRO_LOG_DEBUG, "Cheat code added: '%s' (PAR)\n", codepart); } else log_cb.log(RETRO_LOG_DEBUG, "Invalid or unknown code: '%s'\n", codepart); codepart = strtok(NULL,"+,;._ "); } } typedef struct cartridge_db { char title[256]; uint32_t crc; } cartridge_db_t; static const struct cartridge_db fourscore_db_list[] = { { "Bomberman II (USA)", 0x1ebb5b42 }, #if 0 { "Championship Bowling (USA)", 0xeac38105 }, #endif { "Chris Evert & Ivan Lendl in Top Players' Tennis (USA)", 0xf99e37eb }, #if 0 { "Crash 'n' the Boys - Street Challenge (USA)", 0xc7f0c457 }, #endif { "Four Players' Tennis (Europe)", 0x48b8ee58 }, { "Danny Sullivan's Indy Heat (Europe)", 0x27ca0679, }, { "Gauntlet II (Europe)", 0x79f688bc }, { "Gauntlet II (USA)", 0x1b71ccdb }, { "Greg Norman's Golf Power (USA)", 0x1352f1b9 }, { "Harlem Globetrotters (USA)", 0x2e6ee98d }, { "Ivan 'Ironman' Stewart's Super Off Road (Europe)", 0x05104517 }, { "Ivan 'Ironman' Stewart's Super Off Road (USA)", 0x4b041b6b }, { "Kings of the Beach - Professional Beach Volleyball (USA)", 0xf54b34bd }, { "Magic Johnson's Fast Break (USA)", 0xc6c2edb5 }, { "M.U.L.E. (USA)", 0x0939852f }, { "Micro Mages", 0x4e6b9078 }, { "Monster Truck Rally (USA)", 0x2f698c4d }, { "NES Play Action Football (USA)", 0xb9b4d9e0 }, { "Nightmare on Elm Street, A (USA)", 0xda2cb59a }, { "Nintendo World Cup (Europe)", 0x8da6667d }, { "Nintendo World Cup (Europe) (Rev A)", 0x7c16f819 }, { "Nintendo World Cup (Europe) (Rev B)", 0x7f08d0d9 }, { "Nintendo World Cup (USA)", 0xa22657fa }, { "R.C. Pro-Am II (Europe)", 0x308da987 }, { "R.C. Pro-Am II (USA)", 0x9edd2159 }, { "Rackets & Rivals (Europe)", 0x8fa6e92c }, { "Roundball - 2-on-2 Challenge (Europe)", 0xad0394f0 }, { "Roundball - 2-on-2 Challenge (USA)", 0x6e4dcfd2 }, { "Spot - The Video Game (Japan)", 0x0abdd5ca }, { "Spot - The Video Game (USA)", 0xcfae9dfa }, { "Smash T.V. (Europe)", 0x0b8f8128 }, { "Smash T.V. (USA)", 0x6ee94d32 }, { "Super Jeopardy! (USA)", 0xcf4487a2 }, { "Super Spike V'Ball (Europe)", 0xc05a63b2 }, { "Super Spike V'Ball (USA)", 0xe840fd21 }, { "Super Spike V'Ball + Nintendo World Cup (USA)", 0x407d6ffd }, { "Swords and Serpents (Europe)", 0xd153caf6 }, { "Swords and Serpents (France)", 0x46135141 }, { "Swords and Serpents (USA)", 0x3417ec46 }, { "Battle City (Japan) (4 Players Hack) http://www.romhacking.net/hacks/2142/", 0x69977c9e }, { "Bomberman 3 (Homebrew) http://tempect.de/senil/games.html", 0x2da5ece0 }, { "K.Y.F.F. (Homebrew) http://slydogstudios.org/index.php/k-y-f-f/", 0x90d2e9f0 }, { "Super PakPak (Homebrew) http://wiki.nesdev.com/w/index.php/Super_PakPak", 0x1394ded0 }, { "Super Mario Bros. + Tetris + Nintendo World Cup (Europe)", 0x73298c87 }, { "Super Mario Bros. + Tetris + Nintendo World Cup (Europe) (Rev A)", 0xf46ef39a } }; static const struct cartridge_db famicom_4p_db_list[] = { { "Bakutoushi Patton-Kun (Japan) (FDS)", 0xc39b3bb2 }, { "Bomber Man II (Japan)", 0x0c401790 }, { "Championship Bowling (Japan)", 0x9992f445 }, { "Downtown - Nekketsu Koushinkyoku - Soreyuke Daiundoukai (Japan)", 0x3e470fe0 }, { "Ike Ike! Nekketsu Hockey-bu - Subette Koronde Dairantou (Japan)", 0x4f032933 }, { "Kunio-kun no Nekketsu Soccer League (Japan)", 0x4b5177e9 }, { "Moero TwinBee - Cinnamon Hakase o Sukue! (Japan)", 0x9f03b11f }, { "Moero TwinBee - Cinnamon Hakase wo Sukue! (Japan) (FDS)", 0x13205221 }, { "Nekketsu Kakutou Densetsu (Japan)", 0x37e24797 }, { "Nekketsu Koukou Dodgeball-bu (Japan)", 0x62c67984 }, { "Nekketsu! Street Basket - Ganbare Dunk Heroes (Japan)", 0x88062d9a }, { "Super Dodge Ball (USA) (3-4p with Game Genie code GEUOLZZA)", 0x689971f9 }, { "Super Dodge Ball (USA) (patched) http://www.romhacking.net/hacks/71/", 0x4ff17864 }, { "U.S. Championship V'Ball (Japan)", 0x213cb3fb }, { "U.S. Championship V'Ball (Japan) (Beta)", 0xd7077d96 }, { "Wit's (Japan)", 0xb1b16b8a } }; bool retro_load_game(const struct retro_game_info *info) { unsigned i, j; const char *system_dir = NULL; size_t fourscore_len = sizeof(fourscore_db_list) / sizeof(fourscore_db_list[0]); size_t famicom_4p_len = sizeof(famicom_4p_db_list) / sizeof(famicom_4p_db_list[0]); enum retro_pixel_format pixformat; struct retro_input_descriptor desc[] = { { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "(VSSystem) Insert Coin" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "(FDS) Disk Side Change" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "(FDS) Insert/Eject Disk" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" }, { 0 }, }; struct retro_input_descriptor desc_ps[] = { /* ps: palette switching */ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "Switch Palette (+ Left/Right)" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "(VSSystem) Insert Coin" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "(FDS) Disk Side Change" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "(FDS) Insert/Eject Disk" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" }, { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" }, { 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" }, { 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" }, { 0 }, }; size_t desc_base = 64; struct retro_memory_descriptor descs[64 + 4]; struct retro_memory_map mmaps; struct retro_game_info_ext *info_ext = NULL; const uint8_t *content_data = NULL; size_t content_size = 0; char content_path[2048] = {0}; /* Attempt to fetch extended game info */ if (environ_cb(RETRO_ENVIRONMENT_GET_GAME_INFO_EXT, &info_ext) && info_ext) { content_data = (const uint8_t *)info_ext->data; content_size = info_ext->size; if (info_ext->file_in_archive) { /* We don't have a 'physical' file in this * case, but the core still needs a filename * in order to detect the region of iNES v1.0 * ROMs. We therefore fake it, using the content * directory, canonical content name, and content * file extension */ snprintf(content_path, sizeof(content_path), "%s%c%s.%s", info_ext->dir, PATH_DEFAULT_SLASH_C(), info_ext->name, info_ext->ext); } else strlcpy(content_path, info_ext->full_path, sizeof(content_path)); } else { if (!info || string_is_empty(info->path)) return false; strlcpy(content_path, info->path, sizeof(content_path)); } #ifdef FRONTEND_SUPPORTS_RGB888 pixformat = RETRO_PIXEL_FORMAT_XRGB8888; if(environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixformat)) { log_cb.log(RETRO_LOG_INFO, "Frontend supports RGBX888 - will use that instead of XRGB1555.\n"); active_pixformat = pixformat; } #else #if FRONTEND_SUPPORTS_RGB565 pixformat = RETRO_PIXEL_FORMAT_RGB565; if(environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixformat)) { log_cb.log(RETRO_LOG_INFO, "Frontend supports RGB565 - will use that instead of XRGB1555.\n"); active_pixformat = pixformat; } #endif #endif /* initialize some of the default variables */ #ifdef GEKKO sndsamplerate = 32000; #else sndsamplerate = 48000; #endif sndquality = 0; sndvolume = 150; swapDuty = 0; dendy = 0; opt_region = 0; /* Wii: initialize this or else last variable is passed through * when loading another rom causing save state size change. */ serialize_size = 0; PowerNES(); check_system_specs(); #if defined(_3DS) fceu_video_out = (uint16_t*)linearMemAlign(256 * 240 * sizeof(uint16_t), 128); #elif !defined(PSP) #ifdef HAVE_NTSC_FILTER #define FB_WIDTH NES_NTSC_WIDTH #define FB_HEIGHT NES_HEIGHT #else /* !HAVE_NTSC_FILTER */ #define FB_WIDTH NES_WIDTH #define FB_HEIGHT NES_HEIGHT #endif #if defined(PS2) fceu_video_out = (uint8_t*)malloc(FB_WIDTH * FB_HEIGHT * sizeof(uint8_t)); #else fceu_video_out = (bpp_t*)malloc(FB_WIDTH * FB_HEIGHT * sizeof(bpp_t)); #endif #endif if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_dir) && system_dir) FCEUI_SetBaseDirectory(system_dir); memset(base_palette, 0, sizeof(base_palette)); FCEUI_Initialize(); FCEUI_SetSoundVolume(sndvolume); FCEUI_Sound(sndsamplerate); GameInfo = (FCEUGI*)FCEUI_LoadGame(content_path, content_data, content_size, frontend_post_load_init); if (!GameInfo) { /* On load failure the libretro frontend won't call retro_unload_game, * so free the framebuffer we just allocated to avoid leaking ~256 KB * per failed load. */ #if defined(_3DS) if (fceu_video_out) linearFree(fceu_video_out); #elif !defined(PSP) if (fceu_video_out) free(fceu_video_out); #endif fceu_video_out = NULL; #if 0 /* An error message here is superfluous - the frontend * will report that content loading has failed */ FCEUD_DispMessage(RETRO_LOG_ERROR, 3000, "ROM loading failed..."); #endif return false; } if (palette_switch_enabled) environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc_ps); else environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); for (i = 0; i < MAX_PORTS; i++) { FCEUI_SetInput(i, SI_GAMEPAD, &nes_input.JSReturn, 0); nes_input.type[i] = RETRO_DEVICE_JOYPAD; } external_palette_exist = palette_game_available; if (external_palette_exist) FCEU_printf(" Loading custom palette: %s%cnes.pal\n", system_dir, PATH_DEFAULT_SLASH_C()); /* Save region and dendy mode for region-auto detect */ systemRegion = (dendy << 1) | (retro_get_region() & 1); current_palette = 0; ResetPalette(); FCEUD_SoundToggle(); check_variables(true); stereo_filter_init(); PowerNES(); FCEUI_DisableFourScore(1); for (i = 0; i < fourscore_len; i++) { if (fourscore_db_list[i].crc == iNESCart.CRC32) { FCEUI_DisableFourScore(0); nes_input.enable_4player = true; break; } } for (i = 0; i < famicom_4p_len; i++) { if (famicom_4p_db_list[i].crc == iNESCart.CRC32) { GameInfo->inputfc = SIFC_4PLAYER; FCEUI_SetInputFC(SIFC_4PLAYER, &nes_input.JSReturn, 0); nes_input.enable_4player = true; break; } } memset(descs, 0, sizeof(descs)); i = 0; for (j = 0; j < desc_base; j++) { if (MMapPtrs[j] != NULL) { descs[i].ptr = MMapPtrs[j]; descs[i].start = j * 1024; descs[i].len = 1024; descs[i].select = 0; i++; } } /* This doesn't map in 2004--2007 but those aren't really * worthwhile to read from on a vblank anyway */ descs[i].flags = 0; descs[i].ptr = PPU; descs[i].offset = 0; descs[i].start = 0x2000; descs[i].select = 0; descs[i].disconnect = 0; descs[i].len = 4; descs[i].addrspace="PPUREG"; i++; /* In the future, it would be good to map pattern tables 1 and 2, * but these must be remapped often */ /* descs[i] = (struct retro_memory_descriptor){0, ????, 0, 0x0000 | PPU_BIT, PPU_BIT, PPU_BIT, 0x1000, "PAT0"}; */ /* i++; */ /* descs[i] = (struct retro_memory_descriptor){0, ????, 0, 0x1000 | PPU_BIT, PPU_BIT, PPU_BIT, 0x1000, "PAT1"}; */ /* i++; */ /* Likewise it would be better to use "vnapage" for this but * RetroArch API is inconvenient for handles like that, so we'll * just blithely assume the client will handle mapping and that * we'll ignore those carts that have extra NTARAM. */ descs[i].flags = 0; descs[i].ptr = NTARAM; descs[i].offset = 0; descs[i].start = PPU_BIT | 0x2000; descs[i].select = PPU_BIT; descs[i].disconnect = PPU_BIT; descs[i].len = 0x0800; descs[i].addrspace="NTARAM"; i++; descs[i].flags = 0; descs[i].ptr = PALRAM; descs[i].offset = 0; descs[i].start = PPU_BIT | 0x3000; descs[i].select = PPU_BIT; descs[i].disconnect = PPU_BIT; descs[i].len = 0x020; descs[i].addrspace="PALRAM"; i++; /* OAM doesn't really live anywhere in address space so I'll put it at 0x4000. */ descs[i].flags = 0; descs[i].ptr = SPRAM; descs[i].offset = 0; descs[i].start = PPU_BIT | 0x4000; descs[i].select = PPU_BIT; descs[i].disconnect = PPU_BIT; descs[i].len = 0x100; descs[i].addrspace="OAM"; i++; mmaps.descriptors = descs; mmaps.num_descriptors = i; environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps); return true; } bool retro_load_game_special( unsigned game_type, const struct retro_game_info *info, size_t num_info ) { return false; } void retro_unload_game(void) { FCEUI_CloseGame(); #if defined(_3DS) if (fceu_video_out) linearFree(fceu_video_out); #else if (fceu_video_out) free(fceu_video_out); fceu_video_out = NULL; #endif #if defined(RENDER_GSKIT_PS2) ps2 = NULL; #endif #ifdef HAVE_NTSC_FILTER NTSCFilter_Cleanup(); #endif } unsigned retro_get_region(void) { return FSettings.PAL ? RETRO_REGION_PAL : RETRO_REGION_NTSC; } void *retro_get_memory_data(unsigned type) { uint8_t* data; switch(type) { case RETRO_MEMORY_SAVE_RAM: if (iNESCart.battery && iNESCart.SaveGame[0] && iNESCart.SaveGameLen[0]) return iNESCart.SaveGame[0]; else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0]) return UNIFCart.SaveGame[0]; else if (GameInfo && GameInfo->type == GIT_FDS) return FDSROM_ptr(); else data = NULL; break; case RETRO_MEMORY_SYSTEM_RAM: data = RAM; break; default: data = NULL; break; } return data; } size_t retro_get_memory_size(unsigned type) { unsigned size; switch(type) { case RETRO_MEMORY_SAVE_RAM: if (iNESCart.battery && iNESCart.SaveGame[0] && iNESCart.SaveGameLen[0]) size = iNESCart.SaveGameLen[0]; else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0]) size = UNIFCart.SaveGameLen[0]; else if (GameInfo && GameInfo->type == GIT_FDS) size = FDSROM_size(); else size = 0; break; case RETRO_MEMORY_SYSTEM_RAM: size = 0x800; break; default: size = 0; break; } return size; }