GSdx: New: Dynamic CRC Hacks system (disabled by default).

See tools/dynacrchack/DynaCrcHack.c for full instructions.

For development of CRC hacks (and just for the fun of creating such a system), Allows GSdx to load and use CRC hack logic from an external DLL, and reload it, at runtime, whenever this DLL changes (but act normally if this DLL isn't found).

This external DLL is compiled from a single C source file (a sample is provided, containing the current MGS3 CRC hack logic). There's also a system to automatically compile this C file into the DLL whenever the C file is modified, thus creating a system of instant [save C file] -> [GSdx switches to the new logic].

It's actually a pretty cool system, and might have other usages where it's useful, for the sake of tests/development/tweaking, to modify code logic during runtime. The overhead of such system compared to pre-compiled code is very low (e.g., in the case of CRC hacks which are called thousands of times/sec, I couldn't notice any difference in performance).

Compilation of the C file is currently done using TCC (Tiny C Compiler - http://bellard.org/tcc/ - extremely fast, light and powerful). TCC itself needs to be downloaded separately (~250K download, no install required). The system currently supports Windows only.

git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4914 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
avihal 2011-09-07 18:38:27 +00:00
parent b19e31a1be
commit b4a0af9769
6 changed files with 556 additions and 0 deletions

View File

@ -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 <sys/stat.h>
/***************************************************************************
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;

View File

@ -0,0 +1,41 @@
@echo off
set source=DynaCrcHack.c
@cd /D "%~d1%~p1" > nul
if not exist tcc\tcc.exe (
echo.
echo Missing ^<this-folder^>\tcc\tcc.exe
echo.
echo Please download TCC 0.9.25 for windows from http://bellard.org/tcc/
echo and extract the package to ^<this-folder^>\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

View File

@ -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 <windows.h>
#include <stdio.h>
#include <stdarg.h>
//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","<too-long-to-print>\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_<region> instead of CRC::<region> (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 <this-folder>\tcc
such that tcc.exe is at <this-folder>\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).
\*-------------------------------------------------------------------------------*/

View File

@ -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.

View File

@ -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 <windows.h>
#include <stdio.h>
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] [<n>=1]\n"
"Where <n> 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;
}

View File

@ -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 <stdio.h>
#include <sys/stat.h>
#include <windows.h>
int usage(){
printf("Usage: WaitForChange <filename> [<ms between probes>=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;
}