From 58083e0863c129994bfc0f4a18e1c07bd5f114d5 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Sat, 7 Sep 2019 09:39:41 -0400 Subject: [PATCH 01/18] Started work on a real avatars implementation. Fix issue where since the image ids changed for every get avatar call mudrunner would keep allocating memory indefinitely. --- dll/settings.cpp | 13 +++++++++- dll/settings.h | 10 ++++++++ dll/steam_friends.h | 61 ++++++++++++++++++++++++++++++++++----------- dll/steam_utils.h | 26 ++++++++----------- 4 files changed, 80 insertions(+), 30 deletions(-) diff --git a/dll/settings.cpp b/dll/settings.cpp index 46771a4..95ab05a 100644 --- a/dll/settings.cpp +++ b/dll/settings.cpp @@ -202,4 +202,15 @@ void Settings::setLeaderboard(std::string leaderboard, enum ELeaderboardSortMeth leader.display_type = display_type; leaderboards[leaderboard] = leader; -} \ No newline at end of file +} + +int Settings::add_image(std::string data, uint32 width, uint32 height) +{ + int last = images.size() + 1; + struct Image_Data dt; + dt.width = width; + dt.height = height; + dt.data = data; + images[last] = dt; + return last; +} diff --git a/dll/settings.h b/dll/settings.h index cd6d1de..d787abb 100644 --- a/dll/settings.h +++ b/dll/settings.h @@ -52,6 +52,12 @@ struct Stat_config { }; }; +struct Image_Data { + uint32 width; + uint32 height; + std::string data; +}; + class Settings { CSteamID steam_id; CGameID game_id; @@ -116,6 +122,10 @@ public: //stats std::map getStats() { return stats; } void setStatDefiniton(std::string name, struct Stat_config stat_config) {stats[name] = stat_config; } + + //images + std::map images; + int add_image(std::string data, uint32 width, uint32 height); }; #endif diff --git a/dll/steam_friends.h b/dll/steam_friends.h index 145a1d5..9453c89 100644 --- a/dll/steam_friends.h +++ b/dll/steam_friends.h @@ -19,6 +19,12 @@ #define SEND_FRIEND_RATE 4.0 +struct Avatar_Numbers { + int smallest; + int medium; + int large; +}; + class Steam_Friends : public ISteamFriends004, public ISteamFriends005, @@ -45,7 +51,7 @@ public ISteamFriends bool modified; std::vector friends; - unsigned img_count; + std::map avatars; CSteamID lobby_id; std::chrono::high_resolution_clock::time_point last_sent_friends; @@ -82,6 +88,29 @@ bool isAppIdCompatible(Friend *f) return settings->get_local_game_id().AppID() == f->appid(); } +struct Avatar_Numbers add_friend_avatars(CSteamID id) +{ + uint64 steam_id = id.ConvertToUint64(); + auto avatar_ids = avatars.find(steam_id); + if (avatar_ids != avatars.end()) { + return avatar_ids->second; + } + + //TODO: get real image data from self/other peers + struct Avatar_Numbers avatar_numbers; + char zero_array[184 * 184 * 4] = {}; + std::string small_avatar(zero_array, 32 * 32 * 4); + std::string medium_avatar(zero_array, 64 * 64 * 4); + std::string large_avatar(zero_array, 184 * 184 * 4); + + avatar_numbers.smallest = settings->add_image(small_avatar, 32, 32); + avatar_numbers.medium = settings->add_image(medium_avatar, 64, 64); + avatar_numbers.large = settings->add_image(large_avatar, 184, 184); + + avatars[steam_id] = avatar_numbers; + return avatar_numbers; +} + public: static void steam_friends_callback(void *object, Common_Message *msg) { @@ -552,8 +581,10 @@ void ActivateGameOverlayInviteDialog( CSteamID steamIDLobby ) int GetSmallFriendAvatar( CSteamID steamIDFriend ) { PRINT_DEBUG("Steam_Friends::GetSmallFriendAvatar\n"); - ++img_count; - return (img_count * 3) + 1; + //IMPORTANT NOTE: don't change friend avatar numbers for the same friend or else some games endlessly allocate stuff. + std::lock_guard lock(global_mutex); + struct Avatar_Numbers numbers = add_friend_avatars(steamIDFriend); + return numbers.smallest; } @@ -561,8 +592,9 @@ int GetSmallFriendAvatar( CSteamID steamIDFriend ) int GetMediumFriendAvatar( CSteamID steamIDFriend ) { PRINT_DEBUG("Steam_Friends::GetMediumFriendAvatar\n"); - ++img_count; - return (img_count * 3) + 2; + std::lock_guard lock(global_mutex); + struct Avatar_Numbers numbers = add_friend_avatars(steamIDFriend); + return numbers.medium; } @@ -571,8 +603,9 @@ int GetMediumFriendAvatar( CSteamID steamIDFriend ) int GetLargeFriendAvatar( CSteamID steamIDFriend ) { PRINT_DEBUG("Steam_Friends::GetLargeFriendAvatar\n"); - ++img_count; - return (img_count * 3) + 0; + std::lock_guard lock(global_mutex); + struct Avatar_Numbers numbers = add_friend_avatars(steamIDFriend); + return numbers.large; } int GetFriendAvatar( CSteamID steamIDFriend, int eAvatarSize ) @@ -671,19 +704,19 @@ bool SetRichPresence( const char *pchKey, const char *pchValue ) PRINT_DEBUG("Steam_Friends::SetRichPresence %s %s\n", pchKey, pchValue ? pchValue : "NULL"); std::lock_guard lock(global_mutex); if (pchValue) { - #ifdef SHOW_DIALOG_RICH_CONNECT - if (std::string(pchKey) == std::string("connect")) - MessageBox(0, pchValue, pchKey, MB_OK); - #endif - (*us.mutable_rich_presence())[pchKey] = pchValue; + auto prev_value = (*us.mutable_rich_presence()).find(pchKey); + if (prev_value == (*us.mutable_rich_presence()).end() || prev_value->second != pchValue) { + (*us.mutable_rich_presence())[pchKey] = pchValue; + modified = true; + } } else { auto to_remove = us.mutable_rich_presence()->find(pchKey); if (to_remove != us.mutable_rich_presence()->end()) { us.mutable_rich_presence()->erase(to_remove); + modified = true; } } - modified = true; - + return true; } diff --git a/dll/steam_utils.h b/dll/steam_utils.h index 2405024..6f96efb 100644 --- a/dll/steam_utils.h +++ b/dll/steam_utils.h @@ -82,39 +82,35 @@ const char *GetIPCountry() return "US"; } -static uint32 width_image(int iImage) -{ - if ((iImage % 3) == 1) return 32; - if ((iImage % 3) == 2) return 64; - return 184; -} - // returns true if the image exists, and valid sizes were filled out bool GetImageSize( int iImage, uint32 *pnWidth, uint32 *pnHeight ) { - PRINT_DEBUG("GetImageSize\n"); + PRINT_DEBUG("GetImageSize %i\n", iImage); if (!iImage || !pnWidth || !pnHeight) return false; + auto image = settings->images.find(iImage); + if (image == settings->images.end()) return false; - *pnWidth = width_image(iImage); - *pnHeight = width_image(iImage);; + *pnWidth = image->second.width; + *pnHeight = image->second.height; return true; } - // returns true if the image exists, and the buffer was successfully filled out // results are returned in RGBA format // the destination buffer size should be 4 * height * width * sizeof(char) bool GetImageRGBA( int iImage, uint8 *pubDest, int nDestBufferSize ) { - PRINT_DEBUG("GetImageRGBA\n"); + PRINT_DEBUG("GetImageRGBA %i\n", iImage); if (!iImage || !pubDest || !nDestBufferSize) return false; - unsigned size = width_image(iImage) * width_image(iImage) * 4; + auto image = settings->images.find(iImage); + if (image == settings->images.end()) return false; + + unsigned size = image->second.data.size(); if (nDestBufferSize < size) size = nDestBufferSize; - memset(pubDest, 0xFF, size); + image->second.data.copy((char *)pubDest, nDestBufferSize); return true; } - // returns the IP of the reporting server for valve - currently only used in Source engine games bool GetCSERIPPort( uint32 *unIP, uint16 *usPort ) { From 4db580d94556698226aeddc57a7d4854f1967603 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Sat, 7 Sep 2019 10:35:55 -0400 Subject: [PATCH 02/18] Add missing older steam api SteamUnifiedMessages() function. --- dll/dll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dll/dll.cpp b/dll/dll.cpp index ccfef42..ff4464b 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -471,7 +471,7 @@ STEAMAPI_API ISteamHTMLSurface *SteamHTMLSurface() { PRINT_DEBUG("SteamHTMLSu STEAMAPI_API ISteamInventory *SteamInventory() { PRINT_DEBUG("SteamInventory()\n");return get_steam_client_old()->GetISteamInventory(SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), old_inventory); } STEAMAPI_API ISteamVideo *SteamVideo() { PRINT_DEBUG("SteamVideo()\n");return get_steam_client_old()->GetISteamVideo(SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), old_video); } STEAMAPI_API ISteamParentalSettings *SteamParentalSettings() { PRINT_DEBUG("SteamParentalSettings()\n");return get_steam_client_old()->GetISteamParentalSettings(SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), ""); } - +STEAMAPI_API ISteamUnifiedMessages *SteamUnifiedMessages() { PRINT_DEBUG("SteamUnifiedMessages()\n");return get_steam_client_old()->GetISteamUnifiedMessages(SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), old_unified_messages); } //Gameserver stuff From 2af93427fefa65ca6aae1b7efc67947f78cd501c Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Tue, 10 Sep 2019 14:54:54 -0400 Subject: [PATCH 03/18] Initial Xinput ISteamInput/ISteamController support. --- Readme_release.txt | 52 ++- build_win_debug_experimental.bat | 4 +- build_win_release_experimental.bat | 4 +- controller/gamepad.c | 599 +++++++++++++++++++++++++++++ controller/gamepad.h | 324 ++++++++++++++++ dll/settings.h | 9 + dll/settings_parser.cpp | 75 ++++ dll/steam_client.cpp | 2 +- dll/steam_client.h | 4 + dll/steam_controller.h | 418 +++++++++++++++++--- dll/steam_controller_disabled.h | 375 ++++++++++++++++++ dll/steam_utils.h | 4 + 12 files changed, 1814 insertions(+), 56 deletions(-) create mode 100644 controller/gamepad.c create mode 100644 controller/gamepad.h create mode 100644 dll/steam_controller_disabled.h diff --git a/Readme_release.txt b/Readme_release.txt index 647ffcb..c0d11e0 100644 --- a/Readme_release.txt +++ b/Readme_release.txt @@ -91,14 +91,62 @@ The default value is simply a number that represents the default value for the s Support for CPY steam_api(64).dll cracks: See the build in the experimental folder. - Notes: -You must all be on the same LAN for it to work. This is an early work so a lot of games will likely not work. +You must all be on the same LAN for it to work. IMPORTANT: Do not run more than one steam game with the same appid at the same time on the same computer with my emu or there might be network issues (dedicated servers should be fine though). +Controller (Note: at the moment this feature is only enabled in the windows experimental builds): +SteamController/SteamInput support is limited to XInput controllers. If your controller is not XInput, there are many tools (at least for windows) that you can use to make it emulate an XInput one. +Steam uses things called action sets for controller configuration. An action set is a group of action names. Action names are bound to buttons, triggers or joysticks. +The emulator needs to know for each action set, which button is linked to which action name. Create a ACTION_SET_NAME.txt file in the steam_settings\controller folder for every action set the game uses. +To see an example for the game Crystar see: steam_settings.EXAMPLE\controller.EXAMPLE +In the action set txt files the format is: +For digital actions (buttons, on or off): ACTION_NAME=BUTTON_NAME +For analog actions (joysticks, triggers): ACTION_NAME=ANALOG_NAME=input source mode +Actions can be bound to more than one button by separating the buttons with , like this: ACTION_NAME=A,B + +If you want to configure a game yourself, find the xbox360 or xbox one vdf file for the game and you should be able to figure things out. + +For example to get the vdf file for the game Crystar: https://steamdb.info/app/981750/config/ +If you look at: steamcontrollerconfigdetails, you will see something like: 1779660455/controller_type: controller_xbox360 +1779660455 refers to a file id that you can dl using your favorite steam workshop downloader site. +The url would be: https://steamcommunity.com/sharedfiles/filedetails/?id=1779660455 + +Valid digital button names: +DUP +DDOWN +DLEFT +DRIGHT +START +BACK +LSTICK +RSTICK +LBUMPER +RBUMPER +A +B +X +Y +DLTRIGGER (emulated buttons, the joy ones are used by games in menus for example. When the game wants to know if the trigger is pressed without the intensity) +DRTRIGGER +DLJOYUP +DLJOYDOWN +DLJOYLEFT +DLJOYRIGHT +DRJOYUP +DRJOYDOWN +DRJOYLEFT +DRJOYRIGHT + +Valid analog names: +LTRIGGER +RTRIGGER +LJOY +RJOY + List of valid steam languages: diff --git a/build_win_debug_experimental.bat b/build_win_debug_experimental.bat index b2ea89c..c306adc 100644 --- a/build_win_debug_experimental.bat +++ b/build_win_debug_experimental.bat @@ -4,13 +4,13 @@ call build_set_protobuf_directories.bat "%PROTOC_X86_EXE%" -I.\dll\ --cpp_out=.\dll\ .\dll\net.proto call build_env_x86.bat cl dll/rtlgenrandom.c dll/rtlgenrandom.def -cl /LD /I%PROTOBUF_X86_DIRECTORY%\include\ /DEMU_EXPERIMENTAL_BUILD dll/*.cpp dll/*.cc detours/*.cpp "%PROTOBUF_X86_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /link /OUT:steam_api.dll +cl /LD /I%PROTOBUF_X86_DIRECTORY%\include\ /DEMU_EXPERIMENTAL_BUILD /DCONTROLLER_SUPPORT dll/*.cpp dll/*.cc detours/*.cpp controller/gamepad.c "%PROTOBUF_X86_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /link /OUT:steam_api.dll cl /LD steamclient.cpp /EHsc /MP12 /link /OUT:steamclient.dll cl /LD steamnetworkingsockets.cpp /EHsc /MP12 /link /OUT:steamnetworkingsockets.dll "%PROTOC_X64_EXE%" -I.\dll\ --cpp_out=.\dll\ .\dll\net.proto call build_env_x64.bat cl dll/rtlgenrandom.c dll/rtlgenrandom.def -cl /LD /I%PROTOBUF_X64_DIRECTORY%\include\ /DEMU_EXPERIMENTAL_BUILD dll/*.cpp dll/*.cc detours/*.cpp "%PROTOBUF_X64_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /link /OUT:steam_api64.dll +cl /LD /I%PROTOBUF_X64_DIRECTORY%\include\ /DEMU_EXPERIMENTAL_BUILD /DCONTROLLER_SUPPORT dll/*.cpp dll/*.cc detours/*.cpp controller/gamepad.c "%PROTOBUF_X64_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /link /OUT:steam_api64.dll cl /LD steamclient.cpp /EHsc /MP12 /link /OUT:steamclient64.dll cl /LD steamnetworkingsockets.cpp /EHsc /MP12 /link /OUT:steamnetworkingsockets64.dll diff --git a/build_win_release_experimental.bat b/build_win_release_experimental.bat index 0c58b81..3ffb57e 100644 --- a/build_win_release_experimental.bat +++ b/build_win_release_experimental.bat @@ -6,11 +6,11 @@ call build_set_protobuf_directories.bat "%PROTOC_X86_EXE%" -I.\dll\ --cpp_out=.\dll\ .\dll\net.proto call build_env_x86.bat cl dll/rtlgenrandom.c dll/rtlgenrandom.def -cl /LD /DEMU_RELEASE_BUILD /DEMU_EXPERIMENTAL_BUILD /DNDEBUG /I%PROTOBUF_X86_DIRECTORY%\include\ dll/*.cpp dll/*.cc detours/*.cpp "%PROTOBUF_X86_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /Ox /link /debug:none /OUT:release\experimental\steam_api.dll +cl /LD /DEMU_RELEASE_BUILD /DEMU_EXPERIMENTAL_BUILD /DCONTROLLER_SUPPORT /DNDEBUG /I%PROTOBUF_X86_DIRECTORY%\include\ dll/*.cpp dll/*.cc detours/*.cpp controller/gamepad.c "%PROTOBUF_X86_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /Ox /link /debug:none /OUT:release\experimental\steam_api.dll cl /LD /DEMU_RELEASE_BUILD /DEMU_EXPERIMENTAL_BUILD /DNDEBUG steamclient.cpp /EHsc /MP12 /Ox /link /OUT:release\experimental\steamclient.dll "%PROTOC_X64_EXE%" -I.\dll\ --cpp_out=.\dll\ .\dll\net.proto call build_env_x64.bat cl dll/rtlgenrandom.c dll/rtlgenrandom.def -cl /LD /DEMU_RELEASE_BUILD /DEMU_EXPERIMENTAL_BUILD /DNDEBUG /I%PROTOBUF_X64_DIRECTORY%\include\ dll/*.cpp dll/*.cc detours/*.cpp "%PROTOBUF_X64_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /Ox /link /debug:none /OUT:release\experimental\steam_api64.dll +cl /LD /DEMU_RELEASE_BUILD /DEMU_EXPERIMENTAL_BUILD /DCONTROLLER_SUPPORT /DNDEBUG /I%PROTOBUF_X64_DIRECTORY%\include\ dll/*.cpp dll/*.cc detours/*.cpp controller/gamepad.c "%PROTOBUF_X64_LIBRARY%" Iphlpapi.lib Ws2_32.lib rtlgenrandom.lib Shell32.lib /EHsc /MP12 /Ox /link /debug:none /OUT:release\experimental\steam_api64.dll cl /LD /DEMU_RELEASE_BUILD /DEMU_EXPERIMENTAL_BUILD /DNDEBUG steamclient.cpp /EHsc /MP12 /Ox /link /OUT:release\experimental\steamclient64.dll copy Readme_experimental.txt release\experimental\Readme.txt diff --git a/controller/gamepad.c b/controller/gamepad.c new file mode 100644 index 0000000..53a6a2f --- /dev/null +++ b/controller/gamepad.c @@ -0,0 +1,599 @@ +/** + * Gamepad Input Library + * Sean Middleditch + * Copyright (C) 2010 Sean Middleditch + * LICENSE: MIT/X + */ + +#include +#include +#include +#include + +#define GAMEPAD_EXPORT 1 +#include "gamepad.h" + +/* Platform-specific includes */ +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN 1 +# undef UNICODE +# include "windows.h" +# include "xinput.h" +# pragma comment(lib, "xinput.lib") +#elif defined(__linux__) +# include +# include +# include +# include +# include +#else +# error "Unknown platform in gamepad.c" +#endif + +#define BUTTON_TO_FLAG(b) (1 << (b)) + +/* Axis information */ +typedef struct GAMEPAD_AXIS GAMEPAD_AXIS; +struct GAMEPAD_AXIS { + int x, y; + float nx, ny; + float length; + float angle; + GAMEPAD_STICKDIR dirLast, dirCurrent; +}; + +/* Trigger value information */ +typedef struct GAMEPAD_TRIGINFO GAMEPAD_TRIGINFO; +struct GAMEPAD_TRIGINFO { + int value; + float length; + GAMEPAD_BOOL pressedLast, pressedCurrent; +}; + +/* Structure for state of a particular gamepad */ +typedef struct GAMEPAD_STATE GAMEPAD_STATE; +struct GAMEPAD_STATE { + GAMEPAD_AXIS stick[STICK_COUNT]; + GAMEPAD_TRIGINFO trigger[TRIGGER_COUNT]; + int bLast, bCurrent, flags; +#if defined(__linux__) + char* device; + int fd; + int effect; +#endif +}; + +/* State of the four gamepads */ +static GAMEPAD_STATE STATE[4]; + +/* Note whether a gamepad is currently connected */ +#define FLAG_CONNECTED (1<<0) +#define FLAG_RUMBLE (1<<1) + +/* Prototypes for utility functions */ +static void GamepadResetState (GAMEPAD_DEVICE gamepad); +static void GamepadUpdateCommon (void); +static void GamepadUpdateDevice (GAMEPAD_DEVICE gamepad); +static void GamepadUpdateStick (GAMEPAD_AXIS* axis, float deadzone); +static void GamepadUpdateTrigger (GAMEPAD_TRIGINFO* trig); + +/* Various values of PI */ +#define PI_1_4 0.78539816339744f +#define PI_1_2 1.57079632679489f +#define PI_3_4 2.35619449019234f +#define PI 3.14159265358979f + +/* Platform-specific implementation code */ +#if defined(_WIN32) + +void GamepadInit(void) { + int i; + for (i = 0; i != GAMEPAD_COUNT; ++i) { + STATE[i].flags = 0; + } +} + +void GamepadUpdate(void) { + GamepadUpdateCommon(); +} + +static void GamepadUpdateDevice(GAMEPAD_DEVICE gamepad) { + XINPUT_STATE xs; + if (XInputGetState(gamepad, &xs) == 0) { + /* reset if the device was not already connected */ + if ((STATE[gamepad].flags & FLAG_CONNECTED) == 0) { + GamepadResetState(gamepad); + } + + /* mark that we are connected w/ rumble support */ + STATE[gamepad].flags |= FLAG_CONNECTED|FLAG_RUMBLE; + + /* update state */ + STATE[gamepad].bCurrent = xs.Gamepad.wButtons; + STATE[gamepad].trigger[TRIGGER_LEFT].value = xs.Gamepad.bLeftTrigger; + STATE[gamepad].trigger[TRIGGER_RIGHT].value = xs.Gamepad.bRightTrigger; + STATE[gamepad].stick[STICK_LEFT].x = xs.Gamepad.sThumbLX; + STATE[gamepad].stick[STICK_LEFT].y = xs.Gamepad.sThumbLY; + STATE[gamepad].stick[STICK_RIGHT].x = xs.Gamepad.sThumbRX; + STATE[gamepad].stick[STICK_RIGHT].y = xs.Gamepad.sThumbRY; + } else { + /* disconnected */ + STATE[gamepad].flags &= ~FLAG_CONNECTED; + } +} + +void GamepadShutdown(void) { + /* no Win32 shutdown required */ +} + +void GamepadSetRumble(GAMEPAD_DEVICE gamepad, float left, float right) { + if ((STATE[gamepad].flags & FLAG_RUMBLE) != 0) { + XINPUT_VIBRATION vib; + ZeroMemory(&vib, sizeof(vib)); + vib.wLeftMotorSpeed = (WORD)(left * 65535); + vib.wRightMotorSpeed = (WORD)(right * 65535); + XInputSetState(gamepad, &vib); + } +} + +#elif defined(__linux__) + +/* UDev handles */ +static struct udev* UDEV = NULL; +static struct udev_monitor* MON = NULL; + +static void GamepadAddDevice(const char* devPath); +static void GamepadRemoveDevice(const char* devPath); + +/* Helper to add a new device */ +static void GamepadAddDevice(const char* devPath) { + int i; + + /* try to find a free controller */ + for (i = 0; i != GAMEPAD_COUNT; ++i) { + if ((STATE[i].flags & FLAG_CONNECTED) == 0) { + break; + } + } + if (i == GAMEPAD_COUNT) { + return; + } + + /* copy the device path */ + STATE[i].device = strdup(devPath); + if (STATE[i].device == NULL) { + return; + } + + /* reset device state */ + GamepadResetState((GAMEPAD_DEVICE)i); + + /* attempt to open the device in read-write mode, which we need fo rumble */ + STATE[i].fd = open(STATE[i].device, O_RDWR|O_NONBLOCK); + if (STATE[i].fd != -1) { + STATE[i].flags = FLAG_CONNECTED|FLAG_RUMBLE; + return; + } + + /* attempt to open in read-only mode if access was denied */ + if (errno == EACCES) { + STATE[i].fd = open(STATE[i].device, O_RDONLY|O_NONBLOCK); + if (STATE[i].fd != -1) { + STATE[i].flags = FLAG_CONNECTED; + return; + } + } + + /* could not open the device at all */ + free(STATE[i].device); + STATE[i].device = NULL; +} + +/* Helper to remove a device */ +static void GamepadRemoveDevice(const char* devPath) { + int i; + for (i = 0; i != GAMEPAD_COUNT; ++i) { + if (STATE[i].device != NULL && strcmp(STATE[i].device, devPath) == 0) { + if (STATE[i].fd != -1) { + close(STATE[i].fd); + STATE[i].fd = -1; + } + free(STATE[i].device); + STATE[i].device = 0; + STATE[i].flags = 0; + break; + } + } +} + +void GamepadInit(void) { + struct udev_list_entry* devices; + struct udev_list_entry* item; + struct udev_enumerate* enu; + int i; + + /* initialize connection state */ + for (i = 0; i != GAMEPAD_COUNT; ++i) { + STATE[i].flags = 0; + STATE[i].fd = STATE[i].effect = -1; + } + + /* open the udev handle */ + UDEV = udev_new(); + if (UDEV == NULL) { + /* FIXME: flag error? */ + return; + } + + /* open monitoring device (safe to fail) */ + MON = udev_monitor_new_from_netlink(UDEV, "udev"); + /* FIXME: flag error if hot-plugging can't be supported? */ + if (MON != NULL) { + udev_monitor_enable_receiving(MON); + udev_monitor_filter_add_match_subsystem_devtype(MON, "input", NULL); + } + + /* enumerate joypad devices */ + enu = udev_enumerate_new(UDEV); + udev_enumerate_add_match_subsystem(enu, "input"); + udev_enumerate_scan_devices(enu); + devices = udev_enumerate_get_list_entry(enu); + + udev_list_entry_foreach(item, devices) { + const char* name; + const char* sysPath; + const char* devPath; + struct udev_device* dev; + + name = udev_list_entry_get_name(item); + dev = udev_device_new_from_syspath(UDEV, name); + sysPath = udev_device_get_syspath(dev); + devPath = udev_device_get_devnode(dev); + + if (sysPath != NULL && devPath != NULL && strstr(sysPath, "/js") != 0) { + GamepadAddDevice(devPath); + } + + udev_device_unref(dev); + } + + /* cleanup */ + udev_enumerate_unref(enu); +} + +void GamepadUpdate(void) { + if (MON != NULL) { + fd_set r; + struct timeval tv; + int fd = udev_monitor_get_fd(MON); + + /* set up a poll on the udev device */ + FD_ZERO(&r); + FD_SET(fd, &r); + + tv.tv_sec = 0; + tv.tv_usec = 0; + + select(fd + 1, &r, 0, 0, &tv); + + /* test if we have a device change */ + if (FD_ISSET(fd, &r)) { + struct udev_device* dev = udev_monitor_receive_device(MON); + if (dev) { + const char* devNode = udev_device_get_devnode(dev); + const char* sysPath = udev_device_get_syspath(dev); + const char* action = udev_device_get_action(dev); + sysPath = udev_device_get_syspath(dev); + action = udev_device_get_action(dev); + + if (strstr(sysPath, "/js") != 0) { + if (strcmp(action, "remove") == 0) { + GamepadRemoveDevice(devNode); + } else if (strcmp(action, "add") == 0) { + GamepadAddDevice(devNode); + } + } + + udev_device_unref(dev); + } + } + } + + GamepadUpdateCommon(); +} + +static void GamepadUpdateDevice(GAMEPAD_DEVICE gamepad) { + if (STATE[gamepad].flags & FLAG_CONNECTED) { + struct js_event je; + while (read(STATE[gamepad].fd, &je, sizeof(je)) > 0) { + int button; + switch (je.type) { + case JS_EVENT_BUTTON: + /* determine which button the event is for */ + switch (je.number) { + case 0: button = BUTTON_A; break; + case 1: button = BUTTON_B; break; + case 2: button = BUTTON_X; break; + case 3: button = BUTTON_Y; break; + case 4: button = BUTTON_LEFT_SHOULDER; break; + case 5: button = BUTTON_RIGHT_SHOULDER; break; + case 6: button = BUTTON_BACK; break; + case 7: button = BUTTON_START; break; + case 8: button = 0; break; /* XBOX button */ + case 9: button = BUTTON_LEFT_THUMB; break; + case 10: button = BUTTON_RIGHT_THUMB; break; + default: button = 0; break; + } + + /* set or unset the button */ + if (je.value) { + STATE[gamepad].bCurrent |= BUTTON_TO_FLAG(button); + } else { + STATE[gamepad].bCurrent ^= BUTTON_TO_FLAG(button); + } + + break; + case JS_EVENT_AXIS: + /* normalize and store the axis */ + switch (je.number) { + case 0: STATE[gamepad].stick[STICK_LEFT].x = je.value; break; + case 1: STATE[gamepad].stick[STICK_LEFT].y = -je.value; break; + case 2: STATE[gamepad].trigger[TRIGGER_LEFT].value = (je.value + 32768) >> 8; break; + case 3: STATE[gamepad].stick[STICK_RIGHT].x = je.value; break; + case 4: STATE[gamepad].stick[STICK_RIGHT].y = -je.value; break; + case 5: STATE[gamepad].trigger[TRIGGER_RIGHT].value = (je.value + 32768) >> 8; break; + case 6: + if (je.value == -32767) { + STATE[gamepad].bCurrent |= BUTTON_TO_FLAG(BUTTON_DPAD_LEFT); + STATE[gamepad].bCurrent &= ~BUTTON_TO_FLAG(BUTTON_DPAD_RIGHT); + } else if (je.value == 32767) { + STATE[gamepad].bCurrent |= BUTTON_TO_FLAG(BUTTON_DPAD_RIGHT); + STATE[gamepad].bCurrent &= ~BUTTON_TO_FLAG(BUTTON_DPAD_LEFT); + } else { + STATE[gamepad].bCurrent &= ~BUTTON_TO_FLAG(BUTTON_DPAD_LEFT) & ~BUTTON_TO_FLAG(BUTTON_DPAD_RIGHT); + } + break; + case 7: + if (je.value == -32767) { + STATE[gamepad].bCurrent |= BUTTON_TO_FLAG(BUTTON_DPAD_UP); + STATE[gamepad].bCurrent &= ~BUTTON_TO_FLAG(BUTTON_DPAD_DOWN); + } else if (je.value == 32767) { + STATE[gamepad].bCurrent |= BUTTON_TO_FLAG(BUTTON_DPAD_DOWN); + STATE[gamepad].bCurrent &= ~BUTTON_TO_FLAG(BUTTON_DPAD_UP); + } else { + STATE[gamepad].bCurrent &= ~BUTTON_TO_FLAG(BUTTON_DPAD_UP) & ~BUTTON_TO_FLAG(BUTTON_DPAD_DOWN); + } + break; + default: break; + } + + break; + default: + break; + } + } + } +} + +void GamepadShutdown(void) { + int i; + + /* cleanup udev */ + udev_monitor_unref(MON); + udev_unref(UDEV); + + /* cleanup devices */ + for (i = 0; i != GAMEPAD_COUNT; ++i) { + if (STATE[i].device != NULL) { + free(STATE[i].device); + } + + if (STATE[i].fd != -1) { + close(STATE[i].fd); + } + } +} + +void GamepadSetRumble(GAMEPAD_DEVICE gamepad, float left, float right) { + if (STATE[gamepad].fd != -1) { + struct input_event play; + + /* delete any existing effect */ + if (STATE[gamepad].effect != -1) { + /* stop the effect */ + play.type = EV_FF; + play.code = STATE[gamepad].effect; + play.value = 0; + + write(STATE[gamepad].fd, (const void*)&play, sizeof(play)); + + /* delete the effect */ + ioctl(STATE[gamepad].fd, EVIOCRMFF, STATE[gamepad].effect); + } + + /* if rumble parameters are non-zero, start the new effect */ + if (left != 0.f || right != 0.f) { + struct ff_effect ff; + + /* define an effect for this rumble setting */ + ff.type = FF_RUMBLE; + ff.id = -1; + ff.u.rumble.strong_magnitude = (unsigned short)(left * 65535); + ff.u.rumble.weak_magnitude = (unsigned short)(right * 65535); + ff.replay.length = 5; + ff.replay.delay = 0; + + /* upload the effect */ + if (ioctl(STATE[gamepad].fd, EVIOCSFF, &ff) != -1) { + STATE[gamepad].effect = ff.id; + } + + /* play the effect */ + play.type = EV_FF; + play.code = STATE[gamepad].effect; + play.value = 1; + + write(STATE[gamepad].fd, (const void*)&play, sizeof(play)); + } + } +} + +#else /* !defined(_WIN32) && !defined(__linux__) */ + +# error "Unknown platform in gamepad.c" + +#endif /* end of platform implementations */ + +GAMEPAD_BOOL GamepadIsConnected(GAMEPAD_DEVICE device) { + return (STATE[device].flags & FLAG_CONNECTED) != 0 ? GAMEPAD_TRUE : GAMEPAD_FALSE; +} + +GAMEPAD_BOOL GamepadButtonDown(GAMEPAD_DEVICE device, GAMEPAD_BUTTON button) { + return (STATE[device].bCurrent & BUTTON_TO_FLAG(button)) != 0 ? GAMEPAD_TRUE : GAMEPAD_FALSE; +} + +GAMEPAD_BOOL GamepadButtonTriggered(GAMEPAD_DEVICE device, GAMEPAD_BUTTON button) { + return ((STATE[device].bLast & BUTTON_TO_FLAG(button)) == 0 && + (STATE[device].bCurrent & BUTTON_TO_FLAG(button)) != 0) ? GAMEPAD_TRUE : GAMEPAD_FALSE; +} + +GAMEPAD_BOOL GamepadButtonReleased(GAMEPAD_DEVICE device, GAMEPAD_BUTTON button) { + return ((STATE[device].bCurrent & BUTTON_TO_FLAG(button)) == 0 && + (STATE[device].bLast & BUTTON_TO_FLAG(button)) != 0) ? GAMEPAD_TRUE : GAMEPAD_FALSE; +} + +int GamepadTriggerValue(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger) { + return STATE[device].trigger[trigger].value; +} + +float GamepadTriggerLength(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger) { + return STATE[device].trigger[trigger].length; +} + +GAMEPAD_BOOL GamepadTriggerDown(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger) { + return STATE[device].trigger[trigger].pressedCurrent; +} + +GAMEPAD_BOOL GamepadTriggerTriggered(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger) { + return (STATE[device].trigger[trigger].pressedCurrent && + !STATE[device].trigger[trigger].pressedLast) ? GAMEPAD_TRUE : GAMEPAD_FALSE; +} + +GAMEPAD_BOOL GamepadTriggerReleased(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger) { + return (!STATE[device].trigger[trigger].pressedCurrent && + STATE[device].trigger[trigger].pressedLast) ? GAMEPAD_TRUE : GAMEPAD_FALSE; +} + +void GamepadStickXY(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, int *outX, int *outY) { + *outX = STATE[device].stick[stick].x; + *outY = STATE[device].stick[stick].y; +} + +float GamepadStickLength(GAMEPAD_DEVICE device, GAMEPAD_STICK stick) { + return STATE[device].stick[stick].length; +} + +void GamepadStickNormXY(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, float *outX, float *outY) { + *outX = STATE[device].stick[stick].nx; + *outY = STATE[device].stick[stick].ny; +} + +float GamepadStickAngle(GAMEPAD_DEVICE device, GAMEPAD_STICK stick) { + return STATE[device].stick[stick].angle; +} + +GAMEPAD_STICKDIR GamepadStickDir(GAMEPAD_DEVICE device, GAMEPAD_STICK stick) { + return STATE[device].stick[stick].dirCurrent; +} + +GAMEPAD_BOOL GamepadStickDirTriggered(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, GAMEPAD_STICKDIR dir) { + return (STATE[device].stick[stick].dirCurrent == dir && + STATE[device].stick[stick].dirCurrent != STATE[device].stick[stick].dirLast) ? GAMEPAD_TRUE : GAMEPAD_FALSE; +} + +/* initialize common gamepad state */ +static void GamepadResetState(GAMEPAD_DEVICE gamepad) { + memset(STATE[gamepad].stick, 0, sizeof(STATE[gamepad].stick)); + memset(STATE[gamepad].trigger, 0, sizeof(STATE[gamepad].trigger)); + STATE[gamepad].bLast = STATE[gamepad].bCurrent = 0; +} + +/* Update individual sticks */ +static void GamepadUpdateCommon(void) { + int i; + for (i = 0; i != GAMEPAD_COUNT; ++i) { + /* store previous button state */ + STATE[i].bLast = STATE[i].bCurrent; + + /* per-platform update routines */ + GamepadUpdateDevice((GAMEPAD_DEVICE)i); + + /* calculate refined stick and trigger values */ + if ((STATE[i].flags & FLAG_CONNECTED) != 0) { + GamepadUpdateStick(&STATE[i].stick[STICK_LEFT], GAMEPAD_DEADZONE_LEFT_STICK); + GamepadUpdateStick(&STATE[i].stick[STICK_RIGHT], GAMEPAD_DEADZONE_RIGHT_STICK); + + GamepadUpdateTrigger(&STATE[i].trigger[TRIGGER_LEFT]); + GamepadUpdateTrigger(&STATE[i].trigger[TRIGGER_RIGHT]); + } + } +} + +/* Update stick info */ +static void GamepadUpdateStick(GAMEPAD_AXIS* axis, float deadzone) { + // determine magnitude of stick + axis->length = sqrtf((float)(axis->x*axis->x) + (float)(axis->y*axis->y)); + + if (axis->length > deadzone) { + // clamp length to maximum value + if (axis->length > 32767.0f) { + axis->length = 32767.0f; + } + + // normalized X and Y values + axis->nx = axis->x / axis->length; + axis->ny = axis->y / axis->length; + + // adjust length for deadzone and find normalized length + axis->length -= deadzone; + axis->length /= (32767.0f - deadzone); + + // find angle of stick in radians + axis->angle = atan2f((float)axis->y, (float)axis->x); + } else { + axis->x = axis->y = 0; + axis->nx = axis->ny = 0.0f; + axis->length = axis->angle = 0.0f; + } + + /* update the stick direction */ + axis->dirLast = axis->dirCurrent; + axis->dirCurrent = STICKDIR_CENTER; + + /* check direction to see if it's non-centered */ + if (axis->length != 0.f) { + if (axis->angle >= PI_1_4 && axis->angle < PI_3_4) { + axis->dirCurrent = STICKDIR_UP; + } else if (axis->angle >= -PI_3_4 && axis->angle < -PI_1_4) { + axis->dirCurrent = STICKDIR_DOWN; + } else if (axis->angle >= PI_3_4 || axis->angle < -PI_3_4) { + axis->dirCurrent = STICKDIR_LEFT; + } else /* if (axis->angle < PI_1_4 && axis->angle >= -PI_1_4) */ { + axis->dirCurrent = STICKDIR_RIGHT; + } + } +} + +/* Update trigger info */ +static void GamepadUpdateTrigger(GAMEPAD_TRIGINFO* trig) { + trig->pressedLast = trig->pressedCurrent; + + if (trig->value > GAMEPAD_DEADZONE_TRIGGER) { + trig->length = ((trig->value - GAMEPAD_DEADZONE_TRIGGER) / (255.0f - GAMEPAD_DEADZONE_TRIGGER)); + trig->pressedCurrent = GAMEPAD_TRUE; + } else { + trig->value = 0; + trig->length = 0.0f; + trig->pressedCurrent = GAMEPAD_FALSE; + } +} diff --git a/controller/gamepad.h b/controller/gamepad.h new file mode 100644 index 0000000..4883a3f --- /dev/null +++ b/controller/gamepad.h @@ -0,0 +1,324 @@ +/** + * Gamepad Input Library + * Sean Middleditch + * Copyright (C) 2010,2011 Sean Middleditch + * LICENSE: MIT/X + */ + +#if !defined(GAMEPAD_H) +#define GAMEPAD_H 1 + +#if defined(__cplusplus) +extern "C" { +#endif + +#define GAMEPAD_STATIC_LIB +#if defined(GAMEPAD_STATIC_LIB) +# define GAMEPAD_API +#else +# if defined(_WIN32) +# if defined(GAMEPAD_EXPORT) +# define GAMEPAD_API __declspec(dllexport) +# else +# define GAMEPAD_API __declspec(dllimport) +# endif +# elif defined(__GNUC__) && defined(GAMEPAD_EXPORT) +# define GAMEPAD_API __attribute__((visibility("default"))) +# else +# define GAMEPAD_API extern +# endif +#endif + +/** + * Enumeration of the possible devices. + * + * Only four devices are supported as this is the limit of Windows. + */ +enum GAMEPAD_DEVICE { + GAMEPAD_0 = 0, /**< First gamepad */ + GAMEPAD_1 = 1, /**< Second gamepad */ + GAMEPAD_2 = 2, /**< Third gamepad */ + GAMEPAD_3 = 3, /**< Fourth gamepad */ + + GAMEPAD_COUNT /**< Maximum number of supported gamepads */ +}; + +/** + * Enumeration of the possible buttons. + */ +enum GAMEPAD_BUTTON { + BUTTON_DPAD_UP = 0, /**< UP on the direction pad */ + BUTTON_DPAD_DOWN = 1, /**< DOWN on the direction pad */ + BUTTON_DPAD_LEFT = 2, /**< LEFT on the direction pad */ + BUTTON_DPAD_RIGHT = 3, /**< RIGHT on the direction pad */ + BUTTON_START = 4, /**< START button */ + BUTTON_BACK = 5, /**< BACK button */ + BUTTON_LEFT_THUMB = 6, /**< Left analog stick button */ + BUTTON_RIGHT_THUMB = 7, /**< Right analog stick button */ + BUTTON_LEFT_SHOULDER = 8, /**< Left bumper button */ + BUTTON_RIGHT_SHOULDER = 9, /**< Right bumper button */ + BUTTON_A = 12, /**< A button */ + BUTTON_B = 13, /**< B button */ + BUTTON_X = 14, /**< X button */ + BUTTON_Y = 15, /**< Y button */ + + BUTTON_COUNT /**< Maximum number of supported buttons */ +}; + +/** + * Enumeration of the possible pressure/trigger buttons. + */ +enum GAMEPAD_TRIGGER { + TRIGGER_LEFT = 0, /**< Left trigger */ + TRIGGER_RIGHT = 1, /**< Right trigger */ + + TRIGGER_COUNT /**< Number of triggers */ +}; + +/** + * Enumeration of the analog sticks. + */ +enum GAMEPAD_STICK { + STICK_LEFT = 0, /**< Left stick */ + STICK_RIGHT = 1, /**< Right stick */ + + STICK_COUNT /**< Number of analog sticks */ +}; + +/** + * Enumeration of main stick directions. + * + * This is used for some of the convenience routines in the library. + */ +enum GAMEPAD_STICKDIR { + STICKDIR_CENTER = 0, /**< CENTER, no direction */ + STICKDIR_UP = 1, /**< UP direction */ + STICKDIR_DOWN = 2, /**< DOWN direction */ + STICKDIR_LEFT = 3, /**< LEFT direction */ + STICKDIR_RIGHT = 4, /**< RIGHT direction */ + + STICKDIR_COUNT +}; + +/** + * Enumeration for true/false values + */ +enum GAMEPAD_BOOL { + GAMEPAD_FALSE = 0, /**< FALSE value for boolean parameters */ + GAMEPAD_TRUE = 1 /**< TRUE value for boolean parameters */ +}; + +typedef enum GAMEPAD_DEVICE GAMEPAD_DEVICE; +typedef enum GAMEPAD_BUTTON GAMEPAD_BUTTON; +typedef enum GAMEPAD_TRIGGER GAMEPAD_TRIGGER; +typedef enum GAMEPAD_STICK GAMEPAD_STICK; +typedef enum GAMEPAD_STICKDIR GAMEPAD_STICKDIR; +typedef enum GAMEPAD_BOOL GAMEPAD_BOOL; + +#define GAMEPAD_DEADZONE_LEFT_STICK 7849 /**< Suggested deadzone magnitude for left analog stick */ +#define GAMEPAD_DEADZONE_RIGHT_STICK 8689 /**< Suggested deadzone magnitude for right analog stick */ +#define GAMEPAD_DEADZONE_TRIGGER 30 /**< Suggested deadzone for triggers */ + +/** + * Initialize the library. + * + * This is critical on non-Windows platforms. + */ +GAMEPAD_API void GamepadInit(void); + +/** + * Shutdown the library. + * + * This will release resources allocated by the library internally. + * + * This should be called after forking as well. + */ +GAMEPAD_API void GamepadShutdown(void); + +/** + * Updates the state of the gamepads. + * + * This must be called (at least) once per game loop. + */ +GAMEPAD_API void GamepadUpdate(void); + +/** + * Test if a particular gamepad is connected. + * + * \param device The device to check. + * \returns GAMEPAD_TRUE if the device is connected, GAMEPAD_FALSE if it is not. + */ +GAMEPAD_API GAMEPAD_BOOL GamepadIsConnected(GAMEPAD_DEVICE device); + +/** + * Test if a particular button is being pressed. + * + * \param device The device to check. + * \param button The button to check. + * \returns GAMEPAD_TRUE if the button is down, GAMEPAD_FALSE if it is not. + */ +GAMEPAD_API GAMEPAD_BOOL GamepadButtonDown(GAMEPAD_DEVICE device, GAMEPAD_BUTTON button); + +/** + * Test if a particular button has been depressed since the previous call to GamepadUpdate. + * + * \param device The device to check. + * \param button The button to check. + * \returns GAMEPAD_TRUE if the button has been pressed, GAMEPAD_FALSE if it is not or if it was depressed the previous frame. + */ +GAMEPAD_API GAMEPAD_BOOL GamepadButtonTriggered(GAMEPAD_DEVICE device, GAMEPAD_BUTTON button); + +/** + * Test if a particular button has been released since the previous call to GamepadUpdate. + * + * \param device The device to check. + * \param button The button to check. + * \returns GAMEPAD_TRUE if the button has been released, GAMEPAD_FALSE if it is down or if it was not down the previous frame. + */ +GAMEPAD_API GAMEPAD_BOOL GamepadButtonReleased(GAMEPAD_DEVICE device, GAMEPAD_BUTTON button); + +/** + * Get the trigger value (depression magnitude) in its raw form. + * + * \param device The device to check. + * \param trigger The trigger to check. + * \returns Trigger depression magnitude (0 to 32767). + */ +GAMEPAD_API int GamepadTriggerValue(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger); + +/** + * Get the trigger value (depression magnitude) in normalized form. + * + * \param device The device to check. + * \param trigger The trigger to check. + * \returns Trigger depression magnitude (0 to 1). + */ +GAMEPAD_API float GamepadTriggerLength(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger); + +/** + * Test if a trigger is depressed + * + * \param device The device to check. + * \param trigger The trigger to check. + * \returns GAMEPAD_TRUE if down, GAMEPAD_FALSE otherwise. + */ +GAMEPAD_API GAMEPAD_BOOL GamepadTriggerDown(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger); + +/** + * Test if a trigger is depressed + * + * \param device The device to check. + * \param trigger The trigger to check. + * \returns GAMEPAD_TRUE if triggered, GAMEPAD_FALSE otherwise. + */ +GAMEPAD_API GAMEPAD_BOOL GamepadTriggerTriggered(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger); + +/** + * Test if a trigger is depressed + * + * \param device The device to check. + * \param trigger The trigger to check. + * \returns GAMEPAD_TRUE if released, GAMEPAD_FALSE otherwise. + */ +GAMEPAD_API GAMEPAD_BOOL GamepadTriggerReleased(GAMEPAD_DEVICE device, GAMEPAD_TRIGGER trigger); + +/** + * Set the rumble motors on/off. + * + * To turn off the rumble effect, set values to 0 for both motors. + * + * The left motor is the low-frequency/strong motor, and the right motor is the high-frequency/weak motor. + * + * \param device The device to update. + * \param left Left motor strengh (0 to 1). + * \param right Right motor strengh (0 to 1). + */ +GAMEPAD_API void GamepadSetRumble(GAMEPAD_DEVICE device, float left, float right); + +/** + * Query the position of an analog stick as raw values. + * + * The values retrieved by this function represent the magnitude of the analog + * stick in each direction. Note that it shouldn't be possible to get full + * magnitude in one direction unless the other direction has a magnitude of + * zero, as the stick has a circular movement range. + * + * \param device The device to check. + * \param stick The stick to check. + * \param outX Pointer to integer to store the X magnitude in (-32767 to 32767). + * \param outX Pointer to integer to store the Y magnitude in (-32767 to 32767). + */ +GAMEPAD_API void GamepadStickXY(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, int* outX, int* outY); + +/** + * Query the position of an analog stick as normalized values. + * + * The values retrieved by this function represent the magnitude of the analog + * stick in each direction. Note that it shouldn't be possible to get full + * magnitude in one direction unless the other direction has a magnitude of + * zero, as the stick has a circular movement range. + * + * \param device The device to check. + * \param stick The stick to check. + * \param outX Pointer to float to store the X magnitude in (-1 to 1). + * \param outX Pointer to float to store the Y magnitude in (-1 to 1). + */ +GAMEPAD_API void GamepadStickNormXY(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, float* outX, float* outY); + +/** + * Query the magnitude of an analog stick. + * + * This returns the normalized value of the magnitude of the stick. That is, + * if the stick is pushed all the way in any direction, it returns 1.0. + * + * \param device The device to check. + * \param stick The stick to check. + * \returns The magnitude of the stick (0 to 1). + */ +GAMEPAD_API float GamepadStickLength(GAMEPAD_DEVICE device, GAMEPAD_STICK stick); + +/** + * Query the direction of a stick (in radians). + * + * This returns the direction of the stick. This value is in radians, not + * degrees. Zero is to the right, and the angle increases in a + * counter-clockwise direction. + * + * \param device The device to check. + * \param stick The stick to check. + * \returns The angle of the stick (0 to 2*PI). + */ +GAMEPAD_API float GamepadStickAngle(GAMEPAD_DEVICE device, GAMEPAD_STICK stick); + +/** + * Get the direction the stick is pushed in (if any). + * + * This is a useful utility function for when the stick should be treated as a simple + * directional pad, such as for menu UIs. + * + * \param device The device to check. + * \param stick The trigger to check. + * \returns The stick's current direction. + */ +GAMEPAD_API GAMEPAD_STICKDIR GamepadStickDir(GAMEPAD_DEVICE device, GAMEPAD_STICK stick); + +/** + * Test whether a stick has been pressed in a particular direction since the last update. + * + * This only returns true if the stick was centered last frame. + * + * This is a useful utility function for when the stick should be treated as a simple + * directional pad, such as for menu UIs. + * + * \param device The device to check. + * \param stick The trigger to check. + * \param stickdir The direction to check for. + * \returns GAMEPAD_TRUE if the stick is pressed in the specified direction, GAMEPAD_FALSE otherwise. + */ +GAMEPAD_API GAMEPAD_BOOL GamepadStickDirTriggered(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, GAMEPAD_STICKDIR dir); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif diff --git a/dll/settings.h b/dll/settings.h index d787abb..a66a8f3 100644 --- a/dll/settings.h +++ b/dll/settings.h @@ -58,6 +58,12 @@ struct Image_Data { std::string data; }; +struct Controller_Settings { + std::map, std::string>>> action_sets = {{"ship_controls", {{"analog_controls", {{"LJOY"}, "joystick_move"}}}}, {"menu_controls", {{"menu_up", {{"DLJOYUP", "DUP"}, ""}}, {"menu_down", {{"DLJOYDOWN", "DDOWN"}, ""}}}}}; + std::map action_set_layer_parents; + std::map, std::string>>> action_set_layers; +}; + class Settings { CSteamID steam_id; CGameID game_id; @@ -126,6 +132,9 @@ public: //images std::map images; int add_image(std::string data, uint32 width, uint32 height); + + //controller + struct Controller_Settings controller_settings; }; #endif diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index b2a377a..42849d3 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -18,6 +18,8 @@ #include "settings_parser.h" #include #include +#include +#include static void load_custom_broadcasts(std::string broadcasts_filepath, std::set &custom_broadcasts) { @@ -32,6 +34,77 @@ static void load_custom_broadcasts(std::string broadcasts_filepath, std::set +static void split_string(const std::string &s, char delim, Out result) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + *(result++) = item; + } +} + +static void load_gamecontroller_settings(Settings *settings) +{ + std::string path = Local_Storage::get_game_settings_path() + "controller"; + std::vector paths = Local_Storage::get_filenames_path(path); + + for (auto & p: paths) { + size_t length = p.length(); + if (length < 4) continue; + if ( std::toupper(p.back()) != 'T') continue; + if ( std::toupper(p[length - 2]) != 'X') continue; + if ( std::toupper(p[length - 3]) != 'T') continue; + if (p[length - 4] != '.') continue; + + PRINT_DEBUG("controller config %s\n", p.c_str()); + std::string action_set_name = p.substr(0, length - 4); + std::transform(action_set_name.begin(), action_set_name.end(), action_set_name.begin(),[](unsigned char c){ return std::toupper(c); }); + + std::string controller_config_path = path + PATH_SEPARATOR + p; + std::ifstream input( controller_config_path ); + if (input.is_open()) { + std::map, std::string>> button_pairs; + + for( std::string line; getline( input, line ); ) { + if (!line.empty() && line[line.length()-1] == '\n') { + line.pop_back(); + } + + if (!line.empty() && line[line.length()-1] == '\r') { + line.pop_back(); + } + + std::string action_name; + std::string button_name; + std::string source_mode; + + std::size_t deliminator = line.find("="); + if (deliminator != 0 && deliminator != std::string::npos && deliminator != line.size()) { + action_name = line.substr(0, deliminator); + std::size_t deliminator2 = line.find("=", deliminator + 1); + + if (deliminator2 != std::string::npos && deliminator2 != line.size()) { + button_name = line.substr(deliminator + 1, deliminator2 - (deliminator + 1)); + source_mode = line.substr(deliminator2 + 1); + } else { + button_name = line.substr(deliminator + 1); + source_mode = ""; + } + } + + std::transform(action_name.begin(), action_name.end(), action_name.begin(),[](unsigned char c){ return std::toupper(c); }); + std::transform(button_name.begin(), button_name.end(), button_name.begin(),[](unsigned char c){ return std::toupper(c); }); + std::pair, std::string> button_config = {{}, source_mode}; + split_string(button_name, ',', std::inserter(button_config.first, button_config.first.begin())); + button_pairs[action_name] = button_config; + PRINT_DEBUG("Added %s %s %s\n", action_name.c_str(), button_name.c_str(), source_mode.c_str()); + } + + settings->controller_settings.action_sets[action_set_name] = button_pairs; + PRINT_DEBUG("Added %u action names to %s\n", button_pairs.size(), action_set_name.c_str()); + } + } +} uint32 create_localstorage_settings(Settings **settings_client_out, Settings **settings_server_out, Local_Storage **local_storage_out) { @@ -386,6 +459,8 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s } } + load_gamecontroller_settings(settings_client); + *settings_client_out = settings_client; *settings_server_out = settings_server; *local_storage_out = local_storage; diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 38d86a0..d81f90c 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -66,7 +66,7 @@ Steam_Client::Steam_Client() steam_remote_storage = new Steam_Remote_Storage(settings_client, local_storage, callback_results_client); steam_screenshots = new Steam_Screenshots(); steam_http = new Steam_HTTP(settings_client, network, callback_results_client, callbacks_client); - steam_controller = new Steam_Controller(); + steam_controller = new Steam_Controller(settings_client, callback_results_client, callbacks_client, run_every_runcb); steam_ugc = new Steam_UGC(settings_client, callback_results_client, callbacks_client); steam_applist = new Steam_Applist(); steam_music = new Steam_Music(callbacks_client); diff --git a/dll/steam_client.h b/dll/steam_client.h index 36f40a0..32e77e8 100644 --- a/dll/steam_client.h +++ b/dll/steam_client.h @@ -27,7 +27,11 @@ #include "steam_remote_storage.h" #include "steam_screenshots.h" #include "steam_http.h" +#ifdef CONTROLLER_SUPPORT #include "steam_controller.h" +#else +#include "steam_controller_disabled.h" +#endif #include "steam_ugc.h" #include "steam_applist.h" #include "steam_music.h" diff --git a/dll/steam_controller.h b/dll/steam_controller.h index 51333b6..43305a0 100644 --- a/dll/steam_controller.h +++ b/dll/steam_controller.h @@ -16,6 +16,55 @@ . */ #include "base.h" +#include "../controller/gamepad.h" +#include + +struct Controller_Map { + std::map> active_digital; + std::map, enum EInputSourceMode>> active_analog; +}; + +struct Controller_Action { + ControllerHandle_t controller_handle; + struct Controller_Map active_map; + ControllerDigitalActionHandle_t active_set; + + Controller_Action(ControllerHandle_t controller_handle) { + this->controller_handle = controller_handle; + } + + void activate_action_set(ControllerDigitalActionHandle_t active_set, std::map &controller_maps) { + auto map = controller_maps.find(active_set); + if (map == controller_maps.end()) return; + this->active_set = active_set; + this->active_map = map->second; + } + + std::set button_id(ControllerDigitalActionHandle_t handle) { + auto a = active_map.active_digital.find(handle); + if (a == active_map.active_digital.end()) return {}; + return a->second; + } + + std::pair, enum EInputSourceMode> analog_id(ControllerAnalogActionHandle_t handle) { + auto a = active_map.active_analog.find(handle); + if (a == active_map.active_analog.end()) return std::pair, enum EInputSourceMode>({}, k_EInputSourceMode_None); + return a->second; + } +}; + +enum EXTRA_GAMEPAD_BUTTONS { + BUTTON_LTRIGGER = BUTTON_COUNT + 1, + BUTTON_RTRIGGER = BUTTON_COUNT + 2, + BUTTON_STICK_LEFT_UP = BUTTON_COUNT + 3, + BUTTON_STICK_LEFT_DOWN = BUTTON_COUNT + 4, + BUTTON_STICK_LEFT_LEFT = BUTTON_COUNT + 5, + BUTTON_STICK_LEFT_RIGHT = BUTTON_COUNT + 6, + BUTTON_STICK_RIGHT_UP = BUTTON_COUNT + 7, + BUTTON_STICK_RIGHT_DOWN = BUTTON_COUNT + 8, + BUTTON_STICK_RIGHT_LEFT = BUTTON_COUNT + 9, + BUTTON_STICK_RIGHT_RIGHT = BUTTON_COUNT + 10, +}; class Steam_Controller : public ISteamController001, @@ -26,12 +75,154 @@ public ISteamController006, public ISteamController, public ISteamInput { + class Settings *settings; + class SteamCallResults *callback_results; + class SteamCallBacks *callbacks; + class RunEveryRunCB *run_every_runcb; + + std::map button_strings = { + {"DUP", BUTTON_DPAD_UP}, + {"DDOWN", BUTTON_DPAD_DOWN}, + {"DLEFT", BUTTON_DPAD_LEFT}, + {"DRIGHT", BUTTON_DPAD_RIGHT}, + {"START", BUTTON_START}, + {"BACK", BUTTON_BACK}, + {"LSTICK", BUTTON_LEFT_THUMB}, + {"RSTICK", BUTTON_RIGHT_THUMB}, + {"LBUMPER", BUTTON_LEFT_SHOULDER}, + {"RBUMPER", BUTTON_RIGHT_SHOULDER}, + {"A", BUTTON_A}, + {"B", BUTTON_B}, + {"X", BUTTON_X}, + {"Y", BUTTON_Y}, + {"DLTRIGGER", BUTTON_LTRIGGER}, + {"DRTRIGGER", BUTTON_RTRIGGER}, + {"DLJOYUP", BUTTON_STICK_LEFT_UP}, + {"DLJOYDOWN", BUTTON_STICK_LEFT_DOWN}, + {"DLJOYLEFT", BUTTON_STICK_LEFT_LEFT}, + {"DLJOYRIGHT", BUTTON_STICK_LEFT_RIGHT}, + {"DRJOYUP", BUTTON_STICK_RIGHT_UP}, + {"DRJOYDOWN", BUTTON_STICK_RIGHT_DOWN}, + {"DRJOYLEFT", BUTTON_STICK_RIGHT_LEFT}, + {"DRJOYRIGHT", BUTTON_STICK_RIGHT_RIGHT}, + }; + + std::map analog_strings = { + {"LTRIGGER", TRIGGER_LEFT}, + {"RTRIGGER", TRIGGER_RIGHT}, + {"LJOY", STICK_LEFT + 10}, + {"RJOY", STICK_RIGHT + 10}, + }; + + std::map analog_input_modes = { + {"joystick_move", k_EInputSourceMode_JoystickMove}, + {"joystick_camera", k_EInputSourceMode_JoystickCamera}, + {"trigger", k_EInputSourceMode_Trigger}, + }; + + std::map action_handles; + std::map digital_action_handles; + std::map analog_action_handles; + + std::map controller_maps; + std::map controllers; + + bool disabled; + + void set_handles(std::map, std::string>>> action_sets) { + uint64 handle_num = 1; + for (auto & set : action_sets) { + ControllerActionSetHandle_t action_handle_num = handle_num; + ++handle_num; + + action_handles[set.first] = action_handle_num; + for (auto & config_key : set.second) { + uint64 current_handle_num = handle_num; + ++handle_num; + + for (auto & button_string : config_key.second.first) { + auto digital = button_strings.find(button_string); + if (digital != button_strings.end()) { + ControllerDigitalActionHandle_t digital_handle_num = current_handle_num; + + digital_action_handles[config_key.first] = digital_handle_num; + controller_maps[action_handle_num].active_digital[digital_handle_num].insert(digital->second); + } else { + auto analog = analog_strings.find(button_string); + if (analog != analog_strings.end()) { + ControllerAnalogActionHandle_t analog_handle_num = current_handle_num; + + enum EInputSourceMode source_mode; + if (analog->second == TRIGGER_LEFT || analog->second == TRIGGER_RIGHT) { + source_mode = k_EInputSourceMode_Trigger; + } else { + source_mode = k_EInputSourceMode_JoystickMove; + } + + auto input_mode = analog_input_modes.find(config_key.second.second); + if (input_mode != analog_input_modes.end()) { + source_mode = input_mode->second; + } + + analog_action_handles[config_key.first] = analog_handle_num; + controller_maps[action_handle_num].active_analog[analog_handle_num].first.insert(analog->second); + controller_maps[action_handle_num].active_analog[analog_handle_num].second = source_mode; + + } else { + PRINT_DEBUG("Did not recognize controller button %s\n", button_string.c_str()); + continue; + } + } + } + } + } + } + public: - + +static void steam_run_every_runcb(void *object) +{ + PRINT_DEBUG("steam_controller_run_every_runcb\n"); + + Steam_Controller *steam_controller = (Steam_Controller *)object; + steam_controller->RunCallbacks(); +} + +Steam_Controller(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +{ + this->settings = settings; + this->run_every_runcb = run_every_runcb; + this->run_every_runcb->add(&Steam_Controller::steam_run_every_runcb, this); + + this->callback_results = callback_results; + this->callbacks = callbacks; + + set_handles(settings->controller_settings.action_sets); + disabled = !action_handles.size(); +} + +~Steam_Controller() +{ + //TODO rm network callbacks + this->run_every_runcb->remove(&Steam_Controller::steam_run_every_runcb, this); +} + // Init and Shutdown must be called when starting/ending use of this interface bool Init() { PRINT_DEBUG("Steam_Controller::Init()\n"); + std::lock_guard lock(global_mutex); + if (disabled) { + return true; + } + + GamepadInit(); + GamepadUpdate(); + + for (int i = 1; i < 5; ++i) { + controllers.insert(std::pair(i, Controller_Action(i))); + } + return true; } @@ -44,6 +235,12 @@ bool Init( const char *pchAbsolutePathToControllerConfigVDF ) bool Shutdown() { PRINT_DEBUG("Steam_Controller::Shutdown()\n"); + std::lock_guard lock(global_mutex); + if (disabled) { + return true; + } + + GamepadShutdown(); return true; } @@ -58,6 +255,11 @@ void SetOverrideMode( const char *pchMode ) void RunFrame() { PRINT_DEBUG("Steam_Controller::RunFrame()\n"); + if (disabled) { + return; + } + + GamepadUpdate(); } bool GetControllerState( uint32 unControllerIndex, SteamControllerState001_t *pState ) @@ -71,8 +273,20 @@ bool GetControllerState( uint32 unControllerIndex, SteamControllerState001_t *pS // Returns the number of handles written to handlesOut int GetConnectedControllers( ControllerHandle_t *handlesOut ) { - PRINT_DEBUG("GetConnectedControllers\n"); - return 0; + PRINT_DEBUG("Steam_Controller::GetConnectedControllers\n"); + if (!handlesOut) return 0; + if (disabled) { + return 0; + } + + int count = 0; + if (GamepadIsConnected(GAMEPAD_0)) {*handlesOut = GAMEPAD_0 + 1; ++handlesOut; ++count;}; + if (GamepadIsConnected(GAMEPAD_1)) {*handlesOut = GAMEPAD_1 + 1; ++handlesOut; ++count;}; + if (GamepadIsConnected(GAMEPAD_2)) {*handlesOut = GAMEPAD_2 + 1; ++handlesOut; ++count;}; + if (GamepadIsConnected(GAMEPAD_3)) {*handlesOut = GAMEPAD_3 + 1; ++handlesOut; ++count;}; + + PRINT_DEBUG("returned %i connected controllers\n", count); + return count; } @@ -80,7 +294,7 @@ int GetConnectedControllers( ControllerHandle_t *handlesOut ) // Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode bool ShowBindingPanel( ControllerHandle_t controllerHandle ) { - PRINT_DEBUG("ShowBindingPanel\n"); + PRINT_DEBUG("Steam_Controller::ShowBindingPanel\n"); return false; } @@ -89,8 +303,15 @@ bool ShowBindingPanel( ControllerHandle_t controllerHandle ) // Lookup the handle for an Action Set. Best to do this once on startup, and store the handles for all future API calls. ControllerActionSetHandle_t GetActionSetHandle( const char *pszActionSetName ) { - PRINT_DEBUG("GetActionSetHandle %s\n", pszActionSetName); - return 124; + PRINT_DEBUG("Steam_Controller::GetActionSetHandle %s\n", pszActionSetName); + if (!pszActionSetName) return 0; + std::string upper_action_name(pszActionSetName); + std::transform(upper_action_name.begin(), upper_action_name.end(), upper_action_name.begin(),[](unsigned char c){ return std::toupper(c); }); + + auto set_handle = action_handles.find(upper_action_name); + if (set_handle == action_handles.end()) return 0; + + return set_handle->second; } @@ -99,34 +320,41 @@ ControllerActionSetHandle_t GetActionSetHandle( const char *pszActionSetName ) // your state loops, instead of trying to place it in all of your state transitions. void ActivateActionSet( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle ) { - PRINT_DEBUG("ActivateActionSet\n"); + PRINT_DEBUG("Steam_Controller::ActivateActionSet %llu %llu\n", controllerHandle, actionSetHandle); + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return; + + controller->second.activate_action_set(actionSetHandle, controller_maps); } ControllerActionSetHandle_t GetCurrentActionSet( ControllerHandle_t controllerHandle ) { - PRINT_DEBUG("GetCurrentActionSet\n"); - return 124; + PRINT_DEBUG("Steam_Controller::GetCurrentActionSet %llu\n", controllerHandle); + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return 0; + + return controller->second.active_set; } void ActivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) { - PRINT_DEBUG("ActivateActionSetLayer\n"); + PRINT_DEBUG("Steam_Controller::ActivateActionSetLayer\n"); } void DeactivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) { - PRINT_DEBUG("DeactivateActionSetLayer\n"); + PRINT_DEBUG("Steam_Controller::DeactivateActionSetLayer\n"); } void DeactivateAllActionSetLayers( ControllerHandle_t controllerHandle ) { - PRINT_DEBUG("DeactivateAllActionSetLayers\n"); + PRINT_DEBUG("Steam_Controller::DeactivateAllActionSetLayers\n"); } int GetActiveActionSetLayers( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t *handlesOut ) { - PRINT_DEBUG("GetActiveActionSetLayers\n"); + PRINT_DEBUG("Steam_Controller::GetActiveActionSetLayers\n"); return 0; } @@ -136,17 +364,72 @@ int GetActiveActionSetLayers( ControllerHandle_t controllerHandle, ControllerAct // Lookup the handle for a digital action. Best to do this once on startup, and store the handles for all future API calls. ControllerDigitalActionHandle_t GetDigitalActionHandle( const char *pszActionName ) { - PRINT_DEBUG("GetDigitalActionHandle %s\n", pszActionName); - return 123; + PRINT_DEBUG("Steam_Controller::GetDigitalActionHandle %s\n", pszActionName); + if (!pszActionName) return 0; + std::string upper_action_name(pszActionName); + std::transform(upper_action_name.begin(), upper_action_name.end(), upper_action_name.begin(),[](unsigned char c){ return std::toupper(c); }); + + auto handle = digital_action_handles.find(upper_action_name); + if (handle == digital_action_handles.end()) return 0; + + return handle->second; } // Returns the current state of the supplied digital game action ControllerDigitalActionData_t GetDigitalActionData( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle ) { - PRINT_DEBUG("GetDigitalActionData\n"); + PRINT_DEBUG("Steam_Controller::GetDigitalActionData %llu %llu\n", controllerHandle, digitalActionHandle); ControllerDigitalActionData_t digitalData; digitalData.bActive = false; + digitalData.bState = false; + + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return digitalData; + + std::set buttons = controller->second.button_id(digitalActionHandle); + if (!buttons.size()) return digitalData; + digitalData.bActive = true; + + GAMEPAD_DEVICE device = (GAMEPAD_DEVICE)(controllerHandle - 1); + + for (auto button : buttons) { + bool pressed = false; + if (button < BUTTON_COUNT) { + pressed = GamepadButtonDown(device, (GAMEPAD_BUTTON)button); + } else { + switch (button) { + case BUTTON_LTRIGGER: + pressed = GamepadTriggerLength(device, TRIGGER_LEFT) > 0.8; + break; + case BUTTON_RTRIGGER: + pressed = GamepadTriggerLength(device, TRIGGER_RIGHT) > 0.8; + break; + case BUTTON_STICK_LEFT_UP: + case BUTTON_STICK_LEFT_DOWN: + case BUTTON_STICK_LEFT_LEFT: + case BUTTON_STICK_LEFT_RIGHT: + pressed = GamepadStickLength(device, STICK_LEFT) > 0.1 && + ((int)GamepadStickDir(device, STICK_LEFT) == ((button - BUTTON_STICK_LEFT_UP) + 1)); + break; + case BUTTON_STICK_RIGHT_UP: + case BUTTON_STICK_RIGHT_DOWN: + case BUTTON_STICK_RIGHT_LEFT: + case BUTTON_STICK_RIGHT_RIGHT: + pressed = GamepadStickLength(device, STICK_RIGHT) > 0.1 && + ((int)GamepadStickDir(device, STICK_RIGHT) == ((button - BUTTON_STICK_RIGHT_UP) + 1)); + break; + default: + break; + } + } + + if (pressed) { + digitalData.bState = true; + break; + } + } + return digitalData; } @@ -155,32 +438,58 @@ ControllerDigitalActionData_t GetDigitalActionData( ControllerHandle_t controlle // originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles int GetDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerDigitalActionHandle_t digitalActionHandle, EControllerActionOrigin *originsOut ) { - PRINT_DEBUG("GetDigitalActionOrigins\n"); + PRINT_DEBUG("Steam_Controller::GetDigitalActionOrigins\n"); return 0; } int GetDigitalActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, EInputActionOrigin *originsOut ) { - PRINT_DEBUG("GetDigitalActionOrigins steaminput\n"); + PRINT_DEBUG("Steam_Controller::GetDigitalActionOrigins steaminput\n"); return 0; } // Lookup the handle for an analog action. Best to do this once on startup, and store the handles for all future API calls. ControllerAnalogActionHandle_t GetAnalogActionHandle( const char *pszActionName ) { - PRINT_DEBUG("GetAnalogActionHandle %s\n", pszActionName); - return 125; + PRINT_DEBUG("Steam_Controller::GetAnalogActionHandle %s\n", pszActionName); + if (!pszActionName) return 0; + std::string upper_action_name(pszActionName); + std::transform(upper_action_name.begin(), upper_action_name.end(), upper_action_name.begin(),[](unsigned char c){ return std::toupper(c); }); + + auto handle = analog_action_handles.find(upper_action_name); + if (handle == analog_action_handles.end()) return 0; + + return handle->second; } // Returns the current state of these supplied analog game action ControllerAnalogActionData_t GetAnalogActionData( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle ) { - PRINT_DEBUG("GetAnalogActionData\n"); + PRINT_DEBUG("Steam_Controller::GetAnalogActionData %llu %llu\n", controllerHandle, analogActionHandle); ControllerAnalogActionData_t data; data.eMode = k_EInputSourceMode_None; data.x = data.y = 0; data.bActive = false; + + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return data; + + auto analog = controller->second.analog_id(analogActionHandle); + if (!analog.first.size()) return data; + + data.bActive = true; + data.eMode = analog.second; + + for (auto a : analog.first) { + if (a >= 10) { + int joystick_id = a - 10; + GamepadStickNormXY((GAMEPAD_DEVICE)(controllerHandle - 1), (GAMEPAD_STICK) joystick_id, &data.x, &data.y); + } else { + data.x = GamepadTriggerLength((GAMEPAD_DEVICE)(controllerHandle - 1), (GAMEPAD_TRIGGER) a); + } + } + return data; } @@ -189,32 +498,32 @@ ControllerAnalogActionData_t GetAnalogActionData( ControllerHandle_t controllerH // originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles int GetAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerAnalogActionHandle_t analogActionHandle, EControllerActionOrigin *originsOut ) { - PRINT_DEBUG("GetAnalogActionOrigins\n"); + PRINT_DEBUG("Steam_Controller::GetAnalogActionOrigins\n"); return 0; } int GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, EInputActionOrigin *originsOut ) { - PRINT_DEBUG("GetAnalogActionOrigins steaminput\n"); + PRINT_DEBUG("Steam_Controller::GetAnalogActionOrigins steaminput\n"); return 0; } void StopAnalogActionMomentum( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t eAction ) { - PRINT_DEBUG("StopAnalogActionMomentum\n"); + PRINT_DEBUG("Steam_Controller::StopAnalogActionMomentum\n"); } // Trigger a haptic pulse on a controller void TriggerHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) { - PRINT_DEBUG("TriggerHapticPulse\n"); + PRINT_DEBUG("Steam_Controller::TriggerHapticPulse\n"); } void TriggerHapticPulse( uint32 unControllerIndex, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) { - PRINT_DEBUG("TriggerHapticPulse old\n"); + PRINT_DEBUG("Steam_Controller::TriggerHapticPulse old\n"); TriggerHapticPulse(unControllerIndex, eTargetPad, usDurationMicroSec ); } @@ -222,28 +531,32 @@ void TriggerHapticPulse( uint32 unControllerIndex, ESteamControllerPad eTargetPa // nFlags is currently unused and reserved for future use. void TriggerRepeatedHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags ) { - PRINT_DEBUG("TriggerRepeatedHapticPulse\n"); + PRINT_DEBUG("Steam_Controller::TriggerRepeatedHapticPulse\n"); } // Tigger a vibration event on supported controllers. void TriggerVibration( ControllerHandle_t controllerHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed ) { - PRINT_DEBUG("TriggerVibration\n"); + PRINT_DEBUG("Steam_Controller::TriggerVibration %hu %hu\n", usLeftSpeed, usRightSpeed); + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return; + + GamepadSetRumble((GAMEPAD_DEVICE)(controllerHandle - 1), ((double)usLeftSpeed) / 65535.0, ((double)usRightSpeed) / 65535.0); } // Set the controller LED color on supported controllers. void SetLEDColor( ControllerHandle_t controllerHandle, uint8 nColorR, uint8 nColorG, uint8 nColorB, unsigned int nFlags ) { - PRINT_DEBUG("SetLEDColor\n"); + PRINT_DEBUG("Steam_Controller::SetLEDColor\n"); } // Returns the associated gamepad index for the specified controller, if emulating a gamepad int GetGamepadIndexForController( ControllerHandle_t ulControllerHandle ) { - PRINT_DEBUG("GetGamepadIndexForController\n"); + PRINT_DEBUG("Steam_Controller::GetGamepadIndexForController\n"); return 0; } @@ -251,7 +564,7 @@ int GetGamepadIndexForController( ControllerHandle_t ulControllerHandle ) // Returns the associated controller handle for the specified emulated gamepad ControllerHandle_t GetControllerForGamepadIndex( int nIndex ) { - PRINT_DEBUG("GetControllerForGamepadIndex\n"); + PRINT_DEBUG("Steam_Controller::GetControllerForGamepadIndex\n"); return 0; } @@ -259,7 +572,7 @@ ControllerHandle_t GetControllerForGamepadIndex( int nIndex ) // Returns raw motion data from the specified controller ControllerMotionData_t GetMotionData( ControllerHandle_t controllerHandle ) { - PRINT_DEBUG("GetMotionData\n"); + PRINT_DEBUG("Steam_Controller::GetMotionData\n"); ControllerMotionData_t data = {}; return data; } @@ -269,13 +582,13 @@ ControllerMotionData_t GetMotionData( ControllerHandle_t controllerHandle ) // Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode bool ShowDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle, float flScale, float flXPosition, float flYPosition ) { - PRINT_DEBUG("ShowDigitalActionOrigins\n"); + PRINT_DEBUG("Steam_Controller::ShowDigitalActionOrigins\n"); return true; } bool ShowAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle, float flScale, float flXPosition, float flYPosition ) { - PRINT_DEBUG("ShowAnalogActionOrigins\n"); + PRINT_DEBUG("Steam_Controller::ShowAnalogActionOrigins\n"); return true; } @@ -283,13 +596,13 @@ bool ShowAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerAna // Returns a localized string (from Steam's language setting) for the specified origin const char *GetStringForActionOrigin( EControllerActionOrigin eOrigin ) { - PRINT_DEBUG("GetStringForActionOrigin\n"); + PRINT_DEBUG("Steam_Controller::GetStringForActionOrigin\n"); return "Button String"; } const char *GetStringForActionOrigin( EInputActionOrigin eOrigin ) { - PRINT_DEBUG("GetStringForActionOrigin steaminput\n"); + PRINT_DEBUG("Steam_Controller::GetStringForActionOrigin steaminput\n"); return "Button String"; } @@ -297,75 +610,82 @@ const char *GetStringForActionOrigin( EInputActionOrigin eOrigin ) // Get a local path to art for on-screen glyph for a particular origin const char *GetGlyphForActionOrigin( EControllerActionOrigin eOrigin ) { - PRINT_DEBUG("GetGlyphForActionOrigin\n"); + PRINT_DEBUG("Steam_Controller::GetGlyphForActionOrigin\n"); return ""; } const char *GetGlyphForActionOrigin( EInputActionOrigin eOrigin ) { - PRINT_DEBUG("GetGlyphForActionOrigin steaminput\n"); + PRINT_DEBUG("Steam_Controller::GetGlyphForActionOrigin steaminput\n"); return ""; } // Returns the input type for a particular handle ESteamInputType GetInputTypeForHandle( ControllerHandle_t controllerHandle ) { - PRINT_DEBUG("GetInputTypeForHandle\n"); - return k_ESteamInputType_Unknown; + PRINT_DEBUG("Steam_Controller::GetInputTypeForHandle\n"); + auto controller = controllers.find(controllerHandle); + if (controller == controllers.end()) return k_ESteamInputType_Unknown; + return k_ESteamInputType_XBox360Controller; } const char *GetStringForXboxOrigin( EXboxOrigin eOrigin ) { - PRINT_DEBUG("GetStringForXboxOrigin\n"); + PRINT_DEBUG("Steam_Controller::GetStringForXboxOrigin\n"); return ""; } const char *GetGlyphForXboxOrigin( EXboxOrigin eOrigin ) { - PRINT_DEBUG("GetGlyphForXboxOrigin\n"); + PRINT_DEBUG("Steam_Controller::GetGlyphForXboxOrigin\n"); return ""; } EControllerActionOrigin GetActionOriginFromXboxOrigin_( ControllerHandle_t controllerHandle, EXboxOrigin eOrigin ) { - PRINT_DEBUG("GetActionOriginFromXboxOrigin\n"); + PRINT_DEBUG("Steam_Controller::GetActionOriginFromXboxOrigin\n"); return k_EControllerActionOrigin_None; } EInputActionOrigin GetActionOriginFromXboxOrigin( InputHandle_t inputHandle, EXboxOrigin eOrigin ) { - PRINT_DEBUG("GetActionOriginFromXboxOrigin steaminput\n"); + PRINT_DEBUG("Steam_Controller::GetActionOriginFromXboxOrigin steaminput\n"); return k_EInputActionOrigin_None; } EControllerActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EControllerActionOrigin eSourceOrigin ) { - PRINT_DEBUG("TranslateActionOrigin\n"); + PRINT_DEBUG("Steam_Controller::TranslateActionOrigin\n"); return k_EControllerActionOrigin_None; } EInputActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EInputActionOrigin eSourceOrigin ) { - PRINT_DEBUG("TranslateActionOrigin steaminput\n"); + PRINT_DEBUG("Steam_Controller::TranslateActionOrigin steaminput\n"); return k_EInputActionOrigin_None; } bool GetControllerBindingRevision( ControllerHandle_t controllerHandle, int *pMajor, int *pMinor ) { - PRINT_DEBUG("GetControllerBindingRevision\n"); + PRINT_DEBUG("Steam_Controller::GetControllerBindingRevision\n"); return false; } bool GetDeviceBindingRevision( InputHandle_t inputHandle, int *pMajor, int *pMinor ) { - PRINT_DEBUG("GetDeviceBindingRevision\n"); + PRINT_DEBUG("Steam_Controller::GetDeviceBindingRevision\n"); return false; } uint32 GetRemotePlaySessionID( InputHandle_t inputHandle ) { - PRINT_DEBUG("GetRemotePlaySessionID\n"); + PRINT_DEBUG("Steam_Controller::GetRemotePlaySessionID\n"); return 0; } +void RunCallbacks() +{ + RunFrame(); +} + }; diff --git a/dll/steam_controller_disabled.h b/dll/steam_controller_disabled.h new file mode 100644 index 0000000..825ff0b --- /dev/null +++ b/dll/steam_controller_disabled.h @@ -0,0 +1,375 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#include "base.h" + +class Steam_Controller : +public ISteamController001, +public ISteamController003, +public ISteamController004, +public ISteamController005, +public ISteamController006, +public ISteamController, +public ISteamInput +{ +public: + +Steam_Controller(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +{ +} + +// Init and Shutdown must be called when starting/ending use of this interface +bool Init() +{ + PRINT_DEBUG("Steam_Controller::Init()\n"); + return true; +} + +bool Init( const char *pchAbsolutePathToControllerConfigVDF ) +{ + PRINT_DEBUG("Steam_Controller::Init() old\n"); + return Init(); +} + +bool Shutdown() +{ + PRINT_DEBUG("Steam_Controller::Shutdown()\n"); + return true; +} + +void SetOverrideMode( const char *pchMode ) +{ + PRINT_DEBUG("Steam_Controller::SetOverrideMode\n"); +} + +// Synchronize API state with the latest Steam Controller inputs available. This +// is performed automatically by SteamAPI_RunCallbacks, but for the absolute lowest +// possible latency, you call this directly before reading controller state. +void RunFrame() +{ + PRINT_DEBUG("Steam_Controller::RunFrame()\n"); +} + +bool GetControllerState( uint32 unControllerIndex, SteamControllerState001_t *pState ) +{ + PRINT_DEBUG("Steam_Controller::GetControllerState()\n"); + return false; +} + +// Enumerate currently connected controllers +// handlesOut should point to a STEAM_CONTROLLER_MAX_COUNT sized array of ControllerHandle_t handles +// Returns the number of handles written to handlesOut +int GetConnectedControllers( ControllerHandle_t *handlesOut ) +{ + PRINT_DEBUG("GetConnectedControllers\n"); + return 0; +} + + +// Invokes the Steam overlay and brings up the binding screen +// Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode +bool ShowBindingPanel( ControllerHandle_t controllerHandle ) +{ + PRINT_DEBUG("ShowBindingPanel\n"); + return false; +} + + +// ACTION SETS +// Lookup the handle for an Action Set. Best to do this once on startup, and store the handles for all future API calls. +ControllerActionSetHandle_t GetActionSetHandle( const char *pszActionSetName ) +{ + PRINT_DEBUG("GetActionSetHandle %s\n", pszActionSetName); + return 124; +} + + +// Reconfigure the controller to use the specified action set (ie 'Menu', 'Walk' or 'Drive') +// This is cheap, and can be safely called repeatedly. It's often easier to repeatedly call it in +// your state loops, instead of trying to place it in all of your state transitions. +void ActivateActionSet( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle ) +{ + PRINT_DEBUG("ActivateActionSet\n"); +} + +ControllerActionSetHandle_t GetCurrentActionSet( ControllerHandle_t controllerHandle ) +{ + PRINT_DEBUG("GetCurrentActionSet\n"); + return 124; +} + + +void ActivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) +{ + PRINT_DEBUG("ActivateActionSetLayer\n"); +} + +void DeactivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle ) +{ + PRINT_DEBUG("DeactivateActionSetLayer\n"); +} + +void DeactivateAllActionSetLayers( ControllerHandle_t controllerHandle ) +{ + PRINT_DEBUG("DeactivateAllActionSetLayers\n"); +} + +int GetActiveActionSetLayers( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t *handlesOut ) +{ + PRINT_DEBUG("GetActiveActionSetLayers\n"); + return 0; +} + + + +// ACTIONS +// Lookup the handle for a digital action. Best to do this once on startup, and store the handles for all future API calls. +ControllerDigitalActionHandle_t GetDigitalActionHandle( const char *pszActionName ) +{ + PRINT_DEBUG("GetDigitalActionHandle %s\n", pszActionName); + return 123; +} + + +// Returns the current state of the supplied digital game action +ControllerDigitalActionData_t GetDigitalActionData( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle ) +{ + PRINT_DEBUG("GetDigitalActionData\n"); + ControllerDigitalActionData_t digitalData; + digitalData.bActive = false; + return digitalData; +} + + +// Get the origin(s) for a digital action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action. +// originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles +int GetDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerDigitalActionHandle_t digitalActionHandle, EControllerActionOrigin *originsOut ) +{ + PRINT_DEBUG("GetDigitalActionOrigins\n"); + return 0; +} + +int GetDigitalActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, EInputActionOrigin *originsOut ) +{ + PRINT_DEBUG("GetDigitalActionOrigins steaminput\n"); + return 0; +} + +// Lookup the handle for an analog action. Best to do this once on startup, and store the handles for all future API calls. +ControllerAnalogActionHandle_t GetAnalogActionHandle( const char *pszActionName ) +{ + PRINT_DEBUG("GetAnalogActionHandle %s\n", pszActionName); + return 125; +} + + +// Returns the current state of these supplied analog game action +ControllerAnalogActionData_t GetAnalogActionData( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle ) +{ + PRINT_DEBUG("GetAnalogActionData\n"); + ControllerAnalogActionData_t data; + data.eMode = k_EInputSourceMode_None; + data.x = data.y = 0; + data.bActive = false; + return data; +} + + +// Get the origin(s) for an analog action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action. +// originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles +int GetAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerAnalogActionHandle_t analogActionHandle, EControllerActionOrigin *originsOut ) +{ + PRINT_DEBUG("GetAnalogActionOrigins\n"); + return 0; +} + +int GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, EInputActionOrigin *originsOut ) +{ + PRINT_DEBUG("GetAnalogActionOrigins steaminput\n"); + return 0; +} + + +void StopAnalogActionMomentum( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t eAction ) +{ + PRINT_DEBUG("StopAnalogActionMomentum\n"); +} + + +// Trigger a haptic pulse on a controller +void TriggerHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) +{ + PRINT_DEBUG("TriggerHapticPulse\n"); +} + +void TriggerHapticPulse( uint32 unControllerIndex, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec ) +{ + PRINT_DEBUG("TriggerHapticPulse old\n"); + TriggerHapticPulse(unControllerIndex, eTargetPad, usDurationMicroSec ); +} + +// Trigger a pulse with a duty cycle of usDurationMicroSec / usOffMicroSec, unRepeat times. +// nFlags is currently unused and reserved for future use. +void TriggerRepeatedHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags ) +{ + PRINT_DEBUG("TriggerRepeatedHapticPulse\n"); +} + + +// Tigger a vibration event on supported controllers. +void TriggerVibration( ControllerHandle_t controllerHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed ) +{ + PRINT_DEBUG("TriggerVibration\n"); +} + + +// Set the controller LED color on supported controllers. +void SetLEDColor( ControllerHandle_t controllerHandle, uint8 nColorR, uint8 nColorG, uint8 nColorB, unsigned int nFlags ) +{ + PRINT_DEBUG("SetLEDColor\n"); +} + + +// Returns the associated gamepad index for the specified controller, if emulating a gamepad +int GetGamepadIndexForController( ControllerHandle_t ulControllerHandle ) +{ + PRINT_DEBUG("GetGamepadIndexForController\n"); + return 0; +} + + +// Returns the associated controller handle for the specified emulated gamepad +ControllerHandle_t GetControllerForGamepadIndex( int nIndex ) +{ + PRINT_DEBUG("GetControllerForGamepadIndex\n"); + return 0; +} + + +// Returns raw motion data from the specified controller +ControllerMotionData_t GetMotionData( ControllerHandle_t controllerHandle ) +{ + PRINT_DEBUG("GetMotionData\n"); + ControllerMotionData_t data = {}; + return data; +} + + +// Attempt to display origins of given action in the controller HUD, for the currently active action set +// Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode +bool ShowDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle, float flScale, float flXPosition, float flYPosition ) +{ + PRINT_DEBUG("ShowDigitalActionOrigins\n"); + return true; +} + +bool ShowAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle, float flScale, float flXPosition, float flYPosition ) +{ + PRINT_DEBUG("ShowAnalogActionOrigins\n"); + return true; +} + + +// Returns a localized string (from Steam's language setting) for the specified origin +const char *GetStringForActionOrigin( EControllerActionOrigin eOrigin ) +{ + PRINT_DEBUG("GetStringForActionOrigin\n"); + return "Button String"; +} + +const char *GetStringForActionOrigin( EInputActionOrigin eOrigin ) +{ + PRINT_DEBUG("GetStringForActionOrigin steaminput\n"); + return "Button String"; +} + + +// Get a local path to art for on-screen glyph for a particular origin +const char *GetGlyphForActionOrigin( EControllerActionOrigin eOrigin ) +{ + PRINT_DEBUG("GetGlyphForActionOrigin\n"); + return ""; +} + +const char *GetGlyphForActionOrigin( EInputActionOrigin eOrigin ) +{ + PRINT_DEBUG("GetGlyphForActionOrigin steaminput\n"); + return ""; +} + +// Returns the input type for a particular handle +ESteamInputType GetInputTypeForHandle( ControllerHandle_t controllerHandle ) +{ + PRINT_DEBUG("GetInputTypeForHandle\n"); + return k_ESteamInputType_Unknown; +} + +const char *GetStringForXboxOrigin( EXboxOrigin eOrigin ) +{ + PRINT_DEBUG("GetStringForXboxOrigin\n"); + return ""; +} + +const char *GetGlyphForXboxOrigin( EXboxOrigin eOrigin ) +{ + PRINT_DEBUG("GetGlyphForXboxOrigin\n"); + return ""; +} + +EControllerActionOrigin GetActionOriginFromXboxOrigin_( ControllerHandle_t controllerHandle, EXboxOrigin eOrigin ) +{ + PRINT_DEBUG("GetActionOriginFromXboxOrigin\n"); + return k_EControllerActionOrigin_None; +} + +EInputActionOrigin GetActionOriginFromXboxOrigin( InputHandle_t inputHandle, EXboxOrigin eOrigin ) +{ + PRINT_DEBUG("GetActionOriginFromXboxOrigin steaminput\n"); + return k_EInputActionOrigin_None; +} + +EControllerActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EControllerActionOrigin eSourceOrigin ) +{ + PRINT_DEBUG("TranslateActionOrigin\n"); + return k_EControllerActionOrigin_None; +} + +EInputActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EInputActionOrigin eSourceOrigin ) +{ + PRINT_DEBUG("TranslateActionOrigin steaminput\n"); + return k_EInputActionOrigin_None; +} + +bool GetControllerBindingRevision( ControllerHandle_t controllerHandle, int *pMajor, int *pMinor ) +{ + PRINT_DEBUG("GetControllerBindingRevision\n"); + return false; +} + +bool GetDeviceBindingRevision( InputHandle_t inputHandle, int *pMajor, int *pMinor ) +{ + PRINT_DEBUG("GetDeviceBindingRevision\n"); + return false; +} + +uint32 GetRemotePlaySessionID( InputHandle_t inputHandle ) +{ + PRINT_DEBUG("GetRemotePlaySessionID\n"); + return 0; +} + +}; diff --git a/dll/steam_utils.h b/dll/steam_utils.h index 6f96efb..4c65d0c 100644 --- a/dll/steam_utils.h +++ b/dll/steam_utils.h @@ -87,6 +87,8 @@ bool GetImageSize( int iImage, uint32 *pnWidth, uint32 *pnHeight ) { PRINT_DEBUG("GetImageSize %i\n", iImage); if (!iImage || !pnWidth || !pnHeight) return false; + std::lock_guard lock(global_mutex); + auto image = settings->images.find(iImage); if (image == settings->images.end()) return false; @@ -102,6 +104,8 @@ bool GetImageRGBA( int iImage, uint8 *pubDest, int nDestBufferSize ) { PRINT_DEBUG("GetImageRGBA %i\n", iImage); if (!iImage || !pubDest || !nDestBufferSize) return false; + std::lock_guard lock(global_mutex); + auto image = settings->images.find(iImage); if (image == settings->images.end()) return false; From 8b9e0e25ca7f46d4071722e311bc3782a2721b52 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Tue, 10 Sep 2019 23:58:27 -0400 Subject: [PATCH 04/18] Forgot this in last commit. --- dll/settings.h | 2 +- .../controller.EXAMPLE/InGameControls.txt | 17 +++++++++++++++++ .../controller.EXAMPLE/MenuControls.txt | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 files_example/steam_settings.EXAMPLE/controller.EXAMPLE/InGameControls.txt create mode 100644 files_example/steam_settings.EXAMPLE/controller.EXAMPLE/MenuControls.txt diff --git a/dll/settings.h b/dll/settings.h index a66a8f3..a5efb5b 100644 --- a/dll/settings.h +++ b/dll/settings.h @@ -59,7 +59,7 @@ struct Image_Data { }; struct Controller_Settings { - std::map, std::string>>> action_sets = {{"ship_controls", {{"analog_controls", {{"LJOY"}, "joystick_move"}}}}, {"menu_controls", {{"menu_up", {{"DLJOYUP", "DUP"}, ""}}, {"menu_down", {{"DLJOYDOWN", "DDOWN"}, ""}}}}}; + std::map, std::string>>> action_sets; std::map action_set_layer_parents; std::map, std::string>>> action_set_layers; }; diff --git a/files_example/steam_settings.EXAMPLE/controller.EXAMPLE/InGameControls.txt b/files_example/steam_settings.EXAMPLE/controller.EXAMPLE/InGameControls.txt new file mode 100644 index 0000000..e53d558 --- /dev/null +++ b/files_example/steam_settings.EXAMPLE/controller.EXAMPLE/InGameControls.txt @@ -0,0 +1,17 @@ +Move=LJOY=joystick_move +Camera=RJOY=joystick_move +Dash=RTRIGGER=trigger +LockOn=LTRIGGER=trigger +pause_menu=START +LightAttack=X +HeavyAttack=Y +Jump=A +check=B +ActivateSkillAndMagic=RBUMPER +IdeaRelease=LBUMPER +AllMap=BACK +ResetCamera=RSTICK +ChangeCharacterUp=DUP +ChangeCharacterDown=DDOWN +ChangeCharacterRight=DRIGHT +ChangeCharacterLeft=DLEFT diff --git a/files_example/steam_settings.EXAMPLE/controller.EXAMPLE/MenuControls.txt b/files_example/steam_settings.EXAMPLE/controller.EXAMPLE/MenuControls.txt new file mode 100644 index 0000000..181c107 --- /dev/null +++ b/files_example/steam_settings.EXAMPLE/controller.EXAMPLE/MenuControls.txt @@ -0,0 +1,16 @@ +MenuDash=RTRIGGER=trigger +MenuLockOn=LTRIGGER=trigger +MenuUp=DUP,DLJOYUP +MenuDown=DDOWN,DLJOYDOWN +MenuRight=DRIGHT,DLJOYRIGHT +MenuLeft=DLEFT,DLJOYLEFT +SELECT=A +Cancel=B +MenuX=X +MenuY=Y +MenuRTrigger=RBUMPER +MenuLTrigger=LBUMPER +Sort=BACK +PauseMenu=START +ScrollUp=DRJOYUP +ScrollDown=DRJOYDOWN From 141dfba1915aee4d35139f0732b0ca2889f16391 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Wed, 11 Sep 2019 08:39:24 -0400 Subject: [PATCH 05/18] Link to xinput 9.1.0 for compatibility with windows 7. --- controller/gamepad.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/gamepad.c b/controller/gamepad.c index 53a6a2f..d78c091 100644 --- a/controller/gamepad.c +++ b/controller/gamepad.c @@ -19,7 +19,7 @@ # undef UNICODE # include "windows.h" # include "xinput.h" -# pragma comment(lib, "xinput.lib") +# pragma comment(lib, "XINPUT9_1_0.lib") #elif defined(__linux__) # include # include From 663728edca0a407546060a27f745a79660b6cb58 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Wed, 11 Sep 2019 09:08:05 -0400 Subject: [PATCH 06/18] Allow action names to be used in more than one action set at the same time. --- dll/steam_controller.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/dll/steam_controller.h b/dll/steam_controller.h index 43305a0..b03dc57 100644 --- a/dll/steam_controller.h +++ b/dll/steam_controller.h @@ -145,7 +145,12 @@ public ISteamInput if (digital != button_strings.end()) { ControllerDigitalActionHandle_t digital_handle_num = current_handle_num; - digital_action_handles[config_key.first] = digital_handle_num; + if (digital_action_handles.find(config_key.first) == digital_action_handles.end()) { + digital_action_handles[config_key.first] = digital_handle_num; + } else { + digital_handle_num = digital_action_handles[config_key.first]; + } + controller_maps[action_handle_num].active_digital[digital_handle_num].insert(digital->second); } else { auto analog = analog_strings.find(button_string); @@ -164,7 +169,12 @@ public ISteamInput source_mode = input_mode->second; } - analog_action_handles[config_key.first] = analog_handle_num; + if (analog_action_handles.find(config_key.first) == analog_action_handles.end()) { + analog_action_handles[config_key.first] = analog_handle_num; + } else { + analog_handle_num = analog_action_handles[config_key.first]; + } + controller_maps[action_handle_num].active_analog[analog_handle_num].first.insert(analog->second); controller_maps[action_handle_num].active_analog[analog_handle_num].second = source_mode; From 09704ae2438c7e4bbc786d2306dd988bbb4797c0 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Thu, 12 Sep 2019 07:21:11 -0400 Subject: [PATCH 07/18] Fixed issue where GetAnalogActionData magnitude of joystick was always maximum. --- controller/gamepad.c | 4 ++++ dll/steam_controller.h | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/controller/gamepad.c b/controller/gamepad.c index d78c091..f1e7893 100644 --- a/controller/gamepad.c +++ b/controller/gamepad.c @@ -554,6 +554,10 @@ static void GamepadUpdateStick(GAMEPAD_AXIS* axis, float deadzone) { axis->nx = axis->x / axis->length; axis->ny = axis->y / axis->length; + //fix special case + if (axis->nx < -1.0) axis->nx = -1.0; + if (axis->ny < -1.0) axis->ny = -1.0; + // adjust length for deadzone and find normalized length axis->length -= deadzone; axis->length /= (32767.0f - deadzone); diff --git a/dll/steam_controller.h b/dll/steam_controller.h index b03dc57..7a31879 100644 --- a/dll/steam_controller.h +++ b/dll/steam_controller.h @@ -495,9 +495,16 @@ ControllerAnalogActionData_t GetAnalogActionData( ControllerHandle_t controllerH if (a >= 10) { int joystick_id = a - 10; GamepadStickNormXY((GAMEPAD_DEVICE)(controllerHandle - 1), (GAMEPAD_STICK) joystick_id, &data.x, &data.y); + float length = GamepadStickLength((GAMEPAD_DEVICE)(controllerHandle - 1), (GAMEPAD_STICK) joystick_id); + data.x = data.x * length; + data.y = data.y * length; } else { data.x = GamepadTriggerLength((GAMEPAD_DEVICE)(controllerHandle - 1), (GAMEPAD_TRIGGER) a); } + + if (data.x || data.y) { + break; + } } return data; From 03a1627d071bd2d795fcf23babb58145d40264b5 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Sat, 14 Sep 2019 17:24:05 -0400 Subject: [PATCH 08/18] SteamInput GetDigitalActionOrigins and GetAnalogActionOrigins implemented. --- dll/steam_controller.h | 167 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 7 deletions(-) diff --git a/dll/steam_controller.h b/dll/steam_controller.h index 7a31879..6aa33aa 100644 --- a/dll/steam_controller.h +++ b/dll/steam_controller.h @@ -66,6 +66,8 @@ enum EXTRA_GAMEPAD_BUTTONS { BUTTON_STICK_RIGHT_RIGHT = BUTTON_COUNT + 10, }; +#define JOY_ID_START 10 + class Steam_Controller : public ISteamController001, public ISteamController003, @@ -110,8 +112,8 @@ public ISteamInput std::map analog_strings = { {"LTRIGGER", TRIGGER_LEFT}, {"RTRIGGER", TRIGGER_RIGHT}, - {"LJOY", STICK_LEFT + 10}, - {"RJOY", STICK_RIGHT + 10}, + {"LJOY", STICK_LEFT + JOY_ID_START}, + {"RJOY", STICK_RIGHT + JOY_ID_START}, }; std::map analog_input_modes = { @@ -331,6 +333,12 @@ ControllerActionSetHandle_t GetActionSetHandle( const char *pszActionSetName ) void ActivateActionSet( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle ) { PRINT_DEBUG("Steam_Controller::ActivateActionSet %llu %llu\n", controllerHandle, actionSetHandle); + if (controllerHandle == STEAM_CONTROLLER_HANDLE_ALL_CONTROLLERS) { + for (auto & c: controllers) { + c.second.activate_action_set(actionSetHandle, controller_maps); + } + } + auto controller = controllers.find(controllerHandle); if (controller == controllers.end()) return; @@ -449,13 +457,118 @@ ControllerDigitalActionData_t GetDigitalActionData( ControllerHandle_t controlle int GetDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerDigitalActionHandle_t digitalActionHandle, EControllerActionOrigin *originsOut ) { PRINT_DEBUG("Steam_Controller::GetDigitalActionOrigins\n"); - return 0; + EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; + int ret = GetDigitalActionOrigins(controllerHandle, actionSetHandle, digitalActionHandle, origins ); + for (int i = 0; i < ret; ++i) { + originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBoxOne_A - k_EControllerActionOrigin_XBox360_A)); + } + + return ret; } int GetDigitalActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, EInputActionOrigin *originsOut ) { PRINT_DEBUG("Steam_Controller::GetDigitalActionOrigins steaminput\n"); - return 0; + auto controller = controllers.find(inputHandle); + if (controller == controllers.end()) return 0; + + auto map = controller_maps.find(actionSetHandle); + if (map == controller_maps.end()) return 0; + + auto a = map->second.active_digital.find(digitalActionHandle); + if (a == map->second.active_digital.end()) return 0; + + int count = 0; + for (auto button: a->second) { + switch (button) { + case BUTTON_A: + originsOut[count] = k_EInputActionOrigin_XBox360_A; + break; + case BUTTON_B: + originsOut[count] = k_EInputActionOrigin_XBox360_B; + break; + case BUTTON_X: + originsOut[count] = k_EInputActionOrigin_XBox360_X; + break; + case BUTTON_Y: + originsOut[count] = k_EInputActionOrigin_XBox360_Y; + break; + case BUTTON_LEFT_SHOULDER: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftBumper; + break; + case BUTTON_RIGHT_SHOULDER: + originsOut[count] = k_EInputActionOrigin_XBox360_RightBumper; + break; + case BUTTON_START: + originsOut[count] = k_EInputActionOrigin_XBox360_Start; + break; + case BUTTON_BACK: + originsOut[count] = k_EInputActionOrigin_XBox360_Back; + break; + case BUTTON_LTRIGGER: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftTrigger_Click; + break; + case BUTTON_RTRIGGER: + originsOut[count] = k_EInputActionOrigin_XBox360_RightTrigger_Click; + break; + case BUTTON_LEFT_THUMB: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_Click; + break; + case BUTTON_RIGHT_THUMB: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_Click; + break; + + case BUTTON_STICK_LEFT_UP: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadNorth; + break; + case BUTTON_STICK_LEFT_DOWN: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadSouth; + break; + case BUTTON_STICK_LEFT_LEFT: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadWest; + break; + case BUTTON_STICK_LEFT_RIGHT: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_DPadEast; + break; + + case BUTTON_STICK_RIGHT_UP: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadNorth; + break; + case BUTTON_STICK_RIGHT_DOWN: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadSouth; + break; + case BUTTON_STICK_RIGHT_LEFT: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadWest; + break; + case BUTTON_STICK_RIGHT_RIGHT: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_DPadEast; + break; + + case BUTTON_DPAD_UP: + originsOut[count] = k_EInputActionOrigin_XBox360_DPad_North; + break; + case BUTTON_DPAD_DOWN: + originsOut[count] = k_EInputActionOrigin_XBox360_DPad_South; + break; + case BUTTON_DPAD_LEFT: + originsOut[count] = k_EInputActionOrigin_XBox360_DPad_West; + break; + case BUTTON_DPAD_RIGHT: + originsOut[count] = k_EInputActionOrigin_XBox360_DPad_East; + break; + + default: + originsOut[count] = k_EInputActionOrigin_None; + break; + } + + ++count; + if (count >= STEAM_INPUT_MAX_ORIGINS) { + break; + } + } + + return count; } // Lookup the handle for an analog action. Best to do this once on startup, and store the handles for all future API calls. @@ -492,8 +605,8 @@ ControllerAnalogActionData_t GetAnalogActionData( ControllerHandle_t controllerH data.eMode = analog.second; for (auto a : analog.first) { - if (a >= 10) { - int joystick_id = a - 10; + if (a >= JOY_ID_START) { + int joystick_id = a - JOY_ID_START; GamepadStickNormXY((GAMEPAD_DEVICE)(controllerHandle - 1), (GAMEPAD_STICK) joystick_id, &data.x, &data.y); float length = GamepadStickLength((GAMEPAD_DEVICE)(controllerHandle - 1), (GAMEPAD_STICK) joystick_id); data.x = data.x * length; @@ -516,12 +629,52 @@ ControllerAnalogActionData_t GetAnalogActionData( ControllerHandle_t controllerH int GetAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerAnalogActionHandle_t analogActionHandle, EControllerActionOrigin *originsOut ) { PRINT_DEBUG("Steam_Controller::GetAnalogActionOrigins\n"); - return 0; + EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; + int ret = GetAnalogActionOrigins(controllerHandle, actionSetHandle, analogActionHandle, origins ); + for (int i = 0; i < ret; ++i) { + originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBoxOne_A - k_EControllerActionOrigin_XBox360_A)); + } + + return ret; } int GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, EInputActionOrigin *originsOut ) { PRINT_DEBUG("Steam_Controller::GetAnalogActionOrigins steaminput\n"); + auto controller = controllers.find(inputHandle); + if (controller == controllers.end()) return 0; + + auto map = controller_maps.find(actionSetHandle); + if (map == controller_maps.end()) return 0; + + auto a = map->second.active_analog.find(analogActionHandle); + if (a == map->second.active_analog.end()) return 0; + + int count = 0; + for (auto a: a->second.first) { + switch (a) { + case TRIGGER_LEFT: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftTrigger_Pull; + break; + case TRIGGER_RIGHT: + originsOut[count] = k_EInputActionOrigin_XBox360_RightTrigger_Pull; + break; + case STICK_LEFT + JOY_ID_START: + originsOut[count] = k_EInputActionOrigin_XBox360_LeftStick_Move; + break; + case STICK_RIGHT + JOY_ID_START: + originsOut[count] = k_EInputActionOrigin_XBox360_RightStick_Move; + break; + default: + originsOut[count] = k_EInputActionOrigin_None; + break; + } + + ++count; + if (count >= STEAM_INPUT_MAX_ORIGINS) { + break; + } + } return 0; } From ee4722cb3375667c3fef0740469599d4a6bdce59 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Sat, 14 Sep 2019 18:32:26 -0400 Subject: [PATCH 09/18] Activate the action set if there is only one present. --- dll/steam_controller.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dll/steam_controller.h b/dll/steam_controller.h index 6aa33aa..0f2b641 100644 --- a/dll/steam_controller.h +++ b/dll/steam_controller.h @@ -232,7 +232,14 @@ bool Init() GamepadUpdate(); for (int i = 1; i < 5; ++i) { - controllers.insert(std::pair(i, Controller_Action(i))); + struct Controller_Action cont_action(i); + //Activate the action set if there is only one present. + //TODO: I don't know if one gets activated by default when there's more than one + if (action_handles.size() == 1) { + cont_action.activate_action_set(action_handles.begin()->second, controller_maps); + } + + controllers.insert(std::pair(i, cont_action)); } return true; From b77b4e2ef53dc8eee530a6f906c075ba4070a772 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Fri, 20 Sep 2019 06:14:08 -0400 Subject: [PATCH 10/18] Fix older ActionOrigins functions. --- dll/steam_controller.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dll/steam_controller.h b/dll/steam_controller.h index 0f2b641..926013d 100644 --- a/dll/steam_controller.h +++ b/dll/steam_controller.h @@ -467,7 +467,7 @@ int GetDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerActi EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; int ret = GetDigitalActionOrigins(controllerHandle, actionSetHandle, digitalActionHandle, origins ); for (int i = 0; i < ret; ++i) { - originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBoxOne_A - k_EControllerActionOrigin_XBox360_A)); + originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBox360_A - k_EControllerActionOrigin_XBox360_A)); } return ret; @@ -639,7 +639,7 @@ int GetAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerActio EInputActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; int ret = GetAnalogActionOrigins(controllerHandle, actionSetHandle, analogActionHandle, origins ); for (int i = 0; i < ret; ++i) { - originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBoxOne_A - k_EControllerActionOrigin_XBox360_A)); + originsOut[i] = (EControllerActionOrigin)(origins[i] - (k_EInputActionOrigin_XBox360_A - k_EControllerActionOrigin_XBox360_A)); } return ret; From 1545e375a2109331a99de405b4dd92e70f541a84 Mon Sep 17 00:00:00 2001 From: Nemirtingas Date: Fri, 20 Sep 2019 12:52:56 +0200 Subject: [PATCH 11/18] Fix wrong broadcast addr on windows --- dll/network.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dll/network.cpp b/dll/network.cpp index b85ce3f..7b2628a 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -71,16 +71,15 @@ static void get_broadcast_info(uint16 port) IP_ADAPTER_INFO *pAdapter = pAdapterInfo; while (pAdapter) { - unsigned long gateway = 0, subnet_mask = 0; + unsigned long iface_ip = 0, subnet_mask = 0; if (inet_pton(AF_INET, pAdapter->IpAddressList.IpMask.String, &subnet_mask) == 1 - && inet_pton(AF_INET, pAdapter->GatewayList.IpAddress.String, &gateway) == 1) { + && inet_pton(AF_INET, pAdapter->IpAddressList.IpAddress.String, &iface_ip) == 1) { IP_PORT *ip_port = &broadcasts[number_broadcasts]; //ip_port->ip.family = AF_INET; - uint32 gateway_ip = ntohl(gateway), subnet_ip = ntohl(subnet_mask); - uint32 broadcast_ip = gateway_ip + ~subnet_ip - 1; - ip_port->ip = htonl(broadcast_ip); + uint32 broadcast_ip = iface_ip | ~subnet_mask; + ip_port->ip = broadcast_ip; ip_port->port = port; number_broadcasts++; From 5ed9a7aa77db1602b8e9746c1279f4228a1023ff Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Fri, 20 Sep 2019 11:04:04 -0400 Subject: [PATCH 12/18] Fix dumb bug. --- dll/steam_controller.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dll/steam_controller.h b/dll/steam_controller.h index 926013d..1cd15e4 100644 --- a/dll/steam_controller.h +++ b/dll/steam_controller.h @@ -682,7 +682,8 @@ int GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t ac break; } } - return 0; + + return count; } From 75dd582dec7b45992bde84f9c09a1030cb83c011 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Sun, 22 Sep 2019 11:35:27 -0400 Subject: [PATCH 13/18] Fix crash when one of the lobby keys which was supposed to be an int was not an int. --- dll/steam_matchmaking.h | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/dll/steam_matchmaking.h b/dll/steam_matchmaking.h index 4622cc2..7ca1df9 100644 --- a/dll/steam_matchmaking.h +++ b/dll/steam_matchmaking.h @@ -1152,16 +1152,21 @@ void RunCallbacks() } } } else { - int compare_to = stoi(value->second, 0, 0); - PRINT_DEBUG("Compare Values %i %i\n", compare_to, f.value_int); - if (f.eComparisonType == k_ELobbyComparisonEqual) { - if (compare_to == f.value_int) { - PRINT_DEBUG("Equal\n"); - //use = use; - } else { - PRINT_DEBUG("Not Equal\n"); - use = false; + try { + int compare_to = std::stoi(value->second, 0, 0); + PRINT_DEBUG("Compare Values %i %i\n", compare_to, f.value_int); + if (f.eComparisonType == k_ELobbyComparisonEqual) { + if (compare_to == f.value_int) { + PRINT_DEBUG("Equal\n"); + //use = use; + } else { + PRINT_DEBUG("Not Equal\n"); + use = false; + } } + } catch (...) { + //Same case as if the key is not in the lobby? + use = false; } //TODO: add more comparisons } From 95083267325f2978bb11f4c4c0167ac1318784d7 Mon Sep 17 00:00:00 2001 From: soft as HELL Date: Thu, 3 Oct 2019 16:17:34 +0300 Subject: [PATCH 14/18] Add validation to writes --- dll/steam_remote_storage.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dll/steam_remote_storage.h b/dll/steam_remote_storage.h index d0c7646..2840abb 100644 --- a/dll/steam_remote_storage.h +++ b/dll/steam_remote_storage.h @@ -84,6 +84,11 @@ bool FileWrite( const char *pchFile, const void *pvData, int32 cubData ) { PRINT_DEBUG("Steam_Remote_Storage::FileWrite %s %u\n", pchFile, cubData); std::lock_guard lock(global_mutex); + + if (!pvData || cubData < 0 || cubData > k_unMaxCloudFileChunkSize) { + return false; + } + int data_stored = local_storage->store_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubData); PRINT_DEBUG("Steam_Remote_Storage::Stored %i, %u\n", data_stored, data_stored == cubData); return data_stored == cubData; @@ -104,9 +109,15 @@ SteamAPICall_t FileWriteAsync( const char *pchFile, const void *pvData, uint32 c { PRINT_DEBUG("Steam_Remote_Storage::FileWriteAsync\n"); std::lock_guard lock(global_mutex); + + if (!pvData || cubData > k_unMaxCloudFileChunkSize) { + return k_uAPICallInvalid; + } + bool success = local_storage->store_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubData) == cubData; + RemoteStorageFileWriteAsyncComplete_t data; - data.m_eResult = k_EResultOK; + data.m_eResult = success ? k_EResultOK : k_EResultFail; return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); } From 787cac47db64f740fefcf2d992d7fdb829a198fa Mon Sep 17 00:00:00 2001 From: soft as HELL Date: Fri, 4 Oct 2019 19:01:17 +0300 Subject: [PATCH 15/18] Add delay to FileWriteAsync callback --- dll/steam_remote_storage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dll/steam_remote_storage.h b/dll/steam_remote_storage.h index 2840abb..bc41d6e 100644 --- a/dll/steam_remote_storage.h +++ b/dll/steam_remote_storage.h @@ -119,7 +119,7 @@ SteamAPICall_t FileWriteAsync( const char *pchFile, const void *pvData, uint32 c RemoteStorageFileWriteAsyncComplete_t data; data.m_eResult = success ? k_EResultOK : k_EResultFail; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); } From bd921b09398273050f957a13e4c40a4b5fbc3933 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Sat, 5 Oct 2019 15:39:50 -0400 Subject: [PATCH 16/18] Add a way to disable all the networking functionality in the emulator. --- Readme_release.txt | 4 ++++ dll/network.cpp | 15 +++++++++++++-- dll/network.h | 2 +- dll/settings.h | 3 +++ dll/settings_parser.cpp | 5 +++++ dll/steam_client.cpp | 2 +- 6 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Readme_release.txt b/Readme_release.txt index c0d11e0..a136aeb 100644 --- a/Readme_release.txt +++ b/Readme_release.txt @@ -60,6 +60,10 @@ The steam appid can also be set using the SteamAppId or SteamGameId env variable Offline mode: Some games that connect to online servers might only work if the steam emu behaves like steam is in offline mode. If you need this create a offline.txt file in the steam_settings folder. +Disable networking: +If for some reason you want to disable all the networking functionality of the emu you can create a disable_networking.txt file in the steam_settings folder. This will of course break all the +networking functionality so games that use networking related functionality like lobbies or those that launch a server in the background will not work. + Custom Broadcast ips: If you want to set custom ips (or domains) which the emulator will send broadcast packets to, make a list of them, one on each line in: Goldberg SteamEmu Saves\settings\custom_broadcasts.txt If the custom ips/domains are specific for one game only you can put the custom_broadcasts.txt in the steam_settings\ folder. diff --git a/dll/network.cpp b/dll/network.cpp index 7b2628a..e726a5c 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -706,18 +706,26 @@ bool Networking::handle_low_level_udp(Common_Message *msg, IP_PORT ip_port) #define NUM_TCP_WAITING 128 -Networking::Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts) +Networking::Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts, bool disable_sockets) { - run_at_startup(); tcp_port = udp_port = port; own_ip = 0x7F000001; alive = true; last_run = std::chrono::high_resolution_clock::now(); this->appid = appid; + + if (disable_sockets) { + enabled = false; + udp_socket = -1; + tcp_socket = -1; + return; + } + if (custom_broadcasts) { std::transform(custom_broadcasts->begin(), custom_broadcasts->end(), std::back_inserter(this->custom_broadcasts), [](uint32 ip) {return htonl(ip);}); } + run_at_startup(); sock_t sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); PRINT_DEBUG("UDP socket: %u\n", sock); if (is_socket_valid(sock) && set_socket_nonblocking(sock)) { @@ -1054,6 +1062,7 @@ void Networking::Run() void Networking::addListenId(CSteamID id) { + if (!enabled) return; auto i = std::find(ids.begin(), ids.end(), id); if (i != ids.end()) { return; @@ -1087,6 +1096,8 @@ bool Networking::sendToIPPort(Common_Message *msg, uint32 ip, uint16 port, bool bool Networking::sendTo(Common_Message *msg, bool reliable, Connection *conn) { + if (!enabled) return false; + bool ret = false; CSteamID dest_id((uint64)msg->dest_id()); if (std::find(ids.begin(), ids.end(), dest_id) != ids.end()) { diff --git a/dll/network.h b/dll/network.h index bc0ce91..1a8c064 100644 --- a/dll/network.h +++ b/dll/network.h @@ -124,7 +124,7 @@ public: //NOTE: for all functions ips/ports are passed/returned in host byte order //ex: 127.0.0.1 should be passed as 0x7F000001 static std::set resolve_ip(std::string dns); - Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts); + Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts, bool disable_sockets); void addListenId(CSteamID id); void setAppID(uint32 appid); void Run(); diff --git a/dll/settings.h b/dll/settings.h index a5efb5b..cfb06b0 100644 --- a/dll/settings.h +++ b/dll/settings.h @@ -135,6 +135,9 @@ public: //controller struct Controller_Settings controller_settings; + + //networking + bool disable_networking = false; }; #endif diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 42849d3..ca12e85 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -247,6 +247,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s } bool steam_offline_mode = false; + bool disable_networking = false; { std::string steam_settings_path = Local_Storage::get_game_settings_path(); @@ -255,6 +256,8 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s PRINT_DEBUG("steam settings path %s\n", p.c_str()); if (p == "offline.txt") { steam_offline_mode = true; + } else if (p == "disable_networking.txt") { + disable_networking = true; } } } @@ -265,6 +268,8 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s settings_server->set_port(port); settings_client->custom_broadcasts = custom_broadcasts; settings_server->custom_broadcasts = custom_broadcasts; + settings_client->disable_networking = disable_networking; + settings_server->disable_networking = disable_networking; { std::string dlc_config_path = Local_Storage::get_game_settings_path() + "DLC.txt"; diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index d81f90c..f7b5fe7 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -45,7 +45,7 @@ Steam_Client::Steam_Client() uint32 appid = create_localstorage_settings(&settings_client, &settings_server, &local_storage); std::string items_db_file_path = (Local_Storage::get_game_settings_path() + "items.json"); - network = new Networking(settings_server->get_local_steam_id(), appid, settings_server->get_port(), &(settings_server->custom_broadcasts)); + network = new Networking(settings_server->get_local_steam_id(), appid, settings_server->get_port(), &(settings_server->custom_broadcasts), settings_server->disable_networking); callback_results_client = new SteamCallResults(); callback_results_server = new SteamCallResults(); From 1427a4bcef1fc92f1a8aecb54aeae9577bce8498 Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Sat, 5 Oct 2019 15:40:29 -0400 Subject: [PATCH 17/18] More accurate steam remote storage behavior. --- dll/steam_remote_storage.h | 12 +++++------- .../disable_networking.EXAMPLE.txt | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 files_example/steam_settings.EXAMPLE/disable_networking.EXAMPLE.txt diff --git a/dll/steam_remote_storage.h b/dll/steam_remote_storage.h index bc41d6e..f6a0c51 100644 --- a/dll/steam_remote_storage.h +++ b/dll/steam_remote_storage.h @@ -83,12 +83,11 @@ Steam_Remote_Storage(class Settings *settings, Local_Storage *local_storage, cla bool FileWrite( const char *pchFile, const void *pvData, int32 cubData ) { PRINT_DEBUG("Steam_Remote_Storage::FileWrite %s %u\n", pchFile, cubData); - std::lock_guard lock(global_mutex); - - if (!pvData || cubData < 0 || cubData > k_unMaxCloudFileChunkSize) { + if (!pchFile || cubData <= 0 || cubData > k_unMaxCloudFileChunkSize) { return false; } + std::lock_guard lock(global_mutex); int data_stored = local_storage->store_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubData); PRINT_DEBUG("Steam_Remote_Storage::Stored %i, %u\n", data_stored, data_stored == cubData); return data_stored == cubData; @@ -108,18 +107,17 @@ STEAM_CALL_RESULT( RemoteStorageFileWriteAsyncComplete_t ) SteamAPICall_t FileWriteAsync( const char *pchFile, const void *pvData, uint32 cubData ) { PRINT_DEBUG("Steam_Remote_Storage::FileWriteAsync\n"); - std::lock_guard lock(global_mutex); - - if (!pvData || cubData > k_unMaxCloudFileChunkSize) { + if (!pchFile || cubData > k_unMaxCloudFileChunkSize || cubData == 0) { return k_uAPICallInvalid; } + std::lock_guard lock(global_mutex); bool success = local_storage->store_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubData) == cubData; RemoteStorageFileWriteAsyncComplete_t data; data.m_eResult = success ? k_EResultOK : k_EResultFail; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.01); } diff --git a/files_example/steam_settings.EXAMPLE/disable_networking.EXAMPLE.txt b/files_example/steam_settings.EXAMPLE/disable_networking.EXAMPLE.txt new file mode 100644 index 0000000..4cc106f --- /dev/null +++ b/files_example/steam_settings.EXAMPLE/disable_networking.EXAMPLE.txt @@ -0,0 +1 @@ +Rename this to: disable_networking.txt to disable all networking functionality. \ No newline at end of file From 736d1a9090d5f98c6e3aa437795877fe5452cd3e Mon Sep 17 00:00:00 2001 From: Mr_Goldberg Date: Sat, 5 Oct 2019 15:41:28 -0400 Subject: [PATCH 18/18] Make isteamutils call result functions respect callback delays. --- dll/base.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dll/base.h b/dll/base.h index 4b3453e..f549c1f 100644 --- a/dll/base.h +++ b/dll/base.h @@ -110,8 +110,12 @@ struct Steam_Call_Result { return check_timedout(created, STEAM_CALLRESULT_TIMEOUT); } + bool call_completed() { + return (!reserved) && check_timedout(created, run_in); + } + bool can_execute() { - return (!reserved) && (!to_delete) && check_timedout(created, run_in); + return (!to_delete) && call_completed(); } bool has_cb() { @@ -169,14 +173,14 @@ public: bool exists(SteamAPICall_t api_call) { auto cr = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); if (cr == callresults.end()) return false; - if (cr->reserved) return false; + if (!cr->call_completed()) return false; return true; } bool callback_result(SteamAPICall_t api_call, void *copy_to, unsigned int size) { auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; }); if (cb_result != callresults.end()) { - if (cb_result->reserved) return false; + if (!cb_result->call_completed()) return false; if (cb_result->result.size() > size) return false; memcpy(copy_to, &(cb_result->result[0]), cb_result->result.size());