Index: trunk/src/helpers/apps.c
===================================================================
--- trunk/src/helpers/apps.c	(revision 106)
+++ trunk/src/helpers/apps.c	(revision 108)
@@ -476,62 +476,7 @@
 /* ******************************************************************
  *
- *   Application start
+ *   Application information
  *
  ********************************************************************/
-
-/*
- *@@ CallBatchCorrectly:
- *      fixes the specified PROGDETAILS for
- *      command files in the executable part
- *      by inserting /C XXX into the parameters
- *      and setting the executable according
- *      to an environment variable.
- *
- *@@added V0.9.6 (2000-10-16) [umoeller]
- *@@changed V0.9.7 (2001-01-15) [umoeller]: now using XSTRING
- *@@changed V0.9.12 (2001-05-27) [umoeller]: moved from winh.c to apps.c
- */
-
-VOID CallBatchCorrectly(PPROGDETAILS pProgDetails,
-                        PXSTRING pstrParams,        // in/out: modified parameters (reallocated)
-                        const char *pcszEnvVar,     // in: env var spec'g command proc
-                                                    // (e.g. "OS2_SHELL"); can be NULL
-                        const char *pcszDefProc)    // in: def't command proc (e.g. "CMD.EXE")
-{
-    // XXX.CMD file as executable:
-    // fix args to /C XXX.CMD
-
-    PSZ     pszOldParams = NULL;
-    ULONG   ulOldParamsLength = pstrParams->ulLength;
-    if (ulOldParamsLength)
-        // we have parameters already:
-        // make a backup... we'll append that later
-        pszOldParams = strdup(pstrParams->psz);
-
-    // set new params to "/C filename.cmd"
-    xstrcpy(pstrParams, "/C ", 0);
-    xstrcat(pstrParams,
-            pProgDetails->pszExecutable,
-            0);
-
-    if (pszOldParams)
-    {
-        // .cmd had params:
-        // append space and old params
-        xstrcatc(pstrParams, ' ');
-        xstrcat(pstrParams,
-                pszOldParams,
-                ulOldParamsLength);
-        free(pszOldParams);
-    }
-
-    // set executable to $(OS2_SHELL)
-    pProgDetails->pszExecutable = NULL;
-    if (pcszEnvVar)
-        pProgDetails->pszExecutable = getenv(pcszEnvVar);
-    if (!pProgDetails->pszExecutable)
-        pProgDetails->pszExecutable = (PSZ)pcszDefProc;
-                // should be on PATH
-}
 
 /*
@@ -557,6 +502,5 @@
  *      --  PROG_VDD
  *
- *      --  PROG_XWP_DLL: new apptype defined in winh.h for
- *          dynamic link libraries.
+ *      --  PROG_DLL
  *
  *      --  PROG_WINDOWEDVDM
@@ -590,5 +534,5 @@
         else if ((_ulDosAppType & 0xF0) == FAPPTYP_DLL) // 0x10)
             // DLL bit set
-            *pulWinAppType = PROG_XWP_DLL;
+            *pulWinAppType = PROG_DLL;
         else if (_ulDosAppType & FAPPTYP_DOS)           // 0x20)
             // DOS bit set?
@@ -611,4 +555,62 @@
 
 /*
+ *@@ appDescribeAppType:
+ *      returns a "PROG_*" string for the given
+ *      program type. Useful for WPProgram setup
+ *      strings and such.
+ *
+ *@@added V0.9.16 (2001-10-06)
+ */
+
+PCSZ appDescribeAppType(PROGCATEGORY progc)        // in: from PROGDETAILS.progc
+{
+    switch (progc)
+    {
+        case PROG_DEFAULT: return "PROG_DEFAULT";
+        case PROG_FULLSCREEN: return "PROG_FULLSCREEN";
+        case PROG_WINDOWABLEVIO: return "PROG_WINDOWABLEVIO";
+        case PROG_PM: return "PROG_PM";
+        case PROG_GROUP: return "PROG_GROUP";
+        case PROG_VDM: return "PROG_VDM";
+            // same as case PROG_REAL: return "PROG_REAL";
+        case PROG_WINDOWEDVDM: return "PROG_WINDOWEDVDM";
+        case PROG_DLL: return "PROG_DLL";
+        case PROG_PDD: return "PROG_PDD";
+        case PROG_VDD: return "PROG_VDD";
+        case PROG_WINDOW_REAL: return "PROG_WINDOW_REAL";
+        case PROG_30_STD: return "PROG_30_STD";
+            // same as case PROG_WINDOW_PROT: return "PROG_WINDOW_PROT";
+        case PROG_WINDOW_AUTO: return "PROG_WINDOW_AUTO";
+        case PROG_30_STDSEAMLESSVDM: return "PROG_30_STDSEAMLESSVDM";
+            // same as case PROG_SEAMLESSVDM: return "PROG_SEAMLESSVDM";
+        case PROG_30_STDSEAMLESSCOMMON: return "PROG_30_STDSEAMLESSCOMMON";
+            // same as case PROG_SEAMLESSCOMMON: return "PROG_SEAMLESSCOMMON";
+        case PROG_31_STDSEAMLESSVDM: return "PROG_31_STDSEAMLESSVDM";
+        case PROG_31_STDSEAMLESSCOMMON: return "PROG_31_STDSEAMLESSCOMMON";
+        case PROG_31_ENHSEAMLESSVDM: return "PROG_31_ENHSEAMLESSVDM";
+        case PROG_31_ENHSEAMLESSCOMMON: return "PROG_31_ENHSEAMLESSCOMMON";
+        case PROG_31_ENH: return "PROG_31_ENH";
+        case PROG_31_STD: return "PROG_31_STD";
+
+// Warp 4 toolkit defines, whatever these were designed for...
+#ifndef PROG_DOS_GAME
+    #define PROG_DOS_GAME            (PROGCATEGORY)21
+#endif
+#ifndef PROG_WIN_GAME
+    #define PROG_WIN_GAME            (PROGCATEGORY)22
+#endif
+#ifndef PROG_DOS_MODE
+    #define PROG_DOS_MODE            (PROGCATEGORY)23
+#endif
+
+        case PROG_DOS_GAME: return "PROG_DOS_GAME";
+        case PROG_WIN_GAME: return "PROG_WIN_GAME";
+        case PROG_DOS_MODE: return "PROG_DOS_MODE";
+    }
+
+    return NULL;
+}
+
+/*
  *@@ appIsWindowsApp:
  *      checks the specified program category
@@ -657,4 +659,65 @@
 
     return (0);
+}
+
+/* ******************************************************************
+ *
+ *   Application start
+ *
+ ********************************************************************/
+
+/*
+ *@@ CallBatchCorrectly:
+ *      fixes the specified PROGDETAILS for
+ *      command files in the executable part
+ *      by inserting /C XXX into the parameters
+ *      and setting the executable according
+ *      to an environment variable.
+ *
+ *@@added V0.9.6 (2000-10-16) [umoeller]
+ *@@changed V0.9.7 (2001-01-15) [umoeller]: now using XSTRING
+ *@@changed V0.9.12 (2001-05-27) [umoeller]: moved from winh.c to apps.c
+ */
+
+VOID CallBatchCorrectly(PPROGDETAILS pProgDetails,
+                        PXSTRING pstrParams,        // in/out: modified parameters (reallocated)
+                        const char *pcszEnvVar,     // in: env var spec'g command proc
+                                                    // (e.g. "OS2_SHELL"); can be NULL
+                        const char *pcszDefProc)    // in: def't command proc (e.g. "CMD.EXE")
+{
+    // XXX.CMD file as executable:
+    // fix args to /C XXX.CMD
+
+    PSZ     pszOldParams = NULL;
+    ULONG   ulOldParamsLength = pstrParams->ulLength;
+    if (ulOldParamsLength)
+        // we have parameters already:
+        // make a backup... we'll append that later
+        pszOldParams = strdup(pstrParams->psz);
+
+    // set new params to "/C filename.cmd"
+    xstrcpy(pstrParams, "/C ", 0);
+    xstrcat(pstrParams,
+            pProgDetails->pszExecutable,
+            0);
+
+    if (pszOldParams)
+    {
+        // .cmd had params:
+        // append space and old params
+        xstrcatc(pstrParams, ' ');
+        xstrcat(pstrParams,
+                pszOldParams,
+                ulOldParamsLength);
+        free(pszOldParams);
+    }
+
+    // set executable to $(OS2_SHELL)
+    pProgDetails->pszExecutable = NULL;
+    if (pcszEnvVar)
+        pProgDetails->pszExecutable = getenv(pcszEnvVar);
+    if (!pProgDetails->pszExecutable)
+        pProgDetails->pszExecutable = (PSZ)pcszDefProc;
+                // should be on PATH
 }
 
Index: trunk/src/helpers/datetime.c
===================================================================
--- trunk/src/helpers/datetime.c	(revision 106)
+++ trunk/src/helpers/datetime.c	(revision 108)
@@ -147,6 +147,6 @@
 
 int dtCreateFileTimeStamp(PSZ pszTimeStamp,      // out: time stamp
-                            FDATE* pfdate,         // in: date
-                            FTIME* pftime)         // in: time
+                          FDATE* pfdate,         // in: date
+                          FTIME* pftime)         // in: time
 {
     return (sprintf(pszTimeStamp,
@@ -230,5 +230,5 @@
  *      returns TRUE if yr is a leap year.
  *
- *      (c) Ray Gardner.
+ *      (c) Ray Gardner. Public domain.
  */
 
@@ -260,5 +260,5 @@
  *      you then get 63.
  *
- *      (c) Ray Gardner.
+ *      (c) Ray Gardner. Public domain.
  */
 
@@ -272,5 +272,5 @@
  *      converts a year to the no. of days passed.
  *
- *      (c) Ray Gardner.
+ *      (c) Ray Gardner. Public domain.
  */
 
@@ -288,5 +288,5 @@
  *      the given date.
  *
- *      (c) Ray Gardner.
+ *      (c) Ray Gardner. Public domain.
  */
 
@@ -308,5 +308,5 @@
  *
  *
- *      (c) Ray Gardner.
+ *      (c) Ray Gardner. Public domain.
  */
 
Index: trunk/src/helpers/dialog.c
===================================================================
--- trunk/src/helpers/dialog.c	(revision 106)
+++ trunk/src/helpers/dialog.c	(revision 108)
@@ -227,16 +227,13 @@
 
 /*
- *@@ CalcAutoSizeText:
- *
- *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed various things with statics
- *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed broken fonts
- *@@changed V0.9.14 (2001-08-01) [umoeller]: now caching fonts, which is significantly faster
- */
-
-VOID CalcAutoSizeText(PCONTROLDEF pControlDef,
-                      BOOL fMultiLine,          // in: if TRUE, multiple lines
-                      PSIZEL pszlAuto,          // out: computed size
-                      PDLGPRIVATE pDlgData)
-{
+ *@@ SetDlgFont:
+ *
+ *@@added V0.9.16 (2001-10-11) [umoeller]
+ */
+
+VOID SetDlgFont(PCONTROLDEF pControlDef,
+                PDLGPRIVATE pDlgData)
+{
+    LONG lPointSize = 0;
     const char *pcszFontThis = pControlDef->pcszFont;
                     // can be NULL,
@@ -249,22 +246,20 @@
         pDlgData->hps = WinGetPS(pDlgData->hwndDlg);
 
-    if (pcszFontThis)
-    {
-        LONG lPointSize = 0;
-
-        // check if we can reuse font data from last time
-        // V0.9.14 (2001-08-01) [umoeller]
-        if (strhcmp(pcszFontThis,
-                    pDlgData->pcszFontLast))
-        {
-            // different font than last time:
-
-            // delete old font?
-            if (pDlgData->lcidLast)
-            {
-                GpiSetCharSet(pDlgData->hps, LCID_DEFAULT);
-                GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast);
-            }
-
+    // check if we can reuse font data from last time
+    // V0.9.14 (2001-08-01) [umoeller]
+    if (strhcmp(pcszFontThis,               // can be NULL!
+                pDlgData->pcszFontLast))
+    {
+        // different font than last time:
+
+        // delete old font?
+        if (pDlgData->lcidLast)
+        {
+            GpiSetCharSet(pDlgData->hps, LCID_DEFAULT);     // LCID_DEFAULT == 0
+            GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast);
+        }
+
+        if (pcszFontThis)
+        {
             // create new font
             pDlgData->lcidLast = gpihFindPresFont(NULLHANDLE,        // no window yet
@@ -278,11 +273,36 @@
             if (pDlgData->fmLast.fsDefn & FM_DEFN_OUTLINE)
                 gpihSetPointSize(pDlgData->hps, lPointSize);
-
-            pDlgData->pcszFontLast = pcszFontThis;
-        }
-
-        pszlAuto->cy =   pDlgData->fmLast.lMaxBaselineExt
-                       + pDlgData->fmLast.lExternalLeading;
-    }
+        }
+        else
+        {
+            // use default font:
+            // @@todo handle presparams, maybe inherited?
+            GpiSetCharSet(pDlgData->hps, LCID_DEFAULT);
+            GpiQueryFontMetrics(pDlgData->hps,
+                                sizeof(pDlgData->fmLast),
+                                &pDlgData->fmLast);
+        }
+
+        pDlgData->pcszFontLast = pcszFontThis;      // can be NULL
+    }
+}
+
+/*
+ *@@ CalcAutoSizeText:
+ *
+ *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed various things with statics
+ *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed broken fonts
+ *@@changed V0.9.14 (2001-08-01) [umoeller]: now caching fonts, which is significantly faster
+ */
+
+VOID CalcAutoSizeText(PCONTROLDEF pControlDef,
+                      BOOL fMultiLine,          // in: if TRUE, multiple lines
+                      PSIZEL pszlAuto,          // out: computed size
+                      PDLGPRIVATE pDlgData)
+{
+    SetDlgFont(pControlDef, pDlgData);
+
+    pszlAuto->cy =   pDlgData->fmLast.lMaxBaselineExt
+                   + pDlgData->fmLast.lExternalLeading;
 
     // ok, we FINALLY have a font now...
@@ -396,4 +416,12 @@
             }
         break;
+
+        default:
+            // any other control (just to be safe):
+            SetDlgFont(pControlDef, pDlgData);
+            pszlAuto->cx = 50;
+            pszlAuto->cy =   pDlgData->fmLast.lMaxBaselineExt
+                           + pDlgData->fmLast.lExternalLeading
+                           + 5;         // some space
     }
 }
@@ -566,4 +594,6 @@
     LHANDLE     lHandleSet = NULLHANDLE;
     ULONG       flOld = 0;
+
+    LONG        y, cy;              // for combo box hacks
 
     if (pColumnDef->fIsNestedTable)
@@ -588,4 +618,7 @@
                 pcszTitle = pControlDef->pcszText;
                 flStyle = pControlDef->flStyle;
+
+                y = pcp->y + pDlgData->ptlTotalOfs.y;
+                cy = pcp->cy;
             }
         }
@@ -598,4 +631,7 @@
         pcszTitle = pControlDef->pcszText;
         flStyle = pControlDef->flStyle;
+
+        y = pcp->y + pDlgData->ptlTotalOfs.y;
+        cy = pcp->cy;
 
         // change the title if this is a static with SS_BITMAP;
@@ -613,4 +649,14 @@
             pcszTitle = "";
             lHandleSet = (LHANDLE)pControlDef->pcszText;
+        }
+        // hack the stupid drop-down combobox which doesn't
+        // expand otherwise (the size of the drop-down is
+        // the full size of the control... duh)
+        else if (    ((ULONG)pControlDef->pcszClass == 0xffff0002L)
+                  && (flStyle & (CBS_DROPDOWN | CBS_DROPDOWNLIST))
+                )
+        {
+            y -= 100;
+            cy += 100;
         }
     }
