/****************************************************************************
**
**  Name:  loader.cpp
**
**  Description:
**     Definitions of external interface functions in LOADER.DLL
**
**  Status:  PRELIMINARY | CODED | REVIEWED | TESTED
**
**  $Log:   S:/tbird/mt2_186/loader/loader.cpv  $
** 
**    Rev 1.1   26 Feb 1997 11:49:58   Judy
** 
**    Rev 1.1   27 Jan 1997 17:43:18   Judy
** No change.
** 
**    Rev 1.0   14 Jun 1996 17:25:38   Judy
** Initial revision.
** 
**    Rev 1.8   21 Oct 1994 09:04:52   nghia
** Fixed PPR 9940 - Validating the input LoadFile path string for Shell load
** command.
** 
**    Rev 1.7   27 Sep 1994 18:03:06   nghia
** Revised LdrCliLoad() to get the latest symbol loading information.
** Suppressed the loadfile not found error - reported.
** 
**    Rev 1.6   01 Sep 1994 11:20:16   nghia
** Revised loader Shell command parser to recognize new load option.
** 
**    Rev 1.5   14 Jul 1994 10:51:56   nghia
** Loader UI changes.
** Loader server handles open dialog box and load options.
** 
**    Rev 1.4   17 Jun 1994 11:32:48   nghia
** Revised LoaderLoad() function to handle report error for all loading error.
** 
**    Rev 1.3   10 Jun 1994 16:03:54   nghia
** Added TerminateServer() call to terminate the Loader.
** 
**    Rev 1.2   03 Jun 1994 15:01:30   nghia
** Used LdrSendMessageToCLI to output message.
** 
**    Rev 1.1   23 May 1994 16:50:44   nghia
** Added reporting load status to the Shell as a common service for all loaders.
** 
**    Rev 1.0   18 May 1994 16:59:40   nghia
** Initial revision.
**
**  $Header:   S:/tbird/mt2_186/loader/loader.cpv   1.1   26 Feb 1997 11:49:58   Judy  $
**
**  Copyright (C) 1994 Microtek International.  All rights reserved.
**
*****************************************************************************/

                       /****************************
                        *                          *
                        *       INCLUDE FILES      *
                        *                          *
                        ****************************/
#include <io.h>
#include <ctype.h>
#include <string.h>

#ifndef _LOADER_
#include "loader.h"
#endif

#ifndef __LDROBJ__
#include "ldrobj.h"
#endif

#ifndef __LDRDLGS__
#include "ldrdlgs.h"
#endif

                       /****************************
                        *                          *
                        *     LOCAL DEFINITIONS    *
                        *                          *
                        ****************************/
#define MAX_INVALID_FCHAR 12
STATIC CHAR invalidFileNameChars[MAX_INVALID_FCHAR] = " |=-+[];,<>\"";
CHAR tmpFileName[MAX_STRING_SIZE]; // temporary file name buffer
STATIC CHAR msgBuf[128] = "";      // status information buffer
const LPSTR loadCompletedMsg = "Load complete.";
const LPSTR loadCodeMsg      = "bytes code loaded.";
const LPSTR loadSymMsg       = "symbol(s) loaded.";


                       /****************************
                        *                          *
                        *    EXTERNAL VARIABLES    *
                        *                          *
                        ****************************/
/* Global Loader Variables */
extern PROCESSOR_FAMILY procFamily;  // defined in LIMAIN.CPP
extern PTLdrLauncher LoaderServer;   // defined in LDROBJ.CPP
extern LDRSTATBLOCK LdrStatus;       // defined in LDRPROG.CPP

                       /****************************
                        *                          *
                        *     LOCAL PROTOTYPES     *
                        *                          *
                        ****************************/
PRIVATE RETCODE GetModuleName(LPSTR lpModuleRef, LPSTR lpModuleName);
PRIVATE RETCODE ProcessCmdLineArgs(LPSTR cmdString, U32 argc, U32 argv[],
                                   LPSTR lpFileName, U32 *loadOptions,
                                   LPSTR lpModuleName, LPSTR lpModuleRef,
                                   ADDR_SPACE *loadSpace);

                       /****************************
                        *                          *
                        *      EXECUTABLE CODE     *
                        *                          *
                        ****************************/

