/*  cmdkeyb.c - Keyboard layout support routines
 *
 *
 *  Modification History:
 *
 *  YST 14-Jan_1993 Created
 *
 *  08-Sept-1998, williamh, add third-party KDF support.
 */

#include "cmd.h"
#include <winconp.h>
#include <cmdsvc.h>
#include <softpc.h>
#include <mvdm.h>
#include <ctype.h>
#include <string.H>
#include "cmdkeyb.h"
#include <winnls.h>

CHAR szPrev[5] = "US";
INT  iPrevCP = 437;
CHAR szPrevKbdID[8] = "";

extern BOOL bPifFastPaste;

/************************************************************************\
*
*  FUNCTION:	VOID cmdGetKbdLayout( VOID )
*
*  Input	Client (DX) = 0 - Keyb.com not installed
*			      1 - Keyb.com installed
*		Client (DS:SI) = pointer where exe name has to be placed
*		Client (DS:CX) = pointer where command options are placed
*
*  Output
*	Success (DX = 1 )
*		Client (DS:SI) = Keyb.com execuatable string
*		Client (DS:CX) = command options
*
*	Failure (DX = 0)
*
*  COMMENTS:    This function check KEYBOARD ID for Win session
*		and if ID != US then return lines with
*               filename and options to COMMAND.COM
*
*               If bPifFastPaste is FALSE, then we always run kb16
*               for all keyboard ID including US, to give us a more
*               bios compatible Int 9 handler. 10-Jun-1993 Jonle
*
*
*  HISTORY:     01/05/93 YSt Created.
*
\************************************************************************/

