/*****************************************************************************
 *
 *  CAssert.c
 *
 *  Copyright (c) 1996 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      Assertions and debug output routines
 *      Based on Raymond's assertion code as it looks quite useful
 *
 *
 *  Contents:
 *
 *      DebugOutPtszV
 *      AssertPtszPtszLn
 *      ArgsPalPszV
 *      EnterDbgflPszPal
 *      ExitDbgflPalHresPpv
 *
 *****************************************************************************/

/*
#include <windows.h>
#include <windowsx.h>
#include <objbase.h>
#include <regstr.h>
#include <setupapi.h>
#include <cfgmgr32.h>
#include <devguid.h>
#include <stdio.h>

#include <stilog.h>
#include <stiregi.h>

#include <sti.h>
#include <stierr.h>
#include <stiusd.h>
#include "wia.h"
#include "stipriv.h"
#include "stiapi.h"
#include "stirc.h"
#include "debug.h"
*/
#include "sticomm.h"

#ifdef MAXDEBUG

/*****************************************************************************
 *
 *      WarnPszV
 *
 *      Display a message, suitable for framing.
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

CHAR c_szPrefix[] = "STI: ";

#pragma END_CONST_DATA

void EXTERNAL
WarnPszV(LPCSTR psz, ...)
{
    va_list ap;
    CHAR sz[1024];

    lstrcpyA(sz, c_szPrefix);
    va_start(ap, psz);
    wvsprintfA(sz + cA(c_szPrefix) - 1, psz, ap);
    va_end(ap);
    lstrcatA(sz, "\r\n");
    OutputDebugStringA(sz);
}

#endif

#ifdef DEBUG

/*****************************************************************************
 *
 *      Globals
 *
 *****************************************************************************/

DBGFL DbgflCur;

TCHAR g_tszLogFile[MAX_PATH] = {'\0'};

/*****************************************************************************
 *
 * Set current trace parameters
 *
 * This routine is not thread safe
 *
 *****************************************************************************/


VOID  InitializeDebuggingSupport(VOID)
{

    HKEY    hStiSettingsKey;
    DWORD   dwErr;
    DWORD   dwType;

    CHAR    szLogFile[MAX_PATH];
    UINT    cbBuffer ;

    DBGFL   dwFlags = 0;

    dwErr = OSUtil_RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                        REGSTR_PATH_STICONTROL_W,
                        0L,KEY_READ,
                        &hStiSettingsKey
                        );

    if (NOERROR != dwErr) {
        return;
    }

    cbBuffer = MAX_PATH;
    ZeroX(szLogFile);

    dwErr = RegQueryValueExA( hStiSettingsKey,
                           REGSTR_VAL_DEBUG_FILE_A,
                           NULL,
                           &dwType,
                           (LPBYTE)szLogFile,
                           &cbBuffer );

    if( ( dwErr == NO_ERROR ) || ( dwErr == ERROR_MORE_DATA ) ) {
        if( ( dwType != REG_SZ ) &&
            ( dwType != REG_MULTI_SZ ) &&
            ( dwType != REG_EXPAND_SZ ) ) {

            dwErr = ERROR_FILE_NOT_FOUND;
        }
        else {
            SetDebugLogFileA(szLogFile);
        }
    }

    dwFlags = ReadRegistryDwordW(hStiSettingsKey,
                                      REGSTR_VAL_DEBUG_FLAGS_W,
                                      0L);

    SetCurrentDebugFlags(dwFlags ) ;

    RegCloseKey(hStiSettingsKey);

    return ;

}

DBGFL   SetCurrentDebugFlags(DBGFL NewFlags) {

    DBGFL   OldFlags = DbgflCur;
    DbgflCur = NewFlags;
    return OldFlags;
}

VOID    SetDebugLogFileA(CHAR *pszLogFileName)
{
    if (!pszLogFileName || !*pszLogFileName) {
        *g_tszLogFile = '\0';
        return;
    }

    lstrcpyA((CHAR*)g_tszLogFile,pszLogFileName);
    return;
}

