Skip to content

Commit

Permalink
Initial commit of source code
Browse files Browse the repository at this point in the history
  • Loading branch information
mifanbang committed Jun 7, 2020
1 parent bafbba8 commit 8fe1d3e
Show file tree
Hide file tree
Showing 32 changed files with 3,590 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Auto detect text files and perform LF normalization
* text=auto

# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union

# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
File renamed without changes.
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Hagr

Bridging Nintendo Switch Pro controller and XInput

## Introduction

Nintendo Switch Pro controller is a very well designed but also expensive product. Unfortunately it doesn't work on PC out of the box. The reason is that most modern PC games use [XInput](https://en.wikipedia.org/wiki/DirectInput#XInput) to communicate with gamepads but XInput doesn't support Pro controller. Games on Steam may support the device but that's not good enough.

*Hagr*, meaning skilled or handy in Old Norse, is a tool that communicates with the device and mimics XInput interface so that games can pick up signals from a Pro controller without any modification.

## How to Use

Hagr comes in the form of a set of DLLs. There are four DLLs in total and you have to copy them to the folder of your game, *i.e.*, the folder where you can see the EXE file of the game. Remember to back up any `xinput*.dll` file that already exists. Normally a game only needs one or two of the DLLs. Nevertheless, as it's tedious to check which ones your game really needs, just copy all of them and we can call it a day.

Please note that you DO need to check beforehand whether the game in question is 32-bit or 64-bit. Here's a [quick guide](https://superuser.com/questions/358434/how-to-check-if-a-binary-is-32-or-64-bit-on-windows#889267) of doing it. A 32-bit EXE is unable to load a 64-bit DLL and vise versa.

## Building the Code

Just build `hagr.sln` with Visual Studio, preferably 2019. Output binaries will then be located inside `bin/` folder. In the output folder you will also be able to see `TestMe.exe`. It's a simple test program which sends queries to XInput and shows results at a rate of about 60 ticks per second.

## Limitations

Hagr is an experimental project. I hope it works for as many use cases as possible but please be expecting situations where it doesn't. There are several limitations which may or may not be resolved in the future.

- Only **one** controller is supported, and it has to be a Pro controller.
- Only wired connection is supported.
- Thumbstick calibration values are currently hard-coded.
- Vibration via `XInputSetState()` is currently not implemented.
- There's no guarantee that every game using XInput will load the DLLs. Some games have unique ways to start up.

## An Incomplete List of Working Games

This is a non-exhaustive list. It only shows those that have been tested by me.

- Borderlands 3 (64-bit; in `OakGame/Binaries/Win64/` folder)
- Overwatch (64-bit; in `_retail_/` folder)

## TODO List

- `XInputSetState()` support.
- Better ways of displaying Hagr status.

## Acknowledgement

This project wouldn't be useful in any way without the following pioneer works:

