Add Mahjong Fight Girl Support (#275)

## Link to GitHub Issue, if one exists
n/a

## Description of change
Adds Mahjong Fight Girl Support

## Compiling
+

## Testing
Tested with VFG:J:E:A:2024100800 in HG mode with VFG IO. No
modifications made to data, other than avs-config.xml and
ea3-config.xml. B and C modes don't work because of serial touchscreen.
UKS mode doesn't work because of IO.
This commit is contained in:
oleg238948234
2025-03-25 04:06:55 +05:00
committed by GitHub
parent 165cc6028f
commit 1b7ebd6fc2
21 changed files with 915 additions and 33 deletions
+4
View File
@@ -442,6 +442,9 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/qks/qks.cpp games/qks/qks.cpp
games/qks/io.cpp games/qks/io.cpp
games/qks/bi2x_hook.cpp games/qks/bi2x_hook.cpp
games/mfg/mfg.cpp
games/mfg/io.cpp
games/mfg/bi2a_hook.cpp
# hooks # hooks
hooks/audio/audio.cpp hooks/audio/audio.cpp
@@ -587,6 +590,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/sysutils.cpp util/sysutils.cpp
util/lz77.cpp util/lz77.cpp
util/tapeled.cpp util/tapeled.cpp
util/execexe.cpp
) )
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
+3 -2
View File
@@ -4,8 +4,9 @@
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "util/utils.h" #include "util/utils.h"
acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort) { acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount) {
this->com_port = lpCOMPort; this->com_port = lpCOMPort;
this->icca_node_count = iccaNodeCount;
} }
bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) { bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) {
@@ -16,7 +17,7 @@ bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) {
log_info("acioemu", "Opened {} (ACIO)", ws2s(com_port)); log_info("acioemu", "Opened {} (ACIO)", ws2s(com_port));
// ACIO device // ACIO device
acio_emu.add_device(new acioemu::ICCADevice(false, true, 2)); acio_emu.add_device(new acioemu::ICCADevice(false, true, icca_node_count));
return true; return true;
} }
+3 -1
View File
@@ -9,10 +9,12 @@ namespace acioemu {
private: private:
LPCWSTR com_port; LPCWSTR com_port;
uint8_t icca_node_count;
acioemu::ACIOEmu acio_emu; acioemu::ACIOEmu acio_emu;
public: public:
ACIOHandle(LPCWSTR lpCOMPort); ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount = 2);
bool open(LPCWSTR lpFileName) override; bool open(LPCWSTR lpFileName) override;
+3
View File
@@ -107,6 +107,8 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H')) (avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
) { ) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA"); this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} else if (avs::game::is_model({"VFG"})) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
} else { } else {
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA"); this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
} }
@@ -333,6 +335,7 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
case ACIO_CMD_CLEAR: case ACIO_CMD_CLEAR:
case 0x30: // GetBoardProductNumber case 0x30: // GetBoardProductNumber
case 0x31: // GetMicomInfo case 0x31: // GetMicomInfo
case 0x3A: // ???
case 0x0116: // ??? case 0x0116: // ???
case 0x0120: // ??? case 0x0120: // ???
case 0xFF: // BROADCAST case 0xFF: // BROADCAST
+8
View File
@@ -35,6 +35,7 @@
#include "bc/io.h" #include "bc/io.h"
#include "ccj/io.h" #include "ccj/io.h"
#include "qks/io.h" #include "qks/io.h"
#include "mfg/io.h"
namespace games { namespace games {
@@ -305,6 +306,13 @@ namespace games {
buttons.insert({ qks, qks::get_buttons() }); buttons.insert({ qks, qks::get_buttons() });
buttons_help.insert({ qks, qks::get_buttons_help() }); buttons_help.insert({ qks, qks::get_buttons_help() });
file_hints[qks].emplace_back("game/uks.exe"); file_hints[qks].emplace_back("game/uks.exe");
// Mahjong Fight Girl
const std::string mfg("Mahjong Fight Girl");
games.push_back(mfg);
buttons.insert({ mfg, mfg::get_buttons() });
buttons_help.insert({ mfg, mfg::get_buttons_help() });
file_hints[mfg].emplace_back("game/MFGClient_Data");
} }
const std::vector<std::string> &get_games() { const std::vector<std::string> &get_games() {
+385
View File
@@ -0,0 +1,385 @@
#include "bi2a_hook.h"
#include "util/execexe.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/io.h"
#include "io.h"
#include "util/tapeled.h"
namespace games::mfg {
/*
* class definitions
*/
struct AIO_SCI_COMM {
};
struct AIO_NMGR_IOB {
};
struct AIO_IOB_BI2A_VFG {
};
struct AIO_IOB_BI2A_VFG__INPUTDATA {
uint8_t data[16];
};
struct AIO_IOB_BI2A_VFG__OUTPUTDATA {
uint8_t data[40];
};
struct AIO_IOB_BI2A_VFG__INPUT
{
uint8_t bPcPowerCheck;
uint8_t CoinCount;
uint8_t bTest;
uint8_t bService;
uint8_t bCoinSw;
uint8_t bCoinJam;
uint8_t bCabinet;
uint8_t bHPDetect;
uint8_t bRecDetect;
uint8_t bStart;
};
struct AIO_IOB_BI2A_VFG__DEVSTATUS
{
uint8_t MechType;
uint8_t bIsBi2a;
uint8_t IoCounter;
AIO_IOB_BI2A_VFG__INPUT Input;
AIO_IOB_BI2A_VFG__INPUTDATA InputData;
AIO_IOB_BI2A_VFG__OUTPUTDATA OutputData;
};
/*
* typedefs
*/
// libaio-iob_video.dll
typedef AIO_SCI_COMM* (__fastcall *aioIobBi2a_OpenSciUsbCdc_t)(uint32_t i_SerialNumber);
typedef AIO_IOB_BI2A_VFG* (__fastcall *aioIobBi2aVFG_Create_t)(AIO_NMGR_IOB *i_pNodeMgr, uint32_t i_DevId);
typedef void (__fastcall *aioIobBi2aVFG_SetMechType_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl, uint32_t i_MechType);
typedef void (__fastcall *aioIobBi2aVFG_GetDeviceStatus_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl,
AIO_IOB_BI2A_VFG__DEVSTATUS *o_pDevStatus,
uint32_t i_cbDevStatus);
typedef void (__fastcall *aioIobBi2aVFG_SetWatchDogTimer_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint16_t i_Count);
typedef void (__fastcall *aioIobBi2aVFG_ControlCoinBlocker_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Slot,
bool i_bOpen);
typedef void (__fastcall *aioIobBi2aVFG_AddCounter_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Counter,
uint32_t i_Count);
typedef void (__fastcall *aioIobBi2aVFG_SetAmpVolume_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Amp,
uint32_t i_Volume);
typedef void (__fastcall *aioIobBi2aVFG_EnableUsbCharger_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, bool i_bEnable);
typedef void (__fastcall *aioIobBi2aVFG_SetLamp_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Lamp, uint8_t i_Bright);
typedef void (__fastcall *aioIobBi2aVFG_SetIccrLed_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_RGB);
typedef void (__fastcall *aioIobBi2aVFG_SetLedData_t)(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint8_t *i_pData,
uint32_t i_cbData);
// libaio-iob.dll
typedef AIO_NMGR_IOB* (__fastcall *aioNMgrIob_Create_t)(AIO_SCI_COMM* i_pSci, uint32_t i_bfMode);
// libaio.dll
typedef void (__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB *i_pNodeMgr);
typedef int32_t (__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB *i_pNodeMgr);
typedef bool (__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State);
typedef bool (__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State);
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl);
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl);
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State);
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State);
/*
* function pointers
*/
// libaio-iob_video.dll
static aioIobBi2a_OpenSciUsbCdc_t aioIobBi2a_OpenSciUsbCdc_orig = nullptr;
static aioIobBi2aVFG_Create_t aioIobBi2aVFG_Create_orig = nullptr;
static aioIobBi2aVFG_SetMechType_t aioIobBi2aVFG_SetMechType_orig = nullptr;
static aioIobBi2aVFG_GetDeviceStatus_t aioIobBi2aVFG_GetDeviceStatus_orig = nullptr;
static aioIobBi2aVFG_SetWatchDogTimer_t aioIobBi2aVFG_SetWatchDogTimer_orig = nullptr;
static aioIobBi2aVFG_ControlCoinBlocker_t aioIobBi2aVFG_ControlCoinBlocker_orig = nullptr;
static aioIobBi2aVFG_AddCounter_t aioIobBi2aVFG_AddCounter_orig = nullptr;
static aioIobBi2aVFG_SetAmpVolume_t aioIobBi2aVFG_SetAmpVolume_orig = nullptr;
static aioIobBi2aVFG_EnableUsbCharger_t aioIobBi2aVFG_EnableUsbCharger_orig = nullptr;
static aioIobBi2aVFG_SetLamp_t aioIobBi2aVFG_SetLamp_orig = nullptr;
static aioIobBi2aVFG_SetIccrLed_t aioIobBi2aVFG_SetIccrLed_orig = nullptr;
static aioIobBi2aVFG_SetLedData_t aioIobBi2aVFG_SetLedData_orig = nullptr;
// libaio-iob.dll
static aioNMgrIob_Create_t aioNMgrIob_Create_orig = nullptr;
// libaio.dll
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
static aioNodeMgr_GetState_t aioNodeMgr_GetState_orig = nullptr;
static aioNodeMgr_IsReady_t aioNodeMgr_IsReady_orig = nullptr;
static aioNodeMgr_IsError_t aioNodeMgr_IsError_orig = nullptr;
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
static aioNodeCtl_GetState_t aioNodeCtl_GetState_orig = nullptr;
static aioNodeCtl_IsReady_t aioNodeCtl_IsReady_orig = nullptr;
static aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
/*
* variables
*/
static AIO_SCI_COMM *aioSciComm;
static AIO_NMGR_IOB *aioNmgrIob;
static AIO_IOB_BI2A_VFG *aioIobBi2aVfg;
static uint8_t mechType = 0;
static uint8_t count = 0;
/*
* implementations
*/
static AIO_SCI_COMM* __fastcall aioIob2Bi2a_OpenSciUsbCdc(uint32_t i_SerialNumber) {
log_info("bi2a_hook", "aioIob2Bi2a_OpenSciUsbCdc hook hit");
aioSciComm = new AIO_SCI_COMM;
return aioSciComm;
}
static AIO_IOB_BI2A_VFG* __fastcall aioIobBi2aVFG_Create(AIO_NMGR_IOB *i_pNodeMgr, uint32_t i_DevId) {
if (i_pNodeMgr == aioNmgrIob) {
log_info("bi2a_hook", "node created");
aioIobBi2aVfg = new AIO_IOB_BI2A_VFG;
return aioIobBi2aVfg;
} else {
return aioIobBi2aVFG_Create_orig(i_pNodeMgr, i_DevId);
}
}
void __fastcall aioIobBi2aVFG_SetMechType(AIO_IOB_BI2A_VFG *i_pNodeCtl, uint32_t i_MechType) {
if (i_pNodeCtl == aioIobBi2aVfg) {
mechType = i_MechType;
} else {
return aioIobBi2aVFG_SetMechType_orig(i_pNodeCtl, i_MechType);
}
}
void __fastcall aioIobBi2aVFG_GetDeviceStatus(AIO_IOB_BI2A_VFG* i_pNodeCtl,
AIO_IOB_BI2A_VFG__DEVSTATUS *o_pDevStatus,
uint32_t i_cbDevStatus) {
RI_MGR->devices_flush_output();
if (i_pNodeCtl != aioIobBi2aVfg) {
return aioIobBi2aVFG_GetDeviceStatus_orig(i_pNodeCtl, o_pDevStatus, i_cbDevStatus);
}
memset(o_pDevStatus, 0x00, sizeof(AIO_IOB_BI2A_VFG__DEVSTATUS));
o_pDevStatus->MechType = mechType;
o_pDevStatus->bIsBi2a = 1;
o_pDevStatus->IoCounter = count++;
auto &buttons = get_buttons();
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
o_pDevStatus->Input.bTest = 1;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
o_pDevStatus->Input.bService = 1;
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
o_pDevStatus->Input.bCoinSw = 1;
o_pDevStatus->Input.CoinCount = eamuse_coin_get_stock();
}
void __fastcall aioIobBi2aVFG_SetWatchDogTimer(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint16_t i_Count) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetWatchDogTimer_orig(i_pNodeCtl, i_Count);
}
}
void __fastcall aioIobBi2aVFG_ControlCoinBlocker(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Slot, bool i_bOpen) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_ControlCoinBlocker_orig(i_pNodeCtl, i_Slot, i_bOpen);
}
}
void __fastcall aioIobBi2aVFG_AddCounter(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Counter, uint32_t i_Count) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_AddCounter_orig(i_pNodeCtl, i_Counter, i_Count);
}
}
void __fastcall aioIobBi2aVFG_SetAmpVolume(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Amp, uint32_t i_Volume) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetAmpVolume_orig(i_pNodeCtl, i_Amp, i_Volume);
}
}
void __fastcall aioIobBi2aVFG_EnableUsbCharger(AIO_IOB_BI2A_VFG* i_pNodeCtl, bool i_bEnable) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_EnableUsbCharger_orig(i_pNodeCtl, i_bEnable);
}
}
void __fastcall aioIobBi2aVFG_SetLamp(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_Lamp, uint8_t i_Bright) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetLamp_orig(i_pNodeCtl, i_Lamp, i_Bright);
}
}
void __fastcall aioIobBi2aVFG_SetIccrLed(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint32_t i_RGB) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetIccrLed_orig(i_pNodeCtl, i_RGB);
}
}
void __fastcall aioIobBi2aVFG_SetLedData(AIO_IOB_BI2A_VFG* i_pNodeCtl, uint8_t *i_pData, uint32_t i_cbData) {
if (i_pNodeCtl == aioIobBi2aVfg) {
} else {
return aioIobBi2aVFG_SetLedData_orig(i_pNodeCtl, i_pData, i_cbData);
}
}
static AIO_NMGR_IOB* __fastcall aioNMgrIob_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioSciComm) {
aioNmgrIob = new AIO_NMGR_IOB;
return aioNmgrIob;
} else {
return aioNMgrIob_Create_orig(i_pSci, i_bfMode);
}
}
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob) {
delete aioNmgrIob;
aioNmgrIob = nullptr;
} else {
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
}
}
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob) {
return 1;
} else {
return aioNodeMgr_GetState_orig(i_pNodeMgr);
}
}
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob) {
return true;
} else {
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
}
}
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob) {
return false;
} else {
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
}
}
static void __fastcall aioNodeCtl_Destroy(AIO_IOB_BI2A_VFG *i_pNodeCtl) {
if (i_pNodeCtl == aioIobBi2aVfg) {
delete aioIobBi2aVfg;
aioIobBi2aVfg = nullptr;
} else {
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
}
}
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB_BI2A_VFG *i_pNodeCtl) {
if (i_pNodeCtl == aioIobBi2aVfg) {
return 1;
} else {
return aioNodeCtl_GetState_orig(i_pNodeCtl);
}
}
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIobBi2aVfg) {
return true;
} else {
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
}
}
static bool __fastcall aioNodeCtl_IsError(AIO_IOB_BI2A_VFG *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIobBi2aVfg) {
return false;
} else {
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
}
}
void bi2a_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi2a_hook", "init");
// libaio-iob_video.dll
const auto libaioIobVideoDll = "libaio-iob_video.dll";
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2a_OpenSciUsbCdc",
aioIob2Bi2a_OpenSciUsbCdc, &aioIobBi2a_OpenSciUsbCdc_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_Create",
aioIobBi2aVFG_Create, &aioIobBi2aVFG_Create_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetMechType",
aioIobBi2aVFG_SetMechType, &aioIobBi2aVFG_SetMechType_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_GetDeviceStatus",
aioIobBi2aVFG_GetDeviceStatus, &aioIobBi2aVFG_GetDeviceStatus_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetWatchDogTimer",
aioIobBi2aVFG_SetWatchDogTimer, &aioIobBi2aVFG_SetWatchDogTimer_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_ControlCoinBlocker",
aioIobBi2aVFG_ControlCoinBlocker, &aioIobBi2aVFG_ControlCoinBlocker_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_AddCounter",
aioIobBi2aVFG_AddCounter, &aioIobBi2aVFG_AddCounter_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetAmpVolume",
aioIobBi2aVFG_SetAmpVolume, &aioIobBi2aVFG_SetAmpVolume_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_EnableUsbCharger",
aioIobBi2aVFG_EnableUsbCharger, &aioIobBi2aVFG_EnableUsbCharger_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetLamp",
aioIobBi2aVFG_SetLamp, &aioIobBi2aVFG_SetLamp_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetIccrLed",
aioIobBi2aVFG_SetIccrLed, &aioIobBi2aVFG_SetIccrLed_orig);
execexe::trampoline_try(libaioIobVideoDll, "aioIobBi2aVFG_SetLedData",
aioIobBi2aVFG_SetLedData, &aioIobBi2aVFG_SetLedData_orig);
// libaio-iob.dll
const auto libaioIobDll = "libaio-iob.dll";
execexe::trampoline_try(libaioIobDll, "aioNMgrIob_Create",
aioNMgrIob_Create, &aioNMgrIob_Create_orig);
// libaio.dll
const auto libaioDll = "libaio.dll";
execexe::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_GetState",
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
execexe::trampoline_try(libaioDll, "aioNodeMgr_IsError",
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_GetState",
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
execexe::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
}
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace games::mfg {
void bi2a_hook_init();
}
+23
View File
@@ -0,0 +1,23 @@
#include "io.h"
std::vector<Button> &games::mfg::get_buttons() {
static std::vector<Button> buttons;
if (buttons.empty()) {
buttons = GameAPI::Buttons::getButtons("Mahjong Fight Girl");
GameAPI::Buttons::sortButtons(
&buttons,
"Service",
"Test",
"Coin Mech"
//"Action Button"
);
}
return buttons;
}
std::string games::mfg::get_buttons_help() {
return "";
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <vector>
#include "cfg/api.h"
namespace games::mfg {
namespace Buttons {
enum {
Service,
Test,
CoinMech,
ActionButton
};
}
// getters
std::vector<Button> &get_buttons();
std::string get_buttons_help();
}
+74
View File
@@ -0,0 +1,74 @@
#include "mfg.h"
#include <format>
#include "util/fileutils.h"
#include "util/unity_player.h"
#include "util/utils.h"
#include "util/execexe.h"
#include "acioemu/handle.h"
#include "misc/wintouchemu.h"
#include "hooks/graphics/graphics.h"
#include "bi2a_hook.h"
namespace games::mfg {
std::string MFG_INJECT_ARGS = "";
std::string MFG_CABINET_TYPE = "HG";
bool MFG_NO_IO = false;
static acioemu::ACIOHandle *acioHandle = nullptr;
static std::wstring portName;
void MFGGame::attach() {
Game::attach();
// create required files
fileutils::dir_create_recursive("dev/raw/log");
fileutils::bin_write("dev/raw/log/output_log.txt", nullptr, 0);
SetEnvironmentVariableA("VFG_CABINET_TYPE", MFG_CABINET_TYPE.c_str());
// add card reader
portName = MFG_CABINET_TYPE == "UKS" ? L"\\\\.\\COM1" : L"\\\\.\\COM3";
acioHandle = new acioemu::ACIOHandle(portName.c_str(), 1);
devicehook_init_trampoline();
devicehook_add(acioHandle);
execexe::init();
execexe::init_port_hook(portName, acioHandle);
if (GRAPHICS_SHOW_CURSOR) {
unity_utils::force_show_cursor(true);
}
unity_utils::set_args(
std::format("{} {}{}",
GetCommandLineA(),
MFG_INJECT_ARGS,
unity_utils::get_unity_player_args()));
}
void MFGGame::post_attach() {
Game::post_attach();
execexe::load_library("libaio.dll");
execexe::load_library("libaio-iob.dll");
execexe::load_library("libaio-iob_video.dll");
execexe::load_library("libaio-iob2_video.dll");
execexe::load_library("win10actlog.dll");
if (!MFG_NO_IO) {
// insert BI2* hooks
if (MFG_CABINET_TYPE == "UKS") {
log_fatal("mfg", "UKS io is not supported");
} else {
bi2a_hook_init();
}
}
}
void MFGGame::detach() {
Game::detach();
devicehook_dispose();
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "games/game.h"
namespace games::mfg {
extern std::string MFG_INJECT_ARGS;
extern std::string MFG_CABINET_TYPE;
extern bool MFG_NO_IO;
class MFGGame : public games::Game {
public:
MFGGame() : Game("Mahjong Fight Girl") {}
virtual void attach() override;
virtual void post_attach() override;
virtual void detach() override;
};
}
+31
View File
@@ -59,6 +59,7 @@
#include "games/ccj/ccj.h" #include "games/ccj/ccj.h"
#include "games/ccj/trackball.h" #include "games/ccj/trackball.h"
#include "games/qks/qks.h" #include "games/qks/qks.h"
#include "games/mfg/mfg.h"
#include "games/museca/museca.h" #include "games/museca/museca.h"
#include "hooks/avshook.h" #include "hooks/avshook.h"
#include "hooks/audio/audio.h" #include "hooks/audio/audio.h"
@@ -213,6 +214,7 @@ int main_implementation(int argc, char *argv[]) {
bool attach_bc = false; bool attach_bc = false;
bool attach_ccj = false; bool attach_ccj = false;
bool attach_qks = false; bool attach_qks = false;
bool attach_mfg = false;
bool attach_museca = false; bool attach_museca = false;
// misc settings // misc settings
@@ -602,6 +604,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::LoadQKSModule].value_bool()) { if (options[launcher::Options::LoadQKSModule].value_bool()) {
attach_qks = true; attach_qks = true;
} }
if (options[launcher::Options::LoadMFGModule].value_bool()) {
attach_mfg = true;
}
if (options[launcher::Options::LoadMusecaModule].value_bool()) { if (options[launcher::Options::LoadMusecaModule].value_bool()) {
attach_museca = true; attach_museca = true;
} }
@@ -1028,6 +1033,15 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::QKSArgs].is_active()) { if (options[launcher::Options::QKSArgs].is_active()) {
games::qks::QKS_INJECT_ARGS = options[launcher::Options::QKSArgs].value_text(); games::qks::QKS_INJECT_ARGS = options[launcher::Options::QKSArgs].value_text();
} }
if (options[launcher::Options::MFGArgs].is_active()) {
games::mfg::MFG_INJECT_ARGS = options[launcher::Options::MFGArgs].value_text();
}
if (options[launcher::Options::MFGCabType].is_active()) {
games::mfg::MFG_CABINET_TYPE = options[launcher::Options::MFGCabType].value_text();
}
if (options[launcher::Options::MFGNoIO].is_active()) {
games::mfg::MFG_NO_IO = options[launcher::Options::MFGNoIO].value_bool();
}
if (options[launcher::Options::spice2x_EnableSMXStage].value_bool()) { if (options[launcher::Options::spice2x_EnableSMXStage].value_bool()) {
rawinput::ENABLE_SMX_STAGE = true; rawinput::ENABLE_SMX_STAGE = true;
} }
@@ -1589,6 +1603,19 @@ int main_implementation(int argc, char *argv[]) {
break; break;
} }
// Mahjong Fight Girl
if (check_dll("kamunity.dll") && fileutils::dir_exists("game/MFGClient_Data")) {
avs::game::DLL_NAME = "kamunity.dll";
attach_io = true;
attach_mfg = true;
launcher::signal::USE_VEH_WORKAROUND = true;
// automatically show cursor when no touchscreen is available
if (!is_touch_available()) {
GRAPHICS_SHOW_CURSOR = true;
}
break;
}
// Busou Shinki: Armored Princess Battle Conductor // Busou Shinki: Armored Princess Battle Conductor
if (check_dll("kamunity.dll") && fileutils::file_exists("game/bsac_app.exe")) { if (check_dll("kamunity.dll") && fileutils::file_exists("game/bsac_app.exe")) {
avs::game::DLL_NAME = "kamunity.dll"; avs::game::DLL_NAME = "kamunity.dll";
@@ -1740,6 +1767,10 @@ int main_implementation(int argc, char *argv[]) {
avs::core::set_default_heap_size("kamunity.dll"); avs::core::set_default_heap_size("kamunity.dll");
games.push_back(new games::qks::QKSGame()); games.push_back(new games::qks::QKSGame());
} }
if (attach_mfg) {
avs::core::set_default_heap_size("kamunity.dll");
games.push_back(new games::mfg::MFGGame());
}
// apply user heap size, if defined // apply user heap size, if defined
if (user_heap_size > 0) { if (user_heap_size > 0) {
+42
View File
@@ -1006,6 +1006,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "QuizKnock STADIUM", .game_name = "QuizKnock STADIUM",
.category = "Game Options (Advanced)", .category = "Game Options (Advanced)",
}, },
{
// LoadMFGModule
.title = "Force Load Mahjong Fight Girl",
.name = "mfg",
.desc = "manually enable Mahjong Fight Girl module",
.type = OptionType::Bool,
.game_name = "Mahjong Fight Girl",
.category = "Game Options (Advanced)",
},
{ {
// LoadMusecaModule // LoadMusecaModule
.title = "Force Load Museca", .title = "Force Load Museca",
@@ -1671,6 +1680,39 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Chase Chase Jokers", .game_name = "Chase Chase Jokers",
.category = "Game Options", .category = "Game Options",
}, },
{
.title = "MFG Arguments Override",
.name = "mfgargs",
.desc = "Command line arguments passed to the game.",
.type = OptionType::Text,
.setting_name = "",
.game_name = "Mahjong Fight Girl",
.category = "Game Options (Advanced)",
},
{
.title = "MFG Cabinet Type",
.name = "mfgcabtype",
.desc = "MFG Cabinet Type. Default is HG.",
.type = OptionType::Enum,
.setting_name = "",
.game_name = "Mahjong Fight Girl",
.category = "Game Options (Advanced)",
.elements = {
{"HG", "HG"},
{"B", "B"},
{"C", "C"},
{"UKS", "UKS"},
},
},
{
.title = "MFG Disable IO Emulation",
.name = "mfgnoio",
.desc = "Disables BI2X hooks for MFG",
.type = OptionType::Bool,
.setting_name = "",
.game_name = "Mahjong Fight Girl",
.category = "Game Options (Advanced)"
},
{ {
// spice2x_LightsOverallBrightness // spice2x_LightsOverallBrightness
.title = "Lights Brightness Adjustment", .title = "Lights Brightness Adjustment",
+4
View File
@@ -114,6 +114,7 @@ namespace launcher {
LoadBusouShinkiModule, LoadBusouShinkiModule,
LoadCCJModule, LoadCCJModule,
LoadQKSModule, LoadQKSModule,
LoadMFGModule,
LoadMusecaModule, LoadMusecaModule,
PathToModules, PathToModules,
ScreenshotFolder, ScreenshotFolder,
@@ -193,6 +194,9 @@ namespace launcher {
CCJMouseTrackball, CCJMouseTrackball,
CCJMouseTrackballWithToggle, CCJMouseTrackballWithToggle,
CCJTrackballSensitivity, CCJTrackballSensitivity,
MFGArgs,
MFGCabType,
MFGNoIO,
spice2x_LightsOverallBrightness, spice2x_LightsOverallBrightness,
spice2x_WindowBorder, spice2x_WindowBorder,
spice2x_WindowSize, spice2x_WindowSize,
+2 -1
View File
@@ -27,6 +27,7 @@ namespace launcher::signal {
// settings // settings
bool DISABLE = false; bool DISABLE = false;
bool USE_VEH_WORKAROUND = false;
} }
#define V(variant) case variant: return #variant #define V(variant) case variant: return #variant
@@ -167,7 +168,7 @@ static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_hook(
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) { static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
log_info("signal", "AddVectoredExceptionHandler hook hit"); log_info("signal", "AddVectoredExceptionHandler hook hit");
return nullptr; return launcher::signal::USE_VEH_WORKAROUND ? INVALID_HANDLE_VALUE : nullptr;
} }
void launcher::signal::attach() { void launcher::signal::attach() {
+1
View File
@@ -4,6 +4,7 @@ namespace launcher::signal {
// settings // settings
extern bool DISABLE; extern bool DISABLE;
extern bool USE_VEH_WORKAROUND;
//void print_stacktrace(); //void print_stacktrace();
void attach(); void attach();
+2
View File
@@ -653,6 +653,8 @@ void eamuse_autodetect_game() {
eamuse_set_game("Chase Chase Jokers"); eamuse_set_game("Chase Chase Jokers");
else if (avs::game::is_model("UKS")) else if (avs::game::is_model("UKS"))
eamuse_set_game("QuizKnock STADIUM"); eamuse_set_game("QuizKnock STADIUM");
else if (avs::game::is_model("VFG"))
eamuse_set_game("Mahjong Fight Girl");
else { else {
log_warning("eamuse", "unknown game model: {}", avs::game::MODEL); log_warning("eamuse", "unknown game model: {}", avs::game::MODEL);
eamuse_set_game("unknown"); eamuse_set_game("unknown");
+180
View File
@@ -0,0 +1,180 @@
#include "execexe.h"
#include "util/logging.h"
#include "util/libutils.h"
#include "util/utils.h"
#include "util/detour.h"
namespace execexe {
static HMODULE execexe_module = nullptr;
static decltype(&LoadLibraryW) execexe_LoadLibraryW = nullptr;
static decltype(&GetModuleHandleW) execexe_GetModuleHandleW = nullptr;
static decltype(&GetProcAddress) execexe_GetProcAddress = nullptr;
static decltype(&CreateFileA) execexe_CreateFileA = nullptr;
static decltype(&CreateFileW) execexe_CreateFileW = nullptr;
static decltype(&CloseHandle) execexe_CloseHandle = nullptr;
static std::wstring plugins_dir;
static acioemu::ACIOHandle *acio = nullptr;
static std::wstring port_name;
static bool port_opened = false;
static HANDLE WINAPI execexe_CreateFileA_hook(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
const auto lpFileNameW = s2ws(lpFileName);
if (lpFileNameW == port_name) {
if (!port_opened) {
port_opened = acio->open(port_name.c_str());
} else {
log_info("execexe", "ignored handle open. ({})", ws2s(port_name));
}
SetLastError(0);
return (HANDLE) acio;
}
return execexe_CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
static HANDLE WINAPI execexe_CreateFileW_hook(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) {
if (lpFileName == port_name && acio->open(lpFileName)) {
if (!port_opened) {
port_opened = acio->open(port_name.c_str());
} else {
log_info("execexe", "ignored handle open. ({})", ws2s(port_name));
}
SetLastError(0);
return (HANDLE) acio;
} else {
return execexe_CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
}
static WINBOOL WINAPI execexe_CloseHandle_hook(HANDLE hObject) {
if (hObject == acio && port_opened) {
log_info("execexe", "ignored handle close. ({})", ws2s(port_name));
return TRUE;
}
return execexe_CloseHandle(hObject);
}
HMODULE init() {
execexe_module = libutils::load_library("execexe.dll");
execexe_LoadLibraryW = libutils::get_proc<decltype(&LoadLibraryW)>(execexe_module, MAKEINTRESOURCE(34));
execexe_GetModuleHandleW = libutils::get_proc<decltype(&GetModuleHandleW)>(execexe_module, MAKEINTRESOURCE(25));
execexe_GetProcAddress = libutils::get_proc<decltype(&GetProcAddress)>(execexe_module, MAKEINTRESOURCE(27));
execexe_CloseHandle = libutils::get_proc<decltype(&CloseHandle)>(execexe_module, MAKEINTRESOURCE(7));
execexe_CreateFileA = libutils::get_proc<decltype(&CreateFileA)>(execexe_module, MAKEINTRESOURCE(9));
execexe_CreateFileW = libutils::get_proc<decltype(&CreateFileW)>(execexe_module, MAKEINTRESOURCE(11));
auto module_path = libutils::module_file_name(nullptr);
module_path = module_path.replace_extension("");
module_path = module_path.replace_filename(module_path.filename().wstring() + L"_Data");
plugins_dir = (module_path / L"Plugins" / L"x86_64").wstring() + L"\\";
return execexe_module;
}
void init_port_hook(const std::wstring &portName, acioemu::ACIOHandle *acioHandle) {
static bool init = false;
if (init)
return;
init = true;
port_name = portName;
acio = acioHandle;
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(7),
execexe_CloseHandle_hook, &execexe_CloseHandle);
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9),
execexe_CreateFileA_hook, &execexe_CreateFileA);
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
execexe_CreateFileW_hook, &execexe_CreateFileW);
}
HMODULE load_library(const char *module_name, bool fatal) {
std::wstring module_name_w = s2ws(module_name);
std::wstring plugin_path = plugins_dir + module_name_w;
HMODULE module = execexe_LoadLibraryW(plugin_path.c_str());
if (module != nullptr) {
return module;
}
module = execexe_LoadLibraryW(module_name_w.c_str());
if (module != nullptr) {
return module;
}
if (fatal) {
log_fatal("execexe", "failed to load library {}", module_name);
}
return nullptr;
}
HMODULE get_module(const char *module_name, bool fatal) {
std::wstring module_name_w = s2ws(module_name);
std::wstring plugin_path = plugins_dir + module_name_w;
HMODULE module = execexe_GetModuleHandleW(plugin_path.c_str());
if (module != nullptr) {
return module;
}
module = execexe_GetModuleHandleW(module_name_w.c_str());
if (module != nullptr) {
return module;
}
if (fatal) {
log_fatal("execexe", "failed to get module {}", module_name);
}
return nullptr;
}
FARPROC get_proc(HMODULE module, const char *proc_name, bool fatal) {
FARPROC proc = execexe_GetProcAddress(module, proc_name);
if (proc != nullptr) {
return proc;
}
if (fatal) {
log_fatal("execexe", "proc {} not found", proc_name);
}
return nullptr;
}
bool trampoline(const char *dll, const char *func, void *hook, void **orig) {
HMODULE module = get_module(dll);
FARPROC proc = get_proc(module, func);
return detour::trampoline(
reinterpret_cast<void *>(proc),
hook,
orig
);
}
bool trampoline_try(const char *dll, const char *func, void *hook, void **orig) {
HMODULE module = get_module(dll, false);
if (module == nullptr) {
return false;
}
FARPROC proc = get_proc(module, func, false);
if (proc == nullptr) {
return false;
}
return detour::trampoline(
reinterpret_cast<void *>(proc),
hook,
orig
);
}
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <string>
#include <windows.h>
#include "acioemu/handle.h"
namespace execexe {
HMODULE init();
void init_port_hook(const std::wstring &portName, acioemu::ACIOHandle *acioHandle);
HMODULE load_library(const char *module_name, bool fatal = true);
FARPROC get_proc(HMODULE module, const char *proc_name, bool fatal = true);
HMODULE get_module(const char *module_name, bool fatal = true);
bool trampoline(const char *dll, const char *func, void *hook, void **orig);
bool trampoline_try(const char *dll, const char *func, void *hook, void **orig);
template<typename T>
inline T get_proc(HMODULE module, const char *proc_name, bool fatal = true) {
return reinterpret_cast<T>(get_proc(module, proc_name, fatal));
}
template<typename T>
inline bool trampoline(const char *dll, const char *func, T hook, T *orig) {
return trampoline(
dll,
func,
reinterpret_cast<void *>(hook),
reinterpret_cast<void **>(orig));
}
template<typename T>
inline bool trampoline_try(const char *dll, const char *func, T hook, T *orig) {
return trampoline_try(
dll,
func,
reinterpret_cast<void *>(hook),
reinterpret_cast<void **>(orig));
}
}
+39
View File
@@ -2,6 +2,8 @@
#include "cfg/screen_resize.h" #include "cfg/screen_resize.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "external/fmt/include/fmt/format.h" #include "external/fmt/include/fmt/format.h"
#include "util/detour.h"
#include "util/logging.h"
namespace unity_utils { namespace unity_utils {
@@ -42,4 +44,41 @@ std::string get_unity_player_args() {
return args; return args;
} }
static std::string cmdLine;
static decltype(GetCommandLineA) *GetCommandLineA_orig = nullptr;
static LPSTR WINAPI GetCommandLineA_hook() {
return (LPSTR) cmdLine.c_str();
}
void set_args(const std::string &args) {
static bool init = false;
if (!init) {
init = true;
detour::trampoline_try("kernel32.dll", "GetCommandLineA",
(void*)GetCommandLineA_hook, (void**)&GetCommandLineA_orig);
}
cmdLine = args;
log_info("unity", "unity player args: ```{}```", cmdLine);
}
static bool show = false;
static decltype(ShowCursor) *ShowCursor_orig = nullptr;
static int WINAPI ShowCursor_hook(BOOL bShow) {
return show;
}
void force_show_cursor(bool bShow) {
static bool init = false;
if (!init) {
init = true;
detour::trampoline_try("user32.dll", "ShowCursor",
(void*)ShowCursor_hook, (void**)&ShowCursor_orig);
}
show = bShow;
}
} }
+2
View File
@@ -4,4 +4,6 @@
namespace unity_utils { namespace unity_utils {
std::string get_unity_player_args(); std::string get_unity_player_args();
void set_args(const std::string &args);
void force_show_cursor(bool bShow);
} }