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