diff --git a/src/spice2x/acio/mdxf/mdxf.cpp b/src/spice2x/acio/mdxf/mdxf.cpp index 6871e34..b7085e5 100644 --- a/src/spice2x/acio/mdxf/mdxf.cpp +++ b/src/spice2x/acio/mdxf/mdxf.cpp @@ -1,4 +1,5 @@ #include "mdxf.h" +#include "mdxf_poll.h" #include "avs/game.h" #include "games/ddr/io.h" @@ -6,35 +7,53 @@ #include "rawinput/rawinput.h" #include "util/logging.h" #include "util/utils.h" +#include // constants const size_t STATUS_BUFFER_SIZE = 32; +const size_t STATUS_BUFFER_NUM_ENTRIES = 16; // static stuff static uint8_t HEAD_P1 = 0; static uint8_t HEAD_P2 = 0; +static uint16_t PREV_STATE_P1 = 0; +static uint16_t PREV_STATE_P2 = 0; + +static std::mutex MUTEX_P1; +static std::mutex MUTEX_P2; + +static bool MDXF_IS_ACTIVE = false; + // buffers #pragma pack(push, 1) static struct { - uint8_t STATUS_BUFFER_P1[16][STATUS_BUFFER_SIZE] {}; - uint8_t STATUS_BUFFER_P2[16][STATUS_BUFFER_SIZE] {}; + uint8_t STATUS_BUFFER_P1[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {}; + uint8_t STATUS_BUFFER_P2[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {}; } BUFFERS {}; #pragma pack(pop) static bool STATUS_BUFFER_FREEZE = false; +typedef enum { + ARKMDXP4_POLL = 0, + EXTERNAL_POLL = 1 +} MDXFPollSource; + typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)(); static uint64_t arkGetTickTime64() { - static ARK_GET_TICK_TIME64_T getTickTime64 = - (ARK_GET_TICK_TIME64_T)GetProcAddress(avs::game::DLL_INSTANCE, "arkGetTickTime64"); - if (getTickTime64 == nullptr) { - // this works on 32-bit versions of avs, but not on 64. - // it's better than nothing though. - return timeGetTime(); + static ARK_GET_TICK_TIME64_T getTickTime64 = nullptr; + + if (!getTickTime64) { + HMODULE h = avs::game::DLL_INSTANCE; + if (h) { + getTickTime64 = (ARK_GET_TICK_TIME64_T)GetProcAddress(h, "arkGetTickTime64"); + } } - return getTickTime64(); + // this works on 32-bit versions of avs, but not on 64. + // it's better than nothing though. + return getTickTime64 ? getTickTime64() : timeGetTime(); } /* @@ -43,45 +62,47 @@ static uint64_t arkGetTickTime64() { static uint64_t __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *out, uint8_t index, uint8_t head_in) { - // Default error value (matches original mask behavior) + // Default error value (matches original mask behavior) auto error_ret = static_cast(node - 0x11) & 0xFFFFFFFFFFFFFF00; - + // Dance Dance Revolution if (avs::game::is_model("MDX")) { - // Select player-specific state - uint8_t head; - uint8_t (*buffer)[STATUS_BUFFER_SIZE]; - size_t size; - - if (node == 17 || node == 25) { - head = HEAD_P1; - buffer = BUFFERS.STATUS_BUFFER_P1; - size = std::size(BUFFERS.STATUS_BUFFER_P1); - } else if (node == 18 || node == 26) { - head = HEAD_P2; - buffer = BUFFERS.STATUS_BUFFER_P2; - size = std::size(BUFFERS.STATUS_BUFFER_P2); - } else { - memset(out, 0, STATUS_BUFFER_SIZE); - return error_ret; - } + std::mutex* mutex = nullptr; + uint8_t* head = nullptr; + uint8_t (*buffer)[STATUS_BUFFER_SIZE]; + size_t size = STATUS_BUFFER_NUM_ENTRIES; + + if (node == 17 || node == 25) { + mutex = &MUTEX_P1; + head = &HEAD_P1; + buffer = BUFFERS.STATUS_BUFFER_P1; + } else if (node == 18 || node == 26) { + mutex = &MUTEX_P2; + head = &HEAD_P2; + buffer = BUFFERS.STATUS_BUFFER_P2; + } else { + memset(out, 0, STATUS_BUFFER_SIZE); + return error_ret; + } + + std::lock_guard lock(*mutex); - if (head_in != 0xFF) { - head = head_in; - } + if (head_in != 0xFF) { + *head = head_in; + } - // Compute ring index: walk backwards from head as index increases - // Assumes ring buffer size is a power of two - const size_t mask = size - 1; - const size_t offset = static_cast(index) & mask; - const size_t i = (static_cast(head) - offset + size) & mask; + // Compute ring index: walk backwards from head as index increases + // Assumes ring buffer size is a power of two + const size_t mask = size - 1; + const size_t offset = static_cast(index) & mask; + const size_t i = (static_cast(*head) - offset + size) & mask; - // Copy the chosen entry - memcpy(out, buffer[i], STATUS_BUFFER_SIZE); + // Copy the chosen entry + memcpy(out, buffer[i], STATUS_BUFFER_SIZE); - // Return the head value actually used - return static_cast(head); + // Return the head value actually used + return static_cast(*head); } return error_ret; @@ -126,38 +147,45 @@ static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2 return true; } -static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) { +static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFPollSource source) { // check freeze if (STATUS_BUFFER_FREEZE) { return true; } - // clear buffer - uint8_t *buffer = nullptr; - switch (node) { - case 17: - case 25: - // increase counter - HEAD_P1 = (HEAD_P1 + 1) % std::size(BUFFERS.STATUS_BUFFER_P1); - buffer = BUFFERS.STATUS_BUFFER_P1[HEAD_P1]; - break; - case 18: - case 26: - // increase counter - HEAD_P2 = (HEAD_P2 + 1) % std::size(BUFFERS.STATUS_BUFFER_P2); - buffer = BUFFERS.STATUS_BUFFER_P2[HEAD_P2]; - break; - default: - - // return failure on unknown node - return false; - } - memset(buffer, 0, STATUS_BUFFER_SIZE); - // Dance Dance Revolution if (avs::game::is_model("MDX")) { - + // Marks this module as actively being used, allowing this function to be called from other sources + if (!MDXF_IS_ACTIVE && source == ARKMDXP4_POLL) { + MDXF_IS_ACTIVE = true; + } + + uint8_t (*buffer)[STATUS_BUFFER_SIZE]; + uint8_t *head = nullptr; + uint16_t *prev_state = nullptr; + std::mutex* mutex = nullptr; + + switch (node) { + case 17: + case 25: + mutex = &MUTEX_P1; + head = &HEAD_P1; + prev_state = &PREV_STATE_P1; + buffer = BUFFERS.STATUS_BUFFER_P1; + break; + case 18: + case 26: + mutex = &MUTEX_P2; + head = &HEAD_P2; + prev_state = &PREV_STATE_P2; + buffer = BUFFERS.STATUS_BUFFER_P2; + break; + default: + // return failure on unknown node + return false; + } + // Sensor Map (LDUR): // FOOT DOWN = bit 32-35 = byte 4, bit 0-3 // FOOT UP = bit 36-39 = byte 4, bit 4-7 @@ -189,29 +217,66 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) { break; } - *(uint64_t*)&buffer[0x18] = arkGetTickTime64(); - // get buttons auto &buttons = games::ddr::get_buttons(); + uint8_t up_down = 0; + uint8_t left_right = 0; if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) { - buffer[4] |= 0xF0; + up_down |= 0xF0; } if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) { - buffer[4] |= 0x0F; + up_down |= 0x0F; } if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) { - buffer[5] |= 0xF0; + left_right |= 0xF0; } if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) { - buffer[5] |= 0x0F; + left_right |= 0x0F; } + + uint16_t current_state = (uint16_t(up_down) << 8) | left_right; + + std::lock_guard lock(*mutex); + + // If state hasn't changed and the update was triggered outside arkmdxp4.dll, then don't advance head pointer or write a new entry + if (current_state == *prev_state && source != ARKMDXP4_POLL) { + return true; + } + + *prev_state = current_state; + + // Advance head pointer + *head = (*head + 1) % STATUS_BUFFER_NUM_ENTRIES; + uint8_t* buffer_entry = buffer[*head]; + + // Clear buffer + memset(buffer_entry, 0, STATUS_BUFFER_SIZE); + + // Write button state + buffer_entry[4] = up_down; + buffer_entry[5] = left_right; + + //Write current game time + *(uint64_t*)&buffer_entry[0x18] = arkGetTickTime64(); } // return success return true; } +static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) { + return ac_io_mdxf_update_control_status_buffer_impl(node, ARKMDXP4_POLL); +} + +// Used for triggering updates of the controller states from outside arkmdxp4.dll main refresh loop (i.e. within rawinput.cpp on controller events) +void mdxf_poll() { + if (MDXF_IS_ACTIVE) { + ac_io_mdxf_update_control_status_buffer_impl(17, EXTERNAL_POLL); + ac_io_mdxf_update_control_status_buffer_impl(18, EXTERNAL_POLL); + } +} + /* * Module stuff */ @@ -229,4 +294,4 @@ void acio::MDXFModule::attach() { ACIO_MODULE_HOOK(ac_io_mdxf_get_control_status_buffer); ACIO_MODULE_HOOK(ac_io_mdxf_set_output_level); ACIO_MODULE_HOOK(ac_io_mdxf_update_control_status_buffer); -} +} \ No newline at end of file diff --git a/src/spice2x/acio/mdxf/mdxf_poll.h b/src/spice2x/acio/mdxf/mdxf_poll.h new file mode 100644 index 0000000..cf3126f --- /dev/null +++ b/src/spice2x/acio/mdxf/mdxf_poll.h @@ -0,0 +1,5 @@ +// mdxf_poll.h +#pragma once + +// Called from rawinput thread whenever inputs have just been updated +void mdxf_poll(); \ No newline at end of file diff --git a/src/spice2x/rawinput/rawinput.cpp b/src/spice2x/rawinput/rawinput.cpp index d3fbd0a..a27e6da 100644 --- a/src/spice2x/rawinput/rawinput.cpp +++ b/src/spice2x/rawinput/rawinput.cpp @@ -13,6 +13,7 @@ #include "piuio.h" #include "touch.h" +#include "acio/mdxf/mdxf_poll.h" extern "C" { #include "external/usbhidusage/usb-hid-usage.h" @@ -1825,6 +1826,9 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc( // free device device.mutex->unlock(); + + // update controller state ring buffers (DDR/MDXF) + mdxf_poll(); } // call the default window handler for cleanup