1502 lines
53 KiB
C++
1502 lines
53 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 <algorithm>
|
|
#include <stdio.h>
|
|
|
|
#include <windows.h>
|
|
#include <commctrl.h>
|
|
#include <tchar.h>
|
|
|
|
#include "commonIncludes.h"
|
|
#include "DirectInput.h"
|
|
#include "FileAccess.h"
|
|
#include "GBCart.h"
|
|
#include "Interface.h"
|
|
#include "NRagePluginV2.h"
|
|
#include "PakIO.h"
|
|
|
|
using std::min;
|
|
using std::max;
|
|
|
|
// Prototypes
|
|
BYTE AddressCRC( const unsigned char * Address );
|
|
BYTE DataCRC( const unsigned char * Data, const int iLength );
|
|
VOID CALLBACK WritebackProc( HWND hWnd, UINT msg, UINT_PTR idEvent, DWORD dwTime );
|
|
|
|
bool InitControllerPak( const int iControl )
|
|
// Prepares the pak
|
|
{
|
|
if( !g_pcControllers[iControl].fPlugged )
|
|
return false;
|
|
bool bReturn = false;
|
|
if( g_pcControllers[iControl].pPakData )
|
|
{
|
|
SaveControllerPak( iControl );
|
|
CloseControllerPak( iControl );
|
|
}
|
|
|
|
switch( g_pcControllers[iControl].PakType )
|
|
{
|
|
case PAK_MEM:
|
|
{
|
|
g_pcControllers[iControl].pPakData = P_malloc( sizeof(MEMPAK));
|
|
MEMPAK *mPak = (MEMPAK*)g_pcControllers[iControl].pPakData;
|
|
mPak->bPakType = PAK_MEM;
|
|
mPak->fReadonly = false;
|
|
mPak->fDexSave = false;
|
|
mPak->hMemPakHandle = NULL;
|
|
|
|
DWORD dwFilesize = PAK_MEM_SIZE; // Expected file size
|
|
TCHAR szBuffer[MAX_PATH+1],
|
|
szFullPath[MAX_PATH+1],
|
|
*pcFile;
|
|
|
|
GetAbsoluteFileName( szBuffer, g_pcControllers[iControl].szMempakFile, DIRECTORY_MEMPAK );
|
|
GetFullPathName( szBuffer, sizeof(szFullPath) / sizeof(TCHAR), szFullPath, &pcFile );
|
|
|
|
bool isNewfile = !CheckFileExists( szBuffer );
|
|
|
|
if( pcFile == NULL )
|
|
{ // No filename specified
|
|
WarningMessage( IDS_ERR_MEM_NOSPEC, MB_OK | MB_ICONWARNING );
|
|
g_pcControllers[iControl].PakType = PAK_NONE;
|
|
break; // Memory is freed at the end of this function
|
|
}
|
|
|
|
TCHAR *pcPoint = _tcsrchr( pcFile, '.' );
|
|
if( !lstrcmpi( pcPoint, _T(".n64") ) )
|
|
{
|
|
mPak->fDexSave = true;
|
|
dwFilesize += PAK_MEM_DEXOFFSET;
|
|
}
|
|
else
|
|
{
|
|
mPak->fDexSave = false;
|
|
}
|
|
|
|
HANDLE hFileHandle = CreateFile( szFullPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL );
|
|
if( hFileHandle == INVALID_HANDLE_VALUE )
|
|
{// Test if read-only access is possible
|
|
hFileHandle = CreateFile( szFullPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL );
|
|
if( hFileHandle != INVALID_HANDLE_VALUE )
|
|
{
|
|
TCHAR tszTitle[DEFAULT_BUFFER], tszText[DEFAULT_BUFFER];
|
|
|
|
LoadString( g_hResourceDLL, IDS_DLG_MEM_READONLY, tszText, DEFAULT_BUFFER );
|
|
LoadString( g_hResourceDLL, IDS_DLG_WARN_TITLE, tszTitle, DEFAULT_BUFFER );
|
|
wsprintf( szBuffer, tszText, pcFile );
|
|
MessageBox( g_strEmuInfo.hMainWindow, szBuffer, tszTitle, MB_OK | MB_ICONWARNING );
|
|
mPak->fReadonly = true;
|
|
DebugWriteA("RAM file opened in read only mode.\n");
|
|
}
|
|
else
|
|
{
|
|
TCHAR tszTitle[DEFAULT_BUFFER], tszText[DEFAULT_BUFFER];
|
|
|
|
LoadString( g_hResourceDLL, IDS_ERR_MEMOPEN, tszText, DEFAULT_BUFFER );
|
|
LoadString( g_hResourceDLL, IDS_DLG_WARN_TITLE, tszTitle, DEFAULT_BUFFER );
|
|
wsprintf( szBuffer, tszText, pcFile );
|
|
MessageBox( g_strEmuInfo.hMainWindow, szBuffer, tszTitle, MB_OK | MB_ICONWARNING );
|
|
g_pcControllers[iControl].PakType = PAK_NONE; // Set so that CloseControllerPak doesn't try to close a file that isn't open
|
|
DebugWrite(_T("Unable to read or create memory pak file %s.\n"), pcFile);
|
|
break; // Memory is freed at the end of this function
|
|
}
|
|
}
|
|
|
|
DWORD dwCurrentSize = GetFileSize( hFileHandle, NULL );
|
|
if ( mPak->fReadonly )
|
|
{
|
|
DWORD dwBytesRead = 0;
|
|
|
|
if( mPak->fDexSave )
|
|
{
|
|
SetFilePointer( hFileHandle, PAK_MEM_DEXOFFSET, NULL, FILE_BEGIN );
|
|
}
|
|
else
|
|
{
|
|
SetFilePointer( hFileHandle, 0L, NULL, FILE_BEGIN );
|
|
}
|
|
|
|
dwFilesize = min( dwFilesize, GetFileSize( hFileHandle, NULL ));
|
|
if( !isNewfile )
|
|
{
|
|
mPak->aMemPakData = (LPBYTE)P_malloc( sizeof(BYTE) * PAK_MEM_SIZE );
|
|
if( ReadFile( hFileHandle, mPak->aMemPakData, PAK_MEM_SIZE, &dwBytesRead, NULL ))
|
|
{
|
|
if( dwBytesRead < dwFilesize )
|
|
FillMemory( (LPBYTE)mPak->aMemPakData + dwBytesRead, PAK_MEM_SIZE - dwBytesRead, 0xFF );
|
|
|
|
bReturn = true;
|
|
}
|
|
else
|
|
P_free( mPak->aMemPakData );
|
|
}
|
|
else
|
|
{
|
|
FormatMemPak( mPak->aMemPakData );
|
|
bReturn = true;
|
|
}
|
|
CloseHandle( hFileHandle );
|
|
}
|
|
else
|
|
{
|
|
// Use mapped file
|
|
mPak->hMemPakHandle = CreateFileMapping( hFileHandle, NULL, mPak->fReadonly ? PAGE_READONLY : PAGE_READWRITE, 0, dwFilesize, NULL);
|
|
// Test for invalid handle
|
|
if (mPak->hMemPakHandle == NULL)
|
|
{
|
|
// Note: please don't move the CloseHandle call before GetLastError
|
|
DebugWriteA("Couldn't CreateFileMapping for memory pak file : %08x\n", GetLastError());
|
|
CloseHandle(hFileHandle);
|
|
break; // Memory is freed at the end of the function
|
|
}
|
|
CloseHandle(hFileHandle); // We can close the file handle now with no problems
|
|
|
|
mPak->aMemPakData = (LPBYTE)MapViewOfFile( mPak->hMemPakHandle, FILE_MAP_ALL_ACCESS, 0, 0, mPak->fDexSave ? PAK_MEM_SIZE + PAK_MEM_DEXOFFSET : PAK_MEM_SIZE );
|
|
|
|
// This is a bit tricky:
|
|
// If it's a dexsave, move the pak data pointer forward so it points to where the actual memory pak data starts
|
|
// We need to make sure to move it back when we UnmapViewOfFile
|
|
if (mPak->aMemPakData == NULL)
|
|
ErrorMessage(IDS_ERR_MAPVIEW, GetLastError(), false);
|
|
|
|
if ( mPak->fDexSave )
|
|
mPak->aMemPakData += PAK_MEM_DEXOFFSET;
|
|
|
|
if( dwCurrentSize < dwFilesize )
|
|
FillMemory( (LPBYTE)mPak->aMemPakData + (mPak->fDexSave ? dwCurrentSize - PAK_MEM_DEXOFFSET : dwCurrentSize), dwFilesize - dwCurrentSize, 0xFF );
|
|
|
|
if( isNewfile )
|
|
{
|
|
if (mPak->fDexSave )
|
|
{
|
|
PVOID pHeader = MapViewOfFile( mPak->hMemPakHandle, FILE_MAP_ALL_ACCESS, 0, 0, PAK_MEM_DEXOFFSET );
|
|
const char szHeader[] = "123-456-STD"; // "OMG-WTF-BBQ"? (comment by rabid)
|
|
ZeroMemory( pHeader, PAK_MEM_DEXOFFSET );
|
|
CopyMemory( pHeader, szHeader, sizeof(szHeader) );
|
|
FlushViewOfFile( pHeader, PAK_MEM_DEXOFFSET );
|
|
UnmapViewOfFile( pHeader );
|
|
}
|
|
FormatMemPak( mPak->aMemPakData );
|
|
}
|
|
|
|
bReturn = true;
|
|
}
|
|
}
|
|
break;
|
|
case PAK_RUMBLE:
|
|
{
|
|
g_pcControllers[iControl].pPakData = P_malloc( sizeof(RUMBLEPAK));
|
|
RUMBLEPAK *rPak = (RUMBLEPAK*)g_pcControllers[iControl].pPakData;
|
|
rPak->bPakType = PAK_RUMBLE;
|
|
|
|
rPak->fLastData = true; // Statistically, if uninitialized it would return true (comment by rabid)
|
|
// rPak->bRumbleTyp = g_pcControllers[iControl].bRumbleTyp;
|
|
// rPak->bRumbleStrength = g_pcControllers[iControl].bRumbleStrength;
|
|
// rPak->fVisualRumble = g_pcControllers[iControl].fVisualRumble;
|
|
if( !g_pcControllers[iControl].fXInput ) // Used to make sure only XInput controller rumbles (comment by tecnicors)
|
|
CreateEffectHandle( iControl, g_pcControllers[iControl].bRumbleTyp, g_pcControllers[iControl].bRumbleStrength );
|
|
bReturn = true;
|
|
}
|
|
break;
|
|
case PAK_TRANSFER:
|
|
{
|
|
g_pcControllers[iControl].pPakData = P_malloc( sizeof(TRANSFERPAK));
|
|
LPTRANSFERPAK tPak = (LPTRANSFERPAK)g_pcControllers[iControl].pPakData;
|
|
tPak->bPakType = PAK_TRANSFER;
|
|
|
|
tPak->gbCart.hRomFile = NULL;
|
|
tPak->gbCart.hRamFile = NULL;
|
|
tPak->gbCart.RomData = NULL;
|
|
tPak->gbCart.RamData = NULL;
|
|
|
|
/*
|
|
* Once the Interface is implemented g_pcControllers[iControl].szTransferRom will hold filename of the Game Boy ROM
|
|
* and g_pcControllers[iControl].szTransferSave holds Filename of the SRAM save
|
|
*
|
|
* Here, both files should be opened and the handles stored in tPak (modify the struct for your own purposes, only bPakType must stay at first)
|
|
*/
|
|
|
|
//CreateFile( g_pcControllers[iControl].szTransferSave, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL );
|
|
tPak->iCurrentAccessMode = 0;
|
|
tPak->iCurrentBankNo = 0;
|
|
tPak->iEnableState = false;
|
|
tPak->iAccessModeChanged = 0x44;
|
|
|
|
tPak->bPakInserted = LoadCart( &tPak->gbCart, g_pcControllers[iControl].szTransferRom, g_pcControllers[iControl].szTransferSave, _T("") );
|
|
|
|
if (tPak->bPakInserted)
|
|
{
|
|
DebugWriteA( "*** Initialize transfer pak - success***\n" );
|
|
}
|
|
else
|
|
{
|
|
DebugWriteA( "*** Initialize transfer pak - failure***\n" );
|
|
}
|
|
|
|
bReturn = true;
|
|
}
|
|
break;
|
|
/*case PAK_VOICE:
|
|
{
|
|
g_pcControllers[iControl].pPakData = P_malloc( sizeof(VOICEPAK));
|
|
VOICEPAK *vPak = (VOICEPAK*)g_pcControllers[iControl].pPakData;
|
|
vPak->bPakType = PAK_VOICE;
|
|
|
|
bReturn = true;
|
|
}
|
|
break;*/
|
|
case PAK_ADAPTOID:
|
|
if( !g_pcControllers[iControl].fIsAdaptoid )
|
|
g_pcControllers[iControl].PakType = PAK_NONE;
|
|
else
|
|
{
|
|
g_pcControllers[iControl].pPakData = P_malloc( sizeof(ADAPTOIDPAK));
|
|
ADAPTOIDPAK *aPak = (ADAPTOIDPAK*)g_pcControllers[iControl].pPakData;
|
|
aPak->bPakType = PAK_ADAPTOID;
|
|
|
|
aPak->bIdentifier = 0x80;
|
|
#ifdef ADAPTOIDPAK_RUMBLEFIX
|
|
aPak->fRumblePak = true;
|
|
#pragma message( "Driver fix for rumble with Adaptoid enabled" )
|
|
#else
|
|
aPak->fRumblePak = false;
|
|
#endif
|
|
bReturn = true;
|
|
}
|
|
break;
|
|
/*case PAK_NONE:
|
|
break;*/
|
|
}
|
|
|
|
// If there were any unrecoverable errors and we have allocated pPakData, free it and set pak type to NONE
|
|
if( !bReturn && g_pcControllers[iControl].pPakData )
|
|
CloseControllerPak( iControl );
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
|
|
BYTE ReadControllerPak( const int iControl, LPBYTE Command )
|
|
{
|
|
BYTE bReturn = RD_ERROR;
|
|
LPBYTE Data = &Command[2];
|
|
|
|
#ifdef MAKEADRESSCRCCHECK
|
|
#pragma message( "Addresscheck for Pak-Reads active" )
|
|
if( AddressCRC( Command ) != (Command[1] & 0x1F) )
|
|
{
|
|
g_pcControllers[iControl].fPakCRCError = true;
|
|
if( WarningMessage( IDS_DLG_MEM_BADADDRESSCRC, MB_OKCANCEL | MB_ICONQUESTION ) == IDCANCEL )
|
|
return RD_ERROR;
|
|
}
|
|
#endif
|
|
|
|
if( !g_pcControllers[iControl].pPakData )
|
|
return RD_ERROR;
|
|
|
|
WORD dwAddress = (Command[0] << 8) + (Command[1] & 0xE0);
|
|
|
|
switch( *(BYTE*)g_pcControllers[iControl].pPakData )
|
|
{
|
|
case PAK_MEM:
|
|
{
|
|
MEMPAK *mPak = (MEMPAK*)g_pcControllers[iControl].pPakData;
|
|
|
|
if( dwAddress < 0x8000 )
|
|
CopyMemory( Data, &mPak->aMemPakData[dwAddress], 32 );
|
|
else
|
|
CopyMemory( Data, &mPak->aMemPakTemp[(dwAddress%0x100)], 32 );
|
|
Data[32] = DataCRC( Data, 32 );
|
|
bReturn = RD_OK;
|
|
}
|
|
break;
|
|
case PAK_RUMBLE:
|
|
if(( dwAddress >= 0x8000 ) && ( dwAddress < 0x9000 ) )
|
|
{
|
|
RUMBLEPAK *rPak = (RUMBLEPAK*)g_pcControllers[iControl].pPakData;
|
|
|
|
if (rPak->fLastData)
|
|
FillMemory( Data, 32, 0x80 );
|
|
else
|
|
ZeroMemory( Data, 32 );
|
|
|
|
if( g_pcControllers[iControl].fXInput ) // XInput controller rumble (comment by tecnicors)
|
|
VibrateXInputController( g_pcControllers[iControl].xiController.nControl, 0, 0);
|
|
else if (g_apFFDevice[iControl])
|
|
g_apFFDevice[iControl]->Acquire();
|
|
}
|
|
else
|
|
ZeroMemory( Data, 32 );
|
|
|
|
Data[32] = DataCRC( Data, 32 );
|
|
bReturn = RD_OK;
|
|
break;
|
|
|
|
case PAK_TRANSFER:
|
|
{
|
|
LPTRANSFERPAK tPak = (LPTRANSFERPAK)g_pcControllers[iControl].pPakData; // TODO: null pointer check on tPak
|
|
// Set bReturn = RD_OK when implementing transfer pak
|
|
bReturn = RD_OK;
|
|
DebugWriteA( "Transfer pak Read:\n" );
|
|
DebugWriteA( " Address: %04X\n", dwAddress );
|
|
|
|
switch (dwAddress >> 12)
|
|
{
|
|
case 0x8: // if ((dwAddress >= 0x8000) && (dwAddress <= 0x8FFF))
|
|
DebugWriteA( "Query enable state: %u\n", tPak->iEnableState );
|
|
if (tPak->iEnableState == false)
|
|
ZeroMemory(Data, 32);
|
|
else
|
|
FillMemory(Data, 32, 0x84);
|
|
break;
|
|
case 0xB: // if ((dwAddress >= 0xB000) && (dwAddress <= 0xBFFF))
|
|
if (tPak->iEnableState == true)
|
|
{
|
|
DebugWriteA( "Query cart. State:" );
|
|
if (tPak->bPakInserted)
|
|
{
|
|
if (tPak->iCurrentAccessMode == 1)
|
|
{
|
|
FillMemory(Data, 32, 0x89);
|
|
DebugWriteA( " Inserted, access mode 1\n" );
|
|
}
|
|
else
|
|
{
|
|
FillMemory(Data, 32, 0x80);
|
|
DebugWriteA( " Inserted, access mode 0\n" );
|
|
}
|
|
Data[0] = Data[0] | tPak->iAccessModeChanged;
|
|
}
|
|
else
|
|
{
|
|
FillMemory(Data, 32, 0x40); // Cart not inserted
|
|
DebugWriteA( " not inserted\n" );
|
|
}
|
|
tPak->iAccessModeChanged = 0;
|
|
}
|
|
break;
|
|
case 0xC:
|
|
case 0xD:
|
|
case 0xE:
|
|
case 0xF: // if ((dwAddress >= 0xC000))
|
|
if (tPak->iEnableState == true)
|
|
{
|
|
DebugWriteA( "Cart read: Bank:%i\n", tPak->iCurrentBankNo );
|
|
DebugWriteA( " Address:%04X\n", ((dwAddress & 0xFFE0) - 0xC000) + ((tPak->iCurrentBankNo & 3) * 0x4000) );
|
|
|
|
tPak->gbCart.ptrfnReadCart(&tPak->gbCart, ((dwAddress & 0xFFE0) - 0xC000) + ((tPak->iCurrentBankNo & 3) * 0x4000), Data);
|
|
}
|
|
break;
|
|
default:
|
|
DebugWriteA("Warning: unusual pak read\n" );
|
|
DebugWriteA(" Address: %04X\n", dwAddress);
|
|
} // end switch (dwAddress >> 12)
|
|
|
|
#ifdef ENABLE_RAWPAK_DEBUG
|
|
DebugWriteA( "Transfer pak data: " );
|
|
|
|
for (int i = 0; i < 32; i ++)
|
|
{
|
|
if ((i < 31) && ((i & 7) == 0)) DebugWriteA( "\n " );
|
|
DebugWriteByteA(Data[i]);
|
|
if (i < 31)
|
|
{
|
|
DebugWriteA( ", ");
|
|
}
|
|
}
|
|
DebugWriteA( "\n" );
|
|
#endif
|
|
|
|
Data[32] = DataCRC( Data, 32 );
|
|
|
|
bReturn = RD_OK;
|
|
}
|
|
break;
|
|
/*case PAK_VOICE:
|
|
break;*/
|
|
case PAK_ADAPTOID:
|
|
if( ReadAdaptoidPak( iControl, dwAddress, Data ) == DI_OK )
|
|
{
|
|
Data[32] = DataCRC( Data, 32 );
|
|
bReturn = RD_OK;
|
|
|
|
if( ((ADAPTOIDPAK*)g_pcControllers[iControl].pPakData)->fRumblePak )
|
|
{
|
|
BYTE bId = ((ADAPTOIDPAK*)g_pcControllers[iControl].pPakData)->bIdentifier;
|
|
if( (( dwAddress == 0x8000 ) && ( bId == 0x80 ) && ( Data[0] != 0x80 ))
|
|
|| (( dwAddress == 0x8000 ) && ( bId != 0x80 ) && ( Data[0] != 0x00 ))
|
|
|| (( dwAddress < 0x8000 ) && ( Data[0] != 0x00 )))
|
|
{
|
|
((ADAPTOIDPAK*)g_pcControllers[iControl].pPakData)->fRumblePak = false;
|
|
DebugWriteA( "\nAssuming the inserted pak isn't a rumble pak\nDisabling rumble fix\n" );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
/*case PAK_NONE:
|
|
break;*/
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
// Called when the N64 tries to write to the controller pak, like a memory pak
|
|
BYTE WriteControllerPak( const int iControl, LPBYTE Command )
|
|
{
|
|
BYTE bReturn = RD_ERROR;
|
|
BYTE *Data = &Command[2];
|
|
|
|
#ifdef MAKEADRESSCRCCHECK
|
|
#pragma message( "Addresscheck for Pak-Writes active" )
|
|
if( AddressCRC( Command ) != (Command[1] & 0x1F) )
|
|
{
|
|
g_pcControllers[iControl].fPakCRCError = true;
|
|
if( WarningMessage( IDS_DLG_MEM_BADADDRESSCRC, MB_OKCANCEL | MB_ICONQUESTION ) == IDCANCEL )
|
|
return RD_ERROR;
|
|
}
|
|
#endif
|
|
|
|
if( !g_pcControllers[iControl].pPakData )
|
|
return RD_ERROR;
|
|
|
|
WORD dwAddress = (Command[0] << 8) + (Command[1] & 0xE0);
|
|
|
|
switch( *(BYTE*)g_pcControllers[iControl].pPakData )
|
|
{
|
|
case PAK_MEM:
|
|
{
|
|
// Switched to memory mapped file
|
|
// That way, if the computer dies due to power loss or something during gameplay, the save game is still there
|
|
MEMPAK *mPak = (MEMPAK*)g_pcControllers[iControl].pPakData;
|
|
|
|
if( dwAddress < 0x8000 )
|
|
{
|
|
CopyMemory( &mPak->aMemPakData[dwAddress], Data, 32 );
|
|
if (!mPak->fReadonly )
|
|
SetTimer( g_strEmuInfo.hMainWindow, PAK_MEM, 2000, (TIMERPROC) WritebackProc ); // If we go 2 seconds without a write, call the Writeback proc (which will flush the cache)
|
|
}
|
|
else
|
|
CopyMemory( &mPak->aMemPakTemp[(dwAddress%0x100)], Data, 32 );
|
|
Data[32] = DataCRC( Data, 32 );
|
|
bReturn = RD_OK;
|
|
}
|
|
break;
|
|
case PAK_RUMBLE:
|
|
if( dwAddress == PAK_IO_RUMBLE )
|
|
{
|
|
if( g_pcControllers[iControl].fXInput ) // XInput controller rumble (comment by tecnicors)
|
|
{
|
|
if( *Data )
|
|
VibrateXInputController( g_pcControllers[iControl].xiController.nControl );
|
|
else
|
|
VibrateXInputController( g_pcControllers[iControl].xiController.nControl, 0, 0 );
|
|
goto end_rumble;
|
|
}
|
|
|
|
if( g_pcControllers[iControl].fVisualRumble )
|
|
FlashWindow( g_strEmuInfo.hMainWindow, ( *Data != 0 ) ? TRUE : FALSE );
|
|
if( g_pcControllers[iControl].bRumbleTyp == RUMBLE_DIRECT )
|
|
{ // Adaptoid direct rumble
|
|
if( g_pcControllers[iControl].fIsAdaptoid )
|
|
DirectRumbleCommand( iControl, *Data );
|
|
}
|
|
else
|
|
{ // Force feedback rumble
|
|
if( g_apdiEffect[iControl] )
|
|
{
|
|
g_apFFDevice[iControl]->Acquire();
|
|
if( *Data )
|
|
{
|
|
// g_apdiEffect[iControl]->Start( 1, DIES_SOLO );
|
|
HRESULT hr;
|
|
hr = g_apdiEffect[iControl]->Start( 1, DIES_NODOWNLOAD );
|
|
if( hr != DI_OK )// Just download if needed (seems to work smoother)
|
|
{
|
|
hr = g_apdiEffect[iControl]->Start( 1, 0 );
|
|
if (hr != DI_OK)
|
|
{
|
|
DebugWriteA("Rumble: Can't rumble %d: %lX\n", iControl, hr);
|
|
}
|
|
else
|
|
DebugWriteA("Rumble: DIES_NODOWNLOAD failed, regular OK on control %d\n", iControl);
|
|
}
|
|
else
|
|
DebugWriteA("Rumble: DIES_NODOWNLOAD OK on control %d\n", iControl);
|
|
}
|
|
else
|
|
{
|
|
g_apdiEffect[iControl]->Stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (dwAddress >= 0x8000 && dwAddress < 0x9000)
|
|
{
|
|
RUMBLEPAK *rPak = (RUMBLEPAK*)g_pcControllers[iControl].pPakData;
|
|
rPak->fLastData = (*Data) ? true : false;
|
|
}
|
|
|
|
end_rumble: // Added so after XInput controller rumbles, gets here (comment by tecnicors)
|
|
Data[32] = DataCRC( Data, 32 );
|
|
bReturn = RD_OK;
|
|
break;
|
|
case PAK_TRANSFER:
|
|
{
|
|
LPTRANSFERPAK tPak = (LPTRANSFERPAK)g_pcControllers[iControl].pPakData;
|
|
// Set bReturn = RD_OK when implementing transfer pak
|
|
DebugWriteA( "Transfer pak write:\n" );
|
|
DebugWriteA( " Address: %04X\n", dwAddress );
|
|
|
|
#ifdef ENABLE_RAWPAK_DEBUG
|
|
DebugWriteA( " Data: ");
|
|
|
|
for (int i = 0; i < 32; i++)
|
|
{
|
|
if ((i < 31) && ((i & 7) == 0))
|
|
{
|
|
DebugWriteA( "\n " );
|
|
}
|
|
DebugWriteByteA( Data[i]);
|
|
if (i < 31)
|
|
{
|
|
DebugWriteA( ", " );
|
|
}
|
|
}
|
|
|
|
DebugWriteA( "\n" );
|
|
#endif // #ifdef ENABLE_RAWPAK_DEBUG
|
|
|
|
switch (dwAddress >> 12)
|
|
{
|
|
case 0x8: // if ((dwAddress >= 0x8000) && (dwAddress <= 0x8FFF))
|
|
if (Data[0] == 0xFE)
|
|
{
|
|
DebugWriteA("Cart disable\n" );
|
|
tPak->iEnableState = false;
|
|
}
|
|
else if (Data[0] == 0x84)
|
|
{
|
|
DebugWriteA("Cart enable\n" );
|
|
tPak->iEnableState = true;
|
|
}
|
|
else
|
|
{
|
|
DebugWriteA("Warning: Unusual cart enable/disable\n" );
|
|
DebugWriteA(" Address: " );
|
|
DebugWriteWordA(dwAddress);
|
|
DebugWriteA("\n" );
|
|
DebugWriteA(" Data: " );
|
|
DebugWriteByteA(Data[0]);
|
|
DebugWriteA("\n" );
|
|
}
|
|
break;
|
|
case 0xA: // if ((dwAddress >= 0xA000) && (dwAddress <= 0xAFFF))
|
|
if (tPak->iEnableState == true)
|
|
{
|
|
tPak->iCurrentBankNo = Data[0];
|
|
DebugWriteA("Set transfer pak bank No:%02X\n", Data[0] );
|
|
}
|
|
break;
|
|
case 0xB: // if ((dwAddress >= 0xB000) && (dwAddress <= 0xBFFF))
|
|
if (tPak->iEnableState == true)
|
|
{
|
|
tPak->iCurrentAccessMode = Data[0] & 1;
|
|
tPak->iAccessModeChanged = 4;
|
|
DebugWriteA("Set tranfer pak access mode: %04X\n", tPak->iCurrentAccessMode);
|
|
if ((Data[0] != 1) && (Data[0] != 0))
|
|
{
|
|
DebugWriteA("Warning: Unusual access mode change\n" );
|
|
DebugWriteA(" Address: " );
|
|
DebugWriteWordA(dwAddress);
|
|
DebugWriteA("\n" );
|
|
DebugWriteA(" Data: " );
|
|
DebugWriteByteA(Data[0]);
|
|
DebugWriteA("\n" );
|
|
}
|
|
}
|
|
break;
|
|
case 0xC:
|
|
case 0xD:
|
|
case 0xE:
|
|
case 0xF: // if (dwAddress >= 0xC000)
|
|
tPak->gbCart.ptrfnWriteCart(&tPak->gbCart, ((dwAddress & 0xFFE0) - 0xC000) + ((tPak->iCurrentBankNo & 3) * 0x4000), Data);
|
|
if (tPak->gbCart.hRamFile != NULL )
|
|
SetTimer( g_strEmuInfo.hMainWindow, PAK_TRANSFER, 2000, (TIMERPROC) WritebackProc ); // if we go 2 seconds without a write, call the Writeback proc (which will flush the cache)
|
|
break;
|
|
default:
|
|
DebugWriteA("Warning: Unusual pak write\n" );
|
|
DebugWriteA(" Address: %04X\n", dwAddress);
|
|
} // end switch (dwAddress >> 12)
|
|
|
|
Data[32] = DataCRC( Data, 32 );
|
|
bReturn = RD_OK;
|
|
}
|
|
break;
|
|
/*case PAK_VOICE:
|
|
break;*/
|
|
case PAK_ADAPTOID:
|
|
if(( dwAddress == PAK_IO_RUMBLE ) && ((ADAPTOIDPAK*)g_pcControllers[iControl].pPakData)->fRumblePak )
|
|
{
|
|
if( DirectRumbleCommand( iControl, *Data ) == DI_OK )
|
|
{
|
|
Data[32] = DataCRC( Data, 32 );
|
|
bReturn = RD_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( WriteAdaptoidPak( iControl, dwAddress, Data ) == DI_OK )
|
|
{
|
|
Data[32] = DataCRC( Data, 32 );
|
|
if( dwAddress == 0x8000 )
|
|
((ADAPTOIDPAK*)g_pcControllers[iControl].pPakData)->bIdentifier = Data[0];
|
|
|
|
bReturn = RD_OK;
|
|
}
|
|
}
|
|
break;
|
|
/*case PAK_NONE:
|
|
break;*/
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
void SaveControllerPak( const int iControl )
|
|
{
|
|
if( !g_pcControllers[iControl].pPakData )
|
|
return;
|
|
|
|
switch( *(BYTE*)g_pcControllers[iControl].pPakData )
|
|
{
|
|
case PAK_MEM:
|
|
{
|
|
MEMPAK *mPak = (MEMPAK*)g_pcControllers[iControl].pPakData;
|
|
|
|
if( !mPak->fReadonly )
|
|
FlushViewOfFile( mPak->aMemPakData, PAK_MEM_SIZE ); // We've already written the stuff, just flush the cache
|
|
}
|
|
break;
|
|
case PAK_RUMBLE:
|
|
break;
|
|
case PAK_TRANSFER:
|
|
{
|
|
LPTRANSFERPAK tPak = (LPTRANSFERPAK)g_pcControllers[iControl].pPakData;
|
|
// Here the changes(if any) in the SRAM should be saved
|
|
|
|
if (tPak->gbCart.hRamFile != NULL)
|
|
{
|
|
SaveCart(&tPak->gbCart, g_pcControllers[iControl].szTransferSave, _T(""));
|
|
DebugWriteA( "*** Save transfer pak ***\n" );
|
|
}
|
|
}
|
|
break;
|
|
case PAK_VOICE:
|
|
break;
|
|
case PAK_ADAPTOID:
|
|
break;
|
|
/*case PAK_NONE:
|
|
break;*/
|
|
}
|
|
}
|
|
|
|
// If there is pPakData for the controller, does any closing of handles before freeing the pPakData struct and setting it to NULL
|
|
// also sets fPakInitialized to false
|
|
void CloseControllerPak( const int iControl )
|
|
{
|
|
if( !g_pcControllers[iControl].pPakData )
|
|
return;
|
|
|
|
g_pcControllers[iControl].fPakInitialized = false;
|
|
|
|
switch( *(BYTE*)g_pcControllers[iControl].pPakData )
|
|
{
|
|
case PAK_MEM:
|
|
{
|
|
MEMPAK *mPak = (MEMPAK*)g_pcControllers[iControl].pPakData;
|
|
|
|
if( mPak->fReadonly )
|
|
{
|
|
P_free( mPak->aMemPakData );
|
|
mPak->aMemPakData = NULL;
|
|
}
|
|
else
|
|
{
|
|
FlushViewOfFile( mPak->aMemPakData, PAK_MEM_SIZE );
|
|
// If it's a dexsave, our original mapped view is not aMemPakData
|
|
UnmapViewOfFile( mPak->fDexSave ? mPak->aMemPakData - PAK_MEM_DEXOFFSET : mPak->aMemPakData );
|
|
if ( mPak->hMemPakHandle != NULL )
|
|
CloseHandle( mPak->hMemPakHandle );
|
|
}
|
|
}
|
|
break;
|
|
case PAK_RUMBLE:
|
|
ReleaseEffect( g_apdiEffect[iControl] );
|
|
g_apdiEffect[iControl] = NULL;
|
|
break;
|
|
case PAK_TRANSFER:
|
|
{
|
|
LPTRANSFERPAK tPak = (LPTRANSFERPAK)g_pcControllers[iControl].pPakData;
|
|
UnloadCart(&tPak->gbCart);
|
|
DebugWriteA( "*** Close transfer pak ***\n" );
|
|
// Close files and free any additional resources
|
|
}
|
|
break;
|
|
case PAK_VOICE:
|
|
break;
|
|
case PAK_ADAPTOID:
|
|
break;
|
|
/*case PAK_NONE:
|
|
break;*/
|
|
}
|
|
|
|
freePakData( &g_pcControllers[iControl] );
|
|
return;
|
|
}
|
|
|
|
// Returns the number of remaining blocks in a memory pak
|
|
// aNoteSizes should be an array of 16 bytes, which will be overwritten with the size in blocks of each note
|
|
inline WORD CountBlocks( const unsigned char * bMemPakBinary, LPBYTE aNoteSizes )
|
|
{
|
|
WORD wRemainingBlocks = 123;
|
|
BYTE bNextIndex;
|
|
int i = 0;
|
|
while( i < 16 && wRemainingBlocks <= 123 )
|
|
{
|
|
aNoteSizes[i] = 0;
|
|
bNextIndex = bMemPakBinary[0x307 + (i*0x20)];
|
|
while(( bNextIndex >= 5 ) && ( aNoteSizes[i] < wRemainingBlocks))
|
|
{
|
|
aNoteSizes[i]++;
|
|
bNextIndex = bMemPakBinary[0x101 + (bNextIndex*2)];
|
|
}
|
|
|
|
if( aNoteSizes[i] > wRemainingBlocks )
|
|
wRemainingBlocks = 0xFF;
|
|
else
|
|
wRemainingBlocks -= aNoteSizes[i];
|
|
|
|
i++;
|
|
}
|
|
return wRemainingBlocks;
|
|
}
|
|
|
|
void FormatMemPak( LPBYTE aMemPak )
|
|
{
|
|
size_t iRand, n;
|
|
|
|
FillMemory(aMemPak, 0x100, 0xFF);
|
|
aMemPak[0] = 0x81;
|
|
|
|
// TODO: Check this code since it seems the author isn't convinced it's complete
|
|
// Generate a valid code
|
|
BYTE aValidCodes[] = { 0x12, 0xC5, 0x8F, 0x6F, 0xA4, 0x28, 0x5B, 0xCA };
|
|
BYTE aCode[8];
|
|
|
|
iRand = ((size_t)aMemPak / 4) + ((size_t)g_strEmuInfo.hMainWindow / 4);
|
|
iRand %= sizeof(aValidCodes) / 8;
|
|
for (n = 0; n < 8; n++)
|
|
aCode[n] = aValidCodes[n + iRand];
|
|
|
|
aMemPak[0x20+0] = aMemPak[0x60+0] = aMemPak[0x80+0] = aMemPak[0xC0+0] = 0xFF;
|
|
aMemPak[0x20+1] = aMemPak[0x60+1] = aMemPak[0x80+1] = aMemPak[0xC0+1] = 0xFF;
|
|
aMemPak[0x20+2] = aMemPak[0x60+2] = aMemPak[0x80+2] = aMemPak[0xC0+2] = 0xFF;
|
|
aMemPak[0x20+3] = aMemPak[0x60+3] = aMemPak[0x80+3] = aMemPak[0xC0+3] = 0xFF;
|
|
|
|
aMemPak[0x20+4] = aMemPak[0x60+4] = aMemPak[0x80+4] = aMemPak[0xC0+4] = aCode[0];
|
|
aMemPak[0x20+5] = aMemPak[0x60+5] = aMemPak[0x80+5] = aMemPak[0xC0+5] = aCode[1];
|
|
aMemPak[0x20+6] = aMemPak[0x60+6] = aMemPak[0x80+6] = aMemPak[0xC0+6] = aCode[2];
|
|
aMemPak[0x20+7] = aMemPak[0x60+7] = aMemPak[0x80+7] = aMemPak[0xC0+7] = aCode[3];
|
|
|
|
//aMemPak[0x30+9] = aMemPak[0x70+9] = aMemPak[0x90+9] = aMemPak[0xD0+9] = 0x01; // Not sure
|
|
aMemPak[0x30+10] = aMemPak[0x70+10] = aMemPak[0x90+10] = aMemPak[0xD0+10] = 0x01;
|
|
|
|
aMemPak[0x30+12] = aMemPak[0x70+12] = aMemPak[0x90+12] = aMemPak[0xD0+12] = aCode[4];
|
|
aMemPak[0x30+13] = aMemPak[0x70+13] = aMemPak[0x90+13] = aMemPak[0xD0+13] = aCode[5];
|
|
aMemPak[0x30+14] = aMemPak[0x70+14] = aMemPak[0x90+14] = aMemPak[0xD0+14] = aCode[6];
|
|
aMemPak[0x30+15] = aMemPak[0x70+15] = aMemPak[0x90+15] = aMemPak[0xD0+15] = aCode[7];
|
|
|
|
// Index
|
|
ZeroMemory( &aMemPak[0x100], 0x400 );
|
|
|
|
aMemPak[0x100+1] = aMemPak[0x200+1] = 0x71;
|
|
for( int i = 0x00b; i < 0x100; i += 2 )
|
|
aMemPak[0x100+i] = aMemPak[0x200+i] = 03;
|
|
|
|
FillMemory( &aMemPak[0x500], 0x7B00, 0xFF );
|
|
}
|
|
|
|
// TODO: Possibly update this since it seems it could be hacky or incomplete
|
|
// Translates a memory pak header into a real Unicode string, for display in the memory paks window
|
|
// bNote is now where you want to start translating, text is where the output gets sent, iChars is the number of TCHARs you want translated
|
|
// Return value is the number of characters actually translated
|
|
// Text automatically gets a terminating null, so make sure there's enough space
|
|
int TranslateNotesW( const unsigned char * bNote, LPWSTR Text, const int iChars )
|
|
{
|
|
WCHAR aSpecial[] = { 0x0021, 0x0022, 0x0023, 0x0060, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x003A, 0x003D, 0x003F, 0x0040, 0x2122, 0x00A9, 0x00AE };
|
|
// { '!' , '\"', '#' , '`' , '*' , '+' , ',' , '-' , '.' , '/' , ':' , '=' , '?' , '>' , 'tm', '(c)', '(r)' };
|
|
const WCHAR aJmap[] = { // Map of Japanese characters N64 to UTF-16, starting at 0x45
|
|
0x00A1, 0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30E3, 0x30E5, 0x30E7, 0x30C3, 0x30F2, // Small a-i-u-e-o, next are probably small ya-yu-yo, small tsu, wo
|
|
0x30F3, // 0x4F -> 'n'
|
|
0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, 0x30AF, 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, // nil-K-S
|
|
0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC, 0x30CD, 0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, // T-N-H
|
|
0x30DE, 0x30DF, 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, 0x30EA, 0x30EB, 0x30EC, 0x30ED, // M-Y-R
|
|
0x30EF, // 'wa'
|
|
0x30AC, 0x30AE, 0x30B0, 0x30B2, 0x30B4, 0x30B6, 0x30B8, 0x30BA, 0x30BC, 0x30BE, 0x30C0, 0x30C2, 0x30C5, 0x30C7, 0x30C9, // G-Z-D
|
|
0x30D0, 0x30D3, 0x30D6, 0x30D9, 0x30DC, 0x30D1, 0x30D4, 0x30D7, 0x30DA, 0x30DD // B-P
|
|
};
|
|
int iReturn = 0;
|
|
|
|
while (iChars - iReturn > 0 && *bNote)
|
|
{
|
|
BYTE b = *bNote;
|
|
if( b <= 0x0F ) // Translate icons as spaces
|
|
*Text = 0x0020;
|
|
else if( b <= 0x19 ) // Numbers
|
|
*Text = 0x0020 + b;
|
|
else if( b <= 0x33 ) // English characters
|
|
*Text = 0x0047 + b;
|
|
else if( b <= 0x44 ) // Special symbols
|
|
*Text = aSpecial[b - 0x34];
|
|
else if( b <= 0x94 ) // Japanese (halfwidth katakana, mapped similarly to JIS X 0201 but not enough that we can use a simple codepage)
|
|
{
|
|
aSpecial[7] = 0x30fc; // Change regular dash to Japanese "long sound dash"
|
|
*Text = aJmap[b - 0x45];
|
|
}
|
|
else // Unknown
|
|
*Text = 0x00A4; // Unknown characters become "currency sign" (looks like a letter o superimposed on an x)
|
|
Text++;
|
|
iReturn++;
|
|
bNote++;
|
|
}
|
|
|
|
*Text = L'\0';
|
|
|
|
return iReturn;
|
|
}
|
|
|
|
// TODO: rename this function! It serves a completely different function from TranslateNotesW
|
|
// Translates a memory pak header into an ASCII string for output to .a64 file
|
|
// bNote is now where you want to start translating, text is where the output gets sent, iChars is the number of chars you want translated
|
|
// Return value is the number of characters actually translated
|
|
// Text automatically gets a terminating null, so make sure there's enough space
|
|
int TranslateNotesA( const unsigned char * bNote, LPSTR Text, const int iChars )
|
|
{
|
|
const UCHAR aSpecial[] ={ 0x21, 0x22, 0x23, 0x60, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x3A, 0x3D, 0x3F, 0x40, 0x99, 0xA9, 0xAE };
|
|
// { '!' , '\"', '#' , '`' , '*' , '+' , ',' , '-' , '.' , '/' , ':' , '=' , '?' , '>' , 'tm', '(c)','(r)' };
|
|
int iReturn = 0;
|
|
|
|
while (iChars - iReturn > 0 && *bNote)
|
|
{
|
|
BYTE b = *bNote;
|
|
if( b <= 0x0F ) // Translate icons as spaces
|
|
*Text = 0x20;
|
|
else if( b <= 0x19 ) // Numbers
|
|
*Text = 0x20 + b;
|
|
else if( b <= 0x33 ) // English characters
|
|
*Text = 0x47 + b;
|
|
else if( b <= 0x44 ) // Special symbols
|
|
*Text = aSpecial[b - 0x34];
|
|
else if( b <= 0x94 ) // Japan
|
|
*Text = 0xC0 + ( b % 40 );
|
|
else // Unknown
|
|
*Text = (UCHAR)0xA4; // Hack: this will screw up any save with unknown characters
|
|
|
|
Text++;
|
|
iReturn++;
|
|
bNote++;
|
|
}
|
|
|
|
*Text = '\0';
|
|
return iReturn;
|
|
}
|
|
|
|
int ReverseNotesA( LPCSTR Text, LPBYTE Note )
|
|
{
|
|
const UCHAR aSpecial[] = { 0x21, 0x22, 0x23, 0x60, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x3A, 0x3D, 0x3F, 0x40, 0x74, 0xA9, 0xAE };
|
|
// { '!' , '\"', '#' , '`' , '*' , '+' , ',' , '-' , '.' , '/' , ':' , '=' , '?' , '>' , 'tm', '(r)','(c)' };
|
|
|
|
LPCSTR TextPos = Text;
|
|
while( *TextPos != '\0' )
|
|
{
|
|
char c = *TextPos;
|
|
*Note = 0x0F;
|
|
|
|
if( c >= '0' && c <= '9' )
|
|
*Note = c - '0' + 0x10;
|
|
else if( c >= 'A' && c <= 'Z' )
|
|
*Note = c - 'A' + 0x1A;
|
|
else if( c >= 'a' && c <= 'z' )
|
|
*Note = c - 'a' + 0x1A;
|
|
|
|
else
|
|
{
|
|
for( int i = 0; i < ARRAYSIZE(aSpecial); ++i )
|
|
{
|
|
if( c == aSpecial[i] )
|
|
{
|
|
*Note = i + 0x34;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
TextPos++;
|
|
Note++;
|
|
}
|
|
return TextPos - Text;
|
|
}
|
|
|
|
WORD ShowMemPakContent( const unsigned char * bMemPakBinary, HWND hListWindow )
|
|
{
|
|
BYTE bMemPakValid = MPAK_OK;
|
|
TCHAR szBuffer[40];
|
|
BYTE aNoteSizes[16];
|
|
bool bFirstChar;
|
|
|
|
|
|
LVITEM lvItem;
|
|
lvItem.mask = LVIF_TEXT | LVIF_PARAM;
|
|
lvItem.iItem = 0;
|
|
lvItem.iSubItem = 0;
|
|
lvItem.pszText = szBuffer;
|
|
|
|
int i = 0,
|
|
nNotes = 0,
|
|
iSum = 0,
|
|
iRemainingBlocks = 0;
|
|
|
|
for( i = 0x10A; i < 0x200; i++ )
|
|
iSum += bMemPakBinary[i];
|
|
|
|
|
|
if((( iSum % 256 ) == bMemPakBinary[0x101] ))
|
|
{
|
|
iRemainingBlocks = CountBlocks( bMemPakBinary, aNoteSizes );
|
|
|
|
if( iRemainingBlocks <= 123 )
|
|
{
|
|
for( lvItem.lParam = 0; lvItem.lParam < 16; lvItem.lParam++ )
|
|
{
|
|
|
|
if( bMemPakBinary[0x300 + (lvItem.lParam*32)] ||
|
|
bMemPakBinary[0x301 + (lvItem.lParam*32)] ||
|
|
bMemPakBinary[0x302 + (lvItem.lParam*32)] )
|
|
{
|
|
int iChars = TranslateNotes( &bMemPakBinary[0x300 + (lvItem.lParam*32) + 0x10], szBuffer, 16 );
|
|
|
|
if( TranslateNotes( &bMemPakBinary[0x300 + (lvItem.lParam*32) + 0x0C], &szBuffer[iChars + 1], 1 ) )
|
|
szBuffer[iChars] = _T('_');
|
|
|
|
bFirstChar = true;
|
|
for( i = 0; i < (int)lstrlen(szBuffer); i++ )
|
|
{
|
|
if( szBuffer[i] == ' ' )
|
|
bFirstChar = true;
|
|
else
|
|
{
|
|
if( bFirstChar && ( szBuffer[i] >= 'a') && ( szBuffer[i] <= 'z'))
|
|
{
|
|
bFirstChar = false;
|
|
szBuffer[i] -= 0x20;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
i = ListView_InsertItem( hListWindow, &lvItem );
|
|
|
|
switch( bMemPakBinary[0x303 + (lvItem.lParam*32)] )
|
|
{
|
|
case 0x00:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_NOREGION, szBuffer, 40 );
|
|
break;
|
|
case 0x37:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_BETA, szBuffer, 40 );
|
|
break;
|
|
case 0x41:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_NTSC, szBuffer, 40 );
|
|
break;
|
|
case 0x44:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_GERMANY, szBuffer, 40 );
|
|
break;
|
|
case 0x45:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_USA, szBuffer, 40 );
|
|
break;
|
|
case 0x46:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_FRANCE, szBuffer, 40 );
|
|
break;
|
|
case 0x49:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_ITALY, szBuffer, 40 );
|
|
break;
|
|
case 0x4A:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_JAPAN, szBuffer, 40 );
|
|
break;
|
|
case 0x50:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_EUROPE, szBuffer, 40 );
|
|
break;
|
|
case 0x53:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_SPAIN, szBuffer, 40 );
|
|
break;
|
|
case 0x55:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_AUSTRALIA, szBuffer, 40 );
|
|
break;
|
|
case 0x58:
|
|
case 0x59:
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_PAL, szBuffer, 40 );
|
|
break;
|
|
default:
|
|
{
|
|
TCHAR szTemp[40];
|
|
LoadString( g_hResourceDLL, IDS_P_MEM_UNKNOWNREGION, szTemp, 40 );
|
|
wsprintf( szBuffer, szTemp, bMemPakBinary[0x303 + (lvItem.lParam*32)] );
|
|
}
|
|
}
|
|
|
|
ListView_SetItemText( hListWindow, i, 1, szBuffer );
|
|
|
|
wsprintf( szBuffer, _T("%i"), aNoteSizes[lvItem.lParam] );
|
|
ListView_SetItemText( hListWindow, i, 2, szBuffer );
|
|
nNotes++;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
bMemPakValid = MPAK_DAMAGED;
|
|
|
|
}
|
|
else
|
|
bMemPakValid = MPAK_DAMAGED;
|
|
|
|
return MAKEWORD( (BYTE)iRemainingBlocks, bMemPakValid );
|
|
}
|
|
|
|
void HextoTextA( const unsigned char * Data, LPSTR szText, const int nBytes )
|
|
{
|
|
const char acValues[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
'A', 'B', 'C', 'D', 'E', 'F' };
|
|
|
|
for( int i = 0; i < nBytes; i++ )
|
|
{
|
|
BYTE byte = *Data;
|
|
szText[0] = acValues[(byte>>4) & 0x0F];
|
|
szText[1] = acValues[byte & 0x0F];
|
|
|
|
++Data;
|
|
szText+=2;
|
|
}
|
|
*szText = '\0';
|
|
}
|
|
|
|
// Used when reading in a note file, to convert text to binary (unserialize)
|
|
void TexttoHexA( LPCSTR szText, LPBYTE Data, const int nBytes )
|
|
{
|
|
bool fLowByte = false;
|
|
LPCSTR endText = szText + nBytes * 2;
|
|
|
|
for( ; szText < endText; ++szText )
|
|
{
|
|
BYTE bByte = 0;
|
|
|
|
if(( '0' <= *szText ) && ( *szText <= '9' ))
|
|
bByte = *szText - '0';
|
|
else
|
|
{
|
|
if(( 'A' <= *szText ) && ( *szText <= 'F' ))
|
|
bByte = szText[0] - 'A' + 10;
|
|
else if(( 'a' <= *szText ) && ( *szText <= 'f' ))
|
|
bByte = szText[0] - 'a' + 10;
|
|
}
|
|
|
|
if( !fLowByte )
|
|
*Data = bByte << 4;
|
|
else
|
|
{
|
|
*Data |= bByte;
|
|
++Data;
|
|
}
|
|
|
|
fLowByte = !fLowByte;
|
|
}
|
|
}
|
|
|
|
bool SaveNoteFileA( const unsigned char * aMemPak, const int iNote, LPCTSTR pszFileName )
|
|
{
|
|
BYTE aNoteSizes[16];
|
|
LPBYTE aNote;
|
|
bool bReturn = false;
|
|
if( CountBlocks( aMemPak, aNoteSizes ) > 123 )
|
|
return false;
|
|
|
|
aNote = (LPBYTE)P_malloc( aNoteSizes[iNote] * 0x100 + 32 );
|
|
if( !aNote )
|
|
return false;
|
|
|
|
CopyMemory( aNote, &aMemPak[0x300 + iNote * 32], 32 );
|
|
BYTE bNextIndex = aNote[0x7];
|
|
int iBlock = 0;
|
|
while( iBlock < aNoteSizes[iNote] )
|
|
{
|
|
CopyMemory( &aNote[32 + iBlock * 0x100], &aMemPak[bNextIndex * 0x100], 0x100);
|
|
bNextIndex = aMemPak[0x101 + (bNextIndex*2)];
|
|
|
|
iBlock++;
|
|
}
|
|
|
|
HANDLE hFile = CreateFile( pszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
|
|
if ( hFile != INVALID_HANDLE_VALUE )
|
|
{
|
|
SetFilePointer( hFile, 0L, NULL, FILE_BEGIN );
|
|
char szLine[70];
|
|
|
|
|
|
DWORD dwBytesWritten = 0;
|
|
lstrcpyA( szLine, "a64-notes\r\nDescription of game save goes here...\r\na64-data\r\n" );
|
|
WriteFile( hFile, szLine, lstrlenA(szLine), &dwBytesWritten, NULL );
|
|
|
|
CopyMemory( szLine, aNote, 4 );
|
|
szLine[4] = ' ';
|
|
szLine[5] = aNote[4];
|
|
szLine[6] = aNote[5];
|
|
szLine[7] = ' ';
|
|
HextoTextA( &aNote[8], &szLine[8], 2 );
|
|
|
|
int pos = 12;
|
|
szLine[pos++] = ' ';
|
|
szLine[pos++] = aNote[0x0A] + '0';
|
|
szLine[pos++] = ' ';
|
|
szLine[pos++] = '{';
|
|
|
|
pos += TranslateNotesA( &aNote[0x0C], &szLine[pos], 1 );
|
|
|
|
szLine[pos++] = '}';
|
|
szLine[pos++] = ' ';
|
|
szLine[pos++] = '{';
|
|
|
|
pos += TranslateNotesA( &aNote[0x10], &szLine[pos], 16 );
|
|
|
|
lstrcatA( szLine, "}\r\n" );
|
|
WriteFile( hFile, szLine, lstrlenA(szLine), &dwBytesWritten, NULL );
|
|
|
|
for( int i = 32; i < aNoteSizes[iNote] * 0x100 + 32; i += 32 )
|
|
{
|
|
HextoTextA( &aNote[i], szLine, 32 );
|
|
WriteFile( hFile, szLine, lstrlenA(szLine), &dwBytesWritten, NULL );
|
|
WriteFile( hFile, "\r\n", 2, &dwBytesWritten, NULL );
|
|
}
|
|
WriteFile( hFile, "a64-CRC\r\n", 9, &dwBytesWritten, NULL );
|
|
|
|
// TODO: insert CRC here
|
|
lstrcpynA( szLine, "00000000\r\n", 70 );
|
|
WriteFile( hFile, szLine, lstrlenA(szLine), &dwBytesWritten, NULL );
|
|
//
|
|
WriteFile( hFile, "a64-end\r\n", 9, &dwBytesWritten, NULL );
|
|
|
|
SetEndOfFile( hFile );
|
|
bReturn = true;
|
|
CloseHandle( hFile );
|
|
}
|
|
else
|
|
ErrorMessage( IDS_ERR_NOTEREAD, GetLastError(), false );
|
|
|
|
P_free( aNote );
|
|
return bReturn;
|
|
}
|
|
|
|
// Read a note from a file pszFileName (.a64 format), and insert it into the given memory pak
|
|
// Returns true on success, false otherwise
|
|
bool InsertNoteFile( LPBYTE aMemPak, LPCTSTR pszFileName )
|
|
{
|
|
// bool bReturn = false;
|
|
|
|
FILE* nFile = NULL;
|
|
|
|
if( (nFile = _tfopen(pszFileName, _T("r") ) ) != NULL )
|
|
{
|
|
char szLine[128];
|
|
fpos_t pDataStart;
|
|
|
|
while( fgets(szLine, sizeof(szLine) - 1, nFile) )
|
|
{
|
|
if( !strncmp( "a64-data", szLine, 8 ))
|
|
{
|
|
fgetpos(nFile, &pDataStart);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Assumes the file keeps going
|
|
// Discard the next line
|
|
fgets(szLine, sizeof(szLine) - 1, nFile); // Not really necessary to check for EOF, as it will fail gracefully when dwNoteSize is zero
|
|
|
|
DWORD dwNoteSize = 0;
|
|
while( fgets(szLine, sizeof(szLine) - 1, nFile) && strncmp( "a64-CRC", szLine, 7 ))
|
|
{
|
|
dwNoteSize++;
|
|
}
|
|
dwNoteSize /= 8;
|
|
|
|
BYTE aNoteSizes[16];
|
|
WORD wRemainingBlocks;
|
|
int i,
|
|
ifreeNote = -1;
|
|
|
|
wRemainingBlocks = CountBlocks( aMemPak, aNoteSizes );
|
|
|
|
if( dwNoteSize <= 0 )
|
|
{
|
|
ErrorMessage( IDS_ERR_NOTEREAD, 0, false );
|
|
fclose(nFile);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if( wRemainingBlocks < dwNoteSize )
|
|
{
|
|
ErrorMessage( IDS_ERR_MEMPAK_SPACE, 0, false );
|
|
fclose(nFile);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
i = 0;
|
|
while(( i < 16 ) && ( ifreeNote == -1 ))
|
|
{
|
|
if( aNoteSizes[i] == 0 )
|
|
ifreeNote = i;
|
|
i++;
|
|
}
|
|
if( ifreeNote == -1 )
|
|
{
|
|
ErrorMessage( IDS_ERR_MEMPAK_NONOTES, 0, false );
|
|
fclose(nFile);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Header start
|
|
|
|
// .a64 header should look something like this:
|
|
// NBCE 01 0203 0 {} {BLASTCORPS GAME}
|
|
// First 4 chars are the first 4 bytes
|
|
// Next 2 chars are the next 2 bytes
|
|
// Next 4 chars are bytes 8 and 9, in hex
|
|
// Next character is byte 10, in hex (but only one character this time)
|
|
// Now we've got two sets of braces...the first one contains byte 12 in encoded form (use ReverseNotesA)
|
|
// The second one should contain bytes 16 through 31 (ReverseNotesA)
|
|
|
|
BYTE *pBlock;
|
|
fsetpos(nFile, &pDataStart);
|
|
if (! fgets(szLine, sizeof(szLine) - 1, nFile) )
|
|
{
|
|
ErrorMessage( IDS_ERR_NOTEEOF, 0, false );
|
|
fclose(nFile);
|
|
return false;
|
|
}
|
|
|
|
szLine[strlen(szLine) - 1] = '\0'; // Remove newline
|
|
|
|
pBlock = &aMemPak[0x300 + ifreeNote*32];
|
|
CopyMemory( pBlock, szLine, 4 );
|
|
pBlock[4] = szLine[5];
|
|
pBlock[5] = szLine[6];
|
|
TexttoHexA( &szLine[8], &pBlock[8], 2 );
|
|
pBlock[10] = szLine[13] - '0';
|
|
|
|
int len = lstrlenA( szLine );
|
|
|
|
i = 16;
|
|
while((i < len) && ( szLine[i] != '}' ))
|
|
i++;
|
|
|
|
szLine[i] = '\0';
|
|
i += ReverseNotesA( &szLine[16], &pBlock[12] );
|
|
|
|
while((i < len) && ( szLine[i] != '{' ))
|
|
i++;
|
|
|
|
if(i < len)
|
|
{
|
|
int start = i+1;
|
|
while((i < len) && ( szLine[i] != '}' ))
|
|
i++;
|
|
if(i < len)
|
|
{
|
|
szLine[i] = '\0';
|
|
ReverseNotesA( &szLine[start], &pBlock[16] );
|
|
}
|
|
}
|
|
|
|
while((i < len) && ( szLine[i] != '}' ))
|
|
i++;
|
|
szLine[i] = '\0';
|
|
|
|
// Header end
|
|
|
|
BYTE bDataBlock = 5;
|
|
pBlock = &pBlock[7];
|
|
BYTE *pDataBlock;
|
|
|
|
while( dwNoteSize > 0 )
|
|
{
|
|
while( aMemPak[0x101 + bDataBlock*2] != 0x03 )
|
|
bDataBlock++;
|
|
*pBlock = bDataBlock;
|
|
pBlock = &aMemPak[0x101 + bDataBlock*2];
|
|
pDataBlock = &aMemPak[bDataBlock * 0x100];
|
|
for( i = 0; i < 0x100; i+=32 )
|
|
{
|
|
if (! fgets(szLine, sizeof(szLine) - 1, nFile) )
|
|
{
|
|
ErrorMessage( IDS_ERR_NOTEEOF, 0, false );
|
|
fclose(nFile);
|
|
return false;
|
|
}
|
|
|
|
szLine[strlen(szLine) - 1] = '\0'; // Remove newline
|
|
TexttoHexA( szLine, &pDataBlock[i], 32 );
|
|
}
|
|
bDataBlock++;
|
|
dwNoteSize--;
|
|
}
|
|
*pBlock = 0x01;
|
|
|
|
int iSum = 0;
|
|
|
|
for( i = 0x10A; i < 0x200; i++ )
|
|
iSum += aMemPak[i];
|
|
|
|
aMemPak[0x101] = iSum % 256;
|
|
|
|
CopyMemory( &aMemPak[0x200], &aMemPak[0x100], 0x100 );
|
|
|
|
fclose(nFile);
|
|
return true;
|
|
}
|
|
else
|
|
ErrorMessage( IDS_ERR_NOTEREAD, 0, false );
|
|
return false;
|
|
}
|
|
|
|
// Remove a memory pak "note"
|
|
// See "MemPak-Format.doc" for more info
|
|
bool RemoveNote( LPBYTE aMemPak, const int iNote )
|
|
{
|
|
BYTE bBlock = aMemPak[0x307 + iNote*32];
|
|
int iPos;
|
|
|
|
while( bBlock >= 0x05 )
|
|
{
|
|
iPos = 0x101 + bBlock*2;
|
|
bBlock = aMemPak[iPos];
|
|
aMemPak[iPos] = 0x03;
|
|
}
|
|
|
|
int i = 0, iSum = 0;
|
|
for( i = 0x10A; i < 0x200; i++ )
|
|
iSum += aMemPak[i];
|
|
|
|
aMemPak[0x101] = iSum % 256;
|
|
CopyMemory( &aMemPak[0x200], &aMemPak[0x100], 0x100 );
|
|
ZeroMemory( &aMemPak[0x300 + iNote*32], 32 );
|
|
return true;
|
|
}
|
|
|
|
BYTE AddressCRC( const unsigned char * Address )
|
|
{
|
|
bool HighBit;
|
|
WORD Data = MAKEWORD( Address[1], Address[0] );
|
|
register BYTE Remainder = ( Data >> 11 ) & 0x1F;
|
|
|
|
BYTE bBit = 5;
|
|
|
|
while( bBit < 16 )
|
|
{
|
|
HighBit = (Remainder & 0x10) != 0;
|
|
Remainder = (Remainder << 1) & 0x1E;
|
|
|
|
Remainder += ( bBit < 11 && Data & (0x8000 >> bBit )) ? 1 : 0;
|
|
Remainder ^= (HighBit) ? 0x15 : 0;
|
|
|
|
bBit++;
|
|
}
|
|
|
|
return Remainder;
|
|
}
|
|
|
|
BYTE DataCRC( const unsigned char * Data, const int iLength )
|
|
{
|
|
register BYTE Remainder = Data[0];
|
|
|
|
int iByte = 1;
|
|
BYTE bBit = 0;
|
|
|
|
while( iByte <= iLength )
|
|
{
|
|
bool HighBit = ((Remainder & 0x80) != 0);
|
|
Remainder = Remainder << 1;
|
|
|
|
Remainder += ( iByte < iLength && Data[iByte] & (0x80 >> bBit )) ? 1 : 0;
|
|
|
|
Remainder ^= (HighBit) ? 0x85 : 0;
|
|
|
|
bBit++;
|
|
iByte += bBit/8;
|
|
bBit %= 8;
|
|
}
|
|
|
|
return Remainder;
|
|
}
|
|
|
|
VOID CALLBACK WritebackProc( HWND hWnd, UINT msg, UINT_PTR idEvent, DWORD dwTime )
|
|
{
|
|
KillTimer(hWnd, idEvent); // Timer killed
|
|
|
|
switch (idEvent)
|
|
{
|
|
case PAK_MEM:
|
|
DebugWriteA("Memory pak: WritebackProc flushed file writes\n");
|
|
for( int i = 0; i < 4; i++ )
|
|
{
|
|
MEMPAK *mPak = (MEMPAK*)g_pcControllers[i].pPakData;
|
|
|
|
if ( mPak && mPak->bPakType == PAK_MEM && !mPak->fReadonly && mPak->hMemPakHandle != NULL )
|
|
FlushViewOfFile( mPak->aMemPakData, PAK_MEM_SIZE );
|
|
}
|
|
return;
|
|
case PAK_TRANSFER:
|
|
DebugWriteA("Transfer pak: WritebackProc flushed file writes\n");
|
|
for( int i = 0; i < 4; i++ )
|
|
{
|
|
LPTRANSFERPAK tPak = (LPTRANSFERPAK)g_pcControllers[i].pPakData;
|
|
|
|
if (tPak && tPak->bPakType == PAK_TRANSFER && tPak->bPakInserted && tPak->gbCart.hRamFile != NULL )
|
|
FlushViewOfFile( tPak->gbCart.RamData, (tPak->gbCart.RomData[0x149] == 1 ) ? 0x0800 : tPak->gbCart.iNumRamBanks * 0x2000);
|
|
}
|
|
return;
|
|
}
|
|
}
|