- [SDL](https://hg.libsdl.org/SDL/file/tip/src/joystick/hidapi/SDL_hidapi_switch.c)
- [dekuNukem/Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering)

## Copyright

Copyright (C) 2020 Mifan Bang <https://debug.tw>.

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
40 changes: 40 additions & 0 deletions hagr.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <windows.h>

#define RES_APP_VER "0.1.0"
#define RES_APP_VER_INT 0,1,0


LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL


/////////////////////////////////////////////////////////////////////////////
// Version

VS_VERSION_INFO VERSIONINFO
FILEVERSION RES_APP_VER_INT
PRODUCTVERSION RES_APP_VER_INT
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS 0x40004L
FILETYPE 0x0L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "041d04b0"
BEGIN
VALUE "CompanyName", "Mifan Bang"
VALUE "FileDescription", "Bridging Nintendo Switch Pro controller and XInput"
VALUE "FileVersion", RES_APP_VER
VALUE "InternalName", "hagr.dll"
VALUE "LegalCopyright", "Copyright (C) 2020 Mifan Bang. https://debug.tw"
VALUE "OriginalFilename", "hagr.dll"
VALUE "ProductName", "Hagr"
VALUE "ProductVersion", RES_APP_VER
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x41d, 0x4b0
END
END
77 changes: 77 additions & 0 deletions hagr.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29926.136
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hagr", "vcproj\hagr.vcxproj", "{F10755F3-C007-4D2D-9DAD-C46B42377563}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hagrStub-1_3", "vcproj\hagrStub-1_3.vcxproj", "{2F3E60F4-C508-4557-8F89-6E397D8BC68E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hagrStub-9_1_0", "vcproj\hagrStub-9_1_0.vcxproj", "{830344D1-8B8A-4158-82F0-83ACB5E5AC09}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hagrStub-uap", "vcproj\hagrStub-uap.vcxproj", "{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestMe", "vcproj\TestMe.vcxproj", "{4FFE9904-19CF-4AB1-A703-4B5A5B13EAB2}"
ProjectSection(ProjectDependencies) = postProject
{830344D1-8B8A-4158-82F0-83ACB5E5AC09} = {830344D1-8B8A-4158-82F0-83ACB5E5AC09}
{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E} = {86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}
{F10755F3-C007-4D2D-9DAD-C46B42377563} = {F10755F3-C007-4D2D-9DAD-C46B42377563}
{2F3E60F4-C508-4557-8F89-6E397D8BC68E} = {2F3E60F4-C508-4557-8F89-6E397D8BC68E}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F10755F3-C007-4D2D-9DAD-C46B42377563}.Debug|x64.ActiveCfg = Debug|x64
{F10755F3-C007-4D2D-9DAD-C46B42377563}.Debug|x64.Build.0 = Debug|x64
{F10755F3-C007-4D2D-9DAD-C46B42377563}.Debug|x86.ActiveCfg = Debug|Win32
{F10755F3-C007-4D2D-9DAD-C46B42377563}.Debug|x86.Build.0 = Debug|Win32
{F10755F3-C007-4D2D-9DAD-C46B42377563}.Release|x64.ActiveCfg = Release|x64
{F10755F3-C007-4D2D-9DAD-C46B42377563}.Release|x64.Build.0 = Release|x64
{F10755F3-C007-4D2D-9DAD-C46B42377563}.Release|x86.ActiveCfg = Release|Win32
{F10755F3-C007-4D2D-9DAD-C46B42377563}.Release|x86.Build.0 = Release|Win32
{2F3E60F4-C508-4557-8F89-6E397D8BC68E}.Debug|x64.ActiveCfg = Debug|x64
{2F3E60F4-C508-4557-8F89-6E397D8BC68E}.Debug|x64.Build.0 = Debug|x64
{2F3E60F4-C508-4557-8F89-6E397D8BC68E}.Debug|x86.ActiveCfg = Debug|Win32
{2F3E60F4-C508-4557-8F89-6E397D8BC68E}.Debug|x86.Build.0 = Debug|Win32
{2F3E60F4-C508-4557-8F89-6E397D8BC68E}.Release|x64.ActiveCfg = Release|x64
{2F3E60F4-C508-4557-8F89-6E397D8BC68E}.Release|x64.Build.0 = Release|x64
{2F3E60F4-C508-4557-8F89-6E397D8BC68E}.Release|x86.ActiveCfg = Release|Win32
{2F3E60F4-C508-4557-8F89-6E397D8BC68E}.Release|x86.Build.0 = Release|Win32
{830344D1-8B8A-4158-82F0-83ACB5E5AC09}.Debug|x64.ActiveCfg = Debug|x64
{830344D1-8B8A-4158-82F0-83ACB5E5AC09}.Debug|x64.Build.0 = Debug|x64
{830344D1-8B8A-4158-82F0-83ACB5E5AC09}.Debug|x86.ActiveCfg = Debug|Win32
{830344D1-8B8A-4158-82F0-83ACB5E5AC09}.Debug|x86.Build.0 = Debug|Win32
{830344D1-8B8A-4158-82F0-83ACB5E5AC09}.Release|x64.ActiveCfg = Release|x64
{830344D1-8B8A-4158-82F0-83ACB5E5AC09}.Release|x64.Build.0 = Release|x64
{830344D1-8B8A-4158-82F0-83ACB5E5AC09}.Release|x86.ActiveCfg = Release|Win32
{830344D1-8B8A-4158-82F0-83ACB5E5AC09}.Release|x86.Build.0 = Release|Win32
{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}.Debug|x64.ActiveCfg = Debug|x64
{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}.Debug|x64.Build.0 = Debug|x64
{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}.Debug|x86.ActiveCfg = Debug|Win32
{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}.Debug|x86.Build.0 = Debug|Win32
{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}.Release|x64.ActiveCfg = Release|x64
{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}.Release|x64.Build.0 = Release|x64
{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}.Release|x86.ActiveCfg = Release|Win32
{86FDB7D9-7C1A-44D4-9D2C-AECCEA215D0E}.Release|x86.Build.0 = Release|Win32
{4FFE9904-19CF-4AB1-A703-4B5A5B13EAB2}.Debug|x64.ActiveCfg = Debug|x64
{4FFE9904-19CF-4AB1-A703-4B5A5B13EAB2}.Debug|x64.Build.0 = Debug|x64
{4FFE9904-19CF-4AB1-A703-4B5A5B13EAB2}.Debug|x86.ActiveCfg = Debug|Win32
{4FFE9904-19CF-4AB1-A703-4B5A5B13EAB2}.Debug|x86.Build.0 = Debug|Win32
{4FFE9904-19CF-4AB1-A703-4B5A5B13EAB2}.Release|x64.ActiveCfg = Release|x64
{4FFE9904-19CF-4AB1-A703-4B5A5B13EAB2}.Release|x64.Build.0 = Release|x64
{4FFE9904-19CF-4AB1-A703-4B5A5B13EAB2}.Release|x86.ActiveCfg = Release|Win32
{4FFE9904-19CF-4AB1-A703-4B5A5B13EAB2}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {755C8C48-4D7F-4EC8-A2ED-D93BB41DBCF8}
EndGlobalSection
EndGlobal
82 changes: 82 additions & 0 deletions src/AutoHandle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* hagr - bridging Nintendo Switch Pro controller and XInput
* Copyright (C) 2020 Mifan Bang <https://debug.tw>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include "AutoHandle.h"

#include <windows.h>



bool IsHandleValid(Handle handle)
{
return handle != INVALID_HANDLE_VALUE && handle != nullptr;
}



AutoHandle::AutoHandle() noexcept
: m_handle(nullptr)
{
}

AutoHandle::AutoHandle(Handle handle) noexcept
: m_handle(handle)
{
}

AutoHandle::AutoHandle(AutoHandle&& handle) noexcept
: m_handle(handle.m_handle)
{
handle.m_handle = nullptr;
}

AutoHandle::~AutoHandle() noexcept
{
Close();
}

AutoHandle& AutoHandle::operator = (Handle other) noexcept
{
Close();
m_handle = other;
return *this;
}

AutoHandle& AutoHandle::operator = (AutoHandle&& other) noexcept
{
*this = other.m_handle; // call "operator = (Handle)" version
other.m_handle = nullptr;
return *this;
}

AutoHandle::operator Handle () const noexcept
{
return m_handle;
}

AutoHandle::operator bool () const noexcept
{
return IsHandleValid(m_handle);
}

void AutoHandle::Close() noexcept
{
if (static_cast<bool>(*this))
CloseHandle(m_handle);
m_handle = nullptr;
}
49 changes: 49 additions & 0 deletions src/AutoHandle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* hagr - bridging Nintendo Switch Pro controller and XInput
* Copyright (C) 2020 Mifan Bang <https://debug.tw>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once


using Handle = void*;


bool IsHandleValid(Handle handle);


class AutoHandle
{
public:
AutoHandle() noexcept;
AutoHandle(Handle handle) noexcept;
AutoHandle(AutoHandle&&) noexcept;
~AutoHandle() noexcept;

AutoHandle& operator = (Handle other) noexcept;
AutoHandle& operator = (AutoHandle&& other) noexcept;

operator bool () const noexcept;
operator Handle () const noexcept;

void Close() noexcept;

AutoHandle(const AutoHandle&) = delete;
AutoHandle& operator = (const AutoHandle&) = delete;

private:
Handle m_handle;
};
56 changes: 56 additions & 0 deletions src/DebugUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* hagr - bridging Nintendo Switch Pro controller and XInput
* Copyright (C) 2020 Mifan Bang <https://debug.tw>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include "DebugUtils.h"

#include <iomanip>
#include <sstream>

#include "Pipes.h"
#include "ProInternals.h"



void DebugOutputString([[maybe_unused]] const wchar_t* str)
{
#ifdef _DEBUG
OutputDebugStringW(str);
#endif // _DEBUG
}


void DebugOutputPacket([[maybe_unused]] const Buffer& buffer)
{
#ifdef _DEBUG
unsigned int packetIdx = 0;
const auto& funcDumpPacket = [&packetIdx](const Packet& packet) {
std::ostringstream oss;

oss << packetIdx << ": ";
for (unsigned int i = 0; i < sizeof(packet); ++i)
oss << std::setfill('0') << std::setw(2) << std::uppercase << std::hex << static_cast<unsigned int>(reinterpret_cast<const uint8_t*>(&packet)[i]) << " ";
oss << std::endl;
OutputDebugStringA(oss.str().c_str());

++packetIdx;
return true;
};

IterateBuffer<Packet>(buffer, funcDumpPacket);
#endif // _DEBUG
}
Loading

0 comments on commit 8fe1d3e

Please sign in to comment.