1289 lines
41 KiB
C++
1289 lines
41 KiB
C++
/*
|
|
N-Rage`s Dinput8 Plugin
|
|
(C) 2002, 2006 Norbert Wladyka
|
|
|
|
Author`s Email: norbert.wladyka@chello.at
|
|
Website: http://go.to/nrage
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "commonIncludes.h"
|
|
#include <windows.h>
|
|
#include <Commctrl.h>
|
|
#include <dinput.h>
|
|
#include <Xinput.h>
|
|
#include "NRagePluginV2.h"
|
|
#include "Interface.h"
|
|
#include "FileAccess.h"
|
|
#include "PakIO.h"
|
|
#include "DirectInput.h"
|
|
#include "International.h"
|
|
#include "Version.h"
|
|
|
|
// ProtoTypes //
|
|
bool prepareHeap();
|
|
void FillControls(CONTROL * Controls);
|
|
void InitiatePaks( bool bInitialize );
|
|
void DoShortcut( int iPlayer, int iShortcut );
|
|
DWORD WINAPI MsgThreadFunction( LPVOID lpParam );
|
|
DWORD WINAPI DelayedShortcut(LPVOID lpParam);
|
|
|
|
// Global Variables //
|
|
HMODULE g_hDirectInputDLL = NULL; // Handle to DirectInput8 library
|
|
HMODULE g_hXInputDLL = NULL; // Handle to XInput Library
|
|
HMODULE g_hResourceDLL = NULL; // Handle to resource library; used by LoadString for internationalization
|
|
HANDLE g_hHeap = NULL; // Handle to our heap
|
|
int g_nDevices = 0; // number of devices in g_devList
|
|
DEVICE g_devList[MAX_DEVICES]; // list of attached input devices, except SysMouse
|
|
// note: we never purge the list of devices during normal operation
|
|
DEVICE g_sysMouse; // we need to treat the sysmouse differently, as we may use "locking"; changed from g_apInputDevice[1] --rabid
|
|
|
|
EMULATOR_INFO g_strEmuInfo; // emulator info? Stores stuff like our hWnd handle and whether the plugin is initialized yet
|
|
TCHAR g_aszDefFolders[3][MAX_PATH]; // default folders: DIRECTORY_MEMPAK, DIRECTORY_GBROMS, DIRECTORY_GBSAVES
|
|
TCHAR g_aszLastBrowse[6][MAX_PATH]; // last browsed folders: BF_MEMPAK, BF_GBROM, BF_GBSAVE, BF_PROFILE, BF_NOTE, BF_SHORTCUTS
|
|
|
|
CRITICAL_SECTION g_critical; // our critical section semaphore
|
|
int g_iFirstController = -1; // The first controller which is plugged in
|
|
// Normally controllers are scanned all at once in sequence, 1-4. We only want to scan devices once per pass;
|
|
// this is so we get consistent sample rates on our mouse.
|
|
|
|
bool g_bRunning = false; // Is the emulator running (i.e. have we opened a ROM)?
|
|
bool g_bConfiguring = false; // Are we currently in a config menu?
|
|
bool g_bExclusiveMouse = true; // Do we have an exclusive mouse lock? defaults to true unless we have no bound mouse buttons/axes
|
|
CONTROLLER g_pcControllers[4]; // Our four N64 controllers, connected or otherwise
|
|
SHORTCUTS g_scShortcuts;
|
|
LPDIRECTINPUTDEVICE8 g_apFFDevice[4] = { NULL, NULL, NULL, NULL }; // added by rabid
|
|
LPDIRECTINPUTEFFECT g_apdiEffect[4] = { NULL, NULL, NULL, NULL }; // array of handles for FF-Effects, one for each controller
|
|
TCHAR g_pszThreadMessage[DEFAULT_BUFFER] = _T("");
|
|
|
|
BOOL APIENTRY DllMain( HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
|
|
{
|
|
switch ( ul_reason_for_call )
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
DisableThreadLibraryCalls( hModule );
|
|
if( !prepareHeap())
|
|
return FALSE;
|
|
DebugWriteA("*** DLL Attach (" VER_FILE_VERSION_STR "-Debugbuild | built on " __DATE__ " at " __TIME__")\n");
|
|
ZeroMemory( &g_strEmuInfo, sizeof(g_strEmuInfo) );
|
|
ZeroMemory( g_devList, sizeof(g_devList) );
|
|
ZeroMemory( &g_sysMouse, sizeof(g_sysMouse) );
|
|
ZeroMemory( g_aszDefFolders, sizeof(g_aszDefFolders) );
|
|
ZeroMemory( g_aszLastBrowse, sizeof(g_aszLastBrowse) );
|
|
g_strEmuInfo.hinst = hModule;
|
|
g_strEmuInfo.fDisplayShortPop = true; // display pak switching message windows by default
|
|
#ifdef _UNICODE
|
|
{
|
|
g_strEmuInfo.Language = GetLanguageFromINI();
|
|
if ( g_strEmuInfo.Language == 0 )
|
|
{
|
|
g_strEmuInfo.Language = DetectLanguage();
|
|
DebugWriteA("Autoselect language: %d\n", g_strEmuInfo.Language);
|
|
}
|
|
g_hResourceDLL = LoadLanguageDLL(g_strEmuInfo.Language); // HACK: it's theoretically not safe to call LoadLibraryEx from DllMain... --rabid
|
|
if( g_hResourceDLL == NULL )
|
|
{
|
|
g_strEmuInfo.Language = 0;
|
|
g_hResourceDLL = hModule;
|
|
DebugWriteA("couldn't load language DLL, falling back to defaults\n");
|
|
}
|
|
}
|
|
#else
|
|
DebugWriteA(" (compiled in ANSI mode, language detection DISABLED.)\n");
|
|
g_strEmuInfo.Language = 0;
|
|
g_hResourceDLL = hModule;
|
|
#endif // #ifndef _UNICODE
|
|
InitializeCriticalSection( &g_critical );
|
|
break;
|
|
|
|
case DLL_THREAD_ATTACH:
|
|
break;
|
|
|
|
case DLL_THREAD_DETACH:
|
|
break;
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
//CloseDLL();
|
|
if (g_hResourceDLL != g_strEmuInfo.hinst)
|
|
FreeLibrary(g_hResourceDLL); // HACK: it's not safe to call FreeLibrary from DllMain... but screw it
|
|
|
|
DebugWriteA("*** DLL Detach\n");
|
|
|
|
CloseDebugFile(); // Moved here from CloseDll
|
|
DeleteCriticalSection( &g_critical );
|
|
|
|
// Moved here from CloseDll... Heap is created from DllMain,
|
|
// and now it's destroyed by DllMain... just safer code --rabid
|
|
if( g_hHeap != NULL )
|
|
{
|
|
HeapDestroy( g_hHeap );
|
|
g_hHeap = NULL;
|
|
}
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************
|
|
Function: GetDllInfo
|
|
Purpose: This function allows the emulator to gather information
|
|
about the dll by filling in the PluginInfo structure.
|
|
input: a pointer to a PLUGIN_INFO stucture that needs to be
|
|
filled by the function. (see def above)
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL GetDllInfo ( PLUGIN_INFO* PluginInfo )
|
|
{
|
|
DebugWriteA("CALLED: GetDllInfo\n");
|
|
#ifdef _DEBUG
|
|
sprintf(PluginInfo->Name,"N-Rage For PJ64 (Debug): %s",VER_FILE_VERSION_STR);
|
|
#else
|
|
sprintf(PluginInfo->Name,"N-Rage For PJ64: %s",VER_FILE_VERSION_STR);
|
|
#endif
|
|
PluginInfo->Type = PLUGIN_TYPE_CONTROLLER;
|
|
PluginInfo->Version = SPECS_VERSION;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: DllAbout
|
|
Purpose: This function is optional function that is provided
|
|
to give further information about the DLL.
|
|
input: a handle to the window that calls this function
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL DllAbout ( HWND hParent )
|
|
{
|
|
DebugWriteA("CALLED: DllAbout\n");
|
|
TCHAR tszTitle[DEFAULT_BUFFER], tszTranslator[DEFAULT_BUFFER];
|
|
|
|
LoadString( g_hResourceDLL, IDS_DLG_ABOUT_TITLE, tszTitle, DEFAULT_BUFFER );
|
|
|
|
TCHAR szText[DEFAULT_BUFFER * 4] = _T(VER_FILE_DESCRIPTION_STR) _T("\n\n") \
|
|
_T("Visit my site for support: >>http://go.to/nrage<<\n\n") \
|
|
_T("Version ") VER_FILE_VERSION_STR _T(" (") _T(__DATE__) _T(")\n") \
|
|
_T("Done by N-Rage\n") \
|
|
_T("\n") \
|
|
_T(" - - - - -\n") \
|
|
_T("Transferpak emulation done by MadManMarkAu\n") \
|
|
_T("Cleanup, tweaks, and language support by RabidDeity\n");
|
|
|
|
LoadString( g_hResourceDLL, IDS_DLG_ABOUT, tszTranslator, DEFAULT_BUFFER );
|
|
|
|
_tcscat(szText, tszTranslator);
|
|
|
|
MessageBox( hParent, szText, tszTitle, MB_OK | MB_ICONINFORMATION);
|
|
return;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: DllConfig
|
|
Purpose: This function is optional function that is provided
|
|
to allow the user to configure the dll
|
|
input: a handle to the window that calls this function
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL DllConfig ( HWND hParent )
|
|
{
|
|
DebugWriteA("CALLED: DllConfig\n");
|
|
static bool bInitCC = false;
|
|
if( !prepareHeap())
|
|
return;
|
|
|
|
if( !g_pDIHandle )
|
|
{
|
|
if( InitDirectInput( hParent ))
|
|
{
|
|
EnterCriticalSection ( &g_critical );
|
|
InitMouse();
|
|
g_pDIHandle->EnumDevices( DI8DEVCLASS_ALL, EnumMakeDeviceList, NULL, DIEDFL_ATTACHEDONLY );
|
|
LeaveCriticalSection ( &g_critical );
|
|
DebugWriteA("InitDirectInput run in DllConfig, g_nDevices=%d\n", g_nDevices);
|
|
}
|
|
}
|
|
|
|
if (g_hXInputDLL == NULL)
|
|
{
|
|
if (!InitXinput())
|
|
{
|
|
//TODO Disable ability to set XInput
|
|
//TODO Make XInput and DirectInput settings same page
|
|
}
|
|
}
|
|
|
|
if( g_pDIHandle && !g_bConfiguring )
|
|
{
|
|
g_bConfiguring = true;
|
|
if( !bInitCC )
|
|
{
|
|
INITCOMMONCONTROLSEX ccCtrls = { sizeof(INITCOMMONCONTROLSEX),
|
|
ICC_BAR_CLASSES | ICC_TAB_CLASSES | ICC_LISTVIEW_CLASSES };
|
|
InitCommonControlsEx( &ccCtrls ); // needed for TrackBars & Tabs
|
|
}
|
|
|
|
EnterCriticalSection( &g_critical );
|
|
if( g_sysMouse.didHandle )
|
|
{ // unlock mouse while configuring
|
|
g_sysMouse.didHandle->SetCooperativeLevel( g_strEmuInfo.hMainWindow, DIB_DEVICE );
|
|
g_sysMouse.didHandle->Acquire();
|
|
}
|
|
LeaveCriticalSection( &g_critical );
|
|
|
|
int iOK = DialogBox(g_hResourceDLL, MAKEINTRESOURCE(IDD_MAINCFGDIALOG), hParent, (DLGPROC)MainDlgProc);
|
|
|
|
// If we go into the dialog box, and the user navigates to the Rumble window, our FF device can get unacquired.
|
|
// So let's reinit them now if we're running, just to be safe --rabid
|
|
if( g_bRunning )
|
|
{
|
|
EnterCriticalSection( &g_critical );
|
|
// PrepareInputDevices resets g_bExclusiveMouse to false if no mouse keys are bound, and the only way to
|
|
// re-enable exclusive mouse is with a shortcut.
|
|
// This is undesirable behavior, but it beats the alternative (and we REALLY need to re-init FF devices here)
|
|
PrepareInputDevices();
|
|
if (iOK)
|
|
{
|
|
InitiatePaks( false ); // only re-init the mempaks and such if the user clicked Save or Use
|
|
}
|
|
|
|
if( g_sysMouse.didHandle )
|
|
{
|
|
if ( g_bExclusiveMouse )
|
|
{ // if we have exclusive mouse, we need to relock mouse after closing the config
|
|
g_sysMouse.didHandle->SetCooperativeLevel( g_strEmuInfo.hMainWindow, DIB_MOUSE );
|
|
g_sysMouse.didHandle->Acquire();
|
|
if (g_strEmuInfo.fDisplayShortPop)
|
|
{
|
|
LoadString( g_hResourceDLL, IDS_POP_MOUSELOCKED, g_pszThreadMessage, ARRAYSIZE(g_pszThreadMessage) );
|
|
// HWND hMessage = CreateWindowEx( WS_EX_NOPARENTNOTIFY | WS_EX_STATICEDGE | WS_EX_TOPMOST, _T("STATIC"), pszMessage, WS_CHILD | WS_VISIBLE, 10, 10, 200, 30, g_strEmuInfo.hMainWindow, NULL, g_strEmuInfo.hinst, NULL );
|
|
// SetTimer( hMessage, TIMER_MESSAGEWINDOW, 2000, MessageTimer );
|
|
CreateThread(NULL, 0, MsgThreadFunction, g_pszThreadMessage, 0, NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_sysMouse.didHandle->SetCooperativeLevel( g_strEmuInfo.hMainWindow, DIB_KEYBOARD );
|
|
g_sysMouse.didHandle->Acquire();
|
|
}
|
|
}
|
|
LeaveCriticalSection( &g_critical );
|
|
}
|
|
|
|
g_bConfiguring = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: DllTest
|
|
Purpose: This function is optional function that is provided
|
|
to allow the user to test the dll
|
|
input: a handle to the window that calls this function
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL DllTest ( HWND hParent )
|
|
{
|
|
DebugWriteA("CALLED: DllTest\n");
|
|
return;
|
|
}
|
|
|
|
// It's easier to maintain one version of this, as not much really changes
|
|
// between versions. --rabid
|
|
|
|
/******************************************************************
|
|
Function: InitiateControllers
|
|
Purpose: This function initialises how each of the controllers
|
|
should be handled.
|
|
input: - A controller structure that needs to be filled for
|
|
the emulator to know how to handle each controller.
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL InitiateControllers(
|
|
#if (SPECS_VERSION < 0x0101)
|
|
void * hMainWindow, CONTROL Controls[4]
|
|
#elif (SPECS_VERSION == 0x0101)
|
|
CONTROL_INFO ControlInfo
|
|
#else
|
|
CONTROL_INFO * ControlInfo
|
|
#endif
|
|
)
|
|
{
|
|
DebugWriteA("CALLED: InitiateControllers\n");
|
|
if( !prepareHeap())
|
|
return;
|
|
|
|
#if (SPECS_VERSION < 0x0101)
|
|
g_strEmuInfo.controllers = &Controls[0];
|
|
g_strEmuInfo.hMainWindow = hMainWindow;
|
|
// g_strEmuInfo.MemoryBswaped = TRUE; // Or FALSE. Really does not matter.
|
|
// g_strEmuInfo.HEADER = NULL;
|
|
#elif (SPECS_VERSION == 0x0101)
|
|
g_strEmuInfo.controllers = ControlInfo.Controls;
|
|
g_strEmuInfo.hMainWindow = ControlInfo.hMainWindow;
|
|
// g_strEmuInfo.MemoryBswaped = ControlInfo.MemoryBswaped;
|
|
// g_strEmuInfo.HEADER = ControlInfo.HEADER;
|
|
#else
|
|
g_strEmuInfo.controllers = ControlInfo->Controls;
|
|
g_strEmuInfo.hMainWindow = ControlInfo->hMainWindow;
|
|
// g_strEmuInfo.MemoryBswaped = ControlInfo->MemoryBswaped;
|
|
// g_strEmuInfo.HEADER = ControlInfo->HEADER;
|
|
#endif
|
|
// UNDONE: Instead of just storing the header, figure out what ROM we're running and save that information somewhere
|
|
|
|
// The emulator expects us to tell what controllers are plugged in and what their paks are at this point.
|
|
|
|
if( !g_pDIHandle ) // if we don't have a directinput handle, we need to make one, attach it to the main window (so it will die if our emulator dies), and enumerate devices
|
|
{
|
|
if( InitDirectInput( g_strEmuInfo.hMainWindow ))
|
|
{
|
|
EnterCriticalSection ( &g_critical );
|
|
InitMouse();
|
|
g_pDIHandle->EnumDevices( DI8DEVCLASS_ALL, EnumMakeDeviceList, NULL, DIEDFL_ATTACHEDONLY );
|
|
LeaveCriticalSection ( &g_critical );
|
|
DebugWriteA("InitDirectInput run in InitiateControllers, g_nDevices=%d\n", g_nDevices);
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (g_hXInputDLL == NULL)
|
|
{
|
|
if (!InitXinput())
|
|
{
|
|
//TODO Disable ability to set XInput
|
|
//TODO Make XInput and DirectInput settings same page
|
|
}
|
|
}
|
|
|
|
//To handle XInput controllers better, we need to set id to 0
|
|
iXinputControlId = 0;
|
|
|
|
int iDevice;
|
|
|
|
EnterCriticalSection( &g_critical );
|
|
|
|
// ZeroMemory( g_apFFDevice, sizeof(g_apFFDevice) ); // NO, we'll reinit the existing reference if it's already loaded
|
|
// ZeroMemory( g_apdiEffect, sizeof(g_apdiEffect) ); // NO, we'll release it with CloseControllerPak
|
|
|
|
for( int i = 3; i >= 0; i-- )
|
|
{
|
|
SaveControllerPak( i );
|
|
CloseControllerPak( i );
|
|
// freePakData( &g_pcControllers[i] ); // already called by CloseControllerPak
|
|
freeModifiers( &g_pcControllers[i] );
|
|
SetControllerDefaults( &g_pcControllers[i] );
|
|
}
|
|
|
|
g_pcControllers[0].fPlugged = true;
|
|
|
|
if (! LoadConfigFromINI() )
|
|
{
|
|
DebugWriteA("\tINI load failed, loading defaults from resource\n");
|
|
for ( int i = 0; i < 4; i++ )
|
|
LoadProfileFromResource( i, false );
|
|
LoadShortcutsFromResource(false);
|
|
}
|
|
|
|
// Init: Find force-feedback devices and init
|
|
for( int i = 0; i < 4; i++ )
|
|
{
|
|
DebugWriteA("Controller %d: ", i+1);
|
|
|
|
if( g_pcControllers[i].fPlugged )
|
|
{
|
|
if (g_pcControllers[i].fXInput)
|
|
{
|
|
InitiateXInputController(&g_pcControllers[i].xiController, i);
|
|
continue;
|
|
}
|
|
|
|
// Search for right Controller
|
|
iDevice = FindDeviceinList( g_pcControllers[i].guidFFDevice );
|
|
if( iDevice != -1 && g_devList[iDevice].bEffType )
|
|
{
|
|
DebugWriteA("rumble device set, ");
|
|
}
|
|
else // we couldn't find the device specified in the INI file, or it was already null
|
|
{
|
|
g_pcControllers[i].guidFFDevice = GUID_NULL;
|
|
DebugWriteA("no rumble device/effect type set, ");
|
|
}
|
|
|
|
if( g_pcControllers[i].nModifiers > 0)
|
|
SetModifier( &g_pcControllers[i] );
|
|
g_iFirstController = i;
|
|
DebugWriteA("plugged in, with paktype %d, ", g_pcControllers[i].PakType);
|
|
DebugWriteA("RawMode is %d\n", g_pcControllers[i].fRawData);
|
|
}
|
|
else
|
|
{
|
|
DebugWriteA("unplugged\n");
|
|
freePakData( &g_pcControllers[i] ); // we don't need to do this again, but there's not much overhead so I'll leave it --rabid
|
|
freeModifiers( &g_pcControllers[i] );
|
|
}
|
|
}
|
|
|
|
PrepareInputDevices();
|
|
|
|
if( g_bExclusiveMouse )
|
|
{
|
|
// g_sysMouse.didHandle->Unacquire();
|
|
// g_sysMouse.didHandle->SetCooperativeLevel( g_strEmuInfo.hMainWindow, DIB_MOUSE ); // PrepareInputDevices does this.
|
|
g_sysMouse.didHandle->Acquire();
|
|
}
|
|
|
|
InitiatePaks( true );
|
|
|
|
g_strEmuInfo.fInitialisedPlugin = true;
|
|
|
|
LeaveCriticalSection( &g_critical );
|
|
|
|
FillControls(g_strEmuInfo.controllers);
|
|
return;
|
|
} // end InitiateControllers
|
|
|
|
/******************************************************************
|
|
Function: RomOpen
|
|
Purpose: This function is called when a rom is open. (from the
|
|
emulation thread)
|
|
input: none
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL RomOpen (void)
|
|
{
|
|
DebugWriteA("CALLED: RomOpen\n");
|
|
|
|
//XInputEnable( TRUE ); // enables xinput --tecnicors
|
|
|
|
if( !g_strEmuInfo.fInitialisedPlugin )
|
|
{
|
|
ErrorMessage(IDS_ERR_NOINIT, 0, false);
|
|
return;
|
|
}
|
|
|
|
EnterCriticalSection( &g_critical );
|
|
// re-init our paks and shortcuts
|
|
InitiatePaks( true );
|
|
// LoadShortcuts( &g_scShortcuts ); WHY are we loading shortcuts again?? Should already be loaded!
|
|
LeaveCriticalSection( &g_critical );
|
|
g_bRunning = true;
|
|
return;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: RomClosed
|
|
Purpose: This function is called when a rom is closed.
|
|
input: none
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL RomClosed(void)
|
|
{
|
|
int i;
|
|
|
|
//XInputEnable( FALSE ); // disables xinput --tecnicors
|
|
|
|
DebugWriteA("CALLED: RomClosed\n");
|
|
EnterCriticalSection( &g_critical );
|
|
|
|
if (g_sysMouse.didHandle)
|
|
g_sysMouse.didHandle->SetCooperativeLevel(g_strEmuInfo.hMainWindow, DIB_KEYBOARD); // unlock the mouse, just in case
|
|
|
|
for( i = 0; i < ARRAYSIZE(g_pcControllers); ++i )
|
|
{
|
|
if( g_pcControllers[i].pPakData )
|
|
{
|
|
SaveControllerPak( i );
|
|
CloseControllerPak( i );
|
|
}
|
|
// freePakData( &g_pcControllers[i] ); already done by CloseControllerPak --rabid
|
|
// DON'T free the modifiers!
|
|
// ZeroMemory( &g_pcControllers[i], sizeof(CONTROLLER) );
|
|
}
|
|
|
|
for( i = 0; i < ARRAYSIZE( g_apdiEffect ); ++i )
|
|
ReleaseEffect( g_apdiEffect[i] );
|
|
ZeroMemory( g_apdiEffect, sizeof(g_apdiEffect) );
|
|
|
|
g_bRunning = false;
|
|
LeaveCriticalSection( &g_critical );
|
|
|
|
return;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: GetKeys
|
|
Purpose: To get the current state of the controllers buttons.
|
|
input: - Controller Number (0 to 3)
|
|
- A pointer to a BUTTONS structure to be filled with
|
|
the controller state.
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL GetKeys(int Control, BUTTONS * Keys )
|
|
{
|
|
#ifdef ENABLE_RAWPAK_DEBUG
|
|
DebugWriteA("CALLED: GetKeys\n");
|
|
#endif
|
|
if( g_bConfiguring )
|
|
Keys->Value = 0;
|
|
else
|
|
{
|
|
EnterCriticalSection( &g_critical );
|
|
|
|
if( g_pcControllers[Control].fPlugged )
|
|
{
|
|
if (Control == g_iFirstController )
|
|
{
|
|
GetDeviceDatas();
|
|
CheckShortcuts();
|
|
}
|
|
if( g_pcControllers[Control].fXInput ) // reads the xinput controller keys, if connected --tecnicors
|
|
GetXInputControllerKeys( Control, &Keys->Value );
|
|
else
|
|
GetNControllerInput( Control, &Keys->Value );
|
|
}
|
|
LeaveCriticalSection( &g_critical );
|
|
}
|
|
return;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: ControllerCommand
|
|
Purpose: To process the raw data that has just been sent to a
|
|
specific controller.
|
|
input: - Controller Number (0 to 3) and -1 signalling end of
|
|
processing the pif ram.
|
|
- Pointer of data to be processed.
|
|
output: none
|
|
|
|
note: This function is only needed if the DLL is allowing raw
|
|
data.
|
|
|
|
the data that is being processed looks like this:
|
|
initilize controller: 01 03 00 FF FF FF
|
|
read controller: 01 04 01 FF FF FF FF
|
|
*******************************************************************/
|
|
EXPORT void CALL ControllerCommand( int Control, BYTE * Command)
|
|
{
|
|
// We don't need to use this because it will be echoed immediately afterwards
|
|
// by a call to ReadController
|
|
return;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: ReadController
|
|
Purpose: To process the raw data in the pif ram that is about to
|
|
be read.
|
|
input: - Controller Number (0 to 3) and -1 signalling end of
|
|
processing the pif ram.
|
|
- Pointer of data to be processed.
|
|
output: none
|
|
note: This function is only needed if the DLL is allowing raw
|
|
data.
|
|
*******************************************************************/
|
|
EXPORT void CALL ReadController( int Control, BYTE * Command )
|
|
{
|
|
#ifdef ENABLE_RAWPAK_DEBUG
|
|
DebugWriteA("CALLED: ReadController\n");
|
|
#endif
|
|
if( Control == -1 )
|
|
return;
|
|
|
|
EnterCriticalSection( &g_critical );
|
|
|
|
if( !g_pcControllers[Control].fPlugged )
|
|
{
|
|
Command[1] |= RD_ERROR;
|
|
LeaveCriticalSection( &g_critical );
|
|
return;
|
|
}
|
|
|
|
|
|
switch( Command[2] )
|
|
{
|
|
case RD_RESETCONTROLLER:
|
|
WriteDatasA( "ResetController-PreProcessing", Control, Command, 0);
|
|
case RD_GETSTATUS:
|
|
// expected: controller gets 1 byte (command), controller sends back 3 bytes
|
|
// should be: Command[0] == 0x01
|
|
// Command[1] == 0x03
|
|
#ifdef ENABLE_RAWPAK_DEBUG
|
|
WriteDatasA( "GetStatus-PreProcessing", Control, Command, 0);
|
|
#endif
|
|
Command[3] = RD_GAMEPAD | RD_ABSOLUTE;
|
|
Command[4] = RD_NOEEPROM;
|
|
|
|
if (g_pcControllers[Control].fN64Mouse) // Is Controller a mouse?
|
|
Command[3] = RD_RELATIVE;
|
|
|
|
if( g_pcControllers[Control].fPakInitialized && g_pcControllers[Control].pPakData )
|
|
{
|
|
if( *(BYTE*)g_pcControllers[Control].pPakData == PAK_ADAPTOID )
|
|
{
|
|
Command[5] = GetAdaptoidStatus( Control );
|
|
|
|
if( Command[5] & RD_NOTINITIALIZED )
|
|
((ADAPTOIDPAK*)g_pcControllers[Control].pPakData)->fRumblePak = true;
|
|
}
|
|
else
|
|
{
|
|
Command[5] = ( *(BYTE*)g_pcControllers[Control].pPakData != PAK_NONE ) ? RD_PLUGIN : RD_NOPLUGIN;
|
|
if( g_pcControllers[Control].fPakCRCError )
|
|
{
|
|
Command[5] = Command[5] | RD_ADDRCRCERR;
|
|
g_pcControllers[Control].fPakCRCError = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( !g_bConfiguring && InitControllerPak( Control ) && g_pcControllers[Control].pPakData )
|
|
{
|
|
g_pcControllers[Control].fPakInitialized = true;
|
|
|
|
if( *(BYTE*)g_pcControllers[Control].pPakData == PAK_ADAPTOID )
|
|
Command[5] = GetAdaptoidStatus( Control );
|
|
else
|
|
{
|
|
Command[5] = ( *(BYTE*)g_pcControllers[Control].pPakData ) ? RD_PLUGIN : RD_NOPLUGIN;
|
|
Command[5] = Command[5] | ( g_pcControllers[Control].fPakCRCError ? RD_ADDRCRCERR : 0 );
|
|
}
|
|
}
|
|
else
|
|
Command[5] = RD_NOPLUGIN | RD_NOTINITIALIZED;
|
|
}
|
|
|
|
if( g_pcControllers[Control].fPakCRCError )
|
|
{
|
|
Command[5] = Command[5] | RD_ADDRCRCERR;
|
|
g_pcControllers[Control].fPakCRCError = false;
|
|
}
|
|
|
|
#ifdef ENABLE_RAWPAK_DEBUG
|
|
WriteDatasA( "GetStatus-PostProcessing", Control, Command, 0);
|
|
DebugWriteA( NULL );
|
|
#endif
|
|
break;
|
|
|
|
case RD_READKEYS:
|
|
// expected: controller gets 1 byte (command), controller sends back 4 bytes
|
|
// should be: Command[0] == 0x01
|
|
// Command[1] == 0x04
|
|
if( g_bConfiguring )
|
|
Command[3] = Command[4] = Command[5] = Command[6] = 0;
|
|
else
|
|
{
|
|
if (Control == g_iFirstController )
|
|
{
|
|
GetDeviceDatas();
|
|
CheckShortcuts();
|
|
}
|
|
if( g_pcControllers[Control].fXInput ) // reads xinput controller kesy, if connected --tecnicors
|
|
GetXInputControllerKeys( Control, (LPDWORD)&Command[3] );
|
|
else
|
|
GetNControllerInput( Control, (DWORD*)&Command[3] );
|
|
}
|
|
break;
|
|
case RD_READPAK:
|
|
#ifdef ENABLE_RAWPAK_DEBUG
|
|
WriteDatasA( "ReadPak-PreProcessing", Control, Command, 0);
|
|
#endif
|
|
if( g_pcControllers[Control].fPakInitialized )
|
|
//Command[1] = Command[1] | ReadControllerPak( Control, &Command[3] );
|
|
ReadControllerPak( Control, &Command[3] );
|
|
else
|
|
{
|
|
DebugWriteA("Tried to read, but pak wasn't initialized!!\n");
|
|
// InitControllerPak( Control );
|
|
Command[1] |= RD_ERROR;
|
|
//ZeroMemory( &Command[5], 32 );
|
|
}
|
|
#ifdef ENABLE_RAWPAK_DEBUG
|
|
WriteDatasA( "ReadPak-PostProcessing", Control, Command, 0);
|
|
DebugWriteA( NULL );
|
|
#endif
|
|
break;
|
|
case RD_WRITEPAK:
|
|
#ifdef ENABLE_RAWPAK_DEBUG
|
|
WriteDatasA( "WritePak-PreProcessing", Control, Command, 0);
|
|
#endif
|
|
if( g_pcControllers[Control].fPakInitialized )
|
|
//Command[1] = Command[1] | WriteControllerPak( Control, &Command[3] );
|
|
WriteControllerPak( Control, &Command[3] );
|
|
else
|
|
{
|
|
DebugWriteA("Tried to write, but pak wasn't initialized! (paktype was %u)\n", g_pcControllers[Control].PakType);
|
|
// InitControllerPak( Control );
|
|
Command[1] |= RD_ERROR;
|
|
}
|
|
#ifdef ENABLE_PAK_WRITES_DEBUG
|
|
WriteDatasA( "WritePak-PostProcessing", Control, Command, 0);
|
|
DebugWriteA( NULL );
|
|
#endif
|
|
break;
|
|
case RD_READEEPROM:
|
|
// Should be handled by the Emulator
|
|
WriteDatasA( "ReadEeprom-PreProcessing", Control, Command, 0);
|
|
WriteDatasA( "ReadEeprom-PostProcessing", Control, Command, 0);
|
|
DebugWriteA( NULL );
|
|
break;
|
|
case RD_WRITEEPROM:
|
|
// Should be handled by the Emulator
|
|
WriteDatasA( "WriteEeprom-PreProcessing", Control, Command, 0);
|
|
WriteDatasA( "WriteEeprom-PostProcessing", Control, Command, 0);
|
|
DebugWriteA( NULL );
|
|
break;
|
|
default:
|
|
// only accessible if the Emulator has bugs.. or maybe the Rom is flawed
|
|
WriteDatasA( "ReadController: Bad read", Control, Command, 0);
|
|
DebugWriteA( NULL );
|
|
Command[1] = Command[1] | RD_ERROR;
|
|
}
|
|
|
|
LeaveCriticalSection( &g_critical );
|
|
return;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: WM_KeyDown
|
|
Purpose: To pass the WM_KeyDown message from the emulator to the
|
|
plugin.
|
|
input: wParam and lParam of the WM_KEYDOWN message.
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL WM_KeyDown( WPARAM wParam, LPARAM lParam )
|
|
{
|
|
return;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: WM_KeyUp
|
|
Purpose: To pass the WM_KEYUP message from the emulator to the
|
|
plugin.
|
|
input: wParam and lParam of the WM_KEYDOWN message.
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL WM_KeyUp( WPARAM wParam, LPARAM lParam )
|
|
{
|
|
return;
|
|
}
|
|
|
|
/******************************************************************
|
|
Function: CloseDLL
|
|
Purpose: This function is called when the emulator is closing
|
|
down allowing the dll to de-initialise.
|
|
input: none
|
|
output: none
|
|
*******************************************************************/
|
|
EXPORT void CALL CloseDLL (void)
|
|
{ // HACK: THIS IS BROKEN IN PJ64 1.6 (it calls CloseDLL too often)
|
|
DebugWriteA("CALLED: CloseDLL\n");
|
|
if( g_bRunning )
|
|
RomClosed();
|
|
for( int i = 0; i < 4; i++ )
|
|
{
|
|
freePakData( &g_pcControllers[i] );
|
|
freeModifiers( &g_pcControllers[i] );
|
|
}
|
|
|
|
// ZeroMemory( g_pcControllers, sizeof(g_pcControllers) ); // why zero the memory if we're just going to close down?
|
|
|
|
FreeDirectInput();
|
|
FreeXinput();
|
|
return;
|
|
}
|
|
|
|
// Prepare a global heap. Use P_malloc and P_free as wrappers to grab/release memory.
|
|
bool prepareHeap()
|
|
{
|
|
if( g_hHeap == NULL )
|
|
g_hHeap = HeapCreate( 0, 4*1024, 0 );
|
|
return (g_hHeap != NULL);
|
|
}
|
|
|
|
// Frees pakdata memory (called more often than you'd think).
|
|
void freePakData( CONTROLLER *pcController )
|
|
{
|
|
if( pcController && pcController->pPakData )
|
|
{
|
|
P_free( pcController->pPakData );
|
|
pcController->pPakData = NULL;
|
|
}
|
|
}
|
|
|
|
// Frees modifier memory
|
|
void freeModifiers( CONTROLLER *pcController )
|
|
{
|
|
if( pcController && pcController->pModifiers )
|
|
{
|
|
pcController->nModifiers = 0;
|
|
P_free( pcController->pModifiers );
|
|
pcController->pModifiers = NULL;
|
|
}
|
|
}
|
|
|
|
// After enumerating DirectInput devices into g_devList, find a device by product name and counter
|
|
int FindDeviceinList( const TCHAR *pszProductName, const BYTE bProductCounter, bool fFindSimilar )
|
|
{
|
|
if( !(*pszProductName) )
|
|
return -1;
|
|
|
|
int i = 0, iSimilar = -1, iExact = -1;
|
|
while(( i < ARRAYSIZE(g_devList) ) && ( iExact == -1 ))
|
|
{
|
|
if( !lstrcmp( g_devList[i].szProductName, pszProductName ))
|
|
{
|
|
if(( bProductCounter > 0 ) || ( iSimilar == -1 ))
|
|
iSimilar = i;
|
|
if( g_devList[i].bProductCounter == bProductCounter )
|
|
iExact = i;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if( fFindSimilar && ( iExact == -1 ))
|
|
iExact = iSimilar;
|
|
|
|
return iExact;
|
|
}
|
|
|
|
// After enumerating DirectInput devices into g_devList, find a device by GUID. Finding similar devices is impossible.
|
|
int FindDeviceinList( REFGUID rGUID )
|
|
{
|
|
if (rGUID == GUID_NULL )
|
|
return -1;
|
|
int i = 0;
|
|
while( i < ARRAYSIZE(g_devList) )
|
|
{
|
|
if ( IsEqualGUID(g_devList[i].guidInstance, rGUID) )
|
|
return i;
|
|
i++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Let's initialize all plugged in controllers' paks.
|
|
// Input: false means run InitControlPak on each plugged controller. Input true means just clear each pak's CRC error status?
|
|
// When we call this from RomOpen, it's true, otherwise (in DllConfig) it's false.
|
|
// Rather counterintuitive.
|
|
void InitiatePaks( bool bInitialize )
|
|
{
|
|
for( int i = 0; i < 4; i++ )
|
|
{
|
|
if( g_pcControllers[i].fPlugged)
|
|
{
|
|
g_pcControllers[i].fPakCRCError = false;
|
|
|
|
if( g_pcControllers[i].fRawData )
|
|
{
|
|
if( !bInitialize )
|
|
g_pcControllers[i].fPakInitialized = InitControllerPak( i );
|
|
}
|
|
else
|
|
{ // we only support "RAW mode" paks so this won't do much
|
|
;//if( g_pcControllers[i].PakType == PAK_RUMBLE )
|
|
// CreateEffectHandle( i, g_pcControllers[i].bRumbleTyp, g_pcControllers[i].bRumbleStrength );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This used to be "NotifyEmulator" which was supposed to tell the emulator if we changed the way it sees controllers.
|
|
// Unfortunately the spec doesn't work that way. Fixed the func and changed the func name to something that makes more sense.
|
|
// FillControls takes a Controls array from InitiateControllers and fills it with what we know about whether
|
|
// a controller is plugged in, accepting raw data, and what type of pak is plugged in.
|
|
void FillControls(CONTROL * Controls)
|
|
{
|
|
for( int i = 4-1; i >= 0; i-- )
|
|
{
|
|
if( g_pcControllers[i].fPlugged )
|
|
{
|
|
Controls[i].Present = g_pcControllers[i].fPlugged;
|
|
Controls[i].RawData = g_pcControllers[i].fRawData;
|
|
|
|
switch( g_pcControllers[i].PakType )
|
|
{
|
|
case PAK_MEM:
|
|
Controls[i].Plugin = PLUGIN_MEMPAK;
|
|
Controls[i].RawData = false;
|
|
break;
|
|
case PAK_RUMBLE:
|
|
Controls[i].Plugin = PLUGIN_RUMBLE_PAK;
|
|
break;
|
|
case PAK_TRANSFER:
|
|
Controls[i].Plugin = PLUGIN_TRANSFER_PAK;
|
|
break;
|
|
case PAK_VOICE:
|
|
Controls[i].Plugin = g_pcControllers[i].fRawData ? PLUGIN_RAW : PLUGIN_NONE;
|
|
break;
|
|
case PAK_ADAPTOID:
|
|
Controls[i].Plugin = g_pcControllers[i].fRawData ? PLUGIN_RAW : PLUGIN_NONE;
|
|
break;
|
|
|
|
case PAK_NONE:
|
|
default:
|
|
Controls[i].Plugin = PLUGIN_NONE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Controls[i].Plugin = PLUGIN_NONE;
|
|
Controls[i].Present = false;
|
|
Controls[i].RawData = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// called after a poll to execute any shortcuts
|
|
void CheckShortcuts()
|
|
{
|
|
static bool bWasPressed[ sizeof(SHORTCUTSPL)/sizeof(BUTTON) ][4];
|
|
static bool bMLWasPressed; // mouselock
|
|
bool bMatching = false;
|
|
|
|
if ( g_bConfiguring || !g_bRunning )
|
|
return; // we don't process shortcuts if we're in a config menu or are not running emulation
|
|
|
|
// just process if key wasnt pressed before
|
|
for ( int i = 0; i < 4; i++ ) // controllers
|
|
{
|
|
for( int j = 0; j < SC_TOTAL; j++ )
|
|
{
|
|
bMatching = IsBtnPressed( g_scShortcuts.Player[i].aButtons[j] );
|
|
|
|
if( bMatching && !bWasPressed[j][i] )
|
|
DoShortcut(i, j);
|
|
|
|
bWasPressed[j][i] = bMatching;
|
|
}
|
|
}
|
|
|
|
bMatching = IsBtnPressed( g_scShortcuts.bMouseLock );
|
|
|
|
if( bMatching && !bMLWasPressed )
|
|
DoShortcut(-1, -1); // controller -1 means do mouselock shortcut
|
|
|
|
bMLWasPressed = bMatching;
|
|
}
|
|
|
|
// Executes the shortcut iShortcut on controller iController
|
|
// Special case: if iPlayer is -1, run the mouselock shortcut
|
|
void DoShortcut( int iControl, int iShortcut )
|
|
{
|
|
DebugWriteA("Shortcut: %d %d\n", iControl, iShortcut);
|
|
TCHAR pszMessage[DEFAULT_BUFFER / 2] = TEXT("");
|
|
bool bEjectFirst = false;
|
|
|
|
if (iControl == -1)
|
|
{
|
|
EnterCriticalSection( &g_critical );
|
|
if( g_sysMouse.didHandle )
|
|
{
|
|
g_sysMouse.didHandle->Unacquire();
|
|
if( g_bExclusiveMouse )
|
|
{
|
|
g_sysMouse.didHandle->Unacquire();
|
|
g_sysMouse.didHandle->SetCooperativeLevel( g_strEmuInfo.hMainWindow, DIB_KEYBOARD );
|
|
g_sysMouse.didHandle->Acquire();
|
|
LoadString( g_hResourceDLL, IDS_POP_MOUSEUNLOCKED, pszMessage, ARRAYSIZE(pszMessage) );
|
|
}
|
|
else
|
|
{
|
|
g_sysMouse.didHandle->Unacquire();
|
|
g_sysMouse.didHandle->SetCooperativeLevel( g_strEmuInfo.hMainWindow, DIB_MOUSE );
|
|
g_sysMouse.didHandle->Acquire();
|
|
LoadString( g_hResourceDLL, IDS_POP_MOUSELOCKED, pszMessage, ARRAYSIZE(pszMessage) );
|
|
}
|
|
g_sysMouse.didHandle->Acquire();
|
|
g_bExclusiveMouse = !g_bExclusiveMouse;
|
|
}
|
|
LeaveCriticalSection( &g_critical );
|
|
}
|
|
else if( g_pcControllers[iControl].fPlugged )
|
|
{
|
|
if( g_pcControllers[iControl].pPakData )
|
|
{
|
|
SaveControllerPak( iControl );
|
|
CloseControllerPak( iControl );
|
|
}
|
|
|
|
switch (iShortcut)
|
|
{
|
|
case SC_NOPAK:
|
|
EnterCriticalSection( &g_critical );
|
|
g_pcControllers[iControl].PakType = PAK_NONE;
|
|
g_pcControllers[iControl].fPakInitialized = false;
|
|
LoadString( g_hResourceDLL, IDS_P_NONE, pszMessage, ARRAYSIZE(pszMessage) );
|
|
LeaveCriticalSection( &g_critical );
|
|
break;
|
|
case SC_MEMPAK:
|
|
if (PAK_NONE == g_pcControllers[iControl].PakType)
|
|
{
|
|
EnterCriticalSection( &g_critical );
|
|
g_pcControllers[iControl].PakType = PAK_MEM;
|
|
g_pcControllers[iControl].fPakInitialized = false;
|
|
LoadString( g_hResourceDLL, IDS_P_MEMPAK, pszMessage, ARRAYSIZE(pszMessage) );
|
|
LeaveCriticalSection( &g_critical );
|
|
}
|
|
else
|
|
{
|
|
bEjectFirst = true;
|
|
}
|
|
break;
|
|
case SC_RUMBPAK:
|
|
if (PAK_NONE == g_pcControllers[iControl].PakType)
|
|
{
|
|
EnterCriticalSection( &g_critical );
|
|
g_pcControllers[iControl].PakType = PAK_RUMBLE;
|
|
g_pcControllers[iControl].fPakInitialized = false;
|
|
|
|
if( g_pcControllers[iControl].fRawData )
|
|
if (CreateEffectHandle( iControl, g_pcControllers[iControl].bRumbleTyp, g_pcControllers[iControl].bRumbleStrength ) )
|
|
{
|
|
DebugWriteA("CreateEffectHandle for shortcut switch: OK\n");
|
|
}
|
|
else
|
|
{
|
|
DebugWriteA("Couldn't CreateEffectHandle for shortcut switch.\n");
|
|
}
|
|
|
|
LoadString( g_hResourceDLL, IDS_P_RUMBLEPAK, pszMessage, ARRAYSIZE(pszMessage) );
|
|
LeaveCriticalSection( &g_critical );
|
|
}
|
|
else
|
|
{
|
|
bEjectFirst = true;
|
|
}
|
|
break;
|
|
|
|
case SC_TRANSPAK:
|
|
if (PAK_NONE == g_pcControllers[iControl].PakType)
|
|
{
|
|
EnterCriticalSection( &g_critical );
|
|
g_pcControllers[iControl].PakType = PAK_TRANSFER;
|
|
g_pcControllers[iControl].fPakInitialized = false;
|
|
|
|
LoadString( g_hResourceDLL, IDS_P_TRANSFERPAK, pszMessage, ARRAYSIZE(pszMessage) );
|
|
LeaveCriticalSection( &g_critical );
|
|
}
|
|
else
|
|
{
|
|
bEjectFirst = true;
|
|
}
|
|
break;
|
|
case SC_VOICEPAK:
|
|
if (PAK_NONE == g_pcControllers[iControl].PakType)
|
|
{
|
|
EnterCriticalSection( &g_critical );
|
|
g_pcControllers[iControl].PakType = PAK_VOICE;
|
|
g_pcControllers[iControl].fPakInitialized = false;
|
|
|
|
LoadString( g_hResourceDLL, IDS_P_VOICEPAK, pszMessage, ARRAYSIZE(pszMessage) );
|
|
LeaveCriticalSection( &g_critical );
|
|
}
|
|
else
|
|
{
|
|
bEjectFirst = true;
|
|
}
|
|
break;
|
|
case SC_ADAPTPAK:
|
|
if (PAK_NONE == g_pcControllers[iControl].PakType)
|
|
{
|
|
EnterCriticalSection( &g_critical );
|
|
g_pcControllers[iControl].PakType = PAK_ADAPTOID;
|
|
g_pcControllers[iControl].fPakInitialized = false;
|
|
|
|
LoadString( g_hResourceDLL, IDS_P_ADAPTOIDPAK, pszMessage, ARRAYSIZE(pszMessage) );
|
|
LeaveCriticalSection( &g_critical );
|
|
}
|
|
else
|
|
{
|
|
bEjectFirst = true;
|
|
}
|
|
break;
|
|
case SC_SWMEMRUMB:
|
|
bEjectFirst = true;
|
|
if( g_pcControllers[iControl].PakType == PAK_MEM )
|
|
{
|
|
iShortcut = PAK_RUMBLE;
|
|
}
|
|
else
|
|
{
|
|
iShortcut = PAK_MEM;
|
|
}
|
|
break;
|
|
case SC_SWMEMADAPT:
|
|
bEjectFirst = true;
|
|
if( g_pcControllers[iControl].PakType == PAK_MEM )
|
|
{
|
|
iShortcut = PAK_ADAPTOID;
|
|
}
|
|
else
|
|
{
|
|
iShortcut = PAK_MEM;
|
|
}
|
|
break;
|
|
default:
|
|
DebugWriteA("Invalid iShortcut passed to DoShortcut\n");
|
|
EnterCriticalSection( &g_critical );
|
|
g_pcControllers[iControl].fPakInitialized = false;
|
|
LeaveCriticalSection( &g_critical );
|
|
return;
|
|
} // switch (iShortcut)
|
|
} // else if
|
|
|
|
// let the game code re-init the pak.
|
|
|
|
if (bEjectFirst) // we need to eject the current pack first; then set a DoShortcut to try again in 1 second
|
|
{
|
|
EnterCriticalSection( &g_critical );
|
|
g_pcControllers[iControl].PakType = PAK_NONE;
|
|
g_pcControllers[iControl].fPakInitialized = false;
|
|
LoadString( g_hResourceDLL, IDS_P_SWITCHING, pszMessage, ARRAYSIZE(pszMessage) );
|
|
LeaveCriticalSection( &g_critical );
|
|
|
|
LPMSHORTCUT lpmNextShortcut = (LPMSHORTCUT)P_malloc(sizeof(MSHORTCUT));
|
|
if (!lpmNextShortcut)
|
|
return;
|
|
lpmNextShortcut->iControl = iControl;
|
|
lpmNextShortcut->iShortcut = iShortcut;
|
|
CreateThread(NULL, 0, DelayedShortcut, lpmNextShortcut, 0, NULL);
|
|
iControl = -2; // this is just a hack to get around the check that appends "Changing Pak X to ..."
|
|
}
|
|
|
|
if( g_strEmuInfo.fDisplayShortPop && _tcslen(pszMessage) > 0 )
|
|
{
|
|
if( iControl >= 0 )
|
|
{
|
|
TCHAR tszNotify[DEFAULT_BUFFER / 2];
|
|
|
|
LoadString( g_hResourceDLL, IDS_POP_CHANGEPAK, tszNotify, ARRAYSIZE(tszNotify));
|
|
wsprintf( g_pszThreadMessage, tszNotify, iControl+1, pszMessage );
|
|
}
|
|
else
|
|
lstrcpyn( g_pszThreadMessage, pszMessage, ARRAYSIZE(g_pszThreadMessage) );
|
|
|
|
CreateThread(NULL, 0, MsgThreadFunction, g_pszThreadMessage, 0, NULL);
|
|
}
|
|
}
|
|
|
|
// Use string table refs to generate and throw an error message with title IDS_ERR_TITLE (see string table in resources)
|
|
// Also if compiled with DEBUG, will log the message with DebugWrite
|
|
// uID the string table ref to display
|
|
// dwError if nonzero, will display a Windows error message using FormatMessage (for use when an API function fails)
|
|
// fUserChoose if true, display buttons Retry and Cancel. if false, display single button OK.
|
|
// for fUserChoose==true; ErrorMessage returns true if user selects Retry, and false if user selects Cancel.
|
|
// for fUserChoose==false; ErrorMessage always returns false.
|
|
bool ErrorMessage( UINT uID, DWORD dwError, bool fUserChoose )
|
|
{
|
|
TCHAR pszFirstLine[DEFAULT_BUFFER];
|
|
|
|
bool fReturn = false;
|
|
int iBytes;
|
|
TCHAR szError[512];
|
|
TCHAR tszErrorTitle[DEFAULT_BUFFER];
|
|
|
|
LoadString( g_hResourceDLL, uID, pszFirstLine, DEFAULT_BUFFER );
|
|
LoadString( g_hResourceDLL, IDS_ERR_TITLE, tszErrorTitle, DEFAULT_BUFFER );
|
|
|
|
if( dwError )
|
|
{
|
|
iBytes = wsprintf( szError, _T("%s\n\n Error description: "), pszFirstLine );
|
|
FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError , 0, &szError[iBytes], sizeof(szError) - iBytes, NULL );
|
|
}
|
|
else
|
|
lstrcpyn( szError, pszFirstLine, 512 );
|
|
|
|
DebugWrite(_T("ErrorMessage! ID:%d "), uID);
|
|
DebugFlush();
|
|
|
|
if( fUserChoose )
|
|
fReturn = MessageBox( g_strEmuInfo.hMainWindow, szError, tszErrorTitle, MB_RETRYCANCEL | MB_ICONERROR ) == IDRETRY;
|
|
else
|
|
MessageBox( g_strEmuInfo.hMainWindow, szError, tszErrorTitle, MB_OK | MB_ICONERROR );
|
|
|
|
DebugWriteA(fReturn ? "(user: retry)\n" : "(user: acknowledge)\n");
|
|
return fReturn;
|
|
}
|
|
|
|
// Post a message box, using string resource uTextID and MessageBox style uType
|
|
int WarningMessage( UINT uTextID, UINT uType )
|
|
{
|
|
DebugWriteA("WarningMessage: ID:%d Type:%d\n", uTextID, uType);
|
|
DebugFlush();
|
|
|
|
TCHAR tszTitle[DEFAULT_BUFFER], tszText[DEFAULT_BUFFER];
|
|
|
|
LoadString( g_hResourceDLL, uTextID, tszText, DEFAULT_BUFFER );
|
|
LoadString( g_hResourceDLL, IDS_DLG_WARN_TITLE, tszTitle, DEFAULT_BUFFER );
|
|
|
|
return MessageBox( g_strEmuInfo.hMainWindow, tszText, tszTitle, uType );
|
|
}
|
|
|
|
|
|
/* H.Morii <koolsmoky(at)users.sourceforge.net>
|
|
MsgThreadFunction is used because the SetTimer function relies
|
|
on the WM_TIMER message which is low priority and will not be
|
|
executed if other windows messages are frequently dispatched. */
|
|
|
|
DWORD WINAPI MsgThreadFunction( LPVOID lpParam )
|
|
{
|
|
HWND hMessage = CreateWindowEx( WS_EX_NOPARENTNOTIFY | WS_EX_STATICEDGE | WS_EX_TOPMOST, _T("STATIC"), NULL, WS_CHILD | WS_VISIBLE, 10, 10, 200, 40, g_strEmuInfo.hMainWindow, NULL, g_strEmuInfo.hinst, NULL );
|
|
|
|
/* prepare the screen to bitblt */
|
|
RECT rt;
|
|
GetClientRect(hMessage, &rt);
|
|
HDC hdc = GetDC(hMessage);
|
|
HDC memdc = CreateCompatibleDC(hdc);
|
|
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rt.right - rt.left, rt.bottom - rt.top);
|
|
SelectObject(memdc, hbitmap);
|
|
|
|
/* draw some nice stuff.
|
|
choose fonts, paint the back ground, etc here. */
|
|
FillRect(memdc, &rt, (HBRUSH)(COLOR_WINDOW+1));
|
|
DrawText(memdc, (LPCTSTR)lpParam, -1, &rt, DT_WORDBREAK);
|
|
|
|
/* bitblt to kingdom come */
|
|
for (int i = 0; i < 60; i++)
|
|
{
|
|
Sleep(16); // 1/60 second
|
|
BitBlt(hdc, rt.left, rt.top, rt.right - rt.left, rt.bottom - rt.top, memdc, 0, 0, SRCCOPY);
|
|
}
|
|
|
|
/* cleanup */
|
|
DeleteObject(hbitmap);
|
|
DeleteDC(memdc);
|
|
ReleaseDC(hMessage, hdc);
|
|
|
|
/* Can only destroy windows created from the owner thread */
|
|
DestroyWindow(hMessage);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// This function is called as a thread by DoShortcut in order to tell the plugin to insert a pak shortly
|
|
// (usually after we've just removed whatever pak was there)
|
|
DWORD WINAPI DelayedShortcut(LPVOID lpParam)
|
|
{
|
|
LPMSHORTCUT sc = (LPMSHORTCUT)lpParam;
|
|
if (sc && sc->iShortcut != SC_SWMEMRUMB && sc->iShortcut != SC_SWMEMADAPT) // don't allow recursion into self, it would cause a deadlock
|
|
{
|
|
Sleep(1000); // sleep a little bit before calling DoShortcut again
|
|
DoShortcut(sc->iControl, sc->iShortcut);
|
|
}
|
|
P_free(lpParam);
|
|
return 0;
|
|
}
|