/*
    Simple utility to dump a the registry on a Windows CE device
    to a desktop file via the CESH remote file I/O APIs
*/
#include <windows.h>
#include "ceshio.h"

/*
    print a debug message to the CESH console on the desktop machine
*/
void DbgOut(LPSTR psz)
{
    int fh = U_ropen(L"con", _O_CREAT | _O_WRONLY | _O_TRUNC );

    U_rwrite(fh, (PBYTE)psz, strlen(psz));

    U_rclose(fh);

}


/*
    Dump a single registry value by type
*/

#define LONIBBLE(x)  ((x) & 0x0f)
#define HINIBBLE(x)  (((x) & 0xf0) >> 4)

BOOL DumpRegValue(HKEY hkey, int nIndentSize, LPSTR pszValueName, 
                  DWORD dwType, PBYTE pbValueData, DWORD dwDataSize, 
                  int fh) 
{
    while ( nIndentSize-- )
        U_rwrite(fh, (PBYTE)" ", 1);

    U_rwrite(fh,(PBYTE)pszValueName, strlen(pszValueName));

    LPSTR pszType;
    LPSTR pszData = NULL;

    switch( dwType )
    {
        case REG_BINARY:
            {
                pszType = "(BINARY)";

                const char *pcszHex = "0123456789abcdef";
                pszData = (LPSTR)malloc((dwDataSize * 3) + 1);

                for ( DWORD i = 0; i < dwDataSize; i++ )
                {
                 pszData[i*3] = pcszHex[HINIBBLE(pbValueData[i])];
                 pszData[i*3+1] = pcszHex[LONIBBLE(pbValueData[i])];
                 pszData[i*3+2] = ' ';
                }
                pszData[dwDataSize*3] = 0;
            }
            break;

        case REG_DWORD_BIG_ENDIAN:
        case REG_DWORD: // same value as REG_DWORD_LITTLE_ENDIAN
            {
                if ( dwType == REG_DWORD_BIG_ENDIAN)
                    pszType = "(DWORD_BIG_ENDIAN)";
                else
                    pszType = "(DWORD)";

                wchar_t wchBuf[16];
                pszData = (LPSTR)malloc(16);
                
                wsprintf(wchBuf, _T("0x%x"), *(DWORD*)pbValueData);
                wcstombs(pszData, wchBuf, wcslen(wchBuf)+1);
            }
            break;

        case REG_MULTI_SZ:
            {
                pszType = "(MULTI_SZ)";

                LPWSTR pwsz = (LPWSTR)pbValueData;
                pszData = (LPSTR)malloc(dwDataSize+1);
                pszData[0] = 0;

                LPSTR pszIter = pszData;
                while ( *pwsz )
                {
                    wcstombs(pszIter, pwsz, wcslen(pwsz)+1);
                    pwsz += wcslen(pwsz) + 1;

                    if ( *pwsz )
                    {
                        strcat(pszIter, "\\0");
                        // make sure we overwrite 
                        // the previous zero terminator
                        pszIter += strlen(pszIter);  
                    }
                    else
                        strcat(pszIter, "\\0\\0");
                }
            }
            break;

        case REG_EXPAND_SZ:
        case REG_LINK:
        case REG_RESOURCE_LIST:
        case REG_SZ:
            {
                if ( dwType == REG_EXPAND_SZ )
                    pszType = "(EXPAND_SZ)";
                else if ( dwType == REG_LINK )
                    pszType = "(LINK)";
                else if ( dwType == REG_RESOURCE_LIST )
                    pszType = "(RESOURCE_LIST)";
                else
                    pszType = "(SZ)";


                if ( dwDataSize && pbValueData )
                {
                    //NOTE: have to handle specially be 
                    // cause string is not zero terminated?
                    LPWSTR pwsz = (LPWSTR)pbValueData;
                    int numchars = (dwDataSize/2)+1;
                    pszData = (LPSTR)malloc(numchars);
                    wcstombs(pszData, pwsz, numchars);
                    pszData[numchars-1] = 0;
                }
            }
            break;

        case REG_NONE:
            pszType = "(NONE)";
            break;

        default:
            pszType = "(UNKNOWN)";
            break;
    }


    U_rwrite(fh,(PBYTE)": ", 2);
    U_rwrite(fh, (PBYTE)pszType, 
             strlen(pszType));

    if ( pszData )
    {
        U_rwrite(fh,(PBYTE)" ", 1);
        U_rwrite(fh,(PBYTE)pszData, strlen(pszData));

        free(pszData);
    }

    U_rwrite(fh,(PBYTE)"\r\n", 2);


    return TRUE;
}