/*****************************************************************************
 *
 *      DebugOutPtsz
 *
 *      Writes a message to the debugger and maybe a log file.
 *
 *****************************************************************************/

void INTERNAL
DebugOutPtsz(LPCTSTR ptsz)
{
    DWORD   cbWritten;

    OutputDebugString(ptsz);
    if (g_tszLogFile[0]) {
        HANDLE h = CreateFile(g_tszLogFile, GENERIC_WRITE,
                              FILE_SHARE_READ | FILE_SHARE_WRITE,
                              0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        if (h != INVALID_HANDLE_VALUE) {
#ifdef UNICODE
            CHAR szBuf[1024];
#endif
            SetFilePointer(h, 0, 0, FILE_END);
#ifdef UNICODE
            WriteFile(h, szBuf, UToA(szBuf, cA(szBuf), ptsz),&cbWritten,NULL);
#else
            WriteFile(h, ptsz, cbCtch(lstrlen(ptsz)),&cbWritten,NULL);
#endif
            CloseHandle(h);
        }
    }
}

/*****************************************************************************
 *
 *      DebugOutPtszA
 *
 *      DebugOut an ANSI message to the debugger and maybe a log file.
 *
 *****************************************************************************/

#ifdef UNICODE

void INTERNAL
DebugOutPtszA(LPCSTR psz)
{
    OutputDebugStringA(psz);
    if (g_tszLogFile[0]) {
        HANDLE h = CreateFile(g_tszLogFile, GENERIC_WRITE,
                              FILE_SHARE_READ | FILE_SHARE_WRITE,
                              0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        if (h != INVALID_HANDLE_VALUE) {
            _lwrite((HFILE)(LONG_PTR)h, psz, cbCch(lstrlenA(psz)));
            CloseHandle(h);
        }
    }
}

#else

#define DebugOutPtszA                 DebugOutPtsz

#endif

/*****************************************************************************
 *
 *      DebugOutPtszV
 *
 *      DebugOut a message with a trailing crlf.
 *
 *****************************************************************************/

void EXTERNAL
DebugOutPtszV(DBGFL Dbgfl, LPCTSTR ptsz, ...)
{
    if (Dbgfl == 0 || (Dbgfl & DbgflCur)) {
        va_list ap;
        TCHAR tsz[1024];
        va_start(ap, ptsz);
        wvsprintf(tsz, ptsz, ap);
        va_end(ap);
        lstrcat(tsz, TEXT("\r\n"));
        DebugOutPtsz(tsz);
    }
}

/*****************************************************************************
 *
 *      AssertPtszPtszLn
 *
 *      Something bad happened.
 *
 *****************************************************************************/

int EXTERNAL
AssertPtszPtszLn(LPCTSTR ptszExpr, LPCTSTR ptszFile, int iLine)
{
    DebugOutPtszV(DbgFlAlways, TEXT("Assertion failed: `%s' at %s(%d)"),
                    ptszExpr, ptszFile, iLine);
    DebugBreak();
    return 0;
}

/*****************************************************************************
 *
 *      Procedure call tracing is gross because of the C preprocessor.
 *
 *      Oh, if only we had support for m4...
 *
 *****************************************************************************/

/*****************************************************************************
 *
 *      ArgsPszV
 *
 *      Collect arguments to a procedure.
 *
 *      psz -> ASCIIZ format string
 *      ... = argument list
 *
 *      The characters in the format string are listed in EmitPal.
 *
 *****************************************************************************/

void EXTERNAL
ArgsPalPszV(PARGLIST pal, LPCSTR psz, ...)
{
    va_list ap;
    va_start(ap, psz);
    if (psz) {
        PPV ppv;
        pal->pszFormat = psz;
        for (ppv = pal->rgpv; *psz; psz++) {
            *ppv++ = va_arg(ap, PV);
        }
    } else {
        pal->pszFormat = "";
    }
}

/*****************************************************************************
 *
 *      EmitPal
 *
 *      OutputDebugString the information, given a pal.  No trailing
 *      carriage return is emitted.
 *
 *      pal      -> place where info was saved
 *
 *      Format characters:
 *
 *      p   - 32-bit flat pointer
 *      x   - 32-bit hex integer
 *      s   - TCHAR string
 *      A   - ANSI string
 *      W   - UNICODE string
 *      G   - GUID
 *      u   - unsigned integer
 *      C   - clipboard format
 *
 *****************************************************************************/

void INTERNAL
EmitPal(PARGLIST pal)
{
    char sz[MAX_PATH];
    int i;
    DebugOutPtszA(pal->pszProc);
    DebugOutPtsz(TEXT("("));
    for (i = 0; pal->pszFormat[i]; i++) {
        if (i) {
            DebugOutPtsz(TEXT(", "));
        }
        switch (pal->pszFormat[i]) {

        case 'p':                               /* 32-bit flat pointer */
        case 'x':                               /* 32-bit hex */
            wsprintfA(sz, "%08x", pal->rgpv[i]);
            DebugOutPtszA(sz);
            break;

        case 's':                               /* TCHAR string */
            if (pal->rgpv[i]) {
                DebugOutPtsz(pal->rgpv[i]);
            }
            break;

        case 'A':                               /* ANSI string */
            if (pal->rgpv[i]) {
                DebugOutPtszA(pal->rgpv[i]);
            }
            break;

        case 'W':                               /* UNICODE string */
#ifdef  UNICODE
            OutputDebugStringW(pal->rgpv[i]);
#else
            WideCharToMultiByte(CP_ACP, 0, pal->rgpv[i], -1, sz, cA(sz), 0, 0);
            DebugOutPtszA(sz);
#endif
            break;

#ifndef _WIN64
            //
            // Ignore this option on SunDown
            //
        case 'G':                               /* GUID */
            wsprintfA(sz, "%08x",
                      HIWORD(pal->rgpv[i]) ? *(LPDWORD)pal->rgpv[i]
                                           : (DWORD)pal->rgpv[i]);
            DebugOutPtszA(sz);
            break;

            case 'C':
                if (GetClipboardFormatNameA((UINT)pal->rgpv[i], sz, cA(sz))) {
                } else {
                    wsprintfA(sz, "[%04x]", pal->rgpv[i]);
                }
                DebugOutPtszA(sz);
                break;
#endif

        case 'u':                               /* 32-bit unsigned decimal */
            wsprintfA(sz, "%u", pal->rgpv[i]);
            DebugOutPtszA(sz);
            break;


        default: AssertF(0);                    /* Invalid */
        }
    }
    DebugOutPtsz(TEXT(")"));
}

/*****************************************************************************
 *
 *      EnterDbgflPtsz
 *
 *      Mark entry to a procedure.  Arguments were already collected by
 *      ArgsPszV.
 *
 *      Dbgfl     -> DebugOuty flags
 *      pszProc  -> procedure name
 *      pal      -> place to save the name and get the format/args
 *
 *****************************************************************************/

void EXTERNAL
EnterDbgflPszPal(DBGFL Dbgfl, LPCSTR pszProc, PARGLIST pal)
{
    pal->pszProc = pszProc;
    if (Dbgfl == 0 || (Dbgfl & DbgflCur)) {
        EmitPal(pal);
        DebugOutPtsz(TEXT("\r\n"));
    }
}

/*****************************************************************************
 *
 *      ExitDbgflPalHresPpv
 *
 *      Mark exit from a procedure.
 *
 *      pal      -> argument list
 *      hres     -> exit result
 *      ppv      -> optional OUT pointer;
 *                  ppvDword means that hres is a dword
 *                  ppvBool  means that hres is a boolean
 *                  ppvVoid  means that hres is nothing at all
 *
 *****************************************************************************/

void EXTERNAL
ExitDbgflPalHresPpv(DBGFL Dbgfl, PARGLIST pal, HRESULT hres, PPV ppvObj)
{
    BOOL fInternalError;
    DWORD le = GetLastError();

    fInternalError = 0;
    if (ppvObj == ppvVoid) {
    } else if (ppvObj == ppvBool) {
        if (hres == 0) {
            Dbgfl |= DbgFlError;
        }
    } else {
        if (FAILED(hres)) {
            if (fLimpFF(ppvObj && !IsBadWritePtr(ppvObj, cbX(*ppvObj)),
                        *ppvObj == 0)) {
            } else {
                fInternalError = 1;
            }
            Dbgfl |= DbgFlError;
        }
    }

    if (Dbgfl == 0 || (Dbgfl & DbgflCur) || fInternalError) {
        EmitPal(pal);
        DebugOutPtsz(TEXT(" -> "));
        if (ppvObj != ppvVoid) {
            TCHAR tszBuf[32];
            wsprintf(tszBuf, TEXT("%08x"), hres);
            DebugOutPtsz(tszBuf);
            if (HIWORD(PtrToLong(ppvObj))) {
                wsprintf(tszBuf, TEXT(" [%08x]"), *ppvObj);
                DebugOutPtsz(tszBuf);
            } else if (ppvObj == ppvDword) {
                wsprintf(tszBuf, TEXT(" [%08x]"), hres);
                DebugOutPtsz(tszBuf);
            } else if (ppvObj == ppvBool) {
                wsprintf(tszBuf, hres ? TEXT(" OK ") :
                                 TEXT(" le=[%d]"), le);
                DebugOutPtsz(tszBuf);
            }
        }
        DebugOutPtsz(TEXT("\r\n"));
        AssertF(!fInternalError);
    }

    /*
     *  This redundant test prevents a breakpoint on SetLastError()
     *  from being hit constantly.
     */
    if (le != GetLastError()) {
        SetLastError(le);
    }
}

#endif

#ifdef MAXDEBUG

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   DWORD | Random |
 *
 *          Returns a pseudorandom dword.  The value doesn't need to be
 *          statistically wonderful.
 *
 *  @returns
 *          A not very random dword.
 *
 *****************************************************************************/

DWORD s_dwRandom = 1;                   /* Random number seed */

DWORD INLINE
Random(void)
{
    s_dwRandom = s_dwRandom * 214013 + 2531011;
    return s_dwRandom;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | ScrambleBuf |
 *
 *          Fill a buffer with garbage.  Used in RDEBUG to make sure
 *          the caller is not relying on buffer data.
 *
 *          Note: If the buffer is not a multiple of dwords in size,
 *          the leftover bytes are not touched.
 *
 *  @parm   OUT LPVOID | pv |
 *
 *          The buffer to be scrambled.
 *
 *  @parm   UINT | cb |
 *
 *          The size of the buffer.
 *
 *****************************************************************************/

void EXTERNAL
ScrambleBuf(LPVOID pv, UINT cb)
{
    UINT idw;
    UINT cdw = cb / 4;
    LPDWORD pdw = pv;
    for (idw = 0; idw < cdw; idw++) {
        pdw[idw] = Random();
    }
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | ScrambleBit |
 *
 *          Randomly set or clear a bit.
 *
 *  @parm   OUT LPDWORD | pdw |
 *
 *          The dword whose bit is to be set randomly.
 *
 *  @parm   UINT | flMask |
 *
 *          Mask for the bits to scramble.
 *
 *****************************************************************************/

void EXTERNAL ScrambleBit(LPDWORD pdw, DWORD flMask)
{
    *pdw ^= (*pdw ^ Random()) & flMask;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   BOOL | Callback_CompareContexts |
 *
 *          Check if two <t CONTEXT> structures are substantially the same
 *          to the extent required by the Win32 calling convention.
 *
 *          This is necessary because lots of applications pass
 *          incorrectly prototyped functions as callbacks.  Others will
 *          write callback functions that trash registers that are
 *          supposed to be nonvolatile.  Yuck!
 *
 *          NOTE!  Platform-dependent code!
 *
 *  @parm   LPCONTEXT | pctx1 |
 *
 *          Context structure before we call the callback.
 *
 *  @parm   LPCONTEXT | pctx2 |
 *
 *          Context structure after we call the callback.
 *
 *  @returns
 *
 *          Nonzero if the two contexts are substantially the same.
 *
 *****************************************************************************/

BOOL INLINE
Callback_CompareContexts(LPCONTEXT pctx1, LPCONTEXT pctx2)
{
#if defined(_X86_)
    return pctx1->Esp == pctx2->Esp;            /* Stack pointer */
  #if 0
    /*
     *  Can't test these registers because Win95 doesn't preserve
     *  them properly.  GetThreadContext() stashes what happens to
     *  be in the registers when you finally reach the bowels of
     *  kernel, at which point who knows what they contain...
     */
           pctx1->Ebx == pctx2->Ebx &&          /* Nonvolatile registers */
           pctx1->Esi == pctx2->Esi &&
           pctx1->Edi == pctx2->Edi &&
           pctx1->Ebp == pctx2->Ebp;
  #endif

#elif defined(_ALPHA_)
    return pctx1->IntSp == pctx2->IntSp &&      /* Stack pointer */
           pctx1->IntS0 == pctx2->IntS0 &&      /* Nonvolatile registers */
           pctx1->IntS1 == pctx2->IntS1 &&
           pctx1->IntS2 == pctx2->IntS2 &&
           pctx1->IntS3 == pctx2->IntS3 &&
           pctx1->IntS4 == pctx2->IntS4 &&
           pctx1->IntS5 == pctx2->IntS5 &&
           pctx1->IntFp == pctx2->IntFp;

#elif defined(_MIPS_)
    #pragma message("I hope this is correct for MIPS")
    return pctx1->IntSp == pctx2->IntSp &&      /* Stack pointer */
           pctx1->IntS0 == pctx2->IntS0 &&      /* Nonvolatile registers */
           pctx1->IntS1 == pctx2->IntS1 &&
           pctx1->IntS2 == pctx2->IntS2 &&
           pctx1->IntS3 == pctx2->IntS3 &&
           pctx1->IntS4 == pctx2->IntS4 &&
           pctx1->IntS6 == pctx2->IntS6 &&
           pctx1->IntS7 == pctx2->IntS7 &&
           pctx1->IntS8 == pctx2->IntS8;

#elif defined(_PPC_)
    #pragma message("I don't know what the PPC calling conventions are")

    /* Just check the stack register */
    return pctx1->Gpr1 == pctx2->Gpr1;

#else
    #pragma message("I don't know what the calling conventions are for this platform")
    return 1;
#endif
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   BOOL | Callback |
 *
 *          Perform a callback the paranoid way, checking that the
 *          application used the correct calling convention and preserved
 *          all nonvolatile registers.
 *
 *          NOTE!  Platform-dependent code!
 *
 *  @parm   STICALLBACKPROC | pfn |
 *
 *          Procedure to call back.
 *
 *  @parm   PV | pv1 |
 *
 *          First parameter to callback.
 *
 *  @parm   PV | pv2 |
 *
 *          Second parameter to callback.
 *
 *  @returns
 *
 *          Whatever the callback returns.
 *
 *****************************************************************************/

BOOL EXTERNAL
Callback(STICALLBACKPROC pfn, PV pv1, PV pv2)
{
    CONTEXT ctxPre;             /* Thread context before call */
    CONTEXT ctxPost;            /* Thread context after call */
    volatile BOOL fRc;          /* To prevent compiler from enregistering */

    /* Get state of registers before the callback */
    ctxPre.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
    GetThreadContext(GetCurrentThread(), &ctxPre);

    fRc = pfn(pv1, pv2);

    ctxPost.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
    if (GetThreadContext(GetCurrentThread(), &ctxPost) &&
        !Callback_CompareContexts(&ctxPre, &ctxPost)) {
        RPF("STI: Incorrectly prototyped callback! Crash soon!");
        ValidationException();
    }

    return fRc;
}

#endif