mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c8dc9de32 | |||
| 4694b59cec | |||
| 4f1ada7a2f | |||
| 104a9cbffd | |||
| 45a52cff90 | |||
| bf79b5d2aa | |||
| e74c60f61d | |||
| 89921e5ec2 | |||
| 1b7ebd6fc2 |
@@ -442,6 +442,9 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/qks/qks.cpp
|
||||
games/qks/io.cpp
|
||||
games/qks/bi2x_hook.cpp
|
||||
games/mfg/mfg.cpp
|
||||
games/mfg/io.cpp
|
||||
games/mfg/bi2a_hook.cpp
|
||||
|
||||
# hooks
|
||||
hooks/audio/audio.cpp
|
||||
@@ -587,6 +590,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
util/sysutils.cpp
|
||||
util/lz77.cpp
|
||||
util/tapeled.cpp
|
||||
util/execexe.cpp
|
||||
)
|
||||
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort) {
|
||||
acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount) {
|
||||
this->com_port = lpCOMPort;
|
||||
this->icca_node_count = iccaNodeCount;
|
||||
}
|
||||
|
||||
bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) {
|
||||
@@ -16,7 +17,7 @@ bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) {
|
||||
log_info("acioemu", "Opened {} (ACIO)", ws2s(com_port));
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ namespace acioemu {
|
||||
private:
|
||||
LPCWSTR com_port;
|
||||
|
||||
uint8_t icca_node_count;
|
||||
|
||||
acioemu::ACIOEmu acio_emu;
|
||||
|
||||
public:
|
||||
ACIOHandle(LPCWSTR lpCOMPort);
|
||||
ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount = 2);
|
||||
|
||||
bool open(LPCWSTR lpFileName) override;
|
||||
|
||||
|
||||
@@ -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'))
|
||||
) {
|
||||
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 {
|
||||
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 0x30: // GetBoardProductNumber
|
||||
case 0x31: // GetMicomInfo
|
||||
case 0x3A: // ???
|
||||
case 0x0116: // ???
|
||||
case 0x0120: // ???
|
||||
case 0xFF: // BROADCAST
|
||||
|
||||
@@ -81,16 +81,12 @@ namespace cfg {
|
||||
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
|
||||
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
|
||||
auto& scene = this->scene_settings[i];
|
||||
std::string prefix = "";
|
||||
if (0 < i) {
|
||||
prefix += fmt::format("scenes/{}/", i-1);
|
||||
}
|
||||
const std::string prefix = fmt::format("scenes/{}/", i);
|
||||
load_int_value(doc, root + prefix + "offset_x", scene.offset_x);
|
||||
load_int_value(doc, root + prefix + "offset_y", scene.offset_y);
|
||||
load_float_value(doc, root + prefix + "scale_x", scene.scale_x);
|
||||
load_float_value(doc, root + prefix + "scale_y", scene.scale_y);
|
||||
load_bool_value(doc, root + prefix + "keep_aspect_ratio", scene.keep_aspect_ratio);
|
||||
load_bool_value(doc, root + prefix + "centered", scene.centered);
|
||||
}
|
||||
|
||||
// windowed settings are always under game settings
|
||||
@@ -201,16 +197,12 @@ namespace cfg {
|
||||
rapidjson::Pointer(root + "enable_linear_filter").Set(doc, this->enable_linear_filter);
|
||||
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
|
||||
auto& scene = this->scene_settings[i];
|
||||
std::string prefix = "";
|
||||
if (0 < i) {
|
||||
prefix += fmt::format("scenes/{}/", i-1);
|
||||
}
|
||||
const std::string prefix = fmt::format("scenes/{}/", i);
|
||||
rapidjson::Pointer(root + prefix + "offset_x").Set(doc, scene.offset_x);
|
||||
rapidjson::Pointer(root + prefix + "offset_y").Set(doc, scene.offset_y);
|
||||
rapidjson::Pointer(root + prefix + "scale_x").Set(doc, scene.scale_x);
|
||||
rapidjson::Pointer(root + prefix + "scale_y").Set(doc, scene.scale_y);
|
||||
rapidjson::Pointer(root + prefix + "keep_aspect_ratio").Set(doc, scene.keep_aspect_ratio);
|
||||
rapidjson::Pointer(root + prefix + "centered").Set(doc, scene.centered);
|
||||
}
|
||||
|
||||
// windowed mode settings
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace cfg {
|
||||
float scale_x = 1.0;
|
||||
float scale_y = 1.0;
|
||||
bool keep_aspect_ratio = true;
|
||||
bool centered = true;
|
||||
};
|
||||
|
||||
extern std::optional<std::string> SCREEN_RESIZE_CFG_PATH_OVERRIDE;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
03/29/2025 [spice2x]
|
||||
Add SDVX landscape mode, -forceresswap option
|
||||
Fix bugs in image scaler
|
||||
Configurator UI tweaks
|
||||
|
||||
03/25/2025 [spice2x]
|
||||
Add Mahjong Fight Girl support
|
||||
Add option for custom full screen resolution (-forceres)
|
||||
|
||||
03/24/2025 [spice2x]
|
||||
Auto PIN entry
|
||||
Multiple scenes for image resize
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "bc/io.h"
|
||||
#include "ccj/io.h"
|
||||
#include "qks/io.h"
|
||||
#include "mfg/io.h"
|
||||
|
||||
namespace games {
|
||||
|
||||
@@ -305,6 +306,13 @@ namespace games {
|
||||
buttons.insert({ qks, qks::get_buttons() });
|
||||
buttons_help.insert({ qks, qks::get_buttons_help() });
|
||||
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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::mfg {
|
||||
void bi2a_hook_init();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#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"
|
||||
);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::mfg::get_buttons_help() {
|
||||
return "";
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "cfg/api.h"
|
||||
|
||||
namespace games::mfg {
|
||||
namespace Buttons {
|
||||
enum {
|
||||
Service,
|
||||
Test,
|
||||
CoinMech
|
||||
};
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -11,7 +11,9 @@ namespace games::sdvx {
|
||||
enum SdvxOverlayPosition {
|
||||
SDVX_OVERLAY_TOP,
|
||||
SDVX_OVERLAY_MIDDLE,
|
||||
SDVX_OVERLAY_BOTTOM
|
||||
SDVX_OVERLAY_BOTTOM,
|
||||
SDVX_OVERLAY_BOTTOM_LEFT,
|
||||
SDVX_OVERLAY_BOTTOM_RIGHT
|
||||
};
|
||||
|
||||
// settings
|
||||
|
||||
@@ -680,7 +680,29 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
|
||||
// dump presentation parameters
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
const auto *params = &pPresentationParameters[i];
|
||||
auto *params = &pPresentationParameters[i];
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
|
||||
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
|
||||
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"use custom resolution {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"swap full orientation {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
params->BackBufferHeight, params->BackBufferWidth);
|
||||
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
||||
}
|
||||
}
|
||||
|
||||
log_info("graphics::d3d9",
|
||||
"D3D9 presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
|
||||
@@ -846,7 +868,29 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
const auto *params = &pPresentationParameters[i];
|
||||
auto *params = &pPresentationParameters[i];
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
|
||||
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
|
||||
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"use custom resolution {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"swap full orientation {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
params->BackBufferHeight, params->BackBufferWidth);
|
||||
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
||||
}
|
||||
}
|
||||
|
||||
log_info("graphics::d3d9",
|
||||
"D3D9Ex presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
|
||||
@@ -870,7 +914,16 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
}
|
||||
if (pFullscreenDisplayMode) {
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
const auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
|
||||
auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
|
||||
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
|
||||
}
|
||||
}
|
||||
|
||||
log_info("graphics::d3d9",
|
||||
"D3D9Ex fullscreen display mode for adapter {}: Width: {}, Height: {}, RefreshRate: {}, "
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/flags_helper.h"
|
||||
#include "util/utils.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
|
||||
#include "d3d9_backend.h"
|
||||
@@ -692,89 +693,181 @@ static IDirect3DSurface9 *backbuffer = nullptr;
|
||||
static LPDIRECT3DSWAPCHAIN9 mSwapChain = nullptr;
|
||||
static IDirect3DTexture9* tex;
|
||||
|
||||
static UINT topSurface_width = 0;
|
||||
static UINT topSurface_height = 0;
|
||||
static float topSurface_aspect_ratio = 1.f;
|
||||
|
||||
void SurfaceHook(IDirect3DDevice9 *pReal) {
|
||||
// log_misc("graphics::d3d9", "SurfaceHook called");
|
||||
D3DPRESENT_PARAMETERS param {};
|
||||
|
||||
pReal->GetSwapChain(0, &mSwapChain);
|
||||
mSwapChain->GetPresentParameters(¶m);
|
||||
|
||||
// phase 0 - create a surface that has the same aspect ratio as the original back buffer
|
||||
// but larger in size. this is needed to ensure that when the user zooms out, we have
|
||||
// sufficient buffer space (black border) around the original image
|
||||
//
|
||||
// (only done once)
|
||||
if (!topSurface) {
|
||||
if (pReal->CreateTexture(4096, 4096, 1,
|
||||
topSurface_width = param.BackBufferWidth * 3;
|
||||
topSurface_height = param.BackBufferHeight * 3;
|
||||
topSurface_aspect_ratio = (float)topSurface_width / (float)topSurface_height;
|
||||
if (pReal->CreateTexture(topSurface_width, topSurface_height, 1,
|
||||
D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,
|
||||
D3DPOOL_DEFAULT, &tex, NULL) != D3D_OK) {
|
||||
log_misc("graphics::d3d9", "create texture failed");
|
||||
|
||||
log_warning("graphics::d3d9", "SurfaceHook - create texture failed");
|
||||
}
|
||||
log_misc("graphics::d3d9", "Backbuffer: {} {} {}",
|
||||
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
|
||||
tex->GetSurfaceLevel(0, &topSurface);
|
||||
|
||||
log_misc(
|
||||
"graphics::d3d9", "SurfaceHook - Backbuffer: {} {} {}",
|
||||
param.BackBufferWidth, param.BackBufferHeight, param.BackBufferCount);
|
||||
|
||||
tex->GetSurfaceLevel(0, &topSurface);
|
||||
if (mSwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backbuffer) != D3D_OK) {
|
||||
log_misc("graphics::d3d9", "GetBackBuffer failed");
|
||||
log_warning("graphics::d3d9", "SurfaceHook - GetBackBuffer failed");
|
||||
}
|
||||
}
|
||||
|
||||
const int rectLeft = 1024;
|
||||
const int rectTop = 576;
|
||||
// pre-calculate dimensions used for phase 1
|
||||
const int rectLeft = param.BackBufferWidth;
|
||||
const int rectTop = param.BackBufferHeight;
|
||||
const int w = param.BackBufferWidth;
|
||||
const int h = param.BackBufferHeight;
|
||||
|
||||
D3DLOCKED_RECT rect;
|
||||
HRESULT hr;
|
||||
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
|
||||
|
||||
// phase 1 - copy the original back buffer onto the new surface.
|
||||
// we draw it 1:1 in the center of the surface.
|
||||
// if orientation is swapped, fix up the rect and draw it in the center.
|
||||
//
|
||||
// for this phase we always render with the same rect so that it's always overwritten
|
||||
// by a new frame on the same spot.
|
||||
RECT originRect {
|
||||
rectLeft,
|
||||
rectTop,
|
||||
(LONG)(rectLeft + w),
|
||||
(LONG)(rectTop + h),
|
||||
};
|
||||
if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
originRect.left = (topSurface_width / 2) - (GRAPHICS_FS_ORIGINAL_WIDTH / 2);
|
||||
originRect.top = (topSurface_height / 2) - (GRAPHICS_FS_ORIGINAL_HEIGHT / 2);
|
||||
originRect.right = originRect.left + GRAPHICS_FS_ORIGINAL_WIDTH;
|
||||
originRect.bottom = originRect.top + GRAPHICS_FS_ORIGINAL_HEIGHT;
|
||||
}
|
||||
|
||||
// log_misc(
|
||||
// "graphics::d3d9", "originRect: {} {} {} {}",
|
||||
// originRect.left, originRect.top, originRect.right, originRect.bottom);
|
||||
|
||||
hr = pReal->StretchRect(
|
||||
backbuffer, nullptr,
|
||||
topSurface, &originRect,
|
||||
D3DTEXF_LINEAR);
|
||||
if (hr != D3D_OK) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"SurfaceHook - StretchRect backbuffer failed, rect: {} {} {} {}",
|
||||
originRect.left, originRect.top, originRect.right, originRect.bottom);
|
||||
}
|
||||
topSurface->UnlockRect();
|
||||
|
||||
// phase 2 - viewport calculation - do the actual zoom / offset math
|
||||
// figure out what region of the surface to copy back to the back buffer.
|
||||
RECT targetRect {
|
||||
rectLeft,
|
||||
rectTop,
|
||||
(LONG)(rectLeft + w),
|
||||
(LONG)(rectTop + h),
|
||||
};
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
|
||||
auto w_new = w / scene.scale_x;
|
||||
auto h_new = h / scene.scale_y;
|
||||
|
||||
// stretch to add top/left offset to avoid going negative
|
||||
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
|
||||
auto hr = pReal->StretchRect(
|
||||
backbuffer, nullptr,
|
||||
topSurface, &targetRect,
|
||||
D3DTEXF_LINEAR);
|
||||
// make sure the scaling is within bounds
|
||||
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
|
||||
if (w_new > topSurface_width || h_new > topSurface_height) {
|
||||
w_new = topSurface_width;
|
||||
h_new = topSurface_height;
|
||||
}
|
||||
} else {
|
||||
w_new = MIN(w_new, topSurface_width);
|
||||
h_new = MIN(h_new, topSurface_height);
|
||||
}
|
||||
|
||||
if (hr != D3D_OK) {
|
||||
log_misc("graphics::d3d9", "StretchRect backbuffer failed");
|
||||
}
|
||||
topSurface->UnlockRect();
|
||||
targetRect.left = (topSurface_width / 2) - (w_new / 2) - scene.offset_x;
|
||||
targetRect.top = (topSurface_height / 2) - (h_new / 2) - scene.offset_y;
|
||||
targetRect.right = targetRect.left + w_new;
|
||||
targetRect.bottom = targetRect.top + h_new;
|
||||
|
||||
// do the actual zoom / offset math
|
||||
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
|
||||
if (scene.centered) {
|
||||
targetRect.right = (w + rectLeft) / scene.scale_x;
|
||||
targetRect.bottom = (h + rectTop) / scene.scale_y;
|
||||
const LONG deltaH = ((targetRect.bottom - targetRect.top) - h) / 2;
|
||||
const LONG deltaW = ((targetRect.right - targetRect.left) - w) / 2;
|
||||
targetRect.top -= deltaH;
|
||||
targetRect.bottom -= deltaH;
|
||||
targetRect.left -= deltaW;
|
||||
targetRect.right -= deltaW;
|
||||
} else {
|
||||
targetRect.left -= scene.offset_x;
|
||||
targetRect.top += scene.offset_y;
|
||||
targetRect.right = -scene.offset_x;
|
||||
targetRect.right += (w + rectLeft) / scene.scale_x;
|
||||
targetRect.bottom = scene.offset_y;
|
||||
targetRect.bottom += (h + rectTop) / scene.scale_y;
|
||||
// boundary check to prevent StretchRect failures
|
||||
if (targetRect.left < 0) {
|
||||
targetRect.left = 0;
|
||||
targetRect.right = w_new;
|
||||
} else if (targetRect.right > (LONG)topSurface_width) {
|
||||
targetRect.left = topSurface_width - w_new;
|
||||
targetRect.right = topSurface_width;
|
||||
}
|
||||
if (targetRect.top < 0) {
|
||||
targetRect.top = 0;
|
||||
targetRect.bottom = h_new;
|
||||
} else if (targetRect.bottom > (LONG)topSurface_height) {
|
||||
targetRect.top = topSurface_height - h_new;
|
||||
targetRect.bottom = topSurface_height;
|
||||
}
|
||||
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
// increase the viewport to fit the image on screen (effectively, zoom out a little)
|
||||
if (GRAPHICS_FS_ORIGINAL_WIDTH < GRAPHICS_FS_ORIGINAL_HEIGHT) {
|
||||
// portrait game on landscape display
|
||||
// height should match the original image
|
||||
targetRect.top = originRect.top;
|
||||
targetRect.bottom = originRect.bottom;
|
||||
|
||||
// width of viewport should be wider, effectively shrinking the image on x-axis when rendered
|
||||
const auto w_new = GRAPHICS_FS_ORIGINAL_HEIGHT * topSurface_aspect_ratio;
|
||||
targetRect.left = (topSurface_width / 2) - (w_new / 2);
|
||||
targetRect.right = targetRect.left + w_new;
|
||||
} else {
|
||||
// landscape game on portrait display
|
||||
targetRect.left = originRect.left;
|
||||
targetRect.right = originRect.right;
|
||||
|
||||
// height of viewport should be taller, effectively shrinking the image on y-axis when rendered
|
||||
const auto h_new = GRAPHICS_FS_ORIGINAL_WIDTH / topSurface_aspect_ratio;
|
||||
targetRect.top = (topSurface_height / 2) - (h_new / 2);
|
||||
targetRect.bottom = targetRect.top + h_new;
|
||||
}
|
||||
}
|
||||
|
||||
// draw to back buffer
|
||||
// log_misc("graphics::d3d9", "targetRect: {} {} {} {}",
|
||||
// targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
|
||||
|
||||
// phase 3 - draw the surface to back buffer
|
||||
backbuffer->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
|
||||
bool use_linear_filter = true;
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||
use_linear_filter = cfg::SCREENRESIZE->enable_linear_filter;
|
||||
}
|
||||
hr = pReal->StretchRect(
|
||||
topSurface, &targetRect,
|
||||
backbuffer, nullptr,
|
||||
cfg::SCREENRESIZE->enable_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
|
||||
use_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
|
||||
backbuffer->UnlockRect();
|
||||
if (hr != D3D_OK) {
|
||||
log_misc("graphics::d3d9", "StretchRect targetRect failed");
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
"SurfaceHook - StretchRect targetRect failed, you must reset image scaling to default values! rect: {} {} {} {}",
|
||||
targetRect.left, targetRect.top, targetRect.right, targetRect.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::EndScene() {
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize || GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
SurfaceHook(pReal);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,10 @@ graphics_dx9on12_state GRAPHICS_9_ON_12_STATE = DX9ON12_AUTO;
|
||||
bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false;
|
||||
bool SUBSCREEN_FORCE_REDRAW = false;
|
||||
bool D3D9_DEVICE_HOOK_DISABLE = false;
|
||||
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
|
||||
bool GRAPHICS_FS_ORIENTATION_SWAP = false;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
|
||||
|
||||
// settings
|
||||
std::string GRAPHICS_DEVICEID = "PCI\\VEN_1002&DEV_7146";
|
||||
|
||||
@@ -35,6 +35,10 @@ extern UINT GRAPHICS_FORCE_REFRESH;
|
||||
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
|
||||
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
|
||||
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
|
||||
extern bool GRAPHICS_FS_ORIENTATION_SWAP;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT;
|
||||
|
||||
extern graphics_dx9on12_state GRAPHICS_9_ON_12_STATE;
|
||||
extern bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME;
|
||||
|
||||
@@ -123,10 +123,6 @@ void graphics_load_windowed_parameters() {
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_load_windowed_parameters called");
|
||||
const auto remove_spaces = [](const char& c) {
|
||||
return c == ' ';
|
||||
};
|
||||
|
||||
if (GRAPHICS_WINDOW_STYLE.has_value()) {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
@@ -148,13 +144,11 @@ void graphics_load_windowed_parameters() {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_POS");
|
||||
int32_t x, y;
|
||||
auto s = GRAPHICS_WINDOW_POS.value();
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(GRAPHICS_WINDOW_POS.value(), result)) {
|
||||
cfg::SCREENRESIZE->enable_window_resize = true;
|
||||
cfg::SCREENRESIZE->window_offset_x = x;
|
||||
cfg::SCREENRESIZE->window_offset_y = y;
|
||||
cfg::SCREENRESIZE->window_offset_x = result.first;
|
||||
cfg::SCREENRESIZE->window_offset_y = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -windowpos");
|
||||
}
|
||||
@@ -174,20 +168,16 @@ void graphics_load_windowed_subscreen_parameters() {
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_load_windowed_subscreen_parameters called");
|
||||
const auto remove_spaces = [](const char& c) {
|
||||
return c == ' ';
|
||||
};
|
||||
|
||||
|
||||
if (GRAPHICS_IIDX_WSUB_SIZE.has_value()) {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_SIZE");
|
||||
uint32_t w, h;
|
||||
auto s = GRAPHICS_IIDX_WSUB_SIZE.value();
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) {
|
||||
GRAPHICS_IIDX_WSUB_WIDTH = w;
|
||||
GRAPHICS_IIDX_WSUB_HEIGHT = h;
|
||||
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(GRAPHICS_IIDX_WSUB_SIZE.value(), result)) {
|
||||
GRAPHICS_IIDX_WSUB_WIDTH = result.first;
|
||||
GRAPHICS_IIDX_WSUB_HEIGHT = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubsize");
|
||||
}
|
||||
@@ -197,12 +187,11 @@ void graphics_load_windowed_subscreen_parameters() {
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_load_windowed_parameters - load GRAPHICS_IIDX_WSUB_POS");
|
||||
int32_t x, y;
|
||||
auto s = GRAPHICS_IIDX_WSUB_POS.value();
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) {
|
||||
GRAPHICS_IIDX_WSUB_X = x;
|
||||
GRAPHICS_IIDX_WSUB_Y = y;
|
||||
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(GRAPHICS_IIDX_WSUB_POS.value(), result)) {
|
||||
GRAPHICS_IIDX_WSUB_X = result.first;
|
||||
GRAPHICS_IIDX_WSUB_Y = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubpos");
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
#include "games/ccj/ccj.h"
|
||||
#include "games/ccj/trackball.h"
|
||||
#include "games/qks/qks.h"
|
||||
#include "games/mfg/mfg.h"
|
||||
#include "games/museca/museca.h"
|
||||
#include "hooks/avshook.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
@@ -101,6 +102,7 @@
|
||||
#include "util/sysutils.h"
|
||||
#include "util/tapeled.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "avs/ssl.h"
|
||||
#include "nvapi/nvapi.h"
|
||||
#include "hooks/graphics/nvapi_hook.h"
|
||||
@@ -213,6 +215,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
bool attach_bc = false;
|
||||
bool attach_ccj = false;
|
||||
bool attach_qks = false;
|
||||
bool attach_mfg = false;
|
||||
bool attach_museca = false;
|
||||
|
||||
// misc settings
|
||||
@@ -436,7 +439,12 @@ int main_implementation(int argc, char *argv[]) {
|
||||
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_TOP;
|
||||
} else if (txt == "center") {
|
||||
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_MIDDLE;
|
||||
} else if (txt == "bottomleft") {
|
||||
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_BOTTOM_LEFT;
|
||||
} else if (txt == "bottomright") {
|
||||
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_BOTTOM_RIGHT;
|
||||
}
|
||||
// else - bottom
|
||||
}
|
||||
if (options[launcher::Options::spice2x_SDVXSubRedraw].value_bool()) {
|
||||
SUBSCREEN_FORCE_REDRAW = true;
|
||||
@@ -602,6 +610,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::LoadQKSModule].value_bool()) {
|
||||
attach_qks = true;
|
||||
}
|
||||
if (options[launcher::Options::LoadMFGModule].value_bool()) {
|
||||
attach_mfg = true;
|
||||
}
|
||||
if (options[launcher::Options::LoadMusecaModule].value_bool()) {
|
||||
attach_museca = true;
|
||||
}
|
||||
@@ -947,12 +958,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
GRAPHICS_WINDOW_STYLE = options[launcher::Options::spice2x_WindowBorder].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::spice2x_WindowSize].is_active()) {
|
||||
auto s = options[launcher::Options::spice2x_WindowSize].value_text();
|
||||
uint32_t w, h;
|
||||
const auto remove_spaces = [](const char& c) { return c == ' '; };
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) {
|
||||
GRAPHICS_WINDOW_SIZE = std::pair(w, h);
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::spice2x_WindowSize].value_text(), result)) {
|
||||
GRAPHICS_WINDOW_SIZE = result;
|
||||
} else {
|
||||
log_warning("launcher", "failed to parse -windowsize");
|
||||
}
|
||||
@@ -1028,6 +1036,15 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::QKSArgs].is_active()) {
|
||||
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()) {
|
||||
rawinput::ENABLE_SMX_STAGE = true;
|
||||
}
|
||||
@@ -1139,6 +1156,26 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
log_info("launcher", "arguments:\n{}", arguments.str());
|
||||
|
||||
if (options[launcher::Options::FullscreenResolution].is_active()) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION = result;
|
||||
} else {
|
||||
log_warning("launcher", "failed to parse -forceres");
|
||||
}
|
||||
}
|
||||
|
||||
if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !GRAPHICS_WINDOWED) {
|
||||
#if SPICE64
|
||||
GRAPHICS_FS_ORIENTATION_SWAP = true;
|
||||
#else
|
||||
log_warning("launcher", "-sdvxlandscape is not supported in 32-bit SDVX, ignoring...");
|
||||
#endif
|
||||
}
|
||||
if (options[launcher::Options::FullscreenOrientationFlip].value_bool() && !GRAPHICS_WINDOWED) {
|
||||
GRAPHICS_FS_ORIENTATION_SWAP = true;
|
||||
}
|
||||
|
||||
// deleted options
|
||||
if (options[launcher::Options::OpenKFControl].value_bool()) {
|
||||
log_fatal("launcher", "KFControl has been removed from spice2x; please use an older version.");
|
||||
@@ -1589,6 +1626,19 @@ int main_implementation(int argc, char *argv[]) {
|
||||
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
|
||||
if (check_dll("kamunity.dll") && fileutils::file_exists("game/bsac_app.exe")) {
|
||||
avs::game::DLL_NAME = "kamunity.dll";
|
||||
@@ -1740,6 +1790,10 @@ int main_implementation(int argc, char *argv[]) {
|
||||
avs::core::set_default_heap_size("kamunity.dll");
|
||||
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
|
||||
if (user_heap_size > 0) {
|
||||
|
||||
@@ -19,6 +19,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
|
||||
"Network",
|
||||
"Overlay",
|
||||
"Graphics (Common)",
|
||||
"Graphics (Full Screen)",
|
||||
"Graphics (Windowed)",
|
||||
"Audio",
|
||||
};
|
||||
@@ -189,6 +190,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Integer,
|
||||
.category = "Graphics (Common)",
|
||||
},
|
||||
{
|
||||
.title = "Only Use One Monitor",
|
||||
.name = "graphics-force-single-adapter",
|
||||
.desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n"
|
||||
"May cause unstable framerate and desyncs, especially if monitors have different refresh rates!",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Graphics (Full Screen)",
|
||||
},
|
||||
{
|
||||
.title = "Force Refresh Rate",
|
||||
.name = "graphics-force-refresh",
|
||||
@@ -197,12 +206,28 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.category = "Graphics (Common)",
|
||||
},
|
||||
{
|
||||
.title = "Only Use One Monitor",
|
||||
.name = "graphics-force-single-adapter",
|
||||
.desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n"
|
||||
"May cause unstable framerate and desyncs, especially if monitors have different refresh rates!",
|
||||
// FullscreenResolution
|
||||
.title = "Force Full Screen Resolution",
|
||||
.name = "forceres",
|
||||
.desc =
|
||||
"For full screen mode, forcibly set a custom resolution.\n\n"
|
||||
"Works great for some games, but can COMPLETELY BREAK other games - YMMV!\n\n"
|
||||
"If you are using -sdvxlandscape, put the TARGET monitor resolution in this field.\n\n"
|
||||
"This should only be used as last resort if your GPU/monitor can't display the resolution required by the game",
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "1280,720",
|
||||
.category = "Graphics (Full Screen)"
|
||||
},
|
||||
{
|
||||
// FullscreenOrientationFlip
|
||||
.title = "Full Screen Orientation Swap",
|
||||
.name = "forceresswap",
|
||||
.desc =
|
||||
"Allows you to play portrait games in in landscape (and vice versa) by transposing resolution and applying image scaling.\n\n"
|
||||
"Works great for some games, but can COMPLETELY BREAK other games - YMMV!\n\n"
|
||||
"Strongly consider combining this with -forceres option to render at monitor native resolution",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Graphics (Common)",
|
||||
.category = "Graphics (Full Screen)"
|
||||
},
|
||||
{
|
||||
// Graphics9On12
|
||||
@@ -733,7 +758,13 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Enum,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Overlay",
|
||||
.elements = {{"top", ""}, {"center", ""}, {"bottom", ""}},
|
||||
.elements = {
|
||||
{"top", ""},
|
||||
{"center", ""},
|
||||
{"bottom", ""},
|
||||
{"bottomleft", "for landscape"},
|
||||
{"bottomright", "for landscape"},
|
||||
},
|
||||
},
|
||||
{
|
||||
// spice2x_SDVXSubRedraw
|
||||
@@ -1006,6 +1037,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.game_name = "QuizKnock STADIUM",
|
||||
.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
|
||||
.title = "Force Load Museca",
|
||||
@@ -1671,6 +1711,39 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.game_name = "Chase Chase Jokers",
|
||||
.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",
|
||||
.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
|
||||
.title = "Lights Brightness Adjustment",
|
||||
@@ -1741,7 +1814,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.title = "Window Forced Render Scaling",
|
||||
.name = "windowscale",
|
||||
.desc = "For windowed mode: forcibly set DX9 back buffer dimensions to match window size. "
|
||||
"Reduces pixelated scaling artifacts. Works great on some games, but completely broken on others",
|
||||
"Reduces pixelated scaling artifacts. Works great for some games, but can COMPLETELY BREAK other games - YMMV!",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Graphics (Windowed)",
|
||||
},
|
||||
@@ -1971,6 +2044,18 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
// SDVXFullscreenLandscape
|
||||
.title = "SDVX Landscape Mode (SDVX5+)",
|
||||
.name = "sdvxlandscape",
|
||||
.desc =
|
||||
"Allows you to play in landscape by transposing resolution and applying image scaling.\n\n"
|
||||
"Works only for SDVX5 and above! This is identical to -forceorientation.\n\n"
|
||||
"Will launch at 1080p by default; strongly consider combining this with -forceres option to render at monitor native resolution",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options"
|
||||
},
|
||||
{
|
||||
// spice2x_EnableSMXStage
|
||||
.title = "StepManiaX Stage Lighting Support",
|
||||
|
||||
@@ -27,8 +27,10 @@ namespace launcher {
|
||||
CaptureCursor,
|
||||
ShowCursor,
|
||||
DisplayAdapter,
|
||||
GraphicsForceRefresh,
|
||||
GraphicsForceSingleAdapter,
|
||||
GraphicsForceRefresh,
|
||||
FullscreenResolution,
|
||||
FullscreenOrientationFlip,
|
||||
Graphics9On12,
|
||||
spice2x_Dx9On12,
|
||||
NoLegacy,
|
||||
@@ -114,6 +116,7 @@ namespace launcher {
|
||||
LoadBusouShinkiModule,
|
||||
LoadCCJModule,
|
||||
LoadQKSModule,
|
||||
LoadMFGModule,
|
||||
LoadMusecaModule,
|
||||
PathToModules,
|
||||
ScreenshotFolder,
|
||||
@@ -193,6 +196,9 @@ namespace launcher {
|
||||
CCJMouseTrackball,
|
||||
CCJMouseTrackballWithToggle,
|
||||
CCJTrackballSensitivity,
|
||||
MFGArgs,
|
||||
MFGCabType,
|
||||
MFGNoIO,
|
||||
spice2x_LightsOverallBrightness,
|
||||
spice2x_WindowBorder,
|
||||
spice2x_WindowSize,
|
||||
@@ -217,6 +223,7 @@ namespace launcher {
|
||||
spice2x_NoNVAPI,
|
||||
spice2x_NoD3D9DeviceHook,
|
||||
spice2x_SDVXNoSub,
|
||||
SDVXFullscreenLandscape,
|
||||
spice2x_EnableSMXStage,
|
||||
spice2x_EnableSMXDedicab,
|
||||
IIDXRecQuality,
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace launcher::signal {
|
||||
|
||||
// settings
|
||||
bool DISABLE = false;
|
||||
bool USE_VEH_WORKAROUND = false;
|
||||
}
|
||||
|
||||
#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) {
|
||||
log_info("signal", "AddVectoredExceptionHandler hook hit");
|
||||
|
||||
return nullptr;
|
||||
return launcher::signal::USE_VEH_WORKAROUND ? INVALID_HANDLE_VALUE : nullptr;
|
||||
}
|
||||
|
||||
void launcher::signal::attach() {
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace launcher::signal {
|
||||
|
||||
// settings
|
||||
extern bool DISABLE;
|
||||
extern bool USE_VEH_WORKAROUND;
|
||||
|
||||
//void print_stacktrace();
|
||||
void attach();
|
||||
|
||||
@@ -653,6 +653,8 @@ void eamuse_autodetect_game() {
|
||||
eamuse_set_game("Chase Chase Jokers");
|
||||
else if (avs::game::is_model("UKS"))
|
||||
eamuse_set_game("QuizKnock STADIUM");
|
||||
else if (avs::game::is_model("VFG"))
|
||||
eamuse_set_game("Mahjong Fight Girl");
|
||||
else {
|
||||
log_warning("eamuse", "unknown game model: {}", avs::game::MODEL);
|
||||
eamuse_set_game("unknown");
|
||||
|
||||
@@ -6,36 +6,61 @@
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
const auto fg = ImVec4(0.910f, 0.914f, 0.922f, 1.0f);
|
||||
const auto bg = ImVec4(0.192f, 0.212f, 0.220f, 1.0f);
|
||||
|
||||
void HelpTooltip(const char* desc) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, bg);
|
||||
ImGui::PushStyleColor(ImGuiCol_BorderShadow, bg);
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, bg);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, fg);
|
||||
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
ImGui::TextUnformatted(desc);
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
|
||||
ImGui::PopStyleColor(4);
|
||||
}
|
||||
|
||||
void HelpMarker(const char* desc) {
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
ImGui::TextUnformatted(desc);
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
HelpTooltip(desc);
|
||||
}
|
||||
}
|
||||
|
||||
void WarnTooltip(const char* desc, const char* warn) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, bg);
|
||||
ImGui::PushStyleColor(ImGuiCol_BorderShadow, bg);
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, bg);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, fg);
|
||||
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
if (desc) {
|
||||
ImGui::TextUnformatted(desc);
|
||||
ImGui::TextUnformatted("");
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f));
|
||||
if (warn) {
|
||||
ImGui::TextUnformatted("WARNING:");
|
||||
ImGui::TextUnformatted(warn);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
|
||||
ImGui::PopStyleColor(4);
|
||||
}
|
||||
|
||||
void WarnMarker(const char* desc, const char* warn) {
|
||||
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 1.f, 0.f, 1.f));
|
||||
ImGui::TextDisabled("(!)");
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
if (desc) {
|
||||
ImGui::TextUnformatted(desc);
|
||||
ImGui::TextUnformatted("");
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f));
|
||||
if (warn) {
|
||||
ImGui::TextUnformatted("WARNING:");
|
||||
ImGui::TextUnformatted(warn);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
WarnTooltip(desc, warn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
void HelpTooltip(const char* desc);
|
||||
void HelpMarker(const char* desc);
|
||||
void WarnTooltip(const char* desc, const char* warn);
|
||||
void WarnMarker(const char* desc, const char* warn);
|
||||
void DummyMarker();
|
||||
void Knob(float fraction, float size, float thickness = 2.f,
|
||||
|
||||
@@ -2629,28 +2629,30 @@ namespace overlay::windows {
|
||||
// list entry
|
||||
ImGui::PushID(&option);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::HelpMarker(definition.desc.c_str());
|
||||
ImGui::SameLine();
|
||||
if (option.is_active()) {
|
||||
// active option
|
||||
if (option.disabled || definition.disabled) {
|
||||
ImGui::TextColored(ImVec4(1.f, 0.4f, 0.f, 1.f), "%s", definition.title.c_str());
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1.f, 0.7f, 0.f, 1.f), "%s", definition.title.c_str());
|
||||
ImGui::TextColored(ImVec4(0.f, 1.f, 0.f, 1.f), "%s", definition.title.c_str());
|
||||
}
|
||||
} else if (definition.hidden
|
||||
|| (!definition.game_name.empty() && definition.game_name != this->games_selected_name)) {
|
||||
// wrong game - grayed out
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.f), "%s", definition.title.c_str());
|
||||
} else if (definition.game_name == this->games_selected_name) {
|
||||
ImGui::TextColored(ImVec4(0.8f, 0, 0.8f, 1.f), "%s", definition.title.c_str());
|
||||
} else {
|
||||
// normal text
|
||||
ImGui::Text("%s", definition.title.c_str());
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::HelpTooltip(definition.desc.c_str());
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
if (definition.display_name.empty()) {
|
||||
ImGui::Text("-%s", definition.name.c_str());
|
||||
ImGui::TextDisabled("-%s", definition.name.c_str());
|
||||
} else {
|
||||
ImGui::Text("-%s", definition.display_name.c_str());
|
||||
ImGui::TextDisabled("-%s", definition.display_name.c_str());
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
if (option.disabled || definition.disabled) {
|
||||
@@ -2665,6 +2667,9 @@ namespace overlay::windows {
|
||||
option.value = state ? "/ENABLED" : "";
|
||||
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::HelpTooltip(definition.desc.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OptionType::Integer: {
|
||||
@@ -2691,6 +2696,9 @@ namespace overlay::windows {
|
||||
option.value = buffer;
|
||||
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::HelpTooltip(definition.desc.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OptionType::Hex: {
|
||||
@@ -2724,6 +2732,9 @@ namespace overlay::windows {
|
||||
option.value = buffer;
|
||||
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::HelpTooltip(definition.desc.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OptionType::Text: {
|
||||
@@ -2741,6 +2752,9 @@ namespace overlay::windows {
|
||||
option.value = buffer;
|
||||
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::HelpTooltip(definition.desc.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OptionType::Enum: {
|
||||
@@ -2771,6 +2785,9 @@ namespace overlay::windows {
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::HelpTooltip(definition.desc.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -649,17 +649,9 @@ namespace overlay::windows {
|
||||
|
||||
// first column, part 1: help / caution marker
|
||||
ImGui::TableNextColumn();
|
||||
const std::string description = patch.description;
|
||||
const std::string caution = patch.caution;
|
||||
if (!description.empty() && !caution.empty()) {
|
||||
if (!patch.caution.empty()) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::WarnMarker(description.c_str(), caution.c_str());
|
||||
} else if (!description.empty()) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::HelpMarker(description.c_str());
|
||||
} else if (!caution.empty()) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::WarnMarker(nullptr, caution.c_str());
|
||||
ImGui::WarnMarker(nullptr, patch.caution.c_str());
|
||||
} else {
|
||||
ImGui::DummyMarker();
|
||||
}
|
||||
@@ -705,6 +697,11 @@ namespace overlay::windows {
|
||||
if (style_color_pushed) {
|
||||
ImGui::PopStyleColor(style_color_pushed);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
show_patch_tooltip(patch);
|
||||
}
|
||||
|
||||
// show range after label for integer patches
|
||||
if (patch.type == PatchType::Integer) {
|
||||
ImGui::SameLine();
|
||||
auto& numpatch = patch.patch_number;
|
||||
@@ -741,6 +738,9 @@ namespace overlay::windows {
|
||||
patch.last_status = is_patch_active(patch);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
show_patch_tooltip(patch);
|
||||
}
|
||||
|
||||
// second column, part 2: additional options UI (dropdown, text input)
|
||||
ImGui::SameLine();
|
||||
@@ -767,6 +767,9 @@ namespace overlay::windows {
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
show_patch_tooltip(patch);
|
||||
}
|
||||
} else if (patch.type == PatchType::Integer) {
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
auto& numpatch = patch.patch_number;
|
||||
@@ -780,6 +783,9 @@ namespace overlay::windows {
|
||||
apply_patch(patch, true);
|
||||
config_dirty = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
show_patch_tooltip(patch);
|
||||
}
|
||||
}
|
||||
} else if (patch_status == PatchStatus::Disabled) {
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
@@ -794,6 +800,9 @@ namespace overlay::windows {
|
||||
ImGui::InputInt("##dummy_int_input", &patch.patch_number.value);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
show_patch_tooltip(patch);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
@@ -822,6 +831,14 @@ namespace overlay::windows {
|
||||
}
|
||||
}
|
||||
|
||||
void PatchManager::show_patch_tooltip(const PatchData& patch) {
|
||||
if (!patch.caution.empty()) {
|
||||
ImGui::WarnTooltip(patch.description.c_str(), patch.caution.c_str());
|
||||
} else if (!patch.description.empty()) {
|
||||
ImGui::HelpTooltip(patch.description.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void PatchManager::hard_apply_patches() {
|
||||
std::vector<std::string> written_list;
|
||||
for (auto& patch : patches) {
|
||||
|
||||
@@ -142,6 +142,7 @@ namespace overlay::windows {
|
||||
std::function<bool(const PatchData&)> filter = std::function<bool(const PatchData&)>(),
|
||||
std::string pe_identifier_for_patch = "");
|
||||
|
||||
void show_patch_tooltip(const PatchData& patch);
|
||||
};
|
||||
|
||||
PatchStatus is_patch_active(PatchData &patch);
|
||||
|
||||
@@ -51,7 +51,6 @@ namespace overlay::windows {
|
||||
for (size_t i = 0; i < std::size(cfg::SCREENRESIZE->scene_settings); i++) {
|
||||
auto& scene = cfg::SCREENRESIZE->scene_settings[i];
|
||||
scene.keep_aspect_ratio = true;
|
||||
scene.centered = true;
|
||||
scene.offset_x = 0;
|
||||
scene.offset_y = 0;
|
||||
scene.scale_x = 1.f;
|
||||
@@ -126,21 +125,28 @@ namespace overlay::windows {
|
||||
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
|
||||
|
||||
// general settings
|
||||
ImGui::Checkbox("Centered", &scene.centered);
|
||||
if (!scene.centered) {
|
||||
ImGui::InputInt("X Offset", &scene.offset_x);
|
||||
ImGui::InputInt("Y Offset", &scene.offset_y);
|
||||
}
|
||||
ImGui::InputInt("X Offset", &scene.offset_x);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Hint: ctrl + click on +/- buttons to move quickly.");
|
||||
ImGui::InputInt("Y Offset", &scene.offset_y);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Hint: ctrl + click on +/- buttons to move quickly.");
|
||||
|
||||
// aspect ratio
|
||||
ImGui::Checkbox("Keep Aspect Ratio", &scene.keep_aspect_ratio);
|
||||
if (scene.keep_aspect_ratio) {
|
||||
if (ImGui::SliderFloat("Scale", &scene.scale_x, 0.65f, 2.0f)) {
|
||||
if (ImGui::SliderFloat("Scale", &scene.scale_x, 0.5f, 2.5f)) {
|
||||
scene.scale_y = scene.scale_x;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
|
||||
} else {
|
||||
ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.65f, 2.0f);
|
||||
ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.65f, 2.0f);
|
||||
ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.5f, 2.5f);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
|
||||
ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.5f, 2.5f);
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker("Hint: ctrl + click on the slider to type in a numeric value.");
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
@@ -23,11 +23,35 @@ namespace overlay::windows {
|
||||
|
||||
const auto padding = ImGui::GetFrameHeight() / 2;
|
||||
|
||||
this->init_size = ImVec2(ImGui::GetIO().DisplaySize.x - (padding * 2), 0.f);
|
||||
this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight();
|
||||
switch (games::sdvx::OVERLAY_POS) {
|
||||
case games::sdvx::SDVX_OVERLAY_BOTTOM_LEFT:
|
||||
case games::sdvx::SDVX_OVERLAY_BOTTOM_RIGHT:
|
||||
this->init_size.x = (ImGui::GetIO().DisplaySize.x - (ImGui::GetIO().DisplaySize.y * 9 / 16)) / 2 - padding;
|
||||
this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight();
|
||||
break;
|
||||
case games::sdvx::SDVX_OVERLAY_TOP:
|
||||
case games::sdvx::SDVX_OVERLAY_BOTTOM:
|
||||
case games::sdvx::SDVX_OVERLAY_MIDDLE:
|
||||
default:
|
||||
this->init_size = ImVec2(ImGui::GetIO().DisplaySize.x - (padding * 2), 0.f);
|
||||
this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight();
|
||||
if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
this->init_size.x /= 2;
|
||||
this->init_size.y /= 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this->init_pos = ImVec2(ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2, 0);
|
||||
switch (games::sdvx::OVERLAY_POS) {
|
||||
case games::sdvx::SDVX_OVERLAY_BOTTOM_LEFT:
|
||||
this->init_pos.x = 0;
|
||||
this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y;
|
||||
break;
|
||||
case games::sdvx::SDVX_OVERLAY_BOTTOM_RIGHT:
|
||||
this->init_pos.x = ImGui::GetIO().DisplaySize.x - this->init_size.x;
|
||||
this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y;
|
||||
break;
|
||||
case games::sdvx::SDVX_OVERLAY_TOP:
|
||||
this->init_pos.y = padding;
|
||||
break;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -2,44 +2,83 @@
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "external/fmt/include/fmt/format.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace unity_utils {
|
||||
|
||||
std::string get_unity_player_args() {
|
||||
std::string args = "";
|
||||
// windowed
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
args += " -screen-fullscreen 0";
|
||||
std::string get_unity_player_args() {
|
||||
std::string args = "";
|
||||
// windowed
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
args += " -screen-fullscreen 0";
|
||||
|
||||
// window size - by default unity player will attempt to create a window that fills the
|
||||
// screen, so instead fall back to 1080p resolution
|
||||
uint32_t w = 1920;
|
||||
uint32_t h = 1080;
|
||||
if (GRAPHICS_WINDOW_SIZE.has_value()) {
|
||||
w = GRAPHICS_WINDOW_SIZE.value().first;
|
||||
h = GRAPHICS_WINDOW_SIZE.value().second;
|
||||
}
|
||||
args += fmt::format(" -screen-width {} -screen-height {}", w, h);
|
||||
|
||||
// window border
|
||||
// eventually we should launch the player inside a parent window that we have full control
|
||||
// over using -parentHWND so we can let the user resize it by dragging the border...
|
||||
if (GRAPHICS_WINDOW_STYLE.has_value()) {
|
||||
if (GRAPHICS_WINDOW_STYLE == cfg::WindowDecorationMode::Borderless) {
|
||||
args += " -popupwindow";
|
||||
// window size - by default unity player will attempt to create a window that fills the
|
||||
// screen, so instead fall back to 1080p resolution
|
||||
uint32_t w = 1920;
|
||||
uint32_t h = 1080;
|
||||
if (GRAPHICS_WINDOW_SIZE.has_value()) {
|
||||
w = GRAPHICS_WINDOW_SIZE.value().first;
|
||||
h = GRAPHICS_WINDOW_SIZE.value().second;
|
||||
}
|
||||
args += fmt::format(" -screen-width {} -screen-height {}", w, h);
|
||||
|
||||
// window border
|
||||
// eventually we should launch the player inside a parent window that we have full control
|
||||
// over using -parentHWND so we can let the user resize it by dragging the border...
|
||||
if (GRAPHICS_WINDOW_STYLE.has_value()) {
|
||||
if (GRAPHICS_WINDOW_STYLE == cfg::WindowDecorationMode::Borderless) {
|
||||
args += " -popupwindow";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// need to specify this, otherwise it gets cached and uses previous value
|
||||
args += " -screen-fullscreen 1";
|
||||
}
|
||||
} else {
|
||||
// need to specify this, otherwise it gets cached and uses previous value
|
||||
args += " -screen-fullscreen 1";
|
||||
|
||||
// monitor
|
||||
if (D3D9_ADAPTER.has_value()) {
|
||||
args += fmt::format(" -monitor {}", D3D9_ADAPTER.value());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
// monitor
|
||||
if (D3D9_ADAPTER.has_value()) {
|
||||
args += fmt::format(" -monitor {}", D3D9_ADAPTER.value());
|
||||
static std::string cmdLine;
|
||||
static decltype(GetCommandLineA) *GetCommandLineA_orig = nullptr;
|
||||
|
||||
static LPSTR WINAPI GetCommandLineA_hook() {
|
||||
return (LPSTR) cmdLine.c_str();
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,4 +4,6 @@
|
||||
|
||||
namespace unity_utils {
|
||||
std::string get_unity_player_args();
|
||||
void set_args(const std::string &args);
|
||||
void force_show_cursor(bool bShow);
|
||||
}
|
||||
|
||||
@@ -298,3 +298,16 @@ static inline int get_async_secondary_mouse() {
|
||||
int vk = GetSystemMetrics(SM_SWAPBUTTON) ? VK_LBUTTON : VK_RBUTTON;
|
||||
return GetAsyncKeyState(vk);
|
||||
}
|
||||
|
||||
static inline bool parse_width_height(const std::string wh, std::pair<uint32_t, uint32_t> &result) {
|
||||
std::string s = wh;
|
||||
uint32_t w, h;
|
||||
const auto remove_spaces = [](const char& c) { return c == ' '; };
|
||||
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
|
||||
if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) {
|
||||
result = std::pair(w, h);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user