/*
    Dump reg key values and recursively dump all subkeys
*/
BOOL DumpRegKey(HKEY hkey, int fh)
{
    BOOL bSuccess = FALSE;
    DWORD dwMaxSubkeys;
    DWORD dwMaxValues;
    DWORD dwMaxSubkeyLen;
    DWORD dwMaxValueNameLen;
    DWORD dwMaxValueLen;

    static int s_iCurIndent = 0;

    if ( RegQueryInfoKey(hkey, NULL, NULL, NULL, 
                         &dwMaxSubkeys,&dwMaxSubkeyLen, 
                         NULL, &dwMaxValues,&dwMaxValueNameLen, 
                         &dwMaxValueLen,NULL,NULL) == ERROR_SUCCESS )
    {

        // dump this key's values...
        DWORD dwValueNameLen = (dwMaxValueNameLen+1)*sizeof(wchar_t);
        LPTSTR pszValueName = (LPTSTR)malloc(dwValueNameLen);
        LPSTR pszValueNameA = (LPSTR)malloc(dwValueNameLen);
        PBYTE pbValueData = (PBYTE)malloc(dwMaxValueLen);
        DWORD       dwIndex = 0;

        while ( dwIndex < dwMaxValues )
        {
            DWORD dwSize = dwMaxValueNameLen+1;
            DWORD dwDataSize = dwMaxValueLen;
            DWORD dwType;

            if ( RegEnumValue(hkey, dwIndex, 
                   pszValueName, &dwSize, 
                   NULL, &dwType, 
                   pbValueData, &dwDataSize) != ERROR_SUCCESS )
                  break;

            wcstombs(pszValueNameA, 
                     pszValueName, 
                     wcslen(pszValueName)+1);

            if ( !DumpRegValue(hkey, s_iCurIndent, pszValueNameA, 
                               dwType, pbValueData, dwDataSize, fh) )
                break;

            dwIndex++;
        }


        free(pszValueName);
        free(pszValueNameA);
        free(pbValueData);

        // only succeeded if we did all the values
        bSuccess = ( dwIndex == dwMaxValues );

        if ( bSuccess )
        {
            // ...then enumerate other keys
            DWORD dwKeyNameLen = (dwMaxSubkeyLen+1)*sizeof(wchar_t);
            LPTSTR pszKeyName = (LPTSTR)malloc(dwKeyNameLen);
            LPSTR pszKeyNameA = (LPSTR)malloc(dwKeyNameLen);


            dwIndex = 0;
            while ( dwIndex < dwMaxSubkeys )
            {
                DWORD dwSize = dwMaxSubkeyLen+1;

                if ( RegEnumKeyEx(hkey, dwIndex, 
                                  pszKeyName, &dwSize, 
                                  NULL, NULL, 
                                  NULL, NULL) != ERROR_SUCCESS )
                    break;

                strcpy(pszKeyNameA, "[");
                wcstombs(pszKeyNameA+1, 
                         pszKeyName, 
                         wcslen(pszKeyName)+1);
                strcat(pszKeyNameA,"]\r\n");

                if ( s_iCurIndent )
                {
                    int i = s_iCurIndent;

                    while ( i-- )
                        U_rwrite(fh, (PBYTE)" ", 1);
                }

                if ( U_rwrite(fh, (PBYTE)pszKeyNameA, 
                     strlen(pszKeyNameA))!=(int)strlen(pszKeyNameA) )
                    break;

                // recurse into the key if we can open it
                HKEY hSubkey;
                if ( RegOpenKeyEx(hkey, pszKeyName, 
                                  0,0,&hSubkey) == ERROR_SUCCESS )
                {
                    s_iCurIndent += 4;
                    BOOL b = DumpRegKey(hSubkey, fh);
                    s_iCurIndent -= 4;
                    RegCloseKey(hSubkey);

                    if ( !b )
                        break;
                }
                else
                {
                    DbgOut("ERROR: could not open registry key '");
                    DbgOut(pszKeyNameA);
                    DbgOut("'\n");
                }

                dwIndex++;
            }

            free(pszKeyName);
            free(pszKeyNameA);

            // only succeeded if we did all the keys
            bSuccess = ( dwIndex == dwMaxSubkeys );
        }
    }

    // do an extra crlf if we're finishing the top-level key
    if ( !s_iCurIndent )
        U_rwrite(fh, (PBYTE)"\r\n", 2);

    return bSuccess;
}

int WINAPI WinMain (HINSTANCE hInstance, 
                    HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, 
                    int nCmdShow)
{
    int fh;

    fh = U_ropen(L"regdump.txt", _O_CREAT | _O_WRONLY | _O_TRUNC );

    if ( fh == -1 )
    {
        DbgOut("error creating file\r\n");
    }
    else
    {

#define RKENTRY(x)  { #x "\r\n", strlen(#x) + 2, x }

        struct RKEntry
        {
            LPSTR psz;
            int len;
            HKEY hkey;
        } RegKeys[] = { RKENTRY( HKEY_LOCAL_MACHINE ),
                        RKENTRY( HKEY_CURRENT_USER ),
                        RKENTRY( HKEY_USERS ),
                        RKENTRY( HKEY_CLASSES_ROOT ),
                       };
        int nEntries = sizeof(RegKeys)/sizeof(RKEntry);

        // iterate the list of top level reg keys and dump each one
        for ( int i = 0; i < nEntries; i++ )
        {
            if ( (U_rwrite(fh, (PBYTE)RegKeys[i].psz, 
                           RegKeys[i].len) != RegKeys[i].len) ||
                 !DumpRegKey(RegKeys[i].hkey, fh) )
            {
                DbgOut("ERROR dumping key\r\n");
            }
        }

        U_rclose(fh);
    }

    return 0;

}