/*****************************************************************************
**
** LdrLoad
**
*****************************************************************************/
RETCODE EXPORT LdrLoad(LPSTR loadFileName, U32 loadFlags, U32 parentHWnd,
                       ADDR_SPACE loadSpace, BOOLEAN openDlg) {
   RETCODE err;
   
   if (!LoaderServer || (LoaderServer->Status() != GOOD))
      return ER_LDR_INVALID_SERVER;

   // Mask off the LOAD_FROM_CLI bit if present
   loadFlags &= ~LOAD_FROM_CLI;

   // check <openDlg> to allow the user select a new loadfile first
   if (openDlg) {
      if (!loadFlags) loadFlags = LoaderServer->LastLoadOptions();
      if (!loadSpace) loadSpace = LoaderServer->LastLoadSpace();
      if ((err = LdrOpenLoadFileDlg((HWND) parentHWnd, loadFileName,
                                    tmpFileName, &loadFlags,
                                    &loadSpace)) != GOOD)
         return err;
      // copy the new loadfile name
      lstrcpy(loadFileName, tmpFileName);
   }
   
   // invoke the load function to load
   if ((err = LoaderServer->Load(loadFileName, loadFlags,
                                 (HWND) parentHWnd, loadSpace)) != GOOD)
      return LdrWarning(err, NULL, NULL, NULL);
   return GOOD;
} /* LdrLoad */

/*****************************************************************************
**
** LdrCliLoad
**
*****************************************************************************/
RETCODE EXPORT LdrCliLoad(LPSTR cmdString, U32 argc, U32 argv[]) {
   RETCODE err;     
   U32 loadOptions = DEF_LOAD_OPTIONS; // local to Shell Load command
   CHAR loadFileName[MAX_STRING_SIZE];
   CHAR moduleName[MAX_FILE_NAME];
   CHAR moduleRef[MAX_PATH_SIZE];
   ADDR_SPACE loadSpace;
        
   if (!LoaderServer || (LoaderServer->Status() != GOOD))
      return ER_LDR_INVALID_SERVER;
   // set the default load address space
   loadSpace = (procFamily == FAMILY_X86) ?  SPACE_USER : SPACE_SD;
   
   // parse the command line options
   if ((err = ProcessCmdLineArgs(cmdString, argc, argv, loadFileName,
                                 &loadOptions, moduleName,
                                 moduleRef, &loadSpace)) != GOOD)
      return err;

   // mask the LOAD_FROM_CLI bit so that the specific loader can check
   loadOptions |= LOAD_FROM_CLI;
   
   // load module only 
   if (IsLoadModule(loadOptions)) {
      SYM_DESCRIPTOR moduleDesc;
      // get the module descriptor from moduleName and moduleReference 
      if ((err = SymGetModuleDesc((LPSTR)moduleName, (LPSTR)moduleRef,
               &moduleDesc)) == GOOD) {
         // call the LoaderServer to load 
         err = LoaderServer->LoadModuleByDesc((LPSTR)loadFileName,
             (LPSTR)moduleName, moduleDesc, loadSpace);
      }
   } else {
      // call LoaderServer to load 
      if (((err = LoaderServer->Load((LPSTR)loadFileName, loadOptions,
          NULL, loadSpace)) == GOOD) && IsReportStatus(loadOptions)) {
         // report load status to Shell if report flag is ON
         if (IsLoadCode(loadOptions) && (LdrStatus.numBytes > 0)) {
            wsprintf(msgBuf, "%ld %s", LdrStatus.numBytes, loadCodeMsg);
            LdrSendMessageToCLI(msgBuf);
         }
         // report symbols info if load symbols
         if (IsLoadSymbol(loadOptions) && (LdrStatus.numSymbols > 0)) {
            // update the latest symbol loading status
            SymGetLdrStats((LPSTR)&(LdrStatus.curModule),
                           (U32 FAR *)(&LdrStatus.numSymbols),
                           (U16 FAR *)(&LdrStatus.numModules),
                           (U32 FAR *)(&LdrStatus.numTypes));
            wsprintf(msgBuf,"%ld %s", LdrStatus.numSymbols, loadSymMsg);
            LdrSendMessageToCLI(msgBuf);
         }
         // load completed message
         LdrSendMessageToCLI(loadCompletedMsg);
      } else 
         return (ERR_EQUAL(err,ER_LDR_LOADFILE_NOT_FOUND) ? GOOD : err);  
   }
   return err;
} /* LdrCliLoad */


