Add linux version of LibretroBridge (#2895)

* Add linux version of LibretroBridge

* correct make variables or whatever

* Revert Hawk-side changes (to avoid merge conflict with master)

Co-authored-by: YoshiRulz <OSSYoshiRulz@gmail.com>
This commit is contained in:
Moritz Bender 2021-09-18 10:08:26 +02:00 committed by GitHub
parent 14dd3c1695
commit 158451a68e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1428 additions and 547 deletions

View File

@ -0,0 +1,168 @@
#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

View File

@ -0,0 +1,28 @@
/*
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

View File

@ -0,0 +1,128 @@
#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

View File

@ -19,12 +19,12 @@
#include <string>
#define bool unsigned char
#include "libretro.h"
#include "../libretro.h"
#undef bool
extern "C" uint64_t cpu_features_get();
#include "libco/libco.h"
#include "../libco/libco.h"
//can't use retroarch's dynamic.h, it's too full of weird stuff. don't need it anyway
@ -209,11 +209,11 @@ struct CommStruct
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)); \
}
//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);
@ -294,8 +294,8 @@ void retro_log_printf(enum retro_log_level level, const char *fmt, ...)
u8bool retro_environment(unsigned cmd, void *data)
{
switch (cmd)
{
switch (cmd)
{
case RETRO_ENVIRONMENT_SET_ROTATION:
comm.env.rotation_ccw = (int)*(const unsigned*)data * 90;
return true;
@ -370,19 +370,19 @@ u8bool retro_environment(unsigned cmd, void *data)
{
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
else
comm.variables[i] = comment.substr(ofs,pipe-ofs);
}
return true;
}
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
*(u8bool*)data = comm.variables_dirty;
break;
@ -405,7 +405,7 @@ u8bool retro_environment(unsigned cmd, void *data)
//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;
@ -428,7 +428,7 @@ u8bool retro_environment(unsigned cmd, void *data)
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;
@ -450,113 +450,113 @@ u8bool retro_environment(unsigned cmd, void *data)
*((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:
{
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;
}
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)
if (!data)
return;
comm.env.fb_width = (s32)width;
@ -571,9 +571,9 @@ void retro_video_refresh(const void *data, unsigned width, unsigned height, size
////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
//// 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)
//{
@ -587,58 +587,58 @@ void retro_video_refresh(const void *data, unsigned width, unsigned height, size
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;
}
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()
{
}
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.
@ -646,7 +646,7 @@ s16 retro_input_state(unsigned port, unsigned device, unsigned index, unsigned i
comm.device = device;
comm.index = index;
comm.id = id;
BREAK(eMessage::SIG_InputState);
return (s16)comm.value;
@ -659,28 +659,28 @@ s16 retro_input_state(unsigned port, unsigned device, unsigned index, unsigned i
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);
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);
@ -689,12 +689,12 @@ static void LoadHandler(eMessage msg)
//(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_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
//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();
@ -752,7 +752,7 @@ void cmd_Unserialize()
//TODO maybe not sensible though
//void(*retro_unload_game)(void);
void cmd_SetEnvironment()
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);
@ -795,7 +795,7 @@ 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_control = co_active();
co_emu = co_create(128*1024 * sizeof(void*), new_emuthread);
//grab all the function pointers we need.
@ -871,4 +871,4 @@ extern "C" __declspec(dllexport) void __cdecl SetVariable(const char* key, const
comm.variables[i] = val;
comm.variables_dirty = true;
}
}
}

View File

@ -11,12 +11,12 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="features_cpu.c">
<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">
<ClCompile Include="..\libco\amd64.c">
<EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotSet</EnableEnhancedInstructionSet>
<EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotSet</EnableEnhancedInstructionSet>
</ClCompile>
@ -26,17 +26,15 @@
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="libco\libco.h" />
<ClInclude Include="libretro.h" />
</ItemGroup>
<ItemGroup>
<MASM Include="libco\coswap.asm" />
<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>8.1</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
@ -124,4 +122,4 @@
<UserProperties />
</VisualStudio>
</ProjectExtensions>
</Project>
</Project>

View File

@ -1,168 +0,0 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2016 - Daniel De Matteis
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __DYNAMIC_H
#define __DYNAMIC_H
#include <boolean.h>
#include <retro_common_api.h>
#include <libretro.h>
#include "core_type.h"
RETRO_BEGIN_DECLS
/**
* libretro_get_environment_info:
* @func : Function pointer for get_environment_info.
* @load_no_content : If true, core should be able to auto-start
* without any content loaded.
*
* Sets environment callback in order to get statically known
* information from it.
*
* Fetched via environment callbacks instead of
* retro_get_system_info(), as this info is part of extensions.
*
* Should only be called once right after core load to
* avoid overwriting the "real" environ callback.
*
* For statically linked cores, pass retro_set_environment as argument.
*/
void libretro_get_environment_info(void(*)(retro_environment_t),
bool *load_no_content);
/**
* libretro_get_system_info:
* @path : Path to libretro library.
* @info : System info information.
* @load_no_content : If true, core should be able to auto-start
* without any content loaded.
*
* Gets system info from an arbitrary lib.
* The struct returned must be freed as strings are allocated dynamically.
*
* Returns: true (1) if successful, otherwise false (0).
**/
bool libretro_get_system_info(const char *path,
struct retro_system_info *info, bool *load_no_content);
/**
* libretro_free_system_info:
* @info : Pointer to system info information.
*
* Frees system information.
**/
void libretro_free_system_info(struct retro_system_info *info);
/**
* libretro_get_current_core_pathname:
* @name : Sanitized name of libretro core.
* @size : Size of @name
*
* Transforms a library id to a name suitable as a pathname.
**/
void libretro_get_current_core_pathname(char *name, size_t size);
const struct retro_subsystem_info *libretro_find_subsystem_info(
const struct retro_subsystem_info *info,
unsigned num_info, const char *ident);
/**
* libretro_find_controller_description:
* @info : Pointer to controller info handle.
* @id : Identifier of controller to search
* for.
*
* Search for a controller of type @id in @info.
*
* Returns: controller description of found controller on success,
* otherwise NULL.
**/
const struct retro_controller_description *
libretro_find_controller_description(
const struct retro_controller_info *info, unsigned id);
/**
* rarch_environment_cb:
* @cmd : Identifier of command.
* @data : Pointer to data.
*
* Environment callback function implementation.
*
* Returns: true (1) if environment callback command could
* be performed, otherwise false (0).
**/
bool rarch_environment_cb(unsigned cmd, void *data);
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);
bool(*retro_serialize)(void*, size_t);
bool(*retro_unserialize)(const void*, size_t);
void(*retro_cheat_reset)(void);
void(*retro_cheat_set)(unsigned, bool, const char*);
bool(*retro_load_game)(const struct retro_game_info*);
bool(*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);
};
/**
* init_libretro_sym:
* @type : Type of core to be loaded.
* If CORE_TYPE_DUMMY, will
* load dummy symbols.
*
* Initializes libretro symbols and
* setups environment callback functions. Returns true on success,
* or false if symbols could not be loaded.
**/
bool init_libretro_sym(enum rarch_core_type type,
struct retro_core_t *core);
/**
* uninit_libretro_sym:
*
* Frees libretro core.
*
* Frees all core options,
* associated state, and
* unbind all libretro callback symbols.
**/
void uninit_libretro_sym(struct retro_core_t *core);
RETRO_END_DECLS
#endif