@@ -640,7 +686,7 @@
                               flStyle,      // hacked
                               pcp->x + pDlgData->ptlTotalOfs.x,
-                              pcp->y + pDlgData->ptlTotalOfs.y,
+                              y,
                               pcp->cx,
-                              pcp->cy,
+                              cy,
                               pDlgData->hwndDlg,   // owner
                               HWND_BOTTOM,
Index: trunk/src/helpers/dosh.c
===================================================================
--- trunk/src/helpers/dosh.c	(revision 106)
+++ trunk/src/helpers/dosh.c	(revision 108)
@@ -69,5 +69,5 @@
 #pragma hdrstop
 
-static const CHAR  G_acDriveLetters[28] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+// static const CHAR  G_acDriveLetters[28] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
 /*
@@ -703,5 +703,5 @@
             PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2)fsqBuffer;
 
-            szName[0] = G_acDriveLetters[ulLogicalDrive];
+            szName[0] = ulLogicalDrive + 'A' - 1;
             szName[1] = ':';
             szName[2] = '\0';
@@ -753,5 +753,5 @@
                     &ulBootDrive,
                     sizeof(ulBootDrive));
-    return (G_acDriveLetters[ulBootDrive]);
+    return (ulBootDrive + 'A' - 1);
 }
 
@@ -1034,4 +1034,5 @@
  *@@changed V0.9.1 (99-12-12) [umoeller]: added cbBuf to prototype
  *@@changed V0.9.14 (2001-08-01) [umoeller]: fixed, this never respected cbBuf
+ *@@changed V0.9.16 (2001-10-02) [umoeller]: added check for valid logical disk no
  */
 
@@ -1048,28 +1049,33 @@
 
     // compose "D:"-type string from logical drive letter
-    szName[0] = G_acDriveLetters[ulLogicalDrive];
-    szName[1] = ':';
-    szName[2] = '\0';
-
-    arc = DosQueryFSAttach(szName,          // logical drive of attached FS ("D:"-style)
-                           0,               // ulOrdinal, ignored for FSAIL_QUERYNAME
-                           FSAIL_QUERYNAME, // return name for a drive or device
-                           pfsqBuffer,      // buffer for returned data
-                           &cbBuffer);      // sizeof(*pfsqBuffer)
-
-    if (arc == NO_ERROR)
-    {
-        if (pszBuf)
+    if (ulLogicalDrive > 0 && ulLogicalDrive < 27)
+    {
+        szName[0] = ulLogicalDrive + 'A' - 1;
+        szName[1] = ':';
+        szName[2] = '\0';
+
+        arc = DosQueryFSAttach(szName,          // logical drive of attached FS ("D:"-style)
+                               0,               // ulOrdinal, ignored for FSAIL_QUERYNAME
+                               FSAIL_QUERYNAME, // return name for a drive or device
+                               pfsqBuffer,      // buffer for returned data
+                               &cbBuffer);      // sizeof(*pfsqBuffer)
+
+        if (arc == NO_ERROR)
         {
-            // The data for the last three fields in the FSQBUFFER2
-            // structure are stored at the offset of fsqBuffer.szName.
-            // Each data field following fsqBuffer.szName begins
-            // immediately after the previous item.
-            strncpy(pszBuf,
-                    (CHAR*)(&pfsqBuffer->szName) + pfsqBuffer->cbName + 1,
-                    cbBuf);         // V0.9.14 (2001-08-01) [umoeller]
-            *(pszBuf + cbBuf) = '\0';
+            if (pszBuf)
+            {
+                // The data for the last three fields in the FSQBUFFER2
+                // structure are stored at the offset of fsqBuffer.szName.
+                // Each data field following fsqBuffer.szName begins
+                // immediately after the previous item.
+                strncpy(pszBuf,
+                        (CHAR*)(&pfsqBuffer->szName) + pfsqBuffer->cbName + 1,
+                        cbBuf);         // V0.9.14 (2001-08-01) [umoeller]
+                *(pszBuf + cbBuf) = '\0';
+            }
         }
     }
+    else
+        arc = ERROR_INVALID_PARAMETER; // V0.9.16 (2001-10-02) [umoeller]
 
     return (arc);
@@ -1883,7 +1889,8 @@
         ULONG   cbWritten;
         DosGetDateTime(&dt);
-        sprintf(szTemp, "Time: %02d:%02d:%02d %s",
-            dt.hours, dt.minutes, dt.seconds,
-            pcsz);
+        sprintf(szTemp,
+                "Time: %02d:%02d:%02d %s",
+                dt.hours, dt.minutes, dt.seconds,
+                pcsz);
         return (DosWrite(hfLog, (PVOID)szTemp, strlen(szTemp), &cbWritten));
     }
@@ -2000,9 +2007,8 @@
     ULONG   ulCurDisk = 0;
     ULONG   ulMap = 0;
