From cbfa6aa7fc10c307d14b6090de1ce1979d211310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?eray=20or=C3=A7unus?= Date: Mon, 28 Oct 2019 22:56:26 +0300 Subject: [PATCH] Fix GInput right stick Y-axis for real --- GInputAPI.h | 411 +++++++++++++++++++++++++++++++++++++++++++++++++ ModuleList.hpp | 212 +++++++++++++++++++++++++ Pad.cpp | 4 +- SACarCam.cpp | 35 ++++- 4 files changed, 655 insertions(+), 7 deletions(-) create mode 100644 GInputAPI.h create mode 100644 ModuleList.hpp diff --git a/GInputAPI.h b/GInputAPI.h new file mode 100644 index 0000000..983c754 --- /dev/null +++ b/GInputAPI.h @@ -0,0 +1,411 @@ +#ifndef __GINPUTAPI +#define __GINPUTAPI + +// GInput 1.11 API file + +// With this little API prepared for C++, you can take advantage of some GInput features +// For functions list, scroll down to the interface declaration + + +// As this API file is compatible with GInputIII. GInputVC and GInputSA, you need to declare +// whether to compile III version, VC version, SA version, or a cross-compatible version + +// The API doesn't target a specific game by default - you need one of those four defines in order for API to work correctly: + +// To target GTA III, define GINPUT_COMPILE_III_VERSION in your project settings, code headers or just +// uncomment the line below +//#define GINPUT_COMPILE_III_VERSION + +// To target GTA VC, define GINPUT_COMPILE_VC_VERSION in your project settings, code headers or just +// uncomment the line below +//#define GINPUT_COMPILE_VC_VERSION + +// To target GTA SA, define GINPUT_COMPILE_SA_VERSION in your project settings, code headers or just +// uncomment the line below +//#define GINPUT_COMPILE_SA_VERSION + +// You can also target all three games at once by defining GINPUT_COMPILE_CROSSCOMPATIBLE_VERSION - define it +// in your project settings, code headers or just uncomment the line below +#define GINPUT_COMPILE_CROSSCOMPATIBLE_VERSION + +enum eGInputEvent +{ + GINPUT_NO_EVENT = -1, // DON'T USE, internal use only + + GINPUT_EVENT_CHANGE_RADIO_STATION = 0, // Returns NULL all the time, pParam specifies whether to set + // next or previous radio station (0 for next station, 1 for previous) + // Callable from SendEvent: YES + // Callable from SendConstEvent: NO + + GINPUT_EVENT_CHANGE_USER_TRACK = 1, // Returns NULL all the time, pParam specifies whether to set + // next or previous MP3 (0 for next MP3, 1 for previous) + // Callable from SendEvent: YES + // Callable from SendConstEvent: NO + + GINPUT_EVENT_REGISTER_SETTINGS_RELOAD_CALLBACK = 2, // Registers a callback function which gets called after GInput*.ini gets reloaded + // after WM_SETFOCUS gets called. Returns NULL. pParam should be a callback function + // pointer (see GInputOnSettingsReloadCallback) + // Cannot be recursive - the callbacks registered from within a callback will be ignored + // Callable from SendEvent: YES + // Callable from SendConstEvent: NO + + GINPUT_EVENT_FETCH_SETTINGS = 3, // DEPRECATED EVENT, DO NOT USE + + GINPUT_EVENT_FETCH_CHEAT_STRING = 4, // Copies the current cheat string to a char array sent via pParam. + // pParam should point to an array of at least GINPUT_CHEAT_STRING_LENGTH characters + // or a buffer overflow will occur. + // Returns a value of type BOOL - when set to TRUE, cheat string has been changed since + // the last frame - useful for determining when to re-check for a custom cheat combo + // Letters from a cheat string are assigned to buttons as follows: + // U - DPad Up, D - DPad Down, L - DPad Left, R - DPad Right + // T - Y/Triangle, C - B/Circle, X - A/Cross, S - X/Square + // 1 - L1/LB, 2 - L2/LT, 3 - R1/RB, 4 - R2/RT + // Callable from SendEvent: YES + // Callable from SendConstEvent: YES + + GINPUT_EVENT_RELOAD_WEAPON = 5, // Reloads the weapon player is holding + // Supported only in SA + // Callable from SendEvent: YES + // Callable from SendConstEvent: NO + + GINPUT_EVENT_FETCH_GENERAL_SETTINGS = 6, // Fetches a structure filled with GInput general settings + // pParam should point to a GINPUT_GENERAL_SETTINGS structure + // Structure's cbSize field should equal sizeof(GINPUT_GENERAL_SETTINGS) + // Callable from SendEvent: YES + // Callable from SendConstEvent: YES + + GINPUT_EVENT_FETCH_PAD_SETTINGS = 7, // Fetches a structure filled with GInput ped-specific settings + // pParam should point to a GINPUT_PAD_SETTINGS structure + // Structure's cbSize field should equal sizeof(GINPUT_PAD_SETTINGS) + // Callable from SendEvent: YES + // Callable from SendConstEvent: YES + + GINPUT_EVENT_FETCH_SIXAXIS_INPUT = 8, // Fetches a structure filled with last frame's Sixaxis input + // pParam should point to a SIXAXIS_INPUT structure + // If Sixaxis is not supported, this struct will always contain zeroes + // Callable from SendEvent: YES + // Callable from SendConstEvent: YES + + GINPUT_EVENT_REGISTER_SIXAXIS_FETCH_CALLBACK = 9, // Registers a callback function which gets called after Sixaxis sends input + // pParam should be a callback function pointer (see GInputOnSixaxisFetchCallback) + // Cannot be recursive - the callbacks registered from within a callback will be ignored + // Callable from SendEvent: YES + // Callable from SendConstEvent: NO + + GINPUT_EVENT_IS_USING_SCP_DRIVER_CAPABILITIES = 10, // Checks if the game uses the pressure sensitive buttons feature, exclusive to + // SCP Driver Package. + // Returns BOOL - if TRUE, both SCP's xinput1_3.dll and a controller with pressure sensitive + // face buttons (ie. DualShock 3) is used, FALSE otherwise. + // Can be also used to determine whether Sixaxis is available or not + // Callable from SendEvent: YES + // Callable from SendConstEvent: YES + + GINPUT_EVENT_OVERRIDE_SIXAXIS_SETTINGS = 11, // Overrides all Sixaxis toggles. + // if pParam is set to TRUE, the override gets enabled. If pParam is set to FALSE, + // the override gets disabled. + // Returns NULL all the time + // Callable from SendEvent: YES + // Callable from SendConstEvent: NO + + GINPUT_EVENT_FORCE_MAP_PAD_ONE_TO_PAD_TWO = 12, // Forcibly maps XInput pad 2 to game pad 1, same as "MapPadOneToPadTwo" INI option + // if pParam is set to TRUE, the override gets enabled. If pParam is set to FALSE, + // the override gets disabled. + // Returns NULL all the time + // Callable from SendEvent: YES + // Callable from SendConstEvent: NO + + NUM_GINPUT_EVENTS +}; + +struct SIXAXIS_INPUT +{ + short ACCEL_X; + short ACCEL_Y; + short ACCEL_Z; + + short GYRO; +}; + +// Those are options from [GInput] section +// The options not present in the particular game are always 0 +struct GINPUT_GENERAL_SETTINGS +{ + DWORD cbSize; // Fill with sizeof(GINPUT_GENERAL_SETTINGS) + + bool DisableOnFocusLost : 1; + bool Vibration : 1; + bool CheatsFromPad : 1; + bool GuideLaunchesOverlay : 1; + bool ApplyMissionSpecificFixes : 1; + bool ApplyGXTFixes : 1; + bool PlayStationButtons : 1; + bool MapPadOneToPadTwo : 1; + bool FreeAim : 1; + +}; + +// Those are options from [PadX] section +// The options not present in the particular game are always 0 +struct GINPUT_PAD_SETTINGS +{ + DWORD cbSize; // Fill with sizeof(GINPUT_PAD_SETTINGS) + + BYTE ControlsSet : 4; + bool Southpaw : 1; + bool SAStyleSniperZoom : 1; + bool SwapSticksDuringAiming : 1; + bool DrivebyWithAnalog : 1; + bool HotkeyToDriveby : 1; + bool InvertLook : 1; + + bool InvertLeftXAxis : 1; + bool InvertLeftYAxis : 1; + bool SwapLeftAxes : 1; + float LeftStickDeadzone; + float LeftStickSensitivity; + + bool InvertRightXAxis : 1; + bool InvertRightYAxis : 1; + bool SwapRightAxes : 1; + float RightStickDeadzone; + float RightStickSensitivity; + + float FaceButtonsSensitivity; + float SixaxisSensitivity; + bool SixaxisReloading : 1; + bool SixaxisCarSteering : 1; + bool SixaxisBikeSteering : 1; + bool SixaxisBoatSteering : 1; + bool SixaxisHeliSteering : 1; + bool SixaxisPlaneSteering : 1; + bool SixaxisHydraulics : 1; +}; + +// The size of a char array returned from GINPUT_FETCH_CHEAT_STRING +#define GINPUT_CHEAT_STRING_LENGTH 12 + +// Callback called on INI file reload +// Note - it is NOT called the first time INIs are loaded +typedef void (*GInputOnSettingsReloadCallback)(); + +// Callback called when Sixaxis data gets fetched and processed by GInput +// Gets called ONLY on input change +typedef void (CALLBACK *GInputOnSixaxisFetchCallback)(const SIXAXIS_INPUT&); + + + +// Internal declarations +#ifndef GINPUT_COMPILE_CROSSCOMPATIBLE_VERSION +#if defined GINPUT_COMPILE_III_VERSION +#define GINPUT_FILENAMEA "GInputIII" +#define GINPUT_FILENAMEW L"GInputIII" +#elif defined GINPUT_COMPILE_VC_VERSION +#define GINPUT_FILENAMEA "GInputVC" +#define GINPUT_FILENAMEW L"GInputVC" +#elif defined GINPUT_COMPILE_SA_VERSION +#define GINPUT_FILENAMEA "GInputSA" +#define GINPUT_FILENAMEW L"GInputSA" +#endif + +#ifdef UNICODE +#define GINPUT_FILENAME GINPUT_FILENAMEW +#else +#define GINPUT_FILENAME GINPUT_FILENAMEA +#endif + +#endif + +#define GINPUT_MODVERSION 0x00010B00 // 1.11 + +// The pad interface +// The interface is safe to use without validating a pointer - in case of GInput loading failure, +// these functions are set to return false all the time +// NOTE: Do not use any of these functions before GInput_Load is called on your interface pointer! +class IGInputPad +{ +protected: + virtual ~IGInputPad() { }; + +public: + // Returns true when XInput compatible pad is connected + virtual bool IsPadConnected() const =0; + + // Returns true when last input came from a pad, false otherwise + virtual bool HasPadInHands() const =0; + + // Returns installed GInput version (see GINPUT_MODVERSION), -1 on failure + virtual int GetVersion() const =0; + + // Sends an event to GInput, allowing the script to take advantage of GInput features not available + // through CPad or other original GTA functions + // See eGInputEvent enum for supported events and their params/return values (if any) + virtual void* SendEvent(eGInputEvent eEvent, void* pParam)=0; + virtual void* SendConstEvent(eGInputEvent eEvent, void* pParam) const =0; +}; + +// GInput management functions + +// Use one of those functions ONCE to initialise GInput API +// DO NOT use both functions in the same plugin! +// DO NOT CALL THOSE IN DLLMAIN, SINCE GINPUT MIGHT NOT BE INITIALIZED YET AT THE TIME YOUR PLUGIN LOADS +// Takes a pointer to pad interface pointer as an argument, returns true if succeed and false otherwise +// (GInput not installed or any other error occured) +bool GInput_Load(IGInputPad** pInterfacePtr); +// Takes a pointer to an array of TWO interface pointers as an argument, returns true if succeed and false otherwise +// (GInput not installed or any other error occured) +bool GInput_Load_TwoPads(IGInputPad** pInterfacePtr); + +// Releases GInput API +// Call it when your program terminates +void GInput_Release(); + + +// Management functions definitions - internal use only, do not change anything here +#include +#include "ModuleList.hpp" + +// GInput ASI handle +inline HMODULE* _GInput_HandlePtr() +{ + // Making it a static variable outside of the function would duplicate it for each .cpp file which uses any API funcs (bad) + static HMODULE hGInputHandle = nullptr; + return &hGInputHandle; +} + +// Although these functions may not be inlined, we need to declare them as so +inline IGInputPad* _GInput_SafeMode() +{ + static class CDummyPad : public IGInputPad + { + public: + virtual bool IsPadConnected() const { return false; }; + virtual bool HasPadInHands() const { return false; }; + virtual int GetVersion() const { return -1; }; + virtual void* SendEvent(eGInputEvent eEvent, void* pParam) { return CDummyPad::SendConstEvent(eEvent, pParam); }; + virtual void* SendConstEvent(eGInputEvent eEvent, void* pParam) const { + UNREFERENCED_PARAMETER(eEvent); UNREFERENCED_PARAMETER(pParam); + SetLastError(ERROR_NOT_SUPPORTED); + return reinterpret_cast(GINPUT_NO_EVENT); + }; + } DummyClass; + return &DummyClass; +} + +inline bool GInput_Load(IGInputPad** pInterfacePtr) +{ + static IGInputPad* pCopiedPtr = nullptr; // We keep a backup of the interface pointer in case user calls GInput_Load multiple times + static bool bLoadingResult = false; // Loading result is also cached so GInput_Load always returns the same value when called multiple times + + // Have we attempted to load GInput already? If so, just return a valid interface pointer and return + // The pointer can be either a GInput interface or a dummy, 'safe-mode' interface which got initialised + // due to GInput*.asi loading failure + if ( pCopiedPtr != nullptr ) + { + *pInterfacePtr = pCopiedPtr; + return bLoadingResult; + } + +#ifdef GINPUT_COMPILE_CROSSCOMPATIBLE_VERSION + for ( const HMODULE hHandle : ModuleList().GetAllByPrefix( L"GInput" ) ) +#else + const HMODULE hHandle = ModuleList().Get( GINPUT_FILENAMEW ); +#endif + { + if ( hHandle != nullptr ) + { + // Let's call a GInput export to get the proper interface + auto ExportFunc = (IGInputPad*(*)())GetProcAddress(hHandle, "GetGInputInterface"); + if ( ExportFunc == nullptr ) + { + ExportFunc = (IGInputPad*(*)())GetProcAddress(hHandle, (LPCSTR)1); + } + + if ( ExportFunc != nullptr ) + { + *pInterfacePtr = pCopiedPtr = ExportFunc(); + if ( pCopiedPtr != nullptr ) + { + HMODULE duplicatedHandle; + if ( GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)ExportFunc, &duplicatedHandle ) != 0 ) + { + *_GInput_HandlePtr() = duplicatedHandle; + bLoadingResult = true; + return true; + } + } + } + } + } + + *pInterfacePtr = pCopiedPtr = _GInput_SafeMode(); + bLoadingResult = false; + return false; +} + +inline bool GInput_Load_TwoPads(IGInputPad** pInterfacePtr) +{ + static IGInputPad* pCopiedPtr[2]; // We keep a backup of the interface pointer in case user calls GInput_Load multiple times + static bool bLoadingResult = false; // Loading result is also cached so GInput_Load always returns the same value when called multiple times + + // Have we attempted to load GInput already? If so, just return a valid interface pointer and return + // The pointer can be either a GInput interface or a dummy, 'safe-mode' interface which got initialised + // due to GInput*.asi loading failure + if ( pCopiedPtr[0] != nullptr && pCopiedPtr[1] != nullptr ) + { + pInterfacePtr[0] = pCopiedPtr[0]; + pInterfacePtr[1] = pCopiedPtr[1]; + return bLoadingResult; + } + +#ifdef GINPUT_COMPILE_CROSSCOMPATIBLE_VERSION + for ( const HMODULE hHandle : ModuleList().GetAllByPrefix( L"GInput" ) ) +#else + const HMODULE hHandle = ModuleList().Get( GINPUT_FILENAMEW ); +#endif + { + if ( hHandle != nullptr ) + { + // Let's call a GInput export to get the proper interface + auto ExportFunc = (IGInputPad**(*)())GetProcAddress(hHandle, "GetGInputInterface_2Pads"); + if ( ExportFunc == nullptr ) + { + ExportFunc = (IGInputPad**(*)())GetProcAddress(hHandle, (LPCSTR)2); + } + + if ( ExportFunc != nullptr ) + { + IGInputPad** pad = ExportFunc(); + pInterfacePtr[0] = pCopiedPtr[0] = pad[0]; + pInterfacePtr[1] = pCopiedPtr[1] = pad[1]; + if ( pCopiedPtr[0] != nullptr && pCopiedPtr[1] != nullptr ) + { + HMODULE duplicatedHandle; + if ( GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)ExportFunc, &duplicatedHandle ) != 0 ) + { + *_GInput_HandlePtr() = duplicatedHandle; + bLoadingResult = true; + return true; + } + } + } + } + } + + IGInputPad* pad = _GInput_SafeMode(); + pInterfacePtr[0] = pCopiedPtr[0] = pad; + pInterfacePtr[1] = pCopiedPtr[1] = pad; + bLoadingResult = false; + return false; +} + +inline void GInput_Release() +{ + HMODULE* pHandle = _GInput_HandlePtr(); + if ( *pHandle != nullptr ) + { + FreeLibrary(*pHandle); + *pHandle = nullptr; + } +} + +#endif \ No newline at end of file diff --git a/ModuleList.hpp b/ModuleList.hpp new file mode 100644 index 0000000..f6b0dbc --- /dev/null +++ b/ModuleList.hpp @@ -0,0 +1,212 @@ +#pragma once + +#include +#include +#include + +// Stores a list of loaded modules with their names, WITHOUT extension +class ModuleList +{ +public: + struct LazyEnumerateTag {}; + + ModuleList() + { + Enumerate(); + } + + explicit ModuleList(LazyEnumerateTag) + { + } + + // Initializes module list + // Needs to be called before any calls to Get or GetAll + void Enumerate() + { + // Cannot enumerate twice without cleaing + assert(m_moduleList.size() == 0); + + constexpr size_t INITIAL_SIZE = sizeof(HMODULE) * 256; + HMODULE* modules = static_cast(malloc(INITIAL_SIZE)); + if (modules != nullptr) + { + typedef BOOL(WINAPI * Func)(HANDLE hProcess, HMODULE * lphModule, DWORD cb, LPDWORD lpcbNeeded); + + HMODULE hLib = LoadLibrary(TEXT("kernel32")); + assert(hLib != nullptr); // If this fails then everything is probably broken anyway + + Func pEnumProcessModules = reinterpret_cast(GetProcAddress(hLib, "K32EnumProcessModules")); + if (pEnumProcessModules == nullptr) + { + // Try psapi + FreeLibrary(hLib); + hLib = LoadLibrary(TEXT("psapi")); + if (hLib != nullptr) + { + pEnumProcessModules = reinterpret_cast(GetProcAddress(hLib, "EnumProcessModules")); + } + } + + if (pEnumProcessModules != nullptr) + { + const HANDLE currentProcess = GetCurrentProcess(); + DWORD cbNeeded = 0; + if (pEnumProcessModules(currentProcess, modules, INITIAL_SIZE, &cbNeeded) != 0) + { + if (cbNeeded > INITIAL_SIZE) + { + HMODULE* newModules = static_cast(realloc(modules, cbNeeded)); + if (newModules != nullptr) + { + modules = newModules; + + if (pEnumProcessModules(currentProcess, modules, cbNeeded, &cbNeeded) != 0) + { + EnumerateInternal(modules, cbNeeded / sizeof(HMODULE)); + } + } + else + { + EnumerateInternal(modules, INITIAL_SIZE / sizeof(HMODULE)); + } + } + else + { + EnumerateInternal(modules, cbNeeded / sizeof(HMODULE)); + } + } + } + + if (hLib != nullptr) + { + FreeLibrary(hLib); + } + + free(modules); + } + } + + // Recreates module list + void ReEnumerate() + { + Clear(); + Enumerate(); + } + + // Clears module list + void Clear() + { + m_moduleList.clear(); + } + + // Gets handle of a loaded module with given name, NULL otherwise + HMODULE Get(const wchar_t* moduleName) const + { + // If vector is empty then we're trying to call it without calling Enumerate first + assert(m_moduleList.size() != 0); + + auto it = std::find_if(m_moduleList.begin(), m_moduleList.end(), [&](const auto& e) { + return _wcsicmp(moduleName, e.second.c_str()) == 0; + }); + return it != m_moduleList.end() ? it->first : nullptr; + } + + // Gets handles to all loaded modules with given name + std::vector GetAll(const wchar_t* moduleName) const + { + // If vector is empty then we're trying to call it without calling Enumerate first + assert(m_moduleList.size() != 0); + + std::vector results; + for (auto& e : m_moduleList) + { + if (_wcsicmp(moduleName, e.second.c_str()) == 0) + { + results.push_back(e.first); + } + } + + return results; + } + + // Gets handle of a loaded module with given prefix, NULL otherwise + HMODULE GetByPrefix(const wchar_t* modulePrefix) const + { + // If vector is empty then we're trying to call it without calling Enumerate first + assert(m_moduleList.size() != 0); + + const size_t len = wcslen(modulePrefix); + auto it = std::find_if(m_moduleList.begin(), m_moduleList.end(), [&](const auto& e) { + return _wcsnicmp(modulePrefix, e.second.c_str(), len) == 0; + }); + return it != m_moduleList.end() ? it->first : nullptr; + } + + // Gets handles to all loaded modules with given prefix + std::vector GetAllByPrefix(const wchar_t* modulePrefix) const + { + // If vector is empty then we're trying to call it without calling Enumerate first + assert(m_moduleList.size() != 0); + + const size_t len = wcslen(modulePrefix); + std::vector results; + for (auto& e : m_moduleList) + { + if (_wcsnicmp(modulePrefix, e.second.c_str(), len) == 0) + { + results.push_back(e.first); + } + } + + return results; + } + +private: + void EnumerateInternal(HMODULE* modules, size_t numModules) + { + size_t moduleNameLength = MAX_PATH; + wchar_t* moduleName = static_cast(malloc(moduleNameLength * sizeof(moduleName[0]))); + if (moduleName != nullptr) + { + m_moduleList.reserve(numModules); + for (size_t i = 0; i < numModules; i++) + { + // Obtain module name, with resizing if necessary + DWORD size; + while (size = GetModuleFileNameW(*modules, moduleName, moduleNameLength), size == moduleNameLength) + { + wchar_t* newName = static_cast(realloc(moduleName, 2 * moduleNameLength * sizeof(moduleName[0]))); + if (newName != nullptr) + { + moduleName = newName; + moduleNameLength *= 2; + } + else + { + size = 0; + break; + } + } + + if (size != 0) + { + const wchar_t* nameBegin = wcsrchr(moduleName, '\\') + 1; + const wchar_t* dotPos = wcsrchr(nameBegin, '.'); + if (dotPos != nullptr) + { + m_moduleList.emplace_back(*modules, std::wstring(nameBegin, dotPos)); + } + else + { + m_moduleList.emplace_back(*modules, nameBegin); + } + } + modules++; + } + + free(moduleName); + } + } + + std::vector< std::pair > m_moduleList; +}; \ No newline at end of file diff --git a/Pad.cpp b/Pad.cpp index 65df70c..afc3047 100644 --- a/Pad.cpp +++ b/Pad.cpp @@ -24,9 +24,11 @@ WRAPPER bool CPad::GetLookRight(void) { EAXJMP(glrAddress); } WRAPPER bool CPad::GetLookLeft(void) { EAXJMP(gllAddress); } WRAPPER int16 CPad::GetCarGunLeftRight(void) { EAXJMP(gcglrAddress); } WRAPPER int16 CPad::GetCarGunUpDown(void) { EAXJMP(gcgudAddress); } +WRAPPER int16 CPad::GetSteeringUpDown(void) { EAXJMP(gsudAddress); } + +// Unused WRAPPER int16 CPad::LookAroundLeftRight(void) { EAXJMP(lalrAddress); } WRAPPER int16 CPad::LookAroundUpDown(void) { EAXJMP(laudAddress); } -WRAPPER int16 CPad::GetSteeringUpDown(void) { EAXJMP(gsudAddress); } #if 0 int16 CPad::GetSteeringUpDown(void) diff --git a/SACarCam.cpp b/SACarCam.cpp index 423f076..1dce71d 100644 --- a/SACarCam.cpp +++ b/SACarCam.cpp @@ -4,6 +4,9 @@ #include "Camera.h" #include "Pad.h" +#include "ModuleList.hpp" +#include "GInputAPI.h" + HMODULE dllModule, hDummyHandle; int gtaversion = -1; @@ -27,6 +30,10 @@ int gtaversion = -1; #define DefaultFOV 70.0f +IGInputPad* ginputPad; +int ginputLoaded = 0; // 1: not installed 2: installed +GINPUT_PAD_SETTINGS padSettings = {}; + // Car zoom modes per veh. types doesn't exist in III, so we will emulate it :) // We're just injecting the values for VC float CarZoomModes[] = { @@ -37,7 +44,6 @@ float CarZoomModes[] = { /* // Original SA array, but doesn't give same results as SA (Due to collision bounding box sizes?) -// LCS has different mid/far values, near is same float CarZoomModes[] = { -1.0f, -0.2f, -3.2f, 0.05f, -2.41f, // near 1.0f, 1.4f, 0.65f, 1.9f, 6.49f, // mid @@ -199,6 +205,15 @@ Process_FollowCar_SA(const CVector& CameraTarget, float TargetOrientation, CamCl if (!cam->CamTargetEntity->IsVehicle()) return; + if (!ginputLoaded) { + if (GInput_Load(&ginputPad)) { + padSettings.cbSize = sizeof(GINPUT_PAD_SETTINGS); + ginputPad->SendEvent(GINPUT_EVENT_FETCH_PAD_SETTINGS, &padSettings); + ginputLoaded = 2; + } else + ginputLoaded = 1; + } + VehicleClass* car = (VehicleClass*)cam->CamTargetEntity; CVector TargetCoors = CameraTarget; uint8 camSetArrPos = 0; @@ -235,7 +250,6 @@ Process_FollowCar_SA(const CVector& CameraTarget, float TargetOrientation, CamCl camSetArrPos = 2; } - // Seems same with LCS, at least the ones I've looked. float CARCAM_SET[][15] = { {1.3f, 1.0f, 0.4f, 10.0f, 15.0f, 0.5f, 1.0f, 1.0f, 0.85f, 0.2f, 0.075f, 0.05f, 0.8f, 0.785398f, 1.5533431f}, {1.1f, 1.0f, 0.1f, 10.0f, 11.0f, 0.5f, 1.0f, 1.0f, 0.85f, 0.2f, 0.075f, 0.05f, 0.75f, 0.78539819f, 1.5533431f}, @@ -246,7 +260,6 @@ Process_FollowCar_SA(const CVector& CameraTarget, float TargetOrientation, CamCl {1.1f, 1.0f, 0.2f, 10.0f, 5.0f, 0.5f, 1.0f, 1.0f, 0.75f, 0.1f, 0.005f, 0.2f, 1.0f, 0.34906587f, 1.2217305f} // rc heli/planes }; - // First value of the array below is 0.12f in LCS (camera is higher in near mode) float ZmOneAlphaOffset[] = { 0.08f, 0.08f, 0.15f, 0.08f, 0.08f }; float ZmTwoAlphaOffset[] = { 0.07f, 0.08f, 0.3f, 0.08f, 0.08f }; float ZmThreeAlphaOffset[] = { 0.055f, 0.05f, 0.15f, 0.06f, 0.08f }; @@ -543,13 +556,23 @@ Process_FollowCar_SA(const CVector& CameraTarget, float TargetOrientation, CamCl targetAlphaBlendAmount = maxAlphaBlendAmount; } - // Right stick - float stickX = -(pad->LookAroundLeftRight()); - float stickY = pad->LookAroundUpDown(); + // Using GetCarGun(LR/UD) with Y-axis invert check will give us same unprocessed RightStick value as SA + float stickX = -(pad->GetCarGunLeftRight()); + float stickY = pad->GetCarGunUpDown(); // Why?? if (m_bUseMouse3rdPerson) stickY = 0.0f; + else { + // Added in r4. GInput doesn't hook VC's Y-axis invert option, so that was needed + if (ginputPad->HasPadInHands() && padSettings.InvertLook) + stickY = -stickY; + + // Hidden Y-axis invert option in VC. just in case + if (isVC()) + if (*(bool*)0xA10AF7) + stickY = -stickY; + } float v103 = cam->FOV * 0.0125f;