VOID cmdGetKbdLayout( VOID )
{
  INT  iSize,iSaveSize;
  CHAR szKeybCode[12];
  CHAR szDir[MAX_PATH+15];
  CHAR szBuf[28];
  CHAR szNewKbdID[8];
  CHAR szAutoLine[MAX_PATH+40];
  CHAR szKDF[MAX_PATH];
  PCHAR pVDMKeyb;
  INT  iKeyb;
  HKEY	 hKey;
  HKEY	 hKeyLayout;
  DWORD  dwType;
  DWORD  retCode;
  INT	 iNewCP;
  DWORD  cbData;
  WORD	 KeybID;
  OFSTRUCT  ofstr;
  LANGID LcId = GetSystemDefaultLangID();
  int	 keytype;


#if defined(NEC_98) 
    setDX(0); 
    return;  
#endif // NEC_98

// Get information about 16 bit KEYB.COM from VDM
   iKeyb = getDX();


// The whole logic here is to decide:
// (1). if we have to run kb16.com at all.
// (2). if we have to run kb16.com, what parameters we should pass along,
//	such as keyboard id, language id, code page id and kdf file name.
// We do not load kb16.com at all if one of the following
// conditions is met:
// (1). We can not find the console keyboard layout id.
// (2). The console keyvoard layout id is US and kb16.com is not loaded
//	and fast paste is disabled.
// (3). We can not get the dos keyboard id/dos key code.
// (4). The new (language id, keyboard id, code page id) is the same
//	as the one we loaded previously.
// (5). we can not find kb16.com.
// (6). we can not find the kdf file that supports the
//	(language id, keyboard id, code page id) combination.
//
// If everything goes as planned, we end up with a command
// contains kb16.com fully qualified name and a command line contains
// appropriate parameters to kb16.com
//

    if (LcId == MAKELANGID(LANG_JAPANESE,SUBLANG_DEFAULT)) {
        // JAPAN build. Language id is always "JP" and code page is either 932
        // or 437 depends on keyboard type.
            iNewCP = 437;
            if (7 == GetKeyboardType(0))
            {
	        keytype = GetKeyboardType(1);
                if (keytype == 1 || keytype == 2 || keytype == 3 || (keytype & 0xff00) == 0x1200)
	            iNewCP = 932;
            }
            szBuf[0] = 'J';
            szBuf[1] = 'P';
            szBuf[2] = '\0';
            // no keyboard id available.
            szNewKbdID[0] = '\0';
    }
    else {
//
// check point #1: see if we can get the console keyboard layout id
//
        if (!GetConsoleKeyboardLayoutName(szKeybCode))
	    goto NoInstallkb16;
//
// check point #2: see if the layout is US and kb16.com is loaded and
//		   fast paste is disabled.
//		   If kb16.com is loaded, we need to run it again
//		   so that it will load the correct layout.
//		   If fast paste is disable, we load kb16.com which
//		   definitely will slow down keys delivery.
//
        if( bPifFastPaste && !strcmp(szKeybCode, US_CODE) && !iKeyb)
	    goto NoInstallkb16;
//
// check point #3: see if we can get the language id and keyboard id(if any)
//

  // OPEN THE KEY.
        sprintf(szAutoLine, "%s%s", KBDLAYOUT_PATH, DOSCODES_PATH);
        if (ERROR_SUCCESS != RegOpenKeyEx (HKEY_LOCAL_MACHINE, // Key handle at root level.
			      szAutoLine, // Path name of child key.
                              0,           // Reserved.
                              KEY_EXECUTE, // Requesting read access.
			      &hKey))      // Address of key to be returned.
	    goto NoInstallkb16;


        cbData  = sizeof(szBuf);
        // Query for line from REGISTER file
        retCode = RegQueryValueEx(hKey, szKeybCode, NULL, &dwType, szBuf, &cbData);

        RegCloseKey(hKey);
        if (ERROR_SUCCESS != retCode || REG_SZ != dwType || !cbData)
	    goto NoInstallkb16;
        //
        // szBuf now contains language id('SP' for spanish, for example).
        //
        // look for keyboard id number. For Daytona, Turkish and Italian both
        // have one key code and two layouts.
        szNewKbdID[0] = '\0';
        cbData = sizeof(szNewKbdID);
        sprintf(szAutoLine, "%s%s", KBDLAYOUT_PATH, DOSIDS_PATH);
        if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		          szAutoLine,
		          0,
		          KEY_EXECUTE,
		          &hKey
		          ) == ERROR_SUCCESS)
        {
	    retCode = RegQueryValueEx(hKey, szKeybCode, NULL, &dwType, szNewKbdID, &cbData);
	    if (ERROR_SUCCESS != retCode || REG_SZ != dwType || !cbData)
	        szNewKbdID[0] = '\0';

	    RegCloseKey(hKey);
        }

        iNewCP = GetConsoleCP();

    }

//
// check point #4: see if there are any changes in ids
//

// see if there are changes in language id, keyboard id and code page id.

    if(bPifFastPaste && iNewCP == iPrevCP &&
       !_stricmp(szBuf, szPrev) &&
       !_stricmp(szNewKbdID, szPrevKbdID))
    {
	goto NoInstallkb16;
    }

//
// Check point #5: see if kb16.com can be found.
//
// kb16.com should be found in GetSystemDirectory()\system32 subdirectory.
//
    iSaveSize = iSize = GetSystemDirectory(szDir, MAX_PATH);

    // can't get the system directory!
    if (!iSize || iSize > MAX_PATH)
    {
	goto NoInstallkb16;
    }
    // convert the system directory to short name
    cbData = GetShortPathName(szDir, szDir, MAX_PATH);
    if (!cbData || cbData >= MAX_PATH)
	goto NoInstallkb16;

    sprintf(szAutoLine, "%s%s",
        szDir,              // System directory
        KEYB_COM            // keyb.com
	);
    // if the fully qualified path name to kb16.com is too long
    // we must fail because Dos can not swallow a long path name.
    if (strlen(szAutoLine) > 128)
	goto NoInstallkb16;

    dwType = GetFileAttributes(szAutoLine);
    if (dwType == 0xFFFFFFFF || (dwType & FILE_ATTRIBUTE_DIRECTORY) != 0)
    {
	goto NoInstallkb16;
    }