/*****************************************************************************
**
**  LdrLoadModuleByDesc
**
*****************************************************************************/
RETCODE EXPORT LdrLoadModuleByDesc(LPSTR fileName, LPSTR moduleName,
      SYM_DESCRIPTOR moduleDesc, ADDR_SPACE addrSpace) {

   if (!LoaderServer || (LoaderServer->Status() != GOOD))
      return ER_LDR_INVALID_SERVER;
   return LoaderServer->LoadModuleByDesc(fileName, moduleName, moduleDesc,
                                         addrSpace);
} /* LdrLoadModuleByDesc */   

/*****************************************************************************
**
**  LdrGetStartPC
**
******************************************************************************/
RETCODE EXPORT LdrGetStartPC(DESCRIPTOR FAR *lpStartPC) {
   if (!LoaderServer || (LoaderServer->Status() != GOOD))
      return ER_LDR_INVALID_SERVER;
   return LoaderServer->GetStartPC(lpStartPC);
} /* LdrGetStartPC */   

/******************************************************************************
**
**  LdrGetStackInfo
**
*****************************************************************************/
RETCODE EXPORT LdrGetStackInfo(DESCRIPTOR FAR *lpStkTop, U32 *lpStkSize) {
   if (!LoaderServer || (LoaderServer->Status() != GOOD))
      return ER_LDR_INVALID_SERVER;
   return LoaderServer->GetStackInfo(lpStkTop, lpStkSize);
} /* LdrGetStackInfo */

/*****************************************************************************
**
**  LdrGetLoadInfo
**
*****************************************************************************/
RETCODE EXPORT LdrGetLoadInfo(LPSTR lpFileName, U32 FAR* lpLoadOptions,
                              ADDR_SPACE FAR *lpLoadAddrSpace) {
   assert(lpFileName);  // null pointer
   if (!LoaderServer || (LoaderServer->Status() != GOOD))
      return ER_LDR_INVALID_SERVER;
   return LoaderServer->GetLoadInfo(lpFileName, lpLoadOptions,
                                    lpLoadAddrSpace);  
} /* LdrGetLoadInfo */

/*****************************************************************************
**
**  LdrCheckTimestamp
**
******************************************************************************/
RETCODE EXPORT LdrCheckTimestamp(LPSTR lpSourceFile,  BOOLEAN FAR *lpValid) {
   TIMESTAMP_TYPE tsModule;
   FILE *hInFile;

   if (!LoaderServer || (LoaderServer->Status() != GOOD))
      return ER_LDR_INVALID_SERVER;

   *lpValid = FALSE;
   if ((hInFile = fopen(lpSourceFile, "r")) == NULL)
      return ER_MODULE_NOT_FOUND;
   // get time stamp of the input source file
   LdrGetTimestamp(fileno(hInFile), (TIMESTAMP_TYPE FAR*)&tsModule);
   fclose(hInFile);

   /* compare module timestamp to loadfile timestamp */
   return LdrCompareTimestamps((TIMESTAMP_TYPE FAR*)&tsModule,
                               (TIMESTAMP_TYPE FAR*)
                               LoaderServer->LastLoadFileTimeStamp(),
                               lpValid);
} /* LdrValidateSourceFile */

/******************************************************************************
**
**  LdrGetTimestamp
**
******************************************************************************/
RETCODE EXPORT LdrGetTimestamp(HANDLE fileHandle, TIMESTAMP_TYPE FAR* lpTS) {
   struct ftime ft;

   if (!fileHandle)
     return ER_LDR_INVALID_FILE_HANDLE; 
   // Get time info from disk file specified by handle - DOS-specific 
   if (getftime(fileHandle, &ft) != 0)
     return ER_LDR_CANNOT_GET_FILE_TIME;

   // readjust the time 
   lpTS->year   = ft.ft_year+1980;
   lpTS->month  = ft.ft_month;
   lpTS->day    = ft.ft_day;
   lpTS->hour   = ft.ft_hour;
   lpTS->minute = ft.ft_min;
   lpTS->second = ft.ft_tsec*2;

   return(GOOD);
}  /* LdrGetTimestamp */

