-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7a32943
Showing
11 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Windows files | ||
|
||
desktop.ini | ||
|
||
# Main directory | ||
|
||
/build/ | ||
/out/ | ||
|
||
# Visual Studio | ||
|
||
/.vs/ | ||
|
||
# VS Code | ||
|
||
/.vscode/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[submodule "dependencies/argparse"] | ||
path = dependencies/argparse | ||
url = https://github.com/p-ranav/argparse.git |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
cmake_minimum_required(VERSION 3.21) | ||
|
||
set(CMAKE_CXX_STANDARD 17) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
|
||
project(LightgunM3Remap) | ||
|
||
file(GLOB_RECURSE SRC | ||
"src/*.h" | ||
"src/*.cpp" | ||
) | ||
|
||
add_executable(${PROJECT_NAME} ${SRC}) | ||
|
||
target_compile_definitions(${PROJECT_NAME} | ||
PRIVATE | ||
NOMINMAX | ||
) | ||
|
||
add_subdirectory(dependencies) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
{ | ||
"version": 3, | ||
"configurePresets": [ | ||
{ | ||
"name": "windows", | ||
"hidden": true, | ||
"generator": "Ninja", | ||
"binaryDir": "${sourceDir}/out/build/${presetName}", | ||
"condition": { | ||
"type": "equals", | ||
"lhs": "${hostSystemName}", | ||
"rhs": "Windows" | ||
}, | ||
"cacheVariables": { | ||
"CMAKE_C_COMPILER": "cl.exe", | ||
"CMAKE_CXX_COMPILER": "cl.exe" | ||
} | ||
}, | ||
{ | ||
"name": "x86", | ||
"hidden": true, | ||
"architecture": { | ||
"value": "x86", | ||
"strategy": "external" | ||
}, | ||
"toolset": { | ||
"value": "host=x86", | ||
"strategy": "external" | ||
}, | ||
"cacheVariables": { | ||
"OUT_FILE_SUFFIX": "x86" | ||
} | ||
}, | ||
{ | ||
"name": "debug", | ||
"hidden": true, | ||
"cacheVariables": { | ||
"CMAKE_BUILD_TYPE": "Debug" | ||
} | ||
}, | ||
{ | ||
"name": "release", | ||
"hidden": true, | ||
"cacheVariables": { | ||
"CMAKE_BUILD_TYPE": "Release" | ||
} | ||
}, | ||
|
||
{ "name": "Windows-x86-Debug", "inherits": ["windows", "x86", "debug"] }, | ||
{ "name": "Windows-x86-Release", "inherits": ["windows", "x86", "release"] } | ||
], | ||
"buildPresets": [ | ||
{ "name": "Windows-x86-Debug", "configurePreset": "Windows-x86-Debug" }, | ||
{ "name": "Windows-x86-Release", "configurePreset": "Windows-x86-Release" } | ||
] | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Introduction | ||
|
||
This project is a simple console program that aims to automate the updating of lightgun ids in [Sega Model 3 emulator](https://www.supermodel3.com/). | ||
It does this by updating the `Supermodel.ini` file with new configuration. | ||
|
||
You can pass two parameters to the program: `--gun1` is required, while `--gun2` is optional. | ||
The program requires from the user to pass **vendor id** and **product id** of the lightgun, you can read more in the [Usage Example](#usage-example) section. | ||
|
||
The best recommended use case for this utiltiy is to run each time when you start a game via [LaunchBox](https://www.launchbox-app.com/) and [Bulk Add/Remove Additional Applications addon](https://forums.launchbox-app.com/files/file/4375-bulk-addremove-additional-applications/). | ||
|
||
# Limitations | ||
|
||
Currently, the lightgun configuration is hardcoded inside the source code, if you want to personalize it, you need edit the source code and recompile the program. | ||
I know that this isn't ideal, especially when you want to include your own configuration, but for now it is what it is. | ||
|
||
# Installation | ||
|
||
Simply copy the `SindenM3Remap.exe` into root folder of the SegaM3 emulator, example path: `Your/Path/To/SegaM3/SindenM3Remap.exe`. | ||
You can download the precompiled version of this program from [releases page](https://github.com/Patrix9999/SindenM3Remap/releases). | ||
|
||
# Usage example | ||
|
||
### cmd.exe | ||
|
||
``` | ||
SindenM3Remap.exe --gun1 "VID:16C0 PID:0F38" --gun2 "VID:16C0 PID:0F39" | ||
``` | ||
|
||
### powershell | ||
|
||
``` | ||
./SindenM3Remap --gun1 "VID:16C0 PID:0F38" --gun2 "VID:16C0 PID:0F39" | ||
``` | ||
|
||
# Building | ||
|
||
To build this utility, you only need this software: | ||
- [Visual Studio](https://visualstudio.microsoft.com/pl/) (at least **2019**, make sure to install **C++ Workload** and **CMake Tools for Visual Studio**) | ||
|
||
After having IDE installed, simply open up the folder cloned repository folder and build the project. | ||
The executable file can be located in `out/build/CONFIGURATION/` subdirectory. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
target_include_directories(${PROJECT_NAME} | ||
PRIVATE | ||
"${CMAKE_CURRENT_SOURCE_DIR}/argparse/include/" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#pragma once | ||
|
||
#include <string> | ||
|
||
struct DeviceMeta | ||
{ | ||
std::string vid; | ||
std::string pid; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
#include "RawInput.h" | ||
#include <regex> | ||
|
||
std::vector<DeviceMeta> GetRawInputDeviceMetas(size_t deviceType) | ||
{ | ||
UINT nDevices; | ||
if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0 || nDevices == 0) | ||
return {}; // RawInput not initialized or no raw input devices were found | ||
|
||
std::vector<RAWINPUTDEVICELIST> devices(nDevices); | ||
if (GetRawInputDeviceList(devices.data(), &nDevices, sizeof(RAWINPUTDEVICELIST)) == -1) | ||
return {}; // Failed to enumerate RawInput devices | ||
|
||
std::vector<DeviceMeta> deviceMetas; | ||
static std::regex regex(R"(\\?\HID#VID_([0-9A-Z]+)&PID_([0-9A-Z]+))"); | ||
|
||
for (size_t i = nDevices - 1; i != -1; --i) | ||
{ | ||
if (devices[i].dwType != deviceType) | ||
continue; | ||
|
||
char name[256] = {}; | ||
UINT nLength = sizeof(name); | ||
if (GetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, name, &nLength) == -1) | ||
continue; // Cannot retrieve RawInput device name | ||
|
||
if (strstr(name, "Root#RDP_") != NULL) | ||
continue; // Ignore any RDP devices | ||
|
||
std::cmatch match; | ||
if (!std::regex_search(name, match, regex)) | ||
continue; // regex doesn't match, skip the record | ||
|
||
// All good, add the entry | ||
deviceMetas.push_back({ match[1].str(), match[2].str() }); | ||
} | ||
|
||
return deviceMetas; | ||
} | ||
|
||
size_t GetRawInputDeviceMetaID(const std::vector<DeviceMeta>& deviceMetas, const DeviceMeta& searchedDeviceMeta) | ||
{ | ||
auto it = std::find_if(deviceMetas.begin(), deviceMetas.end(), [&](const DeviceMeta& deviceMeta) | ||
{ | ||
return deviceMeta.vid == searchedDeviceMeta.vid && deviceMeta.pid == searchedDeviceMeta.pid; | ||
}); | ||
|
||
if (it == deviceMetas.end()) | ||
return 0; | ||
|
||
return std::distance(deviceMetas.begin(), it) + 1; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#pragma once | ||
|
||
#include <vector> | ||
#include <windows.h> | ||
|
||
#include "DeviceMeta.h" | ||
|
||
std::vector<DeviceMeta> GetRawInputDeviceMetas(size_t deviceType); | ||
size_t GetRawInputDeviceMetaID(const std::vector<DeviceMeta>& deviceMetas, const DeviceMeta& searchedDeviceMeta); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
#include <argparse/argparse.hpp> | ||
#include <regex> | ||
#include <optional> | ||
#include <iostream> | ||
#include "RawInput.h" | ||
|
||
static std::optional<DeviceMeta> GetLightgunDeviceMeta(const std::string& param) | ||
{ | ||
static std::regex regex(R"(VID:([0-9A-Z]+)\s+PID:([0-9A-Z]+))"); | ||
|
||
std::smatch match; | ||
if (!std::regex_search(param, match, regex)) | ||
return std::nullopt; | ||
|
||
return DeviceMeta{ match[1].str(), match[2].str() }; | ||
} | ||
|
||
static void WriteIniString(const std::string& section, const std::string& key, const std::string& value, const std::string& file) | ||
{ | ||
std::string value_escaped = " \"" + value + "\""; | ||
WritePrivateProfileStringA(section.c_str(), key.c_str(), value_escaped.c_str(), file.c_str()); | ||
} | ||
|
||
static void UpdateGunIni(int gun_id, int lighgun_mouse_id, int lightgun_keyboard_id, const std::string& iniFile) | ||
{ | ||
// global | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputStart1" : "InputStart2", "KEY" + std::to_string(lightgun_keyboard_id) + "_UP", iniFile); | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputCoin1" : "InputCoin2", "KEY" + std::to_string(lightgun_keyboard_id) + "_LEFT", iniFile); | ||
|
||
// Star Wars Trilogy | ||
if (gun_id == 1) | ||
{ | ||
WriteIniString("GLOBAL", "InputAnalogJoyX", "MOUSE" + std::to_string(lighgun_mouse_id) + "_XAXIS", iniFile); | ||
WriteIniString("GLOBAL", "InputAnalogJoyY", "MOUSE" + std::to_string(lighgun_mouse_id) + "_YAXIS", iniFile); | ||
WriteIniString("GLOBAL", "InputAnalogJoyTrigger", "MOUSE" + std::to_string(lighgun_mouse_id) + "_LEFT_BUTTON", iniFile); | ||
WriteIniString("GLOBAL", "InputAnalogJoyEvent", "MOUSE" + std::to_string(lighgun_mouse_id) + "_RIGHT_BUTTON", iniFile); | ||
} | ||
|
||
// Lost World | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputGunX" : "InputGunX2", "MOUSE" + std::to_string(lighgun_mouse_id) + "_XAXIS", iniFile); | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputGunY" : "InputGunY2", "MOUSE" + std::to_string(lighgun_mouse_id) + "_YAXIS", iniFile); | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputTrigger" : "InputTrigger2", "MOUSE" + std::to_string(lighgun_mouse_id) + "_LEFT_BUTTON", iniFile); | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputOffscreen" : "InputOffscreen2", "MOUSE" + std::to_string(lighgun_mouse_id) + "_RIGHT_BUTTON", iniFile); | ||
|
||
// Ocean Hunter | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputAnalogGunX" : "InputAnalogGunX2", "MOUSE" + std::to_string(lighgun_mouse_id) + "_XAXIS", iniFile); | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputAnalogGunY" : "InputAnalogGunY2", "MOUSE" + std::to_string(lighgun_mouse_id) + "_YAXIS", iniFile); | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputAnalogTriggerLeft" : "InputAnalogTriggerLeft2", "MOUSE" + std::to_string(lighgun_mouse_id) + "_LEFT_BUTTON", iniFile); | ||
WriteIniString("GLOBAL", gun_id == 1 ? "InputAnalogTriggerRight" : "InputAnalogTriggerRight2", "MOUSE" + std::to_string(lighgun_mouse_id) + "_RIGHT_BUTTON", iniFile); | ||
} | ||
|
||
int main(int argc, char* argv[]) | ||
{ | ||
// Usage: | ||
// LightgunM3Remap.exe --gun1 "VID:16C0 PID:0F38" --gun2 "VID:16C0 PID:0F39" | ||
|
||
std::string iniFile = argv[0]; | ||
iniFile = iniFile.substr(0, iniFile.find_last_of("\\")); | ||
iniFile += "\\Config\\Supermodel.ini"; | ||
|
||
argparse::ArgumentParser program("LightgunM3Remap"); | ||
|
||
program.add_argument("--gun1") | ||
.help("Pass first gun VendorId and ProductId, e.g. \"VID:16C0 PID:0F38\"") | ||
.required(); | ||
|
||
program.add_argument("--gun2") | ||
.help("Optionally pass second gun VendorID and ProductId, e.g. \"VID:16C0 PID:0F39\"") | ||
.default_value(std::string("")); // Default empty if not provided | ||
|
||
std::optional<DeviceMeta> gun1_device_meta, gun2_device_meta; | ||
|
||
try | ||
{ | ||
program.parse_args(argc, argv); | ||
|
||
std::string gun1_param = program.get<std::string>("--gun1"); | ||
gun1_device_meta = GetLightgunDeviceMeta(gun1_param); | ||
|
||
std::string gun2_param = program.get<std::string>("--gun2"); | ||
if (!gun2_param.empty()) | ||
gun2_device_meta = GetLightgunDeviceMeta(gun2_param); | ||
} | ||
catch (const std::exception& e) | ||
{ | ||
std::cerr << e.what() << std::endl; | ||
std::cerr << program; | ||
|
||
return 1; | ||
} | ||
|
||
if (!gun1_device_meta.has_value()) | ||
{ | ||
std::cerr << "invalid \"gun1\" argument provided" << std::endl; | ||
return 2; | ||
} | ||
|
||
std::vector<DeviceMeta> keyboardDeviceMetas = GetRawInputDeviceMetas(RIM_TYPEKEYBOARD); | ||
std::vector<DeviceMeta> mouseDeviceMetas = GetRawInputDeviceMetas(RIM_TYPEMOUSE); | ||
|
||
size_t lightun_1_mouse_id = GetRawInputDeviceMetaID(mouseDeviceMetas, *gun1_device_meta); | ||
size_t lightun_1_keyboard_id = GetRawInputDeviceMetaID(keyboardDeviceMetas, *gun1_device_meta); | ||
|
||
if (lightun_1_mouse_id == 0 || lightun_1_keyboard_id == 0) | ||
{ | ||
std::cerr << "lightgun 1 not found via RawInput API, make sure you've passed the correct PID and VID" << std::endl; | ||
return 3; | ||
} | ||
|
||
std::cout << "[lightgun 1 found]" << std::endl; | ||
std::cout << "- keyboard_id: " << lightun_1_keyboard_id << std::endl; | ||
std::cout << "- mouse_id: " << lightun_1_mouse_id << std::endl; | ||
std::cout << std::endl; | ||
|
||
std::cout << "Updating lightgun 1 Supermodel.ini entries..." << std::endl; | ||
UpdateGunIni(1, lightun_1_mouse_id, lightun_1_keyboard_id, iniFile); | ||
std::cout << "Done!" << std::endl; | ||
|
||
if (gun2_device_meta.has_value()) | ||
{ | ||
size_t lightun_2_mouse_id = GetRawInputDeviceMetaID(mouseDeviceMetas, *gun2_device_meta); | ||
size_t lightun_2_keyboard_id = GetRawInputDeviceMetaID(keyboardDeviceMetas, *gun2_device_meta); | ||
|
||
if (lightun_2_mouse_id == 0 || lightun_2_keyboard_id == 0) | ||
{ | ||
std::cerr << "lightgun 2 not found via RawInput API, make sure you've passed the correct PID and VID" << std::endl; | ||
return 3; | ||
} | ||
|
||
std::cout << "[lightgun 2 found]" << std::endl; | ||
std::cout << "- keyboard_id: " << lightun_2_keyboard_id << std::endl; | ||
std::cout << "- mouse_id: " << lightun_2_mouse_id << std::endl; | ||
std::cout << std::endl; | ||
|
||
std::cout << "Updating lightgun 2 Supermodel.ini entries..." << std::endl; | ||
UpdateGunIni(2, lightun_2_mouse_id, lightun_2_keyboard_id, iniFile); | ||
std::cout << "Done!" << std::endl; | ||
} | ||
|
||
#ifdef _DEBUG | ||
std::cin.get(); | ||
#endif | ||
|
||
return 0; | ||
} |