Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrix9999 committed Dec 27, 2024
0 parents commit 7a32943
Show file tree
Hide file tree
Showing 11 changed files with 357 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .gitignore
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/
3 changes: 3 additions & 0 deletions .gitmodules
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
20 changes: 20 additions & 0 deletions CMakeLists.txt
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)
57 changes: 57 additions & 0 deletions CMakePresets.json
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" }
]
}

41 changes: 41 additions & 0 deletions README.md
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.
4 changes: 4 additions & 0 deletions dependencies/CMakeLists.txt
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/"
)
1 change: 1 addition & 0 deletions dependencies/argparse
Submodule argparse added at 68fd02
9 changes: 9 additions & 0 deletions src/DeviceMeta.h
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;
};
52 changes: 52 additions & 0 deletions src/RawInput.cpp
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;
}
9 changes: 9 additions & 0 deletions src/RawInput.h
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);
145 changes: 145 additions & 0 deletions src/main.cpp
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;
}

0 comments on commit 7a32943

Please sign in to comment.