//
// Check point #6: see if we can find kdf file that support the
//		   (language id, keyboard id, code page id) combination
//

    //
    // first, convert keyboard id from string to binary if we have one.
    //
    KeybID = (szNewKbdID[0]) ? (WORD)strtoul(szNewKbdID, NULL, 10) : 0;
    cbData = sizeof(szKDF) / sizeof(CHAR);

    // locate the kdf file.
    if (!LocateKDF(szBuf, KeybID, (WORD)iNewCP, szKDF, &cbData))
    {
	goto NoInstallkb16;
    }
    // convert the kdf name to short name
    cbData = GetShortPathName(szKDF, szKDF, sizeof(szKDF)/ sizeof(CHAR));
    if (!cbData || cbData >= sizeof(szKDF) / sizeof(CHAR))
    {
	goto NoInstallkb16;
    }
    //
    // everything is checked and in place. Now compose the command
    // line to execute kb16.com

    // first, the command
    pVDMKeyb = (PCHAR) GetVDMAddr((USHORT) getDS(), (USHORT) getSI());
    strcpy(pVDMKeyb, szAutoLine);
    // then the parameters
    // The format is: XX,YYY, <kdf file>, where XXX is the language id
    // and YYY is the code page id.
    pVDMKeyb = (PCHAR) GetVDMAddr((USHORT) getDS(), (USHORT) getCX());
    // The first byte is resevered for the length of the string.
    sprintf(szAutoLine, " %s,%d,%s",
	szBuf,		    // keyboard code
	iNewCP, 	    // new code page
	szKDF		    // keyboard.sys
	);
    // if we have a keyboard id, pass it also
    if (szNewKbdID[0])
    {
	strcat(szAutoLine, " /id:");
	strcat(szAutoLine, szNewKbdID);
    }
    // standard parameter line has the format:
    // <length><line text><\0xd>, <length> is the length of <line text>
    //
    iSize = strlen(szAutoLine);
    szAutoLine[iSize] = 0xd;
    // Move the line to 16bits, including the terminated cr char
    RtlMoveMemory(pVDMKeyb + 1, szAutoLine, iSize + 1);
    *pVDMKeyb = (CHAR)iSize;
// Save new layout ID  and code page for next call
    strcpy(szPrev, szBuf);
    strcpy(szPrevKbdID, szNewKbdID);
    iPrevCP = iNewCP;

    setDX(1);
    return;

NoInstallkb16:
    setDX(0);
    cmdInitConsole();      // make sure conoutput is on
    return;
}