/******************************************************************************
**
**  LdrCompareTimestamps
**
******************************************************************************/
RETCODE EXPORT LdrCompareTimestamps(TIMESTAMP_TYPE FAR* lpt1,
                                    TIMESTAMP_TYPE FAR* lpt2,
                                    BOOLEAN FAR *resultEqual) {

   if (!lpt1 || !lpt2)
      return ER_LDR_INVALID_INPUT_TIMESTAMPS;
                                                    
   // Return the FALSE if the two timestamps are
   //    if t1 < t2,
   //    if t1 > t2
   // Else return TRUE when they are identical in all fields,
   //
   //
   // If one field is not equal - the resultEqual is FALSE
   *resultEqual = !(((lpt1->year   - lpt2->year)   != 0) ||
                    ((lpt1->month  - lpt2->month)  != 0) ||
                    ((lpt1->day    - lpt2->day)    != 0) ||
                    ((lpt1->hour   - lpt2->hour)   != 0) ||
                    ((lpt1->minute - lpt2->minute) != 0) ||
                    ((lpt1->second - lpt2->second) != 0)); 
   return(GOOD);   
}  /* CompareTimestamp */

/******************************************************************************
**
**  LdrCliResetLoaders
**
******************************************************************************/
RETCODE EXPORT LdrCliResetLoaders(LPSTR cmdString, U32 argc, U32 argv[]) {
   PTLdrLauncher tmpObj = NULL;
   
   // Check semaphore to prevent statics from getting trashed 
   if (LoaderServer && LoaderServer->IsLoading())
      return ER_LDR_IN_PROGRESS;
   if (argc > 2)
      return ER_CLI_SYNTAX;
   
   // User enter :>ResetLoaders "[path to LOADERS.INI]"
   // to reset the current Loader Launcher
   // if path to LOADERS.INI is not specified then use the old one
   if ((argc < 2) && !LdrLauncher::HasPwrViewsDir)
      return ER_LDR_CANNOT_LOCATE_LOADERS_INI;
   else {
      if (argc == 2) {
         // check for bogus charaters in the input path
         if (strstr(&cmdString[(U16)argv[1]], ".,/"))
            return ER_CLI_SYNTAX;
         // strip the last \ if user puts in one
         LPSTR tmpPtr = (LPSTR)&cmdString[(U16)argv[1]];
         U8 len = lstrlen(tmpPtr);
         if (tmpPtr[len] == '\\') 
            tmpPtr[len] = '\0';
         // Get the path to LOADER.INI file
         if (!lstrcpy((LPSTR)LdrLauncher::PwrViewsDir,
                      (LPSTR)&cmdString[(U16)argv[1]]))
            return ER_FAILED_STRCPY;
         // create the path to LOADERS.INI
         strcat((LPSTR)LdrLauncher::PwrViewsDir,
                (LPSTR)LdrLauncher::IniFileName);
      }
   }                   

   // create a new loaderServer object instance
   if ((tmpObj = new LdrLauncher()) == NULL) 
       return ER_OUT_OF_MEMORY;
   if (tmpObj->Status() != GOOD)
      return tmpObj->Status();
   
   // Destroy the old LdrLauncher object
   delete LoaderServer;

   // Save the new LdrLauncher Object to LoaderServer
   LoaderServer = tmpObj;
   return LoaderServer->Status();  // last error occurs in server
}

/******************************************************************************
**
**   TerminateServer
**
*****************************************************************************/
RETCODE EXPORT TerminateServer() {
   
   // Avoid terminate server while loading 
   if (LoaderServer) {
      if (LoaderServer->IsLoading())
         return ER_LDR_IN_PROGRESS;
      // Destroy the Loader object
      delete LoaderServer;
      LoaderServer = NULL;
   }
   return GOOD;   
}

/****************************** LOCAL FUNCTIONS *****************************/

