From f5cafcc44dc3d35b40e8f2a8d4a028e5058c9aca Mon Sep 17 00:00:00 2001 From: Rob Lambell Date: Fri, 28 Feb 2014 20:53:13 +0000 Subject: [PATCH] Added Gtuner Plugin --- ControllerMAX-XInput.sln | 6 + Gtuner-Plugin/Gtuner-Plugin.cpp | 241 ++++++++++++ Gtuner-Plugin/Gtuner-Plugin.def | 7 + Gtuner-Plugin/Gtuner-Plugin.vcxproj | 63 +++ Gtuner-Plugin/Gtuner-Plugin.vcxproj.filters | 12 + Gtuner-Plugin/gcapi.h | 413 ++++++++++++++++++++ Gtuner-Plugin/gcdapi.dll | Bin 0 -> 37376 bytes 7 files changed, 742 insertions(+) create mode 100644 Gtuner-Plugin/Gtuner-Plugin.cpp create mode 100644 Gtuner-Plugin/Gtuner-Plugin.def create mode 100644 Gtuner-Plugin/Gtuner-Plugin.vcxproj create mode 100644 Gtuner-Plugin/Gtuner-Plugin.vcxproj.filters create mode 100644 Gtuner-Plugin/gcapi.h create mode 100644 Gtuner-Plugin/gcdapi.dll diff --git a/ControllerMAX-XInput.sln b/ControllerMAX-XInput.sln index 7e7bed5..4992865 100644 --- a/ControllerMAX-XInput.sln +++ b/ControllerMAX-XInput.sln @@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ControllerMAX-XInput", "ControllerMAX-XInput\ControllerMAX-XInput.vcxproj", "{B9AE96C0-F09B-400A-8AF1-15A9F5D659A8}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Gtuner Plugin", "Gtuner-Plugin\Gtuner-Plugin.vcxproj", "{DB57F735-DD4B-4398-82DB-3D43A12902F6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -13,6 +15,10 @@ Global {B9AE96C0-F09B-400A-8AF1-15A9F5D659A8}.Debug|Win32.Build.0 = Release|Win32 {B9AE96C0-F09B-400A-8AF1-15A9F5D659A8}.Release|Win32.ActiveCfg = Release|Win32 {B9AE96C0-F09B-400A-8AF1-15A9F5D659A8}.Release|Win32.Build.0 = Release|Win32 + {DB57F735-DD4B-4398-82DB-3D43A12902F6}.Debug|Win32.ActiveCfg = Release|Win32 + {DB57F735-DD4B-4398-82DB-3D43A12902F6}.Debug|Win32.Build.0 = Release|Win32 + {DB57F735-DD4B-4398-82DB-3D43A12902F6}.Release|Win32.ActiveCfg = Release|Win32 + {DB57F735-DD4B-4398-82DB-3D43A12902F6}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Gtuner-Plugin/Gtuner-Plugin.cpp b/Gtuner-Plugin/Gtuner-Plugin.cpp new file mode 100644 index 0000000..198e050 --- /dev/null +++ b/Gtuner-Plugin/Gtuner-Plugin.cpp @@ -0,0 +1,241 @@ +/* ControllerMAX-XInput + * + * Plugin for Gtuner + * http://www.controllermax.com + */ + +#include + +/* Define the API model (PLUGIN or DIRECT) before including the + * gcapi.h header file. + */ +#define GPPAPI_PLUGIN +#include "gcapi.h" + + +/* Plugin API callback functions. Check gppapi.h for a more detailed + * description. + */ +GCAPI_IsConnected gcapi_IsConnected = NULL; +GCAPI_GetFWVer gcapi_GetFWVer = NULL; +GCAPI_Read gcapi_Read = NULL; +GCAPI_Write gcapi_Write = NULL; +GCAPI_GetTimeVal gcapi_GetTimeVal = NULL; +GCAPI_CalcPressTime gcapi_CalcPressTime = NULL; +GPPAPI_Exit gpp_Exit = NULL; +GPPAPI_Proc gpp_Proc = NULL; + + +// +// XInput Structures +// +struct XInputStateEx +{ + unsigned long eventCount; // increases with every controller event, but not by one. + unsigned short up:1, down:1, left:1, right:1, start:1, back:1, leftThumb:1, +rightThumb:1, leftShoulder:1, rightShoulder:1, guideButton:1, unknown:1, +aButton:1, bButton:1, xButton:1, yButton:1; + unsigned char leftTrigger; + unsigned char rightTrigger; + short thumbLX; + short thumbLY; + short thumbRX; + short thumbRY; +}; + +struct XInputVibration +{ + WORD wLeftMotorSpeed; + WORD wRightMotorSpeed; +}; + +int controllerNum = 0; + +HINSTANCE hInsXInput1_3 = NULL; + +// typedef the function. It takes an int and a pointer to an XInputStateEx and returns an error code +// as an int. It's 0 for no error and 1167 for "controller not present". Presumably there are others +// but I never saw them. It won't cause a crash on error, it just won't update the data. +typedef int(__stdcall * pICFUNC1)(int, XInputStateEx &); +pICFUNC1 XInputGetStateEx; + +typedef int(__stdcall * pICFUNC2)(int, XInputVibration &); +pICFUNC2 XInputSetState; + +// Create a Controller State +XInputStateEx controllerState; + +// Create a Vibration State +XInputVibration vibration; + +int iround(double num) { + return (num > 0.0) ? (int)floor(num + 0.5) : (int)ceil(num - 0.5); +} + + +/* gpp_PluginName [mandatory] + * Provides to the GPP SW the name of your plugin. + */ +const char * _stdcall gpp_PluginName() { + return("XInput"); +} + + +/* gpp_Load [mandatory] + * The GPP software call the gpp_Load when the plugin dll is loaded. + * Parameters: the hmodule of DLL + * the version of software + * pointers to callback functions + * Return 1 on success, or return 0 on failure. + */ +uint8_t _stdcall gpp_Load(HMODULE hmodule, uint16_t sw_version, GPPAPI_CALLBACKS *callbacks) { + if(sw_version < 0x0131) { + // Check SW compatibility + return(0); + } + // Set up the callback functions + gcapi_IsConnected = callbacks->IsConnected; + gcapi_GetFWVer = callbacks->GetFWVer; + gcapi_Read = callbacks->Read; + gcapi_Write = callbacks->Write; + gcapi_GetTimeVal = callbacks->GetTimeVal; + gcapi_CalcPressTime = callbacks->CalcPressTime; + gpp_Exit = callbacks->Exit; + gpp_Proc = callbacks->Proc; + + + // Create hInstance of xinput1_3 + HINSTANCE hInsXInput1_3 = LoadLibrary(L"xinput1_3.dll"); + if(hInsXInput1_3 == NULL) + { + return(0); + } + + // Alternative to XInputGetState + // https://github.com/DieKatzchen/GuideButtonPoller + // Details on unnamed ordinals: + // https://code.google.com/p/x360ce/issues/detail?id=417 + + // Get the address of ordinal 100 (unnamed: XInputGetStateEx) - exposes Guide button + FARPROC pointerToDLLFunction1 = GetProcAddress(HMODULE(hInsXInput1_3), (LPCSTR)100); + + // Assign XInputGetStateEx for easier use + XInputGetStateEx = pICFUNC1(pointerToDLLFunction1); + + // Get the pointer to XInputSetState (ordinal 3) + FARPROC pointerToDLLFunction2 = GetProcAddress(HMODULE(hInsXInput1_3), "XInputSetState"); + + // Assign XInputSetState for easier use + XInputSetState = pICFUNC2(pointerToDLLFunction2); + + return(1); +} + + +/* gpp_Unload [optional] + * The GPP software call the gpp_Unload (if it exist) when is need to + * immediately quit the plugin, for example, in case the user has closed + * the GPP application. Use this function to free the memory and resources. + */ +void _stdcall gpp_Unload() { + + FreeLibrary(hInsXInput1_3); + + return; +} + + +/* gpp_ReportUpdate [optional] + * The GPP software call the gpp_ReportUpdate function whenever it receives + * a new set of data from GPP device. You can use this function to read the + * input data (from controller) and/or to set the output data (which will be + * sent to the console). + */ +void _stdcall gpp_ReportUpdate() { + GCAPI_REPORT report; + int8_t output[GCAPI_OUTPUT_TOTAL] = {0}; + + DWORD result = XInputGetStateEx(controllerNum, controllerState); + + bool controllerConnected = (result == ERROR_SUCCESS ? true : false); + + if(gcapi_Read) + { + gcapi_Read(&report); + + if(controllerConnected) + { + // Left Thumb + float LX = controllerState.thumbLX; + float LY = controllerState.thumbLY; + + int8_t percentageLX = (int8_t)iround((LX / 32767) * 100); + int8_t percentageLY = (int8_t)iround((LY / 32767) * 100); + + // CM expects Y-axis -100 up, 100 down + percentageLY *= -1; + + // Right Thumb + float RX = controllerState.thumbRX; + float RY = controllerState.thumbRY; + + int8_t percentageRX = (int8_t)iround((RX / 32767) * 100); + int8_t percentageRY = (int8_t)iround((RY / 32767) * 100); + + // CM expects Y-axis -100 up, 100 down + percentageRY *= -1; + + // Left Trigger + float LT = (float)controllerState.leftTrigger; + int8_t percentageLT = (int8_t)iround((LT / 255) * 100); + + // Right Trigger + float RT = (float)controllerState.rightTrigger; + int8_t percentageRT = (int8_t)iround((RT / 255) * 100); + + // Vibrate XInput controller + // reported as [0 ~ 100] %, XInput range [0 ~ 65535] + vibration.wRightMotorSpeed = iround(655.35 * (float)report.rumble[0]); + vibration.wLeftMotorSpeed = iround(655.35 * (float)report.rumble[1]); + XInputSetState(controllerNum, vibration); + + // Output to console + output[0] = controllerState.guideButton ? 100 : 0; // Guide + output[1] = controllerState.back ? 100 : 0; // Back + output[2] = controllerState.start ? 100 : 0; // Start + output[3] = controllerState.rightShoulder ? 100 : 0; // Right Shoulder + output[4] = percentageRT; // Right Trigger [0 ~ 100] % + output[5] = controllerState.rightThumb ? 100 : 0; // Right Analog Stick (Pressed) + output[6] = controllerState.leftShoulder ? 100 : 0; // Left Shoulder + output[7] = percentageLT; // Left Trigger [0 ~ 100] % + output[8] = controllerState.leftThumb ? 100 : 0; // Left Analog Stick (Pressed) + output[9] = percentageRX; // Right Analog Stick X-axis [-100 ~ 100] % + output[10] = percentageRY; // Right Analog Stick Y-axis [-100 ~ 100] % + output[11] = percentageLX; // Left Analog Stick X-axis [-100 ~ 100] % + output[12] = percentageLY; // Left Analog Stick Y-axis [-100 ~ 100] % + output[13] = controllerState.up ? 100 : 0; // DPad Up + output[14] = controllerState.down ? 100 : 0; // DPad Down + output[15] = controllerState.left ? 100 : 0; // DPad Left + output[16] = controllerState.right ? 100 : 0; // DPad Right + output[17] = controllerState.yButton ? 100 : 0; // Y + output[18] = controllerState.bButton ? 100 : 0; // B + output[19] = controllerState.aButton ? 100 : 0; // A + output[20] = controllerState.xButton ? 100 : 0; // X + } + else + { + // Passthrough mode + // Input from controller to console + for(uint8_t i=0; i + + + + Release + Win32 + + + + {DB57F735-DD4B-4398-82DB-3D43A12902F6} + Win32Proj + GtunerPluginTemplate + Gtuner Plugin + + + + DynamicLibrary + false + true + Unicode + + + + + + + + + + false + ControllerMAX-XInput + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;GTUNERPLUGINTEMPLATE_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + true + true + Gtuner-Plugin.def + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Gtuner-Plugin/Gtuner-Plugin.vcxproj.filters b/Gtuner-Plugin/Gtuner-Plugin.vcxproj.filters new file mode 100644 index 0000000..d4d80c4 --- /dev/null +++ b/Gtuner-Plugin/Gtuner-Plugin.vcxproj.filters @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Gtuner-Plugin/gcapi.h b/Gtuner-Plugin/gcapi.h new file mode 100644 index 0000000..47b9336 --- /dev/null +++ b/Gtuner-Plugin/gcapi.h @@ -0,0 +1,413 @@ +/* gcapi.h + * + * Copyright (c) 2011-2014 ISystem Ltda + * + * This file is part of Gtuner and Gamepad Proxy Projects + * http://www.gamepadproxy.com + * + * GPP and CM are USB a devices which provides an open programming + * interface for computer applications receive input data from game + * controllers, and send commands to console (PS4, XBox One, PS3 and + * XBox360). + * + * This header defines the directives used by the plugin API (Gtuner.exe) + * and by the GPP/CM direct API (gcdapi.dll). + * + * Changes: + * 11-Mar-2012: Included to use HMODULE and NULL statements. + * 14-Feb-2012: gppapi.h replaces the gpp_plugin.h and includes the + * directives for the direct API. + * 11-Nov-2013: Added Dualshock 4 (PS4 controller) directives. + * 22-Nov-2013: Added Playstation 4 (console) directives. + * 26-Nov-2013: Added Xbox One controller directives. + * 04-Dec-2013: Added Xbox One (console) directives. + */ + +#ifndef GCAPI_H +#define GCAPI_H + +#include + +/* Exact-width Integer Types + */ +#include + + +/* GPP/CM Input Port State + * The four highest bits defines the controller connected into GPP/CM + * device, and the lower bits defines the controller extension, if + * applicable. + */ +#define CONTROLLER_DISCONNECTED 0x00 +#define CONTROLLER_PS3 0x10 +#define CONTROLLER_XB360 0x20 +#define CONTROLLER_WII 0x30 +#define CONTROLLER_PS4 0x40 +#define CONTROLLER_XB1 0x50 + +#define EXTENSION_NUNCHUK 0x01 +#define EXTENSION_CLASSIC 0x02 + + +/* GPP/CM Output Port State + * Defines the possible states for the output port of the GPP/CM device. + * The state indicates the output protocol in use and not necessarily + * the console model in which the GPP/CM device is plugged. + */ +#define CONSOLE_DISCONNECTED 0 +#define CONSOLE_PS3 1 +#define CONSOLE_XB360 2 +#define CONSOLE_PS4 3 +#define CONSOLE_XB1 4 + + +/* Player LED State + * Each player LED of the controller can be in one of four states: + * off, on, blinking rapidly or blinking slowly. These states are + * consistent with what is expected for the PS3. The GPP/CM does + * not reproduce all the possible states for XBox 360 system. + */ +#define LED_OFF 0 +#define LED_ON 1 +#define LED_FAST 2 +#define LED_SLOW 3 + + +/* PS4 Input/Output Indexes + * The GCAPI implements a generic structure to accommodate all the entries + * of a given system/controller/protocol, below are defined the position + * of the entries for the PS4. + */ +#define PS4_PS 0 +#define PS4_SHARE 1 +#define PS4_OPTIONS 2 +#define PS4_R1 3 +#define PS4_R2 4 +#define PS4_R3 5 +#define PS4_L1 6 +#define PS4_L2 7 +#define PS4_L3 8 +#define PS4_RX 9 +#define PS4_RY 10 +#define PS4_LX 11 +#define PS4_LY 12 +#define PS4_UP 13 +#define PS4_DOWN 14 +#define PS4_LEFT 15 +#define PS4_RIGHT 16 +#define PS4_TRIANGLE 17 +#define PS4_CIRCLE 18 +#define PS4_CROSS 19 +#define PS4_SQUARE 20 +#define PS4_ACCX 21 +#define PS4_ACCY 22 +#define PS4_ACCZ 23 +#define PS4_GYROX 24 +#define PS4_GYROY 25 +#define PS4_GYROZ 26 +#define PS4_TOUCH 27 +#define PS4_TOUCHX 28 +#define PS4_TOUCHY 29 + + +/* PS3 Input/Output Indexes + * Defines the position of the entries for the PS3. + */ +#define PS3_PS 0 +#define PS3_SELECT 1 +#define PS3_START 2 +#define PS3_R1 3 +#define PS3_R2 4 +#define PS3_R3 5 +#define PS3_L1 6 +#define PS3_L2 7 +#define PS3_L3 8 +#define PS3_RX 9 +#define PS3_RY 10 +#define PS3_LX 11 +#define PS3_LY 12 +#define PS3_UP 13 +#define PS3_DOWN 14 +#define PS3_LEFT 15 +#define PS3_RIGHT 16 +#define PS3_TRIANGLE 17 +#define PS3_CIRCLE 18 +#define PS3_CROSS 19 +#define PS3_SQUARE 20 +#define PS3_ACCX 21 +#define PS3_ACCY 22 +#define PS3_ACCZ 23 +#define PS3_GYRO 24 + + +/* XBox One Input/Output Indexes + * Defines the position of the entries for the XBox One. + */ +#define XB1_XBOX 0 +#define XB1_VIEW 1 +#define XB1_MENU 2 +#define XB1_RB 3 +#define XB1_RT 4 +#define XB1_RS 5 +#define XB1_LB 6 +#define XB1_LT 7 +#define XB1_LS 8 +#define XB1_RX 9 +#define XB1_RY 10 +#define XB1_LX 11 +#define XB1_LY 12 +#define XB1_UP 13 +#define XB1_DOWN 14 +#define XB1_LEFT 15 +#define XB1_RIGHT 16 +#define XB1_Y 17 +#define XB1_B 18 +#define XB1_A 19 +#define XB1_X 20 + + +/* XBox 360 Input/Output Indexes + * Defines the position of the entries for the XBox 360. + */ +#define XB360_XBOX 0 +#define XB360_BACK 1 +#define XB360_START 2 +#define XB360_RB 3 +#define XB360_RT 4 +#define XB360_RS 5 +#define XB360_LB 6 +#define XB360_LT 7 +#define XB360_LS 8 +#define XB360_RX 9 +#define XB360_RY 10 +#define XB360_LX 11 +#define XB360_LY 12 +#define XB360_UP 13 +#define XB360_DOWN 14 +#define XB360_LEFT 15 +#define XB360_RIGHT 16 +#define XB360_Y 17 +#define XB360_B 18 +#define XB360_A 19 +#define XB360_X 20 + + +/* Wiimote Input Indexes + * Defines the position of the entries for the Wiimote. + */ +#define WII_HOME 0 +#define WII_MINUS 1 +#define WII_PLUS 2 +#define WII_ONE 5 +#define WII_TWO 8 +#define WII_UP 13 +#define WII_DOWN 14 +#define WII_LEFT 15 +#define WII_RIGHT 16 +#define WII_B 18 +#define WII_A 19 +#define WII_ACCX 21 +#define WII_ACCY 22 +#define WII_ACCZ 23 +#define WII_IRX 28 +#define WII_IRY 29 + +/* Nunchuk Input Indexes + */ +#define WII_C 6 +#define WII_Z 7 +#define WII_NX 11 +#define WII_NY 12 +#define WII_ACCNX 25 +#define WII_ACCNY 26 +#define WII_ACCNZ 27 + +/* Classic Controller [PRO] Input Indexes + */ +#define WII_RT 3 +#define WII_ZR 4 +#define WII_LT 6 +#define WII_ZL 7 +#define WII_RX 9 +#define WII_RY 10 +#define WII_LX 11 +#define WII_LY 12 +#define WII_X 17 +#define WII_Y 20 + + +/* Trace Output Indexes + * The TRACE entries were created to check variables and to debug + * GPC scripts. If you are programming a plugin you can use this + * feature to check and plot values on the Gtuner Monitor. These + * entries do not have usability in the direct access API. + */ +#define TRACE_1 30 +#define TRACE_2 31 +#define TRACE_3 32 +#define TRACE_4 33 +#define TRACE_5 34 +#define TRACE_6 35 + + +/* Input/Output Total Indexes + * Total number of indexes of the data structure for inputs and outputs. + */ +#define GCAPI_INPUT_TOTAL 30 +#define GCAPI_OUTPUT_TOTAL 36 + + +/* GCAPI Report Type + * This data structure is intended to inform the plugin or the application + * about the current state of the GPP/CM input and output ports, about + * parameters set by the console (LED, rumble, battery) and, as well as, + * about the current values of the buttons, sticks and sensors. + * + * Read-only data structure. + */ +typedef struct { + uint8_t console; // Receives values established by the #defines CONSOLE_* + uint8_t controller; // Values from #defines CONTROLLER_* and EXTENSION_* + uint8_t led[4]; // Four LED - #defines LED_* + uint8_t rumble[2]; // Two rumbles - Range: [0 ~ 100] % + uint8_t battery_level; // Battery level - Range: [0 ~ 10] 0 = empty, 10 = full + struct { + int8_t value; // Current value - Range: [-100 ~ 100] % + int8_t prev_value; // Previous value - Range: [-100 ~ 100] % + uint32_t press_tv; // Time marker for the button press event + } input[GCAPI_INPUT_TOTAL]; // Input structure (for controller entries) +} GCAPI_REPORT; + + +/* Generic API functions + * Type definition of generic (plugin and direct access) API functions. + * The _stdcall calling convention is used to call API functions. + * + * IsConnected - Check if the GPP/CM device is connected + * GetFWVer - Get device firmware version + * Read - Read GCDAPI_REPORT (states of device and controller) + * Write - Write output[GCDAPI_OUTPUT_TOTAL] (send it to console) + * GetTimeVal - Get current time value + * CalcPressTime - Calculates the pressing time of a entry. + */ +typedef uint8_t (_stdcall *GCAPI_IsConnected)(); +typedef uint16_t (_stdcall *GCAPI_GetFWVer)(); +typedef uint8_t (_stdcall *GCAPI_Read)(GCAPI_REPORT *); +typedef uint8_t (_stdcall *GCAPI_Write)(int8_t *); +typedef uint32_t (_stdcall *GCAPI_GetTimeVal)(); +typedef uint32_t (_stdcall *GCAPI_CalcPressTime)(uint32_t); + + +/* Direct API functions + * Type definition of specific direct API functions. These + * functions can be accessed through the gcdapi.dll library. Don't + * forget to define GCAPI_DIRECT before include this header in order + * to use these typedefs. + * + * Load - Mandatory! Call after load the gcdapi.dll library + * Unload - Mandatory! Call before unload the gcdapi.dll library + * ... - Plus generic API functions. + */ +#ifdef GCAPI_DIRECT +typedef uint8_t (_stdcall *GCDAPI_Load)(); +typedef void (_stdcall *GCDAPI_Unload)(); +#endif /* GCAPI_DIRECT */ + + +/* Gtuner Plugin API functions - Only avaliable for the GPP/CM devices. + * Defines the specific directives of plugin API. Don't forget to + * define GPPAPI_PLUGIN before include this header in order to use these + * directives. + */ +#ifdef GPPAPI_PLUGIN + +/* Proc Commands + * Definition of the commands identifiers processed by the Proc callback + * function. + */ +#define GPPAPI_GET_MAIN_HWND 0x01 +#define GPPAPI_OPEN_GPPMONITOR 0x20 +#define GPPAPI_CLOSE_GPPMONITOR 0x21 +#define GPPAPI_NEW_SCRIPT 0xB0 + +/* Proc Callback Function - Type Definition + * The Proc function is an application-defined function that processes + * generic commands sent from the Plugin to the Gtuner software (Gtuner.exe). + * The second and third parameters of the Proc() callback function are + * dependent on the command identifier (first parameter). For some + * command identifiers these has values and for some others these are + * either NULL or 0. + */ +typedef uint8_t (_stdcall *GPPAPI_Proc)(uint8_t, void *, void *); + +/* Exit Callback Function - Type Definition + * The plugin must call the Exit function if it want to be unloaded. + */ +typedef void (_stdcall *GPPAPI_Exit)(); + +/* Callback List Type + * List of callback functions used by the plugin to send commands and + * access the data structures related to the Gtuner API. These functions + * have the same structure and purpose of the exported functions in the + * gcdapi.dll library, which makes easy to use the same code to produce + * both an application or a plugin. + */ +typedef struct { + GCAPI_IsConnected IsConnected; + GCAPI_GetFWVer GetFWVer; + GCAPI_Read Read; + GCAPI_Write Write; + GCAPI_GetTimeVal GetTimeVal; + GCAPI_CalcPressTime CalcPressTime; + GPPAPI_Exit Exit; + GPPAPI_Proc Proc; +} GPPAPI_CALLBACKS; + +/* Plugin Exported Functions + * You should implement (or define an empty function body) those + * functions which are called by Gtuner (Gtuner.exe) plugin manager. + * The _stdcall calling convention is used on exported functions of + * plugin DLL. Functions that use this calling convention require a + * function prototype. + * + * You will need create a module definition (.def) file to provides the + * linker with information about the plugin exported functions. Create a + * new text file, name it .def and put it into the project + * directory: + * + * LIBRARY + * + * EXPORTS + * gpp_PluginName @1 + * gpp_Load @2 + * gpp_Unload @3 + * gpp_ReportUpdate @4 + * + * In this file specify the library name, and the name of the exported + * function. Now this .def file should be given as an option to the + * linker. Add .def as a linker option, or provide + * ".def" in the "Module definition file" field (Input + * category) in the project properties (Linker options). + * + * gpp_PluginName - Define and return your plugin name. + * gpp_PluginVersion - The first 8 bits are the MajorPart number and the last + * 8 bits are the MinorPart number. + * gpp_Load - It will be called after loading the plugin. Initialize + * your plugin. + * gpp_ReportUpdate - Called whenever there is an update in Device Report. + * gpp_Unload - It will be called before unloading the plugin. Clean up + * your plugin. + */ +#ifdef _GPPAPISW_ +#include "gswapi.h" +#else +extern const char * _stdcall gpp_PluginName(); +extern uint16_t _stdcall gpp_PluginVersion(); +extern uint8_t _stdcall gpp_Load(HMODULE hmodule, uint16_t sw_version, GPPAPI_CALLBACKS *callbacks); +extern void _stdcall gpp_ReportUpdate(); +extern void _stdcall gpp_Unload(); +#endif /* _GPPAPISW_ */ + +#endif /* GPPAPI_PLUGIN */ + +#endif /* GCAPI_H */ diff --git a/Gtuner-Plugin/gcdapi.dll b/Gtuner-Plugin/gcdapi.dll new file mode 100644 index 0000000000000000000000000000000000000000..8df743a1d88b3a5e67d4d049d9c355b1e7178fbd GIT binary patch literal 37376 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 literal 0 HcmV?d00001