//
// This function locates the appropriate keyboard definition file from
// the given language, keyboard and code page id. It searches the registry
// for third-party installed KDF files first and then falls back to
// the system default, %systemroot%\system32\keyboard.sys.
//
// INPUT:
//	LanguageID	-- the language id
//	KeyboardID	-- the optional keyboard id, 0 means do not care
//	CodePageID	-- the code page id
//	Buffer		-- the buffer to receive fully qualified kdf file name
//	BufferSize	-- the size of Buffer in bytes
//
// OUTPUT:
//	TRUE -- Buffer is filled with the kdf fully qualified file name and
//		*BufferSize is set with the size of the file name, not including
//		the null terminated char. If no kdf file can be found,
//		the Buffer is terminated with NULL and *BufferSize if set to 0.
//	FALSE -- error. GetLastError() should return the error code
//		 If the error occurs because the provided buffer is too small
//		*BufferSize will set to the required size(excluding null
//		terminated char) and error code will be set to
//		ERROR_INSUFFICIENT_BUFFER
//
BOOL
LocateKDF(
    CHAR*   LanguageID,
    WORD    KeyboardID,
    WORD    CodePageID,
    LPSTR   Buffer,
    DWORD*   BufferSize
    )
{
    HKEY  hKeyWow;
    BOOL  Result;
    DWORD dw, Type;
    DWORD Attributes;
    DWORD ErrorCode;
    CHAR* KDFName;
    CHAR* LocalBuffer;
    CHAR* FinalKDFName;
    CHAR  FullName[MAX_PATH + 1];

    // validate buffer parameter first
    if (!CodePageID || !LanguageID || !BufferSize || (*BufferSize && !Buffer))
    {
	SetLastError(ERROR_INVALID_PARAMETER);
	return FALSE;
    }
    // Open the registry to see if we have alternative kdf files avaialble.
    // We seach the file from atlternative file list in the registry first.
    // The first KDF in the list has the highest rank and the last one has
    // the lowest. The search starts from highest rank and then goes
    // on toward the lower ones. As soon as a KDF file is found, the search
    // stops. If no appropriate KDF can be found in the alternative list,
    // the default kdf, keyboard.sys, will be used.
    //
    // FinalKDFName serves as an indicator. If it is NULL,
    // we do not find any file that satisfies the request.
    FinalKDFName = NULL;
    LocalBuffer = NULL;
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		     REG_STR_WOW,
		     0,
		     KEY_EXECUTE,
		     &hKeyWow
		     ) == ERROR_SUCCESS)
    {
	// first probe for size
	dw = 0;
	RegQueryValueEx(hKeyWow, REG_STR_ALTKDF_FILES, NULL, &Type, NULL, &dw);
	if (dw && (REG_MULTI_SZ == Type))
	{
	    // we have something in the registry. Allocate a buffer to reteive
	    // it. We want the value to be double null terminated,
	    // so we add one more char in case it is a REG_SZ.
	    // The returned size from RegQueryValueEx includes the
	    // null terminated char(and the double null chars for
	    // REG_MULTI_SZ. By adding one more char, we are in
	    // good shape.

	    ASSERT(!LocalBuffer);
	    LocalBuffer = malloc((dw + 1)* sizeof(CHAR));
	    if (LocalBuffer)
	    {
		LocalBuffer[0] = '\0';
		if (RegQueryValueEx(hKeyWow, REG_STR_ALTKDF_FILES, NULL, &Type,
				LocalBuffer, &dw) == ERROR_SUCCESS && dw)
		{
		    KDFName = LocalBuffer;
		    while ('\0' != *KDFName)
		    {
			// See if we can find the file first.
			Attributes = GetFileAttributesA(KDFName);
			if (0xFFFFFFFF == Attributes)
			{
			    // file not found, do a search
			    if (SearchPathA(NULL,	// no path
					    KDFName,
					    NULL,	// no extension
					    sizeof(FullName) / sizeof(CHAR),
					    FullName,
					    NULL
					    ))
			    {
				FinalKDFName = FullName;
			    }
			}
			else
			{
			    FinalKDFName = KDFName;
			}
			if (MatchKDF(LanguageID, KeyboardID, CodePageID, FinalKDFName))
				break;
			KDFName += strlen(KDFName) + 1;
			FinalKDFName = NULL;
		    }
		}
	    }
	    else
	    {
		// not enough memory
		RcMessageBox(EG_MALLOC_FAILURE, NULL, NULL,
			     RMB_ICON_BANG | RMB_ABORT);
		 TerminateVDM();
	    }
	}
	if (!FinalKDFName)
	{
	    // either no alternative kdf files are specified in the registry
	    // or none of them contains the required specification,
	    // use the default kdf file
	    FullName[0] = '\0';
	    GetSystemDirectory(FullName, sizeof(FullName) / sizeof(CHAR));

            if (!_stricmp(LanguageID, "JP") &&
	        7 == GetKeyboardType(0)
               ) {
	        // For Japanese language ID, different keyboard types have different
	        // default kdf.

                int Keytype;

                Keytype = GetKeyboardType(1);
                if (Keytype == 1)
                    strcat(FullName, KDF_AX);
                else if (Keytype == 2)
                    strcat(FullName, KDF_106);
                else if (Keytype == 3)
                    strcat(FullName, KDF_IBM5576_02_03);
                else if ((Keytype & 0xFF00) == 0x1200)
                    strcat(FullName, KDF_TOSHIBA_J3100);
                else
                    strcat(FullName, KEYBOARD_SYS);
            }
            else
	        strcat(FullName, KEYBOARD_SYS);

	    FinalKDFName = FullName;
	}
	RegCloseKey(hKeyWow);
    }
    if (FinalKDFName)
    {
	dw = strlen(FinalKDFName);
	if (dw && dw < *BufferSize)
	{
	    strcpy(Buffer, FinalKDFName);
	    *BufferSize = dw;
	    Result = TRUE;
	}
	else
	{
	    *BufferSize = dw;
	    SetLastError(ERROR_INSUFFICIENT_BUFFER);
	    Result = FALSE;
	}
    }
    else
    {
	Result = FALSE;
	*BufferSize = 0;
	SetLastError(ERROR_FILE_NOT_FOUND);
    }
    //
    // finally, free the buffer we allocated
    //
    if (LocalBuffer)
	free(LocalBuffer);
    return Result;
}

