Libretro rewrite (#3212)
* redo all the libretro stuff in pure C# also yes i imported snprintf lol * also remove these * remove clib hack, it doesn't work when there's more than 1 param (apparently ... is really just a variable num of args passed on the stack and not some pointer to some struct, and c# doesn't have any way of representing this. fallback log isn't bad to deal with anyways) * also probably want this to return false * /s/UnmanagedString/PinnedString/ * misc tweaks * oops * Libretro "Bridge", with entirely different design. The bridge here simply handles all the callbacks retro cores use, so there is never a c++ -> .NET callback, and probably avoids any libco issues also a lot of cleanup in various areas, and some bug fixes too (Blit555 now outputs the correct colors) * use blipbuffer instead of speex speex seems to perform much worse often (and outright buggy for some libretro cores like sameboy which reports a sample rate of 384000), and blip works well enough even for "newer" systems * cleanup and linux build * cleanup and bugfixes (thanks uninitalized variables) * oops * misc and cleanup * fix load no game, also handle load game failures correctly
This commit is contained in:
parent
635fff6c5d
commit
25fb816988
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,692 @@
|
|||
#include "LibretroBridge.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LibretroBridge {
|
||||
|
||||
class CallbackHandler;
|
||||
|
||||
class CallbackHandler {
|
||||
public:
|
||||
CallbackHandler()
|
||||
: supportsNoGame(false)
|
||||
, retroMessageString()
|
||||
, retroMessageTime(0)
|
||||
, variablesDirty(false)
|
||||
, variableCount(0)
|
||||
, variableKeys()
|
||||
, variableComments()
|
||||
, variables()
|
||||
, systemDirectory()
|
||||
, saveDirectory()
|
||||
, coreDirectory()
|
||||
, coreAssetsDirectory()
|
||||
, rotation(0)
|
||||
, pixelFormat(RETRO_PIXEL_FORMAT::ZRGB1555)
|
||||
, width(0)
|
||||
, height(0)
|
||||
, videoBuf()
|
||||
, videoBufSz(0)
|
||||
, numSamples(0)
|
||||
, sampleBuf()
|
||||
{
|
||||
std::memset(joypads[0], 0, sizeof (joypads[0]));
|
||||
std::memset(joypads[1], 0, sizeof (joypads[1]));
|
||||
std::memset(mouse, 0, sizeof (mouse));
|
||||
std::memset(keyboard, 0, sizeof (keyboard));
|
||||
std::memset(lightGun, 0, sizeof (lightGun));
|
||||
std::memset(analog, 0, sizeof (analog));
|
||||
std::memset(pointer, 0, sizeof (pointer));
|
||||
std::memset(sensorAccelerometer, 0, sizeof (sensorAccelerometer));
|
||||
}
|
||||
|
||||
static void RetroLog(RETRO_LOG level, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
std::size_t sz = std::vsnprintf(NULL, 0, fmt, args);
|
||||
if (static_cast<s64>(sz) < 0) {
|
||||
std::puts("vsnprintf failed!");
|
||||
std::fflush(stdout);
|
||||
va_end(args);
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> msg(new char[sz + 1]);
|
||||
std::vsnprintf(msg.get(), sz + 1, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
std::string finalMsg;
|
||||
switch (level) {
|
||||
case RETRO_LOG::DEBUG:
|
||||
finalMsg = "[RETRO_LOG_DEBUG] ";
|
||||
break;
|
||||
case RETRO_LOG::INFO:
|
||||
finalMsg = "[RETRO_LOG_INFO] ";
|
||||
break;
|
||||
case RETRO_LOG::WARN:
|
||||
finalMsg = "[RETRO_LOG_WARN] ";
|
||||
break;
|
||||
case RETRO_LOG::ERROR:
|
||||
finalMsg = "[RETRO_LOG_ERROR] ";
|
||||
break;
|
||||
default:
|
||||
finalMsg = "[RETRO_LOG_UNKNOWN] ";
|
||||
break;
|
||||
}
|
||||
|
||||
finalMsg += std::string(msg.get());
|
||||
std::printf("%s", finalMsg.c_str());
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
boolean RetroEnvironment(u32 cmd, void* data) {
|
||||
switch (static_cast<RETRO_ENVIRONMENT>(cmd)) {
|
||||
case RETRO_ENVIRONMENT::SET_ROTATION:
|
||||
rotation = *static_cast<const u32*>(const_cast<const void*>(data));
|
||||
assert(rotation < 4);
|
||||
rotation *= 90;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::GET_OVERSCAN:
|
||||
*static_cast<boolean*>(data) = false;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::GET_CAN_DUPE:
|
||||
*static_cast<boolean*>(data) = true;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::SET_MESSAGE:
|
||||
{
|
||||
const retro_message* message = static_cast<retro_message*>(data);
|
||||
retroMessageString = message->msg;
|
||||
retroMessageTime = message->frames;
|
||||
return true;
|
||||
}
|
||||
case RETRO_ENVIRONMENT::SHUTDOWN:
|
||||
//TODO low priority
|
||||
//maybe we can tell the frontend to stop frame advancing?
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::SET_PERFORMANCE_LEVEL:
|
||||
//unneeded
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::GET_SYSTEM_DIRECTORY:
|
||||
*static_cast<const char**>(data) = systemDirectory.c_str();
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::SET_PIXEL_FORMAT:
|
||||
{
|
||||
const u32 tmp = *static_cast<const u32*>(const_cast<const void*>(data));
|
||||
assert(tmp < 3);
|
||||
pixelFormat = static_cast<RETRO_PIXEL_FORMAT>(tmp);
|
||||
return true;
|
||||
}
|
||||
case RETRO_ENVIRONMENT::SET_INPUT_DESCRIPTORS:
|
||||
//TODO medium priority
|
||||
//would need some custom form displaying these probably
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::SET_KEYBOARD_CALLBACK:
|
||||
//TODO high priority (to support keyboard consoles, probably high value for us. but that may take a lot of infrastructure work)
|
||||
//the callback set is meant to be called from frontend side, so perhaps we should just call this on a SetInput call with keyboard
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::SET_DISK_CONTROL_INTERFACE:
|
||||
//TODO high priority (to support disc systems)
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::SET_HW_RENDER:
|
||||
//TODO high priority (to support 3d renderers)
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::GET_VARIABLE:
|
||||
{
|
||||
//according to retroarch's `core_option_manager_get` this is what we should do
|
||||
|
||||
variablesDirty = false;
|
||||
|
||||
retro_variable* req = static_cast<retro_variable*>(data);
|
||||
req->value = nullptr;
|
||||
|
||||
for (u32 i = 0; i < variableCount; i++) {
|
||||
if (variableKeys[i] == std::string(req->key)) {
|
||||
req->value = variables[i].c_str();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT::SET_VARIABLES:
|
||||
{
|
||||
const retro_variable* var = static_cast<const retro_variable*>(data);
|
||||
u32 nVars = 0;
|
||||
while (var->key) {
|
||||
var++;
|
||||
nVars++;
|
||||
}
|
||||
|
||||
variableCount = nVars;
|
||||
variables.reset(new std::string[nVars]);
|
||||
variableKeys.reset(new std::string[nVars]);
|
||||
variableComments.reset(new std::string[nVars]);
|
||||
var = static_cast<const retro_variable*>(data);
|
||||
|
||||
for (u32 i = 0; i < nVars; i++) {
|
||||
variableKeys[i] = std::string(var[i].key);
|
||||
variableComments[i] = std::string(var[i].value);
|
||||
|
||||
//analyze to find default and save it
|
||||
std::string comment = variableComments[i];
|
||||
std::size_t ofs = comment.find_first_of(';') + 2;
|
||||
std::size_t pipe = comment.find('|', ofs);
|
||||
if (pipe == std::string::npos) {
|
||||
variables[i] = comment.substr(ofs);
|
||||
} else {
|
||||
variables[i] = comment.substr(ofs, pipe - ofs);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT::GET_VARIABLE_UPDATE:
|
||||
*static_cast<boolean*>(data) = variablesDirty;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::SET_SUPPORT_NO_GAME:
|
||||
supportsNoGame = *static_cast<const boolean*>(data);
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::GET_LIBRETRO_PATH:
|
||||
*static_cast<const char**>(data) = coreDirectory.c_str();
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::SET_AUDIO_CALLBACK:
|
||||
//seems to be meant for async sound?
|
||||
//the callback is meant to be called frontend side
|
||||
//so in practice this callback is useless
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::SET_FRAME_TIME_CALLBACK:
|
||||
//the frontend can send real time to the core
|
||||
//and the frontend is meant to tamper with
|
||||
//this timing for fast forward/slow motion?
|
||||
//just no, those are pure frontend responsibilities
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::GET_RUMBLE_INTERFACE:
|
||||
//TODO low priority
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::GET_INPUT_DEVICE_CAPABILITIES:
|
||||
//TODO medium priority - other input methods
|
||||
*static_cast<u64*>(data) = 1 << static_cast<u32>(RETRO_DEVICE::JOYPAD)
|
||||
| 1 << static_cast<u32>(RETRO_DEVICE::KEYBOARD)
|
||||
| 1 << static_cast<u32>(RETRO_DEVICE::POINTER);
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::GET_LOG_INTERFACE:
|
||||
{
|
||||
retro_log_callback* cb = static_cast<retro_log_callback*>(data);
|
||||
cb->log = &RetroLog;
|
||||
return true;
|
||||
}
|
||||
case RETRO_ENVIRONMENT::GET_PERF_INTERFACE:
|
||||
//callbacks for performance counters?
|
||||
//for performance logging???
|
||||
//just no
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::GET_LOCATION_INTERFACE:
|
||||
//the frontend is not a GPS
|
||||
//the core should not need this info
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::GET_CORE_ASSETS_DIRECTORY:
|
||||
*static_cast<const char**>(data) = coreAssetsDirectory.c_str();
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::GET_SAVE_DIRECTORY:
|
||||
*static_cast<const char**>(data) = saveDirectory.c_str();
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT::SET_SYSTEM_AV_INFO:
|
||||
RetroLog(RETRO_LOG::WARN, "NEED RETRO_ENVIRONMENT::SET_SYSTEM_AV_INFO");
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::SET_PROC_ADDRESS_CALLBACK:
|
||||
//this is some way to get symbols for API extensions
|
||||
//of which none exist
|
||||
//so this is useless
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::SET_SUBSYSTEM_INFO:
|
||||
//needs retro_load_game_special to be useful; not supported yet
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::SET_CONTROLLER_INFO:
|
||||
//TODO medium priority probably
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::SET_GEOMETRY:
|
||||
//TODO medium priority probably
|
||||
//this is essentially just set system av info, except only for video
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::GET_USERNAME:
|
||||
//we definitely want to return false here so the core will do something deterministic
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT::GET_LANGUAGE:
|
||||
*static_cast<RETRO_LANGUAGE*>(data) = RETRO_LANGUAGE::ENGLISH;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <u32 rot>
|
||||
static u32* PixelAddress(u32 width, u32 height, u32 x, u32 y, u32* dstBuf, u32* dst) {
|
||||
switch (rot) {
|
||||
case 0:
|
||||
return dst;
|
||||
case 90:
|
||||
{
|
||||
u32 dx = y;
|
||||
u32 dy = height - x - 1;
|
||||
return dstBuf + dy * width + dx;
|
||||
}
|
||||
case 180:
|
||||
{
|
||||
u32 dx = width - y - 1;
|
||||
u32 dy = height - x - 1;
|
||||
return dstBuf + dy * width + dx;
|
||||
}
|
||||
case 270:
|
||||
{
|
||||
u32 dx = width - y - 1;
|
||||
u32 dy = x;
|
||||
return dstBuf + dy * width + dx;
|
||||
}
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <u32 rot>
|
||||
static void Blit555(const u16* srcBuf, u32* dstBuf, u32 width, u32 height, std::size_t pitch) {
|
||||
u32* dst = dstBuf;
|
||||
for (u32 y = 0; y < height; y++) {
|
||||
const u16* row = srcBuf;
|
||||
for (u32 x = 0; x < width; x++) {
|
||||
u16 ci = *row;
|
||||
u32 r = (ci & 0x001F) >> 0;
|
||||
u32 g = (ci & 0x03E0) >> 5;
|
||||
u32 b = (ci & 0x7C00) >> 10;
|
||||
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g << 3) | (g >> 2);
|
||||
b = (b << 3) | (b >> 2);
|
||||
u32 co = r | (g << 8) | (b << 16) | 0xFF000000U;
|
||||
|
||||
*PixelAddress<rot>(width, height, x, y, dstBuf, dst) = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
srcBuf += pitch / 2;
|
||||
}
|
||||
}
|
||||
|
||||
template <u32 rot>
|
||||
static void Blit565(const u16* srcBuf, u32* dstBuf, u32 width, u32 height, std::size_t pitch) {
|
||||
u32* dst = dstBuf;
|
||||
for (u32 y = 0; y < height; y++) {
|
||||
const u16* row = srcBuf;
|
||||
for (u32 x = 0; x < width; x++) {
|
||||
u16 ci = *row;
|
||||
u32 r = (ci & 0x001F) >> 0;
|
||||
u32 g = (ci & 0x07E0) >> 5;
|
||||
u32 b = (ci & 0xF800) >> 11;
|
||||
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g << 2) | (g >> 4);
|
||||
b = (b << 3) | (b >> 2);
|
||||
u32 co = r | (g << 8) | (b << 16) | 0xFF000000U;
|
||||
|
||||
*PixelAddress<rot>(width, height, x, y, dstBuf, dst) = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
srcBuf += pitch / 2;
|
||||
}
|
||||
}
|
||||
|
||||
template <u32 rot>
|
||||
static void Blit888(const u32* srcBuf, u32* dstBuf, u32 width, u32 height, std::size_t pitch) {
|
||||
u32* dst = dstBuf;
|
||||
for (u32 y = 0; y < height; y++) {
|
||||
const u32* row = srcBuf;
|
||||
for (u32 x = 0; x < width; x++) {
|
||||
u32 ci = *row;
|
||||
u32 co = ci | 0xFF000000U;
|
||||
*PixelAddress<rot>(width, height, x, y, dstBuf, dst) = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
srcBuf += pitch / 4;
|
||||
}
|
||||
}
|
||||
|
||||
void RetroVideoRefresh(const void* data, u32 width, u32 height, std::size_t pitch) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert((width * height) <= videoBufSz);
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
|
||||
switch (pixelFormat) {
|
||||
case RETRO_PIXEL_FORMAT::ZRGB1555:
|
||||
switch (rotation) {
|
||||
case 0: return Blit555<0>(static_cast<const u16*>(data), videoBuf.get(), width, height, pitch);
|
||||
case 90: return Blit555<90>(static_cast<const u16*>(data), videoBuf.get(), width, height, pitch);
|
||||
case 180: return Blit555<180>(static_cast<const u16*>(data), videoBuf.get(), width, height, pitch);
|
||||
case 270: return Blit555<270>(static_cast<const u16*>(data), videoBuf.get(), width, height, pitch);
|
||||
default: __builtin_unreachable();
|
||||
}
|
||||
case RETRO_PIXEL_FORMAT::XRGB8888:
|
||||
switch (rotation) {
|
||||
case 0: return Blit888<0>(static_cast<const u32*>(data), videoBuf.get(), width, height, pitch);
|
||||
case 90: return Blit888<90>(static_cast<const u32*>(data), videoBuf.get(), width, height, pitch);
|
||||
case 180: return Blit888<180>(static_cast<const u32*>(data), videoBuf.get(), width, height, pitch);
|
||||
case 270: return Blit888<270>(static_cast<const u32*>(data), videoBuf.get(), width, height, pitch);
|
||||
default: __builtin_unreachable();
|
||||
}
|
||||
case RETRO_PIXEL_FORMAT::RGB565:
|
||||
switch (rotation) {
|
||||
case 0: return Blit565<0>(static_cast<const u16*>(data), videoBuf.get(), width, height, pitch);
|
||||
case 90: return Blit565<90>(static_cast<const u16*>(data), videoBuf.get(), width, height, pitch);
|
||||
case 180: return Blit565<180>(static_cast<const u16*>(data), videoBuf.get(), width, height, pitch);
|
||||
case 270: return Blit565<270>(static_cast<const u16*>(data), videoBuf.get(), width, height, pitch);
|
||||
default: __builtin_unreachable();
|
||||
}
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
void RetroAudioSample(s16 left, s16 right) {
|
||||
sampleBuf.push_back(left);
|
||||
sampleBuf.push_back(right);
|
||||
numSamples++;
|
||||
}
|
||||
|
||||
std::size_t RetroAudioSampleBatch(const s16* data, std::size_t frames) {
|
||||
const std::size_t ret = frames;
|
||||
while (frames--) {
|
||||
sampleBuf.push_back(*data++);
|
||||
sampleBuf.push_back(*data++);
|
||||
numSamples++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RetroInputPoll() {
|
||||
// this is useless
|
||||
}
|
||||
|
||||
s16 RetroInputState(u32 port, u32 device, __attribute__((unused)) u32 index, u32 id) {
|
||||
assert(device < static_cast<u32>(RETRO_DEVICE::LAST));
|
||||
switch (static_cast<RETRO_DEVICE>(device)) {
|
||||
case RETRO_DEVICE::NONE:
|
||||
return 0;
|
||||
case RETRO_DEVICE::JOYPAD:
|
||||
if (port < 2) {
|
||||
assert(id < sizeof (joypads[port]));
|
||||
return joypads[port][id];
|
||||
}
|
||||
return 0; // todo: is this valid?
|
||||
case RETRO_DEVICE::MOUSE:
|
||||
assert(id < sizeof (mouse));
|
||||
return mouse[id];
|
||||
case RETRO_DEVICE::KEYBOARD:
|
||||
assert(id < sizeof (keyboard));
|
||||
return keyboard[id];
|
||||
case RETRO_DEVICE::LIGHTGUN:
|
||||
assert(id < sizeof (lightGun));
|
||||
return lightGun[id];
|
||||
case RETRO_DEVICE::ANALOG:
|
||||
assert(id < sizeof (analog));
|
||||
return analog[id];
|
||||
case RETRO_DEVICE::POINTER:
|
||||
assert(id < sizeof (pointer));
|
||||
return pointer[id];
|
||||
case RETRO_DEVICE::SENSOR_ACCELEROMETER:
|
||||
assert(id < sizeof (sensorAccelerometer));
|
||||
return sensorAccelerometer[id];
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
bool GetSupportsNoGame() {
|
||||
return supportsNoGame;
|
||||
}
|
||||
|
||||
void GetRetroMessage(retro_message* m) {
|
||||
m->msg = retroMessageString.c_str();
|
||||
m->frames = retroMessageTime;
|
||||
|
||||
if (retroMessageTime > 0) {
|
||||
retroMessageTime--;
|
||||
}
|
||||
}
|
||||
|
||||
// need some way to communicate with retro variables later
|
||||
|
||||
void SetDirectories(const char* systemDirectory, const char* saveDirectory, const char* coreDirectory, const char* coreAssetsDirectory) {
|
||||
this->systemDirectory = systemDirectory;
|
||||
this->saveDirectory = saveDirectory;
|
||||
this->coreDirectory = coreDirectory;
|
||||
this->coreAssetsDirectory = coreAssetsDirectory;
|
||||
}
|
||||
|
||||
void SetVideoSize(u32 sz) {
|
||||
videoBuf.reset(new u32[sz]);
|
||||
videoBufSz = sz;
|
||||
}
|
||||
|
||||
void GetVideo(u32* width, u32* height, u32* videoBuf) {
|
||||
*width = this->width;
|
||||
*height = this->height;
|
||||
std::memcpy(videoBuf, this->videoBuf.get(), videoBufSz * sizeof (u32));
|
||||
}
|
||||
|
||||
u32 GetAudioSize() {
|
||||
return sampleBuf.size();
|
||||
}
|
||||
|
||||
void GetAudio(u32* numSamples, s16* sampleBuf) {
|
||||
*numSamples = this->numSamples;
|
||||
std::memcpy(sampleBuf, this->sampleBuf.data(), this->sampleBuf.size() * sizeof (s16));
|
||||
|
||||
this->numSamples = 0;
|
||||
this->sampleBuf.clear();
|
||||
}
|
||||
|
||||
void SetInput(RETRO_DEVICE device, u32 port, s16* input) {
|
||||
switch (device) {
|
||||
case RETRO_DEVICE::NONE:
|
||||
break;
|
||||
case RETRO_DEVICE::JOYPAD:
|
||||
assert(port < 2);
|
||||
std::memcpy(joypads[port], input, sizeof (joypads[port]));
|
||||
break;
|
||||
case RETRO_DEVICE::MOUSE:
|
||||
std::memcpy(mouse, input, sizeof (mouse));
|
||||
break;
|
||||
case RETRO_DEVICE::KEYBOARD:
|
||||
std::memcpy(keyboard, input, sizeof (keyboard));
|
||||
break;
|
||||
case RETRO_DEVICE::LIGHTGUN:
|
||||
std::memcpy(lightGun, input, sizeof (lightGun));
|
||||
break;
|
||||
case RETRO_DEVICE::ANALOG:
|
||||
std::memcpy(analog, input, sizeof (analog));
|
||||
break;
|
||||
case RETRO_DEVICE::POINTER:
|
||||
std::memcpy(pointer, input, sizeof (pointer));
|
||||
break;
|
||||
case RETRO_DEVICE::SENSOR_ACCELEROMETER:
|
||||
std::memcpy(sensorAccelerometer, input, sizeof (sensorAccelerometer));
|
||||
break;
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// environment vars
|
||||
bool supportsNoGame;
|
||||
|
||||
std::string retroMessageString;
|
||||
u32 retroMessageTime;
|
||||
|
||||
bool variablesDirty;
|
||||
u32 variableCount;
|
||||
std::unique_ptr<std::string[]> variableKeys;
|
||||
std::unique_ptr<std::string[]> variableComments;
|
||||
std::unique_ptr<std::string[]> variables;
|
||||
|
||||
std::string systemDirectory;
|
||||
std::string saveDirectory;
|
||||
std::string coreDirectory;
|
||||
std::string coreAssetsDirectory;
|
||||
|
||||
// video vars
|
||||
u32 rotation; // also an environ var
|
||||
RETRO_PIXEL_FORMAT pixelFormat; // also an environ var
|
||||
|
||||
u32 width;
|
||||
u32 height;
|
||||
std::unique_ptr<u32[]> videoBuf;
|
||||
u32 videoBufSz;
|
||||
|
||||
// audio vars
|
||||
u32 numSamples;
|
||||
std::vector<s16> sampleBuf;
|
||||
|
||||
// input vars
|
||||
s16 joypads[2][static_cast<u32>(RETRO_DEVICE_ID_JOYPAD::LAST)];
|
||||
s16 mouse[static_cast<u32>(RETRO_DEVICE_ID_MOUSE::LAST)];
|
||||
s16 keyboard[static_cast<u32>(RETRO_KEY::LAST)];
|
||||
s16 lightGun[static_cast<u32>(RETRO_DEVICE_ID_LIGHTGUN::LAST)];
|
||||
s16 analog[static_cast<u32>(RETRO_DEVICE_ID_ANALOG::LAST)];
|
||||
s16 pointer[static_cast<u32>(RETRO_DEVICE_ID_POINTER::LAST)];
|
||||
s16 sensorAccelerometer[static_cast<u32>(RETRO_DEVICE_ID_SENSOR_ACCELEROMETER::LAST)];
|
||||
};
|
||||
|
||||
static CallbackHandler * gCbHandler = nullptr;
|
||||
|
||||
// make a callback handler
|
||||
EXPORT CallbackHandler * LibretroBridge_CreateCallbackHandler() {
|
||||
return new CallbackHandler();
|
||||
}
|
||||
|
||||
// destroy a callback handler
|
||||
// this will clear the global callback handler if the passed pointer equals the global pointer
|
||||
EXPORT void LibretroBridge_DestroyCallbackHandler(CallbackHandler* cbHandler) {
|
||||
if (cbHandler == gCbHandler) {
|
||||
gCbHandler = nullptr;
|
||||
}
|
||||
|
||||
delete cbHandler;
|
||||
}
|
||||
|
||||
// set a "global" callback handler
|
||||
EXPORT void LibretroBridge_SetGlobalCallbackHandler(CallbackHandler* cbHandler) {
|
||||
gCbHandler = cbHandler;
|
||||
}
|
||||
|
||||
// get whether the core has reported support for no game loaded
|
||||
EXPORT bool LibretroBridge_GetSupportsNoGame(CallbackHandler* cbHandler) {
|
||||
return cbHandler->GetSupportsNoGame();
|
||||
}
|
||||
|
||||
// get current retro_message set by the core
|
||||
EXPORT void LibretroBridge_GetRetroMessage(CallbackHandler* cbHandler, retro_message* m) {
|
||||
cbHandler->GetRetroMessage(m);
|
||||
}
|
||||
|
||||
// set directories for the core
|
||||
EXPORT void LibretroBridge_SetDirectories(CallbackHandler* cbHandler, const char* systemDirectory, const char* saveDirectory, const char* coreDirectory, const char* coreAssetsDirectory) {
|
||||
cbHandler->SetDirectories(systemDirectory, saveDirectory, coreDirectory, coreAssetsDirectory);
|
||||
}
|
||||
|
||||
// set size of video in pixels
|
||||
// this should equal max_width * max_height
|
||||
EXPORT void LibretroBridge_SetVideoSize(CallbackHandler* cbHandler, u32 sz) {
|
||||
cbHandler->SetVideoSize(sz);
|
||||
}
|
||||
|
||||
// get video width/height and a copy of the video buffer
|
||||
EXPORT void LibretroBridge_GetVideo(CallbackHandler* cbHandler, u32* width, u32* height, u32* videoBuf) {
|
||||
cbHandler->GetVideo(width, height, videoBuf);
|
||||
}
|
||||
|
||||
// get size of audio in 16 bit units
|
||||
// this should be used to allocate a buffer for retrieving the audio buffer
|
||||
EXPORT u32 LibretroBridge_GetAudioSize(CallbackHandler* cbHandler) {
|
||||
return cbHandler->GetAudioSize();
|
||||
}
|
||||
|
||||
// get number of stereo samples and a copy of the audio buffer
|
||||
// calling this function will also reset the current audio buffer
|
||||
EXPORT void LibretroBridge_GetAudio(CallbackHandler* cbHandler, u32* numSamples, s16* sampleBuf) {
|
||||
cbHandler->GetAudio(numSamples, sampleBuf);
|
||||
}
|
||||
|
||||
// set input for specific device and port
|
||||
// input is expected to be sent through an array of signed 16 bit integers, the positions of input in this array defined by RETRO_DEVICE_ID_* or RETRO_KEY
|
||||
EXPORT void LibretroBridge_SetInput(CallbackHandler* cbHandler, RETRO_DEVICE device, u32 port, s16* input) {
|
||||
cbHandler->SetInput(device, port, input);
|
||||
}
|
||||
|
||||
// retro callbacks
|
||||
|
||||
static boolean retro_environment(u32 cmd, void* data) {
|
||||
assert(gCbHandler);
|
||||
return gCbHandler->RetroEnvironment(cmd, data);
|
||||
}
|
||||
|
||||
static void retro_video_refresh(const void* data, u32 width, u32 height, std::size_t pitch) {
|
||||
assert(gCbHandler);
|
||||
return gCbHandler->RetroVideoRefresh(data, width, height, pitch);
|
||||
}
|
||||
|
||||
static void retro_audio_sample(s16 left, s16 right) {
|
||||
assert(gCbHandler);
|
||||
return gCbHandler->RetroAudioSample(left, right);
|
||||
}
|
||||
|
||||
static std::size_t retro_audio_sample_batch(const s16* data, std::size_t frames) {
|
||||
assert(gCbHandler);
|
||||
return gCbHandler->RetroAudioSampleBatch(data, frames);
|
||||
}
|
||||
|
||||
static void retro_input_poll() {
|
||||
assert(gCbHandler);
|
||||
return gCbHandler->RetroInputPoll();
|
||||
}
|
||||
|
||||
static s16 retro_input_state(u32 port, u32 device, u32 index, u32 id) {
|
||||
assert(gCbHandler);
|
||||
return gCbHandler->RetroInputState(port, device, index, id);
|
||||
}
|
||||
|
||||
struct RetroProcs {
|
||||
decltype(&retro_environment) retro_environment_proc;
|
||||
decltype(&retro_video_refresh) retro_video_refresh_proc;
|
||||
decltype(&retro_audio_sample) retro_audio_sample_proc;
|
||||
decltype(&retro_audio_sample_batch) retro_audio_sample_batch_proc;
|
||||
decltype(&retro_input_poll) retro_input_poll_proc;
|
||||
decltype(&retro_input_state) retro_input_state_proc;
|
||||
};
|
||||
|
||||
// get procs for retro callbacks
|
||||
// these are not linked to any specific callback handler
|
||||
// please call LibretroBridge_SetCallbackHandler to set a global callback handler
|
||||
// as you can guess, this means only a single instance can use this at a time :/
|
||||
// (not like you can multi-instance libretro cores in the first place)
|
||||
EXPORT void LibretroBridge_GetRetroProcs(RetroProcs* retroProcs) {
|
||||
retroProcs->retro_environment_proc = &retro_environment;
|
||||
retroProcs->retro_video_refresh_proc = &retro_video_refresh;
|
||||
retroProcs->retro_audio_sample_proc = &retro_audio_sample;
|
||||
retroProcs->retro_audio_sample_batch_proc = &retro_audio_sample_batch;
|
||||
retroProcs->retro_input_poll_proc = &retro_input_poll;
|
||||
retroProcs->retro_input_state_proc = &retro_input_state;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,350 @@
|
|||
#include <cstdint>
|
||||
#include <climits>
|
||||
|
||||
typedef std::int8_t s8;
|
||||
typedef std::int16_t s16;
|
||||
typedef std::int32_t s32;
|
||||
typedef std::int64_t s64;
|
||||
|
||||
typedef std::uint8_t u8;
|
||||
typedef std::uint16_t u16;
|
||||
typedef std::uint32_t u32;
|
||||
typedef std::uint64_t u64;
|
||||
|
||||
typedef u8 boolean;
|
||||
|
||||
#define EXPORT extern "C" __attribute__((visibility("default")))
|
||||
|
||||
enum class RETRO_ENVIRONMENT {
|
||||
EXPERIMENTAL = 0x10000,
|
||||
SET_ROTATION = 1,
|
||||
GET_OVERSCAN = 2,
|
||||
GET_CAN_DUPE = 3,
|
||||
SET_MESSAGE = 6,
|
||||
SHUTDOWN = 7,
|
||||
SET_PERFORMANCE_LEVEL = 8,
|
||||
GET_SYSTEM_DIRECTORY = 9,
|
||||
SET_PIXEL_FORMAT = 10,
|
||||
SET_INPUT_DESCRIPTORS = 11,
|
||||
SET_KEYBOARD_CALLBACK = 12,
|
||||
SET_DISK_CONTROL_INTERFACE = 13,
|
||||
SET_HW_RENDER = 14,
|
||||
GET_VARIABLE = 15,
|
||||
SET_VARIABLES = 16,
|
||||
GET_VARIABLE_UPDATE = 17,
|
||||
SET_SUPPORT_NO_GAME = 18,
|
||||
GET_LIBRETRO_PATH = 19,
|
||||
SET_AUDIO_CALLBACK = 22,
|
||||
SET_FRAME_TIME_CALLBACK = 21,
|
||||
GET_RUMBLE_INTERFACE = 23,
|
||||
GET_INPUT_DEVICE_CAPABILITIES = 24,
|
||||
GET_SENSOR_INTERFACE = 25 | RETRO_ENVIRONMENT::EXPERIMENTAL,
|
||||
GET_CAMERA_INTERFACE = 26 | RETRO_ENVIRONMENT::EXPERIMENTAL,
|
||||
GET_LOG_INTERFACE = 27,
|
||||
GET_PERF_INTERFACE = 28,
|
||||
GET_LOCATION_INTERFACE = 29,
|
||||
GET_CONTENT_DIRECTORY = 30,
|
||||
GET_CORE_ASSETS_DIRECTORY = 30,
|
||||
GET_SAVE_DIRECTORY = 31,
|
||||
SET_SYSTEM_AV_INFO = 32,
|
||||
SET_PROC_ADDRESS_CALLBACK = 33,
|
||||
SET_SUBSYSTEM_INFO = 34,
|
||||
SET_CONTROLLER_INFO = 35,
|
||||
SET_MEMORY_MAPS = 36 | RETRO_ENVIRONMENT::EXPERIMENTAL,
|
||||
SET_GEOMETRY = 37,
|
||||
GET_USERNAME = 38,
|
||||
GET_LANGUAGE = 39,
|
||||
GET_CURRENT_SOFTWARE_FRAMEBUFFER = 40 | RETRO_ENVIRONMENT::EXPERIMENTAL,
|
||||
GET_HW_RENDER_INTERFACE = 41 | RETRO_ENVIRONMENT::EXPERIMENTAL,
|
||||
SET_SUPPORT_ACHIEVEMENTS = 42 | RETRO_ENVIRONMENT::EXPERIMENTAL,
|
||||
SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE = 43 | RETRO_ENVIRONMENT::EXPERIMENTAL,
|
||||
SET_SERIALIZATION_QUIRKS = 44,
|
||||
};
|
||||
|
||||
enum class RETRO_DEVICE {
|
||||
NONE = 0,
|
||||
JOYPAD = 1,
|
||||
MOUSE = 2,
|
||||
KEYBOARD = 3,
|
||||
LIGHTGUN = 4,
|
||||
ANALOG = 5,
|
||||
POINTER = 6,
|
||||
SENSOR_ACCELEROMETER = 7,
|
||||
|
||||
LAST,
|
||||
};
|
||||
|
||||
enum class RETRO_DEVICE_ID_ANALOG {
|
||||
X = 0,
|
||||
Y = 1,
|
||||
|
||||
LAST,
|
||||
};
|
||||
|
||||
enum class RETRO_DEVICE_ID_MOUSE {
|
||||
X = 0,
|
||||
Y = 1,
|
||||
LEFT = 2,
|
||||
RIGHT = 3,
|
||||
|
||||
LAST,
|
||||
};
|
||||
|
||||
enum class RETRO_DEVICE_ID_LIGHTGUN {
|
||||
X = 0,
|
||||
Y = 1,
|
||||
TRIGGER = 2,
|
||||
CURSOR = 3,
|
||||
TURBO = 4,
|
||||
PAUSE = 5,
|
||||
START = 6,
|
||||
|
||||
LAST,
|
||||
};
|
||||
|
||||
enum class RETRO_DEVICE_ID_POINTER {
|
||||
X = 0,
|
||||
Y = 1,
|
||||
PRESSED = 2,
|
||||
|
||||
LAST,
|
||||
};
|
||||
|
||||
enum class RETRO_KEY {
|
||||
UNKNOWN = 0,
|
||||
FIRST = 0,
|
||||
BACKSPACE = 8,
|
||||
TAB = 9,
|
||||
CLEAR = 12,
|
||||
RETURN = 13,
|
||||
PAUSE = 19,
|
||||
ESCAPE = 27,
|
||||
SPACE = 32,
|
||||
EXCLAIM = 33,
|
||||
QUOTEDBL = 34,
|
||||
HASH = 35,
|
||||
DOLLAR = 36,
|
||||
AMPERSAND = 38,
|
||||
QUOTE = 39,
|
||||
LEFTPAREN = 40,
|
||||
RIGHTPAREN = 41,
|
||||
ASTERISK = 42,
|
||||
PLUS = 43,
|
||||
COMMA = 44,
|
||||
MINUS = 45,
|
||||
PERIOD = 46,
|
||||
SLASH = 47,
|
||||
_0 = 48,
|
||||
_1 = 49,
|
||||
_2 = 50,
|
||||
_3 = 51,
|
||||
_4 = 52,
|
||||
_5 = 53,
|
||||
_6 = 54,
|
||||
_7 = 55,
|
||||
_8 = 56,
|
||||
_9 = 57,
|
||||
COLON = 58,
|
||||
SEMICOLON = 59,
|
||||
LESS = 60,
|
||||
EQUALS = 61,
|
||||
GREATER = 62,
|
||||
QUESTION = 63,
|
||||
AT = 64,
|
||||
LEFTBRACKET = 91,
|
||||
BACKSLASH = 92,
|
||||
RIGHTBRACKET = 93,
|
||||
CARET = 94,
|
||||
UNDERSCORE = 95,
|
||||
BACKQUOTE = 96,
|
||||
a = 97,
|
||||
b = 98,
|
||||
c = 99,
|
||||
d = 100,
|
||||
e = 101,
|
||||
f = 102,
|
||||
g = 103,
|
||||
h = 104,
|
||||
i = 105,
|
||||
j = 106,
|
||||
k = 107,
|
||||
l = 108,
|
||||
m = 109,
|
||||
n = 110,
|
||||
o = 111,
|
||||
p = 112,
|
||||
q = 113,
|
||||
r = 114,
|
||||
s = 115,
|
||||
t = 116,
|
||||
u = 117,
|
||||
v = 118,
|
||||
w = 119,
|
||||
x = 120,
|
||||
y = 121,
|
||||
z = 122,
|
||||
DELETE = 127,
|
||||
|
||||
KP0 = 256,
|
||||
KP1 = 257,
|
||||
KP2 = 258,
|
||||
KP3 = 259,
|
||||
KP4 = 260,
|
||||
KP5 = 261,
|
||||
KP6 = 262,
|
||||
KP7 = 263,
|
||||
KP8 = 264,
|
||||
KP9 = 265,
|
||||
KP_PERIOD = 266,
|
||||
KP_DIVIDE = 267,
|
||||
KP_MULTIPLY = 268,
|
||||
KP_MINUS = 269,
|
||||
KP_PLUS = 270,
|
||||
KP_ENTER = 271,
|
||||
KP_EQUALS = 272,
|
||||
|
||||
UP = 273,
|
||||
DOWN = 274,
|
||||
RIGHT = 275,
|
||||
LEFT = 276,
|
||||
INSERT = 277,
|
||||
HOME = 278,
|
||||
END = 279,
|
||||
PAGEUP = 280,
|
||||
PAGEDOWN = 281,
|
||||
|
||||
F1 = 282,
|
||||
F2 = 283,
|
||||
F3 = 284,
|
||||
F4 = 285,
|
||||
F5 = 286,
|
||||
F6 = 287,
|
||||
F7 = 288,
|
||||
F8 = 289,
|
||||
F9 = 290,
|
||||
F10 = 291,
|
||||
F11 = 292,
|
||||
F12 = 293,
|
||||
F13 = 294,
|
||||
F14 = 295,
|
||||
F15 = 296,
|
||||
|
||||
NUMLOCK = 300,
|
||||
CAPSLOCK = 301,
|
||||
SCROLLOCK = 302,
|
||||
RSHIFT = 303,
|
||||
LSHIFT = 304,
|
||||
RCTRL = 305,
|
||||
LCTRL = 306,
|
||||
RALT = 307,
|
||||
LALT = 308,
|
||||
RMETA = 309,
|
||||
LMETA = 310,
|
||||
LSUPER = 311,
|
||||
RSUPER = 312,
|
||||
MODE = 313,
|
||||
COMPOSE = 314,
|
||||
|
||||
HELP = 315,
|
||||
PRINT = 316,
|
||||
SYSREQ = 317,
|
||||
BREAK = 318,
|
||||
MENU = 319,
|
||||
POWER = 320,
|
||||
EURO = 321,
|
||||
UNDO = 322,
|
||||
|
||||
LAST,
|
||||
};
|
||||
|
||||
enum class RETRO_MOD {
|
||||
NONE = 0,
|
||||
SHIFT = 1,
|
||||
CTRL = 2,
|
||||
ALT = 4,
|
||||
META = 8,
|
||||
NUMLOCK = 16,
|
||||
CAPSLOCK = 32,
|
||||
SCROLLLOCK = 64,
|
||||
};
|
||||
|
||||
enum class RETRO_DEVICE_ID_SENSOR_ACCELEROMETER {
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Z = 2,
|
||||
|
||||
LAST,
|
||||
};
|
||||
|
||||
enum class RETRO_DEVICE_ID_JOYPAD {
|
||||
B = 0,
|
||||
Y = 1,
|
||||
SELECT = 2,
|
||||
START = 3,
|
||||
UP = 4,
|
||||
DOWN = 5,
|
||||
LEFT = 6,
|
||||
RIGHT = 7,
|
||||
A = 8,
|
||||
X = 9,
|
||||
L = 10,
|
||||
R = 11,
|
||||
L2 = 12,
|
||||
R2 = 13,
|
||||
L3 = 14,
|
||||
R3 = 15,
|
||||
|
||||
LAST,
|
||||
};
|
||||
|
||||
enum class RETRO_PIXEL_FORMAT {
|
||||
ZRGB1555 = 0,
|
||||
XRGB8888 = 1,
|
||||
RGB565 = 2,
|
||||
UNKNOWN = INT_MAX,
|
||||
};
|
||||
|
||||
enum class RETRO_LANGUAGE {
|
||||
ENGLISH = 0,
|
||||
JAPANESE = 1,
|
||||
FRENCH = 2,
|
||||
SPANISH = 3,
|
||||
GERMAN = 4,
|
||||
ITALIAN = 5,
|
||||
DUTCH = 6,
|
||||
PORTUGUESE = 7,
|
||||
RUSSIAN = 8,
|
||||
KOREAN = 9,
|
||||
CHINESE_TRADITIONAL = 10,
|
||||
CHINESE_SIMPLIFIED = 11,
|
||||
ESPERANTO = 12,
|
||||
POLISH = 13,
|
||||
VIETNAMESE = 14,
|
||||
LAST,
|
||||
|
||||
DUMMY = INT_MAX,
|
||||
};
|
||||
|
||||
enum class RETRO_LOG {
|
||||
DEBUG = 0,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
DUMMY = INT_MAX,
|
||||
};
|
||||
|
||||
struct retro_variable {
|
||||
const char* key;
|
||||
const char* value;
|
||||
};
|
||||
|
||||
struct retro_message {
|
||||
const char* msg;
|
||||
u32 frames;
|
||||
};
|
||||
|
||||
typedef void (*retro_log_printf_t)(RETRO_LOG level, const char* fmt, ...);
|
||||
|
||||
struct retro_log_callback {
|
||||
retro_log_printf_t log;
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
CXX = g++
|
||||
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -std=c++11 -O3 -fPIC -fvisibility=hidden -fomit-frame-pointer -fno-exceptions
|
||||
LFLAGS = -shared
|
||||
|
||||
SRCS = LibretroBridge.cpp
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
TARGET = ../Assets/dll/libLibretroBridge.dll
|
||||
else
|
||||
TARGET = ../Assets/dll/libLibretroBridge.so
|
||||
endif
|
||||
|
||||
all: libLibretroBridge
|
||||
|
||||
libLibretroBridge: $(SRCS)
|
||||
$(CXX) $(CFLAGS) $(SRCS) -o $(TARGET) $(LFLAGS)
|
|
@ -1,776 +0,0 @@
|
|||
/* Copyright (C) 2010-2017 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (features_cpu.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
//modified for bizhawk's libretro shim
|
||||
#define __i686__ 1
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
//#include <compat/strl.h>
|
||||
//#include <streams/file_stream.h>
|
||||
#include "libretro.h"
|
||||
//#include <features/features_cpu.h>
|
||||
|
||||
#if defined(_WIN32) && !defined(_XBOX)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(__CELLOS_LV2__)
|
||||
#ifndef _PPU_INTRINSICS_H
|
||||
#include <ppu_intrinsics.h>
|
||||
#endif
|
||||
#elif defined(_XBOX360)
|
||||
#include <PPCIntrinsics.h>
|
||||
#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(ANDROID) || defined(__QNX__) || defined(DJGPP)
|
||||
/* POSIX_MONOTONIC_CLOCK is not being defined in Android headers despite support being present. */
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
#if defined(__QNX__) && !defined(CLOCK_MONOTONIC)
|
||||
#define CLOCK_MONOTONIC 2
|
||||
#endif
|
||||
|
||||
#if defined(PSP)
|
||||
#include <sys/time.h>
|
||||
#include <psprtc.h>
|
||||
#endif
|
||||
|
||||
#if defined(VITA)
|
||||
#include <psp2/kernel/processmgr.h>
|
||||
#include <psp2/rtc.h>
|
||||
#endif
|
||||
|
||||
#if defined(__PSL1GHT__)
|
||||
#include <sys/time.h>
|
||||
#elif defined(__CELLOS_LV2__)
|
||||
#include <sys/sys_time.h>
|
||||
#endif
|
||||
|
||||
#ifdef GEKKO
|
||||
#include <ogc/lwp_watchdog.h>
|
||||
#endif
|
||||
|
||||
#ifdef WIIU
|
||||
#include <wiiu/os/time.h>
|
||||
#endif
|
||||
|
||||
#if defined(_3DS)
|
||||
#include <3ds/svc.h>
|
||||
#include <3ds/os.h>
|
||||
#endif
|
||||
|
||||
/* iOS/OSX specific. Lacks clock_gettime(), so implement it. */
|
||||
#ifdef __MACH__
|
||||
#include <sys/time.h>
|
||||
|
||||
#ifndef CLOCK_MONOTONIC
|
||||
#define CLOCK_MONOTONIC 0
|
||||
#endif
|
||||
|
||||
#ifndef CLOCK_REALTIME
|
||||
#define CLOCK_REALTIME 0
|
||||
#endif
|
||||
|
||||
/* this function is part of iOS 10 now */
|
||||
static int ra_clock_gettime(int clk_ik, struct timespec *t)
|
||||
{
|
||||
struct timeval now;
|
||||
int rv = gettimeofday(&now, NULL);
|
||||
if (rv)
|
||||
return rv;
|
||||
t->tv_sec = now.tv_sec;
|
||||
t->tv_nsec = now.tv_usec * 1000;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__MACH__) && __IPHONE_OS_VERSION_MIN_REQUIRED < 100000
|
||||
#else
|
||||
#define ra_clock_gettime clock_gettime
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
#if defined(BSD) || defined(__APPLE__)
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* cpu_features_get_perf_counter:
|
||||
*
|
||||
* Gets performance counter.
|
||||
*
|
||||
* Returns: performance counter.
|
||||
**/
|
||||
retro_perf_tick_t cpu_features_get_perf_counter(void)
|
||||
{
|
||||
retro_perf_tick_t time_ticks = 0;
|
||||
#if defined(_WIN32)
|
||||
long tv_sec, tv_usec;
|
||||
static const unsigned __int64 epoch = 11644473600000000ULL;
|
||||
FILETIME file_time;
|
||||
SYSTEMTIME system_time;
|
||||
ULARGE_INTEGER ularge;
|
||||
|
||||
GetSystemTime(&system_time);
|
||||
SystemTimeToFileTime(&system_time, &file_time);
|
||||
ularge.LowPart = file_time.dwLowDateTime;
|
||||
ularge.HighPart = file_time.dwHighDateTime;
|
||||
|
||||
tv_sec = (long)((ularge.QuadPart - epoch) / 10000000L);
|
||||
tv_usec = (long)(system_time.wMilliseconds * 1000);
|
||||
time_ticks = (1000000 * tv_sec + tv_usec);
|
||||
#elif defined(__linux__) || defined(__QNX__) || defined(__MACH__)
|
||||
struct timespec tv = {0};
|
||||
if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) == 0)
|
||||
time_ticks = (retro_perf_tick_t)tv.tv_sec * 1000000000 +
|
||||
(retro_perf_tick_t)tv.tv_nsec;
|
||||
|
||||
#elif defined(__GNUC__) && defined(__i386__) || defined(__i486__) || defined(__i686__)
|
||||
__asm__ volatile ("rdtsc" : "=A" (time_ticks));
|
||||
#elif defined(__GNUC__) && defined(__x86_64__)
|
||||
unsigned a, d;
|
||||
__asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
|
||||
time_ticks = (retro_perf_tick_t)a | ((retro_perf_tick_t)d << 32);
|
||||
#elif defined(__ARM_ARCH_6__)
|
||||
__asm__ volatile( "mrc p15, 0, %0, c9, c13, 0" : "=r"(time_ticks) );
|
||||
#elif defined(__CELLOS_LV2__) || defined(_XBOX360) || defined(__powerpc__) || defined(__ppc__) || defined(__POWERPC__)
|
||||
time_ticks = __mftb();
|
||||
#elif defined(GEKKO)
|
||||
time_ticks = gettime();
|
||||
#elif defined(PSP)
|
||||
sceRtcGetCurrentTick((uint64_t*)&time_ticks);
|
||||
#elif defined(VITA)
|
||||
sceRtcGetCurrentTick((SceRtcTick*)&time_ticks);
|
||||
#elif defined(_3DS)
|
||||
time_ticks = svcGetSystemTick();
|
||||
#elif defined(WIIU)
|
||||
time_ticks = OSGetSystemTime();
|
||||
#elif defined(__mips__)
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv,NULL);
|
||||
time_ticks = (1000000 * tv.tv_sec + tv.tv_usec);
|
||||
#endif
|
||||
|
||||
return time_ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
* cpu_features_get_time_usec:
|
||||
*
|
||||
* Gets time in microseconds.
|
||||
*
|
||||
* Returns: time in microseconds.
|
||||
**/
|
||||
retro_time_t cpu_features_get_time_usec(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
static LARGE_INTEGER freq;
|
||||
LARGE_INTEGER count;
|
||||
|
||||
/* Frequency is guaranteed to not change. */
|
||||
if (!freq.QuadPart && !QueryPerformanceFrequency(&freq))
|
||||
return 0;
|
||||
|
||||
if (!QueryPerformanceCounter(&count))
|
||||
return 0;
|
||||
return count.QuadPart * 1000000 / freq.QuadPart;
|
||||
#elif defined(__CELLOS_LV2__)
|
||||
return sys_time_get_system_time();
|
||||
#elif defined(GEKKO)
|
||||
return ticks_to_microsecs(gettime());
|
||||
#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(__QNX__) || defined(ANDROID) || defined(__MACH__)
|
||||
struct timespec tv = {0};
|
||||
if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) < 0)
|
||||
return 0;
|
||||
return tv.tv_sec * INT64_C(1000000) + (tv.tv_nsec + 500) / 1000;
|
||||
#elif defined(EMSCRIPTEN)
|
||||
return emscripten_get_now() * 1000;
|
||||
#elif defined(__mips__) || defined(DJGPP)
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv,NULL);
|
||||
return (1000000 * tv.tv_sec + tv.tv_usec);
|
||||
#elif defined(_3DS)
|
||||
return osGetTime() * 1000;
|
||||
#elif defined(VITA)
|
||||
return sceKernelGetProcessTimeWide();
|
||||
#elif defined(WIIU)
|
||||
return ticks_to_us(OSGetSystemTime());
|
||||
#else
|
||||
#error "Your platform does not have a timer function implemented in cpu_features_get_time_usec(). Cannot continue."
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__) || defined(__i486__) || defined(__i686__)
|
||||
#define CPU_X86
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_XBOX)
|
||||
#if (_MSC_VER > 1310)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(CPU_X86) && !defined(__MACH__)
|
||||
void x86_cpuid(int func, int flags[4])
|
||||
{
|
||||
/* On Android, we compile RetroArch with PIC, and we
|
||||
* are not allowed to clobber the ebx register. */
|
||||
#ifdef __x86_64__
|
||||
#define REG_b "rbx"
|
||||
#define REG_S "rsi"
|
||||
#else
|
||||
#define REG_b "ebx"
|
||||
#define REG_S "esi"
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
__asm__ volatile (
|
||||
"mov %%" REG_b ", %%" REG_S "\n"
|
||||
"cpuid\n"
|
||||
"xchg %%" REG_b ", %%" REG_S "\n"
|
||||
: "=a"(flags[0]), "=S"(flags[1]), "=c"(flags[2]), "=d"(flags[3])
|
||||
: "a"(func));
|
||||
#elif defined(_MSC_VER)
|
||||
__cpuid(flags, func);
|
||||
#else
|
||||
printf("Unknown compiler. Cannot check CPUID with inline assembly.\n");
|
||||
memset(flags, 0, 4 * sizeof(int));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Only runs on i686 and above. Needs to be conditionally run. */
|
||||
static uint64_t xgetbv_x86(uint32_t idx)
|
||||
{
|
||||
#if defined(__GNUC__)
|
||||
uint32_t eax, edx;
|
||||
__asm__ volatile (
|
||||
/* Older GCC versions (Apple's GCC for example) do
|
||||
* not understand xgetbv instruction.
|
||||
* Stamp out the machine code directly.
|
||||
*/
|
||||
".byte 0x0f, 0x01, 0xd0\n"
|
||||
: "=a"(eax), "=d"(edx) : "c"(idx));
|
||||
return ((uint64_t)edx << 32) | eax;
|
||||
#elif _MSC_FULL_VER >= 160040219
|
||||
/* Intrinsic only works on 2010 SP1 and above. */
|
||||
return _xgetbv(idx);
|
||||
#else
|
||||
printf("Unknown compiler. Cannot check xgetbv bits.\n");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__ARM_NEON__)
|
||||
static void arm_enable_runfast_mode(void)
|
||||
{
|
||||
/* RunFast mode. Enables flush-to-zero and some
|
||||
* floating point optimizations. */
|
||||
static const unsigned x = 0x04086060;
|
||||
static const unsigned y = 0x03000000;
|
||||
int r;
|
||||
__asm__ volatile(
|
||||
"fmrx %0, fpscr \n\t" /* r0 = FPSCR */
|
||||
"and %0, %0, %1 \n\t" /* r0 = r0 & 0x04086060 */
|
||||
"orr %0, %0, %2 \n\t" /* r0 = r0 | 0x03000000 */
|
||||
"fmxr fpscr, %0 \n\t" /* FPSCR = r0 */
|
||||
: "=r"(r)
|
||||
: "r"(x), "r"(y)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && !defined(CPU_X86)
|
||||
static unsigned char check_arm_cpu_feature(const char* feature)
|
||||
{
|
||||
char line[1024];
|
||||
unsigned char status = 0;
|
||||
RFILE *fp = filestream_open("/proc/cpuinfo", RFILE_MODE_READ_TEXT, -1);
|
||||
|
||||
if (!fp)
|
||||
return 0;
|
||||
|
||||
while (filestream_gets(fp, line, sizeof(line)) != NULL)
|
||||
{
|
||||
if (strncmp(line, "Features\t: ", 11))
|
||||
continue;
|
||||
|
||||
if (strstr(line + 11, feature) != NULL)
|
||||
status = 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
filestream_close(fp);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#if !defined(_SC_NPROCESSORS_ONLN)
|
||||
/* Parse an decimal integer starting from 'input', but not going further
|
||||
* than 'limit'. Return the value into '*result'.
|
||||
*
|
||||
* NOTE: Does not skip over leading spaces, or deal with sign characters.
|
||||
* NOTE: Ignores overflows.
|
||||
*
|
||||
* The function returns NULL in case of error (bad format), or the new
|
||||
* position after the decimal number in case of success (which will always
|
||||
* be <= 'limit').
|
||||
*/
|
||||
static const char *parse_decimal(const char* input,
|
||||
const char* limit, int* result)
|
||||
{
|
||||
const char* p = input;
|
||||
int val = 0;
|
||||
|
||||
while (p < limit)
|
||||
{
|
||||
int d = (*p - '0');
|
||||
if ((unsigned)d >= 10U)
|
||||
break;
|
||||
val = val*10 + d;
|
||||
p++;
|
||||
}
|
||||
if (p == input)
|
||||
return NULL;
|
||||
|
||||
*result = val;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Parse a textual list of cpus and store the result inside a CpuList object.
|
||||
* Input format is the following:
|
||||
* - comma-separated list of items (no spaces)
|
||||
* - each item is either a single decimal number (cpu index), or a range made
|
||||
* of two numbers separated by a single dash (-). Ranges are inclusive.
|
||||
*
|
||||
* Examples: 0
|
||||
* 2,4-127,128-143
|
||||
* 0-1
|
||||
*/
|
||||
static void cpulist_parse(CpuList* list, char **buf, ssize_t length)
|
||||
{
|
||||
const char* p = (const char*)buf;
|
||||
const char* end = p + length;
|
||||
|
||||
/* NOTE: the input line coming from sysfs typically contains a
|
||||
* trailing newline, so take care of it in the code below
|
||||
*/
|
||||
while (p < end && *p != '\n')
|
||||
{
|
||||
int val, start_value, end_value;
|
||||
/* Find the end of current item, and put it into 'q' */
|
||||
const char *q = (const char*)memchr(p, ',', end-p);
|
||||
|
||||
if (!q)
|
||||
q = end;
|
||||
|
||||
/* Get first value */
|
||||
p = parse_decimal(p, q, &start_value);
|
||||
if (p == NULL)
|
||||
return;
|
||||
|
||||
end_value = start_value;
|
||||
|
||||
/* If we're not at the end of the item, expect a dash and
|
||||
* and integer; extract end value.
|
||||
*/
|
||||
if (p < q && *p == '-')
|
||||
{
|
||||
p = parse_decimal(p+1, q, &end_value);
|
||||
if (p == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set bits CPU list bits */
|
||||
for (val = start_value; val <= end_value; val++)
|
||||
{
|
||||
if ((unsigned)val < 32)
|
||||
list->mask |= (uint32_t)(1U << val);
|
||||
}
|
||||
|
||||
/* Jump to next item */
|
||||
p = q;
|
||||
if (p < end)
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read a CPU list from one sysfs file */
|
||||
static void cpulist_read_from(CpuList* list, const char* filename)
|
||||
{
|
||||
ssize_t length;
|
||||
char *buf = NULL;
|
||||
|
||||
list->mask = 0;
|
||||
|
||||
if (filestream_read_file(filename, (void**)&buf, &length) != 1)
|
||||
return;
|
||||
|
||||
cpulist_parse(list, &buf, length);
|
||||
if (buf)
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* cpu_features_get_core_amount:
|
||||
*
|
||||
* Gets the amount of available CPU cores.
|
||||
*
|
||||
* Returns: amount of CPU cores available.
|
||||
**/
|
||||
unsigned cpu_features_get_core_amount(void)
|
||||
{
|
||||
#if defined(_WIN32) && !defined(_XBOX)
|
||||
/* Win32 */
|
||||
SYSTEM_INFO sysinfo;
|
||||
GetSystemInfo(&sysinfo);
|
||||
return sysinfo.dwNumberOfProcessors;
|
||||
#elif defined(GEKKO)
|
||||
return 1;
|
||||
#elif defined(PSP)
|
||||
return 1;
|
||||
#elif defined(VITA)
|
||||
return 4;
|
||||
#elif defined(_3DS)
|
||||
return 1;
|
||||
#elif defined(WIIU)
|
||||
return 3;
|
||||
#elif defined(_SC_NPROCESSORS_ONLN)
|
||||
/* Linux, most UNIX-likes. */
|
||||
long ret = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
if (ret <= 0)
|
||||
return (unsigned)1;
|
||||
return (unsigned)ret;
|
||||
#elif defined(BSD) || defined(__APPLE__)
|
||||
/* BSD */
|
||||
/* Copypasta from stackoverflow, dunno if it works. */
|
||||
int num_cpu = 0;
|
||||
int mib[4];
|
||||
size_t len = sizeof(num_cpu);
|
||||
|
||||
mib[0] = CTL_HW;
|
||||
mib[1] = HW_AVAILCPU;
|
||||
sysctl(mib, 2, &num_cpu, &len, NULL, 0);
|
||||
if (num_cpu < 1)
|
||||
{
|
||||
mib[1] = HW_NCPU;
|
||||
sysctl(mib, 2, &num_cpu, &len, NULL, 0);
|
||||
if (num_cpu < 1)
|
||||
num_cpu = 1;
|
||||
}
|
||||
return num_cpu;
|
||||
#elif defined(__linux__)
|
||||
CpuList cpus_present[1];
|
||||
CpuList cpus_possible[1];
|
||||
int amount = 0;
|
||||
|
||||
cpulist_read_from(cpus_present, "/sys/devices/system/cpu/present");
|
||||
cpulist_read_from(cpus_possible, "/sys/devices/system/cpu/possible");
|
||||
|
||||
/* Compute the intersection of both sets to get the actual number of
|
||||
* CPU cores that can be used on this device by the kernel.
|
||||
*/
|
||||
cpus_present->mask &= cpus_possible->mask;
|
||||
amount = __builtin_popcount(cpus_present->mask);
|
||||
|
||||
if (amount == 0)
|
||||
return 1;
|
||||
return amount;
|
||||
#elif defined(_XBOX360)
|
||||
return 3;
|
||||
#else
|
||||
/* No idea, assume single core. */
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* According to http://en.wikipedia.org/wiki/CPUID */
|
||||
#define VENDOR_INTEL_b 0x756e6547
|
||||
#define VENDOR_INTEL_c 0x6c65746e
|
||||
#define VENDOR_INTEL_d 0x49656e69
|
||||
|
||||
/**
|
||||
* cpu_features_get:
|
||||
*
|
||||
* Gets CPU features..
|
||||
*
|
||||
* Returns: bitmask of all CPU features available.
|
||||
**/
|
||||
uint64_t cpu_features_get(void)
|
||||
{
|
||||
int flags[4];
|
||||
int vendor_shuffle[3];
|
||||
char vendor[13];
|
||||
size_t len = 0;
|
||||
uint64_t cpu_flags = 0;
|
||||
uint64_t cpu = 0;
|
||||
unsigned max_flag = 0;
|
||||
#if defined(CPU_X86) && !defined(__MACH__)
|
||||
int vendor_is_intel = 0;
|
||||
const int avx_flags = (1 << 27) | (1 << 28);
|
||||
#endif
|
||||
|
||||
char buf[sizeof(" MMX MMXEXT SSE SSE2 SSE3 SSSE3 SS4 SSE4.2 AES AVX AVX2 NEON VMX VMX128 VFPU PS")];
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
(void)len;
|
||||
(void)cpu_flags;
|
||||
(void)flags;
|
||||
(void)max_flag;
|
||||
(void)vendor;
|
||||
(void)vendor_shuffle;
|
||||
|
||||
#if defined(__MACH__)
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.mmx", NULL, &len, NULL, 0) == 0)
|
||||
{
|
||||
cpu |= RETRO_SIMD_MMX;
|
||||
cpu |= RETRO_SIMD_MMXEXT;
|
||||
}
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.floatingpoint", NULL, &len, NULL, 0) == 0)
|
||||
{
|
||||
cpu |= RETRO_SIMD_CMOV;
|
||||
}
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.sse", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_SSE;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.sse2", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_SSE2;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.sse3", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_SSE3;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.supplementalsse3", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_SSSE3;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.sse4_1", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_SSE4;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.sse4_2", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_SSE42;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.aes", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_AES;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.avx1_0", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_AVX;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.avx2_0", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_AVX2;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.altivec", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_VMX;
|
||||
|
||||
len = sizeof(size_t);
|
||||
if (sysctlbyname("hw.optional.neon", NULL, &len, NULL, 0) == 0)
|
||||
cpu |= RETRO_SIMD_NEON;
|
||||
|
||||
#elif defined(CPU_X86)
|
||||
(void)avx_flags;
|
||||
|
||||
x86_cpuid(0, flags);
|
||||
vendor_shuffle[0] = flags[1];
|
||||
vendor_shuffle[1] = flags[3];
|
||||
vendor_shuffle[2] = flags[2];
|
||||
|
||||
vendor[0] = '\0';
|
||||
memcpy(vendor, vendor_shuffle, sizeof(vendor_shuffle));
|
||||
|
||||
/* printf("[CPUID]: Vendor: %s\n", vendor); */
|
||||
|
||||
vendor_is_intel = (
|
||||
flags[1] == VENDOR_INTEL_b &&
|
||||
flags[2] == VENDOR_INTEL_c &&
|
||||
flags[3] == VENDOR_INTEL_d);
|
||||
|
||||
max_flag = flags[0];
|
||||
if (max_flag < 1) /* Does CPUID not support func = 1? (unlikely ...) */
|
||||
return 0;
|
||||
|
||||
x86_cpuid(1, flags);
|
||||
|
||||
if (flags[3] & (1 << 15))
|
||||
cpu |= RETRO_SIMD_CMOV;
|
||||
|
||||
if (flags[3] & (1 << 23))
|
||||
cpu |= RETRO_SIMD_MMX;
|
||||
|
||||
if (flags[3] & (1 << 25))
|
||||
{
|
||||
/* SSE also implies MMXEXT (according to FFmpeg source). */
|
||||
cpu |= RETRO_SIMD_SSE;
|
||||
cpu |= RETRO_SIMD_MMXEXT;
|
||||
}
|
||||
|
||||
|
||||
if (flags[3] & (1 << 26))
|
||||
cpu |= RETRO_SIMD_SSE2;
|
||||
|
||||
if (flags[2] & (1 << 0))
|
||||
cpu |= RETRO_SIMD_SSE3;
|
||||
|
||||
if (flags[2] & (1 << 9))
|
||||
cpu |= RETRO_SIMD_SSSE3;
|
||||
|
||||
if (flags[2] & (1 << 19))
|
||||
cpu |= RETRO_SIMD_SSE4;
|
||||
|
||||
if (flags[2] & (1 << 20))
|
||||
cpu |= RETRO_SIMD_SSE42;
|
||||
|
||||
if ((flags[2] & (1 << 23)))
|
||||
cpu |= RETRO_SIMD_POPCNT;
|
||||
|
||||
if (vendor_is_intel && (flags[2] & (1 << 22)))
|
||||
cpu |= RETRO_SIMD_MOVBE;
|
||||
|
||||
if (flags[2] & (1 << 25))
|
||||
cpu |= RETRO_SIMD_AES;
|
||||
|
||||
|
||||
/* Must only perform xgetbv check if we have
|
||||
* AVX CPU support (guaranteed to have at least i686). */
|
||||
if (((flags[2] & avx_flags) == avx_flags)
|
||||
&& ((xgetbv_x86(0) & 0x6) == 0x6))
|
||||
cpu |= RETRO_SIMD_AVX;
|
||||
|
||||
if (max_flag >= 7)
|
||||
{
|
||||
x86_cpuid(7, flags);
|
||||
if (flags[1] & (1 << 5))
|
||||
cpu |= RETRO_SIMD_AVX2;
|
||||
}
|
||||
|
||||
x86_cpuid(0x80000000, flags);
|
||||
max_flag = flags[0];
|
||||
if (max_flag >= 0x80000001u)
|
||||
{
|
||||
x86_cpuid(0x80000001, flags);
|
||||
if (flags[3] & (1 << 23))
|
||||
cpu |= RETRO_SIMD_MMX;
|
||||
if (flags[3] & (1 << 22))
|
||||
cpu |= RETRO_SIMD_MMXEXT;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
if (check_arm_cpu_feature("neon"))
|
||||
{
|
||||
cpu |= RETRO_SIMD_NEON;
|
||||
#ifdef __ARM_NEON__
|
||||
arm_enable_runfast_mode();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (check_arm_cpu_feature("vfpv3"))
|
||||
cpu |= RETRO_SIMD_VFPV3;
|
||||
|
||||
if (check_arm_cpu_feature("vfpv4"))
|
||||
cpu |= RETRO_SIMD_VFPV4;
|
||||
|
||||
if (check_arm_cpu_feature("asimd"))
|
||||
{
|
||||
cpu |= RETRO_SIMD_ASIMD;
|
||||
#ifdef __ARM_NEON__
|
||||
cpu |= RETRO_SIMD_NEON;
|
||||
arm_enable_runfast_mode();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if 0
|
||||
check_arm_cpu_feature("swp");
|
||||
check_arm_cpu_feature("half");
|
||||
check_arm_cpu_feature("thumb");
|
||||
check_arm_cpu_feature("fastmult");
|
||||
check_arm_cpu_feature("vfp");
|
||||
check_arm_cpu_feature("edsp");
|
||||
check_arm_cpu_feature("thumbee");
|
||||
check_arm_cpu_feature("tls");
|
||||
check_arm_cpu_feature("idiva");
|
||||
check_arm_cpu_feature("idivt");
|
||||
#endif
|
||||
|
||||
#elif defined(__ARM_NEON__)
|
||||
cpu |= RETRO_SIMD_NEON;
|
||||
arm_enable_runfast_mode();
|
||||
#elif defined(__ALTIVEC__)
|
||||
cpu |= RETRO_SIMD_VMX;
|
||||
#elif defined(XBOX360)
|
||||
cpu |= RETRO_SIMD_VMX128;
|
||||
#elif defined(PSP)
|
||||
cpu |= RETRO_SIMD_VFPU;
|
||||
#elif defined(GEKKO)
|
||||
cpu |= RETRO_SIMD_PS;
|
||||
#endif
|
||||
|
||||
//if (cpu & RETRO_SIMD_MMX) strlcat(buf, " MMX", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_MMXEXT) strlcat(buf, " MMXEXT", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_SSE) strlcat(buf, " SSE", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_SSE2) strlcat(buf, " SSE2", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_SSE3) strlcat(buf, " SSE3", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_SSSE3) strlcat(buf, " SSSE3", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_SSE4) strlcat(buf, " SSE4", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_SSE42) strlcat(buf, " SSE4.2", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_AES) strlcat(buf, " AES", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_AVX) strlcat(buf, " AVX", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_AVX2) strlcat(buf, " AVX2", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_NEON) strlcat(buf, " NEON", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_VFPV3) strlcat(buf, " VFPv3", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_VFPV4) strlcat(buf, " VFPv4", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_VMX) strlcat(buf, " VMX", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_VMX128) strlcat(buf, " VMX128", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_VFPU) strlcat(buf, " VFPU", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_PS) strlcat(buf, " PS", sizeof(buf));
|
||||
//if (cpu & RETRO_SIMD_ASIMD) strlcat(buf, " ASIMD", sizeof(buf));
|
||||
|
||||
return cpu;
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
#define LIBCO_C
|
||||
#include "libco.h"
|
||||
#include "settings.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static thread_local long long co_active_buffer[64];
|
||||
static thread_local cothread_t co_active_handle = 0;
|
||||
static void (*co_swap)(cothread_t, cothread_t) = 0;
|
||||
|
||||
#ifdef LIBCO_MPROTECT
|
||||
alignas(4096)
|
||||
#else
|
||||
#ifdef _MSC_VER // honestly fuck that compiler
|
||||
#undef section
|
||||
#pragma section(".text")
|
||||
__declspec(allocate(".text"))
|
||||
#else
|
||||
section(text)
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
/* ABI: Win64 */
|
||||
static const unsigned char co_swap_function[4096] = {
|
||||
0x48, 0x89, 0x22, /* mov [rdx],rsp */
|
||||
0x48, 0x8b, 0x21, /* mov rsp,[rcx] */
|
||||
0x58, /* pop rax */
|
||||
0x48, 0x89, 0x6a, 0x08, /* mov [rdx+ 8],rbp */
|
||||
0x48, 0x89, 0x72, 0x10, /* mov [rdx+16],rsi */
|
||||
0x48, 0x89, 0x7a, 0x18, /* mov [rdx+24],rdi */
|
||||
0x48, 0x89, 0x5a, 0x20, /* mov [rdx+32],rbx */
|
||||
0x4c, 0x89, 0x62, 0x28, /* mov [rdx+40],r12 */
|
||||
0x4c, 0x89, 0x6a, 0x30, /* mov [rdx+48],r13 */
|
||||
0x4c, 0x89, 0x72, 0x38, /* mov [rdx+56],r14 */
|
||||
0x4c, 0x89, 0x7a, 0x40, /* mov [rdx+64],r15 */
|
||||
#if !defined(LIBCO_NO_SSE)
|
||||
0x0f, 0x29, 0x72, 0x50, /* movaps [rdx+ 80],xmm6 */
|
||||
0x0f, 0x29, 0x7a, 0x60, /* movaps [rdx+ 96],xmm7 */
|
||||
0x44, 0x0f, 0x29, 0x42, 0x70, /* movaps [rdx+112],xmm8 */
|
||||
0x48, 0x83, 0xc2, 0x70, /* add rdx,112 */
|
||||
0x44, 0x0f, 0x29, 0x4a, 0x10, /* movaps [rdx+ 16],xmm9 */
|
||||
0x44, 0x0f, 0x29, 0x52, 0x20, /* movaps [rdx+ 32],xmm10 */
|
||||
0x44, 0x0f, 0x29, 0x5a, 0x30, /* movaps [rdx+ 48],xmm11 */
|
||||
0x44, 0x0f, 0x29, 0x62, 0x40, /* movaps [rdx+ 64],xmm12 */
|
||||
0x44, 0x0f, 0x29, 0x6a, 0x50, /* movaps [rdx+ 80],xmm13 */
|
||||
0x44, 0x0f, 0x29, 0x72, 0x60, /* movaps [rdx+ 96],xmm14 */
|
||||
0x44, 0x0f, 0x29, 0x7a, 0x70, /* movaps [rdx+112],xmm15 */
|
||||
#endif
|
||||
0x48, 0x8b, 0x69, 0x08, /* mov rbp,[rcx+ 8] */
|
||||
0x48, 0x8b, 0x71, 0x10, /* mov rsi,[rcx+16] */
|
||||
0x48, 0x8b, 0x79, 0x18, /* mov rdi,[rcx+24] */
|
||||
0x48, 0x8b, 0x59, 0x20, /* mov rbx,[rcx+32] */
|
||||
0x4c, 0x8b, 0x61, 0x28, /* mov r12,[rcx+40] */
|
||||
0x4c, 0x8b, 0x69, 0x30, /* mov r13,[rcx+48] */
|
||||
0x4c, 0x8b, 0x71, 0x38, /* mov r14,[rcx+56] */
|
||||
0x4c, 0x8b, 0x79, 0x40, /* mov r15,[rcx+64] */
|
||||
#if !defined(LIBCO_NO_SSE)
|
||||
0x0f, 0x28, 0x71, 0x50, /* movaps xmm6, [rcx+ 80] */
|
||||
0x0f, 0x28, 0x79, 0x60, /* movaps xmm7, [rcx+ 96] */
|
||||
0x44, 0x0f, 0x28, 0x41, 0x70, /* movaps xmm8, [rcx+112] */
|
||||
0x48, 0x83, 0xc1, 0x70, /* add rcx,112 */
|
||||
0x44, 0x0f, 0x28, 0x49, 0x10, /* movaps xmm9, [rcx+ 16] */
|
||||
0x44, 0x0f, 0x28, 0x51, 0x20, /* movaps xmm10,[rcx+ 32] */
|
||||
0x44, 0x0f, 0x28, 0x59, 0x30, /* movaps xmm11,[rcx+ 48] */
|
||||
0x44, 0x0f, 0x28, 0x61, 0x40, /* movaps xmm12,[rcx+ 64] */
|
||||
0x44, 0x0f, 0x28, 0x69, 0x50, /* movaps xmm13,[rcx+ 80] */
|
||||
0x44, 0x0f, 0x28, 0x71, 0x60, /* movaps xmm14,[rcx+ 96] */
|
||||
0x44, 0x0f, 0x28, 0x79, 0x70, /* movaps xmm15,[rcx+112] */
|
||||
#endif
|
||||
0xff, 0xe0, /* jmp rax */
|
||||
};
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
static void co_init() {
|
||||
#ifdef LIBCO_MPROTECT
|
||||
DWORD old_privileges;
|
||||
VirtualProtect((void*)co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READ, &old_privileges);
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
/* ABI: SystemV */
|
||||
static const unsigned char co_swap_function[4096] = {
|
||||
0x48, 0x89, 0x26, /* mov [rsi],rsp */
|
||||
0x48, 0x8b, 0x27, /* mov rsp,[rdi] */
|
||||
0x58, /* pop rax */
|
||||
0x48, 0x89, 0x6e, 0x08, /* mov [rsi+ 8],rbp */
|
||||
0x48, 0x89, 0x5e, 0x10, /* mov [rsi+16],rbx */
|
||||
0x4c, 0x89, 0x66, 0x18, /* mov [rsi+24],r12 */
|
||||
0x4c, 0x89, 0x6e, 0x20, /* mov [rsi+32],r13 */
|
||||
0x4c, 0x89, 0x76, 0x28, /* mov [rsi+40],r14 */
|
||||
0x4c, 0x89, 0x7e, 0x30, /* mov [rsi+48],r15 */
|
||||
0x48, 0x8b, 0x6f, 0x08, /* mov rbp,[rdi+ 8] */
|
||||
0x48, 0x8b, 0x5f, 0x10, /* mov rbx,[rdi+16] */
|
||||
0x4c, 0x8b, 0x67, 0x18, /* mov r12,[rdi+24] */
|
||||
0x4c, 0x8b, 0x6f, 0x20, /* mov r13,[rdi+32] */
|
||||
0x4c, 0x8b, 0x77, 0x28, /* mov r14,[rdi+40] */
|
||||
0x4c, 0x8b, 0x7f, 0x30, /* mov r15,[rdi+48] */
|
||||
0xff, 0xe0, /* jmp rax */
|
||||
};
|
||||
|
||||
#ifdef LIBCO_MPROTECT
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
static void co_init() {
|
||||
#ifdef LIBCO_MPROTECT
|
||||
unsigned long long addr = (unsigned long long)co_swap_function;
|
||||
unsigned long long base = addr - (addr % sysconf(_SC_PAGESIZE));
|
||||
unsigned long long size = (addr - base) + sizeof co_swap_function;
|
||||
mprotect((void*)base, size, PROT_READ | PROT_EXEC);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
static void crash() {
|
||||
LIBCO_ASSERT(0); /* called only if cothread_t entrypoint returns */
|
||||
}
|
||||
|
||||
cothread_t co_active() {
|
||||
if(!co_active_handle) co_active_handle = &co_active_buffer;
|
||||
return co_active_handle;
|
||||
}
|
||||
|
||||
cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) {
|
||||
cothread_t handle;
|
||||
if(!co_swap) {
|
||||
co_init();
|
||||
co_swap = (void (*)(cothread_t, cothread_t))co_swap_function;
|
||||
}
|
||||
if(!co_active_handle) co_active_handle = &co_active_buffer;
|
||||
|
||||
if(handle = (cothread_t)memory) {
|
||||
unsigned int offset = (size & ~15) - 32;
|
||||
long long *p = (long long*)((char*)handle + offset); /* seek to top of stack */
|
||||
*--p = (long long)crash; /* crash if entrypoint returns */
|
||||
*--p = (long long)entrypoint; /* start of function */
|
||||
*(long long*)handle = (long long)p; /* stack pointer */
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
cothread_t co_create(unsigned int size, void (*entrypoint)(void)) {
|
||||
void* memory = LIBCO_MALLOC(size);
|
||||
if(!memory) return (cothread_t)0;
|
||||
return co_derive(memory, size, entrypoint);
|
||||
}
|
||||
|
||||
void co_delete(cothread_t handle) {
|
||||
LIBCO_FREE(handle);
|
||||
}
|
||||
|
||||
void co_switch(cothread_t handle) {
|
||||
register cothread_t co_previous_handle = co_active_handle;
|
||||
co_swap(co_active_handle = handle, co_previous_handle);
|
||||
}
|
||||
|
||||
int co_serializable() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
libco v20 (2019-10-16)
|
||||
author: byuu
|
||||
license: ISC
|
||||
*/
|
||||
|
||||
#ifndef LIBCO_H
|
||||
#define LIBCO_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void* cothread_t;
|
||||
|
||||
cothread_t co_active(void);
|
||||
cothread_t co_derive(void*, unsigned int, void (*)(void));
|
||||
cothread_t co_create(unsigned int, void (*)(void));
|
||||
void co_delete(cothread_t);
|
||||
void co_switch(cothread_t);
|
||||
int co_serializable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ifndef LIBCO_H */
|
||||
#endif
|
|
@ -1,128 +0,0 @@
|
|||
#if defined(LIBCO_C)
|
||||
|
||||
/*[amd64, arm, ppc, x86]:
|
||||
by default, co_swap_function is marked as a text (code) section
|
||||
if not supported, uncomment the below line to use mprotect instead */
|
||||
/* #define LIBCO_MPROTECT */
|
||||
|
||||
/*[amd64]:
|
||||
Win64 only: provides a substantial speed-up, but will thrash XMM regs
|
||||
do not use this unless you are certain your application won't use SSE */
|
||||
/* #define LIBCO_NO_SSE */
|
||||
|
||||
#if !defined(thread_local) /* User can override thread_local for obscure compilers */
|
||||
#if !defined(LIBCO_MP) /* Running in single-threaded environment */
|
||||
#define thread_local
|
||||
#else /* Running in multi-threaded environment */
|
||||
#if defined(__STDC__) /* Compiling as C Language */
|
||||
#if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */
|
||||
#define thread_local __declspec(thread)
|
||||
#elif __STDC_VERSION__ < 201112L /* If we are on C90/99 */
|
||||
#if defined(__clang__) || defined(__GNUC__) /* Clang and GCC */
|
||||
#define thread_local __thread
|
||||
#else /* Otherwise, we ignore the directive (unless user provides their own) */
|
||||
#define thread_local
|
||||
#endif
|
||||
#else /* C11 and newer define thread_local in threads.h */
|
||||
#include <threads.h>
|
||||
#endif
|
||||
#elif defined(__cplusplus) /* Compiling as C++ Language */
|
||||
#if __cplusplus < 201103L /* thread_local is a C++11 feature */
|
||||
#if defined(_MSC_VER)
|
||||
#define thread_local __declspec(thread)
|
||||
#elif defined(__clang__) || defined(__GNUC__)
|
||||
#define thread_local __thread
|
||||
#else /* Otherwise, we ignore the directive (unless user provides their own) */
|
||||
#define thread_local
|
||||
#endif
|
||||
#else /* In C++ >= 11, thread_local in a builtin keyword */
|
||||
/* Don't do anything */
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* In alignas(a), 'a' should be a power of two that is at least the type's
|
||||
alignment and at most the implementation's alignment limit. This limit is
|
||||
2**13 on MSVC. To be portable to MSVC through at least version 10.0,
|
||||
'a' should be an integer constant, as MSVC does not support expressions
|
||||
such as 1 << 3.
|
||||
|
||||
The following C11 requirements are NOT supported on MSVC:
|
||||
|
||||
- If 'a' is zero, alignas has no effect.
|
||||
- alignas can be used multiple times; the strictest one wins.
|
||||
- alignas (TYPE) is equivalent to alignas (alignof (TYPE)).
|
||||
*/
|
||||
#if !defined(alignas)
|
||||
#if defined(__STDC__) /* C Language */
|
||||
#if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */
|
||||
#define alignas(bytes) __declspec(align(bytes))
|
||||
#elif __STDC_VERSION__ >= 201112L /* C11 and above */
|
||||
#include <stdalign.h>
|
||||
#elif defined(__clang__) || defined(__GNUC__) /* C90/99 on Clang/GCC */
|
||||
#define alignas(bytes) __attribute__ ((aligned (bytes)))
|
||||
#else /* Otherwise, we ignore the directive (user should provide their own) */
|
||||
#define alignas(bytes)
|
||||
#endif
|
||||
#elif defined(__cplusplus) /* C++ Language */
|
||||
#if __cplusplus < 201103L
|
||||
#if defined(_MSC_VER)
|
||||
#define alignas(bytes) __declspec(align(bytes))
|
||||
#elif defined(__clang__) || defined(__GNUC__) /* C++98/03 on Clang/GCC */
|
||||
#define alignas(bytes) __attribute__ ((aligned (bytes)))
|
||||
#else /* Otherwise, we ignore the directive (unless user provides their own) */
|
||||
#define alignas(bytes)
|
||||
#endif
|
||||
#else /* C++ >= 11 has alignas keyword */
|
||||
/* Do nothing */
|
||||
#endif
|
||||
#endif /* = !defined(__STDC_VERSION__) && !defined(__cplusplus) */
|
||||
#endif
|
||||
|
||||
#if !defined(LIBCO_ASSERT)
|
||||
#include <assert.h>
|
||||
#define LIBCO_ASSERT assert
|
||||
#endif
|
||||
|
||||
#if defined (__OpenBSD__)
|
||||
#if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE)
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
static void* malloc_obsd(size_t size) {
|
||||
long pagesize = sysconf(_SC_PAGESIZE);
|
||||
char* memory = (char*)mmap(NULL, size + pagesize, PROT_READ|PROT_WRITE, MAP_STACK|MAP_PRIVATE|MAP_ANON, -1, 0);
|
||||
if (memory == MAP_FAILED) return NULL;
|
||||
*(size_t*)memory = size + pagesize;
|
||||
memory += pagesize;
|
||||
return (void*)memory;
|
||||
}
|
||||
|
||||
static void free_obsd(void *ptr) {
|
||||
char* memory = (char*)ptr - sysconf(_SC_PAGESIZE);
|
||||
munmap(memory, *(size_t*)memory);
|
||||
}
|
||||
|
||||
#define LIBCO_MALLOC malloc_obsd
|
||||
#define LIBCO_FREE free_obsd
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE)
|
||||
#include <stdlib.h>
|
||||
#define LIBCO_MALLOC malloc
|
||||
#define LIBCO_FREE free
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define section(name) __declspec(allocate("." #name))
|
||||
#elif defined(__APPLE__)
|
||||
#define section(name) __attribute__((section("__TEXT,__" #name)))
|
||||
#else
|
||||
#define section(name) __attribute__((section("." #name "#")))
|
||||
#endif
|
||||
|
||||
|
||||
/* if defined(LIBCO_C) */
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
|
@ -1,874 +0,0 @@
|
|||
//derived from libsnes
|
||||
//types of messages:
|
||||
//cmd: frontend->core: "command to core" a command from the frontend which causes emulation to proceed. when sending a command, the frontend should wait for an eMessage::BRK_Complete before proceeding, although a debugger might proceed after any BRK
|
||||
//query: frontend->core: "query to core" a query from the frontend which can (and should) be satisfied immediately by the core but which does not result in emulation processes (notably, nothing resembling a CMD and nothing which can trigger a BRK)
|
||||
//sig: core->frontend: "core signal" a synchronous operation called from the emulation process which the frontend should handle immediately without issuing any calls into the core
|
||||
//brk: core->frontend: "core break" the emulation process has suspended. the frontend is free to do whatever it wishes.
|
||||
|
||||
#define _CRT_NONSTDC_NO_DEPRECATE
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#define bool unsigned char
|
||||
#include "../libretro.h"
|
||||
#undef bool
|
||||
|
||||
extern "C" uint64_t cpu_features_get();
|
||||
|
||||
#include "../libco/libco.h"
|
||||
|
||||
//can't use retroarch's dynamic.h, it's too full of weird stuff. don't need it anyway
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint64_t u64;
|
||||
typedef uint32_t u32;
|
||||
|
||||
typedef u8 u8bool;
|
||||
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t s64;
|
||||
|
||||
typedef void(*Action)();
|
||||
|
||||
struct retro_core_t
|
||||
{
|
||||
void(*retro_init)(void);
|
||||
void(*retro_deinit)(void);
|
||||
unsigned(*retro_api_version)(void);
|
||||
void(*retro_get_system_info)(struct retro_system_info*);
|
||||
void(*retro_get_system_av_info)(struct retro_system_av_info*);
|
||||
void(*retro_set_environment)(retro_environment_t);
|
||||
void(*retro_set_video_refresh)(retro_video_refresh_t);
|
||||
void(*retro_set_audio_sample)(retro_audio_sample_t);
|
||||
void(*retro_set_audio_sample_batch)(retro_audio_sample_batch_t);
|
||||
void(*retro_set_input_poll)(retro_input_poll_t);
|
||||
void(*retro_set_input_state)(retro_input_state_t);
|
||||
void(*retro_set_controller_port_device)(unsigned, unsigned);
|
||||
void(*retro_reset)(void);
|
||||
void(*retro_run)(void);
|
||||
size_t(*retro_serialize_size)(void);
|
||||
u8bool(*retro_serialize)(void*, size_t);
|
||||
u8bool(*retro_unserialize)(const void*, size_t);
|
||||
void(*retro_cheat_reset)(void);
|
||||
void(*retro_cheat_set)(unsigned, u8bool, const char*);
|
||||
u8bool(*retro_load_game)(const struct retro_game_info*);
|
||||
u8bool(*retro_load_game_special)(unsigned,
|
||||
const struct retro_game_info*, size_t);
|
||||
void(*retro_unload_game)(void);
|
||||
unsigned(*retro_get_region)(void);
|
||||
void *(*retro_get_memory_data)(unsigned);
|
||||
size_t(*retro_get_memory_size)(unsigned);
|
||||
};
|
||||
|
||||
enum eMessage : s32
|
||||
{
|
||||
NotSet,
|
||||
|
||||
Resume,
|
||||
|
||||
QUERY_FIRST,
|
||||
QUERY_GetMemory,
|
||||
QUERY_LAST,
|
||||
|
||||
CMD_FIRST,
|
||||
CMD_SetEnvironment,
|
||||
CMD_LoadNoGame,
|
||||
CMD_LoadData,
|
||||
CMD_LoadPath,
|
||||
CMD_Deinit,
|
||||
CMD_Reset,
|
||||
CMD_Run,
|
||||
CMD_UpdateSerializeSize,
|
||||
CMD_Serialize,
|
||||
CMD_Unserialize,
|
||||
CMD_LAST,
|
||||
|
||||
SIG_InputState,
|
||||
SIG_VideoUpdate,
|
||||
SIG_Sample,
|
||||
SIG_SampleBatch,
|
||||
};
|
||||
|
||||
enum eStatus : s32
|
||||
{
|
||||
eStatus_Idle,
|
||||
eStatus_CMD,
|
||||
eStatus_BRK
|
||||
};
|
||||
|
||||
enum BufId : s32 {
|
||||
Param0 = 0,
|
||||
Param1 = 1,
|
||||
SystemDirectory = 2,
|
||||
SaveDirectory = 3,
|
||||
CoreDirectory = 4,
|
||||
CoreAssetsDirectory = 5,
|
||||
BufId_Num //excess sized by 1.. no big deal
|
||||
};
|
||||
|
||||
//TODO: do any of these need to be volatile?
|
||||
struct CommStruct
|
||||
{
|
||||
//the cmd being executed
|
||||
eMessage cmd;
|
||||
|
||||
//the status of the core
|
||||
eStatus status;
|
||||
|
||||
//the SIG or BRK that the core is halted in
|
||||
eMessage reason;
|
||||
|
||||
//flexible in/out parameters
|
||||
//these are all "overloaded" a little so it isn't clear what's used for what in for any particular message..
|
||||
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
|
||||
u32 id, addr, value, size;
|
||||
u32 port, device, index, slot; //for input state
|
||||
|
||||
//variables meant for stateful communication (not parameters)
|
||||
//may be in, out, or inout. it's pretty sloppy.
|
||||
struct {
|
||||
//set by the core
|
||||
retro_system_info retro_system_info;
|
||||
retro_system_av_info retro_system_av_info;
|
||||
size_t retro_serialize_size_initial;
|
||||
size_t retro_serialize_size;
|
||||
u32 retro_region;
|
||||
u32 retro_api_version;
|
||||
retro_pixel_format pixel_format; //default is 0 -- RETRO_PIXEL_FORMAT_0RGB1555
|
||||
s32 rotation_ccw;
|
||||
bool support_no_game;
|
||||
retro_get_proc_address_t core_get_proc_address;
|
||||
|
||||
retro_game_geometry retro_game_geometry;
|
||||
u8bool retro_game_geometry_dirty; //c# can clear this when it's acknowledged (but I think we might handle it from here? not sure)
|
||||
|
||||
//defined by the core. values arent put here, this is just the variables defined by the core
|
||||
//todo: shutdown tidy
|
||||
s32 variable_count;
|
||||
const char** variable_keys;
|
||||
const char** variable_comments;
|
||||
|
||||
//c# sets these with thunked callbacks
|
||||
retro_perf_callback retro_perf_callback;
|
||||
|
||||
//various stashed stuff solely for c# convenience
|
||||
u64 processor_features;
|
||||
|
||||
s32 fb_width, fb_height; //core sets these; c# picks up, and..
|
||||
s32* fb_bufptr; //..sets this for the core to spill its data nito
|
||||
|
||||
} env;
|
||||
|
||||
//always used in pairs
|
||||
void* buf[BufId_Num];
|
||||
size_t buf_size[BufId_Num];
|
||||
|
||||
//===========================================================
|
||||
//private stuff
|
||||
|
||||
std::string *variables;
|
||||
bool variables_dirty;
|
||||
|
||||
|
||||
void* privbuf[BufId_Num]; //TODO remember to tidy this.. (needs to be done in snes too)
|
||||
void SetString(int id, const char* str)
|
||||
{
|
||||
size_t len = strlen(str);
|
||||
CopyBuffer(id, (void*)str, len+1);
|
||||
}
|
||||
void CopyBuffer(int id, void* ptr, size_t size)
|
||||
{
|
||||
if (privbuf[id]) free(privbuf[id]);
|
||||
buf[id] = privbuf[id] = malloc(size);
|
||||
memcpy(buf[id], ptr, size);
|
||||
buf_size[id] = size;
|
||||
}
|
||||
|
||||
void SetBuffer(int id, void* ptr, size_t size)
|
||||
{
|
||||
buf[id] = ptr;
|
||||
buf_size[id] = size;
|
||||
}
|
||||
|
||||
struct {
|
||||
} strings;
|
||||
|
||||
HMODULE dllModule;
|
||||
retro_core_t funs;
|
||||
|
||||
void LoadSymbols()
|
||||
{
|
||||
//retroarch would throw an error here if the FP ws null. maybe better than throwing an error later, but are all the functions required?
|
||||
# define SYMBOL(x) { \
|
||||
FARPROC func = GetProcAddress(dllModule, #x); \
|
||||
memcpy(&funs.x, &func, sizeof(func)); \
|
||||
}
|
||||
|
||||
SYMBOL(retro_init);
|
||||
SYMBOL(retro_deinit);
|
||||
|
||||
SYMBOL(retro_api_version);
|
||||
SYMBOL(retro_get_system_info);
|
||||
SYMBOL(retro_get_system_av_info);
|
||||
|
||||
SYMBOL(retro_set_environment);
|
||||
SYMBOL(retro_set_video_refresh);
|
||||
SYMBOL(retro_set_audio_sample);
|
||||
SYMBOL(retro_set_audio_sample_batch);
|
||||
SYMBOL(retro_set_input_poll);
|
||||
SYMBOL(retro_set_input_state);
|
||||
|
||||
SYMBOL(retro_set_controller_port_device);
|
||||
|
||||
SYMBOL(retro_reset);
|
||||
SYMBOL(retro_run);
|
||||
|
||||
SYMBOL(retro_serialize_size);
|
||||
SYMBOL(retro_serialize);
|
||||
SYMBOL(retro_unserialize);
|
||||
|
||||
SYMBOL(retro_cheat_reset);
|
||||
SYMBOL(retro_cheat_set);
|
||||
|
||||
SYMBOL(retro_load_game);
|
||||
SYMBOL(retro_load_game_special);
|
||||
|
||||
SYMBOL(retro_unload_game);
|
||||
SYMBOL(retro_get_region);
|
||||
SYMBOL(retro_get_memory_data);
|
||||
SYMBOL(retro_get_memory_size);
|
||||
}
|
||||
|
||||
retro_core_t fn;
|
||||
|
||||
} comm;
|
||||
|
||||
//coroutines
|
||||
cothread_t co_control, co_emu, co_emu_suspended;
|
||||
|
||||
//internal state
|
||||
Action CMD_cb;
|
||||
|
||||
void BREAK(eMessage msg) {
|
||||
comm.status = eStatus_BRK;
|
||||
comm.reason = msg;
|
||||
co_emu_suspended = co_active();
|
||||
co_switch(co_control);
|
||||
comm.status = eStatus_CMD;
|
||||
}
|
||||
|
||||
//all this does is run commands on the emulation thread infinitely forever
|
||||
//(I should probably make a mechanism for bailing...)
|
||||
void new_emuthread()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
//process the current CMD
|
||||
CMD_cb();
|
||||
|
||||
//when that returned, we're definitely done with the CMD--so we're now IDLE
|
||||
comm.status = eStatus_Idle;
|
||||
|
||||
co_switch(co_control);
|
||||
}
|
||||
}
|
||||
|
||||
void retro_log_printf(enum retro_log_level level, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vprintf(fmt,args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
u8bool retro_environment(unsigned cmd, void *data)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case RETRO_ENVIRONMENT_SET_ROTATION:
|
||||
comm.env.rotation_ccw = (int)*(const unsigned*)data * 90;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_GET_OVERSCAN:
|
||||
return false; //could return true to crop overscan
|
||||
case RETRO_ENVIRONMENT_GET_CAN_DUPE:
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_MESSAGE:
|
||||
{
|
||||
//TODO: try to respect design principle by forwarding to frontend with the timer
|
||||
auto &msg = *(retro_message*)data;
|
||||
printf("%s\n",msg.msg);
|
||||
return true;
|
||||
}
|
||||
case RETRO_ENVIRONMENT_SHUTDOWN:
|
||||
//TODO low priority
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL:
|
||||
//unneeded
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
|
||||
*(const char**)data = (const char*)comm.buf[SystemDirectory];
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
|
||||
comm.env.pixel_format = *(const enum retro_pixel_format*)data;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
|
||||
//TODO medium priority
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK:
|
||||
//TODO high priority (to support keyboard consoles, probably high value for us. but that may take a lot of infrastructure work)
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE:
|
||||
//TODO high priority (to support disc systems)
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_HW_RENDER:
|
||||
//TODO high priority (to support 3d renderers
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_VARIABLE:
|
||||
{
|
||||
//according to retroarch's `core_option_manager_get` this is what we should do
|
||||
comm.variables_dirty = false;
|
||||
|
||||
auto req = (retro_variable *)data;
|
||||
req->value = nullptr;
|
||||
|
||||
for(int i=0;i<comm.env.variable_count;i++)
|
||||
{
|
||||
if(!strcmp(comm.env.variable_keys[i],req->key))
|
||||
{
|
||||
req->value = comm.variables[i].c_str();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_VARIABLES:
|
||||
{
|
||||
auto var = (retro_variable *)data;
|
||||
int nVars = 0;
|
||||
while(var->key)
|
||||
nVars++, var++;
|
||||
comm.variables = new std::string[nVars];
|
||||
comm.env.variable_count = nVars;
|
||||
comm.env.variable_keys = new const char*[nVars];
|
||||
comm.env.variable_comments = new const char*[nVars];
|
||||
var = (retro_variable *)data;
|
||||
for(int i=0;i<nVars;i++)
|
||||
{
|
||||
comm.env.variable_keys[i] = var[i].key;
|
||||
comm.env.variable_comments[i] = var[i].value;
|
||||
|
||||
//analyze to find default and save it
|
||||
std::string comment = var[i].value;
|
||||
auto ofs = comment.find_first_of(';')+2;
|
||||
auto pipe = comment.find('|',ofs);
|
||||
if(pipe == std::string::npos)
|
||||
comm.variables[i] = comment.substr(ofs);
|
||||
else
|
||||
comm.variables[i] = comment.substr(ofs,pipe-ofs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
|
||||
*(u8bool*)data = comm.variables_dirty;
|
||||
break;
|
||||
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
|
||||
comm.env.support_no_game = !!*(u8bool*)data;
|
||||
break;
|
||||
case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH:
|
||||
*(const char**)data = (const char*)comm.buf[CoreDirectory];
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK:
|
||||
//dont know what to do with this yet
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK:
|
||||
//dont know what to do with this yet
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE:
|
||||
//TODO low priority
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES:
|
||||
//TODO medium priority - other input methods
|
||||
*(u64*)data = (1<<RETRO_DEVICE_JOYPAD);
|
||||
return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
|
||||
((retro_log_callback*)data)->log = retro_log_printf;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_GET_PERF_INTERFACE:
|
||||
*((retro_perf_callback *)data) = comm.env.retro_perf_callback;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE:
|
||||
//TODO low priority
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
|
||||
*(const char**)data = (const char*)comm.buf[CoreAssetsDirectory];
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
|
||||
*(const char**)data = (const char*)comm.buf[SaveDirectory];
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
|
||||
printf("NEED RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO\n");
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK:
|
||||
comm.env.core_get_proc_address = ((retro_get_proc_address_interface*)data)->get_proc_address;
|
||||
return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
|
||||
//needs retro_load_game_special to be useful; not supported yet
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
|
||||
//TODO medium priority probably
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_GEOMETRY:
|
||||
comm.env.retro_game_geometry = *((const retro_game_geometry *)data);
|
||||
comm.env.retro_game_geometry_dirty = true;
|
||||
return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_USERNAME:
|
||||
//we definitely want to return false here so the core will do something deterministic
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_LANGUAGE:
|
||||
*((unsigned *)data) = RETRO_LANGUAGE_ENGLISH;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<int ROT> static inline int* address(int width, int height, int pitch, int x, int y, int* dstbuf, int* optimize0dst)
|
||||
{
|
||||
switch (ROT)
|
||||
{
|
||||
case 0:
|
||||
return optimize0dst;
|
||||
|
||||
case 90:
|
||||
//TODO:
|
||||
return optimize0dst;
|
||||
|
||||
case 180:
|
||||
//TODO:
|
||||
return optimize0dst;
|
||||
|
||||
case 270:
|
||||
{
|
||||
int dx = width - y - 1;
|
||||
int dy = x;
|
||||
return dstbuf + dy * width + dx;
|
||||
}
|
||||
|
||||
default:
|
||||
//impossible
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<int ROT> void Blit555(short* srcbuf, s32* dstbuf, int width, int height, int pitch)
|
||||
{
|
||||
s32* dst = dstbuf;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
short* row = srcbuf;
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
short ci = *row;
|
||||
int r = ci & 0x001f;
|
||||
int g = ci & 0x03e0;
|
||||
int b = ci & 0x7c00;
|
||||
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g >> 2) | (g >> 7);
|
||||
b = (b >> 7) | (b >> 12);
|
||||
int co = r | g | b | 0xff000000;
|
||||
|
||||
*address<ROT>(width, height, pitch, x, y, dstbuf, dst) = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
srcbuf += pitch/2;
|
||||
}
|
||||
}
|
||||
|
||||
template<int ROT> void Blit565(short* srcbuf, s32* dstbuf, int width, int height, int pitch)
|
||||
{
|
||||
s32* dst = dstbuf;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
short* row = srcbuf;
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
short ci = *row;
|
||||
int r = ci & 0x001f;
|
||||
int g = (ci & 0x07e0) >> 5;
|
||||
int b = (ci & 0xf800) >> 11;
|
||||
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g << 2) | (g >> 4);
|
||||
b = (b << 3) | (b >> 2);
|
||||
int co = (b << 16) | (g << 8) | r;
|
||||
|
||||
*address<ROT>(width, height, pitch, x, y, dstbuf, dst) = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
srcbuf += pitch/2;
|
||||
}
|
||||
}
|
||||
|
||||
template<int ROT> void Blit888(int* srcbuf, s32* dstbuf, int width, int height, int pitch)
|
||||
{
|
||||
s32* dst = dstbuf;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
int* row = srcbuf;
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int ci = *row;
|
||||
int co = ci | 0xff000000;
|
||||
*address<ROT>(width,height,pitch,x,y,dstbuf,dst) = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
srcbuf += pitch/4;
|
||||
}
|
||||
}
|
||||
|
||||
void retro_video_refresh(const void *data, unsigned width, unsigned height, size_t pitch)
|
||||
{
|
||||
//handle a "dup frame" -- same as previous frame. so there isn't anything to be done here
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
comm.env.fb_width = (s32)width;
|
||||
comm.env.fb_height = (s32)height;
|
||||
//stash pitch if needed
|
||||
|
||||
//notify c# of these new settings and let it allocate a buffer suitable for receiving the output (so we can work directly into c#'s int[])
|
||||
//c# can read the settings right out of the comm env
|
||||
BREAK(eMessage::SIG_VideoUpdate);
|
||||
|
||||
|
||||
////if (BufferWidth != width) BufferWidth = (int)width;
|
||||
////if (BufferHeight != height) BufferHeight = (int)height;
|
||||
////if (BufferWidth * BufferHeight != rawvidbuff.Length)
|
||||
//// rawvidbuff = new int[BufferWidth * BufferHeight];
|
||||
|
||||
////if we have rotation, we might have a geometry mismatch and in any event we need a temp buffer to do the rotation from
|
||||
////but that's a general problem, isnt it?
|
||||
//if (comm.env.fb.raw == nullptr || comm.env.fb.raw_length != width * height)
|
||||
//{
|
||||
// if(comm.env.fb.raw)
|
||||
// delete[] comm.env.fb.raw;
|
||||
// comm.env.fb.raw = new u32[width * height];
|
||||
// comm.env.fb.width = width;
|
||||
// comm.env.fb.height = height;
|
||||
//}
|
||||
|
||||
int w = (int)width;
|
||||
int h = (int)height;
|
||||
int p = (int)pitch;
|
||||
|
||||
switch(comm.env.pixel_format)
|
||||
{
|
||||
|
||||
case RETRO_PIXEL_FORMAT_0RGB1555:
|
||||
switch (comm.env.rotation_ccw)
|
||||
{
|
||||
case 0: Blit555<0>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 90: Blit555<90>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 180: Blit555<180>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 270: Blit555<270>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RETRO_PIXEL_FORMAT_XRGB8888:
|
||||
switch(comm.env.rotation_ccw)
|
||||
{
|
||||
case 0: Blit888<0>((int*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 90: Blit888<90>((int*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 180: Blit888<180>((int*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 270: Blit888<270>((int*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RETRO_PIXEL_FORMAT_RGB565:
|
||||
switch (comm.env.rotation_ccw)
|
||||
{
|
||||
case 0: Blit565<0>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 90: Blit565<90>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 180: Blit565<180>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 270: Blit565<270>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void retro_audio_sample(s16 left, s16 right)
|
||||
{
|
||||
s16 samples[] = {left,right};
|
||||
comm.SetBuffer(BufId::Param0,(void*)&samples,4);
|
||||
BREAK(SIG_Sample);
|
||||
}
|
||||
size_t retro_audio_sample_batch(const s16 *data, size_t frames)
|
||||
{
|
||||
comm.SetBuffer(BufId::Param0, (void*)data, frames*4);
|
||||
BREAK(SIG_SampleBatch);
|
||||
return frames;
|
||||
}
|
||||
void retro_input_poll()
|
||||
{
|
||||
}
|
||||
s16 retro_input_state(unsigned port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
//we have to bail to c# for this, it's too complex.
|
||||
comm.port = port;
|
||||
comm.device = device;
|
||||
comm.index = index;
|
||||
comm.id = id;
|
||||
|
||||
BREAK(eMessage::SIG_InputState);
|
||||
|
||||
return (s16)comm.value;
|
||||
}
|
||||
|
||||
//loads the game, too
|
||||
//REQUIREMENTS:
|
||||
//set SystemDirectory, SaveDirectory, CoreDirectory, CoreAssetsDirectory are set
|
||||
//retro_perf_callback is set
|
||||
static void LoadHandler(eMessage msg)
|
||||
{
|
||||
//retro_set_environment() is guaranteed to be called before retro_init().
|
||||
|
||||
comm.funs.retro_init();
|
||||
|
||||
retro_game_info rgi;
|
||||
retro_game_info* rgiptr = &rgi;
|
||||
memset(&rgi,0,sizeof(rgi));
|
||||
|
||||
if (msg == eMessage::CMD_LoadNoGame)
|
||||
{
|
||||
rgiptr = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
rgi.path = (const char*)comm.buf[BufId::Param0];
|
||||
if (msg == eMessage::CMD_LoadData)
|
||||
{
|
||||
rgi.data = comm.buf[BufId::Param1];
|
||||
rgi.size = comm.buf_size[BufId::Param1];
|
||||
}
|
||||
}
|
||||
|
||||
comm.funs.retro_load_game(rgiptr);
|
||||
|
||||
//Can be called only after retro_load_game() has successfully completed.
|
||||
comm.funs.retro_get_system_av_info(&comm.env.retro_system_av_info);
|
||||
|
||||
//guaranteed to have been called before the first call to retro_run() is made.
|
||||
//(I've put this after the retro_system_av_info runs, in case that's important
|
||||
comm.funs.retro_set_video_refresh(retro_video_refresh);
|
||||
|
||||
comm.funs.retro_set_audio_sample(retro_audio_sample);
|
||||
comm.funs.retro_set_audio_sample_batch(retro_audio_sample_batch);
|
||||
comm.funs.retro_set_input_poll(retro_input_poll);
|
||||
comm.funs.retro_set_input_state(retro_input_state);
|
||||
|
||||
//Between calls to retro_load_game() and retro_unload_game(), the returned size is never allowed to be larger than a previous returned
|
||||
//value, to ensure that the frontend can allocate a save state buffer once.
|
||||
comm.env.retro_serialize_size_initial = comm.env.retro_serialize_size = comm.funs.retro_serialize_size();
|
||||
|
||||
//not sure when this can be called, but it's surely safe here
|
||||
comm.env.retro_region = comm.funs.retro_get_region();
|
||||
}
|
||||
|
||||
void cmd_LoadNoGame() { LoadHandler(eMessage::CMD_LoadNoGame); }
|
||||
void cmd_LoadData() { LoadHandler(eMessage::CMD_LoadData); }
|
||||
void cmd_LoadPath() { LoadHandler(eMessage::CMD_LoadPath); }
|
||||
|
||||
void cmd_Deinit()
|
||||
{
|
||||
//not sure if we need this
|
||||
comm.funs.retro_unload_game();
|
||||
comm.funs.retro_deinit();
|
||||
//TODO: tidy
|
||||
}
|
||||
|
||||
void cmd_Reset()
|
||||
{
|
||||
comm.funs.retro_reset();
|
||||
}
|
||||
|
||||
void cmd_Run()
|
||||
{
|
||||
comm.funs.retro_run();
|
||||
}
|
||||
|
||||
void cmd_UpdateSerializeSize()
|
||||
{
|
||||
comm.env.retro_serialize_size = comm.funs.retro_serialize_size();
|
||||
}
|
||||
|
||||
void cmd_Serialize()
|
||||
{
|
||||
comm.value = !!comm.funs.retro_serialize(comm.buf[BufId::Param0], comm.buf_size[BufId::Param0]);
|
||||
}
|
||||
|
||||
void cmd_Unserialize()
|
||||
{
|
||||
comm.value = !!comm.funs.retro_unserialize(comm.buf[BufId::Param0], comm.buf_size[BufId::Param0]);
|
||||
}
|
||||
|
||||
//TODO
|
||||
//void(*retro_set_controller_port_device)(unsigned, unsigned);
|
||||
//void *(*retro_get_memory_data)(unsigned);
|
||||
//size_t(*retro_get_memory_size)(unsigned);
|
||||
|
||||
//TODO low priority
|
||||
//void(*retro_cheat_reset)(void);
|
||||
//void(*retro_cheat_set)(unsigned, bool, const char*);
|
||||
//bool(*retro_load_game_special)(unsigned,
|
||||
|
||||
//TODO maybe not sensible though
|
||||
//void(*retro_unload_game)(void);
|
||||
|
||||
void cmd_SetEnvironment()
|
||||
{
|
||||
//stuff that can't be done until our environment is setup (the core will immediately query the environment)
|
||||
comm.funs.retro_set_environment(retro_environment);
|
||||
}
|
||||
|
||||
void query_GetMemory()
|
||||
{
|
||||
comm.buf_size[BufId::Param0] = comm.funs.retro_get_memory_size(comm.value);
|
||||
comm.buf[BufId::Param0] = comm.funs.retro_get_memory_data(comm.value);
|
||||
}
|
||||
|
||||
const Action kHandlers_CMD[] = {
|
||||
cmd_SetEnvironment,
|
||||
cmd_LoadNoGame,
|
||||
cmd_LoadData,
|
||||
cmd_LoadPath,
|
||||
cmd_Deinit,
|
||||
cmd_Reset,
|
||||
cmd_Run,
|
||||
cmd_UpdateSerializeSize,
|
||||
cmd_Serialize,
|
||||
cmd_Unserialize,
|
||||
};
|
||||
|
||||
const Action kHandlers_QUERY[] = {
|
||||
query_GetMemory,
|
||||
};
|
||||
|
||||
//------------------------------------------------
|
||||
//DLL INTERFACE
|
||||
|
||||
BOOL WINAPI DllMain(_In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ LPVOID lpvReserved)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
extern "C" __declspec(dllexport) void* __cdecl DllInit(HMODULE dllModule)
|
||||
{
|
||||
memset(&comm,0,sizeof(comm));
|
||||
|
||||
//make a coroutine thread to run the emulation in. we'll switch back to this cothread when communicating with the frontend
|
||||
co_control = co_active();
|
||||
co_emu = co_create(128*1024 * sizeof(void*), new_emuthread);
|
||||
|
||||
//grab all the function pointers we need.
|
||||
comm.dllModule = dllModule;
|
||||
comm.LoadSymbols();
|
||||
|
||||
//libretro startup steps
|
||||
//"Can be called at any time, even before retro_init()."
|
||||
comm.funs.retro_get_system_info(&comm.env.retro_system_info);
|
||||
comm.env.retro_api_version = (u32)comm.funs.retro_api_version();
|
||||
|
||||
//now after this we return to the c# side to let some more setup happen
|
||||
|
||||
return &comm;
|
||||
}
|
||||
|
||||
|
||||
extern "C" __declspec(dllexport) void __cdecl Message(eMessage msg)
|
||||
{
|
||||
if (msg == eMessage::Resume)
|
||||
{
|
||||
cothread_t temp = co_emu_suspended;
|
||||
co_emu_suspended = NULL;
|
||||
co_switch(temp);
|
||||
}
|
||||
|
||||
if (msg >= eMessage::CMD_FIRST && msg <= eMessage::CMD_LAST)
|
||||
{
|
||||
//CMD is only valid if status is idle
|
||||
if (comm.status != eStatus_Idle)
|
||||
{
|
||||
printf("ERROR: cmd during non-idle\n");
|
||||
return;
|
||||
}
|
||||
|
||||
comm.status = eStatus_CMD;
|
||||
comm.cmd = msg;
|
||||
|
||||
CMD_cb = kHandlers_CMD[msg - eMessage::CMD_FIRST - 1];
|
||||
co_switch(co_emu);
|
||||
|
||||
//we could be in ANY STATE when we return from here
|
||||
}
|
||||
|
||||
//QUERY can run any time
|
||||
//but... some of them might not be safe for re-entrancy.
|
||||
//later, we should have metadata for messages that indicates that
|
||||
if (msg >= eMessage::QUERY_FIRST && msg <= eMessage::QUERY_LAST)
|
||||
{
|
||||
Action cb = kHandlers_QUERY[msg - eMessage::QUERY_FIRST - 1];
|
||||
if (cb) cb();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//receives the given buffer and COPIES it. use this for returning values from SIGs
|
||||
extern "C" __declspec(dllexport) void __cdecl CopyBuffer(int id, void* ptr, s32 size)
|
||||
{
|
||||
comm.CopyBuffer(id, ptr, size);
|
||||
}
|
||||
|
||||
//receives the given buffer and STASHES IT. use this (carefully) for sending params for CMDs
|
||||
extern "C" __declspec(dllexport) void __cdecl SetBuffer(int id, void* ptr, s32 size)
|
||||
{
|
||||
comm.SetBuffer(id, ptr, size);
|
||||
}
|
||||
|
||||
extern "C" __declspec(dllexport) void __cdecl SetVariable(const char* key, const char* val)
|
||||
{
|
||||
for(int i=0;i<comm.env.variable_count;i++)
|
||||
if(!strcmp(key,comm.env.variable_keys[i]))
|
||||
{
|
||||
comm.variables[i] = val;
|
||||
comm.variables_dirty = true;
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LibretroBridge", "LibretroBridge.vcxproj", "{AEACAA89-FDA2-40C6-910C-85AEB9726452}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{AEACAA89-FDA2-40C6-910C-85AEB9726452}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{AEACAA89-FDA2-40C6-910C-85AEB9726452}.Debug|x64.Build.0 = Debug|x64
|
||||
{AEACAA89-FDA2-40C6-910C-85AEB9726452}.Release|x64.ActiveCfg = Release|x64
|
||||
{AEACAA89-FDA2-40C6-910C-85AEB9726452}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -1,125 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\features_cpu.c">
|
||||
<EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
</EnableEnhancedInstructionSet>
|
||||
<EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotSet</EnableEnhancedInstructionSet>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\libco\amd64.c">
|
||||
<EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotSet</EnableEnhancedInstructionSet>
|
||||
<EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotSet</EnableEnhancedInstructionSet>
|
||||
</ClCompile>
|
||||
<ClCompile Include="LibretroBridge.cpp">
|
||||
<EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotSet</EnableEnhancedInstructionSet>
|
||||
<EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotSet</EnableEnhancedInstructionSet>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\libco\libco.h" />
|
||||
<ClInclude Include="..\libco\settings.h" />
|
||||
<ClInclude Include="..\libretro.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{AEACAA89-FDA2-40C6-910C-85AEB9726452}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>LibretroBridge</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>..\..\output\dll\</OutDir>
|
||||
<IntDir>.obj\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>..\..\output\dll\</OutDir>
|
||||
<IntDir>.obj\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;LIBRETRO_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>false</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;LIBRETRO_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalOptions>/pdbaltpath:%_PDB% %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||
</ImportGroup>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties />
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
|
@ -1,873 +0,0 @@
|
|||
//derived from libsnes
|
||||
//types of messages:
|
||||
//cmd: frontend->core: "command to core" a command from the frontend which causes emulation to proceed. when sending a command, the frontend should wait for an eMessage::BRK_Complete before proceeding, although a debugger might proceed after any BRK
|
||||
//query: frontend->core: "query to core" a query from the frontend which can (and should) be satisfied immediately by the core but which does not result in emulation processes (notably, nothing resembling a CMD and nothing which can trigger a BRK)
|
||||
//sig: core->frontend: "core signal" a synchronous operation called from the emulation process which the frontend should handle immediately without issuing any calls into the core
|
||||
//brk: core->frontend: "core break" the emulation process has suspended. the frontend is free to do whatever it wishes.
|
||||
|
||||
#define _CRT_NONSTDC_NO_DEPRECATE
|
||||
|
||||
// #include <Windows.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
// #define bool unsigned char
|
||||
#include "../libretro.h"
|
||||
// #undef bool
|
||||
|
||||
extern "C" uint64_t cpu_features_get();
|
||||
|
||||
#include "../libco/libco.h"
|
||||
|
||||
//can't use retroarch's dynamic.h, it's too full of weird stuff. don't need it anyway
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint64_t u64;
|
||||
typedef uint32_t u32;
|
||||
|
||||
typedef u8 u8bool;
|
||||
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t s64;
|
||||
|
||||
typedef void(*Action)();
|
||||
|
||||
struct retro_core_t
|
||||
{
|
||||
void(*retro_init)(void);
|
||||
void(*retro_deinit)(void);
|
||||
unsigned(*retro_api_version)(void);
|
||||
void(*retro_get_system_info)(struct retro_system_info*);
|
||||
void(*retro_get_system_av_info)(struct retro_system_av_info*);
|
||||
void(*retro_set_environment)(retro_environment_t);
|
||||
void(*retro_set_video_refresh)(retro_video_refresh_t);
|
||||
void(*retro_set_audio_sample)(retro_audio_sample_t);
|
||||
void(*retro_set_audio_sample_batch)(retro_audio_sample_batch_t);
|
||||
void(*retro_set_input_poll)(retro_input_poll_t);
|
||||
void(*retro_set_input_state)(retro_input_state_t);
|
||||
void(*retro_set_controller_port_device)(unsigned, unsigned);
|
||||
void(*retro_reset)(void);
|
||||
void(*retro_run)(void);
|
||||
size_t(*retro_serialize_size)(void);
|
||||
u8bool(*retro_serialize)(void*, size_t);
|
||||
u8bool(*retro_unserialize)(const void*, size_t);
|
||||
void(*retro_cheat_reset)(void);
|
||||
void(*retro_cheat_set)(unsigned, u8bool, const char*);
|
||||
u8bool(*retro_load_game)(const struct retro_game_info*);
|
||||
u8bool(*retro_load_game_special)(unsigned,
|
||||
const struct retro_game_info*, size_t);
|
||||
void(*retro_unload_game)(void);
|
||||
unsigned(*retro_get_region)(void);
|
||||
void *(*retro_get_memory_data)(unsigned);
|
||||
size_t(*retro_get_memory_size)(unsigned);
|
||||
};
|
||||
|
||||
enum eMessage : s32
|
||||
{
|
||||
NotSet,
|
||||
|
||||
Resume,
|
||||
|
||||
QUERY_FIRST,
|
||||
QUERY_GetMemory,
|
||||
QUERY_LAST,
|
||||
|
||||
CMD_FIRST,
|
||||
CMD_SetEnvironment,
|
||||
CMD_LoadNoGame,
|
||||
CMD_LoadData,
|
||||
CMD_LoadPath,
|
||||
CMD_Deinit,
|
||||
CMD_Reset,
|
||||
CMD_Run,
|
||||
CMD_UpdateSerializeSize,
|
||||
CMD_Serialize,
|
||||
CMD_Unserialize,
|
||||
CMD_LAST,
|
||||
|
||||
SIG_InputState,
|
||||
SIG_VideoUpdate,
|
||||
SIG_Sample,
|
||||
SIG_SampleBatch,
|
||||
};
|
||||
|
||||
enum eStatus : s32
|
||||
{
|
||||
eStatus_Idle,
|
||||
eStatus_CMD,
|
||||
eStatus_BRK
|
||||
};
|
||||
|
||||
enum BufId : s32 {
|
||||
Param0 = 0,
|
||||
Param1 = 1,
|
||||
SystemDirectory = 2,
|
||||
SaveDirectory = 3,
|
||||
CoreDirectory = 4,
|
||||
CoreAssetsDirectory = 5,
|
||||
BufId_Num //excess sized by 1.. no big deal
|
||||
};
|
||||
|
||||
//TODO: do any of these need to be volatile?
|
||||
struct CommStruct
|
||||
{
|
||||
//the cmd being executed
|
||||
eMessage cmd;
|
||||
|
||||
//the status of the core
|
||||
eStatus status;
|
||||
|
||||
//the SIG or BRK that the core is halted in
|
||||
eMessage reason;
|
||||
|
||||
//flexible in/out parameters
|
||||
//these are all "overloaded" a little so it isn't clear what's used for what in for any particular message..
|
||||
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
|
||||
u32 id, addr, value, size;
|
||||
u32 port, device, index, slot; //for input state
|
||||
|
||||
//variables meant for stateful communication (not parameters)
|
||||
//may be in, out, or inout. it's pretty sloppy.
|
||||
struct {
|
||||
//set by the core
|
||||
retro_system_info system_info;
|
||||
retro_system_av_info system_av_info;
|
||||
size_t retro_serialize_size_initial;
|
||||
size_t retro_serialize_size;
|
||||
u32 retro_region;
|
||||
u32 retro_api_version;
|
||||
retro_pixel_format pixel_format; //default is 0 -- RETRO_PIXEL_FORMAT_0RGB1555
|
||||
s32 rotation_ccw;
|
||||
bool support_no_game;
|
||||
retro_get_proc_address_t core_get_proc_address;
|
||||
|
||||
retro_game_geometry game_geometry;
|
||||
u8bool retro_game_geometry_dirty; //c# can clear this when it's acknowledged (but I think we might handle it from here? not sure)
|
||||
|
||||
//defined by the core. values arent put here, this is just the variables defined by the core
|
||||
//todo: shutdown tidy
|
||||
s32 variable_count;
|
||||
const char** variable_keys;
|
||||
const char** variable_comments;
|
||||
|
||||
//c# sets these with thunked callbacks
|
||||
retro_perf_callback perf_callback;
|
||||
|
||||
//various stashed stuff solely for c# convenience
|
||||
u64 processor_features;
|
||||
|
||||
s32 fb_width, fb_height; //core sets these; c# picks up, and..
|
||||
s32* fb_bufptr; //..sets this for the core to spill its data nito
|
||||
|
||||
} env;
|
||||
|
||||
//always used in pairs
|
||||
void* buf[BufId_Num];
|
||||
size_t buf_size[BufId_Num];
|
||||
|
||||
//===========================================================
|
||||
//private stuff
|
||||
|
||||
std::string *variables;
|
||||
bool variables_dirty;
|
||||
|
||||
|
||||
void* privbuf[BufId_Num]; //TODO remember to tidy this.. (needs to be done in snes too)
|
||||
void SetString(int id, const char* str)
|
||||
{
|
||||
size_t len = strlen(str);
|
||||
CopyBuffer(id, (void*)str, len+1);
|
||||
}
|
||||
void CopyBuffer(int id, void* ptr, size_t size)
|
||||
{
|
||||
if (privbuf[id]) free(privbuf[id]);
|
||||
buf[id] = privbuf[id] = malloc(size);
|
||||
memcpy(buf[id], ptr, size);
|
||||
buf_size[id] = size;
|
||||
}
|
||||
|
||||
void SetBuffer(int id, void* ptr, size_t size)
|
||||
{
|
||||
buf[id] = ptr;
|
||||
buf_size[id] = size;
|
||||
}
|
||||
|
||||
struct {
|
||||
} strings;
|
||||
|
||||
char* soFile;
|
||||
void* soFileHandle = nullptr;
|
||||
retro_core_t funs;
|
||||
|
||||
void LoadSymbols()
|
||||
{
|
||||
if (!soFileHandle) soFileHandle = dlopen(soFile, RTLD_NOW);
|
||||
//retroarch would throw an error here if the FP ws null. maybe better than throwing an error later, but are all the functions required?
|
||||
# define SYMBOL(x) do { \
|
||||
void* addr = dlsym(soFileHandle, #x); \
|
||||
memcpy(&funs.x, &addr, sizeof(void*)); \
|
||||
} while(0)
|
||||
|
||||
SYMBOL(retro_init);
|
||||
SYMBOL(retro_deinit);
|
||||
|
||||
SYMBOL(retro_api_version);
|
||||
SYMBOL(retro_get_system_info);
|
||||
SYMBOL(retro_get_system_av_info);
|
||||
|
||||
SYMBOL(retro_set_environment);
|
||||
SYMBOL(retro_set_video_refresh);
|
||||
SYMBOL(retro_set_audio_sample);
|
||||
SYMBOL(retro_set_audio_sample_batch);
|
||||
SYMBOL(retro_set_input_poll);
|
||||
SYMBOL(retro_set_input_state);
|
||||
|
||||
SYMBOL(retro_set_controller_port_device);
|
||||
|
||||
SYMBOL(retro_reset);
|
||||
SYMBOL(retro_run);
|
||||
|
||||
SYMBOL(retro_serialize_size);
|
||||
SYMBOL(retro_serialize);
|
||||
SYMBOL(retro_unserialize);
|
||||
|
||||
SYMBOL(retro_cheat_reset);
|
||||
SYMBOL(retro_cheat_set);
|
||||
|
||||
SYMBOL(retro_load_game);
|
||||
SYMBOL(retro_load_game_special);
|
||||
|
||||
SYMBOL(retro_unload_game);
|
||||
SYMBOL(retro_get_region);
|
||||
SYMBOL(retro_get_memory_data);
|
||||
SYMBOL(retro_get_memory_size);
|
||||
}
|
||||
|
||||
retro_core_t fn;
|
||||
|
||||
} comm;
|
||||
|
||||
//coroutines
|
||||
cothread_t co_control, co_emu, co_emu_suspended;
|
||||
|
||||
//internal state
|
||||
Action CMD_cb;
|
||||
|
||||
void BREAK(eMessage msg) {
|
||||
comm.status = eStatus_BRK;
|
||||
comm.reason = msg;
|
||||
co_emu_suspended = co_active();
|
||||
co_switch(co_control);
|
||||
comm.status = eStatus_CMD;
|
||||
}
|
||||
|
||||
//all this does is run commands on the emulation thread infinitely forever
|
||||
//(I should probably make a mechanism for bailing...)
|
||||
void new_emuthread()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
//process the current CMD
|
||||
CMD_cb();
|
||||
|
||||
//when that returned, we're definitely done with the CMD--so we're now IDLE
|
||||
comm.status = eStatus_Idle;
|
||||
|
||||
co_switch(co_control);
|
||||
}
|
||||
}
|
||||
|
||||
void retro_log_printf(enum retro_log_level level, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vprintf(fmt,args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
bool retro_environment(unsigned cmd, void *data)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case RETRO_ENVIRONMENT_SET_ROTATION:
|
||||
comm.env.rotation_ccw = (int)*(const unsigned*)data * 90;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_GET_OVERSCAN:
|
||||
return false; //could return true to crop overscan
|
||||
case RETRO_ENVIRONMENT_GET_CAN_DUPE:
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_MESSAGE:
|
||||
{
|
||||
//TODO: try to respect design principle by forwarding to frontend with the timer
|
||||
auto &msg = *(retro_message*)data;
|
||||
printf("%s\n",msg.msg);
|
||||
return true;
|
||||
}
|
||||
case RETRO_ENVIRONMENT_SHUTDOWN:
|
||||
//TODO low priority
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL:
|
||||
//unneeded
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
|
||||
*(const char**)data = (const char*)comm.buf[SystemDirectory];
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
|
||||
comm.env.pixel_format = *(const enum retro_pixel_format*)data;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
|
||||
//TODO medium priority
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK:
|
||||
//TODO high priority (to support keyboard consoles, probably high value for us. but that may take a lot of infrastructure work)
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE:
|
||||
//TODO high priority (to support disc systems)
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_HW_RENDER:
|
||||
//TODO high priority (to support 3d renderers
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_VARIABLE:
|
||||
{
|
||||
//according to retroarch's `core_option_manager_get` this is what we should do
|
||||
comm.variables_dirty = false;
|
||||
|
||||
auto req = (retro_variable *)data;
|
||||
req->value = nullptr;
|
||||
|
||||
for(int i=0;i<comm.env.variable_count;i++)
|
||||
{
|
||||
if(!strcmp(comm.env.variable_keys[i],req->key))
|
||||
{
|
||||
req->value = comm.variables[i].c_str();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_VARIABLES:
|
||||
{
|
||||
auto var = (retro_variable *)data;
|
||||
int nVars = 0;
|
||||
while(var->key)
|
||||
nVars++, var++;
|
||||
comm.variables = new std::string[nVars];
|
||||
comm.env.variable_count = nVars;
|
||||
comm.env.variable_keys = new const char*[nVars];
|
||||
comm.env.variable_comments = new const char*[nVars];
|
||||
var = (retro_variable *)data;
|
||||
for(int i=0;i<nVars;i++)
|
||||
{
|
||||
comm.env.variable_keys[i] = var[i].key;
|
||||
comm.env.variable_comments[i] = var[i].value;
|
||||
|
||||
//analyze to find default and save it
|
||||
std::string comment = var[i].value;
|
||||
auto ofs = comment.find_first_of(';')+2;
|
||||
auto pipe = comment.find('|',ofs);
|
||||
if(pipe == std::string::npos)
|
||||
comm.variables[i] = comment.substr(ofs);
|
||||
else
|
||||
comm.variables[i] = comment.substr(ofs,pipe-ofs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
|
||||
*(u8bool*)data = comm.variables_dirty;
|
||||
break;
|
||||
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
|
||||
comm.env.support_no_game = !!*(u8bool*)data;
|
||||
break;
|
||||
case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH:
|
||||
*(const char**)data = (const char*)comm.buf[CoreDirectory];
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK:
|
||||
//dont know what to do with this yet
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK:
|
||||
//dont know what to do with this yet
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE:
|
||||
//TODO low priority
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES:
|
||||
//TODO medium priority - other input methods
|
||||
*(u64*)data = (1<<RETRO_DEVICE_JOYPAD);
|
||||
return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
|
||||
((retro_log_callback*)data)->log = retro_log_printf;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_GET_PERF_INTERFACE:
|
||||
*((retro_perf_callback *)data) = comm.env.perf_callback;
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE:
|
||||
//TODO low priority
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
|
||||
*(const char**)data = (const char*)comm.buf[CoreAssetsDirectory];
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
|
||||
*(const char**)data = (const char*)comm.buf[SaveDirectory];
|
||||
return true;
|
||||
case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
|
||||
printf("NEED RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO\n");
|
||||
return false;
|
||||
case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK:
|
||||
comm.env.core_get_proc_address = ((retro_get_proc_address_interface*)data)->get_proc_address;
|
||||
return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
|
||||
//needs retro_load_game_special to be useful; not supported yet
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
|
||||
//TODO medium priority probably
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_GEOMETRY:
|
||||
comm.env.game_geometry = *((const retro_game_geometry *)data);
|
||||
comm.env.retro_game_geometry_dirty = true;
|
||||
return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_USERNAME:
|
||||
//we definitely want to return false here so the core will do something deterministic
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_LANGUAGE:
|
||||
*((unsigned *)data) = RETRO_LANGUAGE_ENGLISH;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<int ROT> static inline int* address(int width, int height, int pitch, int x, int y, int* dstbuf, int* optimize0dst)
|
||||
{
|
||||
switch (ROT)
|
||||
{
|
||||
case 0:
|
||||
return optimize0dst;
|
||||
|
||||
case 90:
|
||||
//TODO:
|
||||
return optimize0dst;
|
||||
|
||||
case 180:
|
||||
//TODO:
|
||||
return optimize0dst;
|
||||
|
||||
case 270:
|
||||
{
|
||||
int dx = width - y - 1;
|
||||
int dy = x;
|
||||
return dstbuf + dy * width + dx;
|
||||
}
|
||||
|
||||
default:
|
||||
//impossible
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<int ROT> void Blit555(short* srcbuf, s32* dstbuf, int width, int height, int pitch)
|
||||
{
|
||||
s32* dst = dstbuf;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
short* row = srcbuf;
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
short ci = *row;
|
||||
int r = ci & 0x001f;
|
||||
int g = ci & 0x03e0;
|
||||
int b = ci & 0x7c00;
|
||||
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g >> 2) | (g >> 7);
|
||||
b = (b >> 7) | (b >> 12);
|
||||
int co = r | g | b | 0xff000000;
|
||||
|
||||
*address<ROT>(width, height, pitch, x, y, dstbuf, dst) = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
srcbuf += pitch/2;
|
||||
}
|
||||
}
|
||||
|
||||
template<int ROT> void Blit565(short* srcbuf, s32* dstbuf, int width, int height, int pitch)
|
||||
{
|
||||
s32* dst = dstbuf;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
short* row = srcbuf;
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
short ci = *row;
|
||||
int r = ci & 0x001f;
|
||||
int g = (ci & 0x07e0) >> 5;
|
||||
int b = (ci & 0xf800) >> 11;
|
||||
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g << 2) | (g >> 4);
|
||||
b = (b << 3) | (b >> 2);
|
||||
int co = (b << 16) | (g << 8) | r;
|
||||
|
||||
*address<ROT>(width, height, pitch, x, y, dstbuf, dst) = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
srcbuf += pitch/2;
|
||||
}
|
||||
}
|
||||
|
||||
template<int ROT> void Blit888(int* srcbuf, s32* dstbuf, int width, int height, int pitch)
|
||||
{
|
||||
s32* dst = dstbuf;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
int* row = srcbuf;
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int ci = *row;
|
||||
int co = ci | 0xff000000;
|
||||
*address<ROT>(width,height,pitch,x,y,dstbuf,dst) = co;
|
||||
dst++;
|
||||
row++;
|
||||
}
|
||||
srcbuf += pitch/4;
|
||||
}
|
||||
}
|
||||
|
||||
void retro_video_refresh(const void *data, unsigned width, unsigned height, size_t pitch)
|
||||
{
|
||||
//handle a "dup frame" -- same as previous frame. so there isn't anything to be done here
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
comm.env.fb_width = (s32)width;
|
||||
comm.env.fb_height = (s32)height;
|
||||
//stash pitch if needed
|
||||
|
||||
//notify c# of these new settings and let it allocate a buffer suitable for receiving the output (so we can work directly into c#'s int[])
|
||||
//c# can read the settings right out of the comm env
|
||||
BREAK(eMessage::SIG_VideoUpdate);
|
||||
|
||||
|
||||
////if (BufferWidth != width) BufferWidth = (int)width;
|
||||
////if (BufferHeight != height) BufferHeight = (int)height;
|
||||
////if (BufferWidth * BufferHeight != rawvidbuff.Length)
|
||||
//// rawvidbuff = new int[BufferWidth * BufferHeight];
|
||||
|
||||
////if we have rotation, we might have a geometry mismatch and in any event we need a temp buffer to do the rotation from
|
||||
////but that's a general problem, isnt it?
|
||||
//if (comm.env.fb.raw == nullptr || comm.env.fb.raw_length != width * height)
|
||||
//{
|
||||
// if(comm.env.fb.raw)
|
||||
// delete[] comm.env.fb.raw;
|
||||
// comm.env.fb.raw = new u32[width * height];
|
||||
// comm.env.fb.width = width;
|
||||
// comm.env.fb.height = height;
|
||||
//}
|
||||
|
||||
int w = (int)width;
|
||||
int h = (int)height;
|
||||
int p = (int)pitch;
|
||||
|
||||
switch(comm.env.pixel_format)
|
||||
{
|
||||
|
||||
case RETRO_PIXEL_FORMAT_0RGB1555:
|
||||
switch (comm.env.rotation_ccw)
|
||||
{
|
||||
case 0: Blit555<0>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 90: Blit555<90>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 180: Blit555<180>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 270: Blit555<270>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RETRO_PIXEL_FORMAT_XRGB8888:
|
||||
switch(comm.env.rotation_ccw)
|
||||
{
|
||||
case 0: Blit888<0>((int*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 90: Blit888<90>((int*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 180: Blit888<180>((int*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 270: Blit888<270>((int*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case RETRO_PIXEL_FORMAT_RGB565:
|
||||
switch (comm.env.rotation_ccw)
|
||||
{
|
||||
case 0: Blit565<0>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 90: Blit565<90>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 180: Blit565<180>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
case 270: Blit565<270>((short*)data, comm.env.fb_bufptr, w, h, p); break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void retro_audio_sample(s16 left, s16 right)
|
||||
{
|
||||
s16 samples[] = {left,right};
|
||||
comm.SetBuffer(BufId::Param0,(void*)&samples,4);
|
||||
BREAK(SIG_Sample);
|
||||
}
|
||||
size_t retro_audio_sample_batch(const s16 *data, size_t frames)
|
||||
{
|
||||
comm.SetBuffer(BufId::Param0, (void*)data, frames*4);
|
||||
BREAK(SIG_SampleBatch);
|
||||
return frames;
|
||||
}
|
||||
void retro_input_poll()
|
||||
{
|
||||
}
|
||||
s16 retro_input_state(unsigned port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
//we have to bail to c# for this, it's too complex.
|
||||
comm.port = port;
|
||||
comm.device = device;
|
||||
comm.index = index;
|
||||
comm.id = id;
|
||||
|
||||
BREAK(eMessage::SIG_InputState);
|
||||
|
||||
return (s16)comm.value;
|
||||
}
|
||||
|
||||
//loads the game, too
|
||||
//REQUIREMENTS:
|
||||
//set SystemDirectory, SaveDirectory, CoreDirectory, CoreAssetsDirectory are set
|
||||
//retro_perf_callback is set
|
||||
static void LoadHandler(eMessage msg)
|
||||
{
|
||||
//retro_set_environment() is guaranteed to be called before retro_init().
|
||||
|
||||
comm.funs.retro_init();
|
||||
|
||||
retro_game_info rgi;
|
||||
retro_game_info* rgiptr = &rgi;
|
||||
memset(&rgi,0,sizeof(rgi));
|
||||
|
||||
if (msg == eMessage::CMD_LoadNoGame)
|
||||
{
|
||||
rgiptr = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
rgi.path = (const char*)comm.buf[BufId::Param0];
|
||||
if (msg == eMessage::CMD_LoadData)
|
||||
{
|
||||
rgi.data = comm.buf[BufId::Param1];
|
||||
rgi.size = comm.buf_size[BufId::Param1];
|
||||
}
|
||||
}
|
||||
|
||||
comm.funs.retro_load_game(rgiptr);
|
||||
|
||||
//Can be called only after retro_load_game() has successfully completed.
|
||||
comm.funs.retro_get_system_av_info(&comm.env.system_av_info);
|
||||
|
||||
//guaranteed to have been called before the first call to retro_run() is made.
|
||||
//(I've put this after the retro_system_av_info runs, in case that's important
|
||||
comm.funs.retro_set_video_refresh(retro_video_refresh);
|
||||
|
||||
comm.funs.retro_set_audio_sample(retro_audio_sample);
|
||||
comm.funs.retro_set_audio_sample_batch(retro_audio_sample_batch);
|
||||
comm.funs.retro_set_input_poll(retro_input_poll);
|
||||
comm.funs.retro_set_input_state(retro_input_state);
|
||||
|
||||
//Between calls to retro_load_game() and retro_unload_game(), the returned size is never allowed to be larger than a previous returned
|
||||
//value, to ensure that the frontend can allocate a save state buffer once.
|
||||
comm.env.retro_serialize_size_initial = comm.env.retro_serialize_size = comm.funs.retro_serialize_size();
|
||||
|
||||
//not sure when this can be called, but it's surely safe here
|
||||
comm.env.retro_region = comm.funs.retro_get_region();
|
||||
}
|
||||
|
||||
void cmd_LoadNoGame() { LoadHandler(eMessage::CMD_LoadNoGame); }
|
||||
void cmd_LoadData() { LoadHandler(eMessage::CMD_LoadData); }
|
||||
void cmd_LoadPath() { LoadHandler(eMessage::CMD_LoadPath); }
|
||||
|
||||
void cmd_Deinit()
|
||||
{
|
||||
//not sure if we need this
|
||||
comm.funs.retro_unload_game();
|
||||
comm.funs.retro_deinit();
|
||||
//TODO: tidy
|
||||
}
|
||||
|
||||
void cmd_Reset()
|
||||
{
|
||||
comm.funs.retro_reset();
|
||||
}
|
||||
|
||||
void cmd_Run()
|
||||
{
|
||||
comm.funs.retro_run();
|
||||
}
|
||||
|
||||
void cmd_UpdateSerializeSize()
|
||||
{
|
||||
comm.env.retro_serialize_size = comm.funs.retro_serialize_size();
|
||||
}
|
||||
|
||||
void cmd_Serialize()
|
||||
{
|
||||
comm.value = !!comm.funs.retro_serialize(comm.buf[BufId::Param0], comm.buf_size[BufId::Param0]);
|
||||
}
|
||||
|
||||
void cmd_Unserialize()
|
||||
{
|
||||
comm.value = !!comm.funs.retro_unserialize(comm.buf[BufId::Param0], comm.buf_size[BufId::Param0]);
|
||||
}
|
||||
|
||||
//TODO
|
||||
//void(*retro_set_controller_port_device)(unsigned, unsigned);
|
||||
//void *(*retro_get_memory_data)(unsigned);
|
||||
//size_t(*retro_get_memory_size)(unsigned);
|
||||
|
||||
//TODO low priority
|
||||
//void(*retro_cheat_reset)(void);
|
||||
//void(*retro_cheat_set)(unsigned, bool, const char*);
|
||||
//bool(*retro_load_game_special)(unsigned,
|
||||
|
||||
//TODO maybe not sensible though
|
||||
//void(*retro_unload_game)(void);
|
||||
|
||||
void cmd_SetEnvironment()
|
||||
{
|
||||
//stuff that can't be done until our environment is setup (the core will immediately query the environment)
|
||||
comm.funs.retro_set_environment(retro_environment);
|
||||
}
|
||||
|
||||
void query_GetMemory()
|
||||
{
|
||||
comm.buf_size[BufId::Param0] = comm.funs.retro_get_memory_size(comm.value);
|
||||
comm.buf[BufId::Param0] = comm.funs.retro_get_memory_data(comm.value);
|
||||
}
|
||||
|
||||
const Action kHandlers_CMD[] = {
|
||||
cmd_SetEnvironment,
|
||||
cmd_LoadNoGame,
|
||||
cmd_LoadData,
|
||||
cmd_LoadPath,
|
||||
cmd_Deinit,
|
||||
cmd_Reset,
|
||||
cmd_Run,
|
||||
cmd_UpdateSerializeSize,
|
||||
cmd_Serialize,
|
||||
cmd_Unserialize,
|
||||
};
|
||||
|
||||
const Action kHandlers_QUERY[] = {
|
||||
query_GetMemory,
|
||||
};
|
||||
|
||||
//------------------------------------------------
|
||||
//DLL INTERFACE
|
||||
|
||||
#define EXPORT extern "C" __attribute__((visibility("default")))
|
||||
|
||||
EXPORT void* DllInit(const char* soFile)
|
||||
{
|
||||
memset(&comm,0,sizeof(comm));
|
||||
|
||||
//make a coroutine thread to run the emulation in. we'll switch back to this cothread when communicating with the frontend
|
||||
co_control = co_active();
|
||||
co_emu = co_create(128*1024 * sizeof(void*), new_emuthread);
|
||||
|
||||
//grab all the function pointers we need.
|
||||
comm.soFile = strdup(soFile);
|
||||
comm.LoadSymbols();
|
||||
|
||||
//libretro startup steps
|
||||
//"Can be called at any time, even before retro_init()."
|
||||
comm.funs.retro_get_system_info(&comm.env.system_info);
|
||||
comm.env.retro_api_version = (u32)comm.funs.retro_api_version();
|
||||
|
||||
//now after this we return to the c# side to let some more setup happen
|
||||
|
||||
return &comm;
|
||||
}
|
||||
|
||||
|
||||
EXPORT void Message(eMessage msg)
|
||||
{
|
||||
if (msg == eMessage::Resume)
|
||||
{
|
||||
cothread_t temp = co_emu_suspended;
|
||||
co_emu_suspended = NULL;
|
||||
co_switch(temp);
|
||||
}
|
||||
|
||||
if (msg >= eMessage::CMD_FIRST && msg <= eMessage::CMD_LAST)
|
||||
{
|
||||
//CMD is only valid if status is idle
|
||||
if (comm.status != eStatus_Idle)
|
||||
{
|
||||
printf("ERROR: cmd during non-idle\n");
|
||||
return;
|
||||
}
|
||||
|
||||
comm.status = eStatus_CMD;
|
||||
comm.cmd = msg;
|
||||
|
||||
CMD_cb = kHandlers_CMD[msg - eMessage::CMD_FIRST - 1];
|
||||
co_switch(co_emu);
|
||||
|
||||
//we could be in ANY STATE when we return from here
|
||||
}
|
||||
|
||||
//QUERY can run any time
|
||||
//but... some of them might not be safe for re-entrancy.
|
||||
//later, we should have metadata for messages that indicates that
|
||||
if (msg >= eMessage::QUERY_FIRST && msg <= eMessage::QUERY_LAST)
|
||||
{
|
||||
Action cb = kHandlers_QUERY[msg - eMessage::QUERY_FIRST - 1];
|
||||
if (cb) cb();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//receives the given buffer and COPIES it. use this for returning values from SIGs
|
||||
EXPORT void CopyBuffer(int id, void* ptr, s32 size)
|
||||
{
|
||||
comm.CopyBuffer(id, ptr, size);
|
||||
}
|
||||
|
||||
//receives the given buffer and STASHES IT. use this (carefully) for sending params for CMDs
|
||||
EXPORT void SetBuffer(int id, void* ptr, s32 size)
|
||||
{
|
||||
comm.SetBuffer(id, ptr, size);
|
||||
}
|
||||
|
||||
EXPORT void SetVariable(const char* key, const char* val)
|
||||
{
|
||||
for(int i=0;i<comm.env.variable_count;i++)
|
||||
if(!strcmp(key,comm.env.variable_keys[i]))
|
||||
{
|
||||
comm.variables[i] = val;
|
||||
comm.variables_dirty = true;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
|
||||
LDLIBS := -ldl
|
||||
LDFLAGS := -shared
|
||||
CFLAGS := -fPIC -Os -fvisibility=hidden
|
||||
CXXFLAGS := -fPIC -Os -fvisibility=hidden
|
||||
|
||||
ROOT_DIR := ..
|
||||
OBJ_DIR := obj
|
||||
sources := $(ROOT_DIR)/features_cpu.c $(ROOT_DIR)/nix/LibretroBridge.cpp $(ROOT_DIR)/libco/amd64.c
|
||||
_OBJS := $(addsuffix .o,$(sources))
|
||||
OBJS := $(patsubst $(ROOT_DIR)%,$(OBJ_DIR)%,$(_OBJS))
|
||||
|
||||
target := $(OBJ_DIR)/LibretroBridge.so
|
||||
all: $(target)
|
||||
|
||||
$(OBJ_DIR)/%.c.o: $(ROOT_DIR)/%.c
|
||||
@mkdir -p $(@D)
|
||||
$(COMPILE.c) $< -o $@
|
||||
$(OBJ_DIR)/%.cpp.o: $(ROOT_DIR)/%.cpp
|
||||
@mkdir -p $(@D)
|
||||
$(COMPILE.cpp) $< -o $@
|
||||
|
||||
$(target): $(OBJS)
|
||||
$(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
cp $(target) ../../Assets/dll
|
||||
if [ -d ../../output/dll ]; then \
|
||||
cp $(target) ../../output/dll; \
|
||||
fi;
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJ_DIR)
|
|
@ -597,7 +597,7 @@ namespace BizHawk.Client.Common
|
|||
bool allowArchives = true;
|
||||
if (OpenAdvanced is OpenAdvanced_MAME) allowArchives = false;
|
||||
using var file = new HawkFile(path, false, allowArchives);
|
||||
if (!file.Exists) return false; // if the provided file doesn't even exist, give up!
|
||||
if (!file.Exists && OpenAdvanced is not OpenAdvanced_LibretroNoGame) return false; // if the provided file doesn't even exist, give up! (unless libretro no game is used)
|
||||
|
||||
CanonicalFullPath = file.CanonicalFullPath;
|
||||
|
||||
|
@ -609,12 +609,12 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
var cancel = false;
|
||||
|
||||
if (OpenAdvanced is OpenAdvanced_Libretro)
|
||||
if (OpenAdvanced is OpenAdvanced_Libretro or OpenAdvanced_LibretroNoGame)
|
||||
{
|
||||
// must be done before LoadNoGame (which triggers retro_init and the paths to be consumed by the core)
|
||||
// game name == name of core
|
||||
Game = game = new GameInfo { Name = Path.GetFileNameWithoutExtension(launchLibretroCore), System = VSystemID.Raw.Libretro };
|
||||
var retro = new LibretroCore(nextComm, game, launchLibretroCore);
|
||||
var retro = new LibretroEmulator(nextComm, game, launchLibretroCore);
|
||||
nextEmulator = retro;
|
||||
|
||||
if (retro.Description.SupportsNoGame && string.IsNullOrEmpty(path))
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
try
|
||||
{
|
||||
var coreComm = _createCoreComm();
|
||||
using var retro = new LibretroCore(coreComm, _game, core);
|
||||
using var retro = new LibretroEmulator(coreComm, _game, core, true);
|
||||
btnLibretroLaunchGame.Enabled = true;
|
||||
if (retro.Description.SupportsNoGame)
|
||||
btnLibretroLaunchNoGame.Enabled = true;
|
||||
|
|
|
@ -0,0 +1,447 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using BizHawk.BizInvoke;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public abstract class LibretroApi
|
||||
{
|
||||
private const CallingConvention cc = CallingConvention.Cdecl;
|
||||
|
||||
public enum RETRO_DEVICE : int
|
||||
{
|
||||
NONE = 0,
|
||||
JOYPAD = 1,
|
||||
MOUSE = 2,
|
||||
KEYBOARD = 3,
|
||||
LIGHTGUN = 4,
|
||||
ANALOG = 5,
|
||||
POINTER = 6,
|
||||
SENSOR_ACCELEROMETER = 7,
|
||||
|
||||
LAST,
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_ANALOG
|
||||
{
|
||||
// LEFT / RIGHT?
|
||||
X = 0,
|
||||
Y = 1,
|
||||
|
||||
LAST,
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_MOUSE
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
LEFT = 2,
|
||||
RIGHT = 3,
|
||||
|
||||
LAST,
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_LIGHTGUN
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
TRIGGER = 2,
|
||||
CURSOR = 3,
|
||||
TURBO = 4,
|
||||
PAUSE = 5,
|
||||
START = 6,
|
||||
|
||||
LAST,
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_POINTER
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
PRESSED = 2,
|
||||
|
||||
LAST,
|
||||
}
|
||||
|
||||
public enum RETRO_KEY
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
FIRST = 0,
|
||||
BACKSPACE = 8,
|
||||
TAB = 9,
|
||||
CLEAR = 12,
|
||||
RETURN = 13,
|
||||
PAUSE = 19,
|
||||
ESCAPE = 27,
|
||||
SPACE = 32,
|
||||
EXCLAIM = 33,
|
||||
QUOTEDBL = 34,
|
||||
HASH = 35,
|
||||
DOLLAR = 36,
|
||||
AMPERSAND = 38,
|
||||
QUOTE = 39,
|
||||
LEFTPAREN = 40,
|
||||
RIGHTPAREN = 41,
|
||||
ASTERISK = 42,
|
||||
PLUS = 43,
|
||||
COMMA = 44,
|
||||
MINUS = 45,
|
||||
PERIOD = 46,
|
||||
SLASH = 47,
|
||||
_0 = 48,
|
||||
_1 = 49,
|
||||
_2 = 50,
|
||||
_3 = 51,
|
||||
_4 = 52,
|
||||
_5 = 53,
|
||||
_6 = 54,
|
||||
_7 = 55,
|
||||
_8 = 56,
|
||||
_9 = 57,
|
||||
COLON = 58,
|
||||
SEMICOLON = 59,
|
||||
LESS = 60,
|
||||
EQUALS = 61,
|
||||
GREATER = 62,
|
||||
QUESTION = 63,
|
||||
AT = 64,
|
||||
LEFTBRACKET = 91,
|
||||
BACKSLASH = 92,
|
||||
RIGHTBRACKET = 93,
|
||||
CARET = 94,
|
||||
UNDERSCORE = 95,
|
||||
BACKQUOTE = 96,
|
||||
a = 97,
|
||||
b = 98,
|
||||
c = 99,
|
||||
d = 100,
|
||||
e = 101,
|
||||
f = 102,
|
||||
g = 103,
|
||||
h = 104,
|
||||
i = 105,
|
||||
j = 106,
|
||||
k = 107,
|
||||
l = 108,
|
||||
m = 109,
|
||||
n = 110,
|
||||
o = 111,
|
||||
p = 112,
|
||||
q = 113,
|
||||
r = 114,
|
||||
s = 115,
|
||||
t = 116,
|
||||
u = 117,
|
||||
v = 118,
|
||||
w = 119,
|
||||
x = 120,
|
||||
y = 121,
|
||||
z = 122,
|
||||
DELETE = 127,
|
||||
|
||||
KP0 = 256,
|
||||
KP1 = 257,
|
||||
KP2 = 258,
|
||||
KP3 = 259,
|
||||
KP4 = 260,
|
||||
KP5 = 261,
|
||||
KP6 = 262,
|
||||
KP7 = 263,
|
||||
KP8 = 264,
|
||||
KP9 = 265,
|
||||
KP_PERIOD = 266,
|
||||
KP_DIVIDE = 267,
|
||||
KP_MULTIPLY = 268,
|
||||
KP_MINUS = 269,
|
||||
KP_PLUS = 270,
|
||||
KP_ENTER = 271,
|
||||
KP_EQUALS = 272,
|
||||
|
||||
UP = 273,
|
||||
DOWN = 274,
|
||||
RIGHT = 275,
|
||||
LEFT = 276,
|
||||
INSERT = 277,
|
||||
HOME = 278,
|
||||
END = 279,
|
||||
PAGEUP = 280,
|
||||
PAGEDOWN = 281,
|
||||
|
||||
F1 = 282,
|
||||
F2 = 283,
|
||||
F3 = 284,
|
||||
F4 = 285,
|
||||
F5 = 286,
|
||||
F6 = 287,
|
||||
F7 = 288,
|
||||
F8 = 289,
|
||||
F9 = 290,
|
||||
F10 = 291,
|
||||
F11 = 292,
|
||||
F12 = 293,
|
||||
F13 = 294,
|
||||
F14 = 295,
|
||||
F15 = 296,
|
||||
|
||||
NUMLOCK = 300,
|
||||
CAPSLOCK = 301,
|
||||
SCROLLOCK = 302,
|
||||
RSHIFT = 303,
|
||||
LSHIFT = 304,
|
||||
RCTRL = 305,
|
||||
LCTRL = 306,
|
||||
RALT = 307,
|
||||
LALT = 308,
|
||||
RMETA = 309,
|
||||
LMETA = 310,
|
||||
LSUPER = 311,
|
||||
RSUPER = 312,
|
||||
MODE = 313,
|
||||
COMPOSE = 314,
|
||||
|
||||
HELP = 315,
|
||||
PRINT = 316,
|
||||
SYSREQ = 317,
|
||||
BREAK = 318,
|
||||
MENU = 319,
|
||||
POWER = 320,
|
||||
EURO = 321,
|
||||
UNDO = 322,
|
||||
|
||||
LAST,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum RETRO_MOD
|
||||
{
|
||||
NONE = 0,
|
||||
SHIFT = 1,
|
||||
CTRL = 2,
|
||||
ALT = 4,
|
||||
META = 8,
|
||||
NUMLOCK = 16,
|
||||
CAPSLOCK = 32,
|
||||
SCROLLLOCK = 64,
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_SENSOR_ACCELEROMETER
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Z = 2,
|
||||
|
||||
LAST,
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_JOYPAD
|
||||
{
|
||||
B = 0,
|
||||
Y = 1,
|
||||
SELECT = 2,
|
||||
START = 3,
|
||||
UP = 4,
|
||||
DOWN = 5,
|
||||
LEFT = 6,
|
||||
RIGHT = 7,
|
||||
A = 8,
|
||||
X = 9,
|
||||
L = 10,
|
||||
R = 11,
|
||||
L2 = 12,
|
||||
R2 = 13,
|
||||
L3 = 14,
|
||||
R3 = 15,
|
||||
|
||||
LAST,
|
||||
}
|
||||
|
||||
public enum RETRO_REGION : uint
|
||||
{
|
||||
NTSC = 0,
|
||||
PAL = 1,
|
||||
};
|
||||
|
||||
public enum RETRO_MEMORY : uint
|
||||
{
|
||||
SAVE_RAM = 0,
|
||||
RTC = 1,
|
||||
SYSTEM_RAM = 2,
|
||||
VIDEO_RAM = 3,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct retro_variable
|
||||
{
|
||||
public IntPtr key;
|
||||
public IntPtr value;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct retro_message
|
||||
{
|
||||
public IntPtr msg;
|
||||
public uint frames;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct retro_system_info
|
||||
{
|
||||
public IntPtr library_name;
|
||||
public IntPtr library_version;
|
||||
public IntPtr valid_extensions;
|
||||
public bool need_fullpath;
|
||||
public bool block_extract;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct retro_game_info
|
||||
{
|
||||
public IntPtr path;
|
||||
public IntPtr data;
|
||||
public long size;
|
||||
public IntPtr meta;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct retro_system_av_info
|
||||
{
|
||||
// struct retro_game_geometry
|
||||
public uint base_width;
|
||||
public uint base_height;
|
||||
public uint max_width;
|
||||
public uint max_height;
|
||||
public float aspect_ratio;
|
||||
|
||||
// struct retro_system_timing
|
||||
public double fps;
|
||||
public double sample_rate;
|
||||
};
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_init();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_deinit();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract uint retro_api_version();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_get_system_info(IntPtr retro_system_info);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_get_system_av_info(IntPtr retro_system_av_info);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_set_environment(IntPtr retro_environment);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_set_video_refresh(IntPtr retro_video_refresh);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_set_audio_sample(IntPtr retro_audio_sample);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract long retro_set_audio_sample_batch(IntPtr retro_audio_sample_batch);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_set_input_poll(IntPtr retro_input_poll);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_set_input_state(IntPtr retro_input_state);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_set_controller_port_device(uint port, uint device);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_reset();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_run();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract long retro_serialize_size();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract bool retro_serialize(IntPtr data, long size);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract bool retro_unserialize(IntPtr data, long size);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_cheat_reset();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_cheat_set(uint index, bool enabled, IntPtr code);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract bool retro_load_game(IntPtr retro_game_info);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract bool retro_load_game_special(uint game_type, IntPtr retro_game_info, long num_info);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void retro_unload_game();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract RETRO_REGION retro_get_region();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract IntPtr retro_get_memory_data(RETRO_MEMORY id);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract long retro_get_memory_size(RETRO_MEMORY id);
|
||||
}
|
||||
|
||||
public abstract class LibretroBridge
|
||||
{
|
||||
private const CallingConvention cc = CallingConvention.Cdecl;
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract IntPtr LibretroBridge_CreateCallbackHandler();
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_DestroyCallbackHandler(IntPtr cbHandler);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_SetGlobalCallbackHandler(IntPtr cbHandler);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract bool LibretroBridge_GetSupportsNoGame(IntPtr cbHandler);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_GetRetroMessage(IntPtr cbHandler, ref LibretroApi.retro_message m);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_SetDirectories(IntPtr cbHandler, byte[] systemDirectory, byte[] saveDirectory, byte[] coreDirectory, byte[] coreAssetsDirectory);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_SetVideoSize(IntPtr cbHandler, int sz);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_GetVideo(IntPtr cbHandler, ref int width, ref int height, int[] videoBuf);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract uint LibretroBridge_GetAudioSize(IntPtr cbHandler);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_GetAudio(IntPtr cbHandler, ref int numSamples, short[] sampleBuf);
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_SetInput(IntPtr cbHandler, LibretroApi.RETRO_DEVICE device, int port, short[] input);
|
||||
|
||||
public struct retro_procs
|
||||
{
|
||||
public IntPtr retro_environment_proc;
|
||||
public IntPtr retro_video_refresh_proc;
|
||||
public IntPtr retro_audio_sample_proc;
|
||||
public IntPtr retro_audio_sample_batch_proc;
|
||||
public IntPtr retro_input_poll_proc;
|
||||
public IntPtr retro_input_state_proc;
|
||||
}
|
||||
|
||||
[BizImport(cc)]
|
||||
public abstract void LibretroBridge_GetRetroProcs(ref retro_procs cb_procs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,333 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using BizHawk.BizInvoke;
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
[PortedCore(CoreNames.Libretro, "CasualPokePlayer", singleInstance: true, isReleased: false)]
|
||||
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
|
||||
public partial class LibretroEmulator : IEmulator
|
||||
{
|
||||
private static readonly LibretroBridge bridge;
|
||||
private static readonly LibretroBridge.retro_procs cb_procs;
|
||||
|
||||
static LibretroEmulator()
|
||||
{
|
||||
var resolver = new DynamicLibraryImportResolver(
|
||||
OSTailoredCode.IsUnixHost ? "libLibretroBridge.so" : "libLibretroBridge.dll", hasLimitedLifetime: false);
|
||||
|
||||
bridge = BizInvoker.GetInvoker<LibretroBridge>(resolver, CallingConventionAdapters.Native);
|
||||
|
||||
cb_procs = new();
|
||||
bridge.LibretroBridge_GetRetroProcs(ref cb_procs);
|
||||
}
|
||||
|
||||
private readonly LibretroApi api;
|
||||
|
||||
private readonly BasicServiceProvider _serviceProvider;
|
||||
public IEmulatorServiceProvider ServiceProvider => _serviceProvider;
|
||||
|
||||
private readonly IntPtr cbHandler;
|
||||
|
||||
// please call this before calling any retro functions
|
||||
private void UpdateCallbackHandler()
|
||||
{
|
||||
bridge.LibretroBridge_SetGlobalCallbackHandler(cbHandler);
|
||||
}
|
||||
|
||||
public LibretroEmulator(CoreComm comm, IGameInfo game, string corePath, bool analysis = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
cbHandler = bridge.LibretroBridge_CreateCallbackHandler();
|
||||
|
||||
if (cbHandler == IntPtr.Zero)
|
||||
{
|
||||
throw new Exception("Failed to create callback handler!");
|
||||
}
|
||||
|
||||
UpdateCallbackHandler();
|
||||
|
||||
api = BizInvoker.GetInvoker<LibretroApi>(
|
||||
new DynamicLibraryImportResolver(corePath, hasLimitedLifetime: false), CallingConventionAdapters.Native);
|
||||
|
||||
_serviceProvider = new(this);
|
||||
Comm = comm;
|
||||
|
||||
if (api.retro_api_version() != 1)
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported Libretro API version (or major error in interop)");
|
||||
}
|
||||
|
||||
var SystemDirectory = RetroString(Comm.CoreFileProvider.GetRetroSystemPath(game));
|
||||
var SaveDirectory = RetroString(Comm.CoreFileProvider.GetRetroSaveRAMDirectory(game));
|
||||
var CoreDirectory = RetroString(Path.GetDirectoryName(corePath));
|
||||
var CoreAssetsDirectory = RetroString(Path.GetDirectoryName(corePath));
|
||||
|
||||
bridge.LibretroBridge_SetDirectories(cbHandler, SystemDirectory, SaveDirectory, CoreDirectory, CoreAssetsDirectory);
|
||||
|
||||
ControllerDefinition = CreateControllerDefinition();
|
||||
|
||||
// check if we're just analysing the core and the core path matches the loaded core path anyways
|
||||
if (analysis && corePath == LoadedCorePath)
|
||||
{
|
||||
Description = CalculateDescription();
|
||||
Description.SupportsNoGame = LoadedCoreSupportsNoGame;
|
||||
// don't set init, we don't want the core deinit later
|
||||
}
|
||||
else
|
||||
{
|
||||
api.retro_set_environment(cb_procs.retro_environment_proc);
|
||||
Description = CalculateDescription();
|
||||
}
|
||||
|
||||
if (!analysis)
|
||||
{
|
||||
LoadedCorePath = corePath;
|
||||
LoadedCoreSupportsNoGame = Description.SupportsNoGame;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private class RetroData
|
||||
{
|
||||
private readonly GCHandle _handle;
|
||||
|
||||
public IntPtr PinnedData => _handle.AddrOfPinnedObject();
|
||||
public long Length { get; }
|
||||
|
||||
public RetroData(object o, long len = 0)
|
||||
{
|
||||
_handle = GCHandle.Alloc(o, GCHandleType.Pinned);
|
||||
Length = len;
|
||||
}
|
||||
|
||||
~RetroData() => _handle.Free();
|
||||
}
|
||||
|
||||
private byte[] RetroString(string managedString)
|
||||
{
|
||||
var s = Encoding.UTF8.GetBytes(managedString);
|
||||
var ret = new byte[s.Length + 1];
|
||||
Array.Copy(s, ret, s.Length);
|
||||
ret[s.Length] = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private LibretroApi.retro_system_av_info av_info;
|
||||
|
||||
private bool inited = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
if (inited)
|
||||
{
|
||||
api.retro_deinit();
|
||||
api.retro_unload_game();
|
||||
inited = false;
|
||||
}
|
||||
|
||||
bridge.LibretroBridge_DestroyCallbackHandler(cbHandler);
|
||||
|
||||
_blipL?.Dispose();
|
||||
_blipR?.Dispose();
|
||||
}
|
||||
|
||||
public RetroDescription Description { get; }
|
||||
|
||||
// single instance hacks
|
||||
private static string LoadedCorePath { get; set; }
|
||||
private static bool LoadedCoreSupportsNoGame { get; set; }
|
||||
|
||||
public enum RETRO_LOAD
|
||||
{
|
||||
DATA,
|
||||
PATH,
|
||||
NO_GAME,
|
||||
}
|
||||
|
||||
public bool LoadData(byte[] data, string id) => LoadHandler(RETRO_LOAD.DATA, new(RetroString(id)), new(data, data.LongLength));
|
||||
|
||||
public bool LoadPath(string path) => LoadHandler(RETRO_LOAD.PATH, new(RetroString(path)));
|
||||
|
||||
public bool LoadNoGame() => LoadHandler(RETRO_LOAD.NO_GAME);
|
||||
|
||||
private unsafe bool LoadHandler(RETRO_LOAD which, RetroData path = null, RetroData data = null)
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
var game = new LibretroApi.retro_game_info();
|
||||
var gameptr = (IntPtr)(&game);
|
||||
|
||||
if (which == RETRO_LOAD.NO_GAME)
|
||||
{
|
||||
gameptr = IntPtr.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
game.path = path.PinnedData;
|
||||
if (which == RETRO_LOAD.DATA)
|
||||
{
|
||||
game.data = data.PinnedData;
|
||||
game.size = data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
api.retro_init();
|
||||
bool success = api.retro_load_game(gameptr);
|
||||
if (!success)
|
||||
{
|
||||
api.retro_deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
var av = new LibretroApi.retro_system_av_info();
|
||||
api.retro_get_system_av_info((IntPtr)(&av));
|
||||
av_info = av;
|
||||
|
||||
api.retro_set_video_refresh(cb_procs.retro_video_refresh_proc);
|
||||
api.retro_set_audio_sample(cb_procs.retro_audio_sample_proc);
|
||||
api.retro_set_audio_sample_batch(cb_procs.retro_audio_sample_batch_proc);
|
||||
api.retro_set_input_poll(cb_procs.retro_input_poll_proc);
|
||||
api.retro_set_input_state(cb_procs.retro_input_state_proc);
|
||||
|
||||
_stateBuf = new byte[_stateLen = api.retro_serialize_size()];
|
||||
|
||||
_region = api.retro_get_region();
|
||||
|
||||
//this stuff can only happen after the game is loaded
|
||||
|
||||
//allocate a video buffer which will definitely be large enough
|
||||
InitVideoBuffer((int)av.base_width, (int)av.base_height, (int)(av.max_width * av.max_height));
|
||||
|
||||
// TODO: more precise
|
||||
VsyncNumerator = (int)(10000000 * av.fps);
|
||||
VsyncDenominator = 10000000;
|
||||
|
||||
SetupResampler(av.fps, av.sample_rate);
|
||||
|
||||
InitMemoryDomains(); // im going to assume this should happen when a game is loaded
|
||||
|
||||
inited = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private LibretroApi.retro_message retro_msg = new();
|
||||
|
||||
private CoreComm Comm { get; }
|
||||
|
||||
private void FrameAdvancePrep(IController controller)
|
||||
{
|
||||
UpdateInput(controller);
|
||||
}
|
||||
|
||||
private void FrameAdvancePost(bool render, bool renderSound)
|
||||
{
|
||||
if (render)
|
||||
{
|
||||
UpdateVideoBuffer();
|
||||
}
|
||||
|
||||
ProcessSound();
|
||||
if (!renderSound)
|
||||
{
|
||||
DiscardSamples();
|
||||
}
|
||||
|
||||
bridge.LibretroBridge_GetRetroMessage(cbHandler, ref retro_msg);
|
||||
if (retro_msg.frames > 0)
|
||||
{
|
||||
Comm.Notify(Mershul.PtrToStringUtf8(retro_msg.msg));
|
||||
}
|
||||
}
|
||||
|
||||
public bool FrameAdvance(IController controller, bool render, bool renderSound = true)
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
FrameAdvancePrep(controller);
|
||||
api.retro_run();
|
||||
FrameAdvancePost(render, renderSound);
|
||||
|
||||
Frame++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static ControllerDefinition CreateControllerDefinition()
|
||||
{
|
||||
ControllerDefinition definition = new("LibRetro Controls"/*for compatibility*/);
|
||||
|
||||
foreach (var item in new[] {
|
||||
"P1 {0} Up", "P1 {0} Down", "P1 {0} Left", "P1 {0} Right", "P1 {0} Select", "P1 {0} Start", "P1 {0} Y", "P1 {0} B", "P1 {0} X", "P1 {0} A", "P1 {0} L", "P1 {0} R",
|
||||
"P2 {0} Up", "P2 {0} Down", "P2 {0} Left", "P2 {0} Right", "P2 {0} Select", "P2 {0} Start", "P2 {0} Y", "P2 {0} B", "P2 {0} X", "P2 {0} A", "P2 {0} L", "P2 {0} R",
|
||||
})
|
||||
definition.BoolButtons.Add(string.Format(item, "RetroPad"));
|
||||
|
||||
definition.BoolButtons.Add("Pointer Pressed"); //TODO: this isnt showing up in the binding panel. I don't want to find out why.
|
||||
definition.AddXYPair("Pointer {0}", AxisPairOrientation.RightAndUp, (-32767).RangeTo(32767), 0);
|
||||
|
||||
foreach (var key in new[]{
|
||||
"Key Backspace", "Key Tab", "Key Clear", "Key Return", "Key Pause", "Key Escape",
|
||||
"Key Space", "Key Exclaim", "Key QuoteDbl", "Key Hash", "Key Dollar", "Key Ampersand", "Key Quote", "Key LeftParen", "Key RightParen", "Key Asterisk", "Key Plus", "Key Comma", "Key Minus", "Key Period", "Key Slash",
|
||||
"Key 0", "Key 1", "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9",
|
||||
"Key Colon", "Key Semicolon", "Key Less", "Key Equals", "Key Greater", "Key Question", "Key At", "Key LeftBracket", "Key Backslash", "Key RightBracket", "Key Caret", "Key Underscore", "Key Backquote",
|
||||
"Key A", "Key B", "Key C", "Key D", "Key E", "Key F", "Key G", "Key H", "Key I", "Key J", "Key K", "Key L", "Key M", "Key N", "Key O", "Key P", "Key Q", "Key R", "Key S", "Key T", "Key U", "Key V", "Key W", "Key X", "Key Y", "Key Z",
|
||||
"Key Delete",
|
||||
"Key KP0", "Key KP1", "Key KP2", "Key KP3", "Key KP4", "Key KP5", "Key KP6", "Key KP7", "Key KP8", "Key KP9",
|
||||
"Key KP_Period", "Key KP_Divide", "Key KP_Multiply", "Key KP_Minus", "Key KP_Plus", "Key KP_Enter", "Key KP_Equals",
|
||||
"Key Up", "Key Down", "Key Right", "Key Left", "Key Insert", "Key Home", "Key End", "Key PageUp", "Key PageDown",
|
||||
"Key F1", "Key F2", "Key F3", "Key F4", "Key F5", "Key F6", "Key F7", "Key F8", "Key F9", "Key F10", "Key F11", "Key F12", "Key F13", "Key F14", "Key F15",
|
||||
"Key NumLock", "Key CapsLock", "Key ScrollLock", "Key RShift", "Key LShift", "Key RCtrl", "Key LCtrl", "Key RAlt", "Key LAlt", "Key RMeta", "Key LMeta", "Key LSuper", "Key RSuper", "Key Mode", "Key Compose",
|
||||
"Key Help", "Key Print", "Key SysReq", "Key Break", "Key Menu", "Key Power", "Key Euro", "Key Undo"
|
||||
})
|
||||
{
|
||||
definition.BoolButtons.Add(key);
|
||||
definition.CategoryLabels[key] = "RetroKeyboard";
|
||||
}
|
||||
|
||||
return definition.MakeImmutable();
|
||||
}
|
||||
|
||||
public ControllerDefinition ControllerDefinition { get; }
|
||||
public int Frame { get; set; }
|
||||
public string SystemId => VSystemID.Raw.Libretro;
|
||||
public bool DeterministicEmulation => false;
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
LagCount = 0;
|
||||
IsLagFrame = false;
|
||||
}
|
||||
|
||||
public unsafe RetroDescription CalculateDescription()
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
var descr = new RetroDescription();
|
||||
var sys_info = new LibretroApi.retro_system_info();
|
||||
api.retro_get_system_info((IntPtr)(&sys_info));
|
||||
descr.LibraryName = Mershul.PtrToStringUtf8(sys_info.library_name);
|
||||
descr.LibraryVersion = Mershul.PtrToStringUtf8(sys_info.library_version);
|
||||
descr.ValidExtensions = Mershul.PtrToStringUtf8(sys_info.valid_extensions);
|
||||
descr.NeedsRomAsPath = sys_info.need_fullpath;
|
||||
descr.NeedsArchives = sys_info.block_extract;
|
||||
descr.SupportsNoGame = bridge.LibretroBridge_GetSupportsNoGame(cbHandler);
|
||||
return descr;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : IInputPollable
|
||||
{
|
||||
public int LagCount { get; set; }
|
||||
public bool IsLagFrame { get; set; }
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public IInputCallbackSystem InputCallbacks => throw new NotImplementedException();
|
||||
|
||||
// todo: make this better
|
||||
void UpdateInput(IController controller)
|
||||
{
|
||||
short[] input = new short[(int)LibretroApi.RETRO_DEVICE_ID_JOYPAD.LAST];
|
||||
// joypad port 0
|
||||
for (uint i = 0; i < input.Length; i++)
|
||||
{
|
||||
input[i] = retro_input_state(controller, 0, (uint)LibretroApi.RETRO_DEVICE.JOYPAD, 0, i);
|
||||
}
|
||||
bridge.LibretroBridge_SetInput(cbHandler, LibretroApi.RETRO_DEVICE.JOYPAD, 0, input);
|
||||
// joypad port 1
|
||||
for (uint i = 0; i < input.Length; i++)
|
||||
{
|
||||
input[i] = retro_input_state(controller, 0, (uint)LibretroApi.RETRO_DEVICE.JOYPAD, 1, i);
|
||||
}
|
||||
bridge.LibretroBridge_SetInput(cbHandler, LibretroApi.RETRO_DEVICE.JOYPAD, 1, input);
|
||||
input = new short[(int)LibretroApi.RETRO_DEVICE_ID_POINTER.LAST];
|
||||
// pointer port 0
|
||||
for (uint i = 0; i < input.Length; i++)
|
||||
{
|
||||
input[i] = retro_input_state(controller, 0, (uint)LibretroApi.RETRO_DEVICE.POINTER, 0, i);
|
||||
}
|
||||
bridge.LibretroBridge_SetInput(cbHandler, LibretroApi.RETRO_DEVICE.POINTER, 0, input);
|
||||
input = new short[(int)LibretroApi.RETRO_KEY.LAST];
|
||||
// keyboard port 0
|
||||
for (uint i = 0; i < input.Length; i++)
|
||||
{
|
||||
input[i] = retro_input_state(controller, 0, (uint)LibretroApi.RETRO_DEVICE.KEYBOARD, 0, i);
|
||||
}
|
||||
bridge.LibretroBridge_SetInput(cbHandler, LibretroApi.RETRO_DEVICE.KEYBOARD, 0, input);
|
||||
}
|
||||
|
||||
//meanings (they are kind of hazy, but once we're done implementing this it will be completely defined by example)
|
||||
//port = console physical port?
|
||||
//device = logical device type
|
||||
//index = sub device index? (multitap?)
|
||||
//id = button id (or key id)
|
||||
private static short retro_input_state(IController controller, uint port, uint device, uint index, uint id)
|
||||
{
|
||||
//helpful debugging
|
||||
//Console.WriteLine("{0} {1} {2} {3}", port, device, index, id);
|
||||
|
||||
switch ((LibretroApi.RETRO_DEVICE)device)
|
||||
{
|
||||
case LibretroApi.RETRO_DEVICE.POINTER:
|
||||
{
|
||||
return (LibretroApi.RETRO_DEVICE_ID_POINTER)id switch
|
||||
{
|
||||
LibretroApi.RETRO_DEVICE_ID_POINTER.X => (short)controller.AxisValue("Pointer X"),
|
||||
LibretroApi.RETRO_DEVICE_ID_POINTER.Y => (short)controller.AxisValue("Pointer Y"),
|
||||
LibretroApi.RETRO_DEVICE_ID_POINTER.PRESSED => (short)(controller.IsPressed("Pointer Pressed") ? 1 : 0),
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
case LibretroApi.RETRO_DEVICE.KEYBOARD:
|
||||
{
|
||||
string button = (LibretroApi.RETRO_KEY)id switch
|
||||
{
|
||||
LibretroApi.RETRO_KEY.BACKSPACE => "Backspace",
|
||||
LibretroApi.RETRO_KEY.TAB => "Tab",
|
||||
LibretroApi.RETRO_KEY.CLEAR => "Clear",
|
||||
LibretroApi.RETRO_KEY.RETURN => "Return",
|
||||
LibretroApi.RETRO_KEY.PAUSE => "Pause",
|
||||
LibretroApi.RETRO_KEY.ESCAPE => "Escape",
|
||||
LibretroApi.RETRO_KEY.SPACE => "Space",
|
||||
LibretroApi.RETRO_KEY.EXCLAIM => "Exclaim",
|
||||
LibretroApi.RETRO_KEY.QUOTEDBL => "QuoteDbl",
|
||||
LibretroApi.RETRO_KEY.HASH => "Hash",
|
||||
LibretroApi.RETRO_KEY.DOLLAR => "Dollar",
|
||||
LibretroApi.RETRO_KEY.AMPERSAND => "Ampersand",
|
||||
LibretroApi.RETRO_KEY.QUOTE => "Quote",
|
||||
LibretroApi.RETRO_KEY.LEFTPAREN => "LeftParen",
|
||||
LibretroApi.RETRO_KEY.RIGHTPAREN => "RightParen",
|
||||
LibretroApi.RETRO_KEY.ASTERISK => "Asterisk",
|
||||
LibretroApi.RETRO_KEY.PLUS => "Plus",
|
||||
LibretroApi.RETRO_KEY.COMMA => "Comma",
|
||||
LibretroApi.RETRO_KEY.MINUS => "Minus",
|
||||
LibretroApi.RETRO_KEY.PERIOD => "Period",
|
||||
LibretroApi.RETRO_KEY.SLASH => "Slash",
|
||||
LibretroApi.RETRO_KEY._0 => "0",
|
||||
LibretroApi.RETRO_KEY._1 => "1",
|
||||
LibretroApi.RETRO_KEY._2 => "2",
|
||||
LibretroApi.RETRO_KEY._3 => "3",
|
||||
LibretroApi.RETRO_KEY._4 => "4",
|
||||
LibretroApi.RETRO_KEY._5 => "5",
|
||||
LibretroApi.RETRO_KEY._6 => "6",
|
||||
LibretroApi.RETRO_KEY._7 => "7",
|
||||
LibretroApi.RETRO_KEY._8 => "8",
|
||||
LibretroApi.RETRO_KEY._9 => "9",
|
||||
LibretroApi.RETRO_KEY.COLON => "Colon",
|
||||
LibretroApi.RETRO_KEY.SEMICOLON => "Semicolon",
|
||||
LibretroApi.RETRO_KEY.LESS => "Less",
|
||||
LibretroApi.RETRO_KEY.EQUALS => "Equals",
|
||||
LibretroApi.RETRO_KEY.GREATER => "Greater",
|
||||
LibretroApi.RETRO_KEY.QUESTION => "Question",
|
||||
LibretroApi.RETRO_KEY.AT => "At",
|
||||
LibretroApi.RETRO_KEY.LEFTBRACKET => "LeftBracket",
|
||||
LibretroApi.RETRO_KEY.BACKSLASH => "Backslash",
|
||||
LibretroApi.RETRO_KEY.RIGHTBRACKET => "RightBracket",
|
||||
LibretroApi.RETRO_KEY.CARET => "Caret",
|
||||
LibretroApi.RETRO_KEY.UNDERSCORE => "Underscore",
|
||||
LibretroApi.RETRO_KEY.BACKQUOTE => "Backquote",
|
||||
LibretroApi.RETRO_KEY.a => "A",
|
||||
LibretroApi.RETRO_KEY.b => "B",
|
||||
LibretroApi.RETRO_KEY.c => "C",
|
||||
LibretroApi.RETRO_KEY.d => "D",
|
||||
LibretroApi.RETRO_KEY.e => "E",
|
||||
LibretroApi.RETRO_KEY.f => "F",
|
||||
LibretroApi.RETRO_KEY.g => "G",
|
||||
LibretroApi.RETRO_KEY.h => "H",
|
||||
LibretroApi.RETRO_KEY.i => "I",
|
||||
LibretroApi.RETRO_KEY.j => "J",
|
||||
LibretroApi.RETRO_KEY.k => "K",
|
||||
LibretroApi.RETRO_KEY.l => "L",
|
||||
LibretroApi.RETRO_KEY.m => "M",
|
||||
LibretroApi.RETRO_KEY.n => "N",
|
||||
LibretroApi.RETRO_KEY.o => "O",
|
||||
LibretroApi.RETRO_KEY.p => "P",
|
||||
LibretroApi.RETRO_KEY.q => "Q",
|
||||
LibretroApi.RETRO_KEY.r => "R",
|
||||
LibretroApi.RETRO_KEY.s => "S",
|
||||
LibretroApi.RETRO_KEY.t => "T",
|
||||
LibretroApi.RETRO_KEY.u => "U",
|
||||
LibretroApi.RETRO_KEY.v => "V",
|
||||
LibretroApi.RETRO_KEY.w => "W",
|
||||
LibretroApi.RETRO_KEY.x => "X",
|
||||
LibretroApi.RETRO_KEY.y => "Y",
|
||||
LibretroApi.RETRO_KEY.z => "Z",
|
||||
LibretroApi.RETRO_KEY.DELETE => "Delete",
|
||||
LibretroApi.RETRO_KEY.KP0 => "KP0",
|
||||
LibretroApi.RETRO_KEY.KP1 => "KP1",
|
||||
LibretroApi.RETRO_KEY.KP2 => "KP2",
|
||||
LibretroApi.RETRO_KEY.KP3 => "KP3",
|
||||
LibretroApi.RETRO_KEY.KP4 => "KP4",
|
||||
LibretroApi.RETRO_KEY.KP5 => "KP5",
|
||||
LibretroApi.RETRO_KEY.KP6 => "KP6",
|
||||
LibretroApi.RETRO_KEY.KP7 => "KP7",
|
||||
LibretroApi.RETRO_KEY.KP8 => "KP8",
|
||||
LibretroApi.RETRO_KEY.KP9 => "KP9",
|
||||
LibretroApi.RETRO_KEY.KP_PERIOD => "KP_Period",
|
||||
LibretroApi.RETRO_KEY.KP_DIVIDE => "KP_Divide",
|
||||
LibretroApi.RETRO_KEY.KP_MULTIPLY => "KP_Multiply",
|
||||
LibretroApi.RETRO_KEY.KP_MINUS => "KP_Minus",
|
||||
LibretroApi.RETRO_KEY.KP_PLUS => "KP_Plus",
|
||||
LibretroApi.RETRO_KEY.KP_ENTER => "KP_Enter",
|
||||
LibretroApi.RETRO_KEY.KP_EQUALS => "KP_Equals",
|
||||
LibretroApi.RETRO_KEY.UP => "Up",
|
||||
LibretroApi.RETRO_KEY.DOWN => "Down",
|
||||
LibretroApi.RETRO_KEY.RIGHT => "Right",
|
||||
LibretroApi.RETRO_KEY.LEFT => "Left",
|
||||
LibretroApi.RETRO_KEY.INSERT => "Insert",
|
||||
LibretroApi.RETRO_KEY.HOME => "Home",
|
||||
LibretroApi.RETRO_KEY.END => "End",
|
||||
LibretroApi.RETRO_KEY.PAGEUP => "PageUp",
|
||||
LibretroApi.RETRO_KEY.PAGEDOWN => "PageDown",
|
||||
LibretroApi.RETRO_KEY.F1 => "F1",
|
||||
LibretroApi.RETRO_KEY.F2 => "F2",
|
||||
LibretroApi.RETRO_KEY.F3 => "F3",
|
||||
LibretroApi.RETRO_KEY.F4 => "F4",
|
||||
LibretroApi.RETRO_KEY.F5 => "F5",
|
||||
LibretroApi.RETRO_KEY.F6 => "F6",
|
||||
LibretroApi.RETRO_KEY.F7 => "F7",
|
||||
LibretroApi.RETRO_KEY.F8 => "F8",
|
||||
LibretroApi.RETRO_KEY.F9 => "F9",
|
||||
LibretroApi.RETRO_KEY.F10 => "F10",
|
||||
LibretroApi.RETRO_KEY.F11 => "F11",
|
||||
LibretroApi.RETRO_KEY.F12 => "F12",
|
||||
LibretroApi.RETRO_KEY.F13 => "F13",
|
||||
LibretroApi.RETRO_KEY.F14 => "F14",
|
||||
LibretroApi.RETRO_KEY.F15 => "F15",
|
||||
LibretroApi.RETRO_KEY.NUMLOCK => "NumLock",
|
||||
LibretroApi.RETRO_KEY.CAPSLOCK => "CapsLock",
|
||||
LibretroApi.RETRO_KEY.SCROLLOCK => "ScrollLock",
|
||||
LibretroApi.RETRO_KEY.RSHIFT => "RShift",
|
||||
LibretroApi.RETRO_KEY.LSHIFT => "LShift",
|
||||
LibretroApi.RETRO_KEY.RCTRL => "RCtrl",
|
||||
LibretroApi.RETRO_KEY.LCTRL => "LCtrl",
|
||||
LibretroApi.RETRO_KEY.RALT => "RAlt",
|
||||
LibretroApi.RETRO_KEY.LALT => "LAlt",
|
||||
LibretroApi.RETRO_KEY.RMETA => "RMeta",
|
||||
LibretroApi.RETRO_KEY.LMETA => "LMeta",
|
||||
LibretroApi.RETRO_KEY.LSUPER => "LSuper",
|
||||
LibretroApi.RETRO_KEY.RSUPER => "RSuper",
|
||||
LibretroApi.RETRO_KEY.MODE => "Mode",
|
||||
LibretroApi.RETRO_KEY.COMPOSE => "Compose",
|
||||
LibretroApi.RETRO_KEY.HELP => "Help",
|
||||
LibretroApi.RETRO_KEY.PRINT => "Print",
|
||||
LibretroApi.RETRO_KEY.SYSREQ => "SysReq",
|
||||
LibretroApi.RETRO_KEY.BREAK => "Break",
|
||||
LibretroApi.RETRO_KEY.MENU => "Menu",
|
||||
LibretroApi.RETRO_KEY.POWER => "Power",
|
||||
LibretroApi.RETRO_KEY.EURO => "Euro",
|
||||
LibretroApi.RETRO_KEY.UNDO => "Undo",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
return (short)(controller.IsPressed("Key " + button) ? 1 : 0);
|
||||
}
|
||||
|
||||
case LibretroApi.RETRO_DEVICE.JOYPAD:
|
||||
{
|
||||
string button = (LibretroApi.RETRO_DEVICE_ID_JOYPAD)id switch
|
||||
{
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.A => "A",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.B => "B",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.X => "X",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.Y => "Y",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.UP => "Up",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.DOWN => "Down",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.LEFT => "Left",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.RIGHT => "Right",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.L => "L",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.R => "R",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.SELECT => "Select",
|
||||
LibretroApi.RETRO_DEVICE_ID_JOYPAD.START => "Start",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
return (short)(GetButton(controller, port + 1, "RetroPad", button) ? 1 : 0);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetButton(IController controller, uint pnum, string type, string button)
|
||||
{
|
||||
string key = $"P{pnum} {type} {button}";
|
||||
return controller.IsPressed(key);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator
|
||||
{
|
||||
private readonly List<MemoryDomain> _memoryDomains = new();
|
||||
private IMemoryDomains MemoryDomains { get; set; }
|
||||
|
||||
private void InitMemoryDomains()
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
foreach (LibretroApi.RETRO_MEMORY m in Enum.GetValues(typeof(LibretroApi.RETRO_MEMORY)))
|
||||
{
|
||||
var mem = api.retro_get_memory_data(m);
|
||||
var sz = api.retro_get_memory_size(m);
|
||||
if (mem != IntPtr.Zero && sz > 0)
|
||||
{
|
||||
var d = new MemoryDomainIntPtr(Enum.GetName(m.GetType(), m), MemoryDomain.Endian.Little, mem, sz, true, 1);
|
||||
_memoryDomains.Add(d);
|
||||
if (m is LibretroApi.RETRO_MEMORY.SAVE_RAM or LibretroApi.RETRO_MEMORY.RTC)
|
||||
{
|
||||
_saveramAreas.Add(d);
|
||||
_saveramSize += d.Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MemoryDomains = new MemoryDomainList(_memoryDomains);
|
||||
_serviceProvider.Register(MemoryDomains);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : IRegionable
|
||||
{
|
||||
private LibretroApi.RETRO_REGION _region = LibretroApi.RETRO_REGION.NTSC;
|
||||
|
||||
public DisplayType Region
|
||||
{
|
||||
get
|
||||
{
|
||||
return _region switch
|
||||
{
|
||||
LibretroApi.RETRO_REGION.NTSC => DisplayType.NTSC,
|
||||
LibretroApi.RETRO_REGION.PAL => DisplayType.PAL,
|
||||
_ => DisplayType.NTSC,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : ISaveRam
|
||||
{
|
||||
private readonly List<MemoryDomainIntPtr> _saveramAreas = new();
|
||||
private long _saveramSize = 0;
|
||||
|
||||
public bool SaveRamModified => _saveramSize > 0;
|
||||
|
||||
public byte[] CloneSaveRam()
|
||||
{
|
||||
if (_saveramSize > 0)
|
||||
{
|
||||
var buf = new byte[_saveramSize];
|
||||
int index = 0;
|
||||
foreach (var m in _saveramAreas)
|
||||
{
|
||||
Marshal.Copy(m.Data, buf, index, (int)m.Size);
|
||||
index += (int)m.Size;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void StoreSaveRam(byte[] data)
|
||||
{
|
||||
if (_saveramSize > 0)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (var m in _saveramAreas)
|
||||
{
|
||||
Marshal.Copy(data, index, m.Data, (int)m.Size);
|
||||
index += (int)m.Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : ISoundProvider
|
||||
{
|
||||
private BlipBuffer _blipL;
|
||||
private BlipBuffer _blipR;
|
||||
|
||||
private short[] _inSampBuf = new short[0];
|
||||
private short[] _outSampBuf = new short[0];
|
||||
private int _outSamps;
|
||||
|
||||
private int _latchL = 0;
|
||||
private int _latchR = 0;
|
||||
|
||||
private void SetupResampler(double fps, double sps)
|
||||
{
|
||||
Console.WriteLine("FPS {0} SPS {1}", fps, sps);
|
||||
|
||||
_outSampBuf = new short[44100]; // big enough
|
||||
|
||||
_blipL = new BlipBuffer(44100);
|
||||
_blipL.SetRates(sps, 44100);
|
||||
_blipR = new BlipBuffer(44100);
|
||||
_blipR.SetRates(sps, 44100);
|
||||
}
|
||||
|
||||
private void ProcessSound()
|
||||
{
|
||||
var len = bridge.LibretroBridge_GetAudioSize(cbHandler);
|
||||
if (len == 0) // no audio?
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (len > _inSampBuf.Length)
|
||||
{
|
||||
_inSampBuf = new short[len];
|
||||
}
|
||||
var ns = 0;
|
||||
bridge.LibretroBridge_GetAudio(cbHandler, ref ns, _inSampBuf);
|
||||
|
||||
for (uint i = 0; i < ns; i++)
|
||||
{
|
||||
int curr = _inSampBuf[i * 2];
|
||||
|
||||
if (curr != _latchL)
|
||||
{
|
||||
int diff = _latchL - curr;
|
||||
_latchL = curr;
|
||||
_blipL.AddDelta(i, diff);
|
||||
}
|
||||
|
||||
curr = _inSampBuf[(i * 2) + 1];
|
||||
|
||||
if (curr != _latchR)
|
||||
{
|
||||
int diff = _latchR - curr;
|
||||
_latchR = curr;
|
||||
_blipR.AddDelta(i, diff);
|
||||
}
|
||||
}
|
||||
|
||||
_blipL.EndFrame((uint)ns);
|
||||
_blipR.EndFrame((uint)ns);
|
||||
_outSamps = _blipL.SamplesAvailable();
|
||||
_blipL.ReadSamplesLeft(_outSampBuf, _outSamps);
|
||||
_blipR.ReadSamplesRight(_outSampBuf, _outSamps);
|
||||
}
|
||||
|
||||
public bool CanProvideAsync => false;
|
||||
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode == SyncSoundMode.Async)
|
||||
{
|
||||
throw new NotSupportedException("Async mode is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
nsamp = _outSamps;
|
||||
samples = _outSampBuf;
|
||||
DiscardSamples();
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new InvalidOperationException("Async mode is not supported.");
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
_outSamps = 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : IStatable
|
||||
{
|
||||
private byte[] _stateBuf;
|
||||
private long _stateLen;
|
||||
|
||||
public void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
_stateLen = api.retro_serialize_size();
|
||||
if (_stateBuf.LongLength != _stateLen)
|
||||
{
|
||||
_stateBuf = new byte[_stateLen];
|
||||
}
|
||||
|
||||
var d = new RetroData(_stateBuf, _stateLen);
|
||||
api.retro_serialize(d.PinnedData, d.Length);
|
||||
writer.Write(_stateBuf.Length);
|
||||
writer.Write(_stateBuf);
|
||||
// other variables
|
||||
writer.Write(Frame);
|
||||
writer.Write(LagCount);
|
||||
writer.Write(IsLagFrame);
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
UpdateCallbackHandler();
|
||||
|
||||
var newlen = reader.ReadInt32();
|
||||
if (newlen > _stateBuf.Length)
|
||||
{
|
||||
throw new Exception("Unexpected buffer size");
|
||||
}
|
||||
|
||||
reader.Read(_stateBuf, 0, newlen);
|
||||
var d = new RetroData(_stateBuf, _stateLen);
|
||||
api.retro_unserialize(d.PinnedData, d.Length);
|
||||
// other variables
|
||||
Frame = reader.ReadInt32();
|
||||
LagCount = reader.ReadInt32();
|
||||
IsLagFrame = reader.ReadBoolean();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroEmulator : IVideoProvider
|
||||
{
|
||||
private int[] vidBuffer;
|
||||
private int vidWidth = -1, vidHeight = -1;
|
||||
|
||||
private void InitVideoBuffer(int width, int height, int maxSize)
|
||||
{
|
||||
vidBuffer = new int[maxSize];
|
||||
vidWidth = width;
|
||||
vidHeight = height;
|
||||
bridge.LibretroBridge_SetVideoSize(cbHandler, maxSize);
|
||||
}
|
||||
|
||||
private void UpdateVideoBuffer()
|
||||
{
|
||||
bridge.LibretroBridge_GetVideo(cbHandler, ref vidWidth, ref vidHeight, vidBuffer);
|
||||
}
|
||||
|
||||
public int BackgroundColor => 0;
|
||||
public int[] GetVideoBuffer() => vidBuffer;
|
||||
|
||||
public int VirtualWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
var dar = av_info.aspect_ratio;
|
||||
if (dar <= 0)
|
||||
{
|
||||
return vidWidth;
|
||||
}
|
||||
if (dar > 1.0f)
|
||||
{
|
||||
return (int)(vidHeight * dar);
|
||||
}
|
||||
return vidWidth;
|
||||
}
|
||||
}
|
||||
|
||||
public int VirtualHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
var dar = av_info.aspect_ratio;
|
||||
if (dar <= 0)
|
||||
{
|
||||
return vidHeight;
|
||||
}
|
||||
if (dar < 1.0f)
|
||||
{
|
||||
return (int)(vidWidth / dar);
|
||||
}
|
||||
return vidHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public int BufferWidth => vidWidth;
|
||||
public int BufferHeight => vidHeight;
|
||||
|
||||
public int VsyncNumerator { get; private set; }
|
||||
public int VsyncDenominator { get; private set; }
|
||||
}
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
//TODO: this isn't "the libretro api" anymore
|
||||
//it's more like a bridge
|
||||
//I may need to rename stuff to make it sound more like a bridge
|
||||
//(the bridge wraps libretro API and presents a very different interface)
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public unsafe partial class LibretroApi : IDisposable
|
||||
{
|
||||
private readonly InstanceDll instanceDll, instanceDllCore;
|
||||
private string InstanceName;
|
||||
|
||||
//YUCK
|
||||
public LibretroCore core;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate IntPtr DllInit(IntPtr dllModule);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate IntPtr DllInitUnix(string dllModule);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void MessageApi(eMessage msg);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void BufferApi(BufId id, void* ptr, ulong size); //size_t
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void SetVariableApi(string key, string value);
|
||||
|
||||
//it's NOT the original plan to make this public
|
||||
//however -- i need to merge the API and the core. theyre too closely related
|
||||
public CommStruct* comm;
|
||||
|
||||
private readonly MessageApi Message;
|
||||
private readonly BufferApi _copyBuffer; //TODO: consider making private and wrapping
|
||||
private readonly BufferApi _setBuffer; //TODO: consider making private and wrapping
|
||||
private SetVariableApi SetVariable;
|
||||
|
||||
public LibretroApi(string dllPath, string corePath)
|
||||
{
|
||||
T GetTypedDelegate<T>(string proc) where T : Delegate => (T) Marshal.GetDelegateForFunctionPointer(instanceDll.GetProcAddrOrThrow(proc), typeof(T));
|
||||
|
||||
InstanceName = "libretro_" + Guid.NewGuid();
|
||||
|
||||
var pipeName = InstanceName;
|
||||
|
||||
instanceDll = new InstanceDll(dllPath);
|
||||
instanceDllCore = new InstanceDll(corePath);
|
||||
|
||||
Message = GetTypedDelegate<MessageApi>("Message");
|
||||
_copyBuffer = GetTypedDelegate<BufferApi>("CopyBuffer");
|
||||
_setBuffer = GetTypedDelegate<BufferApi>("SetBuffer");
|
||||
SetVariable = GetTypedDelegate<SetVariableApi>("SetVariable");
|
||||
|
||||
comm = (CommStruct*) (OSTailoredCode.IsUnixHost
|
||||
? GetTypedDelegate<DllInitUnix>("DllInit")(corePath)
|
||||
: GetTypedDelegate<DllInit>("DllInit")(instanceDllCore.HModule)).ToPointer();
|
||||
|
||||
//TODO: (stash function pointers locally and thunk to IntPtr)
|
||||
//ALSO: this should be done by the core, I think, not the API. No smarts should be in here
|
||||
comm->env.retro_perf_callback.get_cpu_features = IntPtr.Zero;
|
||||
//retro_perf_callback.get_cpu_features = new LibRetro.retro_get_cpu_features_t(() => (ulong)(
|
||||
// (ProcessorFeatureImports.IsProcessorFeaturePresent(ProcessorFeatureImports.ProcessorFeature.InstructionsXMMIAvailable) ? LibRetro.RETRO_SIMD.SSE : 0) |
|
||||
// (ProcessorFeatureImports.IsProcessorFeaturePresent(ProcessorFeatureImports.ProcessorFeature.InstructionsXMMI64Available) ? LibRetro.RETRO_SIMD.SSE2 : 0) |
|
||||
// (ProcessorFeatureImports.IsProcessorFeaturePresent(ProcessorFeatureImports.ProcessorFeature.InstructionsSSE3Available) ? LibRetro.RETRO_SIMD.SSE3 : 0) |
|
||||
// (ProcessorFeatureImports.IsProcessorFeaturePresent(ProcessorFeatureImports.ProcessorFeature.InstructionsMMXAvailable) ? LibRetro.RETRO_SIMD.MMX : 0)
|
||||
// ));
|
||||
//retro_perf_callback.get_perf_counter = new LibRetro.retro_perf_get_counter_t(() => System.Diagnostics.Stopwatch.GetTimestamp());
|
||||
//retro_perf_callback.get_time_usec = new LibRetro.retro_perf_get_time_usec_t(() => DateTime.Now.Ticks / 10);
|
||||
//retro_perf_callback.perf_log = new LibRetro.retro_perf_log_t(() => { });
|
||||
//retro_perf_callback.perf_register = new LibRetro.retro_perf_register_t((ref LibRetro.retro_perf_counter counter) => { });
|
||||
//retro_perf_callback.perf_start = new LibRetro.retro_perf_start_t((ref LibRetro.retro_perf_counter counter) => { });
|
||||
//retro_perf_callback.perf_stop = new LibRetro.retro_perf_stop_t((ref LibRetro.retro_perf_counter counter) => { });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
//TODO: better termination of course
|
||||
instanceDllCore.Dispose();
|
||||
instanceDll.Dispose();
|
||||
}
|
||||
|
||||
public RetroDescription CalculateDescription()
|
||||
{
|
||||
var descr = new RetroDescription();
|
||||
descr.LibraryName = new string(comm->env.retro_system_info.library_name);
|
||||
descr.LibraryVersion = new string(comm->env.retro_system_info.library_version);
|
||||
descr.ValidExtensions = new string(comm->env.retro_system_info.valid_extensions);
|
||||
descr.NeedsRomAsPath = comm->env.retro_system_info.need_fullpath;
|
||||
descr.NeedsArchives = comm->env.retro_system_info.block_extract;
|
||||
descr.SupportsNoGame = comm->env.support_no_game;
|
||||
return descr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy an ascii string into libretro. It keeps the copy.
|
||||
/// </summary>
|
||||
public void CopyAscii(BufId id, string str)
|
||||
{
|
||||
fixed (char* cp = str)
|
||||
_copyBuffer(id, cp, (ulong)str.Length + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy a buffer into libretro. It keeps the copy.
|
||||
/// </summary>
|
||||
public void CopyBytes(BufId id, byte[] bytes)
|
||||
{
|
||||
fixed (byte* bp = bytes)
|
||||
_copyBuffer(id, bp, (ulong)bytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locks a buffer and sets it into libretro. You must pass a delegate to be executed while that buffer is locked.
|
||||
/// This is meant to be used for avoiding a memcpy for large roms (which the core is then just going to memcpy again on its own)
|
||||
/// The memcpy has to happen at some point (libretro semantics specify [not literally, the docs don't say] that the core should finish using the buffer before its init returns)
|
||||
/// but this limits it to once.
|
||||
/// Moreover, this keeps the c++ side from having to free strings when they're no longer used (and memory management is trickier there, so we try to avoid it)
|
||||
/// </summary>
|
||||
public void SetBytes(BufId id, byte[] bytes, Action andThen)
|
||||
{
|
||||
fixed (byte* bp = bytes)
|
||||
{
|
||||
_setBuffer(id, bp, (ulong)bytes.Length);
|
||||
andThen();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// see SetBytes
|
||||
/// </summary>
|
||||
public void SetAscii(BufId id, string str, Action andThen)
|
||||
{
|
||||
fixed (byte* cp = System.Text.Encoding.ASCII.GetBytes(str+"\0"))
|
||||
{
|
||||
_setBuffer(id, cp, (ulong)str.Length + 1);
|
||||
andThen();
|
||||
}
|
||||
}
|
||||
|
||||
public struct CommStructEnv
|
||||
{
|
||||
public retro_system_info retro_system_info;
|
||||
public retro_system_av_info retro_system_av_info;
|
||||
|
||||
public ulong retro_serialize_size_initial; //size_t :(
|
||||
public ulong retro_serialize_size; //size_t :(
|
||||
|
||||
public uint retro_region;
|
||||
public uint retro_api_version;
|
||||
public retro_pixel_format pixel_format; //default is 0 -- RETRO_PIXEL_FORMAT_0RGB1555
|
||||
public int rotation_ccw;
|
||||
public bool support_no_game;
|
||||
public IntPtr core_get_proc_address; //this is.. a callback.. or something.. right?
|
||||
|
||||
public retro_game_geometry retro_game_geometry;
|
||||
public bool retro_game_geometry_dirty; //c# can clear this when it's acknowledged (but I think we might handle it from here? not sure)
|
||||
|
||||
public int variable_count;
|
||||
public char** variable_keys;
|
||||
public char** variable_comments;
|
||||
|
||||
//c# sets these with thunked callbacks
|
||||
public retro_perf_callback retro_perf_callback;
|
||||
|
||||
//various stashed stuff solely for c# convenience
|
||||
public ulong processor_features;
|
||||
|
||||
public int fb_width, fb_height; //core sets these; c# picks up, and..
|
||||
public int* fb_bufptr; //..sets this for the core to spill its data nito
|
||||
}
|
||||
|
||||
public struct CommStruct
|
||||
{
|
||||
//the cmd being executed
|
||||
public eMessage cmd;
|
||||
|
||||
//the status of the core
|
||||
public eStatus status;
|
||||
|
||||
//the SIG or BRK that the core is halted in
|
||||
public eMessage reason;
|
||||
|
||||
//flexible in/out parameters
|
||||
//these are all "overloaded" a little so it isn't clear what's used for what in for any particular message..
|
||||
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
|
||||
public uint id, addr, value, size;
|
||||
public uint port, device, index, slot;
|
||||
|
||||
[MarshalAs(UnmanagedType.Struct)]
|
||||
public CommStructEnv env;
|
||||
|
||||
//this should always be used in pairs
|
||||
public fixed ulong buf[(int)BufId.BufId_Num]; //actually a pointer, but can't marshal IntPtr, so dumb
|
||||
public fixed ulong buf_size[(int)BufId.BufId_Num]; //actually a size_t
|
||||
|
||||
//utilities
|
||||
public bool GetBoolValue() => value != 0; // should this be here or by the other helpers? I don't know
|
||||
}
|
||||
|
||||
public retro_system_av_info AVInfo => comm->env.retro_system_av_info;
|
||||
} //class
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroApi
|
||||
{
|
||||
private bool Handle_BRK(eMessage msg)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
default:
|
||||
return false;
|
||||
|
||||
} //switch(msg)
|
||||
|
||||
// TODO: do we want this ever?
|
||||
#if false
|
||||
Message(eMessage.Resume);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
unsafe partial class LibretroApi
|
||||
{
|
||||
private void WaitForCMD()
|
||||
{
|
||||
for (; ; )
|
||||
{
|
||||
if (comm->status == eStatus.eStatus_Idle)
|
||||
break;
|
||||
if (Handle_SIG(comm->reason)) continue;
|
||||
if (Handle_BRK(comm->reason)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
public void CMD_Run()
|
||||
{
|
||||
Message(eMessage.CMD_Run);
|
||||
WaitForCMD();
|
||||
}
|
||||
|
||||
public void CMD_SetEnvironment()
|
||||
{
|
||||
Message(eMessage.CMD_SetEnvironment);
|
||||
WaitForCMD();
|
||||
}
|
||||
|
||||
public bool CMD_LoadNoGame()
|
||||
{
|
||||
Message(eMessage.CMD_LoadNoGame);
|
||||
WaitForCMD();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CMD_LoadPath(string path)
|
||||
{
|
||||
SetAscii(BufId.Param0, path, ()=> {
|
||||
Message(eMessage.CMD_LoadPath);
|
||||
WaitForCMD();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CMD_LoadData(byte[] data, string id)
|
||||
{
|
||||
SetAscii(BufId.Param0, id, () =>
|
||||
{
|
||||
SetBytes(BufId.Param1, data, () =>
|
||||
{
|
||||
Message(eMessage.CMD_LoadData);
|
||||
WaitForCMD();
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public uint CMD_UpdateSerializeSize()
|
||||
{
|
||||
Message(eMessage.CMD_UpdateSerializeSize);
|
||||
WaitForCMD();
|
||||
return (uint)comm->env.retro_serialize_size;
|
||||
}
|
||||
|
||||
public bool CMD_Serialize(byte[] data)
|
||||
{
|
||||
bool ret = false;
|
||||
SetBytes(BufId.Param0, data, () =>
|
||||
{
|
||||
Message(eMessage.CMD_Serialize);
|
||||
WaitForCMD();
|
||||
ret = comm->GetBoolValue();
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool CMD_Unserialize(byte[] data)
|
||||
{
|
||||
bool ret = false;
|
||||
SetBytes(BufId.Param0, data, () =>
|
||||
{
|
||||
Message(eMessage.CMD_Unserialize);
|
||||
WaitForCMD();
|
||||
ret = comm->GetBoolValue();
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,313 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
public partial class LibretroApi
|
||||
{
|
||||
public enum eMessage : int
|
||||
{
|
||||
NotSet,
|
||||
|
||||
Resume,
|
||||
|
||||
QUERY_FIRST,
|
||||
QUERY_GetMemory,
|
||||
QUERY_LAST,
|
||||
|
||||
CMD_FIRST,
|
||||
CMD_SetEnvironment,
|
||||
CMD_LoadNoGame,
|
||||
CMD_LoadData,
|
||||
CMD_LoadPath,
|
||||
CMD_Deinit,
|
||||
CMD_Reset,
|
||||
CMD_Run,
|
||||
CMD_UpdateSerializeSize,
|
||||
CMD_Serialize,
|
||||
CMD_Unserialize,
|
||||
CMD_LAST,
|
||||
|
||||
SIG_InputState,
|
||||
SIG_VideoUpdate,
|
||||
SIG_Sample,
|
||||
SIG_SampleBatch,
|
||||
}
|
||||
|
||||
|
||||
public enum RETRO_MEMORY
|
||||
{
|
||||
SAVE_RAM = 0,
|
||||
RTC = 1,
|
||||
SYSTEM_RAM = 2,
|
||||
VIDEO_RAM = 3,
|
||||
}
|
||||
|
||||
|
||||
public enum RETRO_DEVICE
|
||||
{
|
||||
NONE = 0,
|
||||
JOYPAD = 1,
|
||||
MOUSE = 2,
|
||||
KEYBOARD = 3,
|
||||
LIGHTGUN = 4,
|
||||
ANALOG = 5,
|
||||
POINTER = 6,
|
||||
SENSOR_ACCELEROMETER = 7
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_ANALOG
|
||||
{
|
||||
// LEFT / RIGHT?
|
||||
X = 0,
|
||||
Y = 1
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_MOUSE
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
LEFT = 2,
|
||||
RIGHT = 3
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_LIGHTGUN
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
TRIGGER = 2,
|
||||
CURSOR = 3,
|
||||
TURBO = 4,
|
||||
PAUSE = 5,
|
||||
START = 6
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_POINTER
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
PRESSED = 2
|
||||
}
|
||||
|
||||
public enum RETRO_KEY
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
FIRST = 0,
|
||||
BACKSPACE = 8,
|
||||
TAB = 9,
|
||||
CLEAR = 12,
|
||||
RETURN = 13,
|
||||
PAUSE = 19,
|
||||
ESCAPE = 27,
|
||||
SPACE = 32,
|
||||
EXCLAIM = 33,
|
||||
QUOTEDBL = 34,
|
||||
HASH = 35,
|
||||
DOLLAR = 36,
|
||||
AMPERSAND = 38,
|
||||
QUOTE = 39,
|
||||
LEFTPAREN = 40,
|
||||
RIGHTPAREN = 41,
|
||||
ASTERISK = 42,
|
||||
PLUS = 43,
|
||||
COMMA = 44,
|
||||
MINUS = 45,
|
||||
PERIOD = 46,
|
||||
SLASH = 47,
|
||||
_0 = 48,
|
||||
_1 = 49,
|
||||
_2 = 50,
|
||||
_3 = 51,
|
||||
_4 = 52,
|
||||
_5 = 53,
|
||||
_6 = 54,
|
||||
_7 = 55,
|
||||
_8 = 56,
|
||||
_9 = 57,
|
||||
COLON = 58,
|
||||
SEMICOLON = 59,
|
||||
LESS = 60,
|
||||
EQUALS = 61,
|
||||
GREATER = 62,
|
||||
QUESTION = 63,
|
||||
AT = 64,
|
||||
LEFTBRACKET = 91,
|
||||
BACKSLASH = 92,
|
||||
RIGHTBRACKET = 93,
|
||||
CARET = 94,
|
||||
UNDERSCORE = 95,
|
||||
BACKQUOTE = 96,
|
||||
a = 97,
|
||||
b = 98,
|
||||
c = 99,
|
||||
d = 100,
|
||||
e = 101,
|
||||
f = 102,
|
||||
g = 103,
|
||||
h = 104,
|
||||
i = 105,
|
||||
j = 106,
|
||||
k = 107,
|
||||
l = 108,
|
||||
m = 109,
|
||||
n = 110,
|
||||
o = 111,
|
||||
p = 112,
|
||||
q = 113,
|
||||
r = 114,
|
||||
s = 115,
|
||||
t = 116,
|
||||
u = 117,
|
||||
v = 118,
|
||||
w = 119,
|
||||
x = 120,
|
||||
y = 121,
|
||||
z = 122,
|
||||
DELETE = 127,
|
||||
|
||||
KP0 = 256,
|
||||
KP1 = 257,
|
||||
KP2 = 258,
|
||||
KP3 = 259,
|
||||
KP4 = 260,
|
||||
KP5 = 261,
|
||||
KP6 = 262,
|
||||
KP7 = 263,
|
||||
KP8 = 264,
|
||||
KP9 = 265,
|
||||
KP_PERIOD = 266,
|
||||
KP_DIVIDE = 267,
|
||||
KP_MULTIPLY = 268,
|
||||
KP_MINUS = 269,
|
||||
KP_PLUS = 270,
|
||||
KP_ENTER = 271,
|
||||
KP_EQUALS = 272,
|
||||
|
||||
UP = 273,
|
||||
DOWN = 274,
|
||||
RIGHT = 275,
|
||||
LEFT = 276,
|
||||
INSERT = 277,
|
||||
HOME = 278,
|
||||
END = 279,
|
||||
PAGEUP = 280,
|
||||
PAGEDOWN = 281,
|
||||
|
||||
F1 = 282,
|
||||
F2 = 283,
|
||||
F3 = 284,
|
||||
F4 = 285,
|
||||
F5 = 286,
|
||||
F6 = 287,
|
||||
F7 = 288,
|
||||
F8 = 289,
|
||||
F9 = 290,
|
||||
F10 = 291,
|
||||
F11 = 292,
|
||||
F12 = 293,
|
||||
F13 = 294,
|
||||
F14 = 295,
|
||||
F15 = 296,
|
||||
|
||||
NUMLOCK = 300,
|
||||
CAPSLOCK = 301,
|
||||
SCROLLOCK = 302,
|
||||
RSHIFT = 303,
|
||||
LSHIFT = 304,
|
||||
RCTRL = 305,
|
||||
LCTRL = 306,
|
||||
RALT = 307,
|
||||
LALT = 308,
|
||||
RMETA = 309,
|
||||
LMETA = 310,
|
||||
LSUPER = 311,
|
||||
RSUPER = 312,
|
||||
MODE = 313,
|
||||
COMPOSE = 314,
|
||||
|
||||
HELP = 315,
|
||||
PRINT = 316,
|
||||
SYSREQ = 317,
|
||||
BREAK = 318,
|
||||
MENU = 319,
|
||||
POWER = 320,
|
||||
EURO = 321,
|
||||
UNDO = 322,
|
||||
|
||||
LAST
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum RETRO_MOD
|
||||
{
|
||||
NONE = 0,
|
||||
SHIFT = 1,
|
||||
CTRL = 2,
|
||||
ALT = 4,
|
||||
META = 8,
|
||||
NUMLOCK = 16,
|
||||
CAPSLOCK = 32,
|
||||
SCROLLLOCK = 64
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_SENSOR_ACCELEROMETER
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Z = 2
|
||||
}
|
||||
|
||||
public enum RETRO_DEVICE_ID_JOYPAD
|
||||
{
|
||||
B = 0,
|
||||
Y = 1,
|
||||
SELECT = 2,
|
||||
START = 3,
|
||||
UP = 4,
|
||||
DOWN = 5,
|
||||
LEFT = 6,
|
||||
RIGHT = 7,
|
||||
A = 8,
|
||||
X = 9,
|
||||
L = 10,
|
||||
R = 11,
|
||||
L2 = 12,
|
||||
R2 = 13,
|
||||
L3 = 14,
|
||||
R3 = 15
|
||||
}
|
||||
|
||||
public enum eStatus : int
|
||||
{
|
||||
eStatus_Idle,
|
||||
eStatus_CMD,
|
||||
eStatus_BRK
|
||||
}
|
||||
|
||||
public enum BufId : int
|
||||
{
|
||||
Param0 = 0,
|
||||
Param1 = 1,
|
||||
SystemDirectory = 2,
|
||||
SaveDirectory = 3,
|
||||
CoreDirectory = 4,
|
||||
CoreAssetsDirectory = 5,
|
||||
BufId_Num
|
||||
}
|
||||
|
||||
//libretro enums:
|
||||
|
||||
public enum retro_pixel_format : uint
|
||||
{
|
||||
XRGB1555 = 0,
|
||||
XRGB8888 = 1,
|
||||
RGB565 = 2
|
||||
}
|
||||
|
||||
public enum retro_region : uint
|
||||
{
|
||||
NTSC = 0,
|
||||
PAL = 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
unsafe partial class LibretroApi
|
||||
{
|
||||
public Tuple<IntPtr, ulong> QUERY_GetMemory(RETRO_MEMORY mem)
|
||||
{
|
||||
comm->value = (uint)mem;
|
||||
Message(eMessage.QUERY_GetMemory);
|
||||
return Tuple.Create(new IntPtr((long)comm->buf[(int)BufId.Param0]), comm->buf_size[(int)BufId.Param0]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
unsafe partial class LibretroApi
|
||||
{
|
||||
private bool Handle_SIG(eMessage msg)
|
||||
{
|
||||
//I know, ive done this two completely different ways
|
||||
//both ways are sloppy glue, anyway
|
||||
//I havent decided on the final architecture yet
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case eMessage.SIG_InputState:
|
||||
comm->value = (uint)core.CB_InputState(comm->port, comm->device, comm->index, comm->id);
|
||||
break;
|
||||
|
||||
case eMessage.SIG_VideoUpdate:
|
||||
core.SIG_VideoUpdate();
|
||||
break;
|
||||
|
||||
case eMessage.SIG_Sample:
|
||||
{
|
||||
short* samples = (short*)comm->buf[(int)BufId.Param0];
|
||||
core.retro_audio_sample(samples[0], samples[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case eMessage.SIG_SampleBatch:
|
||||
{
|
||||
void* samples = (void*)comm->buf[(int)BufId.Param0];
|
||||
core.retro_audio_sample_batch(samples, (int)comm->buf_size[(int)BufId.Param0]/4);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
||||
} //switch(msg)
|
||||
|
||||
Message(eMessage.Resume);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
partial class LibretroApi
|
||||
{
|
||||
|
||||
public struct retro_game_geometry
|
||||
{
|
||||
public uint base_width;
|
||||
public uint base_height;
|
||||
public uint max_width;
|
||||
public uint max_height;
|
||||
public float aspect_ratio;
|
||||
}
|
||||
|
||||
|
||||
public unsafe struct retro_system_info
|
||||
{
|
||||
public sbyte* library_name;
|
||||
public sbyte* library_version;
|
||||
public sbyte* valid_extensions;
|
||||
public bool need_fullpath;
|
||||
public bool block_extract;
|
||||
#pragma warning disable CS0169
|
||||
private short _pad;
|
||||
#pragma warning restore CS0169
|
||||
}
|
||||
|
||||
public struct retro_system_timing
|
||||
{
|
||||
public double fps;
|
||||
public double sample_rate;
|
||||
}
|
||||
|
||||
public struct retro_system_av_info
|
||||
{
|
||||
public retro_game_geometry geometry;
|
||||
public retro_system_timing timing;
|
||||
}
|
||||
|
||||
//untested
|
||||
public struct retro_perf_counter
|
||||
{
|
||||
public string ident;
|
||||
public ulong start;
|
||||
public ulong total;
|
||||
public ulong call_cnt;
|
||||
|
||||
[MarshalAs(UnmanagedType.U1)]
|
||||
public bool registered;
|
||||
}
|
||||
|
||||
//perf callbacks
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate long retro_perf_get_time_usec_t();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate long retro_perf_get_counter_t();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate ulong retro_get_cpu_features_t();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void retro_perf_log_t();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void retro_perf_register_t(ref retro_perf_counter counter);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void retro_perf_start_t(ref retro_perf_counter counter);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void retro_perf_stop_t(ref retro_perf_counter counter);
|
||||
|
||||
public struct retro_perf_callback
|
||||
{
|
||||
public IntPtr get_time_usec;
|
||||
public IntPtr get_cpu_features;
|
||||
public IntPtr get_perf_counter;
|
||||
public IntPtr perf_register;
|
||||
public IntPtr perf_start;
|
||||
public IntPtr perf_stop;
|
||||
public IntPtr perf_log;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,397 +0,0 @@
|
|||
//TODO: it's a bit of a misnomer to call this a 'core'
|
||||
//that's libretro nomenclature for a particular core (nes, genesis, doom, etc.)
|
||||
//we should call this LibretroEmulator (yeah, it was originally called that)
|
||||
//Since it's an IEmulator.. but... I don't know. Yeah, that's probably best
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
[Core(CoreNames.Libretro, "zeromus", isReleased: false)]
|
||||
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
|
||||
public unsafe partial class LibretroCore : IEmulator, ISettable<LibretroCore.Settings, LibretroCore.SyncSettings>,
|
||||
ISaveRam, IStatable, IVideoProvider, IInputPollable
|
||||
{
|
||||
private readonly LibretroApi api;
|
||||
|
||||
// TODO: codepath just for introspection (lighter weight; no speex, no controls, etc.)
|
||||
public LibretroCore(CoreComm nextComm, IGameInfo game, string corePath)
|
||||
{
|
||||
ServiceProvider = new BasicServiceProvider(this);
|
||||
_SyncSettings = new SyncSettings();
|
||||
CoreComm = nextComm;
|
||||
|
||||
var dllPath = Path.Combine(CoreComm.CoreFileProvider.DllPath(), OSTailoredCode.IsUnixHost ? "LibretroBridge.so" : "LibretroBridge.dll");
|
||||
api = new LibretroApi(dllPath, corePath);
|
||||
|
||||
if (api.comm->env.retro_api_version != 1)
|
||||
throw new InvalidOperationException("Unsupported Libretro API version (or major error in interop)");
|
||||
|
||||
// SO: I think I need these paths set before I call retro_set_environment
|
||||
// and I need retro_set_environment set so I can find out if the core supports no-game
|
||||
// therefore, I need a complete environment (including pathing) before I can complete my introspection of the core.
|
||||
// Sucky, but that's life.
|
||||
// I don't even know for sure what paths I should use until... (what?)
|
||||
|
||||
|
||||
// not sure about each of these.. but we may be doing things different than retroarch.
|
||||
// I wish I could initialize these with placeholders during a separate introspection codepath..
|
||||
string SystemDirectory = CoreComm.CoreFileProvider.GetRetroSystemPath(game);
|
||||
string SaveDirectory = CoreComm.CoreFileProvider.GetRetroSaveRAMDirectory(game);
|
||||
string CoreDirectory = Path.GetDirectoryName(corePath);
|
||||
string CoreAssetsDirectory = Path.GetDirectoryName(corePath);
|
||||
|
||||
api.CopyAscii(LibretroApi.BufId.SystemDirectory, SystemDirectory);
|
||||
api.CopyAscii(LibretroApi.BufId.SaveDirectory, SaveDirectory);
|
||||
api.CopyAscii(LibretroApi.BufId.CoreDirectory, CoreDirectory);
|
||||
api.CopyAscii(LibretroApi.BufId.CoreAssetsDirectory, CoreAssetsDirectory);
|
||||
|
||||
api.CMD_SetEnvironment();
|
||||
|
||||
//TODO: IT'S A BOWL OF SPAGHETTI! I KNOW, IM GOING TO FIX IT
|
||||
api.core = this;
|
||||
|
||||
Description = api.CalculateDescription();
|
||||
|
||||
ControllerDefinition = CreateControllerDefinition(_SyncSettings);
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
|
||||
//TODO
|
||||
//api.CMD_unload_cartridge();
|
||||
//api.CMD_term();
|
||||
|
||||
resampler?.Dispose();
|
||||
|
||||
api.Dispose();
|
||||
|
||||
if(vidBufferHandle.IsAllocated)
|
||||
vidBufferHandle.Free();
|
||||
}
|
||||
|
||||
public CoreComm CoreComm { get; }
|
||||
|
||||
public RetroDescription Description { get; }
|
||||
|
||||
public bool LoadData(byte[] data, string id)
|
||||
{
|
||||
bool ret = api.CMD_LoadData(data, id);
|
||||
LoadHandler();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool LoadPath(string path)
|
||||
{
|
||||
bool ret = api.CMD_LoadPath(path);
|
||||
LoadHandler();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool LoadNoGame()
|
||||
{
|
||||
bool ret = api.CMD_LoadNoGame();
|
||||
LoadHandler();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void LoadHandler()
|
||||
{
|
||||
//this stuff can only happen after the game is loaded
|
||||
|
||||
//allocate a video buffer which will definitely be large enough
|
||||
SetVideoBuffer((int)api.comm->env.retro_system_av_info.geometry.base_width, (int)api.comm->env.retro_system_av_info.geometry.base_height);
|
||||
vidBuffer = new int[api.comm->env.retro_system_av_info.geometry.max_width * api.comm->env.retro_system_av_info.geometry.max_height];
|
||||
vidBufferHandle = GCHandle.Alloc(vidBuffer, GCHandleType.Pinned);
|
||||
api.comm->env.fb_bufptr = (int*)vidBufferHandle.AddrOfPinnedObject().ToPointer();
|
||||
//TODO: latch DAR? we may want to change it synchronously, or something
|
||||
|
||||
// TODO: more precise
|
||||
VsyncNumerator = (int)(10000000 * api.comm->env.retro_system_av_info.timing.fps);
|
||||
VsyncDenominator = 10000000;
|
||||
|
||||
SetupResampler(api.comm->env.retro_system_av_info.timing.fps, api.comm->env.retro_system_av_info.timing.sample_rate);
|
||||
(ServiceProvider as BasicServiceProvider).Register<ISoundProvider>(resampler);
|
||||
|
||||
InitMemoryDomains(); // im going to assume this should happen when a game is loaded
|
||||
}
|
||||
|
||||
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||
|
||||
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
return new Dictionary<string, RegisterValue>();
|
||||
}
|
||||
|
||||
public IInputCallbackSystem InputCallbacks => _inputCallbacks;
|
||||
private readonly InputCallbackSystem _inputCallbacks = new InputCallbackSystem();
|
||||
|
||||
public ITraceable Tracer { get; private set; }
|
||||
public IMemoryCallbackSystem MemoryCallbacks { get; private set; }
|
||||
|
||||
public bool CanStep(StepType type) => false;
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public void Step(StepType type) => throw new NotImplementedException();
|
||||
[FeatureNotImplemented]
|
||||
public void SetCpuRegister(string register, int value) => throw new NotImplementedException();
|
||||
[FeatureNotImplemented]
|
||||
public long TotalExecutedCycles => throw new NotImplementedException();
|
||||
|
||||
private IController _controller;
|
||||
|
||||
public bool FrameAdvance(IController controller, bool render, bool rendersound)
|
||||
{
|
||||
_controller = controller;
|
||||
api.CMD_Run();
|
||||
timeFrameCounter++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private GCHandle vidBufferHandle;
|
||||
private int[] vidBuffer;
|
||||
private int vidWidth = -1, vidHeight = -1;
|
||||
|
||||
private void SetVideoBuffer(int width, int height)
|
||||
{
|
||||
//actually, we've already allocated a buffer with the given maximum size
|
||||
if (vidWidth == width && vidHeight == height) return;
|
||||
vidWidth = width;
|
||||
vidHeight = height;
|
||||
}
|
||||
|
||||
//video provider
|
||||
int IVideoProvider.BackgroundColor => 0;
|
||||
int[] IVideoProvider.GetVideoBuffer() { return vidBuffer; }
|
||||
|
||||
public int VirtualWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
var dar = api.AVInfo.geometry.aspect_ratio;
|
||||
if(dar<=0)
|
||||
return vidWidth;
|
||||
if (dar > 1.0f)
|
||||
return (int)(vidHeight * dar);
|
||||
return vidWidth;
|
||||
}
|
||||
}
|
||||
public int VirtualHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
var dar = api.AVInfo.geometry.aspect_ratio;
|
||||
if(dar<=0)
|
||||
return vidHeight;
|
||||
if (dar < 1.0f)
|
||||
return (int)(vidWidth / dar);
|
||||
return vidHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public void SIG_VideoUpdate()
|
||||
{
|
||||
SetVideoBuffer(api.comm->env.fb_width, api.comm->env.fb_height);
|
||||
}
|
||||
|
||||
int IVideoProvider.BufferWidth => vidWidth;
|
||||
int IVideoProvider.BufferHeight => vidHeight;
|
||||
|
||||
public int VsyncNumerator { get; private set; }
|
||||
public int VsyncDenominator { get; private set; }
|
||||
|
||||
private SpeexResampler resampler;
|
||||
|
||||
private short[] sampbuff = new short[0];
|
||||
|
||||
// debug
|
||||
private int nsamprecv = 0;
|
||||
|
||||
private void SetupResampler(double fps, double sps)
|
||||
{
|
||||
Console.WriteLine("FPS {0} SPS {1}", fps, sps);
|
||||
|
||||
// todo: more precise?
|
||||
uint spsnum = (uint)sps * 1000;
|
||||
uint spsden = 1000U;
|
||||
|
||||
resampler = new SpeexResampler(SpeexResampler.Quality.QUALITY_DESKTOP, 44100 * spsden, spsnum, (uint)sps, 44100, null, null);
|
||||
}
|
||||
|
||||
//TODO: handle these in c++ (queue there and blast after frameadvance to c#)
|
||||
public void retro_audio_sample(short left, short right)
|
||||
{
|
||||
resampler.EnqueueSample(left, right);
|
||||
nsamprecv++;
|
||||
}
|
||||
|
||||
public void retro_audio_sample_batch(void* data, int frames)
|
||||
{
|
||||
if (sampbuff.Length < frames * 2)
|
||||
sampbuff = new short[frames * 2];
|
||||
Marshal.Copy(new IntPtr(data), sampbuff, 0, (int)(frames * 2));
|
||||
resampler.EnqueueSamples(sampbuff, (int)frames);
|
||||
nsamprecv += (int)frames;
|
||||
}
|
||||
|
||||
public static ControllerDefinition CreateControllerDefinition(SyncSettings syncSettings)
|
||||
{
|
||||
ControllerDefinition definition = new("LibRetro Controls"/*for compatibility*/);
|
||||
|
||||
foreach(var item in new[] {
|
||||
"P1 {0} Up", "P1 {0} Down", "P1 {0} Left", "P1 {0} Right", "P1 {0} Select", "P1 {0} Start", "P1 {0} Y", "P1 {0} B", "P1 {0} X", "P1 {0} A", "P1 {0} L", "P1 {0} R",
|
||||
"P2 {0} Up", "P2 {0} Down", "P2 {0} Left", "P2 {0} Right", "P2 {0} Select", "P2 {0} Start", "P2 {0} Y", "P2 {0} B", "P2 {0} X", "P2 {0} A", "P2 {0} L", "P2 {0} R",
|
||||
})
|
||||
definition.BoolButtons.Add(string.Format(item,"RetroPad"));
|
||||
|
||||
definition.BoolButtons.Add("Pointer Pressed"); //TODO: this isnt showing up in the binding panel. I don't want to find out why.
|
||||
definition.AddXYPair("Pointer {0}", AxisPairOrientation.RightAndUp, (-32767).RangeTo(32767), 0);
|
||||
|
||||
foreach (var key in new[]{
|
||||
"Key Backspace", "Key Tab", "Key Clear", "Key Return", "Key Pause", "Key Escape",
|
||||
"Key Space", "Key Exclaim", "Key QuoteDbl", "Key Hash", "Key Dollar", "Key Ampersand", "Key Quote", "Key LeftParen", "Key RightParen", "Key Asterisk", "Key Plus", "Key Comma", "Key Minus", "Key Period", "Key Slash",
|
||||
"Key 0", "Key 1", "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9",
|
||||
"Key Colon", "Key Semicolon", "Key Less", "Key Equals", "Key Greater", "Key Question", "Key At", "Key LeftBracket", "Key Backslash", "Key RightBracket", "Key Caret", "Key Underscore", "Key Backquote",
|
||||
"Key A", "Key B", "Key C", "Key D", "Key E", "Key F", "Key G", "Key H", "Key I", "Key J", "Key K", "Key L", "Key M", "Key N", "Key O", "Key P", "Key Q", "Key R", "Key S", "Key T", "Key U", "Key V", "Key W", "Key X", "Key Y", "Key Z",
|
||||
"Key Delete",
|
||||
"Key KP0", "Key KP1", "Key KP2", "Key KP3", "Key KP4", "Key KP5", "Key KP6", "Key KP7", "Key KP8", "Key KP9",
|
||||
"Key KP_Period", "Key KP_Divide", "Key KP_Multiply", "Key KP_Minus", "Key KP_Plus", "Key KP_Enter", "Key KP_Equals",
|
||||
"Key Up", "Key Down", "Key Right", "Key Left", "Key Insert", "Key Home", "Key End", "Key PageUp", "Key PageDown",
|
||||
"Key F1", "Key F2", "Key F3", "Key F4", "Key F5", "Key F6", "Key F7", "Key F8", "Key F9", "Key F10", "Key F11", "Key F12", "Key F13", "Key F14", "Key F15",
|
||||
"Key NumLock", "Key CapsLock", "Key ScrollLock", "Key RShift", "Key LShift", "Key RCtrl", "Key LCtrl", "Key RAlt", "Key LAlt", "Key RMeta", "Key LMeta", "Key LSuper", "Key RSuper", "Key Mode", "Key Compose",
|
||||
"Key Help", "Key Print", "Key SysReq", "Key Break", "Key Menu", "Key Power", "Key Euro", "Key Undo"
|
||||
})
|
||||
{
|
||||
definition.BoolButtons.Add(key);
|
||||
definition.CategoryLabels[key] = "RetroKeyboard";
|
||||
}
|
||||
|
||||
return definition.MakeImmutable();
|
||||
}
|
||||
|
||||
public ControllerDefinition ControllerDefinition { get; }
|
||||
|
||||
private int timeFrameCounter;
|
||||
public int Frame
|
||||
{
|
||||
get => timeFrameCounter;
|
||||
set => timeFrameCounter = value;
|
||||
}
|
||||
public int LagCount { get; set; }
|
||||
public bool IsLagFrame { get; set; }
|
||||
public string SystemId => VSystemID.Raw.Libretro;
|
||||
public bool DeterministicEmulation => false;
|
||||
|
||||
//TODO - terrible things will happen if this changes at runtime
|
||||
|
||||
private byte[] saverambuff = new byte[0];
|
||||
|
||||
public byte[] CloneSaveRam()
|
||||
{
|
||||
var mem = api.QUERY_GetMemory(LibretroApi.RETRO_MEMORY.SAVE_RAM);
|
||||
var buf = new byte[mem.Item2];
|
||||
|
||||
Marshal.Copy(mem.Item1, buf, 0, (int)mem.Item2);
|
||||
return buf;
|
||||
}
|
||||
|
||||
public void StoreSaveRam(byte[] data)
|
||||
{
|
||||
var mem = api.QUERY_GetMemory(LibretroApi.RETRO_MEMORY.SAVE_RAM);
|
||||
|
||||
//bail if the size is 0
|
||||
if (mem.Item2 == 0)
|
||||
return;
|
||||
|
||||
Marshal.Copy(data, 0, mem.Item1, (int)mem.Item2);
|
||||
}
|
||||
|
||||
public bool SaveRamModified
|
||||
{
|
||||
[FeatureNotImplemented]
|
||||
get
|
||||
{
|
||||
//if we don't have saveram, it isnt modified. otherwise, assume it is
|
||||
var mem = api.QUERY_GetMemory(LibretroApi.RETRO_MEMORY.SAVE_RAM);
|
||||
|
||||
//bail if the size is 0
|
||||
if (mem.Item2 == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[FeatureNotImplemented]
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
timeFrameCounter = 0;
|
||||
LagCount = 0;
|
||||
IsLagFrame = false;
|
||||
}
|
||||
|
||||
private byte[] savebuff;
|
||||
|
||||
public void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
api.CMD_UpdateSerializeSize();
|
||||
if (savebuff == null || savebuff.Length != (int)api.comm->env.retro_serialize_size)
|
||||
{
|
||||
savebuff = new byte[api.comm->env.retro_serialize_size];
|
||||
}
|
||||
|
||||
api.CMD_Serialize(savebuff);
|
||||
writer.Write(savebuff.Length);
|
||||
writer.Write(savebuff);
|
||||
// other variables
|
||||
writer.Write(Frame);
|
||||
writer.Write(LagCount);
|
||||
writer.Write(IsLagFrame);
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
int newlen = reader.ReadInt32();
|
||||
if (newlen > savebuff.Length)
|
||||
throw new Exception("Unexpected buffer size");
|
||||
reader.Read(savebuff, 0, newlen);
|
||||
api.CMD_Unserialize(savebuff);
|
||||
// other variables
|
||||
Frame = reader.ReadInt32();
|
||||
LagCount = reader.ReadInt32();
|
||||
IsLagFrame = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
private readonly List<MemoryDomain> _memoryDomains = new();
|
||||
private IMemoryDomains MemoryDomains { get; set; }
|
||||
|
||||
private void InitMemoryDomains()
|
||||
{
|
||||
foreach (LibretroApi.RETRO_MEMORY m in Enum.GetValues(typeof(LibretroApi.RETRO_MEMORY)))
|
||||
{
|
||||
var mem = api.QUERY_GetMemory(m);
|
||||
if (mem.Item1 != IntPtr.Zero && mem.Item2 > 0)
|
||||
{
|
||||
_memoryDomains.Add(new MemoryDomainIntPtr(Enum.GetName(m.GetType(), m), MemoryDomain.Endian.Little, mem.Item1, (long)mem.Item2, true, 1));
|
||||
}
|
||||
}
|
||||
|
||||
MemoryDomains = new MemoryDomainList(_memoryDomains);
|
||||
(ServiceProvider as BasicServiceProvider).Register(MemoryDomains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
|
||||
partial class LibretroCore
|
||||
{
|
||||
private Settings _Settings = new Settings();
|
||||
private SyncSettings _SyncSettings;
|
||||
|
||||
public class SyncSettings
|
||||
{
|
||||
public SyncSettings Clone()
|
||||
{
|
||||
return JsonConvert.DeserializeObject<SyncSettings>(JsonConvert.SerializeObject(this));
|
||||
}
|
||||
|
||||
public SyncSettings()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Settings
|
||||
{
|
||||
public void Validate()
|
||||
{
|
||||
}
|
||||
|
||||
public Settings()
|
||||
{
|
||||
SettingsUtil.SetDefaultValues(this);
|
||||
}
|
||||
|
||||
public Settings Clone()
|
||||
{
|
||||
return (Settings)MemberwiseClone();
|
||||
}
|
||||
}
|
||||
|
||||
public Settings GetSettings()
|
||||
{
|
||||
return _Settings.Clone();
|
||||
}
|
||||
|
||||
public SyncSettings GetSyncSettings()
|
||||
{
|
||||
return _SyncSettings.Clone();
|
||||
}
|
||||
|
||||
public PutSettingsDirtyBits PutSettings(Settings o)
|
||||
{
|
||||
_Settings.Validate();
|
||||
_Settings = o;
|
||||
|
||||
//TODO - store settings into core? or we can just keep doing it before frameadvance
|
||||
|
||||
return PutSettingsDirtyBits.None;
|
||||
}
|
||||
|
||||
public PutSettingsDirtyBits PutSyncSettings(SyncSettings o)
|
||||
{
|
||||
bool reboot = false;
|
||||
|
||||
//we could do it this way roughly if we need to
|
||||
//if(JsonConvert.SerializeObject(o.FIOConfig) != JsonConvert.SerializeObject(_SyncSettings.FIOConfig)
|
||||
|
||||
_SyncSettings = o;
|
||||
|
||||
return reboot ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,219 +0,0 @@
|
|||
namespace BizHawk.Emulation.Cores.Libretro
|
||||
{
|
||||
partial class LibretroCore
|
||||
{
|
||||
//meanings (they are kind of hazy, but once we're done implementing this it will be completely defined by example)
|
||||
//port = console physical port?
|
||||
//device = logical device type
|
||||
//index = sub device index? (multitap?)
|
||||
//id = button id (or key id)
|
||||
public short CB_InputState(uint port, uint device, uint index, uint id)
|
||||
{
|
||||
//helpful debugging
|
||||
//Console.WriteLine("{0} {1} {2} {3}", port, device, index, id);
|
||||
|
||||
switch ((LibretroApi.RETRO_DEVICE)device)
|
||||
{
|
||||
case LibretroApi.RETRO_DEVICE.POINTER:
|
||||
{
|
||||
switch ((LibretroApi.RETRO_DEVICE_ID_POINTER)id)
|
||||
{
|
||||
case LibretroApi.RETRO_DEVICE_ID_POINTER.X: return (short)_controller.AxisValue("Pointer X");
|
||||
case LibretroApi.RETRO_DEVICE_ID_POINTER.Y: return (short)_controller.AxisValue("Pointer Y");
|
||||
case LibretroApi.RETRO_DEVICE_ID_POINTER.PRESSED: return (short)(_controller.IsPressed("Pointer Pressed") ? 1 : 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case LibretroApi.RETRO_DEVICE.KEYBOARD:
|
||||
{
|
||||
string button = "";
|
||||
switch ((LibretroApi.RETRO_KEY)id)
|
||||
{
|
||||
case LibretroApi.RETRO_KEY.BACKSPACE: button = "Backspace"; break;
|
||||
case LibretroApi.RETRO_KEY.TAB: button = "Tab"; break;
|
||||
case LibretroApi.RETRO_KEY.CLEAR: button = "Clear"; break;
|
||||
case LibretroApi.RETRO_KEY.RETURN: button = "Return"; break;
|
||||
case LibretroApi.RETRO_KEY.PAUSE: button = "Pause"; break;
|
||||
case LibretroApi.RETRO_KEY.ESCAPE: button = "Escape"; break;
|
||||
case LibretroApi.RETRO_KEY.SPACE: button = "Space"; break;
|
||||
case LibretroApi.RETRO_KEY.EXCLAIM: button = "Exclaim"; break;
|
||||
case LibretroApi.RETRO_KEY.QUOTEDBL: button = "QuoteDbl"; break;
|
||||
case LibretroApi.RETRO_KEY.HASH: button = "Hash"; break;
|
||||
case LibretroApi.RETRO_KEY.DOLLAR: button = "Dollar"; break;
|
||||
case LibretroApi.RETRO_KEY.AMPERSAND: button = "Ampersand"; break;
|
||||
case LibretroApi.RETRO_KEY.QUOTE: button = "Quote"; break;
|
||||
case LibretroApi.RETRO_KEY.LEFTPAREN: button = "LeftParen"; break;
|
||||
case LibretroApi.RETRO_KEY.RIGHTPAREN: button = "RightParen"; break;
|
||||
case LibretroApi.RETRO_KEY.ASTERISK: button = "Asterisk"; break;
|
||||
case LibretroApi.RETRO_KEY.PLUS: button = "Plus"; break;
|
||||
case LibretroApi.RETRO_KEY.COMMA: button = "Comma"; break;
|
||||
case LibretroApi.RETRO_KEY.MINUS: button = "Minus"; break;
|
||||
case LibretroApi.RETRO_KEY.PERIOD: button = "Period"; break;
|
||||
case LibretroApi.RETRO_KEY.SLASH: button = "Slash"; break;
|
||||
case LibretroApi.RETRO_KEY._0: button = "0"; break;
|
||||
case LibretroApi.RETRO_KEY._1: button = "1"; break;
|
||||
case LibretroApi.RETRO_KEY._2: button = "2"; break;
|
||||
case LibretroApi.RETRO_KEY._3: button = "3"; break;
|
||||
case LibretroApi.RETRO_KEY._4: button = "4"; break;
|
||||
case LibretroApi.RETRO_KEY._5: button = "5"; break;
|
||||
case LibretroApi.RETRO_KEY._6: button = "6"; break;
|
||||
case LibretroApi.RETRO_KEY._7: button = "7"; break;
|
||||
case LibretroApi.RETRO_KEY._8: button = "8"; break;
|
||||
case LibretroApi.RETRO_KEY._9: button = "9"; break;
|
||||
case LibretroApi.RETRO_KEY.COLON: button = "Colon"; break;
|
||||
case LibretroApi.RETRO_KEY.SEMICOLON: button = "Semicolon"; break;
|
||||
case LibretroApi.RETRO_KEY.LESS: button = "Less"; break;
|
||||
case LibretroApi.RETRO_KEY.EQUALS: button = "Equals"; break;
|
||||
case LibretroApi.RETRO_KEY.GREATER: button = "Greater"; break;
|
||||
case LibretroApi.RETRO_KEY.QUESTION: button = "Question"; break;
|
||||
case LibretroApi.RETRO_KEY.AT: button = "At"; break;
|
||||
case LibretroApi.RETRO_KEY.LEFTBRACKET: button = "LeftBracket"; break;
|
||||
case LibretroApi.RETRO_KEY.BACKSLASH: button = "Backslash"; break;
|
||||
case LibretroApi.RETRO_KEY.RIGHTBRACKET: button = "RightBracket"; break;
|
||||
case LibretroApi.RETRO_KEY.CARET: button = "Caret"; break;
|
||||
case LibretroApi.RETRO_KEY.UNDERSCORE: button = "Underscore"; break;
|
||||
case LibretroApi.RETRO_KEY.BACKQUOTE: button = "Backquote"; break;
|
||||
case LibretroApi.RETRO_KEY.a: button = "A"; break;
|
||||
case LibretroApi.RETRO_KEY.b: button = "B"; break;
|
||||
case LibretroApi.RETRO_KEY.c: button = "C"; break;
|
||||
case LibretroApi.RETRO_KEY.d: button = "D"; break;
|
||||
case LibretroApi.RETRO_KEY.e: button = "E"; break;
|
||||
case LibretroApi.RETRO_KEY.f: button = "F"; break;
|
||||
case LibretroApi.RETRO_KEY.g: button = "G"; break;
|
||||
case LibretroApi.RETRO_KEY.h: button = "H"; break;
|
||||
case LibretroApi.RETRO_KEY.i: button = "I"; break;
|
||||
case LibretroApi.RETRO_KEY.j: button = "J"; break;
|
||||
case LibretroApi.RETRO_KEY.k: button = "K"; break;
|
||||
case LibretroApi.RETRO_KEY.l: button = "L"; break;
|
||||
case LibretroApi.RETRO_KEY.m: button = "M"; break;
|
||||
case LibretroApi.RETRO_KEY.n: button = "N"; break;
|
||||
case LibretroApi.RETRO_KEY.o: button = "O"; break;
|
||||
case LibretroApi.RETRO_KEY.p: button = "P"; break;
|
||||
case LibretroApi.RETRO_KEY.q: button = "Q"; break;
|
||||
case LibretroApi.RETRO_KEY.r: button = "R"; break;
|
||||
case LibretroApi.RETRO_KEY.s: button = "S"; break;
|
||||
case LibretroApi.RETRO_KEY.t: button = "T"; break;
|
||||
case LibretroApi.RETRO_KEY.u: button = "U"; break;
|
||||
case LibretroApi.RETRO_KEY.v: button = "V"; break;
|
||||
case LibretroApi.RETRO_KEY.w: button = "W"; break;
|
||||
case LibretroApi.RETRO_KEY.x: button = "X"; break;
|
||||
case LibretroApi.RETRO_KEY.y: button = "Y"; break;
|
||||
case LibretroApi.RETRO_KEY.z: button = "Z"; break;
|
||||
case LibretroApi.RETRO_KEY.DELETE: button = "Delete"; break;
|
||||
|
||||
case LibretroApi.RETRO_KEY.KP0: button = "KP0"; break;
|
||||
case LibretroApi.RETRO_KEY.KP1: button = "KP1"; break;
|
||||
case LibretroApi.RETRO_KEY.KP2: button = "KP2"; break;
|
||||
case LibretroApi.RETRO_KEY.KP3: button = "KP3"; break;
|
||||
case LibretroApi.RETRO_KEY.KP4: button = "KP4"; break;
|
||||
case LibretroApi.RETRO_KEY.KP5: button = "KP5"; break;
|
||||
case LibretroApi.RETRO_KEY.KP6: button = "KP6"; break;
|
||||
case LibretroApi.RETRO_KEY.KP7: button = "KP7"; break;
|
||||
case LibretroApi.RETRO_KEY.KP8: button = "KP8"; break;
|
||||
case LibretroApi.RETRO_KEY.KP9: button = "KP9"; break;
|
||||
case LibretroApi.RETRO_KEY.KP_PERIOD: button = "KP_Period"; break;
|
||||
case LibretroApi.RETRO_KEY.KP_DIVIDE: button = "KP_Divide"; break;
|
||||
case LibretroApi.RETRO_KEY.KP_MULTIPLY: button = "KP_Multiply"; break;
|
||||
case LibretroApi.RETRO_KEY.KP_MINUS: button = "KP_Minus"; break;
|
||||
case LibretroApi.RETRO_KEY.KP_PLUS: button = "KP_Plus"; break;
|
||||
case LibretroApi.RETRO_KEY.KP_ENTER: button = "KP_Enter"; break;
|
||||
case LibretroApi.RETRO_KEY.KP_EQUALS: button = "KP_Equals"; break;
|
||||
|
||||
case LibretroApi.RETRO_KEY.UP: button = "Up"; break;
|
||||
case LibretroApi.RETRO_KEY.DOWN: button = "Down"; break;
|
||||
case LibretroApi.RETRO_KEY.RIGHT: button = "Right"; break;
|
||||
case LibretroApi.RETRO_KEY.LEFT: button = "Left"; break;
|
||||
case LibretroApi.RETRO_KEY.INSERT: button = "Insert"; break;
|
||||
case LibretroApi.RETRO_KEY.HOME: button = "Home"; break;
|
||||
case LibretroApi.RETRO_KEY.END: button = "End"; break;
|
||||
case LibretroApi.RETRO_KEY.PAGEUP: button = "PageUp"; break;
|
||||
case LibretroApi.RETRO_KEY.PAGEDOWN: button = "PageDown"; break;
|
||||
|
||||
case LibretroApi.RETRO_KEY.F1: button = "F1"; break;
|
||||
case LibretroApi.RETRO_KEY.F2: button = "F2"; break;
|
||||
case LibretroApi.RETRO_KEY.F3: button = "F3"; break;
|
||||
case LibretroApi.RETRO_KEY.F4: button = "F4"; break;
|
||||
case LibretroApi.RETRO_KEY.F5: button = "F5"; break;
|
||||
case LibretroApi.RETRO_KEY.F6: button = "F6"; break;
|
||||
case LibretroApi.RETRO_KEY.F7: button = "F7"; break;
|
||||
case LibretroApi.RETRO_KEY.F8: button = "F8"; break;
|
||||
case LibretroApi.RETRO_KEY.F9: button = "F9"; break;
|
||||
case LibretroApi.RETRO_KEY.F10: button = "F10"; break;
|
||||
case LibretroApi.RETRO_KEY.F11: button = "F11"; break;
|
||||
case LibretroApi.RETRO_KEY.F12: button = "F12"; break;
|
||||
case LibretroApi.RETRO_KEY.F13: button = "F13"; break;
|
||||
case LibretroApi.RETRO_KEY.F14: button = "F14"; break;
|
||||
case LibretroApi.RETRO_KEY.F15: button = "F15"; break;
|
||||
|
||||
case LibretroApi.RETRO_KEY.NUMLOCK: button = "NumLock"; break;
|
||||
case LibretroApi.RETRO_KEY.CAPSLOCK: button = "CapsLock"; break;
|
||||
case LibretroApi.RETRO_KEY.SCROLLOCK: button = "ScrollLock"; break;
|
||||
case LibretroApi.RETRO_KEY.RSHIFT: button = "RShift"; break;
|
||||
case LibretroApi.RETRO_KEY.LSHIFT: button = "LShift"; break;
|
||||
case LibretroApi.RETRO_KEY.RCTRL: button = "RCtrl"; break;
|
||||
case LibretroApi.RETRO_KEY.LCTRL: button = "LCtrl"; break;
|
||||
case LibretroApi.RETRO_KEY.RALT: button = "RAlt"; break;
|
||||
case LibretroApi.RETRO_KEY.LALT: button = "LAlt"; break;
|
||||
case LibretroApi.RETRO_KEY.RMETA: button = "RMeta"; break;
|
||||
case LibretroApi.RETRO_KEY.LMETA: button = "LMeta"; break;
|
||||
case LibretroApi.RETRO_KEY.LSUPER: button = "LSuper"; break;
|
||||
case LibretroApi.RETRO_KEY.RSUPER: button = "RSuper"; break;
|
||||
case LibretroApi.RETRO_KEY.MODE: button = "Mode"; break;
|
||||
case LibretroApi.RETRO_KEY.COMPOSE: button = "Compose"; break;
|
||||
|
||||
case LibretroApi.RETRO_KEY.HELP: button = "Help"; break;
|
||||
case LibretroApi.RETRO_KEY.PRINT: button = "Print"; break;
|
||||
case LibretroApi.RETRO_KEY.SYSREQ: button = "SysReq"; break;
|
||||
case LibretroApi.RETRO_KEY.BREAK: button = "Break"; break;
|
||||
case LibretroApi.RETRO_KEY.MENU: button = "Menu"; break;
|
||||
case LibretroApi.RETRO_KEY.POWER: button = "Power"; break;
|
||||
case LibretroApi.RETRO_KEY.EURO: button = "Euro"; break;
|
||||
case LibretroApi.RETRO_KEY.UNDO: button = "Undo"; break;
|
||||
}
|
||||
|
||||
return (short)(_controller.IsPressed("Key " + button) ? 1 : 0);
|
||||
}
|
||||
|
||||
case LibretroApi.RETRO_DEVICE.JOYPAD:
|
||||
{
|
||||
//The JOYPAD is sometimes called RetroPad (and we'll call it that in user-facing stuff cos retroarch does)
|
||||
//It is essentially a Super Nintendo controller, but with additional L2/R2/L3/R3 buttons, similar to a PS1 DualShock.
|
||||
|
||||
string button = "";
|
||||
switch ((LibretroApi.RETRO_DEVICE_ID_JOYPAD)id)
|
||||
{
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.A: button = "A"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.B: button = "B"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.X: button = "X"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.Y: button = "Y"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.UP: button = "Up"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.DOWN: button = "Down"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.LEFT: button = "Left"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.RIGHT: button = "Right"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.L: button = "L"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.R: button = "R"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.SELECT: button = "Select"; break;
|
||||
case LibretroApi.RETRO_DEVICE_ID_JOYPAD.START: button = "Start"; break;
|
||||
}
|
||||
|
||||
return (short)(GetButton(port+1, "RetroPad", button) ? 1 : 0);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetButton(uint pnum, string type, string button)
|
||||
{
|
||||
string key = $"P{pnum} {type} {button}";
|
||||
bool b = _controller.IsPressed(key);
|
||||
if (b == true)
|
||||
{
|
||||
return true; //debugging placeholder
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue