mirror of
https://github.com/libretro/gambatte-libretro.git
synced 2024-11-27 01:40:23 +00:00
2772 lines
87 KiB
C++
2772 lines
87 KiB
C++
#include <stdlib.h>
|
|
|
|
#include <libretro.h>
|
|
#include <libretro_core_options.h>
|
|
#include "gambatte_log.h"
|
|
#include "blipper.h"
|
|
#include "cc_resampler.h"
|
|
#include "gambatte.h"
|
|
#include "gbcpalettes.h"
|
|
#include "bootloader.h"
|
|
#ifdef HAVE_NETWORK
|
|
#include "net_serial.h"
|
|
#endif
|
|
|
|
#if defined(__DJGPP__) && defined(__STRICT_ANSI__)
|
|
/* keep this above libretro-common includes */
|
|
#undef __STRICT_ANSI__
|
|
#endif
|
|
|
|
#ifndef PATH_MAX_LENGTH
|
|
#if defined(_XBOX1) || defined(_3DS) || defined(PSP) || defined(PS2) || defined(GEKKO)|| defined(WIIU) || defined(ORBIS) || defined(__PSL1GHT__) || defined(__PS3__)
|
|
#define PATH_MAX_LENGTH 512
|
|
#else
|
|
#define PATH_MAX_LENGTH 4096
|
|
#endif
|
|
#endif
|
|
|
|
#include <string/stdstring.h>
|
|
#include <file/file_path.h>
|
|
#include <streams/file_stream.h>
|
|
#include <array/rhmap.h>
|
|
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
#ifdef _3DS
|
|
extern "C" void* linearMemAlign(size_t size, size_t alignment);
|
|
extern "C" void linearFree(void* mem);
|
|
#endif
|
|
|
|
static retro_video_refresh_t video_cb;
|
|
static retro_input_poll_t input_poll_cb;
|
|
static retro_input_state_t input_state_cb;
|
|
static retro_audio_sample_batch_t audio_batch_cb;
|
|
static retro_environment_t environ_cb;
|
|
static gambatte::video_pixel_t* video_buf;
|
|
static gambatte::GB gb;
|
|
|
|
static bool libretro_supports_option_categories = false;
|
|
static bool libretro_supports_bitmasks = false;
|
|
static bool libretro_supports_set_variable = false;
|
|
static unsigned libretro_msg_interface_version = 0;
|
|
static bool libretro_supports_ff_override = false;
|
|
static bool libretro_ff_enabled = false;
|
|
static bool libretro_ff_enabled_prev = false;
|
|
|
|
static bool show_gb_link_settings = true;
|
|
|
|
/* Minimum (and default) turbo pulse train
|
|
* is 2 frames ON, 2 frames OFF */
|
|
#define TURBO_PERIOD_MIN 4
|
|
#define TURBO_PERIOD_MAX 120
|
|
#define TURBO_PULSE_WIDTH_MIN 2
|
|
#define TURBO_PULSE_WIDTH_MAX 15
|
|
|
|
static unsigned libretro_input_state = 0;
|
|
static bool up_down_allowed = false;
|
|
static unsigned turbo_period = TURBO_PERIOD_MIN;
|
|
static unsigned turbo_pulse_width = TURBO_PULSE_WIDTH_MIN;
|
|
static unsigned turbo_a_counter = 0;
|
|
static unsigned turbo_b_counter = 0;
|
|
|
|
static bool rom_loaded = false;
|
|
|
|
//Dual mode runs two GBCs side by side.
|
|
//Currently, they load the same ROM, take the same input, and only the left one supports SRAM, cheats, savestates, or sound.
|
|
//Can be made useful later, but for now, it's just a tech demo.
|
|
//#define DUAL_MODE
|
|
|
|
#ifdef DUAL_MODE
|
|
static gambatte::GB gb2;
|
|
#define NUM_GAMEBOYS 2
|
|
#else
|
|
#define NUM_GAMEBOYS 1
|
|
#endif
|
|
|
|
bool use_official_bootloader = false;
|
|
|
|
#define GB_SCREEN_WIDTH 160
|
|
#define VIDEO_WIDTH (GB_SCREEN_WIDTH * NUM_GAMEBOYS)
|
|
#define VIDEO_HEIGHT 144
|
|
/* Video buffer 'width' is 256, not 160 -> assume
|
|
* there is a benefit to making this a power of 2 */
|
|
#define VIDEO_BUFF_SIZE (256 * NUM_GAMEBOYS * VIDEO_HEIGHT * sizeof(gambatte::video_pixel_t))
|
|
#define VIDEO_PITCH (256 * NUM_GAMEBOYS)
|
|
#define VIDEO_REFRESH_RATE (4194304.0 / 70224.0)
|
|
|
|
/*************************/
|
|
/* Audio Resampler START */
|
|
/*************************/
|
|
|
|
/* There are 35112 stereo sound samples in a video frame */
|
|
#define SOUND_SAMPLES_PER_FRAME 35112
|
|
/* We request 2064 samples from each call of GB::runFor() */
|
|
#define SOUND_SAMPLES_PER_RUN 2064
|
|
/* Native GB/GBC hardware audio sample rate (~2 MHz) */
|
|
#define SOUND_SAMPLE_RATE_NATIVE (VIDEO_REFRESH_RATE * (double)SOUND_SAMPLES_PER_FRAME)
|
|
|
|
#define SOUND_SAMPLE_RATE_CC (SOUND_SAMPLE_RATE_NATIVE / CC_DECIMATION_RATE) /* ~64k */
|
|
#define SOUND_SAMPLE_RATE_BLIPPER (SOUND_SAMPLE_RATE_NATIVE / 64) /* ~32k */
|
|
|
|
/* GB::runFor() nominally generates up to
|
|
* (SOUND_SAMPLES_PER_RUN + 2064) samples, which
|
|
* defines our sound buffer size
|
|
* NOTE: This is in fact a lie - in the upstream code,
|
|
* GB::runFor() can generate more than
|
|
* (SOUND_SAMPLES_PER_RUN + 2064) samples, causing a
|
|
* buffer overflow. It has been necessary to add an
|
|
* internal hard cap/bail out in the event that
|
|
* excess samples are detected... */
|
|
#define SOUND_BUFF_SIZE (SOUND_SAMPLES_PER_RUN + 2064)
|
|
|
|
/* Blipper produces between 548 and 549 output samples
|
|
* per frame. For safety, we want to keep the blip
|
|
* buffer no more than ~50% full. (2 * 549) = 1098,
|
|
* so add some padding and round up to (1024 + 512) */
|
|
#define BLIP_BUFFER_SIZE (1024 + 512)
|
|
|
|
static blipper_t *resampler_l = NULL;
|
|
static blipper_t *resampler_r = NULL;
|
|
|
|
static bool use_cc_resampler = false;
|
|
|
|
static int16_t *audio_out_buffer = NULL;
|
|
static size_t audio_out_buffer_size = 0;
|
|
static size_t audio_out_buffer_pos = 0;
|
|
static size_t audio_batch_frames_max = (1 << 16);
|
|
|
|
static void audio_out_buffer_init(void)
|
|
{
|
|
float sample_rate = use_cc_resampler ?
|
|
SOUND_SAMPLE_RATE_CC : SOUND_SAMPLE_RATE_BLIPPER;
|
|
float samples_per_frame = sample_rate / VIDEO_REFRESH_RATE;
|
|
size_t buffer_size = ((size_t)samples_per_frame + 1) << 1;
|
|
|
|
/* Create a buffer that is double the required size
|
|
* to minimise the likelihood of resize operations
|
|
* (core tends to produce very brief 'bursts' of high
|
|
* sample counts depending upon the emulated content...) */
|
|
buffer_size = (buffer_size << 1);
|
|
|
|
audio_out_buffer = (int16_t *)malloc(buffer_size * sizeof(int16_t));
|
|
audio_out_buffer_size = buffer_size;
|
|
audio_out_buffer_pos = 0;
|
|
audio_batch_frames_max = (1 << 16);
|
|
}
|
|
|
|
static void audio_out_buffer_deinit(void)
|
|
{
|
|
if (audio_out_buffer)
|
|
free(audio_out_buffer);
|
|
|
|
audio_out_buffer = NULL;
|
|
audio_out_buffer_size = 0;
|
|
audio_out_buffer_pos = 0;
|
|
audio_batch_frames_max = (1 << 16);
|
|
}
|
|
|
|
static INLINE void audio_out_buffer_resize(size_t num_samples)
|
|
{
|
|
size_t buffer_capacity = (audio_out_buffer_size -
|
|
audio_out_buffer_pos) >> 1;
|
|
|
|
if (buffer_capacity < num_samples)
|
|
{
|
|
int16_t *tmp_buffer = NULL;
|
|
size_t tmp_buffer_size;
|
|
|
|
tmp_buffer_size = audio_out_buffer_size +
|
|
((num_samples - buffer_capacity) << 1);
|
|
tmp_buffer_size = (tmp_buffer_size << 1) - (tmp_buffer_size >> 1);
|
|
tmp_buffer = (int16_t *)malloc(tmp_buffer_size * sizeof(int16_t));
|
|
|
|
memcpy(tmp_buffer, audio_out_buffer,
|
|
audio_out_buffer_pos * sizeof(int16_t));
|
|
|
|
free(audio_out_buffer);
|
|
audio_out_buffer = tmp_buffer;
|
|
audio_out_buffer_size = tmp_buffer_size;
|
|
}
|
|
}
|
|
|
|
void audio_out_buffer_write(int16_t *samples, size_t num_samples)
|
|
{
|
|
audio_out_buffer_resize(num_samples);
|
|
|
|
memcpy(audio_out_buffer + audio_out_buffer_pos,
|
|
samples, (num_samples << 1) * sizeof(int16_t));
|
|
|
|
audio_out_buffer_pos += num_samples << 1;
|
|
}
|
|
|
|
static void audio_out_buffer_read_blipper(size_t num_samples)
|
|
{
|
|
int16_t *audio_out_buffer_ptr = NULL;
|
|
|
|
audio_out_buffer_resize(num_samples);
|
|
audio_out_buffer_ptr = audio_out_buffer + audio_out_buffer_pos;
|
|
|
|
blipper_read(resampler_l, audio_out_buffer_ptr , num_samples, 2);
|
|
blipper_read(resampler_r, audio_out_buffer_ptr + 1, num_samples, 2);
|
|
|
|
audio_out_buffer_pos += num_samples << 1;
|
|
}
|
|
|
|
static void audio_upload_samples(void)
|
|
{
|
|
int16_t *audio_out_buffer_ptr = audio_out_buffer;
|
|
size_t num_samples = audio_out_buffer_pos >> 1;
|
|
|
|
while (num_samples > 0)
|
|
{
|
|
size_t samples_to_write = (num_samples >
|
|
audio_batch_frames_max) ?
|
|
audio_batch_frames_max :
|
|
num_samples;
|
|
size_t samples_written = audio_batch_cb(
|
|
audio_out_buffer_ptr, samples_to_write);
|
|
|
|
if ((samples_written < samples_to_write) &&
|
|
(samples_written > 0))
|
|
audio_batch_frames_max = samples_written;
|
|
|
|
num_samples -= samples_to_write;
|
|
audio_out_buffer_ptr += samples_to_write << 1;
|
|
}
|
|
|
|
audio_out_buffer_pos = 0;
|
|
}
|
|
|
|
static void blipper_renderaudio(const int16_t *samples, unsigned frames)
|
|
{
|
|
if (!frames)
|
|
return;
|
|
|
|
blipper_push_samples(resampler_l, samples + 0, frames, 2);
|
|
blipper_push_samples(resampler_r, samples + 1, frames, 2);
|
|
}
|
|
|
|
static void audio_resampler_deinit(void)
|
|
{
|
|
if (resampler_l)
|
|
blipper_free(resampler_l);
|
|
|
|
if (resampler_r)
|
|
blipper_free(resampler_r);
|
|
|
|
resampler_l = NULL;
|
|
resampler_r = NULL;
|
|
|
|
audio_out_buffer_deinit();
|
|
}
|
|
|
|
static void audio_resampler_init(bool startup)
|
|
{
|
|
if (use_cc_resampler)
|
|
CC_init();
|
|
else
|
|
{
|
|
resampler_l = blipper_new(32, 0.85, 6.5, 64, BLIP_BUFFER_SIZE, NULL);
|
|
resampler_r = blipper_new(32, 0.85, 6.5, 64, BLIP_BUFFER_SIZE, NULL);
|
|
|
|
/* It is possible for blipper_new() to fail,
|
|
* must handle errors */
|
|
if (!resampler_l || !resampler_r)
|
|
{
|
|
/* Display warning message */
|
|
if (libretro_msg_interface_version >= 1)
|
|
{
|
|
struct retro_message_ext msg = {
|
|
"Sinc resampler unsupported on this platform - using Cosine",
|
|
2000,
|
|
1,
|
|
RETRO_LOG_WARN,
|
|
RETRO_MESSAGE_TARGET_OSD,
|
|
RETRO_MESSAGE_TYPE_NOTIFICATION,
|
|
-1
|
|
};
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg);
|
|
}
|
|
else
|
|
{
|
|
struct retro_message msg = {
|
|
"Sinc resampler unsupported on this platform - using Cosine",
|
|
120
|
|
};
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg);
|
|
}
|
|
|
|
/* Force CC resampler */
|
|
audio_resampler_deinit();
|
|
use_cc_resampler = true;
|
|
CC_init();
|
|
|
|
/* Notify frontend of option value change */
|
|
if (libretro_supports_set_variable)
|
|
{
|
|
struct retro_variable var = {0};
|
|
var.key = "gambatte_audio_resampler";
|
|
var.value = "cc";
|
|
environ_cb(RETRO_ENVIRONMENT_SET_VARIABLE, &var);
|
|
}
|
|
|
|
/* Notify frontend of sample rate change */
|
|
if (!startup)
|
|
{
|
|
struct retro_system_av_info av_info;
|
|
retro_get_system_av_info(&av_info);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &av_info);
|
|
}
|
|
}
|
|
}
|
|
|
|
audio_out_buffer_init();
|
|
}
|
|
|
|
/***********************/
|
|
/* Audio Resampler END */
|
|
/***********************/
|
|
|
|
/***************************/
|
|
/* Palette Switching START */
|
|
/***************************/
|
|
|
|
static bool internal_palette_active = false;
|
|
static size_t internal_palette_index = 0;
|
|
static unsigned palette_switch_counter = 0;
|
|
|
|
/* Period in frames between palette switches
|
|
* when holding RetroPad L/R */
|
|
#define PALETTE_SWITCH_PERIOD 30
|
|
|
|
/* Note: These must be updated if the internal
|
|
* palette options in libretro_core_options.h
|
|
* are changed
|
|
* > We could count the palettes at runtime,
|
|
* but this adds unnecessary performance
|
|
* overheads and seems futile given that
|
|
* a number of other parameters must be
|
|
* hardcoded anyway... */
|
|
#define NUM_PALETTES_DEFAULT 51
|
|
#define NUM_PALETTES_TWB64_1 100
|
|
#define NUM_PALETTES_TWB64_2 100
|
|
#define NUM_PALETTES_TWB64_3 100
|
|
#define NUM_PALETTES_PIXELSHIFT_1 45
|
|
#define NUM_PALETTES_TOTAL (NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1 + NUM_PALETTES_TWB64_2 + \
|
|
NUM_PALETTES_TWB64_3 + NUM_PALETTES_PIXELSHIFT_1)
|
|
|
|
struct retro_core_option_value *palettes_default_opt_values = NULL;
|
|
struct retro_core_option_value *palettes_twb64_1_opt_values = NULL;
|
|
struct retro_core_option_value *palettes_twb64_2_opt_values = NULL;
|
|
struct retro_core_option_value *palettes_twb64_3_opt_values = NULL;
|
|
struct retro_core_option_value *palettes_pixelshift_1_opt_values = NULL;
|
|
|
|
static const char *internal_palette_labels[NUM_PALETTES_TOTAL] = {0};
|
|
|
|
static size_t *palettes_default_index_map = NULL;
|
|
static size_t *palettes_twb64_1_index_map = NULL;
|
|
static size_t *palettes_twb64_2_index_map = NULL;
|
|
static size_t *palettes_twb64_3_index_map = NULL;
|
|
static size_t *palettes_pixelshift_1_index_map = NULL;
|
|
|
|
static void parse_internal_palette_values(const char *key,
|
|
struct retro_core_option_v2_definition *opt_defs_intl,
|
|
size_t num_palettes, size_t palette_offset,
|
|
struct retro_core_option_value **opt_values,
|
|
size_t **index_map)
|
|
{
|
|
size_t i;
|
|
struct retro_core_option_v2_definition *opt_defs = option_defs_us;
|
|
struct retro_core_option_v2_definition *opt_def = NULL;
|
|
size_t label_index = 0;
|
|
#ifndef HAVE_NO_LANGEXTRA
|
|
struct retro_core_option_v2_definition *opt_def_intl = NULL;
|
|
#endif
|
|
/* Find option corresponding to key */
|
|
for (opt_def = opt_defs; !string_is_empty(opt_def->key); opt_def++)
|
|
if (string_is_equal(opt_def->key, key))
|
|
break;
|
|
|
|
/* Cache option values array for fast access
|
|
* when setting palette index */
|
|
*opt_values = opt_def->values;
|
|
|
|
/* Loop over all palette values for specified
|
|
* option and:
|
|
* - Generate palette index maps
|
|
* - Fetch palette labels for notification
|
|
* purposes
|
|
* Note: We perform no error checking here,
|
|
* since we are operating on hardcoded structs
|
|
* over which the core has full control */
|
|
for (i = 0; i < num_palettes; i++)
|
|
{
|
|
const char *value = opt_def->values[i].value;
|
|
const char *value_label = NULL;
|
|
|
|
/* Add entry to hash map
|
|
* > Note that we have to set index+1, since
|
|
* a return value of 0 from RHMAP_GET_STR()
|
|
* indicates that the key string was not found */
|
|
RHMAP_SET_STR((*index_map), value, i + 1);
|
|
|
|
/* 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;
|
|
!string_is_empty(opt_def_intl->key);
|
|
opt_def_intl++)
|
|
{
|
|
if (string_is_equal(opt_def_intl->key, key))
|
|
{
|
|
size_t j = 0;
|
|
|
|
/* Search for current option value */
|
|
for (;;)
|
|
{
|
|
const char *value_intl = opt_def_intl->values[j].value;
|
|
|
|
if (string_is_empty(value_intl))
|
|
break;
|
|
|
|
if (string_is_equal(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 value itself from option_defs_us */
|
|
if (!value_label)
|
|
value_label = value;
|
|
|
|
/* Cache label for 'consolidated' palette index */
|
|
internal_palette_labels[palette_offset + label_index++] = value_label;
|
|
}
|
|
}
|
|
|
|
static void init_palette_switch(void)
|
|
{
|
|
struct retro_core_option_v2_definition *opt_defs_intl = NULL;
|
|
#ifndef HAVE_NO_LANGEXTRA
|
|
unsigned language = 0;
|
|
#endif
|
|
|
|
libretro_supports_set_variable = false;
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_VARIABLE, NULL))
|
|
libretro_supports_set_variable = true;
|
|
|
|
libretro_msg_interface_version = 0;
|
|
environ_cb(RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION,
|
|
&libretro_msg_interface_version);
|
|
|
|
internal_palette_active = false;
|
|
internal_palette_index = 0;
|
|
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
|
|
|
|
/* Parse palette values for each palette group
|
|
* > Default palettes */
|
|
parse_internal_palette_values("gambatte_gb_internal_palette",
|
|
opt_defs_intl, NUM_PALETTES_DEFAULT,
|
|
0,
|
|
&palettes_default_opt_values,
|
|
&palettes_default_index_map);
|
|
/* > TWB64 Pack 1 palettes */
|
|
parse_internal_palette_values("gambatte_gb_palette_twb64_1",
|
|
opt_defs_intl, NUM_PALETTES_TWB64_1,
|
|
NUM_PALETTES_DEFAULT,
|
|
&palettes_twb64_1_opt_values,
|
|
&palettes_twb64_1_index_map);
|
|
/* > TWB64 Pack 2 palettes */
|
|
parse_internal_palette_values("gambatte_gb_palette_twb64_2",
|
|
opt_defs_intl, NUM_PALETTES_TWB64_2,
|
|
NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1,
|
|
&palettes_twb64_2_opt_values,
|
|
&palettes_twb64_2_index_map);
|
|
/* > TWB64 Pack 3 palettes */
|
|
parse_internal_palette_values("gambatte_gb_palette_twb64_3",
|
|
opt_defs_intl, NUM_PALETTES_TWB64_3,
|
|
NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1 + NUM_PALETTES_TWB64_2,
|
|
&palettes_twb64_3_opt_values,
|
|
&palettes_twb64_3_index_map);
|
|
/* > PixelShift - Pack 1 palettes */
|
|
parse_internal_palette_values("gambatte_gb_palette_pixelshift_1",
|
|
opt_defs_intl, NUM_PALETTES_PIXELSHIFT_1,
|
|
NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1 + NUM_PALETTES_TWB64_2 +
|
|
NUM_PALETTES_TWB64_3,
|
|
&palettes_pixelshift_1_opt_values,
|
|
&palettes_pixelshift_1_index_map);
|
|
}
|
|
|
|
static void deinit_palette_switch(void)
|
|
{
|
|
libretro_supports_set_variable = false;
|
|
libretro_msg_interface_version = 0;
|
|
internal_palette_active = false;
|
|
internal_palette_index = 0;
|
|
palette_switch_counter = 0;
|
|
palettes_default_opt_values = NULL;
|
|
palettes_twb64_1_opt_values = NULL;
|
|
palettes_twb64_2_opt_values = NULL;
|
|
palettes_twb64_3_opt_values = NULL;
|
|
palettes_pixelshift_1_opt_values = NULL;
|
|
|
|
RHMAP_FREE(palettes_default_index_map);
|
|
RHMAP_FREE(palettes_twb64_1_index_map);
|
|
RHMAP_FREE(palettes_twb64_2_index_map);
|
|
RHMAP_FREE(palettes_twb64_3_index_map);
|
|
RHMAP_FREE(palettes_pixelshift_1_index_map);
|
|
}
|
|
|
|
static void palette_switch_set_index(size_t palette_index)
|
|
{
|
|
const char *palettes_default_value = NULL;
|
|
const char *palettes_ext_key = NULL;
|
|
const char *palettes_ext_value = NULL;
|
|
size_t opt_index = 0;
|
|
struct retro_variable var = {0};
|
|
|
|
if (palette_index >= NUM_PALETTES_TOTAL)
|
|
palette_index = NUM_PALETTES_TOTAL - 1;
|
|
|
|
/* Check which palette group the specified
|
|
* index corresponds to */
|
|
if (palette_index < NUM_PALETTES_DEFAULT)
|
|
{
|
|
/* This is a palette from the default group */
|
|
opt_index = palette_index;
|
|
palettes_default_value = palettes_default_opt_values[opt_index].value;
|
|
}
|
|
else if (palette_index <
|
|
NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1)
|
|
{
|
|
/* This is a palette from the TWB64 Pack 1 group */
|
|
palettes_default_value = "TWB64 - Pack 1";
|
|
|
|
opt_index = palette_index - NUM_PALETTES_DEFAULT;
|
|
palettes_ext_key = "gambatte_gb_palette_twb64_1";
|
|
palettes_ext_value = palettes_twb64_1_opt_values[opt_index].value;
|
|
}
|
|
else if (palette_index <
|
|
NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1 + NUM_PALETTES_TWB64_2)
|
|
{
|
|
/* This is a palette from the TWB64 Pack 2 group */
|
|
palettes_default_value = "TWB64 - Pack 2";
|
|
|
|
opt_index = palette_index -
|
|
(NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1);
|
|
palettes_ext_key = "gambatte_gb_palette_twb64_2";
|
|
palettes_ext_value = palettes_twb64_2_opt_values[opt_index].value;
|
|
}
|
|
else if (palette_index <
|
|
NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1 + NUM_PALETTES_TWB64_2 +
|
|
NUM_PALETTES_TWB64_3)
|
|
{
|
|
/* This is a palette from the TWB64 Pack 3 group */
|
|
palettes_default_value = "TWB64 - Pack 3";
|
|
|
|
opt_index = palette_index -
|
|
(NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1 + NUM_PALETTES_TWB64_2);
|
|
palettes_ext_key = "gambatte_gb_palette_twb64_3";
|
|
palettes_ext_value = palettes_twb64_3_opt_values[opt_index].value;
|
|
}
|
|
else
|
|
{
|
|
/* This is a palette from the PixelShift Pack 1 group */
|
|
palettes_default_value = "PixelShift - Pack 1";
|
|
|
|
opt_index = palette_index -
|
|
(NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1 + NUM_PALETTES_TWB64_2 +
|
|
NUM_PALETTES_TWB64_3);
|
|
palettes_ext_key = "gambatte_gb_palette_pixelshift_1";
|
|
palettes_ext_value = palettes_pixelshift_1_opt_values[opt_index].value;
|
|
}
|
|
|
|
/* Notify frontend of option value changes */
|
|
var.key = "gambatte_gb_internal_palette";
|
|
var.value = palettes_default_value;
|
|
environ_cb(RETRO_ENVIRONMENT_SET_VARIABLE, &var);
|
|
|
|
if (palettes_ext_key)
|
|
{
|
|
var.key = palettes_ext_key;
|
|
var.value = palettes_ext_value;
|
|
environ_cb(RETRO_ENVIRONMENT_SET_VARIABLE, &var);
|
|
}
|
|
|
|
/* Display notification message */
|
|
if (libretro_msg_interface_version >= 1)
|
|
{
|
|
struct retro_message_ext msg = {
|
|
internal_palette_labels[palette_index],
|
|
2000,
|
|
1,
|
|
RETRO_LOG_INFO,
|
|
RETRO_MESSAGE_TARGET_OSD,
|
|
RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
-1
|
|
};
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg);
|
|
}
|
|
else
|
|
{
|
|
struct retro_message msg = {
|
|
internal_palette_labels[palette_index],
|
|
120
|
|
};
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg);
|
|
}
|
|
}
|
|
|
|
/*************************/
|
|
/* Palette Switching END */
|
|
/*************************/
|
|
|
|
/*****************************/
|
|
/* Interframe blending START */
|
|
/*****************************/
|
|
|
|
#define LCD_RESPONSE_TIME 0.333f
|
|
/* > 'LCD Ghosting (Fast)' method does not
|
|
* correctly interpret the set response time,
|
|
* leading to an artificially subdued blur effect.
|
|
* We have to compensate for this by increasing
|
|
* the response time, hence this 'fake' value */
|
|
#define LCD_RESPONSE_TIME_FAKE 0.5f
|
|
|
|
enum frame_blend_method
|
|
{
|
|
FRAME_BLEND_NONE = 0,
|
|
FRAME_BLEND_MIX,
|
|
FRAME_BLEND_LCD_GHOSTING,
|
|
FRAME_BLEND_LCD_GHOSTING_FAST
|
|
};
|
|
|
|
static enum frame_blend_method frame_blend_type = FRAME_BLEND_NONE;
|
|
static gambatte::video_pixel_t* video_buf_prev_1 = NULL;
|
|
static gambatte::video_pixel_t* video_buf_prev_2 = NULL;
|
|
static gambatte::video_pixel_t* video_buf_prev_3 = NULL;
|
|
static gambatte::video_pixel_t* video_buf_prev_4 = NULL;
|
|
static float* video_buf_acc_r = NULL;
|
|
static float* video_buf_acc_g = NULL;
|
|
static float* video_buf_acc_b = NULL;
|
|
static float frame_blend_response[4] = {0.0f};
|
|
static bool frame_blend_response_set = false;
|
|
static void (*blend_frames)(void) = NULL;
|
|
|
|
/* > Note: The individual frame blending functions
|
|
* are somewhat WET (Write Everything Twice), in that
|
|
* we duplicate the entire nested for loop.
|
|
* This code is performance-critical, so we want to
|
|
* minimise logic in the inner loops where possible */
|
|
static void blend_frames_mix(void)
|
|
{
|
|
gambatte::video_pixel_t *curr = video_buf;
|
|
gambatte::video_pixel_t *prev = video_buf_prev_1;
|
|
size_t x, y;
|
|
|
|
for (y = 0; y < VIDEO_HEIGHT; y++)
|
|
{
|
|
for (x = 0; x < VIDEO_WIDTH; x++)
|
|
{
|
|
/* Get colours from current + previous frames */
|
|
gambatte::video_pixel_t rgb_curr = *(curr + x);
|
|
gambatte::video_pixel_t rgb_prev = *(prev + x);
|
|
|
|
/* Store colours for next frame */
|
|
*(prev + x) = rgb_curr;
|
|
|
|
/* Mix colours
|
|
* > "Mixing Packed RGB Pixels Efficiently"
|
|
* http://blargg.8bitalley.com/info/rgb_mixing.html */
|
|
#ifdef VIDEO_RGB565
|
|
*(curr + x) = (rgb_curr + rgb_prev + ((rgb_curr ^ rgb_prev) & 0x821)) >> 1;
|
|
#elif defined(VIDEO_ABGR1555)
|
|
*(curr + x) = (rgb_curr + rgb_prev + ((rgb_curr ^ rgb_prev) & 0x521)) >> 1;
|
|
#else
|
|
*(curr + x) = (rgb_curr + rgb_prev + ((rgb_curr ^ rgb_prev) & 0x10101)) >> 1;
|
|
#endif
|
|
}
|
|
|
|
curr += VIDEO_PITCH;
|
|
prev += VIDEO_PITCH;
|
|
}
|
|
}
|
|
|
|
static void blend_frames_lcd_ghost(void)
|
|
{
|
|
gambatte::video_pixel_t *curr = video_buf;
|
|
gambatte::video_pixel_t *prev_1 = video_buf_prev_1;
|
|
gambatte::video_pixel_t *prev_2 = video_buf_prev_2;
|
|
gambatte::video_pixel_t *prev_3 = video_buf_prev_3;
|
|
gambatte::video_pixel_t *prev_4 = video_buf_prev_4;
|
|
float *response = frame_blend_response;
|
|
size_t x, y;
|
|
|
|
for (y = 0; y < VIDEO_HEIGHT; y++)
|
|
{
|
|
for (x = 0; x < VIDEO_WIDTH; x++)
|
|
{
|
|
/* Get colours from current + previous frames */
|
|
gambatte::video_pixel_t rgb_curr = *(curr + x);
|
|
gambatte::video_pixel_t rgb_prev_1 = *(prev_1 + x);
|
|
gambatte::video_pixel_t rgb_prev_2 = *(prev_2 + x);
|
|
gambatte::video_pixel_t rgb_prev_3 = *(prev_3 + x);
|
|
gambatte::video_pixel_t rgb_prev_4 = *(prev_4 + x);
|
|
|
|
/* Store colours for next frame */
|
|
*(prev_1 + x) = rgb_curr;
|
|
*(prev_2 + x) = rgb_prev_1;
|
|
*(prev_3 + x) = rgb_prev_2;
|
|
*(prev_4 + x) = rgb_prev_3;
|
|
|
|
/* Unpack colours and convert to float */
|
|
#ifdef VIDEO_RGB565
|
|
float r_curr = static_cast<float>(rgb_curr >> 11 & 0x1F);
|
|
float g_curr = static_cast<float>(rgb_curr >> 6 & 0x1F);
|
|
float b_curr = static_cast<float>(rgb_curr & 0x1F);
|
|
|
|
float r_prev_1 = static_cast<float>(rgb_prev_1 >> 11 & 0x1F);
|
|
float g_prev_1 = static_cast<float>(rgb_prev_1 >> 6 & 0x1F);
|
|
float b_prev_1 = static_cast<float>(rgb_prev_1 & 0x1F);
|
|
|
|
float r_prev_2 = static_cast<float>(rgb_prev_2 >> 11 & 0x1F);
|
|
float g_prev_2 = static_cast<float>(rgb_prev_2 >> 6 & 0x1F);
|
|
float b_prev_2 = static_cast<float>(rgb_prev_2 & 0x1F);
|
|
|
|
float r_prev_3 = static_cast<float>(rgb_prev_3 >> 11 & 0x1F);
|
|
float g_prev_3 = static_cast<float>(rgb_prev_3 >> 6 & 0x1F);
|
|
float b_prev_3 = static_cast<float>(rgb_prev_3 & 0x1F);
|
|
|
|
float r_prev_4 = static_cast<float>(rgb_prev_4 >> 11 & 0x1F);
|
|
float g_prev_4 = static_cast<float>(rgb_prev_4 >> 6 & 0x1F);
|
|
float b_prev_4 = static_cast<float>(rgb_prev_4 & 0x1F);
|
|
#elif defined(VIDEO_ABGR1555)
|
|
float r_curr = static_cast<float>(rgb_curr & 0x1F);
|
|
float g_curr = static_cast<float>(rgb_curr >> 5 & 0x1F);
|
|
float b_curr = static_cast<float>(rgb_curr >> 10 & 0x1F);
|
|
|
|
float r_prev_1 = static_cast<float>(rgb_prev_1 & 0x1F);
|
|
float g_prev_1 = static_cast<float>(rgb_prev_1 >> 5 & 0x1F);
|
|
float b_prev_1 = static_cast<float>(rgb_prev_1 >> 10 & 0x1F);
|
|
|
|
float r_prev_2 = static_cast<float>(rgb_prev_2 & 0x1F);
|
|
float g_prev_2 = static_cast<float>(rgb_prev_2 >> 5 & 0x1F);
|
|
float b_prev_2 = static_cast<float>(rgb_prev_2 >> 10 & 0x1F);
|
|
|
|
float r_prev_3 = static_cast<float>(rgb_prev_3 & 0x1F);
|
|
float g_prev_3 = static_cast<float>(rgb_prev_3 >> 5 & 0x1F);
|
|
float b_prev_3 = static_cast<float>(rgb_prev_3 >> 10 & 0x1F);
|
|
|
|
float r_prev_4 = static_cast<float>(rgb_prev_4 & 0x1F);
|
|
float g_prev_4 = static_cast<float>(rgb_prev_4 >> 5 & 0x1F);
|
|
float b_prev_4 = static_cast<float>(rgb_prev_4 >> 10 & 0x1F);
|
|
#else
|
|
float r_curr = static_cast<float>(rgb_curr >> 16 & 0x1F);
|
|
float g_curr = static_cast<float>(rgb_curr >> 8 & 0x1F);
|
|
float b_curr = static_cast<float>(rgb_curr & 0x1F);
|
|
|
|
float r_prev_1 = static_cast<float>(rgb_prev_1 >> 16 & 0x1F);
|
|
float g_prev_1 = static_cast<float>(rgb_prev_1 >> 8 & 0x1F);
|
|
float b_prev_1 = static_cast<float>(rgb_prev_1 & 0x1F);
|
|
|
|
float r_prev_2 = static_cast<float>(rgb_prev_2 >> 16 & 0x1F);
|
|
float g_prev_2 = static_cast<float>(rgb_prev_2 >> 8 & 0x1F);
|
|
float b_prev_2 = static_cast<float>(rgb_prev_2 & 0x1F);
|
|
|
|
float r_prev_3 = static_cast<float>(rgb_prev_3 >> 16 & 0x1F);
|
|
float g_prev_3 = static_cast<float>(rgb_prev_3 >> 8 & 0x1F);
|
|
float b_prev_3 = static_cast<float>(rgb_prev_3 & 0x1F);
|
|
|
|
float r_prev_4 = static_cast<float>(rgb_prev_4 >> 16 & 0x1F);
|
|
float g_prev_4 = static_cast<float>(rgb_prev_4 >> 8 & 0x1F);
|
|
float b_prev_4 = static_cast<float>(rgb_prev_4 & 0x1F);
|
|
#endif
|
|
/* Mix colours for current frame and convert back to video_pixel_t
|
|
* > Response time effect implemented via an exponential
|
|
* drop-off algorithm, taken from the 'Gameboy Classic Shader'
|
|
* by Harlequin:
|
|
* https://github.com/libretro/glsl-shaders/blob/master/handheld/shaders/gameboy/shader-files/gb-pass0.glsl */
|
|
r_curr += (r_prev_1 - r_curr) * *response;
|
|
r_curr += (r_prev_2 - r_curr) * *(response + 1);
|
|
r_curr += (r_prev_3 - r_curr) * *(response + 2);
|
|
r_curr += (r_prev_4 - r_curr) * *(response + 3);
|
|
gambatte::video_pixel_t r_mix = static_cast<gambatte::video_pixel_t>(r_curr + 0.5f) & 0x1F;
|
|
|
|
g_curr += (g_prev_1 - g_curr) * *response;
|
|
g_curr += (g_prev_2 - g_curr) * *(response + 1);
|
|
g_curr += (g_prev_3 - g_curr) * *(response + 2);
|
|
g_curr += (g_prev_4 - g_curr) * *(response + 3);
|
|
gambatte::video_pixel_t g_mix = static_cast<gambatte::video_pixel_t>(g_curr + 0.5f) & 0x1F;
|
|
|
|
b_curr += (b_prev_1 - b_curr) * *response;
|
|
b_curr += (b_prev_2 - b_curr) * *(response + 1);
|
|
b_curr += (b_prev_3 - b_curr) * *(response + 2);
|
|
b_curr += (b_prev_4 - b_curr) * *(response + 3);
|
|
gambatte::video_pixel_t b_mix = static_cast<gambatte::video_pixel_t>(b_curr + 0.5f) & 0x1F;
|
|
|
|
/* Repack colours for current frame */
|
|
#ifdef VIDEO_RGB565
|
|
*(curr + x) = r_mix << 11 | g_mix << 6 | b_mix;
|
|
#elif defined(VIDEO_ABGR1555)
|
|
*(curr + x) = b_mix << 10 | g_mix << 5 | b_mix;
|
|
#else
|
|
*(curr + x) = r_mix << 16 | g_mix << 8 | b_mix;
|
|
#endif
|
|
}
|
|
|
|
curr += VIDEO_PITCH;
|
|
prev_1 += VIDEO_PITCH;
|
|
prev_2 += VIDEO_PITCH;
|
|
prev_3 += VIDEO_PITCH;
|
|
prev_4 += VIDEO_PITCH;
|
|
}
|
|
}
|
|
|
|
static void blend_frames_lcd_ghost_fast(void)
|
|
{
|
|
gambatte::video_pixel_t *curr = video_buf;
|
|
float *prev_r = video_buf_acc_r;
|
|
float *prev_g = video_buf_acc_g;
|
|
float *prev_b = video_buf_acc_b;
|
|
size_t x, y;
|
|
|
|
for (y = 0; y < VIDEO_HEIGHT; y++)
|
|
{
|
|
for (x = 0; x < VIDEO_WIDTH; x++)
|
|
{
|
|
/* Get colours from current + previous frames */
|
|
gambatte::video_pixel_t rgb_curr = *(curr + x);
|
|
float r_prev = *(prev_r + x);
|
|
float g_prev = *(prev_g + x);
|
|
float b_prev = *(prev_b + x);
|
|
|
|
/* Unpack current colours and convert to float */
|
|
#ifdef VIDEO_RGB565
|
|
float r_curr = static_cast<float>(rgb_curr >> 11 & 0x1F);
|
|
float g_curr = static_cast<float>(rgb_curr >> 6 & 0x1F);
|
|
float b_curr = static_cast<float>(rgb_curr & 0x1F);
|
|
#elif defined(VIDEO_ABGR1555)
|
|
float r_curr = static_cast<float>(rgb_curr & 0x1F);
|
|
float g_curr = static_cast<float>(rgb_curr >> 5 & 0x1F);
|
|
float b_curr = static_cast<float>(rgb_curr >> 10 & 0x1F);
|
|
#else
|
|
float r_curr = static_cast<float>(rgb_curr >> 16 & 0x1F);
|
|
float g_curr = static_cast<float>(rgb_curr >> 8 & 0x1F);
|
|
float b_curr = static_cast<float>(rgb_curr & 0x1F);
|
|
#endif
|
|
/* Mix colours for current frame */
|
|
float r_mix = (r_curr * (1.0f - LCD_RESPONSE_TIME_FAKE)) + (LCD_RESPONSE_TIME_FAKE * r_prev);
|
|
float g_mix = (g_curr * (1.0f - LCD_RESPONSE_TIME_FAKE)) + (LCD_RESPONSE_TIME_FAKE * g_prev);
|
|
float b_mix = (b_curr * (1.0f - LCD_RESPONSE_TIME_FAKE)) + (LCD_RESPONSE_TIME_FAKE * b_prev);
|
|
|
|
/* Store colours for next frame */
|
|
*(prev_r + x) = r_mix;
|
|
*(prev_g + x) = g_mix;
|
|
*(prev_b + x) = b_mix;
|
|
|
|
/* Convert, repack and assign colours for current frame */
|
|
#ifdef VIDEO_RGB565
|
|
*(curr + x) = (static_cast<gambatte::video_pixel_t>(r_mix + 0.5f) & 0x1F) << 11
|
|
| (static_cast<gambatte::video_pixel_t>(g_mix + 0.5f) & 0x1F) << 6
|
|
| (static_cast<gambatte::video_pixel_t>(b_mix + 0.5f) & 0x1F);
|
|
#elif defined(ABGR1555)
|
|
*(curr + x) = (static_cast<gambatte::video_pixel_t>(r_mix + 0.5f) & 0x1F)
|
|
| (static_cast<gambatte::video_pixel_t>(g_mix + 0.5f) & 0x1F) << 5
|
|
| (static_cast<gambatte::video_pixel_t>(b_mix + 0.5f) & 0x1F) << 10;
|
|
#else
|
|
*(curr + x) = (static_cast<gambatte::video_pixel_t>(r_mix + 0.5f) & 0x1F) << 16
|
|
| (static_cast<gambatte::video_pixel_t>(g_mix + 0.5f) & 0x1F) << 8
|
|
| (static_cast<gambatte::video_pixel_t>(b_mix + 0.5f) & 0x1F);
|
|
#endif
|
|
}
|
|
|
|
curr += VIDEO_PITCH;
|
|
prev_r += VIDEO_PITCH;
|
|
prev_g += VIDEO_PITCH;
|
|
prev_b += VIDEO_PITCH;
|
|
}
|
|
}
|
|
|
|
static bool allocate_video_buf_prev(gambatte::video_pixel_t** buf)
|
|
{
|
|
if (!*buf)
|
|
{
|
|
*buf = (gambatte::video_pixel_t*)malloc(VIDEO_BUFF_SIZE);
|
|
if (!*buf)
|
|
return false;
|
|
}
|
|
memset(*buf, 0, VIDEO_BUFF_SIZE);
|
|
return true;
|
|
}
|
|
|
|
static bool allocate_video_buf_acc(void)
|
|
{
|
|
size_t i;
|
|
size_t buf_size = 256 * NUM_GAMEBOYS * VIDEO_HEIGHT * sizeof(float);
|
|
|
|
if (!video_buf_acc_r)
|
|
{
|
|
video_buf_acc_r = (float*)malloc(buf_size);
|
|
if (!video_buf_acc_r)
|
|
return false;
|
|
}
|
|
|
|
if (!video_buf_acc_g)
|
|
{
|
|
video_buf_acc_g = (float*)malloc(buf_size);
|
|
if (!video_buf_acc_g)
|
|
return false;
|
|
}
|
|
|
|
if (!video_buf_acc_b)
|
|
{
|
|
video_buf_acc_b = (float*)malloc(buf_size);
|
|
if (!video_buf_acc_b)
|
|
return false;
|
|
}
|
|
|
|
/* Cannot use memset() on arrays of floats... */
|
|
for (i = 0; i < (256 * NUM_GAMEBOYS * VIDEO_HEIGHT); i++)
|
|
{
|
|
video_buf_acc_r[i] = 0.0f;
|
|
video_buf_acc_g[i] = 0.0f;
|
|
video_buf_acc_b[i] = 0.0f;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void init_frame_blending(void)
|
|
{
|
|
blend_frames = NULL;
|
|
|
|
/* Allocate interframe blending buffers, as required
|
|
* NOTE: In all cases, any used buffers are 'reset'
|
|
* to avoid drawing garbage on the next frame */
|
|
switch (frame_blend_type)
|
|
{
|
|
case FRAME_BLEND_MIX:
|
|
/* Simple 50:50 blending requires a single buffer */
|
|
if (!allocate_video_buf_prev(&video_buf_prev_1))
|
|
return;
|
|
break;
|
|
case FRAME_BLEND_LCD_GHOSTING:
|
|
/* 'Accurate' LCD ghosting requires four buffers */
|
|
if (!allocate_video_buf_prev(&video_buf_prev_1))
|
|
return;
|
|
if (!allocate_video_buf_prev(&video_buf_prev_2))
|
|
return;
|
|
if (!allocate_video_buf_prev(&video_buf_prev_3))
|
|
return;
|
|
if (!allocate_video_buf_prev(&video_buf_prev_4))
|
|
return;
|
|
break;
|
|
case FRAME_BLEND_LCD_GHOSTING_FAST:
|
|
/* 'Fast' LCD ghosting requires three (RGB)
|
|
* 'accumulator' buffers */
|
|
if (!allocate_video_buf_acc())
|
|
return;
|
|
break;
|
|
case FRAME_BLEND_NONE:
|
|
default:
|
|
/* Error condition - cannot happen
|
|
* > Just leave blend_frames() function set to NULL */
|
|
return;
|
|
}
|
|
|
|
/* Set LCD ghosting response time factors,
|
|
* if required */
|
|
if ((frame_blend_type == FRAME_BLEND_LCD_GHOSTING) &&
|
|
!frame_blend_response_set)
|
|
{
|
|
/* For the default response time of 0.333,
|
|
* only four previous samples are required
|
|
* since the response factor for the fifth
|
|
* is:
|
|
* pow(LCD_RESPONSE_TIME, 5.0f) -> 0.00409
|
|
* ...which is less than half a percent, and
|
|
* therefore irrelevant.
|
|
* If the response time were significantly
|
|
* increased, we may need to rethink this
|
|
* (but more samples == greater performance
|
|
* overheads) */
|
|
frame_blend_response[0] = LCD_RESPONSE_TIME;
|
|
frame_blend_response[1] = std::pow(LCD_RESPONSE_TIME, 2.0f);
|
|
frame_blend_response[2] = std::pow(LCD_RESPONSE_TIME, 3.0f);
|
|
frame_blend_response[3] = std::pow(LCD_RESPONSE_TIME, 4.0f);
|
|
|
|
frame_blend_response_set = true;
|
|
}
|
|
|
|
/* Assign frame blending function */
|
|
switch (frame_blend_type)
|
|
{
|
|
case FRAME_BLEND_MIX:
|
|
blend_frames = blend_frames_mix;
|
|
return;
|
|
case FRAME_BLEND_LCD_GHOSTING:
|
|
blend_frames = blend_frames_lcd_ghost;
|
|
return;
|
|
case FRAME_BLEND_LCD_GHOSTING_FAST:
|
|
blend_frames = blend_frames_lcd_ghost_fast;
|
|
return;
|
|
case FRAME_BLEND_NONE:
|
|
default:
|
|
/* Error condition - cannot happen
|
|
* > Just leave blend_frames() function set to NULL */
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void deinit_frame_blending(void)
|
|
{
|
|
if (video_buf_prev_1)
|
|
{
|
|
free(video_buf_prev_1);
|
|
video_buf_prev_1 = NULL;
|
|
}
|
|
|
|
if (video_buf_prev_2)
|
|
{
|
|
free(video_buf_prev_2);
|
|
video_buf_prev_2 = NULL;
|
|
}
|
|
|
|
if (video_buf_prev_3)
|
|
{
|
|
free(video_buf_prev_3);
|
|
video_buf_prev_3 = NULL;
|
|
}
|
|
|
|
if (video_buf_prev_4)
|
|
{
|
|
free(video_buf_prev_4);
|
|
video_buf_prev_4 = NULL;
|
|
}
|
|
|
|
if (video_buf_acc_r)
|
|
{
|
|
free(video_buf_acc_r);
|
|
video_buf_acc_r = NULL;
|
|
}
|
|
|
|
if (video_buf_acc_g)
|
|
{
|
|
free(video_buf_acc_g);
|
|
video_buf_acc_g = NULL;
|
|
}
|
|
|
|
if (video_buf_acc_b)
|
|
{
|
|
free(video_buf_acc_b);
|
|
video_buf_acc_b = NULL;
|
|
}
|
|
|
|
frame_blend_type = FRAME_BLEND_NONE;
|
|
frame_blend_response_set = false;
|
|
}
|
|
|
|
static void check_frame_blend_variable(void)
|
|
{
|
|
struct retro_variable var;
|
|
enum frame_blend_method old_frame_blend_type = frame_blend_type;
|
|
|
|
frame_blend_type = FRAME_BLEND_NONE;
|
|
|
|
var.key = "gambatte_mix_frames";
|
|
var.value = 0;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "mix"))
|
|
frame_blend_type = FRAME_BLEND_MIX;
|
|
else if (!strcmp(var.value, "lcd_ghosting"))
|
|
frame_blend_type = FRAME_BLEND_LCD_GHOSTING;
|
|
else if (!strcmp(var.value, "lcd_ghosting_fast"))
|
|
frame_blend_type = FRAME_BLEND_LCD_GHOSTING_FAST;
|
|
}
|
|
|
|
if (frame_blend_type == FRAME_BLEND_NONE)
|
|
blend_frames = NULL;
|
|
else if (frame_blend_type != old_frame_blend_type)
|
|
init_frame_blending();
|
|
}
|
|
|
|
/***************************/
|
|
/* Interframe blending END */
|
|
/***************************/
|
|
|
|
/************************/
|
|
/* Rumble support START */
|
|
/************************/
|
|
|
|
static struct retro_rumble_interface rumble = {0};
|
|
static uint16_t rumble_strength_last = 0;
|
|
static uint16_t rumble_strength_up = 0;
|
|
static uint16_t rumble_strength_down = 0;
|
|
static uint16_t rumble_level = 0;
|
|
static bool rumble_active = false;
|
|
|
|
void cartridge_set_rumble(unsigned active)
|
|
{
|
|
if (!rumble.set_rumble_state ||
|
|
!rumble_level)
|
|
return;
|
|
|
|
if (active)
|
|
rumble_strength_up++;
|
|
else
|
|
rumble_strength_down++;
|
|
|
|
rumble_active = true;
|
|
}
|
|
|
|
static void apply_rumble(void)
|
|
{
|
|
uint16_t strength;
|
|
|
|
if (!rumble.set_rumble_state ||
|
|
!rumble_level)
|
|
return;
|
|
|
|
strength = (rumble_strength_up > 0) ?
|
|
(rumble_strength_up * rumble_level) /
|
|
(rumble_strength_up + rumble_strength_down) : 0;
|
|
|
|
rumble_strength_up = 0;
|
|
rumble_strength_down = 0;
|
|
|
|
if (strength == rumble_strength_last)
|
|
return;
|
|
|
|
rumble.set_rumble_state(0, RETRO_RUMBLE_WEAK, strength);
|
|
rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, strength);
|
|
|
|
rumble_strength_last = strength;
|
|
}
|
|
|
|
static void deactivate_rumble(void)
|
|
{
|
|
rumble_strength_up = 0;
|
|
rumble_strength_down = 0;
|
|
rumble_active = false;
|
|
|
|
if (!rumble.set_rumble_state ||
|
|
(rumble_strength_last == 0))
|
|
return;
|
|
|
|
rumble.set_rumble_state(0, RETRO_RUMBLE_WEAK, 0);
|
|
rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 0);
|
|
|
|
rumble_strength_last = 0;
|
|
}
|
|
|
|
/**********************/
|
|
/* Rumble support END */
|
|
/**********************/
|
|
|
|
#ifdef HAVE_NETWORK
|
|
/* Core options 'update display' callback */
|
|
static bool update_option_visibility(void)
|
|
{
|
|
struct retro_variable var = {0};
|
|
bool updated = false;
|
|
unsigned i;
|
|
|
|
/* If frontend supports core option categories,
|
|
* then gambatte_show_gb_link_settings is ignored
|
|
* and no options should be hidden */
|
|
if (libretro_supports_option_categories)
|
|
return false;
|
|
|
|
var.key = "gambatte_show_gb_link_settings";
|
|
var.value = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
bool show_gb_link_settings_prev = show_gb_link_settings;
|
|
|
|
show_gb_link_settings = true;
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
show_gb_link_settings = false;
|
|
|
|
if (show_gb_link_settings != show_gb_link_settings_prev)
|
|
{
|
|
struct retro_core_option_display option_display;
|
|
|
|
option_display.visible = show_gb_link_settings;
|
|
|
|
option_display.key = "gambatte_gb_link_mode";
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
|
|
option_display.key = "gambatte_gb_link_network_port";
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
|
|
for (i = 0; i < 12; i++)
|
|
{
|
|
char key[64] = {0};
|
|
|
|
/* Should be using std::to_string() here, but some
|
|
* compilers don't support it... */
|
|
sprintf(key, "%s%u",
|
|
"gambatte_gb_link_network_server_ip_", i + 1);
|
|
|
|
option_display.key = key;
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
}
|
|
|
|
updated = true;
|
|
}
|
|
}
|
|
|
|
return updated;
|
|
}
|
|
#endif
|
|
|
|
/* Fast forward override */
|
|
static void set_fastforward_override(bool fastforward)
|
|
{
|
|
struct retro_fastforwarding_override ff_override;
|
|
|
|
if (!libretro_supports_ff_override)
|
|
return;
|
|
|
|
ff_override.ratio = -1.0f;
|
|
ff_override.notification = true;
|
|
|
|
if (fastforward)
|
|
{
|
|
ff_override.fastforward = true;
|
|
ff_override.inhibit_toggle = true;
|
|
}
|
|
else
|
|
{
|
|
ff_override.fastforward = false;
|
|
ff_override.inhibit_toggle = false;
|
|
}
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE, &ff_override);
|
|
}
|
|
|
|
static bool file_present_in_system(const char *fname)
|
|
{
|
|
const char *system_dir = NULL;
|
|
char full_path[PATH_MAX_LENGTH];
|
|
|
|
full_path[0] = '\0';
|
|
|
|
if (string_is_empty(fname))
|
|
return false;
|
|
|
|
/* Get system directory */
|
|
if (!environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_dir) ||
|
|
!system_dir)
|
|
{
|
|
gambatte_log(RETRO_LOG_WARN,
|
|
"No system directory defined, unable to look for '%s'.\n", fname);
|
|
return false;
|
|
}
|
|
|
|
fill_pathname_join(full_path, system_dir,
|
|
fname, sizeof(full_path));
|
|
|
|
return path_is_valid(full_path);
|
|
}
|
|
|
|
static bool get_bootloader_from_file(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size)
|
|
{
|
|
const char *system_dir = NULL;
|
|
const char *bios_name = NULL;
|
|
RFILE *bios_file = NULL;
|
|
int64_t bios_size = 0;
|
|
int64_t bytes_read = 0;
|
|
char bios_path[PATH_MAX_LENGTH];
|
|
|
|
bios_path[0] = '\0';
|
|
|
|
if (!use_official_bootloader)
|
|
return false;
|
|
|
|
/* Get system directory */
|
|
if (!environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_dir) ||
|
|
!system_dir)
|
|
{
|
|
gambatte_log(RETRO_LOG_WARN,
|
|
"No system directory defined, unable to look for bootloader.\n");
|
|
return false;
|
|
}
|
|
|
|
/* Get BIOS type */
|
|
if (isgbc)
|
|
{
|
|
bios_name = "gbc_bios.bin";
|
|
bios_size = 0x900;
|
|
}
|
|
else
|
|
{
|
|
bios_name = "gb_bios.bin";
|
|
bios_size = 0x100;
|
|
}
|
|
|
|
if (bios_size > buf_size)
|
|
return false;
|
|
|
|
/* Get BIOS path */
|
|
fill_pathname_join(bios_path, system_dir,
|
|
bios_name, sizeof(bios_path));
|
|
|
|
/* Read BIOS file */
|
|
bios_file = filestream_open(bios_path,
|
|
RETRO_VFS_FILE_ACCESS_READ,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
|
|
if (!bios_file)
|
|
return false;
|
|
|
|
bytes_read = filestream_read(bios_file,
|
|
data, bios_size);
|
|
filestream_close(bios_file);
|
|
|
|
if (bytes_read != bios_size)
|
|
return false;
|
|
|
|
gambatte_log(RETRO_LOG_INFO, "Read bootloader: %s\n", bios_path);
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace input
|
|
{
|
|
struct map { unsigned snes; unsigned gb; };
|
|
static const map btn_map[] = {
|
|
{ RETRO_DEVICE_ID_JOYPAD_A, gambatte::InputGetter::A },
|
|
{ RETRO_DEVICE_ID_JOYPAD_B, gambatte::InputGetter::B },
|
|
{ RETRO_DEVICE_ID_JOYPAD_SELECT, gambatte::InputGetter::SELECT },
|
|
{ RETRO_DEVICE_ID_JOYPAD_START, gambatte::InputGetter::START },
|
|
{ RETRO_DEVICE_ID_JOYPAD_RIGHT, gambatte::InputGetter::RIGHT },
|
|
{ RETRO_DEVICE_ID_JOYPAD_LEFT, gambatte::InputGetter::LEFT },
|
|
{ RETRO_DEVICE_ID_JOYPAD_UP, gambatte::InputGetter::UP },
|
|
{ RETRO_DEVICE_ID_JOYPAD_DOWN, gambatte::InputGetter::DOWN },
|
|
};
|
|
}
|
|
|
|
static void update_input_state(void)
|
|
{
|
|
unsigned i;
|
|
unsigned res = 0;
|
|
bool turbo_a = false;
|
|
bool turbo_b = false;
|
|
bool palette_prev = false;
|
|
bool palette_next = false;
|
|
bool palette_switch_enabled = (libretro_supports_set_variable &&
|
|
internal_palette_active);
|
|
|
|
if (libretro_supports_bitmasks)
|
|
{
|
|
int16_t ret = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK);
|
|
for (i = 0; i < sizeof(input::btn_map) / sizeof(input::map); i++)
|
|
res |= (ret & (1 << input::btn_map[i].snes)) ? input::btn_map[i].gb : 0;
|
|
|
|
libretro_ff_enabled = libretro_supports_ff_override &&
|
|
(ret & (1 << RETRO_DEVICE_ID_JOYPAD_R2));
|
|
|
|
turbo_a = (ret & (1 << RETRO_DEVICE_ID_JOYPAD_X));
|
|
turbo_b = (ret & (1 << RETRO_DEVICE_ID_JOYPAD_Y));
|
|
|
|
if (palette_switch_enabled)
|
|
{
|
|
palette_prev = (bool)(ret & (1 << RETRO_DEVICE_ID_JOYPAD_L));
|
|
palette_next = (bool)(ret & (1 << RETRO_DEVICE_ID_JOYPAD_R));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < sizeof(input::btn_map) / sizeof(input::map); i++)
|
|
res |= input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, input::btn_map[i].snes) ? input::btn_map[i].gb : 0;
|
|
|
|
libretro_ff_enabled = libretro_supports_ff_override &&
|
|
input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2);
|
|
|
|
turbo_a = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X);
|
|
turbo_b = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y);
|
|
|
|
if (palette_switch_enabled)
|
|
{
|
|
palette_prev = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L);
|
|
palette_next = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R);
|
|
}
|
|
}
|
|
|
|
if (!up_down_allowed)
|
|
{
|
|
if (res & gambatte::InputGetter::UP)
|
|
if (res & gambatte::InputGetter::DOWN)
|
|
res &= ~(gambatte::InputGetter::UP | gambatte::InputGetter::DOWN);
|
|
|
|
if (res & gambatte::InputGetter::LEFT)
|
|
if (res & gambatte::InputGetter::RIGHT)
|
|
res &= ~(gambatte::InputGetter::LEFT | gambatte::InputGetter::RIGHT);
|
|
}
|
|
|
|
/* Handle fast forward button */
|
|
if (libretro_ff_enabled != libretro_ff_enabled_prev)
|
|
{
|
|
set_fastforward_override(libretro_ff_enabled);
|
|
libretro_ff_enabled_prev = libretro_ff_enabled;
|
|
}
|
|
|
|
/* Handle turbo buttons */
|
|
if (turbo_a)
|
|
{
|
|
res |= (turbo_a_counter < turbo_pulse_width) ?
|
|
gambatte::InputGetter::A : 0;
|
|
|
|
turbo_a_counter++;
|
|
if (turbo_a_counter >= turbo_period)
|
|
turbo_a_counter = 0;
|
|
}
|
|
else
|
|
turbo_a_counter = 0;
|
|
|
|
if (turbo_b)
|
|
{
|
|
res |= (turbo_b_counter < turbo_pulse_width) ?
|
|
gambatte::InputGetter::B : 0;
|
|
|
|
turbo_b_counter++;
|
|
if (turbo_b_counter >= turbo_period)
|
|
turbo_b_counter = 0;
|
|
}
|
|
else
|
|
turbo_b_counter = 0;
|
|
|
|
/* Handle internal palette switching */
|
|
if (palette_prev || palette_next)
|
|
{
|
|
if (palette_switch_counter == 0)
|
|
{
|
|
size_t palette_index = internal_palette_index;
|
|
|
|
if (palette_prev)
|
|
{
|
|
if (palette_index > 0)
|
|
palette_index--;
|
|
else
|
|
palette_index = NUM_PALETTES_TOTAL - 1;
|
|
}
|
|
else /* palette_next */
|
|
{
|
|
if (palette_index < NUM_PALETTES_TOTAL - 1)
|
|
palette_index++;
|
|
else
|
|
palette_index = 0;
|
|
}
|
|
|
|
palette_switch_set_index(palette_index);
|
|
}
|
|
|
|
palette_switch_counter++;
|
|
if (palette_switch_counter >= PALETTE_SWITCH_PERIOD)
|
|
palette_switch_counter = 0;
|
|
}
|
|
else
|
|
palette_switch_counter = 0;
|
|
|
|
libretro_input_state = res;
|
|
}
|
|
|
|
/* gb_input is called multiple times per frame.
|
|
* Determine input state once per frame using
|
|
* update_input_state(), and simply return
|
|
* cached value here */
|
|
class SNESInput : public gambatte::InputGetter
|
|
{
|
|
public:
|
|
unsigned operator()()
|
|
{
|
|
return libretro_input_state;
|
|
}
|
|
} static gb_input;
|
|
|
|
#ifdef HAVE_NETWORK
|
|
enum SerialMode {
|
|
SERIAL_NONE,
|
|
SERIAL_SERVER,
|
|
SERIAL_CLIENT
|
|
};
|
|
static NetSerial gb_net_serial;
|
|
static SerialMode gb_serialMode = SERIAL_NONE;
|
|
static int gb_NetworkPort = 12345;
|
|
static std::string gb_NetworkClientAddr;
|
|
#endif
|
|
|
|
void retro_get_system_info(struct retro_system_info *info)
|
|
{
|
|
info->library_name = "Gambatte";
|
|
#ifndef GIT_VERSION
|
|
#define GIT_VERSION ""
|
|
#endif
|
|
#ifdef HAVE_NETWORK
|
|
info->library_version = "v0.5.0-netlink" GIT_VERSION;
|
|
#else
|
|
info->library_version = "v0.5.0" GIT_VERSION;
|
|
#endif
|
|
info->need_fullpath = false;
|
|
info->block_extract = false;
|
|
info->valid_extensions = "gb|gbc|dmg";
|
|
}
|
|
|
|
void retro_get_system_av_info(struct retro_system_av_info *info)
|
|
{
|
|
|
|
info->geometry.base_width = VIDEO_WIDTH;
|
|
info->geometry.base_height = VIDEO_HEIGHT;
|
|
info->geometry.max_width = VIDEO_WIDTH;
|
|
info->geometry.max_height = VIDEO_HEIGHT;
|
|
info->geometry.aspect_ratio = (float)GB_SCREEN_WIDTH / (float)VIDEO_HEIGHT;
|
|
|
|
info->timing.fps = VIDEO_REFRESH_RATE;
|
|
info->timing.sample_rate = use_cc_resampler ?
|
|
SOUND_SAMPLE_RATE_CC : SOUND_SAMPLE_RATE_BLIPPER;
|
|
}
|
|
|
|
static void check_system_specs(void)
|
|
{
|
|
unsigned level = 4;
|
|
environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level);
|
|
}
|
|
|
|
void retro_init(void)
|
|
{
|
|
struct retro_log_callback log;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
|
|
gambatte_log_set_cb(log.log);
|
|
else
|
|
gambatte_log_set_cb(NULL);
|
|
|
|
// Using uint_least32_t in an audio interface expecting you to cast to short*? :( Weird stuff.
|
|
assert(sizeof(gambatte::uint_least32_t) == sizeof(uint32_t));
|
|
gb.setInputGetter(&gb_input);
|
|
#ifdef DUAL_MODE
|
|
gb2.setInputGetter(&gb_input);
|
|
#endif
|
|
|
|
#ifdef _3DS
|
|
video_buf = (gambatte::video_pixel_t*)linearMemAlign(VIDEO_BUFF_SIZE, 128);
|
|
#else
|
|
video_buf = (gambatte::video_pixel_t*)malloc(VIDEO_BUFF_SIZE);
|
|
#endif
|
|
|
|
check_system_specs();
|
|
|
|
//gb/gbc bootloader support
|
|
gb.setBootloaderGetter(get_bootloader_from_file);
|
|
#ifdef DUAL_MODE
|
|
gb2.setBootloaderGetter(get_bootloader_from_file);
|
|
#endif
|
|
|
|
// Initialise internal palette maps
|
|
initPaletteMaps();
|
|
|
|
// Initialise palette switching functionality
|
|
init_palette_switch();
|
|
|
|
struct retro_variable var = {0};
|
|
var.key = "gambatte_gb_bootloader";
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "enabled"))
|
|
use_official_bootloader = true;
|
|
else
|
|
use_official_bootloader = false;
|
|
}
|
|
else
|
|
use_official_bootloader = false;
|
|
|
|
libretro_supports_bitmasks = false;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
|
|
libretro_supports_bitmasks = true;
|
|
|
|
libretro_supports_ff_override = false;
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE, NULL))
|
|
libretro_supports_ff_override = true;
|
|
}
|
|
|
|
void retro_deinit(void)
|
|
{
|
|
#ifdef _3DS
|
|
linearFree(video_buf);
|
|
#else
|
|
free(video_buf);
|
|
#endif
|
|
video_buf = NULL;
|
|
deinit_frame_blending();
|
|
audio_resampler_deinit();
|
|
|
|
freePaletteMaps();
|
|
deinit_palette_switch();
|
|
|
|
if (libretro_ff_enabled)
|
|
set_fastforward_override(false);
|
|
|
|
libretro_supports_option_categories = false;
|
|
libretro_supports_bitmasks = false;
|
|
libretro_supports_ff_override = false;
|
|
libretro_ff_enabled = false;
|
|
libretro_ff_enabled_prev = false;
|
|
|
|
libretro_input_state = 0;
|
|
up_down_allowed = false;
|
|
turbo_period = TURBO_PERIOD_MIN;
|
|
turbo_pulse_width = TURBO_PULSE_WIDTH_MIN;
|
|
turbo_a_counter = 0;
|
|
turbo_b_counter = 0;
|
|
|
|
deactivate_rumble();
|
|
memset(&rumble, 0, sizeof(struct retro_rumble_interface));
|
|
rumble_level = 0;
|
|
}
|
|
|
|
void retro_set_environment(retro_environment_t cb)
|
|
{
|
|
struct retro_vfs_interface_info vfs_iface_info;
|
|
bool option_categories = false;
|
|
environ_cb = cb;
|
|
|
|
/* Set core options
|
|
* An annoyance: retro_set_environment() can be called
|
|
* multiple times, and depending upon the current frontend
|
|
* state various environment callbacks may be disabled.
|
|
* This means the reported 'categories_supported' status
|
|
* may change on subsequent iterations. We therefore have
|
|
* to record whether 'categories_supported' is true on any
|
|
* iteration, and latch the result */
|
|
libretro_set_core_options(environ_cb, &option_categories);
|
|
libretro_supports_option_categories |= option_categories;
|
|
|
|
#ifdef HAVE_NETWORK
|
|
/* If frontend supports core option categories,
|
|
* gambatte_show_gb_link_settings is unused and
|
|
* should be hidden */
|
|
if (libretro_supports_option_categories)
|
|
{
|
|
struct retro_core_option_display option_display;
|
|
|
|
option_display.visible = false;
|
|
option_display.key = "gambatte_show_gb_link_settings";
|
|
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
vfs_iface_info.required_interface_version = 2;
|
|
vfs_iface_info.iface = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info))
|
|
filestream_vfs_init(&vfs_iface_info);
|
|
}
|
|
|
|
void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
|
|
void retro_set_audio_sample(retro_audio_sample_t) { }
|
|
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { audio_batch_cb = cb; }
|
|
void retro_set_input_poll(retro_input_poll_t cb) { input_poll_cb = cb; }
|
|
void retro_set_input_state(retro_input_state_t cb) { input_state_cb = cb; }
|
|
|
|
void retro_set_controller_port_device(unsigned, unsigned) {}
|
|
|
|
void retro_reset()
|
|
{
|
|
// gambatte seems to clear out SRAM on reset.
|
|
uint8_t *sram = 0;
|
|
uint8_t *rtc = 0;
|
|
if (gb.savedata_size())
|
|
{
|
|
sram = new uint8_t[gb.savedata_size()];
|
|
memcpy(sram, gb.savedata_ptr(), gb.savedata_size());
|
|
}
|
|
if (gb.rtcdata_size())
|
|
{
|
|
rtc = new uint8_t[gb.rtcdata_size()];
|
|
memcpy(rtc, gb.rtcdata_ptr(), gb.rtcdata_size());
|
|
}
|
|
|
|
gb.reset();
|
|
#ifdef DUAL_MODE
|
|
gb2.reset();
|
|
#endif
|
|
|
|
if (sram)
|
|
{
|
|
memcpy(gb.savedata_ptr(), sram, gb.savedata_size());
|
|
delete[] sram;
|
|
}
|
|
if (rtc)
|
|
{
|
|
memcpy(gb.rtcdata_ptr(), rtc, gb.rtcdata_size());
|
|
delete[] rtc;
|
|
}
|
|
}
|
|
|
|
static size_t serialize_size = 0;
|
|
size_t retro_serialize_size(void)
|
|
{
|
|
return gb.stateSize();
|
|
}
|
|
|
|
bool retro_serialize(void *data, size_t size)
|
|
{
|
|
serialize_size = retro_serialize_size();
|
|
|
|
if (size != serialize_size)
|
|
return false;
|
|
|
|
gb.saveState(data);
|
|
return true;
|
|
}
|
|
|
|
bool retro_unserialize(const void *data, size_t size)
|
|
{
|
|
serialize_size = retro_serialize_size();
|
|
|
|
if (size != serialize_size)
|
|
return false;
|
|
|
|
gb.loadState(data);
|
|
return true;
|
|
}
|
|
|
|
void retro_cheat_reset()
|
|
{
|
|
gb.clearCheats();
|
|
}
|
|
|
|
void retro_cheat_set(unsigned index, bool enabled, const char *code)
|
|
{
|
|
std::string code_str(code);
|
|
|
|
replace(code_str.begin(), code_str.end(), '+', ';');
|
|
|
|
if (code_str.find("-") != std::string::npos) {
|
|
gb.setGameGenie(code_str);
|
|
} else {
|
|
gb.setGameShark(code_str);
|
|
}
|
|
}
|
|
|
|
enum gb_colorization_enable_type
|
|
{
|
|
GB_COLORIZATION_DISABLED = 0,
|
|
GB_COLORIZATION_AUTO = 1,
|
|
GB_COLORIZATION_CUSTOM = 2,
|
|
GB_COLORIZATION_INTERNAL = 3,
|
|
GB_COLORIZATION_GBC = 4,
|
|
GB_COLORIZATION_SGB = 5
|
|
};
|
|
|
|
static enum gb_colorization_enable_type gb_colorization_enable = GB_COLORIZATION_DISABLED;
|
|
|
|
static std::string rom_path;
|
|
static char internal_game_name[17];
|
|
|
|
static void load_custom_palette(void)
|
|
{
|
|
const char *system_dir = NULL;
|
|
const char *rom_file = NULL;
|
|
char *rom_name = NULL;
|
|
RFILE *palette_file = NULL;
|
|
unsigned line_index = 0;
|
|
bool path_valid = false;
|
|
unsigned rgb32 = 0;
|
|
char palette_path[PATH_MAX_LENGTH];
|
|
|
|
palette_path[0] = '\0';
|
|
|
|
/* Get system directory */
|
|
if (!environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_dir) ||
|
|
!system_dir)
|
|
{
|
|
gambatte_log(RETRO_LOG_WARN,
|
|
"No system directory defined, unable to look for custom palettes.\n");
|
|
return;
|
|
}
|
|
|
|
/* Look for palette named after ROM file */
|
|
rom_file = path_basename(rom_path.c_str());
|
|
if (!string_is_empty(rom_file))
|
|
{
|
|
size_t len = (strlen(rom_file) + 1) * sizeof(char);
|
|
rom_name = (char*)malloc(len);
|
|
strlcpy(rom_name, rom_file, len);
|
|
path_remove_extension(rom_name);
|
|
if (!string_is_empty(rom_name))
|
|
{
|
|
fill_pathname_join_special_ext(palette_path,
|
|
system_dir, "palettes", rom_name, ".pal",
|
|
sizeof(palette_path));
|
|
path_valid = path_is_valid(palette_path);
|
|
}
|
|
free(rom_name);
|
|
rom_name = NULL;
|
|
}
|
|
|
|
if (!path_valid)
|
|
{
|
|
/* Look for palette named after the internal game
|
|
* name in the ROM header */
|
|
fill_pathname_join_special_ext(palette_path,
|
|
system_dir, "palettes", internal_game_name, ".pal",
|
|
sizeof(palette_path));
|
|
path_valid = path_is_valid(palette_path);
|
|
}
|
|
|
|
if (!path_valid)
|
|
{
|
|
/* Look for default custom palette file (default.pal) */
|
|
fill_pathname_join_special_ext(palette_path,
|
|
system_dir, "palettes", "default", ".pal",
|
|
sizeof(palette_path));
|
|
path_valid = path_is_valid(palette_path);
|
|
}
|
|
|
|
if (!path_valid)
|
|
return; /* Unable to find any custom palette file */
|
|
|
|
palette_file = filestream_open(palette_path,
|
|
RETRO_VFS_FILE_ACCESS_READ,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
|
|
if (!palette_file)
|
|
{
|
|
gambatte_log(RETRO_LOG_WARN,
|
|
"Failed to open custom palette: %s\n", palette_path);
|
|
return;
|
|
}
|
|
|
|
gambatte_log(RETRO_LOG_INFO, "Using custom palette: %s\n", palette_path);
|
|
|
|
/* Iterate over palette file lines */
|
|
while (!filestream_eof(palette_file))
|
|
{
|
|
char *line = filestream_getline(palette_file);
|
|
const char *value_str = NULL;
|
|
|
|
if (!line)
|
|
break;
|
|
|
|
/* Remove any leading/trailing whitespace
|
|
* > Additionally handles 'leftovers' from
|
|
* CRLF line terminators if palette file
|
|
* happens to be in DOS format */
|
|
string_trim_whitespace(line);
|
|
|
|
if (string_is_empty(line) || /* Skip empty lines */
|
|
(*line == '[') || /* Skip ini sections */
|
|
(*line == ';')) /* Skip ini comments */
|
|
goto palette_line_end;
|
|
|
|
/* Supposed to be a typo here... */
|
|
if (string_starts_with(line, "slectedScheme="))
|
|
goto palette_line_end;
|
|
|
|
/* Get substring after first '=' character */
|
|
value_str = strchr(line, '=');
|
|
if (!value_str ||
|
|
string_is_empty(++value_str))
|
|
{
|
|
gambatte_log(RETRO_LOG_WARN,
|
|
"Error in %s, line %u (color left as default)\n",
|
|
palette_path, line_index);
|
|
goto palette_line_end;
|
|
}
|
|
|
|
/* Extract colour value */
|
|
rgb32 = string_to_unsigned(value_str);
|
|
if (rgb32 == 0)
|
|
{
|
|
/* string_to_unsigned() will return 0 if
|
|
* string is invalid, so perform a manual
|
|
* validity check... */
|
|
for (; *value_str != '\0'; value_str++)
|
|
{
|
|
if (*value_str != '0')
|
|
{
|
|
gambatte_log(RETRO_LOG_WARN,
|
|
"Unable to read palette color in %s, line %u (color left as default)\n",
|
|
palette_path, line_index);
|
|
goto palette_line_end;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef VIDEO_RGB565
|
|
rgb32 = (rgb32 & 0x0000F8) >> 3 | /* blue */
|
|
(rgb32 & 0x00FC00) >> 5 | /* green */
|
|
(rgb32 & 0xF80000) >> 8; /* red */
|
|
#elif defined(VIDEO_ABGR1555)
|
|
rgb32 = (rgb32 & 0x0000F8) << 7 | /* blue */
|
|
(rgb32 & 0xF800) >> 6 | /* green */
|
|
(rgb32 & 0xF80000) >> 19; /* red */
|
|
#endif
|
|
|
|
if ( string_starts_with(line, "Background0="))
|
|
gb.setDmgPaletteColor(0, 0, rgb32);
|
|
else if (string_starts_with(line, "Background1="))
|
|
gb.setDmgPaletteColor(0, 1, rgb32);
|
|
else if (string_starts_with(line, "Background2="))
|
|
gb.setDmgPaletteColor(0, 2, rgb32);
|
|
else if (string_starts_with(line, "Background3="))
|
|
gb.setDmgPaletteColor(0, 3, rgb32);
|
|
else if (string_starts_with(line, "Sprite%2010="))
|
|
gb.setDmgPaletteColor(1, 0, rgb32);
|
|
else if (string_starts_with(line, "Sprite%2011="))
|
|
gb.setDmgPaletteColor(1, 1, rgb32);
|
|
else if (string_starts_with(line, "Sprite%2012="))
|
|
gb.setDmgPaletteColor(1, 2, rgb32);
|
|
else if (string_starts_with(line, "Sprite%2013="))
|
|
gb.setDmgPaletteColor(1, 3, rgb32);
|
|
else if (string_starts_with(line, "Sprite%2020="))
|
|
gb.setDmgPaletteColor(2, 0, rgb32);
|
|
else if (string_starts_with(line, "Sprite%2021="))
|
|
gb.setDmgPaletteColor(2, 1, rgb32);
|
|
else if (string_starts_with(line, "Sprite%2022="))
|
|
gb.setDmgPaletteColor(2, 2, rgb32);
|
|
else if (string_starts_with(line, "Sprite%2023="))
|
|
gb.setDmgPaletteColor(2, 3, rgb32);
|
|
else
|
|
gambatte_log(RETRO_LOG_WARN,
|
|
"Error in %s, line %u (color left as default)\n",
|
|
palette_path, line_index);
|
|
|
|
palette_line_end:
|
|
line_index++;
|
|
free(line);
|
|
line = NULL;
|
|
}
|
|
|
|
filestream_close(palette_file);
|
|
}
|
|
|
|
static void find_internal_palette(const unsigned short **palette, bool *is_gbc)
|
|
{
|
|
const char *palette_title = NULL;
|
|
size_t index = 0;
|
|
struct retro_variable var = {0};
|
|
|
|
// Read main internal palette setting
|
|
var.key = "gambatte_gb_internal_palette";
|
|
var.value = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
// Handle TWB64 packs
|
|
if (string_is_equal(var.value, "TWB64 - Pack 1"))
|
|
{
|
|
var.key = "gambatte_gb_palette_twb64_1";
|
|
var.value = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
palette_title = var.value;
|
|
|
|
// Determine 'consolidated' palette index
|
|
if (palette_title)
|
|
index = RHMAP_GET_STR(palettes_twb64_1_index_map, palette_title);
|
|
if (index > 0)
|
|
index--;
|
|
internal_palette_index = NUM_PALETTES_DEFAULT + index;
|
|
}
|
|
else if (string_is_equal(var.value, "TWB64 - Pack 2"))
|
|
{
|
|
var.key = "gambatte_gb_palette_twb64_2";
|
|
var.value = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
palette_title = var.value;
|
|
|
|
// Determine 'consolidated' palette index
|
|
if (palette_title)
|
|
index = RHMAP_GET_STR(palettes_twb64_2_index_map, palette_title);
|
|
if (index > 0)
|
|
index--;
|
|
internal_palette_index = NUM_PALETTES_DEFAULT +
|
|
NUM_PALETTES_TWB64_1 + index;
|
|
}
|
|
else if (string_is_equal(var.value, "TWB64 - Pack 3"))
|
|
{
|
|
var.key = "gambatte_gb_palette_twb64_3";
|
|
var.value = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
palette_title = var.value;
|
|
|
|
// Determine 'consolidated' palette index
|
|
if (palette_title)
|
|
index = RHMAP_GET_STR(palettes_twb64_3_index_map, palette_title);
|
|
if (index > 0)
|
|
index--;
|
|
internal_palette_index = NUM_PALETTES_DEFAULT +
|
|
NUM_PALETTES_TWB64_1 + NUM_PALETTES_TWB64_2 + index;
|
|
}
|
|
// Handle PixelShift packs
|
|
else if (string_is_equal(var.value, "PixelShift - Pack 1"))
|
|
{
|
|
var.key = "gambatte_gb_palette_pixelshift_1";
|
|
var.value = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
palette_title = var.value;
|
|
|
|
// Determine 'consolidated' palette index
|
|
if (palette_title)
|
|
index = RHMAP_GET_STR(palettes_pixelshift_1_index_map, palette_title);
|
|
if (index > 0)
|
|
index--;
|
|
internal_palette_index = NUM_PALETTES_DEFAULT + NUM_PALETTES_TWB64_1 +
|
|
NUM_PALETTES_TWB64_2 + NUM_PALETTES_TWB64_3 + index;
|
|
}
|
|
else
|
|
{
|
|
palette_title = var.value;
|
|
|
|
// Determine 'consolidated' palette index
|
|
index = RHMAP_GET_STR(palettes_default_index_map, palette_title);
|
|
if (index > 0)
|
|
index--;
|
|
internal_palette_index = index;
|
|
}
|
|
}
|
|
|
|
// Ensure we have a valid palette title
|
|
if (!palette_title)
|
|
{
|
|
palette_title = "GBC - Grayscale";
|
|
internal_palette_index = 8;
|
|
}
|
|
|
|
// Search for requested palette
|
|
*palette = findGbcDirPal(palette_title);
|
|
|
|
// If palette is not found (i.e. if a palette
|
|
// is removed from the core, and a user loads
|
|
// old core options settings), fall back to
|
|
// black and white
|
|
if (!(*palette))
|
|
{
|
|
palette_title = "GBC - Grayscale";
|
|
*palette = findGbcDirPal(palette_title);
|
|
internal_palette_index = 8;
|
|
// No error check here - if this fails,
|
|
// the core is entirely broken...
|
|
}
|
|
|
|
// Check whether this is a GBC palette
|
|
if (!strncmp("GBC", palette_title, 3))
|
|
*is_gbc = true;
|
|
else
|
|
*is_gbc = false;
|
|
|
|
// Record that an internal palette is
|
|
// currently in use
|
|
internal_palette_active = true;
|
|
}
|
|
|
|
static void check_variables(bool startup)
|
|
{
|
|
unsigned i, j;
|
|
|
|
unsigned colorCorrection = 0;
|
|
struct retro_variable var = {0};
|
|
var.key = "gambatte_gbc_color_correction";
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "GBC only"))
|
|
colorCorrection = 1;
|
|
else if (!strcmp(var.value, "always"))
|
|
colorCorrection = 2;
|
|
}
|
|
|
|
unsigned colorCorrectionMode = 0;
|
|
var.key = "gambatte_gbc_color_correction_mode";
|
|
var.value = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "fast")) {
|
|
colorCorrectionMode = 1;
|
|
}
|
|
gb.setColorCorrectionMode(colorCorrectionMode);
|
|
|
|
float colorCorrectionBrightness = 0.5f; /* central */
|
|
var.key = "gambatte_gbc_frontlight_position";
|
|
var.value = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "above screen"))
|
|
colorCorrectionBrightness = 1.0f;
|
|
else if (!strcmp(var.value, "below screen"))
|
|
colorCorrectionBrightness = 0.0f;
|
|
}
|
|
gb.setColorCorrectionBrightness(colorCorrectionBrightness);
|
|
|
|
unsigned darkFilterLevel = 0;
|
|
var.key = "gambatte_dark_filter_level";
|
|
var.value = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
darkFilterLevel = static_cast<unsigned>(atoi(var.value));
|
|
}
|
|
gb.setDarkFilterLevel(darkFilterLevel);
|
|
|
|
bool old_use_cc_resampler = use_cc_resampler;
|
|
use_cc_resampler = false;
|
|
var.key = "gambatte_audio_resampler";
|
|
var.value = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) &&
|
|
var.value && !strcmp(var.value, "cc"))
|
|
use_cc_resampler = true;
|
|
|
|
if (!startup && (use_cc_resampler != old_use_cc_resampler))
|
|
{
|
|
struct retro_system_av_info av_info;
|
|
audio_resampler_deinit();
|
|
audio_resampler_init(false);
|
|
retro_get_system_av_info(&av_info);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &av_info);
|
|
}
|
|
|
|
up_down_allowed = false;
|
|
var.key = "gambatte_up_down_allowed";
|
|
var.value = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "enabled"))
|
|
up_down_allowed = true;
|
|
else
|
|
up_down_allowed = false;
|
|
}
|
|
|
|
turbo_period = TURBO_PERIOD_MIN;
|
|
turbo_pulse_width = TURBO_PULSE_WIDTH_MIN;
|
|
var.key = "gambatte_turbo_period";
|
|
var.value = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
turbo_period = atoi(var.value);
|
|
turbo_period = (turbo_period < TURBO_PERIOD_MIN) ?
|
|
TURBO_PERIOD_MIN : turbo_period;
|
|
turbo_period = (turbo_period > TURBO_PERIOD_MAX) ?
|
|
TURBO_PERIOD_MAX : turbo_period;
|
|
|
|
turbo_pulse_width = turbo_period >> 1;
|
|
turbo_pulse_width = (turbo_pulse_width < TURBO_PULSE_WIDTH_MIN) ?
|
|
TURBO_PULSE_WIDTH_MIN : turbo_pulse_width;
|
|
turbo_pulse_width = (turbo_pulse_width > TURBO_PULSE_WIDTH_MAX) ?
|
|
TURBO_PULSE_WIDTH_MAX : turbo_pulse_width;
|
|
|
|
turbo_a_counter = 0;
|
|
turbo_b_counter = 0;
|
|
}
|
|
|
|
rumble_level = 0;
|
|
var.key = "gambatte_rumble_level";
|
|
var.value = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
rumble_level = atoi(var.value);
|
|
rumble_level = (rumble_level > 10) ? 10 : rumble_level;
|
|
rumble_level = (rumble_level > 0) ? ((0x1999 * rumble_level) + 0x5) : 0;
|
|
}
|
|
if (rumble_level == 0)
|
|
deactivate_rumble();
|
|
|
|
/* Interframe blending option has its own handler */
|
|
check_frame_blend_variable();
|
|
|
|
#ifdef HAVE_NETWORK
|
|
|
|
gb_serialMode = SERIAL_NONE;
|
|
var.key = "gambatte_gb_link_mode";
|
|
var.value = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
|
if (!strcmp(var.value, "Network Server")) {
|
|
gb_serialMode = SERIAL_SERVER;
|
|
} else if (!strcmp(var.value, "Network Client")) {
|
|
gb_serialMode = SERIAL_CLIENT;
|
|
}
|
|
}
|
|
|
|
var.key = "gambatte_gb_link_network_port";
|
|
var.value = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
|
gb_NetworkPort=atoi(var.value);
|
|
}
|
|
|
|
unsigned ip_index = 1;
|
|
gb_NetworkClientAddr = "";
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
std::string octet = "0";
|
|
char tmp[8] = {0};
|
|
|
|
for (j = 0; j < 3; j++)
|
|
{
|
|
char key[64] = {0};
|
|
|
|
/* Should be using std::to_string() here, but some
|
|
* compilers don't support it... */
|
|
sprintf(key, "%s%u",
|
|
"gambatte_gb_link_network_server_ip_", ip_index);
|
|
|
|
var.key = key;
|
|
var.value = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
octet += std::string(var.value);
|
|
|
|
ip_index++;
|
|
}
|
|
|
|
/* Remove leading zeros
|
|
* Should be using std::stoul() here, but some compilers
|
|
* don't support it... */
|
|
sprintf(tmp, "%u", atoi(octet.c_str()));
|
|
octet = std::string(tmp);
|
|
|
|
if (i < 3)
|
|
octet += ".";
|
|
|
|
gb_NetworkClientAddr += octet;
|
|
}
|
|
|
|
switch(gb_serialMode)
|
|
{
|
|
case SERIAL_SERVER:
|
|
gb_net_serial.start(true, gb_NetworkPort, gb_NetworkClientAddr);
|
|
gb.setSerialIO(&gb_net_serial);
|
|
break;
|
|
case SERIAL_CLIENT:
|
|
gb_net_serial.start(false, gb_NetworkPort, gb_NetworkClientAddr);
|
|
gb.setSerialIO(&gb_net_serial);
|
|
break;
|
|
default:
|
|
gb_net_serial.stop();
|
|
gb.setSerialIO(NULL);
|
|
break;
|
|
}
|
|
|
|
/* Show/hide core options */
|
|
update_option_visibility();
|
|
|
|
#endif
|
|
|
|
internal_palette_active = false;
|
|
var.key = "gambatte_gb_colorization";
|
|
|
|
if (!environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value) {
|
|
// Should really wait until the end to call setColorCorrection(),
|
|
// but don't want to have to change the indentation of all the
|
|
// following code... (makes it too difficult to see the changes in
|
|
// a git diff...)
|
|
gb.setColorCorrection(gb.isCgb() && (colorCorrection != 0));
|
|
return;
|
|
}
|
|
|
|
if (gb.isCgb()) {
|
|
gb.setColorCorrection(colorCorrection != 0);
|
|
return;
|
|
}
|
|
|
|
// else it is a GB-mono game -> set a color palette
|
|
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
gb_colorization_enable = GB_COLORIZATION_DISABLED;
|
|
else if (strcmp(var.value, "auto") == 0)
|
|
gb_colorization_enable = GB_COLORIZATION_AUTO;
|
|
else if (strcmp(var.value, "custom") == 0)
|
|
gb_colorization_enable = GB_COLORIZATION_CUSTOM;
|
|
else if (strcmp(var.value, "internal") == 0)
|
|
gb_colorization_enable = GB_COLORIZATION_INTERNAL;
|
|
else if (strcmp(var.value, "GBC") == 0)
|
|
gb_colorization_enable = GB_COLORIZATION_GBC;
|
|
else if (strcmp(var.value, "SGB") == 0)
|
|
gb_colorization_enable = GB_COLORIZATION_SGB;
|
|
|
|
// Containers for GBC/SGB BIOS built-in palettes
|
|
const unsigned short *gbc_bios_palette = NULL;
|
|
const unsigned short *sgb_bios_palette = NULL;
|
|
bool isGbcPalette = false;
|
|
|
|
switch (gb_colorization_enable)
|
|
{
|
|
case GB_COLORIZATION_AUTO:
|
|
// Automatic colourisation
|
|
// Order of preference:
|
|
// 1 - SGB, if more colourful than GBC
|
|
// 2 - GBC, if more colourful than SGB
|
|
// 3 - SGB, if no GBC palette defined
|
|
// 4 - User-defined internal palette, if neither GBC nor SGB palettes defined
|
|
//
|
|
// Load GBC BIOS built-in palette
|
|
gbc_bios_palette = findGbcTitlePal(internal_game_name);
|
|
// Load SGB BIOS built-in palette
|
|
sgb_bios_palette = findSgbTitlePal(internal_game_name);
|
|
// If both GBC and SGB palettes are defined,
|
|
// use whichever is more colourful
|
|
if (gbc_bios_palette)
|
|
{
|
|
isGbcPalette = true;
|
|
if (sgb_bios_palette)
|
|
{
|
|
if (gbc_bios_palette != p005 &&
|
|
gbc_bios_palette != p006 &&
|
|
gbc_bios_palette != p007 &&
|
|
gbc_bios_palette != p008 &&
|
|
gbc_bios_palette != p012 &&
|
|
gbc_bios_palette != p013 &&
|
|
gbc_bios_palette != p016 &&
|
|
gbc_bios_palette != p017 &&
|
|
gbc_bios_palette != p01B)
|
|
{
|
|
// Limited color GBC palette -> use SGB equivalent
|
|
gbc_bios_palette = sgb_bios_palette;
|
|
isGbcPalette = false;
|
|
}
|
|
}
|
|
}
|
|
// If no GBC palette is defined, use SGB palette
|
|
if (!gbc_bios_palette)
|
|
{
|
|
gbc_bios_palette = sgb_bios_palette;
|
|
}
|
|
// If neither GBC nor SGB palettes are defined, set
|
|
// user-defined internal palette
|
|
if (!gbc_bios_palette)
|
|
{
|
|
find_internal_palette(&gbc_bios_palette, &isGbcPalette);
|
|
}
|
|
break;
|
|
case GB_COLORIZATION_CUSTOM:
|
|
load_custom_palette();
|
|
break;
|
|
case GB_COLORIZATION_INTERNAL:
|
|
find_internal_palette(&gbc_bios_palette, &isGbcPalette);
|
|
break;
|
|
case GB_COLORIZATION_GBC:
|
|
// Force GBC colourisation
|
|
gbc_bios_palette = findGbcTitlePal(internal_game_name);
|
|
if (!gbc_bios_palette)
|
|
{
|
|
gbc_bios_palette = findGbcDirPal("GBC - Dark Green"); // GBC Default
|
|
}
|
|
isGbcPalette = true;
|
|
break;
|
|
case GB_COLORIZATION_SGB:
|
|
// Force SGB colourisation
|
|
gbc_bios_palette = findSgbTitlePal(internal_game_name);
|
|
if (!gbc_bios_palette)
|
|
{
|
|
gbc_bios_palette = findGbcDirPal("SGB - 1A"); // SGB Default
|
|
}
|
|
break;
|
|
default: // GB_COLORIZATION_DISABLED
|
|
gbc_bios_palette = findGbcDirPal("GBC - Grayscale");
|
|
break;
|
|
}
|
|
|
|
// Enable colour correction, if required
|
|
gb.setColorCorrection((colorCorrection == 2) || ((colorCorrection == 1) && isGbcPalette));
|
|
|
|
// If gambatte is using custom colourisation
|
|
// then we have already loaded the palette.
|
|
// In this case we can therefore skip this loop.
|
|
if (gb_colorization_enable != GB_COLORIZATION_CUSTOM)
|
|
{
|
|
unsigned rgb32 = 0;
|
|
for (unsigned palnum = 0; palnum < 3; ++palnum)
|
|
{
|
|
for (unsigned colornum = 0; colornum < 4; ++colornum)
|
|
{
|
|
rgb32 = gb.gbcToRgb32(gbc_bios_palette[palnum * 4 + colornum]);
|
|
gb.setDmgPaletteColor(palnum, colornum, rgb32);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static unsigned pow2ceil(unsigned n) {
|
|
--n;
|
|
n |= n >> 1;
|
|
n |= n >> 2;
|
|
n |= n >> 4;
|
|
n |= n >> 8;
|
|
++n;
|
|
|
|
return n;
|
|
}
|
|
|
|
bool retro_load_game(const struct retro_game_info *info)
|
|
{
|
|
bool can_dupe = false;
|
|
environ_cb(RETRO_ENVIRONMENT_GET_CAN_DUPE, &can_dupe);
|
|
if (!can_dupe)
|
|
{
|
|
gambatte_log(RETRO_LOG_ERROR, "Cannot dupe frames!\n");
|
|
return false;
|
|
}
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble))
|
|
gambatte_log(RETRO_LOG_INFO, "Rumble environment supported.\n");
|
|
else
|
|
gambatte_log(RETRO_LOG_INFO, "Rumble environment not supported.\n");
|
|
|
|
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_Y, "Turbo B" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
|
{ 0 },
|
|
};
|
|
|
|
struct retro_input_descriptor desc_ff[] = { /* ff: fast forward */
|
|
{ 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_Y, "Turbo B" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
{ 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, "Fast Forward" },
|
|
{ 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_Y, "Turbo B" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
{ 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_L, "Prev. Internal Palette" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "Next Internal Palette" },
|
|
{ 0 },
|
|
};
|
|
|
|
struct retro_input_descriptor desc_ff_ps[] = { /* ff: fast forward, 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_Y, "Turbo B" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
{ 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_L, "Prev. Internal Palette" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "Next Internal Palette" },
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "Fast Forward" },
|
|
{ 0 },
|
|
};
|
|
|
|
if (libretro_supports_ff_override)
|
|
{
|
|
if (libretro_supports_set_variable)
|
|
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc_ff_ps);
|
|
else
|
|
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc_ff);
|
|
}
|
|
else
|
|
{
|
|
if (libretro_supports_set_variable)
|
|
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc_ps);
|
|
else
|
|
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
|
|
}
|
|
|
|
#if defined(VIDEO_RGB565) || defined(VIDEO_ABGR1555)
|
|
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_RGB565;
|
|
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
|
|
{
|
|
gambatte_log(RETRO_LOG_ERROR, "RGB565 is not supported.\n");
|
|
return false;
|
|
}
|
|
#else
|
|
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
|
|
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
|
|
{
|
|
gambatte_log(RETRO_LOG_ERROR, "XRGB8888 is not supported.\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool has_gbc_bootloader = file_present_in_system("gbc_bios.bin");
|
|
|
|
unsigned flags = 0;
|
|
struct retro_variable var = {0};
|
|
var.key = "gambatte_gb_hwmode";
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "GB"))
|
|
{
|
|
flags |= gambatte::GB::FORCE_DMG;
|
|
}
|
|
|
|
if (!strcmp(var.value, "GBC"))
|
|
{
|
|
if (has_gbc_bootloader && use_official_bootloader)
|
|
flags |= gambatte::GB::FORCE_CGB;
|
|
}
|
|
|
|
if (!strcmp(var.value, "GBA"))
|
|
{
|
|
flags |= gambatte::GB::GBA_CGB;
|
|
if (has_gbc_bootloader && use_official_bootloader)
|
|
flags |= gambatte::GB::FORCE_CGB;
|
|
}
|
|
}
|
|
|
|
if (gb.load(info->data, info->size, flags) != 0)
|
|
return false;
|
|
#ifdef DUAL_MODE
|
|
if (gb2.load(info->data, info->size, flags) != 0)
|
|
return false;
|
|
#endif
|
|
|
|
rom_path = info->path ? info->path : "";
|
|
strncpy(internal_game_name, (const char*)info->data + 0x134, sizeof(internal_game_name) - 1);
|
|
internal_game_name[sizeof(internal_game_name)-1]='\0';
|
|
|
|
gambatte_log(RETRO_LOG_INFO, "Got internal game name: %s.\n", internal_game_name);
|
|
|
|
check_variables(true);
|
|
audio_resampler_init(true);
|
|
|
|
unsigned sramlen = gb.savedata_size();
|
|
const uint64_t rom = RETRO_MEMDESC_CONST;
|
|
const uint64_t mainram = RETRO_MEMDESC_SYSTEM_RAM;
|
|
struct retro_memory_map mmaps;
|
|
|
|
struct retro_memory_descriptor descs[10] =
|
|
{
|
|
{ mainram, gb.rambank0_ptr(), 0, 0xC000, 0, 0, 0x1000, NULL },
|
|
{ mainram, gb.rambank1_ptr(), 0, 0xD000, 0, 0, 0x1000, NULL },
|
|
{ mainram, gb.zeropage_ptr(), 0, 0xFF80, 0, 0, 0x0080, NULL },
|
|
{ 0, gb.vram_ptr(), 0, 0x8000, 0, 0, 0x2000, NULL },
|
|
{ 0, gb.oamram_ptr(), 0, 0xFE00, 0xFFFFFFE0, 0, 0x00A0, NULL },
|
|
{ rom, gb.rombank0_ptr(), 0, 0x0000, 0, 0, 0x4000, NULL },
|
|
{ rom, gb.rombank1_ptr(), 0, 0x4000, 0, 0, 0x4000, NULL },
|
|
{ 0, gb.oamram_ptr(), 0x100, 0xFF00, 0, 0, 0x0080, NULL },
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
|
};
|
|
|
|
unsigned i = 8;
|
|
if (sramlen)
|
|
{
|
|
descs[i].ptr = gb.savedata_ptr();
|
|
descs[i].start = 0xA000;
|
|
descs[i].select = (size_t)~0x1FFF;
|
|
descs[i].len = sramlen;
|
|
i++;
|
|
}
|
|
|
|
if (gb.isCgb())
|
|
{
|
|
descs[i].flags = mainram;
|
|
descs[i].ptr = gb.rambank2_ptr();
|
|
descs[i].start = 0x10000;
|
|
descs[i].select = 0xFFFFA000;
|
|
descs[i].len = 0x6000;
|
|
i++;
|
|
}
|
|
|
|
mmaps.descriptors = descs;
|
|
mmaps.num_descriptors = i;
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
|
|
|
|
bool yes = true;
|
|
environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes);
|
|
|
|
rom_loaded = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool retro_load_game_special(unsigned, const struct retro_game_info*, size_t) { return false; }
|
|
|
|
void retro_unload_game()
|
|
{
|
|
rom_loaded = false;
|
|
}
|
|
|
|
unsigned retro_get_region() { return RETRO_REGION_NTSC; }
|
|
|
|
void *retro_get_memory_data(unsigned id)
|
|
{
|
|
if (rom_loaded) switch (id)
|
|
{
|
|
case RETRO_MEMORY_SAVE_RAM:
|
|
return gb.savedata_ptr();
|
|
case RETRO_MEMORY_RTC:
|
|
return gb.rtcdata_ptr();
|
|
case RETRO_MEMORY_SYSTEM_RAM:
|
|
/* Really ugly hack here, relies upon
|
|
* libgambatte/src/memory/memptrs.cpp MemPtrs::reset not
|
|
* realizing that that memchunk hack is ugly, or
|
|
* otherwise getting rearranged. */
|
|
return gb.rambank0_ptr();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t retro_get_memory_size(unsigned id)
|
|
{
|
|
if (rom_loaded) switch (id)
|
|
{
|
|
case RETRO_MEMORY_SAVE_RAM:
|
|
return gb.savedata_size();
|
|
case RETRO_MEMORY_RTC:
|
|
return gb.rtcdata_size();
|
|
case RETRO_MEMORY_SYSTEM_RAM:
|
|
/* This is rather hacky too... it relies upon
|
|
* libgambatte/src/memory/cartridge.cpp not changing
|
|
* the call to memptrs.reset, but this is
|
|
* probably mostly safe.
|
|
*
|
|
* GBC will probably not get a
|
|
* hardware upgrade anytime soon. */
|
|
return (gb.isCgb() ? 8 : 2) * 0x1000ul;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void retro_run()
|
|
{
|
|
static uint64_t samples_count = 0;
|
|
static uint64_t frames_count = 0;
|
|
|
|
input_poll_cb();
|
|
update_input_state();
|
|
|
|
uint64_t expected_frames = samples_count / SOUND_SAMPLES_PER_FRAME;
|
|
if (frames_count < expected_frames) // Detect frame dupes.
|
|
{
|
|
video_cb(NULL, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_PITCH * sizeof(gambatte::video_pixel_t));
|
|
frames_count++;
|
|
return;
|
|
}
|
|
|
|
union
|
|
{
|
|
gambatte::uint_least32_t u32[SOUND_BUFF_SIZE];
|
|
int16_t i16[2 * SOUND_BUFF_SIZE];
|
|
} static sound_buf;
|
|
unsigned samples = SOUND_SAMPLES_PER_RUN;
|
|
|
|
while (gb.runFor(video_buf, VIDEO_PITCH, sound_buf.u32, SOUND_BUFF_SIZE, samples) == -1)
|
|
{
|
|
if (use_cc_resampler)
|
|
CC_renderaudio((audio_frame_t*)sound_buf.u32, samples);
|
|
else
|
|
{
|
|
blipper_renderaudio(sound_buf.i16, samples);
|
|
|
|
unsigned read_avail = blipper_read_avail(resampler_l);
|
|
if (read_avail >= (BLIP_BUFFER_SIZE >> 1))
|
|
audio_out_buffer_read_blipper(read_avail);
|
|
}
|
|
|
|
samples_count += samples;
|
|
samples = SOUND_SAMPLES_PER_RUN;
|
|
}
|
|
#ifdef DUAL_MODE
|
|
while (gb2.runFor(video_buf + GB_SCREEN_WIDTH, VIDEO_PITCH, sound_buf.u32, samples) == -1) {}
|
|
#endif
|
|
|
|
/* Perform interframe blending, if required */
|
|
if (blend_frames)
|
|
blend_frames();
|
|
|
|
video_cb(video_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_PITCH * sizeof(gambatte::video_pixel_t));
|
|
|
|
if (use_cc_resampler)
|
|
CC_renderaudio((audio_frame_t*)sound_buf.u32, samples);
|
|
else
|
|
{
|
|
blipper_renderaudio(sound_buf.i16, samples);
|
|
|
|
unsigned read_avail = blipper_read_avail(resampler_l);
|
|
audio_out_buffer_read_blipper(read_avail);
|
|
}
|
|
samples_count += samples;
|
|
audio_upload_samples();
|
|
|
|
/* Apply any 'pending' rumble effects */
|
|
if (rumble_active)
|
|
apply_rumble();
|
|
|
|
frames_count++;
|
|
|
|
bool updated = false;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
|
|
check_variables(false);
|
|
}
|
|
|
|
unsigned retro_api_version() { return RETRO_API_VERSION; }
|
|
|