diff --git a/plugins/GSdx/GSState.cpp b/plugins/GSdx/GSState.cpp index 85606d9f98..b9a22739b8 100644 --- a/plugins/GSdx/GSState.cpp +++ b/plugins/GSdx/GSState.cpp @@ -3182,6 +3182,137 @@ bool GSC_Yakuza2(const GSFrameInfo& fi, int& skip) } +#define USE_DYNAMIC_CRC_HACK +#ifdef USE_DYNAMIC_CRC_HACK + +#define DYNA_DLL_PATH "c:/dev/pcsx2/trunk/tools/dynacrchack/DynaCrcHack.dll" + +#include +/*************************************************************************** + AutoReloadLibrary : Automatically reloads a dll if the file was modified. + Uses a temporary copy of the watched dll such that the original + can be modified while the copy is loaded and used. + + NOTE: The API is not platform specific, but current implementation is Win32. +***************************************************************************/ +class AutoReloadLibrary +{ +private: + string m_dllPath, m_loadedDllPath; + DWORD m_minMsBetweenProbes; + time_t m_lastFileModification; + DWORD m_lastProbe; + HMODULE m_library; + + string GetTempName() + { + string result = m_loadedDllPath + ".tmp"; //default name + TCHAR tmpPath[MAX_PATH], tmpName[MAX_PATH]; + DWORD ret = GetTempPath(MAX_PATH, tmpPath); + if(ret && ret <= MAX_PATH && GetTempFileName(tmpPath, TEXT("GSdx"), 0, tmpName)) + result = tmpName; + + return result; + }; + + void UnloadLib() + { + if( !m_library ) + return; + + FreeLibrary( m_library ); + m_library = NULL; + + // If can't delete (might happen when GSdx closes), schedule delete on reboot + if(!DeleteFile( m_loadedDllPath.c_str() ) ) + MoveFileEx( m_loadedDllPath.c_str(), NULL, MOVEFILE_DELAY_UNTIL_REBOOT ); + } + +public: + AutoReloadLibrary( const string dllPath, const int minMsBetweenProbes=100 ) + : m_minMsBetweenProbes( minMsBetweenProbes ) + , m_dllPath( dllPath ) + , m_lastFileModification( 0 ) + , m_lastProbe( 0 ) + , m_library( 0 ) + {}; + + ~AutoReloadLibrary(){ UnloadLib(); }; + + // If timeout has ellapsed, probe the dll for change, and reload if it was changed. + // If it returns true, then the dll was freed/reloaded, and any symbol addresse previously obtained is now invalid and needs to be re-obtained. + // Overhead is very low when when probe timeout has not ellapsed, and especially if current timestamp is supplied as argument. + // Note: there's no relation between the file modification date and currentMs value, so it need'nt neccessarily be an actual timestamp. + // Note: isChanged is guarenteed to return true at least once + // (even if the file doesn't exist, at which case the following GetSymbolAddress will return NULL) + bool isChanged( const DWORD currentMs=0 ) + { + DWORD current = currentMs? currentMs : GetTickCount(); + if( current >= m_lastProbe && ( current - m_lastProbe ) < m_minMsBetweenProbes ) + return false; + + bool firstTime = !m_lastProbe; + m_lastProbe = current; + + struct stat s; + if( stat( m_dllPath.c_str(), &s ) ) + { + // File doesn't exist or other error, unload dll + bool wasLoaded = m_library?true:false; + UnloadLib(); + return firstTime || wasLoaded; // Changed if previously loaded or the first time accessing this method (and file doesn't exist) + } + + if( m_lastFileModification == s.st_mtime ) + return false; + m_lastFileModification = s.st_mtime; + + // File modified, reload + UnloadLib(); + + if( !CopyFile( m_dllPath.c_str(), ( m_loadedDllPath = GetTempName() ).c_str(), false ) ) + return true; + + m_library = LoadLibrary( m_loadedDllPath.c_str() ); + return true; + }; + + // Return value is NULL if the dll isn't loaded (failure or doesn't exist) or if the symbol isn't found. + void* GetSymbolAddress( const char* name ){ return m_library? GetProcAddress( m_library, name ) : NULL; }; +}; + + +// Use DynamicCrcHack function from a dll which can be modified while GSdx/PCSX2 is running. +// return value is true if the call succeeded or false otherwise (If the hack could not be invoked: no dll/function/etc). +// result contains the result of the hack call. + +typedef uint32 (__cdecl* DynaHackType)(uint32, uint32, uint32, uint32, uint32, uint32, uint32, int32*, uint32, int32); + +bool IsInvokedDynamicCrcHack( GSFrameInfo &fi, int& skip, int region, bool &result ) +{ + static AutoReloadLibrary dll( DYNA_DLL_PATH ); + static DynaHackType dllFunc = NULL; + + if( dll.isChanged() ) + { + dllFunc = (DynaHackType)dll.GetSymbolAddress( "DynamicCrcHack" ); + printf( "GSdx: Dynamic CRC-hacks: %s\n", dllFunc? + "Loaded OK (-> overriding internal hacks)" : "Not available (-> using internal hacks)"); + } + + if( !dllFunc ) + return false; + + int32 skip32 = skip; + bool hasSharedBits = GSUtil::HasSharedBits(fi.FBP, fi.FPSM, fi.TBP0, fi.TPSM); + result = dllFunc( fi.FBP, fi.FPSM, fi.FBMSK, fi.TBP0, fi.TPSM, fi.TZTST, (uint32)fi.TME, &skip32, (uint32)region, (uint32)(hasSharedBits?1:0) )?true:false; + skip = skip32; + + return true; +} + +#endif + bool GSState::IsBadFrame(int& skip, int UserHacks_SkipDraw) { GSFrameInfo fi; @@ -3280,6 +3411,9 @@ bool GSState::IsBadFrame(int& skip, int UserHacks_SkipDraw) GetSkipCount gsc = map[m_game.title]; g_crc_region = m_game.region; +#ifdef USE_DYNAMIC_CRC_HACK + bool res=false; if(IsInvokedDynamicCrcHack(fi, skip, g_crc_region, res)){ if( !res ) return false; } else +#endif if(gsc && !gsc(fi, skip)) { return false; diff --git a/tools/dynacrchack/Auto_Compile.bat b/tools/dynacrchack/Auto_Compile.bat new file mode 100644 index 0000000000..ebf445062f --- /dev/null +++ b/tools/dynacrchack/Auto_Compile.bat @@ -0,0 +1,41 @@ +@echo off +set source=DynaCrcHack.c + +@cd /D "%~d1%~p1" > nul + +if not exist tcc\tcc.exe ( + echo. + echo Missing ^\tcc\tcc.exe + echo. + echo Please download TCC 0.9.25 for windows from http://bellard.org/tcc/ + echo and extract the package to ^\tcc + echo. + pause + goto end +) + +if not exist utils\waitForChange.exe tcc\tcc utils\waitForChange.c -o utils\waitForChange.exe +if not exist utils\ding.exe tcc\tcc utils\ding.c -luser32 -o utils\ding.exe + +:start +echo Compiling ... +echo. +tcc\tcc -shared -Wall %source% +if %errorlevel% == 0 ( + echo -^> OK + utils\ding 2 +) else ( + echo -^> ERROR + utils\ding 1 + utils\ding 1 +) + +if exist *.def del /Q *.def + +echo Waiting ... +utils\waitForChange %source% +echo. + +goto start + +:end \ No newline at end of file diff --git a/tools/dynacrchack/DynaCrcHack.c b/tools/dynacrchack/DynaCrcHack.c new file mode 100644 index 0000000000..6cc6e22ae8 --- /dev/null +++ b/tools/dynacrchack/DynaCrcHack.c @@ -0,0 +1,260 @@ +/*----------------------------------------------------------*\ + * + * Copyright (C) 2011 Avi Halachmi + * avihpit (at) yahoo (dot) com + * + * 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, 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 GNU Make; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + + + This file compiles into a DLL which implements a generic GSdx CRC hack logic. + - GSdx will (re)load this DLL at runtime whenever this DLL is modified. + - See Notes and Usage instructions at the bottom of this file. + + - Compiles using tcc ( http://bellard.org/tcc/ ). Tested: tcc v0.9.25/Win32 + + version 1.1 - initial release + Version 1.2 - better GSdx integration, Bugfix. + Version 1.4 - Configuration, few utils, Cleanups (dll API is unchanged) + +\*----------------------------------------------------------*/ + +#include +#include +#include + +//some common tokens not available in C +typedef int int32; +typedef unsigned int uint32; +typedef int bool; +#define true (1) +#define false (0) + +//GSdx values available to CRC hacks +typedef struct{ uint32 FBP; uint32 FPSM; uint32 FBMSK; uint32 TBP0; uint32 TPSM; uint32 TZTST; bool TME;} GSFrameInfo; +enum Region{CRC_NoRegion,CRC_US,CRC_EU,CRC_JP,CRC_JPUNDUB,CRC_RU,CRC_FR,CRC_DE,CRC_IT,CRC_ES,CRC_ASIA,CRC_KO,CRC_RegionCount}; +enum GS_PSM{PSM_PSMCT32=0,PSM_PSMCT24=1,PSM_PSMCT16=2,PSM_PSMCT16S=10,PSM_PSMT8=19,PSM_PSMT4=20,PSM_PSMT8H=27, + PSM_PSMT4HL=36,PSM_PSMT4HH=44,PSM_PSMZ32=48,PSM_PSMZ24=49,PSM_PSMZ16=50,PSM_PSMZ16S=58}; + +//trickery to make the hack function appear and behave (almost) identical to the CRC hack functions at GSState.cpp +// - see the Notes section for exceptions +#define skip (*pSkip) +#define GSUtil_HasSharedBits(a,b,c,d) sharedBits +#define GSC_AnyGame(a,b) _GSC_AnyGame() +GSFrameInfo fi; int* pSkip; uint32 g_crc_region; uint32 sharedBits; + +//utils +const int MODE_3_DELAY = 750; // ms +void dprintf(const char* format, ...); +void dings(const int n); +bool isCornerTriggered(); +bool IsIn(const DWORD val, ...); +#define END 0x72951413 + + +// ---------- Configuration --------------------------------- +// +// Available Modes (can be changed and recompiled while PCSX2 is running): +// 0 - Disable this file (use the pre-compiled GSdx hacks as if this file never existed) +// 1 - Disable all CRC hacks (both this file and the pre-compiled GSdx hacks) +// 2 - * Enable this file (use this file instead of any pre-compiled GSdx hacks) +// 3 - Toggle repeatedly between modes 1 and 2 +#define INITIAL_MODE 2 + +// If MOUSE_TOGGLE is 1 and INITIAL_MODE is NOT 0, you can switch between modes 1/2/3 by moving the mouse +// pointer to the top-left corner of the screen (while PCSX2 is running). Set to 0 to disable mouse toggle. +// If MOUSE_TOGGLE is 2, it's the same as 1, but also plays beep sounds when switching modes. +#define MOUSE_TOGGLE 0 + + +/************ Dynamic CRC hack code starts here *****************/ + +bool GSC_AnyGame( const GSFrameInfo& fi, int& skip ) +{ + +//Example: MGS3 CRC hack copied directly from GSState.cpp (see the Notes section exceptions): + + if(skip == 0) + { + if(fi.TME && fi.FBP == 0x02000 && fi.FPSM == PSM_PSMCT32 && (fi.TBP0 == 0x00000 || fi.TBP0 == 0x01000) && fi.TPSM == PSM_PSMCT24) + { + skip = 1000; // 76, 79 + } + else if(fi.TME && fi.FBP == 0x02800 && fi.FPSM == PSM_PSMCT24 && (fi.TBP0 == 0x00000 || fi.TBP0 == 0x01000) && fi.TPSM == PSM_PSMCT32) + { + skip = 1000; // 69 + } + } + else + { + if(!fi.TME && (fi.FBP == 0x00000 || fi.FBP == 0x01000) && fi.FPSM == PSM_PSMCT32) + { + skip = 0; + } + else if(!fi.TME && fi.FBP == fi.TBP0 && fi.TBP0 == 0x2000 && fi.FPSM == PSM_PSMCT32 && fi.TPSM == PSM_PSMCT24) + { + + if(g_crc_region == CRC_US || g_crc_region == CRC_JP || g_crc_region == CRC_KO) + { + skip = 119; //ntsc + } + else + { + skip = 136; //pal + } + } + } + + return true; +} + +/*********** Dynamic CRC hack code ends here *****************/ + + +// Prints to the Debugger's output window or to DebugView ( http://technet.microsoft.com/en-us/sysinternals/bb896647 ) +void dprintf( const char* format, ...) +{ + #define BUFSIZ 2048 + char buffer[BUFSIZ]; + va_list args; + va_start( args, format ); + if( 0 > vsnprintf( buffer, BUFSIZ, format, args ) ) + sprintf( buffer, "%s","\n" ); + OutputDebugString( buffer ); + va_end( args ); +}; + +// Tests if the first argument is equal to any of the other arguments. +// - The last argument must be 'END'. +// - All values MUST be 32 bits (int/uint32/DWORD etc) or else it may crash. +// E.g. IsIn( 6, 2,4,6,8, END) is true +// E.g. IsIn( 7, 2,4,6,8,10, END) is false +bool IsIn( const DWORD val, ... ) +{ + va_list args; + va_start( args, val ); + DWORD test, res = 0; + for ( ; ( test = va_arg( args, DWORD ) ) != END ; ) + if( test == val) + {res=1; break;} + va_end( args ); + return res; +} + +// Returns true if the mouse pointer is at the top-left corner +// of the screen and it wasn't there on the previous iteration +bool isCornerTriggered() +{ + static POINT last; + POINT coord; + GetCursorPos( &coord ); + bool triggered = ( !coord.x && !coord.y && ( last.x || last.y ) ); + last = coord; + return triggered; +} + +DWORD _dingsLeft=0, _nextDing=0; +// Initiate n ding sounds (at 250ms intervals) while aborting any previous sequence. +// Use n=0 to stop an already playing sequence. +void dings(const int n){_dingsLeft=n; _nextDing=0;} +void processDings() +{ + if( _dingsLeft <= 0 ) + return; + DWORD now = GetTickCount(); + if( now < _nextDing ) + return; + _nextDing = now + 250; + _dingsLeft--; + MessageBeep( 0 ); // Asynchronous +} + +// Executed every iteration of the DLL hack invocation, takes care of mode and sounds +bool preProcess_isAbort() +{ + static int mode = INITIAL_MODE; + + if( MOUSE_TOGGLE && isCornerTriggered() ){ + mode = 1 + mode%3; + if( MOUSE_TOGGLE == 2 ) + dings( mode ); + dprintf( "Hack Mode: %s\n", mode==1?"Off":(mode==2?"On":"Toggle") ); + } + processDings(); + + if( mode == 1 ) return true; + if( mode == 2 ) return false; + return ( GetTickCount() / MODE_3_DELAY) %2; +} + + +#define DLL_EXPORT __declspec(dllexport) + +#define CRC_HACK DynamicCrcHack +#if INITIAL_MODE == 0 + #define CRC_HACK Voldemort +#endif +DLL_EXPORT bool CRC_HACK (uint32 FBP, uint32 FPSM, uint32 FBMSK, uint32 TBP0, uint32 TPSM, uint32 TZTST, + uint32 TME, int* _pSkip, uint32 _g_crc_region, uint32 _sharedBits) +{ + if(preProcess_isAbort()) // Process dings if required + return true; // Abort hack depending on mode + + fi.FBP=FBP; fi.FPSM=FPSM; fi.FBMSK=FBMSK; fi.TBP0=TBP0; fi.TPSM=TPSM; fi.TZTST=TZTST; fi.TME=TME; + pSkip=_pSkip; g_crc_region=_g_crc_region; sharedBits=_sharedBits; + + return _GSC_AnyGame(); +} + +/*-------------------- Notes- -------------------------------*\ + + 1. If required, Use CRC_ instead of CRC:: (e.g. CRC_US instead of CRC::US) + - g_crc_region will only be valid if your CRC is already associated with a hack function at GSdx + AND it's not disabled (via CrcHacksExclusions). + + 2. If required, Use GSUtil_HasSharedBits(fi.FBP, fi.FPSM, fi.TBP0, fi.TPSM) instead of + GSUtil::HasSharedBits(fi.FBP, fi.FPSM, fi.TBP0, fi.TPSM) + - Note: GSUtil_HasSharedBits supports only these arguments: (fi.FBP, fi.FPSM, fi.TBP0, fi.TPSM) + + 3. When copying the code back to GSState.cpp, remember to restore CRC::.. and GSUtil::... + +\*----------------------------------------------------------*/ + +/* --------------- Usage instructions --------------------------------------------*\ + +1. Download tcc (Tiny C Compiler) from http://bellard.org/tcc/ (Tested with v0.9.25 for Win32) + and extract it fully to \tcc + such that tcc.exe is at \tcc\tcc.exe + +2. Compile GSdx with dynamic crc hacks support by uncommenting this line at GSState.cpp: +//#define USE_DYNAMIC_CRC_HACK + +3. Configure GSdx to use this DLL by setting DYNA_DLL_PATH (The DLL is created at the same folder as this C file) + Yes, it's not configurable via ini. + +4. Run PCSX2 with a game you want to develop a CRC hack for. If the DLL exists and not disabled, you'll see + a message at the PCSX2 console: "GSdx: Dynamic CRC-hacks: Loaded OK (-> overriding internal hacks)" + If the DLL doesn't exist (yet): "GSdx: Dynamic CRC-hacks: Not available (-> using internal hacks)" + - Note: Whenever this DLL is created or changed, GSdx would automatically reload it at runtime, and display a message. + +5. Run the batch file Auto_Compile.bat + This batch file will keep a window open and automatically compile this C file into the DLL whenever this C file + is changes and saved. The window will display a message if the compile succeeded or failed, and will and play some beeps. + +6. Open this C file with a programmer's editor of your choice, change the configuration and/or modify the contents + of the function GSC_AnyGame. Save this file and watch the GSdx window for the effect it has. Repeat till you're happy. + Once you're happy with the hack, copy GSC_AnyGame function to GSState.cpp to be an integral part of GSdx (see Notes). + +\*-------------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/tools/dynacrchack/tcc/extract_tcc_here.txt b/tools/dynacrchack/tcc/extract_tcc_here.txt new file mode 100644 index 0000000000..656eb08087 --- /dev/null +++ b/tools/dynacrchack/tcc/extract_tcc_here.txt @@ -0,0 +1,2 @@ +Download tcc (Tiny C Compiler) from http://bellard.org/tcc/ (Tested with v0.9.25 for Win32) +Extract it fully such that tcc.exe is at this folder. \ No newline at end of file diff --git a/tools/dynacrchack/utils/ding.c b/tools/dynacrchack/utils/ding.c new file mode 100644 index 0000000000..243dc607f2 --- /dev/null +++ b/tools/dynacrchack/utils/ding.c @@ -0,0 +1,64 @@ +/*----------------------------------------------------------*\ + * + * Copyright (C) 2011 Avi Halachmi + * avihpit (at) yahoo (dot) com + * + * 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, 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 GNU Make; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + + ding - Play a windows notification sound + +\*----------------------------------------------------------*/ + +#include +#include + +LONG sounds[]={ + MB_OK, + MB_ICONINFORMATION, + MB_ICONWARNING, + MB_ICONERROR, + MB_ICONQUESTION, + 0xFFFFFFFF //"Simple" (with fallback to PC speaker beep) + }; + +int usage( int res ) +{ + printf( "Usage: ding [-h] [=1]\n" + "Where is a number as follows:\n" + "1-Default, 2-Information, 3-Warning, 4-Error, 5-Question, 6-Simple\n"); + return res; +} + +int main( int argc, char* argv[] ){ + LONG s = sounds[0]; + + if( argc > 2 ) + return usage( 1 ); + + if( argc == 2 ){ + if( !strcmp( argv[1], "-h" ) ) + return usage( 0 ); + + if( atoi( argv[1] ) < 1 || atoi( argv[1] ) > sizeof(sounds)/sizeof(LONG) ) + return usage( 1 ); + + s=sounds[atoi( argv[1] ) - 1]; + } + + MessageBeep( s ); + Sleep( 250 ); + return 0; +} \ No newline at end of file diff --git a/tools/dynacrchack/utils/waitForChange.c b/tools/dynacrchack/utils/waitForChange.c new file mode 100644 index 0000000000..03a6a3473c --- /dev/null +++ b/tools/dynacrchack/utils/waitForChange.c @@ -0,0 +1,55 @@ +/*----------------------------------------------------------*\ + * + * Copyright (C) 2011 Avi Halachmi + * avihpit (at) yahoo (dot) com + * + * 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, 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 GNU Make; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + + waitForChange - watches a file for modifications and exit when modified + +\*----------------------------------------------------------*/ + +#include +#include +#include + +int usage(){ + printf("Usage: WaitForChange [=100]\n"); + return 1; +} + +int main(int argc, char** argv){ + struct stat buf; + struct stat test; + + if( argc<=1 ) + return usage(); + + if(stat(argv[1], &buf)){ + printf("Error (not found?): '%s'\n", argv[1]); + return usage(); + }; + + do{ + Sleep(argc>=2?(atoi(argv[2])?atoi(argv[2]):100):100); + if(stat(argv[1], &test)){ + printf("Stat error\n"); + return 1; + }; + } while (test.st_mtime == buf.st_mtime); + + return 0; +} \ No newline at end of file