//
// This function determines if the given kdf supports the given
// (language id, keyboard id, code page id) combination
//
// INPUT:
//	LanguageID	-- the language.
//	KeyboardID	-- optional keyboard id. 0 if do not care
//	CodePageID	-- code page id
//	KDFPath		-- fully qualified kdf file
// OUTPUT:
//	TRUE  -- The kdf contains the given combination
//	FALSE -- either the kdf does not contain the combination or
//		 can not determine.
//
BOOL
MatchKDF(
    CHAR* LanguageID,
    WORD KeyboardID,
    WORD CodePageID,
    LPCSTR KDFPath
    )
{
    HANDLE hKDF;
    KDF_HEADER	Header;
    KDF_LANGID_ENTRY	  LangIdEntry;
    DWORD BytesRead, BufferSize;
    WORD  Index;
    DWORD LangIdEntryOffset;
    PKDF_CODEPAGEID_OFFSET pCodePageIdOffset;
    BOOL Matched;

    if (!KDFPath || !LanguageID || !CodePageID)
    {
	SetLastError(ERROR_INVALID_PARAMETER);
	return FALSE;
    }
    Matched = FALSE;

    LangIdEntryOffset = 0;
    // open the kdf file.
    hKDF = CreateFile(KDFPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
			  NULL, OPEN_EXISTING, 0, NULL);
    if (INVALID_HANDLE_VALUE != hKDF &&
	ReadFile(hKDF, &Header, sizeof(Header),&BytesRead, NULL) &&
	BytesRead == sizeof(Header) && Header.TotalLangIDs &&
	Header.TotalKeybIDs &&
	!strncmp(Header.Signature, KDF_SIGNATURE, sizeof(Header.Signature))
	)
    {
	// The file header is loaded, the signature checked and sanity check
	// on language and keyboard id counts is also done.
	// We are now ready to verfiy if the given language id, keyboard id
	// and code page id is supported in this file.
	// A KDF has two sets of offset table. One is based on language ID
	// while the other one is based on keyboard id. Since a language ID
	// may contain multiple keyboard id, the keyboard id set is always
	// encompass the language id table.
	// If the caller gives us a keyboard id, we use the id as the
	// key for search and verify language id when we found the keyboard
	// id. If no keyboard id is provided, we use the language id as the
	// key.
	if (KeyboardID)
	{
	    // move the file pointer to the keyboard id offset array
	    BufferSize = sizeof(KDF_LANGID_OFFSET) * Header.TotalLangIDs;
	    BufferSize = SetFilePointer(hKDF, BufferSize, NULL, FILE_CURRENT);
	    if (0xFFFFFFFF != BufferSize)
	    {
		PKDF_KEYBOARDID_OFFSET pKeybIdOffset;
		BufferSize = sizeof(KDF_KEYBOARDID_OFFSET) * Header.TotalKeybIDs;
		pKeybIdOffset = (PKDF_KEYBOARDID_OFFSET)malloc(BufferSize);
		if (!pKeybIdOffset)
		{
		    // not enough memory
		    RcMessageBox(EG_MALLOC_FAILURE, NULL, NULL,
				 RMB_ICON_BANG | RMB_ABORT);
		     TerminateVDM();
		}
		if (ReadFile(hKDF, pKeybIdOffset, BufferSize, &BytesRead, NULL) &&
		    BytesRead == BufferSize)
		{
		    // loop though each KDF_KEYBOARDID_OFFSET to see
		    // if the keyboard id matches.
		    for (Index = 0; Index < Header.TotalKeybIDs; Index++)
		    {
			if (pKeybIdOffset[Index].ID == KeyboardID)
			{
			    // got it. Remeber the file offset to
			    // the KDF_LANGID_ENTRY
			    LangIdEntryOffset = pKeybIdOffset[Index].DataOffset;
			    break;
			}
		    }
		}
		free(pKeybIdOffset);
	    }
	}
	else
	{
	    PKDF_LANGID_OFFSET	pLangIdOffset;
	    BufferSize = sizeof(KDF_LANGID_OFFSET) * Header.TotalLangIDs;
	    pLangIdOffset = (PKDF_LANGID_OFFSET)malloc(BufferSize);
	    if (!pLangIdOffset)
	    {
		// not enough memory
		RcMessageBox(EG_MALLOC_FAILURE, NULL, NULL,
			     RMB_ICON_BANG | RMB_ABORT);
		TerminateVDM();
	    }
	    if (ReadFile(hKDF, pLangIdOffset, BufferSize, &BytesRead, NULL) &&
		BytesRead == BufferSize)
	    {
		// loop through each KDF_LANGID_OFFSET to see if
		// language id matches
		for (Index = 0; Index < Header.TotalLangIDs; Index++)
		{
		    if (IS_LANGID_EQUAL(pLangIdOffset[Index].ID, LanguageID))
		    {
			LangIdEntryOffset = pLangIdOffset[Index].DataOffset;
			break;
		    }
		}
	    }
	    free(pLangIdOffset);
	}
	if (LangIdEntryOffset)
	{
	    BufferSize = SetFilePointer(hKDF, LangIdEntryOffset, NULL, FILE_BEGIN);
	    if (0xFFFFFFFF != BufferSize &&
		ReadFile(hKDF, &LangIdEntry, sizeof(LangIdEntry), &BytesRead, NULL) &&
		BytesRead == sizeof(LangIdEntry))
	    {
		// sanity checks
		if (IS_LANGID_EQUAL(LangIdEntry.ID, LanguageID) &&
		    LangIdEntry.TotalCodePageIDs)
		{
		    // the KDF_LANGID_ENTRY looks fine. Now retrieve
		    // its code page offset table and search the given
		    // code page id

		    BufferSize = LangIdEntry.TotalCodePageIDs * sizeof(KDF_CODEPAGEID_OFFSET);
		    pCodePageIdOffset = (PKDF_CODEPAGEID_OFFSET)malloc(BufferSize);
		    if (!pCodePageIdOffset)
		    {
			// not enough memory
			RcMessageBox(EG_MALLOC_FAILURE, NULL, NULL,
				     RMB_ICON_BANG | RMB_ABORT);
			TerminateVDM();
		    }
		    if (ReadFile(hKDF, pCodePageIdOffset, BufferSize, &BytesRead, NULL) &&
			BytesRead == BufferSize)
		    {
			for (Index = 0; Index < LangIdEntry.TotalCodePageIDs; Index++)
			{
			    if (CodePageID == pCodePageIdOffset[Index].ID)
			    {
				Matched = TRUE;
				break;
			    }
			}
		    }
		    free(pCodePageIdOffset);
		}
	    }
	}
	CloseHandle(hKDF);
    }
    return Matched;
}