213 lines
5.6 KiB
C
213 lines
5.6 KiB
C
#include <windows.h>
|
|
|
|
#include <stdatomic.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "bemanitools/ddrio.h"
|
|
#include "bemanitools/input.h"
|
|
|
|
#include "imports/avs.h"
|
|
#include "imports/SMX.h"
|
|
|
|
#include "util/defs.h"
|
|
#include "util/log.h"
|
|
|
|
struct ddr_io_smx_pad_map {
|
|
int pad_no;
|
|
uint16_t smx_bit;
|
|
uint32_t ddr_bit;
|
|
};
|
|
|
|
struct ddr_io_smx_light_map {
|
|
uint32_t extio_bit;
|
|
int smx_light_offset;
|
|
unsigned char r;
|
|
unsigned char g;
|
|
unsigned char b;
|
|
};
|
|
|
|
static void ddr_io_smx_callback(
|
|
int pad_no,
|
|
enum SMXUpdateCallbackReason reason,
|
|
void *ctx);
|
|
|
|
static const struct ddr_io_smx_pad_map ddr_io_smx_pad_map[] = {
|
|
{ 0, 1 << 1, 1 << DDR_P1_UP },
|
|
{ 0, 1 << 3, 1 << DDR_P1_LEFT },
|
|
{ 0, 1 << 5, 1 << DDR_P1_RIGHT },
|
|
{ 0, 1 << 7, 1 << DDR_P1_DOWN },
|
|
|
|
{ 1, 1 << 1, 1 << DDR_P2_UP },
|
|
{ 1, 1 << 3, 1 << DDR_P2_LEFT },
|
|
{ 1, 1 << 5, 1 << DDR_P2_RIGHT },
|
|
{ 1, 1 << 7, 1 << DDR_P2_DOWN },
|
|
};
|
|
|
|
static const struct ddr_io_smx_light_map ddr_io_smx_light_map[] = {
|
|
/* Light L/R blue and U/D red to match DDR pad color scheme */
|
|
|
|
{ 1 << LIGHT_P1_UP, 48 * 1, 0xFF, 0x00, 0x00 },
|
|
{ 1 << LIGHT_P1_LEFT, 48 * 3, 0x00, 0x00, 0xFF },
|
|
{ 1 << LIGHT_P1_RIGHT, 48 * 5, 0x00, 0x00, 0xFF },
|
|
{ 1 << LIGHT_P1_DOWN, 48 * 7, 0xFF, 0x00, 0x00 },
|
|
|
|
{ 1 << LIGHT_P2_UP, 48 * 10, 0xFF, 0x00, 0x00 },
|
|
{ 1 << LIGHT_P2_LEFT, 48 * 12, 0x00, 0x00, 0xFF },
|
|
{ 1 << LIGHT_P2_RIGHT, 48 * 14, 0x00, 0x00, 0xFF },
|
|
{ 1 << LIGHT_P2_DOWN, 48 * 16, 0xFF, 0x00, 0x00 },
|
|
};
|
|
|
|
static _Atomic uint32_t ddr_io_smx_pad_state[2];
|
|
static CRITICAL_SECTION ddr_io_smx_lights_lock;
|
|
static uint8_t ddr_io_smx_lights_counter;
|
|
static char ddr_io_smx_lights[864];
|
|
|
|
void ddr_io_set_loggers(
|
|
log_formatter_t misc,
|
|
log_formatter_t info,
|
|
log_formatter_t warning,
|
|
log_formatter_t fatal)
|
|
{
|
|
log_to_external(misc, info, warning, fatal);
|
|
input_set_loggers(misc, info, warning, fatal);
|
|
|
|
/* We would need a log server thread to accept log messages from SMX, since
|
|
it uses raw Win32 threads and not AVS threads (only AVS threads are
|
|
permitted to use the AVS logging API). So it's not really worth it. */
|
|
}
|
|
|
|
bool ddr_io_init(
|
|
thread_create_t thread_create,
|
|
thread_join_t thread_join,
|
|
thread_destroy_t thread_destroy)
|
|
{
|
|
/* Use geninput for menu/operator btns */
|
|
|
|
input_init(thread_create, thread_join, thread_destroy);
|
|
mapper_config_load("ddr");
|
|
|
|
/* Start up SMX API */
|
|
|
|
log_info("Starting SMX.DLL");
|
|
log_info("SMX.DLL version: %s", "[predates SMX_Version()]");
|
|
SMX_Start(ddr_io_smx_callback, NULL);
|
|
log_info("Started SMX.DLL");
|
|
|
|
InitializeCriticalSection(&ddr_io_smx_lights_lock);
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t ddr_io_read_pad(void)
|
|
{
|
|
/* SMX pads require a constant stream of lighting updates or they will
|
|
quickly revert to autonomous lighting control. Here is a hacky way of
|
|
feeding that light data to the pad. Hopefully it won't starve anything
|
|
important.
|
|
|
|
Changing the light state causes the counter to reset, and the counter
|
|
itself ought to roll over at a rate of about 4 Hz. */
|
|
|
|
EnterCriticalSection(&ddr_io_smx_lights_lock);
|
|
|
|
if (ddr_io_smx_lights_counter == 0) {
|
|
SMX_SetLights(ddr_io_smx_lights);
|
|
}
|
|
|
|
ddr_io_smx_lights_counter++;
|
|
|
|
LeaveCriticalSection(&ddr_io_smx_lights_lock);
|
|
|
|
/* Sleep first: input is timestamped immediately AFTER the ioctl returns.
|
|
|
|
Which is the right thing to do, for once. We sleep here because
|
|
the game polls input in a tight loop. Can't complain, at there isn't
|
|
an artificial limit on the poll frequency. */
|
|
|
|
Sleep(1);
|
|
|
|
/* We don't atomically read both pads, but they are separate USB devices
|
|
so they don't update in lockstep anyway. */
|
|
|
|
return mapper_update() |
|
|
atomic_load(&ddr_io_smx_pad_state[0]) |
|
|
atomic_load(&ddr_io_smx_pad_state[1]) ;
|
|
}
|
|
|
|
void ddr_io_set_lights_extio(uint32_t lights)
|
|
{
|
|
const struct ddr_io_smx_light_map *map;
|
|
size_t offset;
|
|
size_t i;
|
|
size_t j;
|
|
|
|
EnterCriticalSection(&ddr_io_smx_lights_lock);
|
|
|
|
ddr_io_smx_lights_counter = 0;
|
|
memset(ddr_io_smx_lights, 0, sizeof(ddr_io_smx_lights));
|
|
|
|
for (i = 0 ; i < lengthof(ddr_io_smx_light_map) ; i++) {
|
|
map = &ddr_io_smx_light_map[i];
|
|
|
|
if (lights & map->extio_bit) {
|
|
offset = map->smx_light_offset;
|
|
|
|
for (j = 0 ; j < 48 ; j += 3) {
|
|
ddr_io_smx_lights[offset + j ] = map->r;
|
|
ddr_io_smx_lights[offset + j + 1] = map->g;
|
|
ddr_io_smx_lights[offset + j + 2] = map->b;
|
|
}
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&ddr_io_smx_lights_lock);
|
|
}
|
|
|
|
void ddr_io_set_lights_p3io(uint32_t lights)
|
|
{
|
|
uint8_t i;
|
|
|
|
for (i = 0x0E; i <= 0x1E; i++) {
|
|
mapper_write_light(i, lights & (1 << i) ? 255 : 0);
|
|
}
|
|
}
|
|
|
|
void ddr_io_fini(void)
|
|
{
|
|
log_info("Stopping SMX.DLL");
|
|
SMX_Stop();
|
|
log_info("Stopped SMX.DLL");
|
|
|
|
DeleteCriticalSection(&ddr_io_smx_lights_lock);
|
|
input_fini();
|
|
}
|
|
|
|
static void ddr_io_smx_callback(
|
|
int pad_no,
|
|
enum SMXUpdateCallbackReason reason,
|
|
void *ctx)
|
|
{
|
|
const struct ddr_io_smx_pad_map *map;
|
|
uint16_t smx_state;
|
|
uint32_t ddr_state;
|
|
size_t i;
|
|
|
|
if (pad_no < 0 || pad_no > 1) {
|
|
return;
|
|
}
|
|
|
|
smx_state = SMX_GetInputState(pad_no);
|
|
ddr_state = 0;
|
|
|
|
for (i = 0 ; i < lengthof(ddr_io_smx_pad_map) ; i++) {
|
|
map = &ddr_io_smx_pad_map[i];
|
|
|
|
if (pad_no == map->pad_no && (smx_state & map->smx_bit) != 0) {
|
|
ddr_state |= map->ddr_bit;
|
|
}
|
|
}
|
|
|
|
atomic_store(&ddr_io_smx_pad_state[pad_no], ddr_state);
|
|
}
|
|
|