View File

@ -1,82 +0,0 @@
/*
libco.amd64 (2016-09-14)
author: byuu
license: public domain
*/
#define LIBCO_C
#include "libco.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
static long long co_active_buffer[64];
static cothread_t co_active_handle = 0;
static void* smalloc(size_t size)
{
char* ret = malloc(size + 16);
if (ret)
{
*(size_t*)ret = size;
return ret + 16;
}
return NULL;
}
static void sfree(void* ptr)
{
char* original = (char*)ptr - 16;
size_t size = *(size_t*)original + 16;
memset(original, 0, size);
free(original);
}
extern void co_swap(cothread_t, cothread_t);
static void crash() {
assert(0); /* called only if cothread_t entrypoint returns */
}
void co_clean() {
memset(co_active_buffer, 0, sizeof(co_active_buffer));
}
cothread_t co_active() {
if (!co_active_handle) co_active_handle = &co_active_buffer;
return co_active_handle;
}
cothread_t co_create(unsigned int size, void(*entrypoint)(void)) {
cothread_t handle;
if (!co_active_handle) co_active_handle = &co_active_buffer;
size += 512; /* allocate additional space for storage */
size &= ~15; /* align stack to 16-byte boundary */
if (handle = (cothread_t)smalloc(size)) {
long long *p = (long long*)((char*)handle + size); /* 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;
}
void co_delete(cothread_t handle) {
sfree(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);
}
#ifdef __cplusplus
}
#endif

View File

@ -1,57 +0,0 @@
_TEXT SEGMENT
PUBLIC co_swap
co_swap PROC
mov [rdx],rsp
mov rsp,[rcx]
pop rax
mov [rdx+ 8],rbp
mov [rdx+16],rsi
mov [rdx+24],rdi
mov [rdx+32],rbx
mov [rdx+40],r12
mov [rdx+48],r13
mov [rdx+56],r14
mov [rdx+64],r15
movaps [rdx+ 80],xmm6
movaps [rdx+ 96],xmm7
movaps [rdx+112],xmm8
add rdx,112
movaps [rdx+ 16],xmm9
movaps [rdx+ 32],xmm10
movaps [rdx+ 48],xmm11
movaps [rdx+ 64],xmm12
movaps [rdx+ 80],xmm13
movaps [rdx+ 96],xmm14
movaps [rdx+112],xmm15
mov rbp,[rcx+ 8]
mov rsi,[rcx+16]
mov rdi,[rcx+24]
mov rbx,[rcx+32]
mov r12,[rcx+40]
mov r13,[rcx+48]
mov r14,[rcx+56]
mov r15,[rcx+64]
movaps xmm6, [rcx+ 80]
movaps xmm7, [rcx+ 96]
movaps xmm8, [rcx+112]
add rcx,112
movaps xmm9, [rcx+ 16]
movaps xmm10,[rcx+ 32]
movaps xmm11,[rcx+ 48]
movaps xmm12,[rcx+ 64]
movaps xmm13,[rcx+ 80]
movaps xmm14,[rcx+ 96]
movaps xmm15,[rcx+112]
jmp rax
co_swap ENDP
_TEXT ENDS
END

View File

@ -1,38 +0,0 @@
/*
libco
version: 0.16 (2010-12-24)
license: public domain
*/
#ifndef LIBCO_H
#define LIBCO_H
#ifdef LIBCO_C
#ifdef LIBCO_MP
#define thread_local __thread
#else
#define thread_local
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef void* cothread_t;
typedef void (*coentry_t)(void);
void* co_getstack(cothread_t);
cothread_t co_active();
cothread_t co_create_withstack(void* stack, int stacksize, coentry_t);
cothread_t co_create(unsigned int, coentry_t);
void co_delete(cothread_t);
void co_switch(cothread_t);
cothread_t co_primary();
#ifdef __cplusplus
}
#endif
/* ifndef LIBCO_H */
#endif

View File

@ -0,0 +1,873 @@
//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;
}
}

View File

@ -0,0 +1,31 @@
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)