/*****************************************************************************
**
**  GetModuleNameName
**
** Description:
**    Parse the module reference in to its path and name string.
**
** Parameters:
**    Input:
**       lpModuleRef  - module path reference 
**
**    Output:
**       lpModuleName - module name 
**
*****************************************************************************/
PRIVATE RETCODE GetModuleName(LPSTR lpModuleRef, LPSTR lpModuleName) {
   BOOLEAN found = FALSE;
   U16 i, pathLength;
   LPSTR tmpPtr = lpModuleRef;

   if ((pathLength = lstrlen(lpModuleRef)) == 0)
      return(GOOD);
   /* Algorithm:
   ** Search from right to left for the last slash or a colon,
   ** if found
   **    copy moduleName
   ** else only module name is specified
   **    copy moduleName and set moduleRef to empty tring.
   ** return results
   */
   for (i = pathLength; i > 0 && !found; i--) {
      if ((CHAR)*(tmpPtr+i) == '.')
         *(tmpPtr+i) = '\0'; /* Excluding the extension for module name */
      if ((i < pathLength) && (((CHAR)*(tmpPtr+i) == '\\') ||
            ((CHAR)*(tmpPtr+i) == '/') || ((CHAR)*(tmpPtr+i) == ':')) ) {
         lstrcpy(lpModuleName, (LPSTR)tmpPtr+i+1); /* backup 1 position */
         found = TRUE;
      }
   }
   /* There is no path reference in lpModuleRef */
   if (!found) {
      lstrcpy(lpModuleName, lpModuleRef);
      *lpModuleRef = '\0'; /* NULL string */
   }
   return(GOOD);
} /* GetModuleName */

