Compare commits

...

9 Commits

Author SHA1 Message Date
sp2xdev 3c8dc9de32 overlay: change active option color to green 2025-03-28 23:49:12 -07:00
sp2xdev 4694b59cec update changelog 2025-03-28 20:36:52 -07:00
bicarus-dev 4f1ada7a2f overlay: help tooltip updates (#279)
## Link to GitHub Issue, if one exists
n/a

## Description of change

In Options (and in Advanced/API/Search tabs), remove the help marker
`(?)` and instead add a tooltip to the option name label and the control
widgets like the checkbox, text input, and combo. This is to help with
discoverability as many users failed to discover the `(?)` widget.

Similarly, update the Patches tab to remove `(?)` and add tooltips to
widgets. Patches with warnings `(!)` will continue to show it to
distinguish it from other patches.

Update styling for the tooltip (dark red background -> dark gray) so
that it stands out from other widgets.

Remove the purple option text used for game-specific options.

## Compiling
👍 

## Testing
Tested in spicecfg, and in-game overlay.
2025-03-28 20:34:58 -07:00
bicarus-dev 104a9cbffd graphics: rewrite image scaler (#278)
## Link to GitHub Issue, if one exists
Fixes #276 

## Description of change

Note: this change is not compatible with previous screen resize config;
i.e., if you had the zoom setting dialed into a certain view, after this
change it won't be the same area, so you'll have to set up a scene
again. I think this is a worthwhile cost since the old logic is just
broken in weird ways, and preserving old settings is really tedious math
(for a feature that not many people use). Besides, this change will only
use the new Scenes values from the JSON.

Rewrite the DX9 image scaler logic.

* Previously, the rendering surface was fixed at `4096*4096 px`. Now,
this is relative to the backbuffer dimensions, which depends on the game
& respects user provided `-forceres`.
* Remove "center" option, make it the default.

Above two changes fix the aspect ratio issue (scaling in portrait games
not respecting screen ratio) and things breaking at higher res (1080p
when zoomed out far enough, or when forced to run at 4k resolution for
example)

* Use `scenes` leaf of screen resize JSON config for Scene 1 as well,
completely removing ourselves from being compatible with older spice2x
versions for these values.
* Add additional bounds check before calling `StretchRect` to avoid
users getting stuck with a bad layout, which can kill performance.

## Compiling
🦾 

## Testing
Tested 32 bit (popn / ddr), 64 bit (ldj), portrait and landscape modes
(kfc)
2025-03-28 17:23:01 -07:00
bicarus-dev 45a52cff90 sdvx: landscape mode (#277)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add `SDVX Landscape Mode` option, allowing you to play SDVX in landscape
monitor orientation, with black borders on left and right - similar to
how EAC does it. This is meant to be an alternative to what people do
today, which is launching in windowed mode.

By default, with just `-sdvxlandscape` enabled, EG will launch at
1920x1080 (transposed from the original 1080x1920 resolution).

It is possible to combine with `-forceres` to launch at higher
resolutions. At some point though, when rendering at large resolutions
past 1440p, at some point scaling may break due to #276.

Disabled on 32-bit SDVX since dx9 `CreateDeviceEx` call is required;
`CreateDevice` won't work. For the same reason this isn't being made a
generic option for museca/rb/etc. In theory you can supply
`-sdvxlandscape` to games like IIDX and play landscape games in portrait
mode, although again, scaling my break due to #276.

Also, add new options (bottomleft/right) for subscreen overlay position
so that they don't overlap with the main screen in landscape mode.

## Compiling
🙂

## Testing
Tested a couple versions of VW and EG.
2025-03-26 20:21:05 -07:00
sp2xdev bf79b5d2aa update changelog 2025-03-24 16:10:10 -07:00
sp2xdev e74c60f61d MFG code review cleanup 2025-03-24 16:08:22 -07:00
bicarus-dev 89921e5ec2 graphics: custom full screen resolution (#274)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Adds a new option to forcibly set the full screen resolution.

Same idea as -windowscale.

## Compiling
🔺 

## Testing
Seems to be showing similar results as -windowscale:

* Works GREAT for IIDX/SDVX
* popn launches at correct resolution but only draws a small image (at
native res) - can be scaled with F11 menu
* nostalgia is broken in weird ways, image is drawn correctly but bottom
`credits` text draws at wrong offset, game's wintouch logic broken
(perhaps expectedly)

## Other notes
In theory this could enable "play sdvx in landscape mode" option but the
image scaling doesn't quite work with extreme values.
2025-03-24 16:07:04 -07:00
oleg238948234 1b7ebd6fc2 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.
2025-03-24 16:06:55 -07:00
38 changed files with 1376 additions and 177 deletions
+4
View File
@@ -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})
+3 -2
View File
@@ -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;
}
+3 -1
View File
@@ -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;
+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'))
) {
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
+2 -10
View File
@@ -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
-1
View File
@@ -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;
+9
View File
@@ -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
+8
View File
@@ -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() {
+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();
}
+22
View File
@@ -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 "";
}
+18
View File
@@ -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();
}
+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;
};
}
+3 -1
View File
@@ -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(&param);
// 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;
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);
}
+4
View File
@@ -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";
+4
View File
@@ -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");
}
+60 -6
View File
@@ -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) {
+92 -7
View File
@@ -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",
+8 -1
View File
@@ -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,
+2 -1
View File
@@ -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() {
+1
View File
@@ -4,6 +4,7 @@ namespace launcher::signal {
// settings
extern bool DISABLE;
extern bool USE_VEH_WORKAROUND;
//void print_stacktrace();
void attach();
+2
View File
@@ -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");
+44 -19
View File
@@ -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
View File
@@ -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,
+24 -7
View File
@@ -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: {
+27 -10
View File
@@ -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);
+15 -9
View File
@@ -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();
+26 -2
View File
@@ -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;
+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));
}
}
+68 -29
View File
@@ -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;
}
}
+2
View File
@@ -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);
}
+13
View File
@@ -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;
}
}