project64/Source/nragev20/NRagePluginV2.cpp

1308 lines
45 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 <windows.h>
#include <commctrl.h>
#include <dinput.h>
#include <xinput.h>
#include "commonIncludes.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] (comment by 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? This 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("Auto select language: %d\n", g_strEmuInfo.Language);
}
g_hResourceDLL = LoadLanguageDLL(g_strEmuInfo.Language); // HACK: it's theoretically not safe to call LoadLibraryEx from DllMain... (comment by 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, then heap is created from DllMain,
// and now it's destroyed by DllMain...just safer code (comment by 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 structure 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 Project64 (debug): %s",VER_FILE_VERSION_STR);
#else
sprintf(PluginInfo->Name,"N-Rage For Project64: %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("Transfer pak 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 and 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 reinitialize them now if we're running, just to be safe (comment by 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-initialize FF devices here)
PrepareInputDevices();
if (iOK)
{
InitiatePaks( false ); // only re-initialize the memory paks 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 (comment by rabid)
/*
Function: InitiateControllers
Purpose: This function initializes 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 reinitialize 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 initialize
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 pak type %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 (comment by 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 (comment by tecnicors)
if( !g_strEmuInfo.fInitialisedPlugin )
{
ErrorMessage(IDS_ERR_NOINIT, 0, false);
return;
}
EnterCriticalSection( &g_critical );
// Re-initialize our paks and shortcuts
InitiatePaks( true );
// LoadShortcuts( &g_scShortcuts ); Why are we loading shortcuts again? They 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 (comment by tecnicors)
DebugWriteA("CALLED: RomClosed\n");
EnterCriticalSection( &g_critical );
if (g_sysMouse.didHandle)
{
g_sysMouse.didHandle->Unacquire();
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 (comment by 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 || (!g_pcControllers[Control].bBackgroundInput && GetForegroundWindow() != g_strEmuInfo.hMainWindow) )
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 (comment by 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 signaling 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:
Initialize 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 signaling 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 || (!g_pcControllers[Control].bBackgroundInput && GetForegroundWindow() != g_strEmuInfo.hMainWindow) )
Command[3] = Command[4] = Command[5] = Command[6] = 0;
else
{
if (Control == g_iFirstController )
{
GetDeviceDatas();
CheckShortcuts();
}
if( g_pcControllers[Control].fXInput ) // Reads XInput controller keys, if connected (comment by 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! (pak type 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 in some way
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-initialize.
Input: None
Output: None
*/
EXPORT void CALL CloseDLL (void)
{ // Hack: This is broken in Project64 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 pak data 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 connected controller paks.
// Input: false means run InitControlPak on each plugged controller. Input true means just clear each paks 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 function and changed the function 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 wasn't 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 = true;
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-initialize 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
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 things here, like choosing fonts, painting the background, 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;
}