#include "kfca.h" #include "avs/game.h" #include "games/bs/io.h" #include "games/nost/io.h" #include "games/scotto/io.h" #include "games/sdvx/sdvx.h" #include "games/sdvx/io.h" #include "misc/eamuse.h" #include "rawinput/rawinput.h" #include "util/utils.h" using namespace GameAPI; // globals uint8_t KFCA_VOL_SOUND = 96; uint8_t KFCA_VOL_HEADPHONE = 96; uint8_t KFCA_VOL_EXTERNAL = 96; uint8_t KFCA_VOL_WOOFER = 96; // static stuff static uint8_t STATUS_BUFFER[64] {}; static bool STATUS_BUFFER_FREEZE = false; static unsigned int KFCA_VOLL = 0; static unsigned int KFCA_VOLR = 0; /* * Implementations */ static int __cdecl ac_io_kfca_control_button_led(unsigned int button, bool state) { // Sound Voltex if (avs::game::is_model("KFC")) { // control mapping static const size_t mapping[] = { games::sdvx::Lights::BT_A, games::sdvx::Lights::BT_B, games::sdvx::Lights::BT_C, games::sdvx::Lights::BT_D, games::sdvx::Lights::FX_L, games::sdvx::Lights::FX_R, games::sdvx::Lights::START, games::sdvx::Lights::GENERATOR_B, }; // check if button is mapped if (button < 8) { // get lights auto &lights = games::sdvx::get_lights(); // write light float value = state ? 1.f : 0.f; Lights::writeLight(RI_MGR, lights.at(mapping[button]), value); } } // Scotto if (avs::game::is_model("NSC")) { // control mapping static const size_t mapping[] = { games::scotto::Lights::PAD_F_B, games::scotto::Lights::PAD_E_R, games::scotto::Lights::PAD_E_B, ~0u, ~0u, ~0u, games::scotto::Lights::PAD_F_R, games::scotto::Lights::BUTTON, }; // check if button is mapped if (button < std::size(mapping) && button[mapping] != ~0u) { // get lights auto &lights = games::scotto::get_lights(); // write light float value = state ? 1.f : 0.f; Lights::writeLight(RI_MGR, lights.at(mapping[button]), value); } } // return success return 1; } static int __cdecl ac_io_kfca_control_coin_blocker_close(int a1) { eamuse_coin_set_block(true); return 1; } static int __cdecl ac_io_kfca_control_coin_blocker_open(int a1) { eamuse_coin_set_block(false); return 1; } static int __cdecl ac_io_kfca_control_led_bright(uint32_t led_field, uint8_t brightness) { // Sound Voltex if (avs::game::is_model("KFC")) { // get lights auto &lights = games::sdvx::get_lights(); // control mapping static const size_t mapping[] { games::sdvx::Lights::WING_LEFT_UP_R, games::sdvx::Lights::WING_LEFT_UP_G, games::sdvx::Lights::WING_LEFT_UP_B, games::sdvx::Lights::WING_RIGHT_UP_R, games::sdvx::Lights::WING_RIGHT_UP_G, games::sdvx::Lights::WING_RIGHT_UP_B, games::sdvx::Lights::WING_LEFT_LOW_R, games::sdvx::Lights::WING_LEFT_LOW_G, games::sdvx::Lights::WING_LEFT_LOW_B, games::sdvx::Lights::WING_RIGHT_LOW_R, games::sdvx::Lights::WING_RIGHT_LOW_G, games::sdvx::Lights::WING_RIGHT_LOW_B, games::sdvx::Lights::WOOFER_R, games::sdvx::Lights::WOOFER_G, games::sdvx::Lights::WOOFER_B, games::sdvx::Lights::CONTROLLER_R, games::sdvx::Lights::CONTROLLER_G, games::sdvx::Lights::CONTROLLER_B, games::sdvx::Lights::GENERATOR_R, games::sdvx::Lights::GENERATOR_G, }; // write light float value = brightness / 255.f; for (size_t i = 0; i < std::size(mapping); i++) { if (led_field & (1 << i)) { Lights::writeLight(RI_MGR, lights.at(mapping[i]), value); } } } // BeatStream if (avs::game::is_model("NBT")) { // get lights auto &lights = games::bs::get_lights(); // mapping static const size_t mapping[] { ~0u, ~0u, ~0u, games::bs::Lights::RightR, games::bs::Lights::RightG, games::bs::Lights::RightB, games::bs::Lights::LeftR, games::bs::Lights::LeftG, games::bs::Lights::LeftB, games::bs::Lights::BottomR, games::bs::Lights::BottomG, games::bs::Lights::BottomB, }; // write light float value = brightness / 127.f; for (size_t i = 0; i < std::size(mapping); i++) { if (mapping[i] != ~0u && led_field & (1 << i)) { Lights::writeLight(RI_MGR, lights.at(mapping[i]), value); } } } // Nostalgia if (avs::game::is_model("PAN")) { // get lights auto &lights = games::nost::get_lights(); // mapping static const size_t mapping[] { ~0u, ~0u, ~0u, games::nost::Lights::TitleR, games::nost::Lights::TitleG, games::nost::Lights::TitleB, ~0u, ~0u, ~0u, games::nost::Lights::BottomR, games::nost::Lights::BottomG, games::nost::Lights::BottomB, }; // write light float value = brightness / 127.f; for (size_t i = 0; i < std::size(mapping); i++) { if (mapping[i] != ~0u && led_field & (1 << i)) { Lights::writeLight(RI_MGR, lights.at(mapping[i]), value); } } } // Scotto if (avs::game::is_model("NSC")) { // get lights auto &lights = games::scotto::get_lights(); // mapping static const size_t mapping[] { games::scotto::Lights::CUP_R, games::scotto::Lights::CUP_G, games::scotto::Lights::CUP_B, games::scotto::Lights::PAD_A_R, games::scotto::Lights::PAD_A_G, games::scotto::Lights::PAD_A_B, games::scotto::Lights::PAD_B_R, games::scotto::Lights::PAD_B_G, games::scotto::Lights::PAD_B_B, games::scotto::Lights::PAD_C_R, games::scotto::Lights::PAD_C_G, games::scotto::Lights::PAD_C_B, games::scotto::Lights::PAD_D_R, games::scotto::Lights::PAD_D_G, games::scotto::Lights::PAD_D_B, games::scotto::Lights::FIRST_PAD_R, games::scotto::Lights::FIRST_PAD_G, games::scotto::Lights::FIRST_PAD_B, games::scotto::Lights::PAD_F_G, games::scotto::Lights::PAD_E_G, }; // write light float value = brightness / 255.f; for (size_t i = 0; i < std::size(mapping); i++) { if (led_field & (1 << i)) { Lights::writeLight(RI_MGR, lights.at(mapping[i]), value); } } } // return success return 1; } static char __cdecl ac_io_kfca_current_coinstock(int a1, DWORD *a2) { *a2 = (DWORD) eamuse_coin_get_stock(); return 1; } static void *__cdecl ac_io_kfca_get_control_status_buffer(void *target_buffer) { // copy buffer return memcpy(target_buffer, STATUS_BUFFER, 64); } static int __cdecl ac_io_kfca_lock_coincounter(int a1) { eamuse_coin_set_block(true); return 1; } static bool __cdecl ac_io_kfca_req_volume_control( uint8_t vol_sound, uint8_t vol_headphone, uint8_t vol_external, uint8_t vol_woofer) { // update globals KFCA_VOL_SOUND = vol_sound; KFCA_VOL_HEADPHONE = vol_headphone; KFCA_VOL_EXTERNAL = vol_external; KFCA_VOL_WOOFER = vol_woofer; // Sound Voltex if (avs::game::is_model("KFC")) { // get lights auto &lights = games::sdvx::get_lights(); GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_SOUND], (100 - vol_sound) / 100.f); GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_HEADPHONE], (100 - vol_headphone) / 100.f); GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_EXTERNAL], (100 - vol_external) / 100.f); GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_WOOFER], (100 - vol_woofer) / 100.f); } return true; } static bool __cdecl ac_io_kfca_set_watchdog_time(short a1) { return true; } static char __cdecl ac_io_kfca_unlock_coincounter(int a1) { eamuse_coin_set_block(false); return 1; } static char __cdecl ac_io_kfca_update_control_status_buffer() { static const int input_offset = 4; // check freeze if (STATUS_BUFFER_FREEZE) { return true; } // clear buffer memset(STATUS_BUFFER, 0, 64); // SDVX if (avs::game::is_model("KFC")) { // get buttons auto &buttons = games::sdvx::get_buttons(); if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Test))) { STATUS_BUFFER[input_offset + 1] |= 0x20; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Service))) { STATUS_BUFFER[input_offset + 1] |= 0x10; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::CoinMech))) { STATUS_BUFFER[input_offset + 1] |= 0x04; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Start))) { STATUS_BUFFER[input_offset + 9] |= 0x08; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_A))) { STATUS_BUFFER[input_offset + 9] |= 0x04; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_B))) { STATUS_BUFFER[input_offset + 9] |= 0x02; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_C))) { STATUS_BUFFER[input_offset + 9] |= 0x01; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_D))) { STATUS_BUFFER[input_offset + 11] |= 0x20; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_L))) { STATUS_BUFFER[input_offset + 11] |= 0x10; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_R))) { STATUS_BUFFER[input_offset + 11] |= 0x08; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Headphone))) { STATUS_BUFFER[input_offset + 9] |= 0x20; } // volume left if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) { KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) { KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023; } // volume right if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) { KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023; } if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) { KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023; } // update volumes auto &analogs = games::sdvx::get_analogs(); auto vol_left = KFCA_VOLL; auto vol_right = KFCA_VOLR; if (analogs.at(0).isSet() || analogs.at(1).isSet()) { vol_left += (unsigned int) (Analogs::getState(RI_MGR, analogs.at(games::sdvx::Analogs::VOL_L)) * 1023.99f); vol_right += (unsigned int) (Analogs::getState(RI_MGR, analogs.at(games::sdvx::Analogs::VOL_R)) * 1023.99f); } // proper loops vol_left %= 1024; vol_right %= 1024; // save volumes in buffer STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF); STATUS_BUFFER[input_offset + 16 + 1] |= (unsigned char) ((vol_left >> 2) & 0xFF); STATUS_BUFFER[input_offset + 16 + 2] |= (unsigned char) ((vol_right << 6) & 0xFF); STATUS_BUFFER[input_offset + 16 + 3] |= (unsigned char) ((vol_right >> 2) & 0xFF); } // Beatstream if (avs::game::is_model("NBT")) { // get buttons auto &buttons = games::bs::get_buttons(); if (Buttons::getState(RI_MGR, buttons.at(games::bs::Buttons::Test))) { STATUS_BUFFER[input_offset + 1] |= 0x20; } if (Buttons::getState(RI_MGR, buttons.at(games::bs::Buttons::Service))) { STATUS_BUFFER[input_offset + 1] |= 0x10; } if (Buttons::getState(RI_MGR, buttons.at(games::bs::Buttons::CoinMech))) { STATUS_BUFFER[input_offset + 1] |= 0x04; } } // Nostalgia if (avs::game::is_model("PAN")) { // get buttons auto &buttons = games::nost::get_buttons(); if (Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::Service))) { STATUS_BUFFER[input_offset + 1] |= 0x10; } if (Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::Test))) { STATUS_BUFFER[input_offset + 1] |= 0x20; } if (Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::CoinMech))) { STATUS_BUFFER[input_offset + 1] |= 0x04; } } // Scotto if (avs::game::is_model("NSC")) { // get buttons auto &buttons = games::scotto::get_buttons(); if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Test)) == Buttons::State::BUTTON_PRESSED) { STATUS_BUFFER[input_offset + 1] |= 0x20; } if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Service)) == Buttons::State::BUTTON_PRESSED) { STATUS_BUFFER[input_offset + 1] |= 0x10; } if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::CoinMech)) == Buttons::State::BUTTON_PRESSED) { STATUS_BUFFER[input_offset + 1] |= 0x04; } if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Start)) == Buttons::State::BUTTON_PRESSED) { STATUS_BUFFER[input_offset + 9] |= 0x20; } if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Up)) == Buttons::State::BUTTON_PRESSED) { STATUS_BUFFER[input_offset + 9] |= 0x10; } if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Down)) == Buttons::State::BUTTON_PRESSED) { STATUS_BUFFER[input_offset + 9] |= 0x08; } // the code also checks `input_offset + 9` for 0x01 but that does not trigger any response // in the "I/O CHECK" scene } // success return true; } static void __cdecl ac_io_kfca_watchdog_off() { } // yes this is spelled "marge" instead of "merge" static int __cdecl ac_io_kfca_set_status_marge_func(void *cb) { return 1; } /* * Module stuff */ acio::KFCAModule::KFCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("KFCA", module, hookMode) { this->status_buffer = STATUS_BUFFER; this->status_buffer_size = sizeof(STATUS_BUFFER); this->status_buffer_freeze = &STATUS_BUFFER_FREEZE; } void acio::KFCAModule::attach() { ACIOModule::attach(); // hooks ACIO_MODULE_HOOK(ac_io_kfca_control_button_led); ACIO_MODULE_HOOK(ac_io_kfca_control_coin_blocker_close); ACIO_MODULE_HOOK(ac_io_kfca_control_coin_blocker_open); ACIO_MODULE_HOOK(ac_io_kfca_control_led_bright); ACIO_MODULE_HOOK(ac_io_kfca_current_coinstock); ACIO_MODULE_HOOK(ac_io_kfca_get_control_status_buffer); ACIO_MODULE_HOOK(ac_io_kfca_lock_coincounter); ACIO_MODULE_HOOK(ac_io_kfca_req_volume_control); ACIO_MODULE_HOOK(ac_io_kfca_set_watchdog_time); ACIO_MODULE_HOOK(ac_io_kfca_unlock_coincounter); ACIO_MODULE_HOOK(ac_io_kfca_update_control_status_buffer); ACIO_MODULE_HOOK(ac_io_kfca_watchdog_off); ACIO_MODULE_HOOK(ac_io_kfca_set_status_marge_func); }