Compare commits

...

8 Commits

Author SHA1 Message Date
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
27 changed files with 653 additions and 100 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`.
+1
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
+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.
+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:
+6
View File
@@ -1,3 +1,9 @@
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;
}
}
+12
View File
@@ -437,8 +437,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,
+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;
}
}
@@ -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
+68
View File
@@ -143,6 +143,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
@@ -735,6 +737,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) {
@@ -1923,6 +1934,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 +1977,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 +2066,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 +2121,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);
+21 -1
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",
@@ -1721,7 +1741,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 on some games, but completely broken on others",
.type = OptionType::Bool,
.category = "Graphics (Windowed)",
},
+2
View File
@@ -18,6 +18,8 @@ namespace launcher {
PCBID,
Player1Card,
Player2Card,
Player1PinMacro,
Player2PinMacro,
WindowedMode,
InjectHook,
EarlyInjectHook,
+98
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
+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();