392 lines
15 KiB
C++
392 lines
15 KiB
C++
#include "bizhawk.hpp"
|
|
#include <stdlib.h>
|
|
#include <string>
|
|
#include <libretro.h>
|
|
#include <lr_input.h>
|
|
#include <opera_vdlp.h>
|
|
#include <opera_mem.h>
|
|
#include <opera_cdrom.h>
|
|
#include <opera_xbus.h>
|
|
|
|
std::string _biosFilePath;
|
|
std::string _gameFilePath;
|
|
std::string _fontFilePath;
|
|
int _port1Type;
|
|
int _port2Type;
|
|
controllerData_t _port1Value;
|
|
controllerData_t _port2Value;
|
|
uint32_t* _videoBuffer;
|
|
size_t _videoHeight;
|
|
size_t _videoWidth;
|
|
size_t _videoPitch;
|
|
int _region;
|
|
int _nvramChanged;
|
|
int _inputPortsRead;
|
|
|
|
#define _MAX_SAMPLES 4096
|
|
#define _CHANNEL_COUNT 2
|
|
int16_t _audioBuffer[_MAX_SAMPLES * _CHANNEL_COUNT];
|
|
size_t _audioSamples;
|
|
|
|
extern "C"
|
|
{
|
|
void* xbus_cdrom_plugin(int proc_, void* data_);
|
|
void opera_cdrom_set_callbacks(opera_cdrom_get_size_cb_t get_size_, opera_cdrom_set_sector_cb_t set_sector_, opera_cdrom_read_sector_cb_t read_sector_);
|
|
void opera_nvram_init(void *buf, const int bufsize);
|
|
void opera_lr_callbacks_set_audio_sample(retro_audio_sample_t cb);
|
|
void opera_lr_callbacks_set_audio_sample_batch(retro_audio_sample_batch_t cb);
|
|
void opera_lr_callbacks_set_environment(retro_environment_t cb);
|
|
void opera_lr_callbacks_set_input_poll(retro_input_poll_t cb);
|
|
void opera_lr_callbacks_set_input_state(retro_input_state_t cb);
|
|
void opera_lr_callbacks_set_log_printf(retro_log_printf_t cb);
|
|
void opera_lr_callbacks_set_video_refresh(retro_video_refresh_t cb);
|
|
RETRO_API void *retro_get_memory_data(unsigned id);
|
|
RETRO_API size_t retro_get_memory_size(unsigned id);
|
|
void retro_set_controller_port_device(unsigned port_, unsigned device_);
|
|
void retro_get_system_av_info(struct retro_system_av_info *info_);
|
|
}
|
|
|
|
void RETRO_CALLCONV retro_video_refresh_callback(const void *data, unsigned width, unsigned height, size_t pitch)
|
|
{
|
|
// printf("Video %p, w: %u, h: %u, p: %lu\n", data, width, height, pitch);
|
|
_videoBuffer = (uint32_t*)data;
|
|
_videoWidth = width;
|
|
_videoHeight = height;
|
|
_videoPitch = pitch;
|
|
}
|
|
|
|
void RETRO_CALLCONV retro_log_printf_callback(enum retro_log_level level, const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
printf(format, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
size_t RETRO_CALLCONV retro_audio_sample_batch_callback(const int16_t *data, size_t frames)
|
|
{
|
|
memcpy(_audioBuffer, data, sizeof(int16_t) * frames * _CHANNEL_COUNT);
|
|
_audioSamples = frames;
|
|
return frames;
|
|
}
|
|
|
|
void RETRO_CALLCONV retro_input_poll_callback()
|
|
{
|
|
// printf("Libretro Input Poll Callback Called:\n");
|
|
}
|
|
|
|
int16_t processController(const int portType, controllerData_t& portValue, const unsigned device, const unsigned index, const unsigned id)
|
|
{
|
|
switch (portType)
|
|
{
|
|
case RETRO_DEVICE_JOYPAD:
|
|
switch (id)
|
|
{
|
|
case RETRO_DEVICE_ID_JOYPAD_UP: return portValue.gamePad.up;
|
|
case RETRO_DEVICE_ID_JOYPAD_DOWN: return portValue.gamePad.down;
|
|
case RETRO_DEVICE_ID_JOYPAD_LEFT: return portValue.gamePad.left;
|
|
case RETRO_DEVICE_ID_JOYPAD_RIGHT: return portValue.gamePad.right;
|
|
case RETRO_DEVICE_ID_JOYPAD_L: return portValue.gamePad.buttonL;
|
|
case RETRO_DEVICE_ID_JOYPAD_R: return portValue.gamePad.buttonR;
|
|
case RETRO_DEVICE_ID_JOYPAD_SELECT: return portValue.gamePad.buttonX;
|
|
case RETRO_DEVICE_ID_JOYPAD_START: return portValue.gamePad.buttonP;
|
|
case RETRO_DEVICE_ID_JOYPAD_Y: return portValue.gamePad.buttonA;
|
|
case RETRO_DEVICE_ID_JOYPAD_B: return portValue.gamePad.buttonB;
|
|
case RETRO_DEVICE_ID_JOYPAD_A: return portValue.gamePad.buttonC;
|
|
default: return 0;
|
|
}
|
|
|
|
case RETRO_DEVICE_MOUSE:
|
|
switch (id)
|
|
{
|
|
case RETRO_DEVICE_ID_MOUSE_X: return portValue.mouse.dX;
|
|
case RETRO_DEVICE_ID_MOUSE_Y: return portValue.mouse.dY;
|
|
case RETRO_DEVICE_ID_MOUSE_LEFT: return portValue.mouse.leftButton;
|
|
case RETRO_DEVICE_ID_MOUSE_MIDDLE: return portValue.mouse.middleButton;
|
|
case RETRO_DEVICE_ID_MOUSE_RIGHT: return portValue.mouse.rightButton;
|
|
case RETRO_DEVICE_ID_MOUSE_BUTTON_4: return portValue.mouse.fourthButton;
|
|
default: return 0;
|
|
}
|
|
|
|
case RETRO_DEVICE_FLIGHTSTICK:
|
|
if (index == RETRO_DEVICE_INDEX_ANALOG_BUTTON)
|
|
{
|
|
switch (id)
|
|
{
|
|
case RETRO_DEVICE_ID_JOYPAD_R2: return portValue.flightStick.fire;
|
|
case RETRO_DEVICE_ID_JOYPAD_Y: return portValue.flightStick.buttonA;
|
|
case RETRO_DEVICE_ID_JOYPAD_B: return portValue.flightStick.buttonB;
|
|
case RETRO_DEVICE_ID_JOYPAD_A: return portValue.flightStick.buttonC;
|
|
case RETRO_DEVICE_ID_JOYPAD_UP: return portValue.flightStick.up;
|
|
case RETRO_DEVICE_ID_JOYPAD_DOWN: return portValue.flightStick.down;
|
|
case RETRO_DEVICE_ID_JOYPAD_LEFT: return portValue.flightStick.left;
|
|
case RETRO_DEVICE_ID_JOYPAD_RIGHT: return portValue.flightStick.right;
|
|
case RETRO_DEVICE_ID_JOYPAD_START: return portValue.flightStick.buttonP;
|
|
case RETRO_DEVICE_ID_JOYPAD_SELECT: return portValue.flightStick.buttonX;
|
|
case RETRO_DEVICE_ID_JOYPAD_L: return portValue.flightStick.leftTrigger;
|
|
case RETRO_DEVICE_ID_JOYPAD_R: return portValue.flightStick.rightTrigger;
|
|
default: return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (id)
|
|
{
|
|
case RETRO_DEVICE_ID_ANALOG_X:
|
|
if (index == RETRO_DEVICE_INDEX_ANALOG_LEFT) return portValue.flightStick.horizontalAxis;
|
|
if (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT) return portValue.flightStick.altitudeAxis;
|
|
return 0;
|
|
|
|
case RETRO_DEVICE_ID_ANALOG_Y:
|
|
if (index == RETRO_DEVICE_INDEX_ANALOG_LEFT) return portValue.flightStick.verticalAxis;
|
|
if (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT) return portValue.flightStick.altitudeAxis;
|
|
return 0;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
case RETRO_DEVICE_LIGHTGUN:
|
|
switch (id)
|
|
{
|
|
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X: return portValue.lightGun.screenX;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y: return portValue.lightGun.screenY;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_TRIGGER: return portValue.lightGun.trigger;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_SELECT: return portValue.lightGun.select;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_RELOAD: return portValue.lightGun.reload;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN: return portValue.lightGun.isOffScreen;
|
|
default: return 0;
|
|
}
|
|
|
|
case RETRO_DEVICE_ARCADE_LIGHTGUN:
|
|
switch (id)
|
|
{
|
|
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X: return portValue.arcadeLightGun.screenX;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y: return portValue.arcadeLightGun.screenY;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_TRIGGER: return portValue.arcadeLightGun.trigger;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_SELECT: return portValue.arcadeLightGun.select;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_START: return portValue.arcadeLightGun.start;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_RELOAD: return portValue.arcadeLightGun.reload;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_AUX_A: return portValue.arcadeLightGun.auxA;
|
|
case RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN: return portValue.arcadeLightGun.isOffScreen;
|
|
default: return 0;
|
|
}
|
|
|
|
case RETRO_DEVICE_ORBATAK_TRACKBALL:
|
|
switch (id)
|
|
{
|
|
case RETRO_DEVICE_ID_ANALOG_X:
|
|
if (index == RETRO_DEVICE_INDEX_ANALOG_LEFT) return portValue.orbatakTrackball.dX;
|
|
if (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT) return portValue.orbatakTrackball.dX;
|
|
return 0;
|
|
|
|
case RETRO_DEVICE_ID_ANALOG_Y:
|
|
if (index == RETRO_DEVICE_INDEX_ANALOG_LEFT) return portValue.orbatakTrackball.dY;
|
|
if (index == RETRO_DEVICE_INDEX_ANALOG_RIGHT) return portValue.orbatakTrackball.dY;
|
|
return 0;
|
|
|
|
case RETRO_DEVICE_ID_JOYPAD_SELECT: return portValue.orbatakTrackball.startP1;
|
|
case RETRO_DEVICE_ID_JOYPAD_START: return portValue.orbatakTrackball.startP2;
|
|
case RETRO_DEVICE_ID_JOYPAD_L: return portValue.orbatakTrackball.coinP1;
|
|
case RETRO_DEVICE_ID_JOYPAD_R: return portValue.orbatakTrackball.coinP2;
|
|
case RETRO_DEVICE_ID_JOYPAD_R2: return portValue.orbatakTrackball.service;
|
|
default: return 0;
|
|
}
|
|
|
|
default: return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int16_t RETRO_CALLCONV retro_input_state_callback(unsigned port, unsigned device, unsigned index, unsigned id)
|
|
{
|
|
// printf("Libretro Input State Callback Called. Port: %u, Device: %u, Index: %u, Id: %u\n", port, device, index, id);
|
|
if (port == 0) return processController(_port1Type, _port1Value, device, index, id);
|
|
if (port == 1) return processController(_port2Type, _port2Value, device, index, id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
char _deviceCountOption[256];
|
|
void configHandler(struct retro_variable *var)
|
|
{
|
|
printf("Variable Name: %s / Value: %s\n", var->key, var->value);
|
|
|
|
std::string key(var->key);
|
|
|
|
if (key == "opera_bios" && _biosFilePath != "None") var->value = _biosFilePath.c_str();
|
|
if (key == "opera_font" && _fontFilePath != "None") var->value = _fontFilePath.c_str();
|
|
if (key == "opera_region")
|
|
{
|
|
if (_region == 0) var->value = "ntsc";
|
|
if (_region == 1) var->value = "pal1";
|
|
if (_region == 2) var->value = "pal2";
|
|
}
|
|
if (key == "opera_active_devices")
|
|
{
|
|
int deviceCount = 0;
|
|
if (_port1Type != RETRO_DEVICE_NONE) deviceCount++;
|
|
if (_port2Type != RETRO_DEVICE_NONE) deviceCount++;
|
|
sprintf(_deviceCountOption, "%d", deviceCount);
|
|
var->value = _deviceCountOption;
|
|
}
|
|
}
|
|
|
|
const char* systemPath = ".";
|
|
bool RETRO_CALLCONV retro_environment_callback(unsigned cmd, void *data)
|
|
{
|
|
// printf("Libretro Environment Callback Called: %u\n", cmd);
|
|
|
|
if (cmd == RETRO_ENVIRONMENT_GET_LOG_INTERFACE) { *((retro_log_printf_t*)data) = retro_log_printf_callback; return true; }
|
|
if (cmd == RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL) { return true; }
|
|
if (cmd == RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS) { return true; }
|
|
if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE) { configHandler((struct retro_variable *)data); return true; }
|
|
if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE) { return true; }
|
|
if (cmd == RETRO_ENVIRONMENT_SET_PIXEL_FORMAT) { *((vdlp_pixel_format_e*) data) = VDLP_PIXEL_FORMAT_XRGB8888; return true; }
|
|
if (cmd == RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY) { *((const char**)data) = systemPath; return true; }
|
|
if (cmd == RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY) { *((const char**)data) = systemPath; return true; }
|
|
|
|
fprintf(stderr, "Unrecognized environment callback command: %u\n", cmd);
|
|
|
|
return false;
|
|
}
|
|
|
|
/// CD Management Logic Start
|
|
#define CDIMAGE_SECTOR_SIZE 2048
|
|
uint32_t _currentSector = 0;
|
|
void (*cd_read_callback)(int32_t lba, void * dest);
|
|
int (*cd_sector_count_callback)();
|
|
ECL_EXPORT void SetCdCallbacks(void (*cdrc)(int32_t lba, void * dest), int (*cdscc)())
|
|
{
|
|
cd_read_callback = cdrc;
|
|
cd_sector_count_callback = cdscc;
|
|
}
|
|
|
|
uint32_t cd_get_size(void) { return cd_sector_count_callback(); }
|
|
void cd_set_sector(const uint32_t sector_) { _currentSector = sector_; }
|
|
void cd_read_sector(void *buf_) { cd_read_callback(_currentSector, buf_); }
|
|
/// CD Management Logic End
|
|
|
|
// SRAM management start
|
|
bool _sram_changed = false;
|
|
ECL_EXPORT bool sram_changed() { return _nvramChanged; }
|
|
ECL_EXPORT int get_sram_size() { return NVRAM_SIZE; }
|
|
ECL_EXPORT uint8_t* get_sram_buffer() { return (uint8_t*) NVRAM; }
|
|
ECL_EXPORT void get_sram(uint8_t* sramBuffer)
|
|
{
|
|
if (NVRAM == NULL) return;
|
|
memcpy(sramBuffer, get_sram_buffer(), get_sram_size());
|
|
}
|
|
|
|
ECL_EXPORT void set_sram(uint8_t* sramBuffer)
|
|
{
|
|
if (NVRAM == NULL) opera_nvram_init(NVRAM,NVRAM_SIZE);
|
|
memcpy(get_sram_buffer(), sramBuffer, get_sram_size());
|
|
}
|
|
// SRAM Management end
|
|
|
|
ECL_EXPORT bool Init(const char* gameFilePath, const char* biosFilePath, const char* fontFilePath, int port1Type, int port2Type, int region)
|
|
{
|
|
_gameFilePath = gameFilePath;
|
|
_biosFilePath = biosFilePath;
|
|
_fontFilePath = fontFilePath;
|
|
_port1Type = port1Type;
|
|
_port2Type = port2Type;
|
|
_region = region;
|
|
|
|
opera_lr_callbacks_set_environment(retro_environment_callback);
|
|
opera_lr_callbacks_set_input_state(retro_input_state_callback);
|
|
opera_lr_callbacks_set_input_poll(retro_input_poll_callback);
|
|
opera_lr_callbacks_set_audio_sample_batch(retro_audio_sample_batch_callback);
|
|
opera_lr_callbacks_set_video_refresh(retro_video_refresh_callback);
|
|
|
|
retro_set_controller_port_device(0, port1Type);
|
|
retro_set_controller_port_device(1, port2Type);
|
|
retro_init();
|
|
|
|
// Setting cd callbacks
|
|
opera_cdrom_set_callbacks(cd_get_size, cd_set_sector, cd_read_sector);
|
|
|
|
// Loading game file
|
|
struct retro_game_info game;
|
|
game.path = _gameFilePath.c_str();
|
|
auto loadResult = retro_load_game(&game);
|
|
if (loadResult == false) { fprintf(stderr, "Could not load game: '%s'\n", _gameFilePath.c_str()); return false; }
|
|
|
|
// Getting av info
|
|
struct retro_system_av_info info;
|
|
retro_get_system_av_info(&info);
|
|
printf("3DO Framerate: %f\n", info.timing.fps);
|
|
|
|
return true;
|
|
}
|
|
|
|
ECL_EXPORT void opera_get_video(int& w, int& h, int& pitch, uint8_t*& buffer)
|
|
{
|
|
buffer = (uint8_t*)_videoBuffer;
|
|
w = _videoWidth;
|
|
h = _videoHeight;
|
|
pitch = _videoPitch;
|
|
}
|
|
|
|
ECL_EXPORT void FrameAdvance(MyFrameInfo* f)
|
|
{
|
|
// Setting inputs
|
|
_port1Value = f->port1;
|
|
_port2Value = f->port2;
|
|
|
|
//printf("Mouse X%d(%d), Y%d(%d), L%d, M%d, B%d\n", _port1Value.mouse.posX, _port1Value.mouse.dX, _port1Value.mouse.posY, _port1Value.mouse.dY, _port1Value.mouse.leftButton, _port1Value.mouse.middleButton, _port1Value.mouse.rightButton);
|
|
//fflush(stdout);
|
|
|
|
// Checking for changes in NVRAM
|
|
_nvramChanged = false;
|
|
|
|
// Checking if ports have been read
|
|
_inputPortsRead = 0;
|
|
|
|
// If resetting, do it now. Otherwise, running a single frame
|
|
if (f->isReset == 1) retro_reset();
|
|
else retro_run();
|
|
|
|
// The frame is lagged if no inputs were read
|
|
f->base.Lagged = !_inputPortsRead;
|
|
|
|
// Setting video buffer
|
|
f->base.Width = _videoWidth;
|
|
f->base.Height = _videoHeight;
|
|
memcpy(f->base.VideoBuffer, _videoBuffer, sizeof(uint32_t) * _videoWidth * _videoHeight);
|
|
|
|
// Setting audio buffer
|
|
f->base.Samples = _audioSamples;
|
|
memcpy(f->base.SoundBuffer, _audioBuffer, _audioSamples * sizeof(int16_t) * _CHANNEL_COUNT);
|
|
}
|
|
|
|
ECL_EXPORT void GetMemoryAreas(MemoryArea *m)
|
|
{
|
|
int memAreaIdx = 0;
|
|
|
|
m[memAreaIdx].Data = retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM);
|
|
m[memAreaIdx].Name = "System RAM";
|
|
m[memAreaIdx].Size = retro_get_memory_size(RETRO_MEMORY_SYSTEM_RAM);
|
|
m[memAreaIdx].Flags = MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_PRIMARY;
|
|
memAreaIdx++;
|
|
|
|
m[memAreaIdx].Data = retro_get_memory_data(RETRO_MEMORY_VIDEO_RAM);
|
|
m[memAreaIdx].Name = "Video RAM";
|
|
m[memAreaIdx].Size = retro_get_memory_size(RETRO_MEMORY_VIDEO_RAM);
|
|
m[memAreaIdx].Flags = MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE;
|
|
memAreaIdx++;
|
|
|
|
m[memAreaIdx].Data = get_sram_buffer();
|
|
m[memAreaIdx].Name = "Non-volatile RAM";
|
|
m[memAreaIdx].Size = get_sram_size();
|
|
m[memAreaIdx].Flags = MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_SAVERAMMABLE;
|
|
memAreaIdx++;
|
|
}
|
|
|
|
void (*InputCallback)();
|
|
ECL_EXPORT void SetInputCallback(void (*callback)())
|
|
{
|
|
InputCallback = callback;
|
|
}
|