-    arc = DosQueryCurrentDisk(&ulCurDisk, &ulMap);
-    if (arc == NO_ERROR)
+    if (!(arc = DosQueryCurrentDisk(&ulCurDisk, &ulMap)))
     {
         ULONG   cbBuf = CCHMAXPATH - 3;
-        *pszBuf = G_acDriveLetters[ulCurDisk];
+        *pszBuf = ulCurDisk + 'A' - 1;
         *(pszBuf + 1) = ':';
         *(pszBuf + 2) = '\\';
@@ -2270,74 +2276,51 @@
 
         // resolve DosPerfSysCall API entry
-        arc = DosLoadModule(NULL, 0, "DOSCALLS", &pPerfSys->hmod);
-        if (arc == NO_ERROR)
+        if (!(arc = DosLoadModule(NULL, 0, "DOSCALLS", &pPerfSys->hmod)))
         {
-            arc = DosQueryProcAddr(pPerfSys->hmod,
-                                   976,
-                                   "DosPerfSysCall",
-                                   (PFN*)(&pPerfSys->pDosPerfSysCall));
-            if (arc == NO_ERROR)
+            if (!(arc = DosQueryProcAddr(pPerfSys->hmod,
+                                         976,
+                                         "DosPerfSysCall",
+                                         (PFN*)(&pPerfSys->pDosPerfSysCall))))
             {
                 // OK, we got the API: initialize!
-                arc = pPerfSys->pDosPerfSysCall(CMD_KI_ENABLE, 0, 0, 0);
-                if (arc == NO_ERROR)
+                if (!(arc = pPerfSys->pDosPerfSysCall(CMD_KI_ENABLE, 0, 0, 0)))
                 {
                     pPerfSys->fInitialized = TRUE;
                             // call CMD_KI_DISABLE later
 
-                    arc = pPerfSys->pDosPerfSysCall(CMD_PERF_INFO,
-                                                    0,
-                                                    (ULONG)(&pPerfSys->cProcessors),
-                                                    0);
-                    if (arc == NO_ERROR)
+                    if (!(arc = pPerfSys->pDosPerfSysCall(CMD_PERF_INFO,
+                                                          0,
+                                                          (ULONG)(&pPerfSys->cProcessors),
+                                                          0)))
                     {
-                        ULONG   ul = 0;
+                        ULONG   ul = 0,
+                                cProcs = pPerfSys->cProcessors,
+                                cbDouble = cProcs * sizeof(double),
+                                cbLong = cProcs * sizeof(LONG);
 
                         // allocate arrays
-                        pPerfSys->paCPUUtils = (PCPUUTIL)calloc(pPerfSys->cProcessors,
-                                                                sizeof(CPUUTIL));
-                        if (!pPerfSys->paCPUUtils)
+                        if (    (!(pPerfSys->paCPUUtils = (PCPUUTIL)calloc(cProcs,
+                                                                sizeof(CPUUTIL))))
+                             || (!(pPerfSys->padBusyPrev
+                                    = (double*)malloc(cbDouble)))
+                             || (!(pPerfSys->padTimePrev
+                                    = (double*)malloc(cbDouble)))
+                             || (!(pPerfSys->padIntrPrev
+                                    = (double*)malloc(cbDouble)))
+                             || (!(pPerfSys->palLoads
+                                    = (PLONG)malloc(cbLong)))
+                             || (!(pPerfSys->palIntrs
+                                    = (PLONG)malloc(cbLong)))
+                           )
                             arc = ERROR_NOT_ENOUGH_MEMORY;
                         else
                         {
-                            pPerfSys->padBusyPrev = (double*)malloc(pPerfSys->cProcessors * sizeof(double));
-                            if (!pPerfSys->padBusyPrev)
-                                arc = ERROR_NOT_ENOUGH_MEMORY;
-                            else
+                            for (ul = 0; ul < cProcs; ul++)
                             {
-                                pPerfSys->padTimePrev
-                                    = (double*)malloc(pPerfSys->cProcessors * sizeof(double));
-                                if (!pPerfSys->padTimePrev)
-                                    arc = ERROR_NOT_ENOUGH_MEMORY;
-                                else
-                                {
-                                    pPerfSys->padIntrPrev
-                                        = (double*)malloc(pPerfSys->cProcessors * sizeof(double));
-                                    if (!pPerfSys->padIntrPrev)
-                                        arc = ERROR_NOT_ENOUGH_MEMORY;
-                                    else
-                                    {
-                                        pPerfSys->palLoads = (PLONG)malloc(pPerfSys->cProcessors * sizeof(LONG));
-                                        if (!pPerfSys->palLoads)
-                                            arc = ERROR_NOT_ENOUGH_MEMORY;
-                                        else
-                                        {
-                                            pPerfSys->palIntrs = (PLONG)malloc(pPerfSys->cProcessors * sizeof(LONG));
-                                            if (!pPerfSys->palIntrs)
-                                                arc = ERROR_NOT_ENOUGH_MEMORY;
-                                            else
-                                            {
-                                                for (ul = 0; ul < pPerfSys->cProcessors; ul++)
-                                                {
-                                                    pPerfSys->padBusyPrev[ul] = 0.0;
-                                                    pPerfSys->padTimePrev[ul] = 0.0;
-                                                    pPerfSys->padIntrPrev[ul] = 0.0;
-                                                    pPerfSys->palLoads[ul] = 0;
-                                                    pPerfSys->palIntrs[ul] = 0;
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
+                                pPerfSys->padBusyPrev[ul] = 0.0;
+                                pPerfSys->padTimePrev[ul] = 0.0;
+                                pPerfSys->padIntrPrev[ul] = 0.0;
+                                pPerfSys->palLoads[ul] = 0;
+                                pPerfSys->palIntrs[ul] = 0;
                             }
                         }
@@ -2347,8 +2330,8 @@
         } // end if (arc == NO_ERROR)
 
-        if (arc != NO_ERROR)
-        {
+        if (arc)
+            // error: clean up
             doshPerfClose(ppPerfSys);
-        }
+
     } // end else if (!*ppPerfSys)
 
@@ -2405,8 +2388,7 @@
     else
     {
-        arc = pPerfSys->pDosPerfSysCall(CMD_KI_RDCNT,
-                                        (ULONG)pPerfSys->paCPUUtils,
-                                        0, 0);
-        if (arc == NO_ERROR)
+        if (!(arc = pPerfSys->pDosPerfSysCall(CMD_KI_RDCNT,
+                                              (ULONG)pPerfSys->paCPUUtils,
+                                              0, 0)))
         {
             // go thru all processors
Index: trunk/src/helpers/dosh2.c
===================================================================
--- trunk/src/helpers/dosh2.c	(revision 106)
+++ trunk/src/helpers/dosh2.c	(revision 108)
@@ -2186,8 +2186,130 @@
 
 /*
+ *@@ CopyToBuffer:
+ *      little helper for copying a string to
+ *      a target buffer with length checking.
+ *
+ *      Returns:
+ *
+ *      --  NO_ERROR
+ *
+ *      --  ERROR_BUFFER_OVERFLOW if pszTarget does
+ *          not have enough room to hold pcszSource
+ *          (including the null terminator).
+ *
+ *@@added V0.9.16 (2001-10-08) [umoeller]
+ */
+
+APIRET CopyToBuffer(PSZ pszTarget,      // out: target buffer
+                    PCSZ pcszSource,    // in: source string
+                    ULONG cbTarget)     // in: size of target buffer
+{
+    ULONG ulLength = strlen(pcszSource);
+    if (ulLength < cbTarget)
+    {
+        memcpy(pszTarget,
+               pcszSource,
+               ulLength + 1);
+        return (NO_ERROR);
+    }
+
+    return(ERROR_BUFFER_OVERFLOW);
+}
+
+/*
+ *@@ doshSearchPath:
+ *      replacement for DosSearchPath.
+ *
+ *      This looks along all directories which are
+ *      specified in the value of the given environment
+ *      variable if pcszFile is found.
+ *
+ *      As opposed to the stupid DosSearchPath, this
+ *      ignores subdirectories in the path particles.
+ *      For example, DosSearchPath would usually not
+ *      find an INSTALL file because \OS2 contains
+ *      an INSTALL directory, or NETSCAPE because
+ *      \OS2\INSTALL contains a NETSCAPE directory.
+ *
+ *      Returns:
+ *
+ *      --  NO_ERROR: pszExecutable has received the
+ *          full path of pcszFile.
+ *
+ *      --  ERROR_FILE_NOT_FOUND: pcszFile was not found
+ *          in the specified path (or is a directory).
+ *
+ *      --  ERROR_BUFFER_OVERFLOW: pcszFile was found, but
+ *          the pszExecutable buffer is too small to hold
+ *          the full path.
+ *
+ *@@added V0.9.16 (2001-10-08) [umoeller]
+ */
+
+APIRET doshSearchPath(const char *pcszPath,     // in: path variable name (e.g. "PATH")
+                      const char *pcszFile,     // in: file to look for (e.g. "LVM.EXE")
+                      PSZ pszExecutable,        // out: full path (e.g. "F:\os2\lvm.exe")
+                      ULONG cbExecutable)       // in: sizeof (*pszExecutable)
+{
+    APIRET arc = NO_ERROR;
+
+    // get the PATH value
+    PSZ pszPath;
+    if (!(arc = DosScanEnv((PSZ)pcszPath,
+                           &pszPath)))
+    {
+        // run thru the path components
+        PSZ pszPathCopy;
+        if (pszPathCopy = strdup(pszPath))
+        {
+            PSZ pszToken = strtok(pszPathCopy, ";");
+            while (pszToken)        // V0.9.12 (2001-05-03) [umoeller]
+            {
+                CHAR szFileMask[2*CCHMAXPATH];
+                FILESTATUS3 fs3;
+
+                sprintf(szFileMask,
+                        "%s\\%s",
+                        pszToken,           // path particle
+                        pcszFile);          // e.g. "netscape"
+
+                if (    (!(arc = DosQueryPathInfo(szFileMask,
+                                                  FIL_STANDARD,
+                                                  &fs3,
+                                                  sizeof(fs3))))
+                     // make sure it's not a directory
+                     // and that it's not hidden
+                     && (!(fs3.attrFile & (FILE_DIRECTORY | FILE_HIDDEN)))
+                   )
+                {
+                    // copy
+                    arc = CopyToBuffer(pszExecutable,
+                                       szFileMask,
+                                       cbExecutable);
+                    // and stop
+                    break;
+                }
+                else
+                    arc = ERROR_FILE_NOT_FOUND;
+                    // and search on
+
+                pszToken = strtok(NULL, ";");
+            };
+
+            free(pszPathCopy);
+        }
+        else
+            arc = ERROR_NOT_ENOUGH_MEMORY;
+    }
+
+    return (arc);
+}
+
+/*
  * FindFile:
  *      helper for doshFindExecutable.
  *
  *added V0.9.11 (2001-04-25) [umoeller]
+ *@@changed V0.9.16 (2001-10-08) [umoeller]: rewrote second half for DosSearchPath replacement, which returns directories too
  */
 
@@ -2210,7 +2332,7 @@
         if (!arc)
             if (!(fs3.attrFile & FILE_DIRECTORY))
-                strhncpy0(pszExecutable,
-                          pcszCommand,
-                          cbExecutable);
+                arc = CopyToBuffer(pszExecutable,
+                                   pcszCommand,
+                                   cbExecutable);
             else
                 // directory:
@@ -2218,10 +2340,22 @@
     }
     else
+    {
         // non-qualified:
-        arc = DosSearchPath(SEARCH_IGNORENETERRS | SEARCH_ENVIRONMENT | SEARCH_CUR_DIRECTORY,
+        /* arc = DosSearchPath(SEARCH_IGNORENETERRS
+                                | SEARCH_ENVIRONMENT
+                                | SEARCH_CUR_DIRECTORY,
                             "PATH",
                             (PSZ)pcszCommand,
                             pszExecutable,
-                            cbExecutable);
+                            cbExecutable); */
+            // The above is not useable. It returns directories
+            // on the path... for example, it returns \OS2\INSTALL\NETSCAPE
+            // if netscape is looked for. So we search manually... sigh.
+            // V0.9.16 (2001-10-08) [umoeller]
+        arc = doshSearchPath("PATH",
+                             pcszCommand,
+                             pszExecutable,
+                             cbExecutable);
+    }
 
     return (arc);
@@ -2235,11 +2369,14 @@
  *      1)  If pcszCommand appears to be qualified (i.e. contains
  *          a backslash), this checks for whether the file exists.
- *
- *      2)  If pcszCommand contains no backslash, this calls
- *          DosSearchPath in order to find the full path of the
- *          executable.
+ *          If it is a directory, ERROR_INVALID_EXE_SIGNATURE is
+ *          returned.
+ *
+ *      2)  If pcszCommand contains no backslash, this searches
+ *          all directories on the PATH in order to find the full
+ *          path of the executable. Starting with V0.9.16, we
+ *          use doshSearchPath for that.
  *
  *      papcszExtensions determines if additional searches are to be
- *      performed if the file doesn't exist (case 1) or DosSearchPath
+ *      performed if the file doesn't exist (case 1) or doshSearchPath
  *      returned ERROR_FILE_NOT_FOUND (case 2).
  *      This must point to an array of strings specifying the extra
@@ -2249,7 +2386,14 @@
  *      extra searches are performed.
  *
- *      If this returns NO_ERROR, pszExecutable receives
- *      the full path of the executable found by DosSearchPath.
- *      Otherwise ERROR_FILE_NOT_FOUND is returned.
+ *      Returns:
+ *
+ *      --  NO_ERROR: pszExecutable has received the full path of
+ *          the executable found by DosSearchPath.
+ *
+ *      --  ERROR_FILE_NOT_FOUND
+ *
+ *      --  ERROR_BUFFER_OVERFLOW: pcszCommand was found, but
+ *          the pszExecutable buffer is too small to hold
+ *          the full path.
  *
  *      Example:
@@ -2294,5 +2438,8 @@
             {
                 const char *pcszExtThis = papcszExtensions[ul];
-                sprintf(psz2, "%s.%s", pcszCommand, pcszExtThis);
+                sprintf(psz2,
+                        "%s.%s",
+                        pcszCommand,
+                        pcszExtThis);
                 arc = FindFile(psz2,
                                pszExecutable,
@@ -2328,6 +2475,4 @@
  *      on the system.
  *
- *      Based on code (C) Dmitry A. Steklenev.
- *
  *@@added V0.9.0 [umoeller]
  */
@@ -2335,57 +2480,7 @@
 UINT doshQueryDiskCount(VOID)
 {
-    USHORT count = 0;
-
-    DosPhysicalDisk(INFO_COUNT_PARTITIONABLE_DISKS, &count, 2, 0, 0);
-    return (count);
-}
-
-/*
- *@@ doshReadSector:
- *      reads a physical disk sector.
- *
- *      If NO_ERROR is returned, the sector contents
- *      have been stored in *buff.
- *
- *      Based on code (C) Dmitry A. Steklenev.
- *
- *@@added V0.9.0 [umoeller]
- *@@changed V0.9.9 (2001-04-04) [umoeller]: added more error checking
- */
-
-APIRET doshReadSector(USHORT disk,      // in: physical disk no. (1, 2, 3, ...)
-                      void *buff,
-                      USHORT head,
-                      USHORT cylinder,
-                      USHORT sector)
-{
-    APIRET  arc;
-    HFILE   dh = 0;
-    char    dn[256];
-
-    sprintf(dn, "%u:", disk);
-    if (!(arc = DosPhysicalDisk(INFO_GETIOCTLHANDLE, &dh, 2, dn, 3)))
-    {
-        TRACKLAYOUT DiskIOParm;
-        ULONG IOCtlDataLength = sizeof(DiskIOParm);
-        ULONG IOCtlParmLength = 512;
-
-        DiskIOParm.bCommand = 0;
-        DiskIOParm.usHead = head;
-        DiskIOParm.usCylinder = cylinder;
-        DiskIOParm.usFirstSector = 0;
-        DiskIOParm.cSectors = 1;
-        DiskIOParm.TrackTable[0].usSectorNumber = sector;
-        DiskIOParm.TrackTable[0].usSectorSize = 512;
-
-        arc = DosDevIOCtl(dh,
-                          IOCTL_PHYSICALDISK, PDSK_READPHYSTRACK,
-                          &DiskIOParm, IOCtlParmLength, &IOCtlParmLength,
-                          buff       , IOCtlDataLength, &IOCtlDataLength);
-
-        DosPhysicalDisk(INFO_FREEIOCTLHANDLE, 0, 0, &dh, 2);
-    }
-
-    return (arc);
+    USHORT usCount = 0;
+    DosPhysicalDisk(INFO_COUNT_PARTITIONABLE_DISKS, &usCount, 2, 0, 0);
+    return (usCount);
 }
 
@@ -2393,275 +2488,111 @@
  *@@ doshType2FSName:
  *      this returns a static, zero-terminated string
- *      for the given FS type. This is always 7 bytes
- *      in length.
- *
- *      Values for operating system indicator:
- *      --  00h  empty
- *      --  01h  DOS 12-bit FAT
- *      --  02h  XENIX root file system
- *      --  03h  XENIX /usr file system (obsolete)
- *      --  04h  DOS 16-bit FAT (up to 32M)
- *      --  05h  DOS 3.3+ extended partition
- *      --  06h  DOS 3.31+ Large File System (16-bit FAT, over 32M)
- *      --  07h  QNX
- *      --  07h  OS/2 HPFS
- *      --  07h  Windows NT NTFS
- *      --  07h  Advanced Unix
- *      --  08h  OS/2 (v1.0-1.3 only)
- *      --  08h  AIX bootable partition, SplitDrive
- *      --  08h  Commodore DOS
- *      --  08h  DELL partition spanning multiple drives
- *      --  09h  AIX data partition
- *      --  09h  Coherent filesystem
- *      --  0Ah  OS/2 Boot Manager
- *      --  0Ah  OPUS
- *      --  0Ah  Coherent swap partition
- *      --  0Bh  Windows95 with 32-bit FAT
- *      --  0Ch  Windows95 with 32-bit FAT (using LBA-mode INT 13 extensions)
- *      --  0Eh  logical-block-addressable VFAT (same as 06h but using LBA-mode INT 13)
- *      --  0Fh  logical-block-addressable VFAT (same as 05h but using LBA-mode INT 13)
- *      --  10h  OPUS
- *      --  11h  OS/2 Boot Manager hidden 12-bit FAT partition
- *      --  12h  Compaq Diagnostics partition
- *      --  14h  (resulted from using Novell DOS 7.0 FDISK to delete Linux Native part)
- *      --  14h  OS/2 Boot Manager hidden sub-32M 16-bit FAT partition
- *      --  16h  OS/2 Boot Manager hidden over-32M 16-bit FAT partition
- *      --  17h  OS/2 Boot Manager hidden HPFS partition
- *      --  18h  AST special Windows swap file ("Zero-Volt Suspend" partition)
- *      --  21h  officially listed as reserved
- *      --  23h  officially listed as reserved
- *      --  24h  NEC MS-DOS 3.x
- *      --  26h  officially listed as reserved
- *      --  31h  officially listed as reserved
- *      --  33h  officially listed as reserved
- *      --  34h  officially listed as reserved
- *      --  36h  officially listed as reserved
- *      --  38h  Theos
- *      --  3Ch  PowerQuest PartitionMagic recovery partition
- *      --  40h  VENIX 80286
- *      --  41h  Personal RISC Boot
- *      --  42h  SFS (Secure File System) by Peter Gutmann
- *      --  50h  OnTrack Disk Manager, read-only partition
- *      --  51h  OnTrack Disk Manager, read/write partition
- *      --  51h  NOVEL
- *      --  52h  CP/M
- *      --  52h  Microport System V/386
- *      --  53h  OnTrack Disk Manager, write-only partition???
- *      --  54h  OnTrack Disk Manager (DDO)
- *      --  56h  GoldenBow VFeature
- *      --  61h  SpeedStor
- *      --  63h  Unix SysV/386, 386/ix
- *      --  63h  Mach, MtXinu BSD 4.3 on Mach
- *      --  63h  GNU HURD
- *      --  64h  Novell NetWare 286
- *      --  65h  Novell NetWare (3.11)
- *      --  67h  Novell
- *      --  68h  Novell
- *      --  69h  Novell
- *      --  70h  DiskSecure Multi-Boot
- *      --  71h  officially listed as reserved
- *      --  73h  officially listed as reserved
- *      --  74h  officially listed as reserved
- *      --  75h  PC/IX
- *      --  76h  officially listed as reserved
- *      --  80h  Minix v1.1 - 1.4a
- *      --  81h  Minix v1.4b+
- *      --  81h  Linux
- *      --  81h  Mitac Advanced Disk Manager
- *      --  82h  Linux Swap partition
- *      --  82h  Prime
- *      --  83h  Linux native file system (ext2fs/xiafs)
- *      --  84h  OS/2-renumbered type 04h partition (related to hiding DOS C: drive)
- *      --  86h  FAT16 volume/stripe set (Windows NT)
- *      --  87h  HPFS Fault-Tolerant mirrored partition
- *      --  87h  NTFS volume/stripe set
- *      --  93h  Amoeba file system
- *      --  94h  Amoeba bad block table
- *      --  A0h  Phoenix NoteBIOS Power Management "Save-to-Disk" partition
- *      --  A1h  officially listed as reserved
- *      --  A3h  officially listed as reserved
- *      --  A4h  officially listed as reserved
- *      --  A5h  FreeBSD, BSD/386
- *      --  A6h  officially listed as reserved
- *      --  B1h  officially listed as reserved
- *      --  B3h  officially listed as reserved
- *      --  B4h  officially listed as reserved
- *      --  B6h  officially listed as reserved
- *      --  B7h  BSDI file system (secondarily swap)
- *      --  B8h  BSDI swap partition (secondarily file system)
- *      --  C1h  DR DOS 6.0 LOGIN.EXE-secured 12-bit FAT partition
- *      --  C4h  DR DOS 6.0 LOGIN.EXE-secured 16-bit FAT partition
- *      --  C6h  DR DOS 6.0 LOGIN.EXE-secured Huge partition
- *      --  C6h  corrupted FAT16 volume/stripe set (Windows NT)
- *      --  C7h  Syrinx Boot
- *      --  C7h  corrupted NTFS volume/stripe set
- *      --  D8h  CP/M-86
- *      --  DBh  CP/M, Concurrent CP/M, Concurrent DOS
- *      --  DBh  CTOS (Convergent Technologies OS)
- *      --  E1h  SpeedStor 12-bit FAT extended partition
- *      --  E3h  DOS read-only
- *      --  E3h  Storage Dimensions
- *      --  E4h  SpeedStor 16-bit FAT extended partition
- *      --  E5h  officially listed as reserved
- *      --  E6h  officially listed as reserved
- *      --  F1h  Storage Dimensions
- *      --  F2h  DOS 3.3+ secondary partition
- *      --  F3h  officially listed as reserved
- *      --  F4h  SpeedStor
- *      --  F4h  Storage Dimensions
- *      --  F6h  officially listed as reserved
- *      --  FEh  LANstep
- *      --  FEh  IBM PS/2 IML
- *      --  FFh  Xenix bad block table
- *
- *      Note: for partition type 07h, one should inspect the partition boot record
- *            for the actual file system type
- *
- *      Based on code (C) Dmitry A. Steklenev.
+ *      for the given FS type, or NULL if the type
+ *      is unknown.
  *
  *@@added V0.9.0 [umoeller]
+ *@@changed V0.9.16 (2001-10-08) [umoeller]: rewritten
  */
 
 const char* doshType2FSName(unsigned char bFSType)  // in: FS type
 {
-    PSZ zFSName = NULL;
-
     switch (bFSType)
     {
-        case PAR_UNUSED:
-            zFSName = "UNUSED ";
-            break;
-        case PAR_FAT12SMALL:
-            zFSName = "FAT-12 ";
-            break;
-        case PAR_XENIXROOT:
-            zFSName = "XENIX  ";
-            break;
-        case PAR_XENIXUSER:
-            zFSName = "XENIX  ";
-            break;
-        case PAR_FAT16SMALL:
-            zFSName = "FAT-16 ";
-            break;
-        case PAR_EXTENDED:
-            zFSName = "EXTEND ";
-            break;
-        case PAR_FAT16BIG:
-            zFSName = "BIGDOS ";
-            break;
-        case PAR_HPFS:
-            zFSName = "HPFS   ";
-            break;
-        case PAR_AIXBOOT:
-            zFSName = "AIX    ";
-            break;
-        case PAR_AIXDATA:
-            zFSName = "AIX    ";
-            break;
-        case PAR_BOOTMANAGER:
-            zFSName = "BOOTMNG";
-            break;
-        case PAR_WINDOWS95:
-            zFSName = "WIN95  ";
-            break;
-        case PAR_WINDOWS95LB:
-            zFSName = "WIN95  ";
-            break;
-        case PAR_VFAT16BIG:
-            zFSName = "VFAT   ";
-            break;
-        case PAR_VFAT16EXT:
-            zFSName = "VFAT   ";
-            break;
-        case PAR_OPUS:
-            zFSName = "OPUS   ";
-            break;
-        case PAR_HID12SMALL:
-            zFSName = "FAT-12*";
-            break;
-        case PAR_COMPAQDIAG:
-            zFSName = "COMPAQ ";
-            break;
-        case PAR_HID16SMALL:
-            zFSName = "FAT-16*";
-            break;
-        case PAR_HID16BIG:
-            zFSName = "BIGDOS*";
-            break;
-        case PAR_HIDHPFS:
-            zFSName = "HPFS*  ";
-            break;
-        case PAR_WINDOWSSWP:
-            zFSName = "WINSWAP";
-            break;
-        case PAR_NECDOS:
-            zFSName = "NECDOS ";
-            break;
-        case PAR_THEOS:
-            zFSName = "THEOS  ";
-            break;
-        case PAR_VENIX:
-            zFSName = "VENIX  ";
-            break;
-        case PAR_RISCBOOT:
-            zFSName = "RISC   ";
-            break;
-        case PAR_SFS:
-            zFSName = "SFS    ";
-            break;
-        case PAR_ONTRACK:
-            zFSName = "ONTRACK";
-            break;
-        case PAR_ONTRACKEXT:
-            zFSName = "ONTRACK";
-            break;
-        case PAR_CPM:
-            zFSName = "CP/M   ";
-            break;
-        case PAR_UNIXSYSV:
-            zFSName = "UNIX   ";
-            break;
-        case PAR_NOVELL_64:
-            zFSName = "NOVELL ";
-            break;
-        case PAR_NOVELL_65:
-            zFSName = "NOVELL ";
-            break;
-        case PAR_NOVELL_67:
-            zFSName = "NOVELL ";
-            break;
-        case PAR_NOVELL_68:
-            zFSName = "NOVELL ";
-            break;
-        case PAR_NOVELL_69:
-            zFSName = "NOVELL ";
-            break;
-        case PAR_PCIX:
-            zFSName = "PCIX   ";
-            break;
-        case PAR_MINIX:
-            zFSName = "MINIX  ";
-            break;
-        case PAR_LINUX:
-            zFSName = "LINUX  ";
-            break;
-        case PAR_LINUXSWAP:
-            zFSName = "LNXSWP ";
-            break;
-        case PAR_LINUXFILE:
-            zFSName = "LINUX  ";
-            break;
-        case PAR_FREEBSD:
-            zFSName = "FREEBSD";
-            break;
-        case PAR_BBT:
-            zFSName = "BBT    ";
-            break;
-
-        default:
-            zFSName = "       ";
-            break;
+        case 0x00: return "empty";
+        case 0x01: return "DOS 12-bit FAT < 10 Mb";
+        case 0x02: return "XENIX root file system";
+        case 0x03: return "XENIX /usr file system (obsolete)";
+        case 0x04: return "DOS 16-bit FAT < 32 Mb";
+        case 0x05: return "DOS 3.3+ extended partition";
+        case 0x06: return "DOS 3.31+ 16-bit FAT > 32 Mb";
+        case 0x07: return "HPFS/NTFS/QNX/Advanced Unix";
+        case 0x08: return "OS/2 1.0-1.3/AIX/Commodore/DELL";
+        case 0x09: return "AIX data/Coherent";
+        case 0x0A: return "OS/2 Boot Manager/OPUS/Coherent Swap";
+        case 0x0B: return "Windows95 with 32-bit FAT";
+        case 0x0C: return "Windows95 with 32-bit FAT (LBA)";
+        case 0x0E: return "Windows 95 VFAT (06h plus LBA)";
+        case 0x0F: return "Windows 95 VFAT (05h plus LBA)";
+        case 0x10: return "OPUS";
+        case 0x11: return "OS/2 Boot Manager hidden 12-bit FAT";
+        case 0x12: return "Compaq Diagnostics";
+        case 0x14: return "OS/2 Boot Manager hidden sub-32M 16-bit FAT";
+        case 0x16: return "OS/2 Boot Manager hidden over-32M 16-bit FAT";
+        case 0x17: return "OS/2 Boot Manager hidden HPFS";
+        case 0x18: return "AST special Windows swap file (\"Zero-Volt Suspend\")";
+        // case 0x21: reserved
+        // case 0x23: reserved
+        case 0x24: return "NEC MS-DOS 3.x";
+        // case 0x26: reserved
+        // case 0x31: reserved
+        // case 0x33: reserved
+        // case 0x34: reserved
+        // case 0x36: reserved
+        case 0x38: return "Theos";
+        case 0x3C: return "PowerQuest PartitionMagic recovery partition";
+        case 0x40: return "VENIX 80286";
+        case 0x41: return "Personal RISC Boot";
+        case 0x42: return "SFS (Secure File System) by Peter Gutmann";
+        case 0x50: return "OnTrack Disk Manager, read-only";
+        case 0x51: return "OnTrack Disk Manager, read/write";
+        case 0x52: return "CP/M or Microport System V/386";
+        case 0x53: return "OnTrack Disk Manager, write-only???";
+        case 0x54: return "OnTrack Disk Manager (DDO)";
+        case 0x56: return "GoldenBow VFeature";
+        case 0x61: return "SpeedStor";
+        case 0x63: return "Unix SysV/386, 386/ix or Mach, MtXinu BSD 4.3 on Mach or GNU HURD";
+        case 0x64: return "Novell NetWare 286";
+        case 0x65: return "Novell NetWare (3.11)";
+        case 0x67:
+        case 0x68:
+        case 0x69: return "Novell";
+        case 0x70: return "DiskSecure Multi-Boot";
+        // case 0x71: reserved
+        // case 0x73: reserved
+        // case 0x74: reserved
+        case 0x75: return "PC/IX";
+        // case 0x76: reserved
+        case 0x80: return "Minix v1.1 - 1.4a";
+        case 0x81: return "Minix v1.4b+ or Linux or Mitac Advanced Disk Manager";
+        case 0x82: return "Linux Swap or Prime";
+        case 0x83: return "Linux native file system (ext2fs/xiafs)";
+        case 0x84: return "OS/2-renumbered type 04h (hidden DOS C: drive)";
+        case 0x86: return "FAT16 volume/stripe set (Windows NT)";
+        case 0x87: return "HPFS Fault-Tolerant mirrored partition or NTFS volume/stripe set";
+        case 0x93: return "Amoeba file system";
+        case 0x94: return "Amoeba bad block table";
+        case 0xA0: return "Phoenix NoteBIOS Power Management \"Save-to-Disk\" partition";
+        // case 0xA1: reserved
+        // case 0xA3: reserved
+        // case 0xA4: reserved
+        case 0xA5: return "FreeBSD, BSD/386";
+        // case 0xA6: reserved
+        // case 0xB1: reserved
+        // case 0xB3: reserved
+        // case 0xB4: reserved
+        // case 0xB6: reserved
+        case 0xB7: return "BSDI file system (secondarily swap)";
+        case 0xB8: return "BSDI swap (secondarily file system)";
+        case 0xC1: return "DR DOS 6.0 LOGIN.EXE-secured 12-bit FAT";
+        case 0xC4: return "DR DOS 6.0 LOGIN.EXE-secured 16-bit FAT";
+        case 0xC6: return "DR DOS 6.0 LOGIN.EXE-secured Huge partition or NT corrupted FAT16 volume/stripe set";
+        case 0xC7: return "Syrinx Boot or corrupted NTFS volume/stripe set";
+        case 0xD8: return "CP/M-86";
+        case 0xDB: return "CP/M, Concurrent CP/M, Concurrent DOS, Convergent Technologies OS";
+        case 0xE1: return "SpeedStor 12-bit FAT extended partition";
+        case 0xE3: return "DOS read-only or Storage Dimensions";
+        case 0xE4: return "SpeedStor 16-bit FAT extended partition";
+        // case 0xE5: reserved
+        // case 0xE6: reserved
+        case 0xF1: return "Storage Dimensions";
+        case 0xF2: return "DOS 3.3+ secondary partition";
+        // case 0xF3: reserved
+        case 0xF4: return "SpeedStor or Storage Dimensions";
+        // case 0xF6: reserved
+        case 0xFE: return "LANstep or IBM PS/2 IML";
+        case 0xFF: return "Xenix bad block table";
     }
-    return zFSName;
+
+    return NULL;
 }
 
@@ -2678,6 +2609,4 @@
  *      to point to the newly created PARTITIONINFO, so before
  *      calling this function for the first time,
- *
- *      Based on code (C) Dmitry A. Steklenev.
  *
  *@@added V0.9.0 [umoeller]
@@ -2712,6 +2641,5 @@
         ppiNew->cLetter = cLetter;
         ppiNew->bFSType = bFsType;
-        strcpy(ppiNew->szFSType,
-               doshType2FSName(bFsType));
+        ppiNew->pcszFSType = doshType2FSName(bFsType);
         ppiNew->fPrimary = fPrimary;
         ppiNew->fBootable = fBootable;
@@ -2741,4 +2669,55 @@
 }
 
+#ifndef __XWPLITE__
+
+/*
+ *@@ doshReadSector:
+ *      reads a physical disk sector.
+ *
+ *      If NO_ERROR is returned, the sector contents
+ *      have been stored in *buff.
+ *
+ *      Originally contributed by Dmitry A. Steklenev.
+ *
+ *@@added V0.9.0 [umoeller]
+ *@@changed V0.9.9 (2001-04-04) [umoeller]: added more error checking
+ */
+
+APIRET doshReadSector(USHORT disk,      // in: physical disk no. (1, 2, 3, ...)
+                      void *buff,
+                      USHORT head,
+                      USHORT cylinder,
+                      USHORT sector)
+{
+    APIRET  arc;
+    HFILE   dh = 0;
+    char    dn[256];
+
+    sprintf(dn, "%u:", disk);
+    if (!(arc = DosPhysicalDisk(INFO_GETIOCTLHANDLE, &dh, 2, dn, 3)))
+    {
+        TRACKLAYOUT DiskIOParm;
+        ULONG IOCtlDataLength = sizeof(DiskIOParm);
+        ULONG IOCtlParmLength = 512;
+
+        DiskIOParm.bCommand = 0;
+        DiskIOParm.usHead = head;
+        DiskIOParm.usCylinder = cylinder;
+        DiskIOParm.usFirstSector = 0;
+        DiskIOParm.cSectors = 1;
+        DiskIOParm.TrackTable[0].usSectorNumber = sector;
+        DiskIOParm.TrackTable[0].usSectorSize = 512;
+
+        arc = DosDevIOCtl(dh,
+                          IOCTL_PHYSICALDISK, PDSK_READPHYSTRACK,
+                          &DiskIOParm, IOCtlParmLength, &IOCtlParmLength,
+                          buff       , IOCtlDataLength, &IOCtlDataLength);
+
+        DosPhysicalDisk(INFO_FREEIOCTLHANDLE, 0, 0, &dh, 2);
+    }
+
+    return (arc);
+}
+
 // Sector and Cylinder values are actually 6 bits and 10 bits:
 //
@@ -2756,5 +2735,5 @@
  *      get cylinder number.
  *
- *      Based on code (C) Dmitry A. Steklenev.
+ *      Originally contributed by Dmitry A. Steklenev.
  *
  *@@added V0.9.0 [umoeller]
@@ -2771,5 +2750,5 @@
  *      get sector number.
  *
- *      Based on code (C) Dmitry A. Steklenev.
+ *      Originally contributed by Dmitry A. Steklenev.
  *
  *@@added V0.9.0 [umoeller]
@@ -2796,5 +2775,5 @@
  *      -- ERROR_NOT_SUPPORTED (50): boot manager not installed.
  *
- *      Based on code (C) Dmitry A. Steklenev.
+ *      Originally contributed by Dmitry A. Steklenev.
  *
  *@@added V0.9.0 [umoeller]
@@ -2858,5 +2837,5 @@
  *      -- ERROR_INVALID_PARAMETER: BMInfo is NULL.
  *
- *      Based on code (C) Dmitry A. Steklenev.
+ *      Originally contributed by Dmitry A. Steklenev.
  *
  *@@added V0.9.0 [umoeller]
@@ -2907,6 +2886,6 @@
                     // skip unused partition, BootManager or Extended partition
                     if (    (MBoot.sPrtnInfo[i].bFileSysCode)  // skip unused
-                        &&  (MBoot.sPrtnInfo[i].bFileSysCode != PAR_BOOTMANAGER) // skip boot manager
-                        &&  (MBoot.sPrtnInfo[i].bFileSysCode != PAR_EXTENDED) // skip extended
+                        &&  (MBoot.sPrtnInfo[i].bFileSysCode != 0x0A) // skip boot manager
+                        &&  (MBoot.sPrtnInfo[i].bFileSysCode != 0x05) // skip extended partition
                        )
                     {
@@ -2945,5 +2924,5 @@
  *      This gets called from GetExtendedPartition.
  *
- *      Based on code (C) Dmitry A. Steklenev.
+ *      Originally contributed by Dmitry A. Steklenev.
  *
  *@@added V0.9.0 [umoeller]
@@ -2973,5 +2952,5 @@
         // skip unused partition or BootManager partition
         if (    (MBoot.sPrtnInfo[i].bFileSysCode)
-             && (MBoot.sPrtnInfo[i].bFileSysCode != PAR_BOOTMANAGER)
+             && (MBoot.sPrtnInfo[i].bFileSysCode != 0x0A)
            )
         {
@@ -2980,5 +2959,5 @@
 
             // special work around extended partition
-            if (MBoot.sPrtnInfo[i].bFileSysCode == PAR_EXTENDED)
+            if (MBoot.sPrtnInfo[i].bFileSysCode == 0x05)
             {
                 if ((arc = GetLogicalDrives(pppiFirst,
@@ -2995,5 +2974,5 @@
 
             // raise driver letter if OS/2 would recognize this drive
-            if (    (MBoot.sPrtnInfo[i].bFileSysCode < PAR_PCIX)
+            if (    (MBoot.sPrtnInfo[i].bFileSysCode < 0x75)
                )
                 fAssignLetter = TRUE;
@@ -3034,5 +3013,5 @@
  *      This gets called from doshGetPartitionsList.
  *
- *      Based on code (C) Dmitry A. Steklenev.
+ *      Originally contributed by Dmitry A. Steklenev.
  *
  *@@added V0.9.0 [umoeller]
@@ -3058,5 +3037,5 @@
          i++)
     {
-        if (MBoot.sPrtnInfo[i].bFileSysCode == PAR_EXTENDED)
+        if (MBoot.sPrtnInfo[i].bFileSysCode == 0x05)
         {
             if ((arc = GetLogicalDrives(pppiFirst,
@@ -3075,4 +3054,81 @@
 
 /*
+ *@@ ReadFDiskPartitions:
+ *      helper for doshGetPartitionsList for non-LVM
+ *      systems.
+ *
+ *      Originally contributed by Dmitry A. Steklenev.
+ *
+ *@@added V0.9.16 (2001-10-08) [umoeller]
+ */
+
+APIRET ReadFDiskPartitions(PARTITIONINFO **ppPartitionInfos,
+                           USHORT *pcPartitions,
+                           PUSHORT pusContext)              // out: error context
+{
+    APIRET          arc = NO_ERROR;
+
+    PAR_INFO        BmInfo;     // BootManager partition
+    USHORT          usBmDisk;     // BootManager disk
+    USHORT          cDisks = doshQueryDiskCount();    // physical disks count
+    USHORT          i;
+
+    CHAR            cLetter = 'C';  // first drive letter
+
+    PARTITIONINFO   *ppiTemp = NULL;
+
+    if (cDisks > 8)              // Not above 8 disks
+        cDisks = 8;
+
+    // get boot manager disk and info
+    if ((arc = doshGetBootManager(&usBmDisk,
+                                  NULL,
+                                  &BmInfo)) != NO_ERROR)
+    {
+        *pusContext = 1;
+    }
+    else
+    {
+        // on each disk, read primary partitions
+        for (i = 1; i <= cDisks; i++)
+        {
+            if ((arc = GetPrimaryPartitions(ppPartitionInfos,
+                                            &ppiTemp,
+                                            pcPartitions,
+                                            &cLetter,
+                                            usBmDisk,
+                                            usBmDisk ? &BmInfo : 0,
+                                            i)))
+            {
+                *pusContext = 2;
+            }
+        }
+
+        if (!arc && usBmDisk)
+        {
+            // boot manager found:
+            // on each disk, read extended partition
+            // with logical drives
+            for (i = 1; i <= cDisks; i++)
+            {
+                if ((arc = GetExtendedPartition(ppPartitionInfos,
+                                                &ppiTemp,
+                                                pcPartitions,
+                                                &cLetter,
+                                                &BmInfo,
+                                                i)))
+                {
+                    *pusContext = 3;
+                }
+            }
+        }
+    } // end else if ((arc = doshGetBootManager(&usBmDisk,
+
+    return (arc);
+}
+
+#endif
+
+/*
  *@@ CleanPartitionInfos:
  *
@@ -3137,5 +3193,5 @@
  *      --  0: something else.
  *
- *      Based on code (C) Dmitry A. Steklenev.
+ *      Originally contributed by Dmitry A. Steklenev.
  *
  *@@added V0.9.0 [umoeller]
@@ -3151,6 +3207,5 @@
     PLVMINFO        pLVMInfo = NULL;
 
-    PARTITIONINFO   *pPartitionInfos = NULL, // linked list of all partitions
-                    *ppiTemp = NULL;
+    PARTITIONINFO   *pPartitionInfos = NULL; // linked list of all partitions
     USHORT          cPartitions = 0;        // bootable partition count
 
@@ -3176,65 +3231,12 @@
     }
 
+#ifndef __XWPLITE__
     if (arc)
-    {
         // LVM not installed, or failed:
         // parse partitions manually
-        PAR_INFO        BmInfo;     // BootManager partition
-        USHORT          usBmDisk;     // BootManager disk
-        USHORT          cDisks = doshQueryDiskCount();    // physical disks count
-        USHORT          i;
-
-        CHAR            cLetter = 'C';  // first drive letter
-
-        // start over
-        arc = NO_ERROR;
-
-        if (cDisks > 8)              // Not above 8 disks
-            cDisks = 8;
-
-        // get boot manager disk and info
-        if ((arc = doshGetBootManager(&usBmDisk,
-                                      NULL,
-                                      &BmInfo)) != NO_ERROR)
-        {
-            *pusContext = 1;
-        }
-        else
-        {
-            // on each disk, read primary partitions
-            for (i = 1; i <= cDisks; i++)
-            {
-                if ((arc = GetPrimaryPartitions(&pPartitionInfos,
-                                                &ppiTemp,
-                                                &cPartitions,
-                                                &cLetter,
-                                                usBmDisk,
-                                                usBmDisk ? &BmInfo : 0,
-                                                i)))
-                {
-                    *pusContext = 2;
-                }
-            }
-
-            if (!arc && usBmDisk)
-            {
-                // boot manager found:
-                // on each disk, read extended partition
-                // with logical drives
-                for (i = 1; i <= cDisks; i++)
-                {
-                    if ((arc = GetExtendedPartition(&pPartitionInfos,
-                                                    &ppiTemp,
-                                                    &cPartitions,
-                                                    &cLetter,
-                                                    &BmInfo,
-                                                    i)))
-                    {
-                        *pusContext = 3;
-                    }
-                }
-            }
-        } // end else if ((arc = doshGetBootManager(&usBmDisk,
-    } // end else if (!doshQueryLVMInfo(&pLVMInfo))
+        arc = ReadFDiskPartitions(&pPartitionInfos,
+                                  &cPartitions,
+                                  pusContext);
+#endif
 
     if (!arc)
Index: trunk/src/helpers/eah.c
===================================================================
--- trunk/src/helpers/eah.c	(revision 106)
+++ trunk/src/helpers/eah.c	(revision 108)
@@ -857,9 +857,8 @@
                                     USHORT usCodepage)  // in: codepage to set in EAT_MVMT
 {
-    PEABINDING peab;
+    PEABINDING peab = NULL;
     if (pcszInput)
     {
-        peab = (PEABINDING)malloc(sizeof(EABINDING));
-        if (peab)
+        if ((peab = (PEABINDING)malloc(sizeof(EABINDING))))
         {
             const char *p = pcszInput,
Index: trunk/src/helpers/helpers_pre.in
===================================================================
--- trunk/src/helpers/helpers_pre.in	(revision 106)
+++ trunk/src/helpers/helpers_pre.in	(revision 108)
@@ -60,4 +60,5 @@
 $(OUTPUTDIR)\except.obj \
 $(OUTPUTDIR)\level.obj \
+$(OUTPUTDIR)\nls.obj \
 $(OUTPUTDIR)\procstat.obj \
 $(OUTPUTDIR)\prfh.obj \
Index: trunk/src/helpers/prfh.c
===================================================================
--- trunk/src/helpers/prfh.c	(revision 106)
+++ trunk/src/helpers/prfh.c	(revision 108)
@@ -45,4 +45,5 @@
 #include "setup.h"                      // code generation and debugging options
 
+#define INCLUDE_PRFH_PRIVATE
 #include "helpers\prfh.h"
 
@@ -237,39 +238,4 @@
                           szTemp, sizeof(szTemp)-1);
     return (szTemp[0]);
-}
-
-/*
- *@@ prfhQueryCountrySettings:
- *      this returns the most frequently used country settings
- *      all at once into a COUNTRYSETTINGS structure (prfh.h).
- *      This data corresponds to the user settings in the
- *      WPS "Country" object (which writes the data in "PM_National"
- *      in OS2.INI).
- *
- *      In case a key cannot be found, the following (English)
- *      default values are set:
- *      --  ulDateFormat = 0 (English date format, mm.dd.yyyy);
- *      --  ulTimeFormat = 0 (12-hour clock);
- *      --  cDateSep = '/' (date separator);
- *      --  cTimeSep = ':' (time separator);
- *      --  cDecimal = '.' (decimal separator).
- *      --  cThousands = ',' (thousands separator).
- *
- *@@added V0.9.0 [umoeller]
- *@@changed V0.9.7 (2000-12-02) [umoeller]: added cDecimal
- */
-
-VOID prfhQueryCountrySettings(PCOUNTRYSETTINGS pcs)
-{
-    if (pcs)
-    {
-        const char *pcszApp = "PM_National";
-        pcs->ulDateFormat = PrfQueryProfileInt(HINI_USER, (PSZ)pcszApp, "iDate", 0);
-        pcs->ulTimeFormat = PrfQueryProfileInt(HINI_USER, (PSZ)pcszApp, "iTime", 0);
-        pcs->cDateSep = prfhQueryProfileChar(HINI_USER, (PSZ)pcszApp, "sDate", '/');
-        pcs->cTimeSep = prfhQueryProfileChar(HINI_USER, (PSZ)pcszApp, "sTime", ':');
-        pcs->cDecimal = prfhQueryProfileChar(HINI_USER, (PSZ)pcszApp, "sDecimal", '.');
-        pcs->cThousands = prfhQueryProfileChar(HINI_USER, (PSZ)pcszApp, "sThousand", ',');
-    }
 }
 
Index: trunk/src/helpers/stringh.c
===================================================================
--- trunk/src/helpers/stringh.c	(revision 106)
+++ trunk/src/helpers/stringh.c	(revision 108)
@@ -651,308 +651,4 @@
     }
     return (ulWords);
-}
-
-/*
- *@@ strhThousandsULong:
- *      converts a ULONG into a decimal string, while
- *      inserting thousands separators into it. Specify
- *      the separator character in cThousands.
- *
- *      Returns pszTarget so you can use it directly
- *      with sprintf and the "%s" flag.
- *
- *      For cThousands, you should use the data in
- *      OS2.INI ("PM_National" application), which is
- *      always set according to the "Country" object.
- *      You can use prfhQueryCountrySettings to
- *      retrieve this setting.
- *
- *      Use strhThousandsDouble for "double" values.
- */
-
-PSZ strhThousandsULong(PSZ pszTarget,       // out: decimal as string
-                       ULONG ul,            // in: decimal to convert
-                       CHAR cThousands)     // in: separator char (e.g. '.')
-{
-    USHORT ust, uss, usc;
-    CHAR   szTemp[40];
-    sprintf(szTemp, "%lu", ul);
-
-    ust = 0;
-    usc = strlen(szTemp);
-    for (uss = 0; uss < usc; uss++)
-    {
-        if (uss)
-            if (((usc - uss) % 3) == 0)
-            {
-                pszTarget[ust] = cThousands;
-                ust++;
-            }
-        pszTarget[ust] = szTemp[uss];
-        ust++;
-    }
-    pszTarget[ust] = '\0';
-
-    return (pszTarget);
-}
-
-/*
- *@@ strhThousandsDouble:
- *      like strhThousandsULong, but for a "double"
- *      value. Note that after-comma values are truncated.
- */
-
-PSZ strhThousandsDouble(PSZ pszTarget, double dbl, CHAR cThousands)
-{
-    USHORT ust, uss, usc;
-    CHAR   szTemp[40];
-    sprintf(szTemp, "%.0f", floor(dbl));
-
-    ust = 0;
-    usc = strlen(szTemp);
-    for (uss = 0; uss < usc; uss++)
-    {
-        if (uss)
-            if (((usc - uss) % 3) == 0)
-            {
-                pszTarget[ust] = cThousands;
-                ust++;
-            }
-        pszTarget[ust] = szTemp[uss];
-        ust++;
-    }
-    pszTarget[ust] = '\0';
-
-    return (pszTarget);
-}
-
-/*
- *@@ strhVariableDouble:
- *      like strhThousandsULong, but for a "double" value, and
- *      with a variable number of decimal places depending on the
- *      size of the quantity.
- *
- *@@added V0.9.6 (2000-11-12) [pr]
- */
-
-PSZ strhVariableDouble(PSZ pszTarget,
-                       double dbl,
-                       PSZ pszUnits,
-                       CHAR cThousands)
-{
-    if (dbl < 100.0)
-        sprintf(pszTarget, "%.2f%s", dbl, pszUnits);
-    else
-        if (dbl < 1000.0)
-            sprintf(pszTarget, "%.1f%s", dbl, pszUnits);
-        else
-            strcat(strhThousandsDouble(pszTarget, dbl, cThousands),
-                   pszUnits);
-
-    return(pszTarget);
-}
-
-/*
- *@@ strhFileDate:
- *      converts file date data to a string (to pszBuf).
- *      You can pass any FDATE structure to this function,
- *      which are returned in those FILEFINDBUF* or
- *      FILESTATUS* structs by the Dos* functions.
- *
- *      ulDateFormat is the PM setting for the date format,
- *      as set in the "Country" object, and can be queried using
- +              PrfQueryProfileInt(HINI_USER, "PM_National", "iDate", 0);
- *
- *      meaning:
- *      --  0   mm.dd.yyyy  (English)
- *      --  1   dd.mm.yyyy  (e.g. German)
- *      --  2   yyyy.mm.dd  (Japanese, ISO)
- *      --  3   yyyy.dd.mm
- *
- *      cDateSep is used as a date separator (e.g. '.').
- *      This can be queried using:
- +          prfhQueryProfileChar(HINI_USER, "PM_National", "sDate", '/');
- *
- *      Alternatively, you can query all the country settings
- *      at once using prfhQueryCountrySettings (prfh.c).
- *
- *@@changed V0.9.0 (99-11-07) [umoeller]: now calling strhDateTime
- */
-
-VOID strhFileDate(PSZ pszBuf,           // out: string returned
-                  FDATE *pfDate,        // in: date information
-                  ULONG ulDateFormat,   // in: date format (0-3)
-                  CHAR cDateSep)        // in: date separator (e.g. '.')
-{
-    DATETIME dt;
-    dt.day = pfDate->day;
-    dt.month = pfDate->month;
-    dt.year = pfDate->year + 1980;
-
-    strhDateTime(pszBuf,
-                 NULL,          // no time
-                 &dt,
-                 ulDateFormat,
-                 cDateSep,
-                 0, 0);         // no time
-}
-
-/*
- *@@ strhFileTime:
- *      converts file time data to a string (to pszBuf).
- *      You can pass any FTIME structure to this function,
- *      which are returned in those FILEFINDBUF* or
- *      FILESTATUS* structs by the Dos* functions.
- *
- *      ulTimeFormat is the PM setting for the time format,
- *      as set in the "Country" object, and can be queried using
- +              PrfQueryProfileInt(HINI_USER, "PM_National", "iTime", 0);
- *      meaning:
- *      --  0   12-hour clock
- *      --  >0  24-hour clock
- *
- *      cDateSep is used as a time separator (e.g. ':').
- *      This can be queried using:
- +              prfhQueryProfileChar(HINI_USER, "PM_National", "sTime", ':');
- *
- *      Alternatively, you can query all the country settings
- *      at once using prfhQueryCountrySettings (prfh.c).
- *
- *@@changed V0.8.5 (99-03-15) [umoeller]: fixed 12-hour crash
- *@@changed V0.9.0 (99-11-07) [umoeller]: now calling strhDateTime
- */
-
-VOID strhFileTime(PSZ pszBuf,           // out: string returned
-                  FTIME *pfTime,        // in: time information
-                  ULONG ulTimeFormat,   // in: 24-hour time format (0 or 1)
-                  CHAR cTimeSep)        // in: time separator (e.g. ':')
-{
-    DATETIME dt;
-    dt.hours = pfTime->hours;
-    dt.minutes = pfTime->minutes;
-    dt.seconds = pfTime->twosecs * 2;
-
-    strhDateTime(NULL,          // no date
-                 pszBuf,
-                 &dt,
-                 0, 0,          // no date
-                 ulTimeFormat,
-                 cTimeSep);
-}
-
-/*
- *@@ strhDateTime:
- *      converts Control Program DATETIME info
- *      into two strings. See strhFileDate and strhFileTime
- *      for more detailed parameter descriptions.
- *
- *@@added V0.9.0 (99-11-07) [umoeller]
- */
-
-VOID strhDateTime(PSZ pszDate,          // out: date string returned (can be NULL)
-                  PSZ pszTime,          // out: time string returned (can be NULL)
-                  DATETIME *pDateTime,  // in: date/time information
-                  ULONG ulDateFormat,   // in: date format (0-3); see strhFileDate
-                  CHAR cDateSep,        // in: date separator (e.g. '.')
-                  ULONG ulTimeFormat,   // in: 24-hour time format (0 or 1); see strhFileTime
-                  CHAR cTimeSep)        // in: time separator (e.g. ':')
-{
-    if (pszDate)
-    {
-        switch (ulDateFormat)
-        {
-            case 0:  // mm.dd.yyyy  (English)
-                sprintf(pszDate, "%02d%c%02d%c%04d",
-                    pDateTime->month,
-                        cDateSep,
-                    pDateTime->day,
-                        cDateSep,
-                    pDateTime->year);
-            break;
-
-            case 1:  // dd.mm.yyyy  (e.g. German)
-                sprintf(pszDate, "%02d%c%02d%c%04d",
-                    pDateTime->day,
-                        cDateSep,
-                    pDateTime->month,
-                        cDateSep,
-                    pDateTime->year);
-            break;
-
-            case 2: // yyyy.mm.dd  (Japanese)
-                sprintf(pszDate, "%04d%c%02d%c%02d",
-                    pDateTime->year,
-                        cDateSep,
-                    pDateTime->month,
-                        cDateSep,
-                    pDateTime->day);
-            break;
-
-            default: // yyyy.dd.mm
-                sprintf(pszDate, "%04d%c%02d%c%02d",
-                    pDateTime->year,
-                        cDateSep,
-                    pDateTime->day,
-                        cDateSep,
-                    pDateTime->month);
-            break;
-        }
-    }
-
-    if (pszTime)
-    {
-        if (ulTimeFormat == 0)
-        {
-            // for 12-hour clock, we need additional INI data
-            CHAR szAMPM[10] = "err";
-
-            if (pDateTime->hours > 12)
-            {
-                // > 12h: PM.
-
-                // Note: 12:xx noon is 12 AM, not PM (even though
-                // AM stands for "ante meridiam", but English is just
-                // not logical), so that's handled below.
-
-                PrfQueryProfileString(HINI_USER,
-                    "PM_National",
-                    "s2359",        // key
-                    "PM",           // default
-                    szAMPM, sizeof(szAMPM)-1);
-                sprintf(pszTime, "%02d%c%02d%c%02d %s",
-                    // leave 12 == 12 (not 0)
-                    pDateTime->hours % 12,
-                        cTimeSep,
-                    pDateTime->minutes,
-                        cTimeSep,
-                    pDateTime->seconds,
-                    szAMPM);
-            }
-            else
-            {
-                // <= 12h: AM
-                PrfQueryProfileString(HINI_USER,
-                                      "PM_National",
-                                      "s1159",        // key
-                                      "AM",           // default
-                                      szAMPM, sizeof(szAMPM)-1);
-                sprintf(pszTime, "%02d%c%02d%c%02d %s",
-                    pDateTime->hours,
-                        cTimeSep,
-                    pDateTime->minutes,
-                        cTimeSep,
-                    pDateTime->seconds,
-                    szAMPM);
-            }
-        }
-        else
-            // 24-hour clock
-            sprintf(pszTime, "%02d%c%02d%c%02d",
-                pDateTime->hours,
-                    cTimeSep,
-                pDateTime->minutes,
-                    cTimeSep,
-                pDateTime->seconds);
-    }
 }
 
Index: trunk/src/helpers/tmsgfile.c
===================================================================
--- trunk/src/helpers/tmsgfile.c	(revision 106)
+++ trunk/src/helpers/tmsgfile.c	(revision 108)
@@ -2,15 +2,7 @@
 /*
  *@@sourcefile tmsgfile.c:
- *      contains a "text message file" helper function,
- *      which works similar to DosGetMessage. See
- *      tmfGetMessage for details.
- *
- *      tmfGetMessage operates on plain-text ".TMF" files.
- *      The file is "compiled" into the file's extended
- *      attributes for faster access after the first access.
- *      When the file's contents change, the EAs are recompiled
- *      automatically.
- *
- *      This has the following advantages over DosGetMessage:
+ *      replacement code for DosGetMessage for decent NLS support.
+ *
+ *      This code has the following advantages over DosGetMessage:
  *
  *      1)  No external utility is necessary to change message
@@ -21,5 +13,29 @@
  *          only numerical IDs.
  *
- *      This file is entirely new with V0.9.0.
+ *      The .TMF file must have the following format:
+ *
+ *      1)  Any message must start on the beginning of a line
+ *          and look like this:
+ *
+ +              <--MSGID-->: message text
+ *
+ *          "MSGID" can be any string and will serve as the
+ *          message identifier for tmfGetMessage.
+ *          Leading spaces after "-->:" will be ignored.
+ *
+ *      2)  The message text may span across several lines.
+ *          If so, the line breaks are returned as plain
+ *          newline (\n) characters by tmfGetMessage.
+ *
+ *      3)  Comments in the file are supported if they start
+ *          with a semicolon (";") at the beginning of a line.
+ *          Any following text is ignored until the next
+ *          message ID.
+ *
+ *      This file was originally added V0.9.0. The original
+ *      code was contributed by Christian Langanke, but this
+ *      has been completely rewritten with V0.9.16 to use my
+ *      fast string functions now. Also, tmfGetMessage requires
+ *      tmfOpenMessageFile to be called beforehand.
  *
  *      Usage: All OS/2 programs.
@@ -28,16 +44,4 @@
  *      --  tmf*   text message file functions
  *
- *      This code uses only C standard library functions
- *      which are also part of the subsystem libraries.
- *      It does _not_ use the fopen etc. functions, but
- *      DosOpen etc. instead.
- *      You can therefore use this code with your software
- *      which uses the subsystem libraries only.
- *
- *      This code was kindly provided by Christian Langanke.
- *      Modifications April 1999 by Ulrich Mller. Any
- *      substantial changes (other than comments and
- *      formatting) are marked with (*UM).
- *
  *      Note: Version numbering in this file relates to XWorkplace version
  *            numbering.
@@ -48,6 +52,5 @@
 
 /*
- *      Copyright (C) 1999 Christian Langanke.
- *      Copyright (C) 1999-2000 Ulrich Mller.
+ *      Copyright (C) 2001 Ulrich Mller.
  *      This file is part of the "XWorkplace helpers" source package.
  *      This is free software; you can redistribute it and/or modify
@@ -68,5 +71,4 @@
 #define INCL_DOSFILEMGR
 #define INCL_DOSMISC
-#define INCL_DOSNLS
 #define INCL_DOSERRORS
 #include <os2.h>
@@ -79,5 +81,11 @@
 #include "setup.h"                      // code generation and debugging options
 
+#include "helpers\dosh.h"
 #include "helpers\eah.h"
+#include "helpers\standards.h"
+#include "helpers\stringh.h"
+#include "helpers\tree.h"
+#include "helpers\xstring.h"
+
 #include "helpers\tmsgfile.h"
 
@@ -93,361 +101,217 @@
  ********************************************************************/
 
-// extended attribute used for timestamp; written to .TMF file
-#define EA_TIMESTAMP "TMF.FILEINFO"
-// extended attribute used for compiled text file; written to .TMF file
-#define EA_MSGTABLE  "TMF.MSGTABLE"
-
-#define NEWLINE "\n"
-
-// start-of-msgid marker
-#define MSG_NAME_START   "\r\n<--"
-// end-of-msgid marker
-#define MSG_NAME_END     "-->:"       // fixed: the space had to be removed, because
-                                      // if a newline was the first character after ":",
-                                      // we got complete garbage (crashes) (99-10-23) [umoeller]
-// comment marker
-#define MSG_COMMENT_LINE "\r\n;"
-
-// internal prototypes
-APIRET          CompileMsgTable(PSZ pszMessageFile, PBYTE * ppbTableData);
-APIRET          GetTimeStamp(PFILESTATUS3 pfs3, PSZ pszBuffer, ULONG ulBufferlen);
+/*
+ *@@ MSGENTRY:
+ *
+ *@@added V0.9.16 (2001-10-08) [umoeller]
+ */
+
+typedef struct _MSGENTRY
+{
+    TREE    Tree;               // ulKey points to strID.psz
+    XSTRING strID;              // message ID
+    ULONG   ulOfsText;          // offset of start of text (in C-format buffer)
+    ULONG   cbText;             // length of text in msg file
+} MSGENTRY, *PMSGENTRY;
+
+// globals
+static PCSZ     G_pcszStartMarker = "\n<--",
+                G_pcszEndMarker = "-->:";
 
 /* ******************************************************************
  *
- *   Text Message File Code
+ *   Functions
  *
  ********************************************************************/
 
 /*
- *@@ tmfGetMessage:
- *      just like DosGetMessage, except that this does not
- *      take a simple message number for the message in the
- *      given message file, but a PSZ message identifier
- *      (pszMessageName).
- *
- *      This function automatically compiles the given .TMF
- *      file into the file's extended attributes, if we find
- *      that this is necessary, by calling CompileMsgTable.
- *
- *      Note that for compatibily, this function performs the
- *      same brain-dead string processing as DosGetMessage, that
- *      is, the return string is _not_ null-terminated, and
- *      trailing newline characters are _not_ cut off.
- *
- *      If you don't like this, call tmfGetMessageExt, which
- *      calls this function in turn.
- *
- *      <B>Returns:</B>
- *      --  ERROR_INVALID_PARAMETER
- *      --  ERROR_BUFFER_OVERFLOW
- *      --  ERROR_MR_MID_NOT_FOUND
- *      --  ERROR_MR_INV_MSGF_FORMAT
- *      --  ERROR_BUFFER_OVERFLOW
- *
- *      plus the error codes of DosOpen, DosRead, DosClose.
- */
-
-APIRET tmfGetMessage(PCHAR* pTable,    // in: pointer table for string insertion
-                     ULONG cTable,     // in: number of pointers in *pTable
-                     PBYTE pbBuffer,   // out: returned string
-                     ULONG cbBuffer,   // out: sizeof(*pbBuffer)
-                     PCSZ pszMessageName,  // in: message identifier
-                     PCSZ pszFile,      // in: message file (.TMF extension proposed)
-                     PULONG pcbMsg)    // out: bytes written to *pbBuffer
+ *@@ tmfOpenMessageFile:
+ *      opens a .TMF message file for future use
+ *      with tmfGetMessage.
+ *
+ *      Use tmfCloseMessageFile to close the file
+ *      again and free all resources. This thing
+ *      can allocate quite a bit of memory.
+ *
+ *      Returns:
+ *
+ *      --  NO_ERROR: *ppMsgFile has received the
+ *          new TMFMSGFILE structure.
+ *
+ *      --  ERROR_NOT_ENOUGH_MEMORY
+ *
+ *      plus any of the errors of doshLoadTextFile,
+ *      such as ERROR_FILE_NOT_FOUND.
+ *
+ *@@added V0.9.16 (2001-10-08) [umoeller]
+ */
+
+APIRET tmfOpenMessageFile(const char *pcszMessageFile, // in: fully q'fied .TMF file name
+                          PTMFMSGFILE *ppMsgFile)     // out: TMFMSGFILE struct
 {
-    // fixed UM 99-10-22: now initializing all vars to 0,
-    // because ulBytesRead might be returned from this func
-    APIRET          rc = NO_ERROR;
-    CHAR            szMessageFile[_MAX_PATH];
-    PBYTE           pbTableData = NULL;
-    PSZ             pszEntry = 0;
-    PSZ             pszEndOfEntry = 0;
-    ULONG           ulMessagePos = 0;
-    ULONG           ulMessageLen = 0;
-
-    HFILE           hfile = NULLHANDLE;
-    ULONG           ulFilePtr = 0;
-    ULONG           ulAction = 0;
-    ULONG           ulBytesToRead = 0;
-    ULONG           ulBytesRead = 0;
-
-    do
+    APIRET arc;
+    PSZ pszContent = NULL;
+
+    if (!(arc = doshLoadTextFile(pcszMessageFile,
+                                 &pszContent)))
     {
-        // check parms
-        if ((!pbBuffer) ||
-            (!cbBuffer) ||
-            (!pszMessageName) ||
-            (!*pszMessageName) ||
-            (!*pszFile))
+        // file loaded:
+        // create a TMFMSGFILE entry
+        PTMFMSGFILE pFile;
+        if (!(pFile = NEW(TMFMSGFILE)))
         {
-            rc = ERROR_INVALID_PARAMETER;
-            break;
+            arc = ERROR_NOT_ENOUGH_MEMORY;
+            free(pszContent);
         }
-
-        if (cbBuffer < 2)
-        {
-            rc = ERROR_BUFFER_OVERFLOW;
-            break;
-        }
-
-        // search file
-        if ((strchr(pszFile, ':')) ||
-            (strchr(pszFile, '\\')) ||
-            (strchr(pszFile, '/')))
-            // drive and/or path given: no search in path
-            strcpy(szMessageFile, pszFile);
         else
         {
-            // only filename, search in current dir and DPATH
-            rc = DosSearchPath(SEARCH_IGNORENETERRS
-                               | SEARCH_ENVIRONMENT
-                               | SEARCH_CUR_DIRECTORY,
-                               "DPATH",
-                               (PSZ)pszFile,
-                               szMessageFile,
-                               sizeof(szMessageFile));
-            if (rc != NO_ERROR)
-                break;
-        }
-
-        // _Pmpf(("tmfGetMessage: Found %s", szMessageFile));
-
-        // compile table if neccessary
-        rc = CompileMsgTable(szMessageFile, &pbTableData);
-        if (rc != NO_ERROR)
-            break;
-
-        // _Pmpf(("tmfGetMessage: Now searching compiled file"));
-
-        // search the name
-        pszEntry = strstr(pbTableData, pszMessageName);
-        if (!pszEntry)
-        {
-            rc = ERROR_MR_MID_NOT_FOUND;
-            break;
-        }
-        else
-            pszEntry += strlen(pszMessageName) + 1;
-
-        // isolate entry
-        pszEndOfEntry = strchr(pszEntry, '\n');
-        if (pszEndOfEntry)
-            *pszEndOfEntry = 0;
-
-        // get numbers
-        ulMessagePos = atol(pszEntry);
-        if (ulMessagePos == 0)
-            if (!pszEntry)
+            // TMFMSGFILE created:
+
+            PCSZ    pStartOfFile,
+                    pStartOfMarker;
+
+            ULONG   ulStartMarkerLength = strlen(G_pcszStartMarker),
+                    ulEndMarkerLength = strlen(G_pcszEndMarker);
+
+            // initialize TMFMSGFILE struct
+            ZERO(pFile);
+            pFile->pszFilename = strdup(pcszMessageFile);
+            treeInit(&pFile->IDsTreeRoot);
+
+            xstrInitSet(&pFile->strContent, pszContent);
+
+            // convert to plain C format
+            xstrConvertLineFormat(&pFile->strContent,
+                                  CRLF2LF);
+
+            // kick out all the comments
+            while (pStartOfMarker = strstr(pFile->strContent.psz, "\n;"))
             {
-                rc = ERROR_MR_INV_MSGF_FORMAT;
-                break;
+                // copy the next line over this
+                PCSZ pEOL = strhFindEOL(pStartOfMarker + 2, NULL);
+                /* printf("pStartOfMarker = %lX, pEOL = %lX\n",
+                        pStartOfMarker,
+                        pEOL); */
+                xstrrpl(&pFile->strContent,
+                        // ofs of first char to replace: "\n;"
+                        pStartOfMarker - pFile->strContent.psz,
+                        // no. of chars to replace:
+                        pEOL - pStartOfMarker,
+                        // string to replace chars with:
+                        NULL,
+                        // length of replacement string:
+                        0);
             }
 
-        pszEntry = strchr(pszEntry, ' ');
-        if (!pszEntry)
-        {
-            rc = ERROR_MR_INV_MSGF_FORMAT;
-            break;
-        }
-        ulMessageLen = atol(pszEntry);
-
-        // determine maximum read len
-        ulBytesToRead = _min(ulMessageLen, cbBuffer);
-
-        // open file and read message
-        rc = DosOpen((PSZ)pszFile,
-                     &hfile,
-                     &ulAction,
-                     0, 0,
-                     OPEN_ACTION_FAIL_IF_NEW
-                     | OPEN_ACTION_OPEN_IF_EXISTS,
-                     OPEN_FLAGS_FAIL_ON_ERROR
-                     | OPEN_SHARE_DENYWRITE
-                     | OPEN_ACCESS_READONLY,
-                     NULL);
-        if (rc != NO_ERROR)
-            break;
-
-        rc = DosSetFilePtr(hfile, ulMessagePos, FILE_BEGIN, &ulFilePtr);
-        if ((rc != NO_ERROR) || (ulFilePtr != ulMessagePos))
-            break;
-
-        rc = DosRead(hfile,
-                     pbBuffer,
-                     ulBytesToRead,
-                     &ulBytesRead);
-        if (rc != NO_ERROR)
-            break;
-
-        // report "buffer too small" here
-        if (ulBytesToRead < ulMessageLen)
-            rc = ERROR_BUFFER_OVERFLOW;
-
-    }
-    while (FALSE);
-
-    // replace string placeholders ("%1" etc.) (*UM)
-    if (rc == NO_ERROR)     // added (99-10-24) [umoeller]
-        if (cTable)
-        {
-            // create a temporary buffer for replacements
-            PSZ             pszTemp = (PSZ)malloc(cbBuffer);
-
-            if (!pszTemp)
-                rc = ERROR_NOT_ENOUGH_MEMORY;
+            // free excessive memory
+            xstrShrink(&pFile->strContent);
+
+            pStartOfFile = pFile->strContent.psz;
+
+            // go build a tree of all message IDs...
+
+            // find first start message marker
+            pStartOfMarker = strstr(pStartOfFile,
+                                    G_pcszStartMarker);     // start-of-line marker
+            while (    (pStartOfMarker)
+                    && (!arc)
+                  )
+            {
+                // start marker found:
+                PCSZ pStartOfMsgID = pStartOfMarker + ulStartMarkerLength;
+                // search next start marker
+                PCSZ pStartOfNextMarker = strstr(pStartOfMsgID + 1,
+                                                 G_pcszStartMarker);
+                // and the end-marker
+                PCSZ pEndOfMarker = strstr(pStartOfMsgID + 1,
+                                           G_pcszEndMarker);
+
+                PMSGENTRY pNew;
+
+                // sanity checks...
+
+                if (    (pStartOfNextMarker)
+                     && (pStartOfNextMarker < pEndOfMarker)
+                   )
+                {
+                    // next start marker before end marker:
+                    // that doesn't look correct, skip this entry
+                    pStartOfMarker = pStartOfNextMarker;
+                    continue;
+                }
+
+                if (!pEndOfMarker)
+                    // no end marker found:
+                    // that's invalid too, and there can't be any
+                    // message left in the file then...
+                    break;
+
+                // alright, this ID looks correct now
+                if (!(pNew = NEW(MSGENTRY)))
+                    arc = ERROR_NOT_ENOUGH_MEMORY;
+                else
+                {
+                    // length of the ID
+                    ULONG ulIDLength = pEndOfMarker - pStartOfMsgID;
+                    PCSZ pStartOfText = pEndOfMarker + ulEndMarkerLength;
+
+                    ZERO(pNew);
+
+                    // copy the string ID (between start and end markers)
+                    xstrInit(&pNew->strID, 0);
+                    xstrcpy(&pNew->strID,
+                            pStartOfMsgID,
+                            ulIDLength);
+                    // make ulKey point to the string ID for tree sorting
+                    pNew->Tree.ulKey = (ULONG)pNew->strID.psz;
+
+                    // skip leading spaces
+                    while (*pStartOfText == ' ')
+                        pStartOfText++;
+
+                    // store start of text
+                    pNew->ulOfsText = pStartOfText - pStartOfFile;
+
+                    // check if there's a comment before the
+                    // next item
+                    /* if (pNextComment = strstr(pStartOfText, "\n;"))
+                    {
+                        if (    (!pStartOfNextMarker)
+                             || (pNextComment < pStartOfNextMarker)
+                           )
+                            pEndOfText = pNextComment;
+                    } */
+
+                    if (pStartOfNextMarker)
+                        // other markers left:
+                        pNew->cbText =    // offset of next marker
+                                         (pStartOfNextMarker - pStartOfFile)
+                                       - pNew->ulOfsText;
+                    else
+                        // this was the last message:
+                        pNew->cbText = strlen(pStartOfText);
+
+                    // store this thing
+                    if (!treeInsert(&pFile->IDsTreeRoot,
+                                    (TREE*)pNew,
+                                    treeCompareStrings))
+                        // successfully inserted:
+                        (pFile->cIDs)++;
+                }
+
+                // go on with next start marker (can be NULL)
+                pStartOfMarker = pStartOfNextMarker;
+            } // end while (    (pStartOfMarker) ...
+
+            // done with IDs, or error occured:
+            if (!arc)
+                // output
+                *ppMsgFile = pFile;
             else
-            {
-                // two pointers for copying
-                PSZ             pSource = pbBuffer,
-                                pTarget = pszTemp;
-
-                CHAR            szMsgNum[5] = "0";
-                ULONG           ulTableIndex = 0;
-
-                ULONG           ulCount = 0, ulTargetWritten = 0;
-
-                // 1) copy the stuff we've read to the
-                // new temporary buffer
-                for (ulCount = 0;
-                     ulCount < ulBytesRead;
-                     ulCount++)
-                    *pTarget++ = *pSource++;
-
-                // 2) now go thru the source string
-                // (which we've read above) and
-                // look for "%" characters; we do
-                // this while copying the stuff
-                // back to pbBuffer
-
-                pSource = pszTemp;
-                pTarget = pbBuffer;
-
-                for (ulCount = 0;
-                     ulCount < ulBytesRead;
-                     ulCount++)
-                {
-                    if (*pSource == '%')
-                    {
-                        // copy the index (should be > 0)
-                        szMsgNum[0] = *(pSource + 1);
-                        // szMsgNum[1] is still 0
-
-                        // Pmpf(("      Found %s", szMsgNum));
-
-                        ulTableIndex = atoi(szMsgNum);
-                        // _Pmpf(("        --> %d", ulTableIndex));
-                        if ((ulTableIndex != 0)
-                            && (ulTableIndex <= cTable)
-                            )
-                        {
-                            // valid index:
-                            // insert stuff from table
-                            PSZ            *ppTableThis = (PSZ*)(pTable + (ulTableIndex - 1));
-
-                            // (pTable) if 1, (pTable+1) if 2, ...
-                            PSZ             pTableSource = *ppTableThis;
-
-                            // copy string from table
-                            // and increase the target pointer
-                            while ((*pTarget++ = *pTableSource++))
-                                ulTargetWritten++;
-
-                            // increase source pointer to point
-                            // behind the "%x"
-                            pSource += 2;
-                            // decrease target again, because
-                            // we've copied a null-byte
-                            pTarget--;
-
-                            ulCount++;
-                            // next for
-                            continue;
-                        }
-                        // else invalid index: just copy
-                    }
-
-                    // regular character: copy
-                    *pTarget++ = *pSource++;
-                    ulTargetWritten++;
-                }                   // end for (ulCount = 0) ...
-
-                ulBytesRead = ulTargetWritten;
-
-                free(pszTemp);      // V0.9.9 (2001-03-13) [umoeller]
-            } // if pszTemp
-        }                           // end if (cTable)
-
-    // report bytes written (*UM)
-    if (pcbMsg)
-        *pcbMsg = ulBytesRead;
-
-    // cleanup
-    if (hfile)
-        DosClose(hfile);
-    if (pbTableData)
-        free(pbTableData);
-
-    return (rc);
-}
-
-/*
- *@@ tmfGetMessageExt:
- *      just like tmfGetMessage, but this func is smart
- *      enough to return a zero-terminated string and
- *      strip trailing newlines.
- *
- *      See tmfGetMessage for details.
- */
-
-APIRET tmfGetMessageExt(PCHAR* pTable,    // in: pointer table for string insertion
-                        ULONG cTable,     // in: number of pointers in *pTable
-                        PBYTE pbBuffer,   // out: returned string
-                        ULONG cbBuffer,   // out: sizeof(*pbBuffer)
-                        PCSZ pszMessageName,  // in: message identifier
-                        PCSZ pszFile,      // in: message file (.TMF extension proposed)
-                        PULONG pcbMsg)    // out: bytes written to *pbBuffer
-{
-    APIRET  arc = NO_ERROR;
-    ULONG   cbReturned = 0;
-    arc = tmfGetMessage(pTable, cTable, pbBuffer, cbBuffer, pszMessageName, pszFile,
-                        &cbReturned);
-
-    if (arc == NO_ERROR)
-    {
-        PSZ p = pbBuffer;
-
-        // terminate string
-        if (cbReturned < cbBuffer)
-            *(pbBuffer+cbReturned) = 0;
-        else
-            *(pbBuffer+cbBuffer-1) = 0;
-
-        // remove leading spaces (99-10-24) [umoeller]
-        while (*p == ' ')
-            p++;
-        if (p != pbBuffer)
-        {
-            // we have leading spaces:
-            strcpy(pbBuffer, p);
-            // decrease byte count
-            cbReturned -= (p - pbBuffer);
-        }
-
-        // remove trailing newlines
-        while (TRUE)
-        {
-            p = pbBuffer + strlen(pbBuffer) - 1;
-            if (    (*p == '\n')
-                 || (*p == '\r')
-               )
-            {
-                *p = '\0';
-            } else
-                break; // while (TRUE)
-        }
-    }
-
-    if (pcbMsg)
-        *pcbMsg = cbReturned;
+                // error:
+                tmfCloseMessageFile(&pFile);
+
+        } // end else if (!(pFile = NEW(TMFMSGFILE)))
+    } // end if (!(arc = doshLoadTextFile(pcszMessageFile,
 
     return (arc);
@@ -455,433 +319,167 @@
 
 /*
- *@@ CompileMsgTable:
- *      this gets called from tmfGetMessage.
- *      Here we check for whether the given
- *      message file has been compiled to the
- *      EAs yet or if the compilation is
- *      outdated.
- *
- *      If we need recompilation, we do this here.
- *      Memory for the compiled message table is
- *      allocated here using malloc(), and the
- *      buffer is stored in ppbTableData. You
- *      must free() this buffer after using this
- *      function.
- *
- *      <B>Returns:</B>
+ *@@ tmfCloseMessageFile:
+ *      closes a message file opened by
+ *      tmfOpenMessageFile, frees all resources,
+ *      and sets *ppMsgFile to NULL for safety.
+ *
+ *@@added V0.9.16 (2001-10-08) [umoeller]
+ */
+
+APIRET tmfCloseMessageFile(PTMFMSGFILE *ppMsgFile)
+{
+    if (ppMsgFile && *ppMsgFile)
+    {
+        PTMFMSGFILE pFile = *ppMsgFile;
+        ULONG   cItems;
+        TREE**  papNodes;
+
+        if (pFile->pszFilename)
+            free(pFile->pszFilename);
+        xstrClear(&pFile->strContent);
+
+        if (cItems = pFile->cIDs)
+        {
+            if (papNodes = treeBuildArray(pFile->IDsTreeRoot,
+                                          &cItems))
+            {
+                ULONG ul;
+                for (ul = 0; ul < cItems; ul++)
+                {
+                    PMSGENTRY pNodeThis = (PMSGENTRY)(papNodes[ul]);
+
+                    xstrClear(&pNodeThis->strID);
+
+                    free(pNodeThis);
+                }
+
+                free(papNodes);
+            }
+        }
+
+        free(pFile);
+        *ppMsgFile = NULL;
+
+        return (NO_ERROR);
+    }
+
+    return (ERROR_INVALID_PARAMETER);
+}
+
+/*
+ *@@ tmfGetMessage:
+ *      replacement for DosGetMessage.
+ *
+ *      After you have opened a .TMF file with tmfOpenMessageFile,
+ *      you can pass it to this function to retrieve a message
+ *      with the given string (!) ID. See tmsgfile.c for details.
+ *
+ *      Note that this will invoke xstrcpy on the given XSTRING
+ *      buffer. In other words, the string must be initialized
+ *      (see xstrInit), but will be replaced.
+ *
+ *      This does perform the same simple string replacements
+ *      as DosGetMessage, that is, the string "%1" will be
+ *      replaced with pTable[0], "%2" will be replaced with
+ *      pTable[1], and so on.
+ *
+ *      Returns:
+ *
+ *      --  NO_ERROR;
+ *
  *      --  ERROR_INVALID_PARAMETER
- *      --  ERROR_BUFFER_OVERFLOW
- *      --  ERROR_NOT_ENOUGH_MEMORY
- *      --  ERROR_READ_FAULT
- *      --  ERROR_INVALID_DATA
- *
- *@@changed V0.9.1 (99-12-12) [umoeller]: fixed heap overwrites which caused irregular crashes
- *@@changed V0.9.1 (99-12-12) [umoeller]: fixed realloc failure
- *@@changed V0.9.1 (99-12-12) [umoeller]: file last-write date is now reset when EAs are written
- *@@changed V0.9.1 (2000-02-01) [umoeller]: now skipping leading spaces in msg
- *@@todo handle 64KB EA limit
- */
-
-APIRET CompileMsgTable(PSZ pszMessageFile,     // in: file to compile
-                       PBYTE *ppbTableDataReturn)  // out: compiled message table
+ *
+ *      --  ERROR_MR_MID_NOT_FOUND
+ *
+ *@@added V0.9.16 (2001-10-08) [umoeller]
+ */
+
+APIRET tmfGetMessage(PTMFMSGFILE pMsgFile,      // in: msg file opened by tmfOpenMessageFile
+                     PCSZ pcszMessageName,      // in: msg name to look for (case-sensitive!)
+                     PXSTRING pstr,             // out: message string, if found (XSTRING must be initialized)
+                     PSZ *pTable,               // in: replacement table or NULL
+                     ULONG cTableEntries)       // in: count of items in pTable or null
 {
-    APIRET          rc = NO_ERROR;
-    // CHAR            szMessageFile[_MAX_PATH];
-
-    FILESTATUS3     fs3MessageFile;
-
-    // fixed UM 99-10-22: initializing all vars to 0
-    ULONG           ulStampLength = 0;
-    PBYTE           pbFileData = NULL;
-    ULONG           ulFileDataLength = 0;
-
-    CHAR            szFileStampOld[18] = "";     // yyyymmddhhmmssms.
-
-    CHAR            szFileStampCurrent[18] = "";
-
-    PBYTE           pbTableData = NULL;
-    ULONG           cbTableDataAllocated = 0;
-    ULONG           cbTableDataUsed = 0;
-
-    HFILE           hfileMessageFile = NULLHANDLE;
-    ULONG           ulAction = 0;
-    ULONG           ulBytesRead = 0;
-
-    COUNTRYCODE     cc = {0, 0};
-
-    ULONG           cbOpenTag = strlen(MSG_NAME_START);
-    ULONG           cbClosingTag = strlen(MSG_NAME_END);
-
-    do
+    APIRET arc = NO_ERROR;
+
+    if (!pMsgFile)
+        arc = ERROR_INVALID_PARAMETER;
+    else
     {
-        PSZ             pCurrentNameStart = 0;
-        PSZ             pCurrentNameEnd = 0;
-        ULONG           ulCurrentMessagePos = 0;
-        ULONG           ulCurrentMessageLen = 0;
-        // BOOL            fError = FALSE;
-
-        // check parms
-        if ((!pszMessageFile) ||
-            (!ppbTableDataReturn))
+        // go find the message in the tree
+        PMSGENTRY pEntry;
+        if (pEntry = (PMSGENTRY)treeFind(pMsgFile->IDsTreeRoot,
+                                         (ULONG)pcszMessageName,
+                                         treeCompareStrings))
         {
-            rc = ERROR_INVALID_PARAMETER;
-            break;
-        }
-
-        // get length and timestamp of file
-        rc = DosQueryPathInfo(pszMessageFile,
-                              FIL_STANDARD,
-                              &fs3MessageFile,
-                              sizeof(fs3MessageFile));
-        if (rc != NO_ERROR)
-            break;
-        ulFileDataLength = fs3MessageFile.cbFile;
-
-        // determine current timestamp
-        GetTimeStamp(&fs3MessageFile, szFileStampCurrent, sizeof(szFileStampCurrent));
-
-        // _Pmpf(("    Timestamp for %s: '%s'", pszMessageFile, szFileStampCurrent));
-
-        // determine saved timestamp
-        ulStampLength = sizeof(szFileStampOld);
-        rc = eahReadStringEA(pszMessageFile,
-                          EA_TIMESTAMP,
-                          szFileStampOld,
-                          &ulStampLength);
-
-        // _Pmpf(("    Saved timestamp for %s: '%s'", pszMessageFile, szFileStampOld));
-
-        // compare timestamps
-        if ((rc == NO_ERROR)
-            && (ulStampLength == (strlen(szFileStampCurrent) + 1))
-            && (!strcmp(szFileStampCurrent, szFileStampOld))
-            )
-        {
-
-            // read table out of EAs
-            do
+            // copy the raw string to the output buffer
+            xstrcpy(pstr,
+                    pMsgFile->strContent.psz + pEntry->ulOfsText,
+                    pEntry->cbText);
+
+            // now replace strings from the table
+            if (cTableEntries && pTable)
             {
-                // get ea length of table
-                rc = eahReadStringEA(pszMessageFile,
-                                  EA_MSGTABLE,
-                                  NULL, &cbTableDataAllocated);
-                if (rc != ERROR_BUFFER_OVERFLOW)
-                    break;
-
-                // get memory
-                if ((pbTableData = (PBYTE)malloc(cbTableDataAllocated)) == NULL)
+                CHAR szFind[34] = "%0";
+                ULONG ul;
+                for (ul = 0;
+                     ul < cTableEntries;
+                     ul++)
                 {
-                    rc = ERROR_NOT_ENOUGH_MEMORY;
-                    break;
-                }
-
-                // read table
-                rc = eahReadStringEA(pszMessageFile,
-                                  EA_MSGTABLE,
-                                  pbTableData, &cbTableDataAllocated);
-
-            }
-            while (FALSE);
-
-            // if no error occurred, we are finished
-            if (rc == NO_ERROR)
-            {
-                // _Pmpf(("      --> using precompiled table"));
-                break;
-            }
-        } // end if
-
-        // _Pmpf(("      --> recompiling table"));
-
-        // recompilation needed:
-        // get memory for file data
-        if ((pbFileData = (PBYTE)malloc(ulFileDataLength + 1)) == NULL)
-        {
-            rc = ERROR_NOT_ENOUGH_MEMORY;
-            break;
-        }
-        *(pbFileData + ulFileDataLength) = 0;
-
-        // get memory for table data
-        cbTableDataAllocated = ulFileDataLength / 2;
-        if ((pbTableData = (PBYTE)malloc(cbTableDataAllocated)) == NULL)
-        {
-            rc = ERROR_NOT_ENOUGH_MEMORY;
-            break;
-        }
-
-        *pbTableData = 0;
-                // fixed V0.9.1 (99-12-12);
-                // this was
-                //      *(pbTableData + cbTableDataAllocated) = 0;
-                // since we're using "strcat" below to write into
-                // the table data, this wasn't such a good idea
-
-        // open file and read it
-        rc = DosOpen(pszMessageFile,
-                     &hfileMessageFile,
-                     &ulAction,
-                     0, 0,
-                     OPEN_ACTION_FAIL_IF_NEW
-                     | OPEN_ACTION_OPEN_IF_EXISTS,
-                     OPEN_FLAGS_FAIL_ON_ERROR
-                     | OPEN_SHARE_DENYWRITE
-                     | OPEN_ACCESS_READWRITE,   // needed for EA attachement
-                      NULL);
-        if (rc != NO_ERROR)
-            break;
-
-        rc = DosRead(hfileMessageFile,
-                     pbFileData,
-                     ulFileDataLength,
-                     &ulBytesRead);
-        if (rc != NO_ERROR)
-            break;
-        if (ulBytesRead != ulFileDataLength)
-        {
-            rc = ERROR_READ_FAULT;
-            break;
-        }
-
-        // search first message name
-        // this will automatically skip comments
-        // at the beginning
-        pCurrentNameStart = strstr(pbFileData, MSG_NAME_START);
-
-        if (!pCurrentNameStart)
-        {
-            rc = ERROR_INVALID_DATA;
-            break;
-        }
-        else
-            pCurrentNameStart += cbOpenTag;
-                // this points to message ID string after "<--" now
-
-        // is first name complete ?
-        pCurrentNameEnd = strstr(pCurrentNameStart, MSG_NAME_END);
-        if (!pCurrentNameEnd)
-        {
-            rc = ERROR_INVALID_DATA;
-            break;
-        }
-
-        // scan through all names
-        while (     (pCurrentNameStart)
-                 && (*pCurrentNameStart)
-              )
-        {
-            PSZ     pNextNameStart = 0,
-                    pNextCommentStart = 0,
-                    pEndOfMsg = 0;
-            CHAR    szEntry[500] = "";
-
-            // search end of name, if not exist, skip end of file
-            pCurrentNameEnd = strstr(pCurrentNameStart, MSG_NAME_END);
-                // this points to the closing "-->" now
-            if (!pCurrentNameEnd)
-                break;
-
-            // search next name (ID), if none, use end of string
-            pNextNameStart = strstr(pCurrentNameEnd, MSG_NAME_START);
-                    // points to next "<--" now
-            if (!pNextNameStart)
-                // not found:
-                // use terminating null byte
-                pNextNameStart = pCurrentNameStart + strlen(pCurrentNameStart);
-            else
-                // found:
-                // point to next message ID string
-                pNextNameStart += cbOpenTag;
-
-            pEndOfMsg = pNextNameStart - cbOpenTag;
-
-            // search next comment (99-11-28) [umoeller]
-            pNextCommentStart = strstr(pCurrentNameEnd, MSG_COMMENT_LINE);
-            if (pNextCommentStart)
-                // another comment found:
-                if (pNextCommentStart < pNextNameStart)
-                    // if it's even before the next key, use that
-                    // for the end of the current string; otherwise
-                    // the comment would appear in the message too
-                    pEndOfMsg = pNextCommentStart;
-
-            // now we have:
-            // -- pCurrentNameStart: points to message ID after "<--"
-            // -- pCurrentNameEnd: points to "-->"
-            // -- pNextNameStart: points to _next_ message ID after "<--"
-            // -- pNextCommentStart: points to _next_ comment
-            // -- pEndOfMsg: either pNextNameStart-cbOpenTag
-            //               or pNextCommentStart,
-            //               whichever comes first
-
-            // calculate table entry data
-            *pCurrentNameEnd = 0;
-            ulCurrentMessagePos = (pCurrentNameEnd - pbFileData) + cbClosingTag;
-            // this points to the first character after the <-- --> tag now;
-            // if this points to a space character, take next char
-            // (V0.9.1 (2000-02-13) [umoeller])
-            while (     (*(pbFileData + ulCurrentMessagePos))
-                     && (*(pbFileData + ulCurrentMessagePos) == ' ')
-                  )
-                ulCurrentMessagePos++;
-
-            ulCurrentMessageLen = (pEndOfMsg - pbFileData) - ulCurrentMessagePos;
-                    // pszNextNameStart - pbFileData - ulCurrentMessagePos - 1;
-
-            // determine entry
-            sprintf(szEntry, "%s %lu %lu" NEWLINE,
-                    pCurrentNameStart,
-                    ulCurrentMessagePos,
-                    ulCurrentMessageLen);
-
-            /* _Pmpf(("Found %s at %d, length %d",
-                    pCurrentNameStart,
-                    ulCurrentMessagePos,
-                    ulCurrentMessageLen)); */
-
-            // need more space ?
-            if ((cbTableDataUsed + strlen(szEntry) + 1) > cbTableDataAllocated)
-            {
-                PBYTE           pbTmp;
-                // _Pmpf(("  Re-allocating!!"));
-
-                cbTableDataAllocated += ulFileDataLength / 2;
-                pbTmp = (PBYTE)realloc(pbTableData, cbTableDataAllocated);
-                if (!pbTmp)
-                {
-                    rc = ERROR_NOT_ENOUGH_MEMORY;
-                    break;
-                }
-                else
-                    pbTableData = pbTmp;
-            }
-
-            // add entry
-            strcat(pbTableData, szEntry);
-            cbTableDataUsed += strlen(szEntry);
-                    // added V0.9.1 (99-12-12);
-                    // this was 0 all the time and never raised
-
-            // adress next entry
-            pCurrentNameStart = pNextNameStart;
-
-        }  // while (pszCurrentNameStart)
-
-        // write new timestamp and table
-        // @@todo handle 64 kb limit here !!!
-        if (rc == NO_ERROR)
-        {
-            FILESTATUS3 fs3Tmp;
-
-            rc = eahWriteStringEA(hfileMessageFile, EA_TIMESTAMP, szFileStampCurrent);
-            rc = eahWriteStringEA(hfileMessageFile, EA_MSGTABLE, pbTableData);
-
-            if (rc == NO_ERROR)
-            {
-                // fixed V0.9.1 (99-12-12): we need to reset the file date to
-                // the old date,
-                // because writing EAs updates the "last write date" also;
-                // without this, we'd recompile every single time
-                rc = DosQueryFileInfo(hfileMessageFile,
-                                      FIL_STANDARD,
-                                      &fs3Tmp,
-                                      sizeof(fs3Tmp));
-                if (rc == NO_ERROR)
-                {
-                    // restore original "last write" date
-                    // _Pmpf(("Resetting file date"));
-                    fs3Tmp.fdateLastWrite = fs3MessageFile.fdateLastWrite;
-                    fs3Tmp.ftimeLastWrite = fs3MessageFile.ftimeLastWrite;
-                    DosSetFileInfo(hfileMessageFile,
-                                   FIL_STANDARD,
-                                   &fs3Tmp,
-                                   sizeof(fs3Tmp));
+                    ULONG ulOfs = 0;
+                    PSZ pszReplThis = pTable[ul];
+
+                    _ultoa(ul + 1, szFind + 1, 10);
+                    while (xstrFindReplaceC(pstr,
+                                            &ulOfs,
+                                            szFind,
+                                            pszReplThis))
+                        ;
                 }
             }
         }
-    } while (FALSE);
-
-
-    if (rc == NO_ERROR)
-    {
-        // hand over result
-        *ppbTableDataReturn = pbTableData;
-
-        // make text uppercase
-        rc = DosMapCase(cbTableDataAllocated, &cc, pbTableData);
+        else
+            arc = ERROR_MR_MID_NOT_FOUND;
     }
+
+    return (arc);
+}
+
+/* test case */
+
+#ifdef __TMFDEBUG__
+
+int main(int argc, char *argv[])
+{
+    APIRET arc;
+    PTMFMSGFILE pMsgFile;
+
+    if (argc < 3)
+        printf("tmsgfile <file> <msgid>\n");
     else
     {
-        // error occured:
-        if (pbTableData)
-            free(pbTableData);
+        if (!(arc = tmfOpenMessageFile(argv[1], &pMsgFile)))
+        {
+            XSTRING str;
+            xstrInit(&str, 0);
+            if (!(arc = tmfGetMessage(pMsgFile,
+                                      argv[2],
+                                      &str,
+                                      NULL,
+                                      0)))
+            {
+                printf("String:\n%s", str.psz);
+            }
+            else
+                printf("tmfGetMessage returned %d\n", arc);
+
+            xstrClear(&str);
+
+            tmfCloseMessageFile(&pMsgFile);
+        }
+        else
+            printf("tmfOpenMessageFile returned %d\n", arc);
     }
-
-    // cleanup
-    if (pbFileData)
-        free(pbFileData);
-    if (hfileMessageFile)
-        DosClose(hfileMessageFile);
-
-    return rc;
 }
 
-/*
- * GetTimeStamp:
- *      this returns the "last write date" timestamp
- *      of the file specified in pfs3.
- *
- *      <B>Usage:</B> called from CompileMsgTable
- *      to check whether the compilation is
- *      outdated.
- *
- *      Returns:
- *      --  NO_ERROR
- *      --  ERROR_INVALID_PARAMETER
- *      --  ERROR_BUFFER_OVERFLOW: pszBuffer too small for timestamp
- */
-
-APIRET GetTimeStamp(PFILESTATUS3 pfs3,     // in: file to check
-                    PSZ pszBuffer,    // out: timestamp
-                    ULONG ulBufferlen)    // in: sizeof(*pszBuffer)
-{
-
-    APIRET          rc = NO_ERROR;
-    CHAR            szTimeStamp[15];
-    static PSZ      pszFormatTimestamp = "%4u%02u%02u%02u%02u%02u%";
-
-    do
-    {
-        // check parms
-        if ((!pfs3) ||
-            (!pszBuffer))
-        {
-            rc = ERROR_INVALID_PARAMETER;
-            break;
-        }
-
-        // create stamp
-        sprintf(szTimeStamp,
-                pszFormatTimestamp,
-                pfs3->fdateLastWrite.year + 1980,
-                pfs3->fdateLastWrite.month,
-                pfs3->fdateLastWrite.day,
-                pfs3->ftimeLastWrite.hours,
-                pfs3->ftimeLastWrite.minutes,
-                pfs3->ftimeLastWrite.twosecs * 2);
-
-        // check bufferlen
-        if (strlen(szTimeStamp) + 1 > ulBufferlen)
-        {
-            rc = ERROR_BUFFER_OVERFLOW;
-            break;
-        }
-
-        // hand over result
-        strcpy(pszBuffer, szTimeStamp);
-    }
-    while (FALSE);
-
-    return rc;
-}
-
+#endif
Index: trunk/src/helpers/winh.c
===================================================================
--- trunk/src/helpers/winh.c	(revision 106)
+++ trunk/src/helpers/winh.c	(revision 108)
@@ -195,10 +195,10 @@
 
     /*
-     *@@ winhSetWindowText:
+     *@@ winhSetWindowText2:
      *
      *@@added V0.9.13 (2001-06-21) [umoeller]
      */
 
-    BOOL winhSetWindowText(HWND hwnd, const char *pcsz)
+    BOOL winhSetWindowText2(HWND hwnd, const char *pcsz)
     {
         // put the call in brackets so the macro won't apply here
@@ -3419,4 +3419,30 @@
 
 /*
+ *@@ winhSetWindowText:
+ *      like WinSetWindowText, but this one accepts
+ *      printf-like arguments.
+ *
+ *      Note that the total string is limited to
+ *      1000 characters.
+ *
+ *@@added V0.9.16 (2001-10-08) [umoeller]
+ */
+
+BOOL winhSetWindowText(HWND hwnd,
+                       const char *pcszFormat,
+                       ...)
+{
+    CHAR szBuf[1000];
+    va_list     args;
+    int         i;
+    va_start(args, pcszFormat);
+    i = vsprintf(szBuf, pcszFormat, args);
+    va_end(args);
+
+    return (WinSetWindowText(hwnd,
+                             szBuf));
+}
+
+/*
  *@@ winhReplaceWindowText:
  *      this is a combination of winhQueryWindowText
Index: trunk/src/helpers/wphandle.c
===================================================================
--- trunk/src/helpers/wphandle.c	(revision 106)
+++ trunk/src/helpers/wphandle.c	(revision 108)
@@ -60,5 +60,4 @@
 #include "helpers\standards.h"
 #include "helpers\stringh.h"
-#define INCLUDE_WPHANDLE_PRIVATE
 #include "helpers\wphandle.h"
 #include "helpers\xstring.h"
Index: trunk/src/helpers/xstring.c
===================================================================
--- trunk/src/helpers/xstring.c	(revision 106)
+++ trunk/src/helpers/xstring.c	(revision 108)
@@ -379,4 +379,28 @@
 
 /*
+ *@@ xstrShrink:
+ *      reallocates the string buffer so that it
+ *      is exactly the length of the string with
+ *      its null byte, if the string has excessive
+ *      memory allocated. Useful if you are sure
+ *      that the string won't grow again.
+ *
+ *@@added V0.9.16 (2001-10-08) [umoeller]
+ */
+
+void XWPENTRY xstrShrink(PXSTRING pxstr)
+{
+    if (    (pxstr)
+         && (pxstr->psz)
+         && (pxstr->cbAllocated > pxstr->ulLength + 1)
+       )
+    {
+        pxstr->psz = realloc(pxstr->psz,
+                             pxstr->ulLength + 1);
+        pxstr->cbAllocated = pxstr->ulLength + 1;
+    }
+}
+
+/*
  *@@ xstrCreate:
  *      allocates a new XSTRING from the heap