/****************************************************************************
**
** ProcessCmdLineArgs
**
** Description:
**    Process Shell command line arguments.
**
** Parameters:
**    Input:
**       cmdString    - raw string from command-line
**       argc         - number of argv-type parameters
**       argv         - string array indices for each parameter
**
**    Output:
**       lpfile       - loadfile name
**       ldrflags     - load options
**       lpModuleName - module name (if module-only load)
**       lpModuleRef  - module path reference (if module-only load)
**       loadSpace    - load address space.
**
*****************************************************************************/
PRIVATE RETCODE ProcessCmdLineArgs(LPSTR cmdString, U32 argc, U32 argv[],
                                   LPSTR lpFileName, U32 *loadOptions,
                                   LPSTR lpModuleName, LPSTR lpModuleRef,
                                   ADDR_SPACE *loadSpace) {   
   U16 index;
   CHAR cparm[MAX_STRING_SIZE];

   if (argc < 2) 
      // error propagated as return from load 
      return ER_CLI_SYNTAX;
  
   // get the loadfile name 
   lstrcpy((LPSTR)lpFileName, (LPSTR)&cmdString[(U16)argv[1]]);

   // validate the loadfile name string
   for (U8 i = 0; i < MAX_INVALID_FCHAR; i++) {
      if (strchr(lpFileName, invalidFileNameChars[i]))
         return ER_CLI_SYNTAX;
   }
   // process command inputs 
   for (i = 2; i < argc; i++) {
      index = (U16) argv[i];
      lstrcpy((LPSTR)cparm, (LPSTR)&cmdString[index]);
      switch (toupper(cparm[0])) {
         case 'A':   /* load assembly modules */
            if (strnicmp(cparm, "ASM", 3) == 0) *loadOptions |= LOAD_ASM;
            else return ER_CLI_SYNTAX;
            break;
         case 'C':   /* load code */
            if (strnicmp(cparm, "COD", 3) == 0) *loadOptions |= LOAD_CODE;
            else if (strnicmp(cparm, "CAS", 3) == 0)
               *loadOptions &= ~LOAD_IGNORE_CASE;  // symbols case sensitive
            else return ER_CLI_SYNTAX;
            break;
         case 'D':   /* ondemand load */
            if (strnicmp(cparm, "DEM", 3) == 0) {
               *loadOptions |= LOAD_ONDEMAND;
            } else if (strnicmp(cparm, "DEMANGLE", 8) == 0) {
               *loadOptions |= LOAD_DEMANGLE;  // demangle C++ names
            }           
            else
               return ER_CLI_SYNTAX;
            break;
         case 'I':
            if (strnicmp(cparm, "IGNORE", 6) == 0)
               *loadOptions |= LOAD_IGNORE_CASE; // symbol case insensitive
            else
               return ER_CLI_SYNTAX;
            break;
         case 'L':
            if (strnicmp(cparm, "LOADREG", 7) == 0) 
               *loadOptions |= LOAD_REGISTERS;
            else
               return ER_CLI_SYNTAX;
            break;
         case 'M':   /* module-only load, mangled names */
            if (strnicmp(cparm, "MOD", 3) == 0) {
               /* next argument is module name */
               if (i < argc-1) {
                  lstrcpy((LPSTR)lpModuleRef,
                          (LPSTR)&cmdString[(U16)argv[++i]]);
                  /* Parse the lpModuleRef to path and module name */
                  if ((GetModuleName(lpModuleRef, lpModuleName) != GOOD) ||
                     (lstrlen(lpModuleName) == 0)) {
                     return ER_CLI_SYNTAX;
                  }
               }
               else 
                  return ER_MODULE_NOT_FOUND;
            } else
               return ER_CLI_SYNTAX;
            break;

         case 'N':   /* turn off option */
            if (strnicmp(cparm, "NOSYM", 5) == 0)
               *loadOptions &= ~LOAD_SYMBOL;   // do not load symbol
            else if (strnicmp(cparm, "NOCOD", 5) == 0)
               *loadOptions &= ~LOAD_CODE;     // do not load code
            else if (strnicmp(cparm, "NOSTA", 5) == 0)
               *loadOptions &= ~LOAD_STATUS;   // do not report status
            else if (strnicmp(cparm, "NODEM", 5) == 0)
               *loadOptions &= ~LOAD_ONDEMAND; // do not load ondemand   
            else if (strnicmp(cparm, "NOASM", 5) == 0)
               *loadOptions &= ~LOAD_ASM;      // do not load ASM modules   
            else if (strnicmp(cparm, "NOWAR", 5) == 0) 
               *loadOptions &= ~LOAD_WARNING;  // do not report warnings
            else if (strnicmp(cparm, "NODEMAN", 7) == 0)
               *loadOptions &= ~LOAD_DEMANGLE; // do not demangle C++ names  
            else if (strnicmp(cparm, "NOUPDATE", 8) == 0)
               *loadOptions &= ~LOAD_UPDATE_BASE; // do not updates bases
            else if (strnicmp(cparm, "NOLOADREG", 8) == 0)
               *loadOptions &= ~LOAD_REGISTERS; // do not load registers
            else
               return ER_CLI_SYNTAX;
            break;
         case 'R':   /* reload */
            if (strnicmp(cparm, "REL", 3) == 0)
               *loadOptions |= LOAD_RELOAD;
            else return ER_CLI_SYNTAX;
            break;
         case 'S':   /* symbol load */
            if (strnicmp(cparm, "SYM", 3) == 0)
               *loadOptions |= LOAD_SYMBOL;
            else if (strnicmp(cparm, "SMM", 3) == 0)
               *loadSpace = SPACE_SMM;
            else if (strnicmp(cparm, "STAT", 4) == 0)
               *loadOptions |= LOAD_STATUS; // load statistic report 
            else return ER_CLI_SYNTAX;
            break;
         case 'U':   /* update bases */
            if (strnicmp(cparm, "UPDATE", 6) == 0)
               *loadOptions |= LOAD_UPDATE_BASE;
            else if (strnicmp(cparm, "USER", 4) == 0)
               return *loadSpace = SPACE_USER;
            else return ER_CLI_SYNTAX;
            break;
         case 'W':   /* warnings */
            if (strnicmp(cparm, "WAR", 3) == 0)
               *loadOptions |= LOAD_WARNING;
            else return ER_CLI_SYNTAX;
            break;
         default:
            return ER_CLI_SYNTAX;
      }
   }

   // check for consistency of arguments 
   if (IsLoadAsm(*loadOptions) && !IsLoadSymbol(*loadOptions)) 
      return ER_ARGS_NEED_SYM;
   return GOOD;
}  /* ProcessCmdLineArgs */


/********************************** E O F ***********************************/
