/* 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 #include #include #include #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; }