Compare commits

...

12 Commits

Author SHA1 Message Date
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
sp2xdev 165cc6028f update changelog 2025-03-23 18:15:10 -07:00
bicarus-dev 8dc835bf52 Auto PIN entry (#272)
## Link to GitHub Issue, if one exists
Fixes https://github.com/spice2x/spice2x.github.io/issues/193

## Description of change
Merges guardianblue's changes plus some code review feedback.

Adds the ability to automatically type out the digits of a PIN when an
overlay shortcut is pressed.

## Compiling
💯 

## Testing
Tested:
* 32 bit popn
* TDJ, 1p/2p
* KFC, UFC
2025-03-23 18:05:38 -07:00
bicarus-dev 8241641502 audio: log errors when RegOpenKey fails for ASIO registry hooks (#273)
## Link to GitHub Issue, if one exists
n/a

## Description of change
When RegOpenKey* hooks fail when substituting user's ASIO driver, log a
warning message.

This can happen when the user types in the wrong value for `-iidxasio`
or `-sdvxasio`, which is a common issue.

## Compiling
🥇 

## Testing
Tested LDJ/KFC
2025-03-23 18:02:41 -07:00
bicarus-dev 6de122c9f8 api: allow lights.read() to be filtered by light names (#271)
## Link to GitHub Issue, if one exists
Fixes https://github.com/spice2x/spice2x.github.io/issues/179

## Description of change
Previously, lights.read() did not accept any parameters, instead it
returned all lights implemented by the current game.
Problem is that this is too large for some embedded implementations;
also just inefficient.

Add code to accept light names as strings and only return those in the
result.
For performance, use `robin_hood::unordered_map` to speed up the lookup
operation, which was previously a linear search.
Add this to Python wrapper library. Did not bother with others.

Also, as a bonus - update lights.write_reset() to also accept a flat
list of strings, as opposed to array containing a string, which was
silly.

## Compiling
 

## Testing
Tested using spiceremote and writing a short Python program.
2025-03-22 21:50:51 -07:00
bicarus-dev b267ad09ac Multiple "scenes" for screen resize (#270)
## Link to GitHub Issue, if one exists
Fixes #263 

## Description of change
Add "scenes" to screen resize.

* UI changes - F11 menu now has a "scene switcher" for `Scene 1/2/3/4`.
Clean up other UI bits.
* Add hotkeys for `Screen Resize Scene 1`, `Screen Resize 2`, and so on.
* Detect hot key and trigger resizes.
* Add additional objects to screen_resize.json for saving and loading
the new scene settings.
* Existing screen resize data is backwards compatible (can be
loaded/saved to JSON), and is treated as `Scene 1`.
* Add API endpoint for `Resize`, allow toggling resize on/off, switching
active scene
* Add Python wrapper. Did not bother with the other wrappers (Dart, C++,
etc)...

## Compiling
🥇 

## Testing
Still testing, but seems to work fine for TDJ.
2025-03-21 21:52:47 -07:00
sp2xdev e0dd371d61 Update README.md 2025-03-21 03:09:17 -07:00
bicarus-dev f8f45a0cbd When UCRT is in use, hackishly populate msvcrt!__argc and msvcrt!__argv for DLL hook compat (#267)
## Link to GitHub Issue, if one exists
#264 

## Description of change
When spice2x is compiled with UCRT, fix up `__argc` and `__argv` for
msvcrt since some DLL hooks rely on them.

## Compiling
👍 

## Testing
Tested on Win10 LTSC, Win11, and Win7.
2025-03-20 18:32:05 -07:00
bicarus-dev bf8ba050ce Improve render scale help text in F11 menu (#269)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Previously, F11 menu just provided text-based hint on how to enable
-windowscale option.

Instead, show a disabled checkbox that correctly reflects the current
state, nudging the user to read the tooltip.

Elaborate the option tooltip for -windowscale as well for consistency &
explain *why* you might want to enable this.

## Compiling
💯 

## Testing
Tested 32/64 bit.
2025-03-20 18:31:52 -07:00
47 changed files with 1651 additions and 175 deletions
+3
View File
@@ -55,3 +55,6 @@ We explicitly do **NOT** have a Discord server for dicussions - we try to do eve
Please see [CONTRIBUTING page](https://github.com/spice2x/spice2x.github.io/blob/main/CONTRIBUTING.md) for a full list of guidelines when submitting code.
## Additional information
Please read [README.md](https://github.com/spice2x/spice2x.github.io/blob/main/src/spice2x/README.md) inside `src/spice2x`.
+5
View File
@@ -302,6 +302,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
api/modules/drs.cpp
api/modules/lcd.cpp
api/modules/ddr.cpp
api/modules/resize.cpp
# avs
avs/core.cpp
@@ -441,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
@@ -586,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})
+18
View File
@@ -181,6 +181,12 @@ All of those three modules have equally named methods for you to call.
- removes the override value from the objects specified by name
- if no names were passed, all overrides will be removed
##### Additional API for lights
- read(name: string, ...)
- same as read(), but you can specify light names
- write_reset(name: str, ...)
- same as write_reset(), but it accepts a flat list of strings
#### Touch
- read()
- returns an array of state objects containing id, x and y
@@ -246,6 +252,18 @@ which also means that your hex edits are applicable directly.
- info()
- returns information about the serial LCD controller some games use
#### Resize
- image_resize_enable(enable: bool)
- enables or disables image resize state
- image_resize_set_scene(scene: int)
- sets the active scene for image resize state; set to 0 to disable resize
## Native wrapper libraries
Spicetools provides wrapper libraries in: Arduino, C++, Dart, and Python.
Python is the only one that is fully spec compliant.
Other libraries may be missing features and contain bugs; please feel free to
contribute code to fill the gaps if you work on a project using these libraries.
## License
Unless otherwise noted, all files are licensed under the GPLv3.
See the LICENSE file for the full license text.
+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
View File
@@ -27,6 +27,7 @@
#include "modules/lights.h"
#include "modules/memory.h"
#include "modules/touch.h"
#include "modules/resize.h"
#include "request.h"
#include "response.h"
@@ -403,6 +404,7 @@ void Controller::init_state(api::ClientState *state) {
state->modules.push_back(new modules::Lights());
state->modules.push_back(new modules::Memory());
state->modules.push_back(new modules::Touch());
state->modules.push_back(new modules::Resize());
}
void Controller::free_state(api::ClientState *state) {
+74 -46
View File
@@ -19,11 +19,16 @@ namespace api::modules {
functions["read"] = std::bind(&Lights::read, this, _1, _2);
functions["write"] = std::bind(&Lights::write, this, _1, _2);
functions["write_reset"] = std::bind(&Lights::write_reset, this, _1, _2);
lights = games::get_lights(eamuse_get_game());
this->lights = games::get_lights(eamuse_get_game());
for (auto &light : *this->lights) {
this->lights_by_names.emplace(light.getName(), light);
}
}
/**
* read()
* read(name: str, ...)
*/
void Lights::read(api::Request &req, Response &res) {
@@ -32,17 +37,39 @@ namespace api::modules {
return;
}
// add state for each light
for (auto &light : *this->lights) {
Value state(kArrayType);
Value light_name(light.getName().c_str(), res.doc()->GetAllocator());
Value light_state(GameAPI::Lights::readLight(RI_MGR, light));
Value light_enabled(light.override_enabled);
state.PushBack(light_name, res.doc()->GetAllocator());
state.PushBack(light_state, res.doc()->GetAllocator());
state.PushBack(light_enabled, res.doc()->GetAllocator());
res.add_data(state);
// all lights for this game
if (req.params.Size() == 0) {
// add state for each light
for (auto &light : *this->lights) {
get_light(light, res);
}
return;
}
// specified light names
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsString()) {
error_type(res, "name", "string");
return;
}
const auto name = param.GetString();
if (this->lights_by_names.contains(name)) {
get_light(this->lights_by_names.at(name).get(), res);
}
}
}
void Lights::get_light(Light &light, Response &res) {
Value state(kArrayType);
Value light_name(light.getName().c_str(), res.doc()->GetAllocator());
Value light_state(GameAPI::Lights::readLight(RI_MGR, light));
Value light_enabled(light.override_enabled);
state.PushBack(light_name, res.doc()->GetAllocator());
state.PushBack(light_state, res.doc()->GetAllocator());
state.PushBack(light_enabled, res.doc()->GetAllocator());
res.add_data(state);
}
/**
@@ -90,6 +117,7 @@ namespace api::modules {
/**
* write_reset()
* write_reset(name: str, ...)
* write_reset([name: str], ...)
*/
void Lights::write_reset(Request &req, Response &res) {
@@ -119,26 +147,28 @@ namespace api::modules {
// loop parameters
for (Value &param : req.params.GetArray()) {
const char* light_name = nullptr;
// check params
if (!param.IsArray()) {
error(res, "parameters must be arrays");
return;
if (param.IsArray()) {
if (param.Size() < 1) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
// get params
light_name = param[0].GetString();
} else if (param.IsString()) {
light_name = param.GetString();
} else {
error(res, "parameters must be arrays or strings");
}
if (param.Size() < 1) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
// get params
auto light_name = param[0].GetString();
// write analog state
if (!this->write_light_reset(light_name)) {
if (light_name && !this->write_light_reset(light_name)) {
error_unknown(res, "analog", light_name);
continue;
}
@@ -153,21 +183,20 @@ namespace api::modules {
}
// find light
for (auto &light : *this->lights) {
if (light.getName() == name) {
light.override_state = CLAMP(state, 0.f, 1.f);
light.override_enabled = true;
if (this->lights_by_names.contains(name)) {
auto &light = this->lights_by_names.at(name).get();
light.override_state = CLAMP(state, 0.f, 1.f);
light.override_enabled = true;
if (cfg::CONFIGURATOR_STANDALONE) {
GameAPI::Lights::writeLight(RI_MGR, light, state);
}
return true;
if (cfg::CONFIGURATOR_STANDALONE) {
GameAPI::Lights::writeLight(RI_MGR, light, state);
}
}
// unknown light
return false;
return true;
} else {
// unknown light
return false;
}
}
bool Lights::write_light_reset(std::string name) {
@@ -178,14 +207,13 @@ namespace api::modules {
}
// find light
for (auto &light : *this->lights) {
if (light.getName() == name) {
light.override_enabled = false;
return true;
}
if (this->lights_by_names.contains(name)) {
auto &light = this->lights_by_names.at(name).get();
light.override_enabled = false;
return true;
} else {
// unknown light
return false;
}
// unknown light
return false;
}
}
+4
View File
@@ -1,6 +1,8 @@
#pragma once
#include <vector>
#include <external/robin_hood.h>
#include "api/module.h"
#include "api/request.h"
#include "cfg/api.h"
@@ -15,6 +17,7 @@ namespace api::modules {
// state
std::vector<Light> *lights;
robin_hood::unordered_map<std::string, std::reference_wrapper<Light>> lights_by_names;
// function definitions
void read(Request &req, Response &res);
@@ -22,6 +25,7 @@ namespace api::modules {
void write_reset(Request &req, Response &res);
// helper
void get_light(Light &light, Response &res);
bool write_light(std::string name, float state);
bool write_light_reset(std::string name);
};
+53
View File
@@ -0,0 +1,53 @@
#include "resize.h"
#include "external/rapidjson/document.h"
#include "cfg/screen_resize.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
static thread_local std::vector<uint8_t> CAPTURE_BUFFER;
Resize::Resize() : Module("resize") {
functions["image_resize_enable"] = std::bind(&Resize::image_resize_enable, this, _1, _2);
functions["image_resize_set_scene"] = std::bind(&Resize::image_resize_set_scene, this, _1, _2);
}
/**
* image_resize_enable(enable: bool)
*/
void Resize::image_resize_enable(Request &req, Response &res) {
if (req.params.Size() < 1) {
return error_params_insufficient(res);
}
if (!req.params[0].IsBool()) {
return error_type(res, "enable", "bool");
}
cfg::SCREENRESIZE->enable_screen_resize = req.params[0].GetBool();
}
/**
* image_resize_set_scene(scene: int)
*/
void Resize::image_resize_set_scene(Request &req, Response &res) {
if (req.params.Size() < 1) {
return error_params_insufficient(res);
}
if (!req.params[0].IsInt()) {
return error_type(res, "scene", "int");
}
const auto scene = req.params[0].GetInt();
if (scene < 0 || (int)std::size(cfg::SCREENRESIZE->scene_settings) < scene) {
return error(res, "invalid scene number");
}
if (scene == 0) {
cfg::SCREENRESIZE->enable_screen_resize = false;
} else {
cfg::SCREENRESIZE->enable_screen_resize = true;
cfg::SCREENRESIZE->screen_resize_current_scene = scene - 1;
}
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Resize : public Module {
public:
Resize();
private:
// function definitions
void image_resize_enable(Request &req, Response &res);
void image_resize_set_scene(Request &req, Response &res);
};
}
@@ -12,3 +12,4 @@ from .keypads import *
from .lights import *
from .memory import *
from .touch import *
from .resize import *
@@ -2,8 +2,14 @@ from .connection import Connection
from .request import Request
def lights_read(con: Connection):
res = con.request(Request("lights", "read"))
def lights_read(con: Connection, light_names=None):
req = Request("lights", "read")
if light_names:
for light_name in light_names:
req.add_param(light_name)
res = con.request(req)
return res.get_data()
@@ -0,0 +1,12 @@
from .connection import Connection
from .request import Request
def image_resize_enable(con: Connection, enable: bool):
req = Request("resize", "image_resize_enable")
req.add_param(enable)
con.request(req)
def image_resize_set_scene(con: Connection, scene: int):
req = Request("resize", "image_resize_set_scene")
req.add_param(scene)
con.request(req)
@@ -397,6 +397,65 @@ class LightsTab(ttk.Frame):
# set text
self.txt_lights.set_text(txt)
class ResizeTab(ttk.Frame):
"""Resize tab."""
def __init__(self, app, parent, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.app = app
self.parent = parent
# scale grid
self.columnconfigure(0, weight=1)
# image resize
self.resize = ttk.Frame(self, padding=(8, 8, 8, 8))
self.resize.grid(row=0, column=0, sticky=tk.E+tk.W)
self.resize.columnconfigure(0, weight=1)
self.resize.columnconfigure(1, weight=1)
self.resize_lbl = ttk.Label(self.resize, text="Image Resize")
self.resize_lbl.grid(row=0, columnspan=2)
self.resize_off = ttk.Button(self.resize, text="Disable", command=self.action_resize_false)
self.resize_off.grid(row=2, column=0, sticky=NSEW, padx=2, pady=2)
self.resize_on = ttk.Button(self.resize, text="Enable", command=self.action_resize_on)
self.resize_on.grid(row=2, column=1, sticky=NSEW, padx=2, pady=2)
self.resize_scene_1 = ttk.Button(self.resize, text="Scene 1", command=self.action_resize_scene_1)
self.resize_scene_1.grid(row=3, column=0, sticky=NSEW, padx=2, pady=2)
self.resize_scene_2 = ttk.Button(self.resize, text="Scene 2", command=self.action_resize_scene_2)
self.resize_scene_2.grid(row=3, column=1, sticky=NSEW, padx=2, pady=2)
self.resize_scene_3 = ttk.Button(self.resize, text="Scene 3", command=self.action_resize_scene_3)
self.resize_scene_3.grid(row=4, column=0, sticky=NSEW, padx=2, pady=2)
self.resize_scene_4 = ttk.Button(self.resize, text="Scene 4", command=self.action_resize_scene_4)
self.resize_scene_4.grid(row=4, column=1, sticky=NSEW, padx=2, pady=2)
@api_action
def action_resize_on(self):
spiceapi.image_resize_enable(self.app.connection, True)
@api_action
def action_resize_false(self):
spiceapi.image_resize_enable(self.app.connection, False)
@api_action
def action_resize_scene_1(self):
spiceapi.image_resize_set_scene(self.app.connection, 1)
@api_action
def action_resize_scene_2(self):
spiceapi.image_resize_set_scene(self.app.connection, 2)
@api_action
def action_resize_scene_3(self):
spiceapi.image_resize_set_scene(self.app.connection, 3)
@api_action
def action_resize_scene_4(self):
spiceapi.image_resize_set_scene(self.app.connection, 4)
class MainApp(ttk.Frame):
"""The main application frame."""
@@ -419,6 +478,8 @@ class MainApp(ttk.Frame):
self.tabs.add(self.tab_analogs, text="Analogs")
self.tab_lights = LightsTab(self, self.tabs)
self.tabs.add(self.tab_lights, text="Lights")
self.tab_resize = ResizeTab(self, self.tabs)
self.tabs.add(self.tab_resize, text="Resize")
self.tab_manual = ManualTab(self, self.tabs)
self.tabs.add(self.tab_manual, text="Manual")
self.tabs.pack(expand=True, fill=tk.BOTH)
+27 -12
View File
@@ -76,14 +76,22 @@ namespace cfg {
eamuse_get_game(),
use_game_setting,
root);
load_int_value(doc, root + "offset_x", this->offset_x);
load_int_value(doc, root + "offset_y", this->offset_y);
load_float_value(doc, root + "scale_x", this->scale_x);
load_float_value(doc, root + "scale_y", this->scale_y);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
load_bool_value(doc, root + "keep_aspect_ratio", this->keep_aspect_ratio);
load_bool_value(doc, root + "centered", this->centered);
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);
}
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
root = "/sp2x_games/" + eamuse_get_game() + "/";
@@ -189,14 +197,21 @@ namespace cfg {
root);
// full screen image settings
rapidjson::Pointer(root + "offset_x").Set(doc, this->offset_x);
rapidjson::Pointer(root + "offset_y").Set(doc, this->offset_y);
rapidjson::Pointer(root + "scale_x").Set(doc, this->scale_x);
rapidjson::Pointer(root + "scale_y").Set(doc, this->scale_y);
rapidjson::Pointer(root + "enable_screen_resize").Set(doc, this->enable_screen_resize);
rapidjson::Pointer(root + "enable_linear_filter").Set(doc, this->enable_linear_filter);
rapidjson::Pointer(root + "keep_aspect_ratio").Set(doc, this->keep_aspect_ratio);
rapidjson::Pointer(root + "centered").Set(doc, this->centered);
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);
}
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
rapidjson::Pointer(root + "w_always_on_top").Set(doc, this->window_always_on_top);
+11 -6
View File
@@ -14,6 +14,15 @@ namespace cfg {
ResizableFrame = 2
};
struct fullscreen_setting {
int offset_x = 0;
int offset_y = 0;
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;
class ScreenResize {
@@ -31,14 +40,10 @@ namespace cfg {
~ScreenResize();
// full screen (directx) image settings
int offset_x = 0;
int offset_y = 0;
float scale_x = 1.0;
float scale_y = 1.0;
bool enable_screen_resize = false;
int8_t screen_resize_current_scene = 0;
bool enable_linear_filter = true;
bool keep_aspect_ratio = true;
bool centered = true;
fullscreen_setting scene_settings[4];
// windowed mode sizing
// Windows terminology:
+10
View File
@@ -1,3 +1,13 @@
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
API: add resize function, lights.read can now filter lights by name
Fix DLL hooks not receiving command-line arguments
03/16/2025 [spice2x]
Fix clipboard copy function truncating last character of string
+18 -2
View File
@@ -100,9 +100,25 @@ namespace games::iidx {
*phkResult = DEVICE_ASIO_REG_HANDLE;
log_info("iidx::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
return RegOpenKeyExA_orig(real_asio_reg_handle, ASIO_DRIVER.value().c_str(), ulOptions, samDesired,
const auto result = RegOpenKeyExA_orig(
real_asio_reg_handle,
ASIO_DRIVER.value().c_str(),
ulOptions,
samDesired,
&real_asio_device_reg_handle);
if (result != ERROR_SUCCESS) {
log_warning(
"iidx::asio",
"failed to open registry subkey '{}', error=0x{:x}",
ASIO_DRIVER.value().c_str(), result);
log_warning(
"iidx::asio",
"due to improper ASIO setting, game will likely fail to launch; double check -iidxasio and the registry",
ASIO_DRIVER.value().c_str(), result);
}
return result;
}
}
+20
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() {
@@ -437,8 +445,20 @@ namespace games {
vkey_defaults.push_back(VK_F12);
names.emplace_back("Toggle Camera Control");
vkey_defaults.push_back(0xFF);
names.emplace_back("Player 1 PIN Macro");
vkey_defaults.push_back(0xFF);
names.emplace_back("Player 2 PIN Macro");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize Scene 1");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize Scene 2");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize Scene 3");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize Scene 4");
vkey_defaults.push_back(0xFF);
names.emplace_back("Force Exit Game");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Activate");
+6
View File
@@ -22,7 +22,13 @@ namespace games {
ToggleScreenResize,
ToggleOverlay,
ToggleCameraControl,
TriggerPinMacroP1,
TriggerPinMacroP2,
ScreenResize,
ScreenResizeScene1,
ScreenResizeScene2,
ScreenResizeScene3,
ScreenResizeScene4,
SuperExit,
NavigatorActivate,
NavigatorCancel,
+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;
};
}
+18 -2
View File
@@ -79,9 +79,25 @@ namespace games::sdvx {
*phkResult = DEVICE_ASIO_REG_HANDLE;
log_info("sdvx::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
return RegOpenKeyExA_orig(real_asio_reg_handle, ASIO_DRIVER.value().c_str(), ulOptions, samDesired,
const auto result = RegOpenKeyExA_orig(
real_asio_reg_handle,
ASIO_DRIVER.value().c_str(),
ulOptions,
samDesired,
&real_asio_device_reg_handle);
if (result != ERROR_SUCCESS) {
log_warning(
"sdvx::asio",
"failed to open registry subkey '{}', error=0x{:x}",
ASIO_DRIVER.value().c_str(), result);
log_warning(
"sdvx::asio",
"due to improper ASIO setting, game will likely fall back to WASAPI; double check -sdvxasio and the registry",
ASIO_DRIVER.value().c_str(), result);
}
return result;
}
}
@@ -680,7 +680,12 @@ 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_CUSTOM_RESOLUTION.has_value()) {
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
}
log_info("graphics::d3d9",
"D3D9 presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
@@ -846,7 +851,12 @@ 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_CUSTOM_RESOLUTION.has_value()) {
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
}
log_info("graphics::d3d9",
"D3D9Ex presentation parameters for adapter {}: BackBufferWidth: {}, BackBufferHeight: {}, "
@@ -870,7 +880,12 @@ 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 && 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;
}
log_info("graphics::d3d9",
"D3D9Ex fullscreen display mode for adapter {}: Width: {}, Height: {}, RefreshRate: {}, "
@@ -740,9 +740,10 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
topSurface->UnlockRect();
// do the actual zoom / offset math
if (cfg::SCREENRESIZE->centered) {
targetRect.right = (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
targetRect.bottom = (h + rectTop) / cfg::SCREENRESIZE->scale_y;
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;
@@ -750,12 +751,12 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
targetRect.left -= deltaW;
targetRect.right -= deltaW;
} else {
targetRect.left -= cfg::SCREENRESIZE->offset_x;
targetRect.top += cfg::SCREENRESIZE->offset_y;
targetRect.right = -cfg::SCREENRESIZE->offset_x;
targetRect.right += (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
targetRect.bottom = cfg::SCREENRESIZE->offset_y;
targetRect.bottom += (h + rectTop) / cfg::SCREENRESIZE->scale_y;
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;
}
// draw to back buffer
+1
View File
@@ -67,6 +67,7 @@ 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;
// settings
std::string GRAPHICS_DEVICEID = "PCI\\VEN_1002&DEV_7146";
+1
View File
@@ -35,6 +35,7 @@ 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 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");
}
+112 -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"
@@ -143,6 +145,8 @@ static bool check_dll(const std::string &model) {
}
}
void update_msvcrt_args(int argc, char *argv[]);
int main_implementation(int argc, char *argv[]) {
// remember argv, argv
@@ -211,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
@@ -362,6 +367,15 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FORCE_SINGLE_ADAPTER = false;
}
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::spice2x_NvapiProfile].value_bool() && !cfg::CONFIGURATOR_STANDALONE) {
nvapi::ADD_PROFILE = true;
}
@@ -600,6 +614,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;
}
@@ -735,6 +752,15 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::Player2Card].is_active()) {
CARD_OVERRIDES[1] = options[launcher::Options::Player2Card].value_text();
}
if (options[launcher::Options::Player1PinMacro].is_active()) {
PIN_MACRO_ENABLED = true;
PIN_MACRO_VALUES[0] = options[launcher::Options::Player1PinMacro].value_text();
}
if (options[launcher::Options::Player2PinMacro].is_active()) {
PIN_MACRO_ENABLED = true;
PIN_MACRO_VALUES[1] = options[launcher::Options::Player2PinMacro].value_text();
}
for (auto &reader : options[launcher::Options::ICCAReaderPort].values_text()) {
static int reader_id = 0;
if (reader_id < 2) {
@@ -936,12 +962,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");
}
@@ -1017,6 +1040,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;
}
@@ -1578,6 +1610,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";
@@ -1729,6 +1774,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) {
@@ -1923,6 +1972,8 @@ int main_implementation(int argc, char *argv[]) {
log_warning("launcher", "https://github.com/mon/ifs_layeredfs");
}
update_msvcrt_args(argc, argv);
// load hooks
for (auto &hook : game_hooks) {
log_info("launcher", "loading hook DLL {}", hook);
@@ -1964,6 +2015,11 @@ int main_implementation(int argc, char *argv[]) {
// start coin input thread
eamuse_coin_start_thread();
// pin macro
if (!cfg::CONFIGURATOR_STANDALONE && PIN_MACRO_ENABLED) {
eamuse_pin_macro_start_thread();
}
// print PEB
if (peb_print) {
peb::peb_print();
@@ -2048,6 +2104,8 @@ int main_implementation(int argc, char *argv[]) {
// stop coin input thread
eamuse_coin_stop_thread();
eamuse_pin_macro_stop_thread();
// BT5API
if (BT5API_ENABLED) {
bt5api_dispose();
@@ -2101,6 +2159,54 @@ int main_implementation(int argc, char *argv[]) {
return 0;
}
// https://github.com/spice2x/spice2x.github.io/issues/264
// huge ugly hack to work around things that broke when MinGW switched from msvcrt to ucrt
// this is done to ensure that any DLL hooks that rely on msvcrt continue to work
void update_msvcrt_args(int argc, char *argv[]) {
#if defined(_UCRT)
auto msvc = LoadLibraryA("msvcrt.dll");
if (!msvc) {
log_warning("launcher", "failed to load msvcrt.dll");
return;
}
// get __argc
PINT32 argc_addr = (PINT32)GetProcAddress(msvc, "__argc");
if (!argc_addr) {
log_warning("launcher", "failed to find msvcrt!__argc");
return;
}
try {
if (*argc_addr == argc) {
log_warning("launcher", "msvcrt!__argc is already set");
return;
}
} catch (const std::exception &e) {
log_warning("launcher", "exception while reading msvcrt!_argc: {}", e.what());
}
// get __argv
PCHAR **argv_addr = (PCHAR **)GetProcAddress(msvc, "__argv");
if (!argv_addr) {
log_warning("launcher", "failed to find msvcrt!__argv");
return;
}
// update them
try {
log_info("launcher", "msvcrt!__argc value before: {}", *argc_addr);
*argc_addr = argc;
log_info("launcher", "msvcrt!__argc value after: {}", *argc_addr);
*argv_addr = argv;
} catch (const std::exception &e) {
log_warning("launcher", "exception while messing with msvcrt!_argc and _argv: {}", e.what());
}
#else
log_misc("launcher", "not UCRT, skipping msvcrt!_argc / _argv hacks");
#endif
}
#ifndef SPICETOOLS_SPICECFG_STANDALONE
int main(int argc, char *argv[]) {
return main_implementation(argc, argv);
+81 -7
View File
@@ -113,6 +113,26 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Network",
.sensitive = true,
},
{
// Player1PinMacro
.title = "Player 1 PIN Macro",
.name = "pinmacro0",
.desc = "Set a PIN for Player 1 that will cause the PIN to be automatically typed when Player 1 PIN Macro overlay key is pressed",
.type = OptionType::Text,
.setting_name = "1234",
.category = "Network (Advanced)",
.sensitive = true,
},
{
// Player2PinMacro
.title = "Player 2 PIN Macro",
.name = "pinmacro1",
.desc = "Set a PIN for Player 2 that will cause the PIN to be automatically typed when Player 2 PIN Macro overlay key is pressed",
.type = OptionType::Text,
.setting_name = "5678",
.category = "Network (Advanced)",
.sensitive = true,
},
{
.title = "Windowed Mode",
.name = "w",
@@ -169,6 +189,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 (Common)",
},
{
.title = "Force Refresh Rate",
.name = "graphics-force-refresh",
@@ -177,12 +205,16 @@ 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!",
.type = OptionType::Bool,
.category = "Graphics (Common)",
// 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"
"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 (Common)"
},
{
// Graphics9On12
@@ -986,6 +1018,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",
@@ -1651,6 +1692,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",
@@ -1721,7 +1795,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. "
"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)",
},
+8 -1
View File
@@ -18,6 +18,8 @@ namespace launcher {
PCBID,
Player1Card,
Player2Card,
Player1PinMacro,
Player2PinMacro,
WindowedMode,
InjectHook,
EarlyInjectHook,
@@ -25,8 +27,9 @@ namespace launcher {
CaptureCursor,
ShowCursor,
DisplayAdapter,
GraphicsForceRefresh,
GraphicsForceSingleAdapter,
GraphicsForceRefresh,
FullscreenResolution,
Graphics9On12,
spice2x_Dx9On12,
NoLegacy,
@@ -112,6 +115,7 @@ namespace launcher {
LoadBusouShinkiModule,
LoadCCJModule,
LoadQKSModule,
LoadMFGModule,
LoadMusecaModule,
PathToModules,
ScreenshotFolder,
@@ -191,6 +195,9 @@ namespace launcher {
CCJMouseTrackball,
CCJMouseTrackballWithToggle,
CCJTrackballSensitivity,
MFGArgs,
MFGCabType,
MFGNoIO,
spice2x_LightsOverallBrightness,
spice2x_WindowBorder,
spice2x_WindowSize,
+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();
+100
View File
@@ -11,6 +11,7 @@
#include "util/logging.h"
#include "util/time.h"
#include "util/utils.h"
#include "overlay/overlay.h"
#include "bt5api.h"
@@ -39,6 +40,16 @@ static std::optional<double> AUTO_INSERT_CARD_FIRST_CONSUME_TIME;
static bool AUTO_INSERT_CARD_CACHED[2];
static uint8_t AUTO_INSERT_CARD_CACHED_DATA[2][8];
// pin macro
bool PIN_MACRO_ENABLED = false;
std::string PIN_MACRO_VALUES[2] = {"", ""};
static std::thread *PIN_MACRO_THREAD = nullptr;
static bool PIN_MACRO_THREAD_ACTIVE = false;
static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = {
games::OverlayButtons::TriggerPinMacroP1,
games::OverlayButtons::TriggerPinMacroP2
};
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) {
// get unit index
@@ -295,6 +306,93 @@ void eamuse_coin_stop_thread() {
COIN_INPUT_THREAD = nullptr;
}
void eamuse_pin_macro_start_thread() {
// set active
PIN_MACRO_THREAD_ACTIVE = true;
// create thread
PIN_MACRO_THREAD = new std::thread([]() {
uint16_t keypad_overrides[] = {
1 << EAM_IO_KEYPAD_0,
1 << EAM_IO_KEYPAD_1,
1 << EAM_IO_KEYPAD_2,
1 << EAM_IO_KEYPAD_3,
1 << EAM_IO_KEYPAD_4,
1 << EAM_IO_KEYPAD_5,
1 << EAM_IO_KEYPAD_6,
1 << EAM_IO_KEYPAD_7,
1 << EAM_IO_KEYPAD_8,
1 << EAM_IO_KEYPAD_9,
};
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()};
std::optional<uint8_t> active_unit = std::nullopt;
while (PIN_MACRO_THREAD_ACTIVE) {
// wait for key press
if (!active_unit.has_value()) {
for (int unit = 0; unit < 2; unit++) {
if (PIN_MACRO_VALUES[unit].empty()) {
continue;
}
if (overlay_buttons &&
(!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) &&
GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(PIN_MACRO_TRIGGER_KEYS[unit]))) {
active_unit = unit;
// Reset key index
pin_index[unit] = 0;
break;
}
}
if (!active_unit.has_value()) {
Sleep(20);
continue;
}
}
const auto unit = active_unit.value();
// get character from config
if (pin_index[unit] < PIN_MACRO_VALUES[unit].length()) {
// insert character
char pin_char = PIN_MACRO_VALUES[unit].at(pin_index[unit]);
if (pin_char >= '0' && pin_char <= '9') {
int char_index = pin_char - '0';
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
}
pin_index[unit]++;
Sleep(100);
// clear
eamuse_set_keypad_overrides(unit, 0);
Sleep(50);
// end of PIN
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
active_unit = std::nullopt;
Sleep(120);
}
continue;
}
Sleep(200);
}
});
}
void eamuse_pin_macro_stop_thread() {
PIN_MACRO_THREAD_ACTIVE = false;
if (PIN_MACRO_THREAD != nullptr) {
PIN_MACRO_THREAD->join();
delete PIN_MACRO_THREAD;
PIN_MACRO_THREAD = nullptr;
}
}
void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state) {
// check unit
@@ -555,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
View File
@@ -34,6 +34,9 @@ extern std::string CARD_OVERRIDES[2];
extern bool AUTO_INSERT_CARD[2];
extern float AUTO_INSERT_CARD_COOLDOWN;
extern bool PIN_MACRO_ENABLED;
extern std::string PIN_MACRO_VALUES[2];
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card);
bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int unit_id);
bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card, int index);
@@ -56,6 +59,9 @@ int eamuse_coin_add();
void eamuse_coin_start_thread();
void eamuse_coin_stop_thread();
void eamuse_pin_macro_start_thread();
void eamuse_pin_macro_stop_thread();
void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state);
void eamuse_set_keypad_overrides_bt5(size_t unit, uint16_t keypad_state);
void eamuse_set_keypad_overrides_reader(size_t unit, uint16_t keypad_state);
+92 -20
View File
@@ -17,6 +17,10 @@ namespace overlay::windows {
this->init_pos = ImVec2(10, 10);
this->toggle_button = games::OverlayButtons::ToggleScreenResize;
this->toggle_screen_resize = games::OverlayButtons::ScreenResize;
this->toggle_scene[0] = games::OverlayButtons::ScreenResizeScene1;
this->toggle_scene[1] = games::OverlayButtons::ScreenResizeScene2;
this->toggle_scene[2] = games::OverlayButtons::ScreenResizeScene3;
this->toggle_scene[3] = games::OverlayButtons::ScreenResizeScene4;
}
ScreenResize::~ScreenResize() {
@@ -42,13 +46,17 @@ namespace overlay::windows {
void ScreenResize::reset_vars_to_default() {
cfg::SCREENRESIZE->enable_screen_resize = false;
cfg::SCREENRESIZE->screen_resize_current_scene = 0;
cfg::SCREENRESIZE->enable_linear_filter = true;
cfg::SCREENRESIZE->keep_aspect_ratio = true;
cfg::SCREENRESIZE->centered = true;
cfg::SCREENRESIZE->offset_x = 0;
cfg::SCREENRESIZE->offset_y = 0;
cfg::SCREENRESIZE->scale_x = 1.f;
cfg::SCREENRESIZE->scale_y = 1.f;
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;
scene.scale_y = 1.f;
}
cfg::SCREENRESIZE->enable_window_resize = false;
cfg::SCREENRESIZE->window_always_on_top = false;
@@ -88,28 +96,51 @@ namespace overlay::windows {
void ScreenResize::build_fullscreen_config() {
// enable checkbox
ImGui::TextWrapped("Hint: bind a key to Screen Resize for quickly toggling this on/off.");
ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize);
ImGui::SameLine();
ImGui::HelpMarker("Hint: bind a key to Screen Resize for a quick toggle.");
ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_screen_resize);
ImGui::Checkbox("Linear Filter", &cfg::SCREENRESIZE->enable_linear_filter);
if (ImGui::RadioButton("Scene 1", cfg::SCREENRESIZE->screen_resize_current_scene == 0)) {
cfg::SCREENRESIZE->screen_resize_current_scene = 0;
}
ImGui::SameLine();
if (ImGui::RadioButton("Scene 2", cfg::SCREENRESIZE->screen_resize_current_scene == 1)) {
cfg::SCREENRESIZE->screen_resize_current_scene = 1;
}
ImGui::SameLine();
if (ImGui::RadioButton("Scene 3", cfg::SCREENRESIZE->screen_resize_current_scene == 2)) {
cfg::SCREENRESIZE->screen_resize_current_scene = 2;
}
ImGui::SameLine();
if (ImGui::RadioButton("Scene 4", cfg::SCREENRESIZE->screen_resize_current_scene == 3)) {
cfg::SCREENRESIZE->screen_resize_current_scene = 3;
}
ImGui::SameLine();
ImGui::HelpMarker(
"Hint: bind a key to Screen Resize 1/2/3/4 for quick scene switching. "
"Scene 1 is the default scene activated when starting the game.");
auto& scene = cfg::SCREENRESIZE->scene_settings[cfg::SCREENRESIZE->screen_resize_current_scene];
// general settings
ImGui::Checkbox("Linear Filter", &cfg::SCREENRESIZE->enable_linear_filter);
ImGui::Checkbox("Centered", &cfg::SCREENRESIZE->centered);
if (!cfg::SCREENRESIZE->centered) {
ImGui::InputInt("X Offset", &cfg::SCREENRESIZE->offset_x);
ImGui::InputInt("Y Offset", &cfg::SCREENRESIZE->offset_y);
ImGui::Checkbox("Centered", &scene.centered);
if (!scene.centered) {
ImGui::InputInt("X Offset", &scene.offset_x);
ImGui::InputInt("Y Offset", &scene.offset_y);
}
// aspect ratio
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->keep_aspect_ratio);
if (cfg::SCREENRESIZE->keep_aspect_ratio) {
if (ImGui::SliderFloat("Scale", &cfg::SCREENRESIZE->scale_x, 0.65f, 2.0f)) {
cfg::SCREENRESIZE->scale_y = cfg::SCREENRESIZE->scale_x;
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)) {
scene.scale_y = scene.scale_x;
}
} else {
ImGui::SliderFloat("Width Scale", &cfg::SCREENRESIZE->scale_x, 0.65f, 2.0f);
ImGui::SliderFloat("Height Scale", &cfg::SCREENRESIZE->scale_y, 0.65f, 2.0f);
ImGui::SliderFloat("Width Scale", &scene.scale_x, 0.65f, 2.0f);
ImGui::SliderFloat("Height Scale", &scene.scale_y, 0.65f, 2.0f);
}
ImGui::EndDisabled();
@@ -139,8 +170,16 @@ namespace overlay::windows {
if (ImGui::Checkbox("Always on Top", &cfg::SCREENRESIZE->window_always_on_top) ) {
graphics_update_z_order(window);
}
ImGui::BeginDisabled();
ImGui::Checkbox("Forced Render Scaling", &GRAPHICS_WINDOW_BACKBUFFER_SCALE);
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::HelpMarker(
"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.\n\n"
"This can't be changed in-game; instead, set -windowscale option in spicecfg and restart.");
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->client_keep_aspect_ratio);
ImGui::TextWrapped("Hint: if game is blurry after resizing, try -windowscale option in spicecfg.");
ImGui::Checkbox("Manual window move/resize", &cfg::SCREENRESIZE->enable_window_resize);
ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_window_resize);
@@ -209,8 +248,10 @@ namespace overlay::windows {
void ScreenResize::update() {
Window::update();
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
// toggle
if (this->toggle_screen_resize != ~0u) {
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
bool toggle_screen_resize_new = overlay_buttons
&& this->overlay->hotkeys_triggered()
&& GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(this->toggle_screen_resize));
@@ -220,5 +261,36 @@ namespace overlay::windows {
}
this->toggle_screen_resize_state = toggle_screen_resize_new;
}
// scene switch
auto toggle_scene_state_new = ~0u;
for (size_t i = 0; i < std::size(this->toggle_scene); i++) {
if (this->toggle_scene[i] == ~0u) {
continue;
}
bool scene_switched = overlay_buttons
&& this->overlay->hotkeys_triggered()
&& GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(this->toggle_scene[i]));
if (scene_switched) {
toggle_scene_state_new = (uint32_t)i;
}
// only detect rising edges of key presses
if (scene_switched && (this->toggle_scene_state != i)) {
if (cfg::SCREENRESIZE->screen_resize_current_scene == (int8_t)i &&
cfg::SCREENRESIZE->enable_screen_resize) {
// this scene is already active, turn scaling off
cfg::SCREENRESIZE->enable_screen_resize = false;
} else {
// switch to scene
cfg::SCREENRESIZE->enable_screen_resize = true;
cfg::SCREENRESIZE->screen_resize_current_scene = i;
}
break;
}
}
// remember if a key was pressed (or nothing pressed) this frame
this->toggle_scene_state = toggle_scene_state_new;
}
}
@@ -16,6 +16,9 @@ namespace overlay::windows {
size_t toggle_screen_resize = ~0u;
bool toggle_screen_resize_state = false;
size_t toggle_scene[4] = { ~0u, ~0u, ~0u, ~0u };
uint32_t toggle_scene_state = ~0u;
void build_fullscreen_config();
void build_windowed_config();
void build_footer();
+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;
}
}