Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.

Channels ▼


Undocumented Corner

Source Code Accompanies This Article. Download It Now.


The PIF File Format, or, Topview (sort of) Lives!

Mike is the developer of EDOS and other enhancements to DOS sessions under Windows. He can be reached on CompuServe at 71171,47, or by telephone at 503-694-2221. Additional PIF-related resources are available on the EDOS BBS, 503-643-8396.

Edited by Andrew Schulman

The next time you're in Silicon Valley, visit the Weird Stuff Warehouse in Sunnyvale, just across the street from Fry's Electronics. In addition to piles of dead disk drives and boxes full of doohickies, Weird Stuff ("We Buy Excess Inventories") also has several aisles of defunct software.

Walking these aisles is a sobering experience for any software developer. Here, for example, you will find the mammoth OS/2 Extended Edition for $14.95 (the disks alone are probably worth more), IBM's Topview software development kit for $7.95, and Topview itself for only $4.95. This is the software boulevard of broken dreams.

The fact is, most new technology never goes anywhere. Very often, something to which many man-years have been devoted ends up as little more than excess inventory. Equally often, these products were greeted with great fanfare when they first appeared. How many of today's hot products are going to wind upon the shelves of Weird Stuff with their price knocked down to $9.95?

So products, technologies, and companies come and go. At the same time, another aspect of our industry is the way that outmoded technology persists longer than anyone would expect. The presence of many CP/M-isms in MS-DOS, even when running on the hottest Pentium processor, is a good example of this kind of uneven development.

Even unsuccessful products can leave their mark. A case in point is this month's topic, the Program Information File (PIF) format. Most Windows programmers and users are probably familiar with PIF files as the mechanism that Windows uses to exercise some control over how "old" DOS programs are run. An end user might use the PIF editor to instruct Windows to run his or her copy of dBase III Plus in a window, with background execution enabled. Windows NT (which may or may not be destined to wind up at Weird Stuff, next to the piles of OS/2 Extended Edition) also uses PIF files.

Most Windows programmers probably don't remember that Windows PIF files come directly from that $4.95 operating environment, Topview. Quarterdeck's Desqview operating environment, which at one point in its career was a Topview clone, uses the same PIF file format, under the name DVP. As Stephen Manes and Paul Andrews note in their biography of Bill Gates, "Microsoft's adaptation of PIFs would remain long after Topview had withered and died."

So we have a more or less unbroken chain from Topview to NT; the more things change, the more they stay the same. As Mike Maurice shows this month, a large portion of this file format really has remained unchanged since the days of Topview. This portion of the format was documented by Quarterdeck for the Desqview SDK; you can read more about it in the chapter on Desqview in Extending DOS, second edition, edited by Ray Duncan (Addison-Wesley, 1992).

Unfortunately, most items of interest to Windows programmers, such as the flags controlling windowed vs. full-screen display, background vs. foreground execution, the idle-detection flag, and so on, are in portions of the file added later by Microsoft, and thus are not described in the Desqview documentation. Microsoft has documented many other less-important file formats (such as those used by the Calendar applet), but it has not documented PIF.

Mike (author of EDOS, a popular Windows DOS-box enhancer) has done a nice job of cracking this format, and even of explaining how the PIF editor in Windows 3.1 can manage NT PIFs. In essence, after the initial Topview/

Desqview portion of the PIF file, there's a linked list of records; each record starts with a string. The first of these records starts at offset 171h in the PIF file and has the string "MICROSOFT PIFEX"; this can be used as a signature to determine that you have a valid Windows PIF file. Most of the interesting PIF flags are kept in the record that begins with the string "WINDOWS 386 3.0"; the PIFSTRUC.H header file (described later) refers to the corresponding structure as DATA386.

As an illustration of what you can do once you know the PIF file format, I've written a small set of functions (PIFEXEC.C, Listing One, page 144) that allow a few PIF flags to be set programmatically. The PifExec() function is similar to WinExec(), except that you can specify a window title and set or clear the background-execution and windowed-display flags. PifExec() is written using the functions ReadPif(), WritePif(), GetPif386(), and IsPif(), also in PIFEXEC.C. PifExec() reads in the stock _DEFAULT.PIF file, modifies a few fields, and writes out a new __TMP.PIF file, which it then passes to WinExec(). PIFEXEC.C uses Mike's PIFSTRUC.H (Listing Two, page 144).

As always, I welcome your comments and suggestions for this column; send e-mail via CompuServe (76320,302) or Internet ([email protected]).

--Andrew Schulman

By now you are probably thinking, "Who cares about PIFs, anyway?" Program Information Files: What could be more boring? Well, maybe PIF files are slightly boring. But knowing how they are constructed can also be very useful!

In case you have forgotten (or never knew), PIF files contain information--flags, integer, byte quantities, and alphabetic strings--to guide the operating environment (notice I did not call it an "operating system"), in starting and running a DOS application.

The DOS executable file format does not contain enough information for a multitasking environment. When writing new applications, you use a new executable file format. In fact, Windows applications use something called, appropriately enough, the "new" executable file format, with an NE signature. But what about running old applications under new environments? The old executable can't be changed. So a secondary file tags along, with information needed by the operating environment, called a Program Information File (PIF).

If you have ever used the PIF Editor in Windows, you know that PIF files contain flags that determine whether the target application can run in the background, what its timeslice priorities are, whether it starts up windowed or full-screen, how much XMS and EMS memory it uses, and so on.

When Moses came down from the mountain, he said there would be PIFs. Well, maybe it was someone else. But the effect was the same. The first record of PIFs seems to have been with IBM's Topview. Remember Topview? It came before QuarterDeck's Desqview. Both of these systems were designed to run more than one task. Would you believe that even Windows NT uses PIFs? This just proves that we are stuck with PIFs, as far as the eye can see.

The PIF Record Structure

Topview used a PIF of about 171h bytes. This basic early PIF contained a filename and stored some BIOS data variables that needed duplicating, such as memory and screen information.

When Desqview came out, PIFs were renamed .DVP files, and their size increased, from 172h up to 18Dh in later versions. This extra room was needed for more exotic flags to indicate serial-port use and virtualization of other shared hardware resources (memory, screen, keyboard, and so on). Ralf Brown's "MS-DOS Interrupt List" (available on CompuServe in the IBMPRO forum and at Simtel20 on the Internet) and the book PC Interrupts (Addison-Wesley, 1991) by Ralf Brown and Jim Kyle, contains a fair description of the basic Topview/Desqview PIF format. This information can be used as the beginning basis for decoding the layout of the Windows PIF format.

Today, Windows uses the basic 171h data structure with few modifications, but increases the file, potentially to 3FFh (1023) bytes. These extra bytes are of course not covered in Topview/Desqview descriptions. The new portion of the file is used for storing the flags and switches used by standard and 386 Enhanced Mode Windows when running a DOS session. Only about a third or a half of the original 171h byte space is used by Windows. The PIF Editor reads in 3FFh bytes and will save the number of bytes read, which may be less than 3FFh, as most Windows PIF files are only 221h (545) or 23Ch (572) bytes long.

PIFs created under NT use the same format, but store different information. NT PIFs can be edited and run using Windows 3.1 and vice-versa. How's that? Obviously, the only way that a newer file layout can be recognized by an older editor is if all the formats follow the same guidelines.

The area from 171h to 3FFh in the Windows PIF file is a simple linked-list record system. A sample hex dump of this area (using _DEFAULT.PIF) is shown in Figure 1. A block consists of:

  • A 16-byte string, such as "WINDOWS 386 3.0".
  • A three-word structure that contains the offsets to the next and current record and the data-record size.
  • The data record itself.
The four known record types are: "MICROSOFT PIFEX", "WINDOWS 286 3.0", "WINDOWS 386 3.0", and "WINDOWS NT 3.1". (Note that there are two spaces between "NT" and "3.1".)

The MICROSOFT PIFEX record must come directly at offset 171h. (WinOldAp, the Windows module responsible for running DOS programs, relies on the "MICROSOFT PIFEX" string at offset 171h as a "sanity check" that it has a valid PIF file.) However, the number and order of the WINDOWS ?86 3.0 record groups does not appear to be important. In some files the first W in the 286 record will be zeroed. This seems to indicate that it is not being used, in which case there is normally another 286 record that does not have the W zeroed.

A COMMENT record can be created, using the appropriate string, and plugging in the correct offsets and size. This has been tested and worked on the March 1993 NT beta. The resulting records can be read and written by both the Windows 3.1 and NT PIF editors.

The layout of the Windows PIF format is presented in PIFSTRUC.H (Listing Two). Byte offsets in hex are noted in the comments. These offsets are correct up to 171h. After that, they are based on the offsets typical of Windows 3.1 PIFs. These are not correct for all PIFs, but since most readers will only have access to Windows 3.1, I've also provided them in this manner. It is more portable to use the structures and fields in PIFSTRUC.H rather than the hard-coded file offsets.

PIFSTRUC.H uses C bit fields. Oddly enough, in some cases the PIF bit fields are organized on half-byte boundaries; very strange! For example, the reserved hotkeys start in the middle of one byte and continue into the next.

NT PIF files add a new record type; a typical NT PIF file is 745 bytes long. The record consists of the ID string ("WINDOWS NT 3.1"), then 12 unknown bytes followed by two 64-byte fields to hold the pathnames for two files: session-specific AUTOEXEC.BAT and CONFIG.SYS files. NT totally ignores several kinds of information used in Windows 3.1 Enhanced Mode. NT automatically sets the size of conventional memory, priorities, monitor ports, idle detection, and use of the HMA. Under NT, PIF information is much less environment sensitive than in Windows 3.1: Options are simplified, and the NT PIF is left with identifying strings, files, paths, and keyboard issues.

READPIF.C is a demonstration program that prints the contents of a PIF file (available electronically; see "Availability," page 5). The program is a DOS application that will compile under Borland or Microsoft C; it basically reads the PIF file into a buffer, checks to see if it is a legal MS PIF and then prints out the defined strings. Using printf statements, it formats and prints the values of the various flags, bytes, and word variables. The first 171h bytes are in a fixed format and can be decoded on the spot. The record blocks that follow require following the linked list; a pointer initialized to the first record leads to the next record, and so on. At each record block, a compare is made to find the record type. The record can then be dumped using an appropriately typedefed pointer. The last record has a next pointer of --1, at which point the For loop is exited and a success message is printed to indicate that the record system was successfully decoded.

How Windows Uses PIF Settings

It's interesting to look for a few moments at how Windows actually uses PIF files. This requires an understanding of how Windows starts a DOS session. The following discussion assumes that Standard mode is a dead issue, and that the emphasis should be on Enhanced mode.

DOS sessions are started by a call to WinExec. When it sees that it has a DOS rather than a Windows program, WinExec starts WinOldAp (WINOA386.MOD in Enhanced mode), giving it the PIF or DOS application name to run. If a DOS application name is given, WinOldAp will use _DEFAULT.PIF. If an explicit PIF name is used, it will contain the name of the DOS application or batch file to be executed (see the prog_path field in Listing Two).

When the first instance of WinOldAp is started, the 386 grabber is loaded. WinOldAp creates a Virtual Machine (VM), and then WSHELL, a virtual device driver (VxD) built into WIN386.EXE, forces the DOS application to start in the new VM. WinOldAp opens the PIF file to be used and at various times reads out the data it needs to start the session. In general, WinOldAp does not store the PIF information internally.

The DOS session system consists of the grabber, virtual display driver (VDD), virtual keyboard driver (VKD), and WinOldAp. There are other virtual drivers but these are the basic components. The grabber is a Windows DLL that renders a windowed DOS session into a Windows window. When a DOS session is windowed, the DOS application is not actually printing to the real display, but only to a virtual hidden ("shadow") screen. The grabber reads this hidden screen and displays the results in the window. The VDD maps the physical display memory in and out of the various DOS sessions (VMs), traps the I/O ports, and attempts to bring order in controlled chaos. Many times, when you get a GP fault while running a DOS application, the VDD is at fault. VKD does similar work for the keyboard.

PIF options are supported by several function calls documented in the Windows Device Driver Kit (DDK) Virtual Device Adaptation Guide (VDAG). The following VxD calls are related in one way or another to PIF settings: VDD_PIF_State, VKD_Define_Paste_Mode, VMPoll_Enable_Disable, _DOSMGR_Set_Exec_VM_Data, the Get/Set_Time_Slice_Priority/Granularity functions, SHELL_GetVMInfo, and the V86MMGR_Get/Set_EMS_XMS_Limits functions. There are also undocumented calls, and generally the data structures in WinOldAp and the VxDs are all undocumented.

PIF settings fall into several categories. Those set at VM start-up can't be changed; this includes settings such as the file to be started. Some PIF settings are easy to change in mid-session: priority, exclusive, background, application hotkey, and window title. Some are quite difficult to change in midstream, including memory locking and reserving hot keys. Most video options seem impossible to change on-the-fly. Finally, some options are simply not practical to change once a session starts, such as EMS/XMS memory size.

The Monitor Ports settings in the PIF editor Advanced screen turn on and off VDD's trapping of I/O for the various display modes. Enabling VDD trapping causes a performance slowdown and is normally avoided. The Emulating Text Mode option causes the VDD to replace some video BIOS calls with its own routines. The performance improvement is substantial.

Changing PIF Settings On-the-Fly

Knowledge of PIF layout is necessary to change PIF settings which, as shown in the PIFEXEC example, allows much more dynamic DOS-session creation. However, the weakness in this approach is that the PIF has to be changed before the DOS application is started.

A more elegant solution would allow changes on-the-fly, while the DOS application is running. Changing PIF settings on-the-fly requires building a Virtual Device Driver (VxD). A VxD can watch PIF setting changes and make its own changes, in concert or at any time it chooses.

For the past year I've been building just such a system. It's called "EDOS," or Enhanced DOS for Windows, and is built from a VxD and a DLL. It supports changing most PIF settings (for instance, priorities, time slice, exclusive, background, fast paste, and task switching) either from the command line (using the undocumented COMMAND.COM interface, INT 2Fh functions AE00h and AE01h) or by way of a virtual-8086 (V86) entry point. A V86 entry point allows a DOS application to call into a VxD and execute code running at ring zero. There is also a protected-mode (PM) entry-point system for use by Windows applications. The entry-point mechanism is a feature of the VxD system, but the code that is executed and that provides the useful functionality is the responsibility of the VxD developer. VxD development is not trivial, but it can be fun.

If a Windows PIF had additional flag bits defined, then a utility such as EDOS could be enhanced to examine this information. The VxD could be modified to enable/disable disk swapping, or perhaps to assign the serial port automatically. Alternatively, it could be modified to support a V86-mode DOS call that would assign the serial port in an open/

close environment.

A shareware version of EDOS, which demonstrates changing many PIF settings from the command line, is available on CompuServe (GO WINADV) and is also included in Brian Livingston's book Windows 3.1 Secrets (IDG Books, 1992).

In closing, we might speculate on why Microsoft does not document the PIF file format, especially when so many other formats, such as font files, Program Manager files, and even the file format used by the Calendar applet have been documented. One developer at Microsoft told us that, "We can't document that; it's going away in the next release." However, as we've seen, the ages-old PIF format persists in NT, and, for better or worse, definitely is not going away any time soon.

Figure 1: Hex dump of _DEFAULT.PIF: Boxes indicate next and current pointers.

0000: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 ................
0010: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 ................
0020: 80 02 80 00 5F 44 45 46  41 55 4C 54 2E 42 41 54 ...._DEFAULT.BAT
0030: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 ................
0170: 00 4D 49 43 52 4F 53 4F  46 54 20 50 49 46 45 58 .MICROSOFT PIFEX
0180: 00 87 01 00 00 71 01 57  49 4E 44 4F 57 53 20 32 .....q.WINDOWS 2
0190: 38 36 20 33 2E 30 00 A3  01 9D 01 06 00 00 00 00 86 3.0..........
01A0: 00 00 00 57 49 4E 44 4F  57 53 20 33 38 36 20 33 ...WINDOWS 386 3
01B0: 2E 30 00 FF FF B9 01 68  00 80 02 80 00 64 00 32 .0.....h.....d.2
01C0: 00 00 04 00 00 00 04 00  00 08 10 02 00 1F 00 00 ................
01D0: 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 ................

edited by Andrew Schulman
by Michael P. Maurice

: PIFEXEC.C<a name="0210_0009">

/* PIFEXEC.C -- Dr. Dobb's Journal "Undocumented Corner"
Andrew Schulman, April 1993 -- bcc -WS -DTESTING pifexec.c */

#include <stdlib.h>
#include <string.h>
#include "windows.h"
#include "pifstruc.h"

/* is this a valid Windows PIF file? */
BOOL IsPif(PIF far *fppif)
    BYTE far *fp = (BYTE far *) fppif;
    return (lstrcmp(&fp[PIFEX_OFFSET], "MICROSOFT PIFEX") == 0);
/* read PIF file into memory */
BOOL ReadPif(char far *name, PIF far *fppif)
    HFILE f;
    UINT cb;
    if ((f = _lopen(name, READ)) == HFILE_ERROR)
        return FALSE;
    if ((cb = _lread(f, fppif, MAX_PIFFILE_SIZE)) == HFILE_ERROR)
        return FALSE;
    if (cb < PIFEX_OFFSET)
        return FALSE;
    return IsPif(fppif);
/* write PIF structure to file on disk */
BOOL WritePif(char far *name, PIF far *fppif)
    HFILE f;
    UINT cb;
    if ((f = _lcreat(name, 0)) == HFILE_ERROR)
        return FALSE;
    if ((cb = _lwrite(f, fppif, MAX_PIFFILE_SIZE)) == HFILE_ERROR)
        return FALSE;
    if (cb < MAX_PIFFILE_SIZE)
        return FALSE;
    return TRUE;
/* return pointer to the Windows 386 section */
DATA386 far *GetPif386(PIF far *fppif)
    BYTE far *fp = ((BYTE far *) fppif) + PIFEX_OFFSET;
    SECTIONHDR far *fpsection =
        (SECTIONHDR far *) (fp + sizeof(SECTIONNAME));
    if (! IsPif(fppif))
        return (DATA386 far *) 0;
    for (;;)
        if (lstrcmp(fp+1, "INDOWS 386 3.0") == 0)
            return (DATA386 far *) ((BYTE far *) fppif +
        if (fpsection->next_section == 0xFFFF)
        fp = (BYTE far *) fppif + fpsection->next_section;
        fpsection = (SECTIONHDR far *) (fp + sizeof(SECTIONNAME));
    /* still here */
    return (DATA386 far *) 0;
int _dos_delete_file(char far *filename)
    _asm push ds
    _asm lds dx, dword ptr filename
    _asm mov ah, 41h
    _asm int 21h
    _asm pop ds
    _asm jc error
    return 0;   // success
    // return error in AX
/* WinExec a DOS app, specifying a few PIF settings. This is intended only as
  an example; other PIF settings can similarly be manipulated programmatically.
  For example, the program's command line
   (ppif->prog_param), default directory (ppif->def_dir), and
   idle-detect flag (data386->flags386.Detect_Idle). */
UINT PifExec(char far *name, char far *title, BOOL background, BOOL windowed)
    static char *tmp_pif = "__tmp.pif";
    PIF *ppif;
    UINT retval = 0;
    DATA386 far *data386;
    char *pathname;

    if (! (pathname = (char *) malloc(256)))
        return FALSE;
    if (! (ppif = (PIF *) malloc(MAX_PIFFILE_SIZE)))
        return FALSE;
    /* read in the standard _DEFAULT.PIF file */
    GetWindowsDirectory(pathname, 256);
    strcat(pathname, "\\_default.pif");
    if (! ReadPif(pathname, ppif))
        goto done;
    /* modify some fields in the PIF structure */
    if ((lstrlen(name) > 63) || (lstrlen(title) > 30))
        goto done;
    lstrcpy(ppif->prog_path, name);
    lstrcpy(ppif->title, title);
    if (! (data386 = GetPif386(ppif)))
        goto done;
    data386->flags_386.BackgroundON = background;
    data386->flags_386.FullScreenYes = (! windowed);
    /* write out a new __TMP.PIF file, WinExec it, and delete it */
    if (WritePif(tmp_pif, ppif))
        retval = WinExec(tmp_pif, SW_NORMAL);
    return retval;
#ifdef TESTING
/* Standalone test:  run with a DOS program name on the command
   line.  For example:  PIFEXEC \DOS\COMMAND.COM */
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
    if (lpszCmdLine && *lpszCmdLine)
        PifExec(lpszCmdLine, "A Test of PifExec", 1, 1);
        MessageBox(0, "usage: pifexec [program name]", "PIFEXEC", MB_OK);
    return 0;

<a name="0210_000a">
 : PIFSTRUC.H<a name="0210_000b">

/* PIFSTRUC.H -- Structure of Windows PIF files --
Dr. Dobb's Journal "Undocumented Corner" -- Mike Maurice, July 1993 */

#define MAX_PIFFILE_SIZE        0x3FF
#define PIFEX_OFFSET            0x171

typedef struct {
    char name_string[16];
typedef struct {
    WORD next_section; /* offset of section after this      */
    /* last section if contents = FFFF   */
    /* contents = 205, NT = 1A3          */
    WORD current_section; /* offset of data */
    /* contents = 19d                    */
    WORD size_section; /* sizeof section */
    /* contents = 68, NT = 06            */
typedef struct {
    int Unused0 :1;
    int Graph286 :1;
    int PreventSwitch :1;
    int NoScreenExch :1;
    int Close_OnExit :1; /* only bit used in 386 mode */    // 0x10
    int Unused001 :1;
    int Com2 :1;
    int Com1 :1;
typedef struct {
    int AllowCloseAct :1;       // 0x01
    int BackgroundON :1;        // 0x02
    int ExclusiveON :1;         // 0x04
    int FullScreenYes :1;       // 0x08
    int Unused1 :1;
    int RSV_ALTTAB :1;          // 0x20
    int RSV_ALTESC :1;          // 0x40
    int RSV_ALTSPACE :1;        // 0x80
    int RSV_ALTENTER :1;        // 0x01 << 8
    int RSV_ALTPRTSCR :1;       // 0x02 << 8
    int RSV_PRTSCR :1;          // 0x04 << 8
    int RSV_CTRLESC :1;         // 0x08 << 8
    int Detect_Idle :1;         // 0x10 << 8
    int UseHMA :1;              // 0x20 << 8
    int Unused2 :1;
    int EMS_Locked :1;          // 0x80 << 8
} FLAGS386;
typedef struct {
    int XMS_Locked :1;          // 0x01
    int Allow_FastPst :1;       // 0x02
    int Lock_App :1;            // 0x04
    int Unused3 :5+8;
typedef struct {
    int VidEmulateTxt :1;       // 0x01
    int MonitorText :1;         // 0x02
    int MonitorMGr :1;          // 0x04
    int MonitorHiGr :1;         // 0x08
    int InitModeText :1;        // 0x10
    int InitModeMGr :1;         // 0x20
    int InitModeHiGr :1;        // 0x40
    int VidRetainVid :1;        // 0x80
    int VideoUnused :8;
typedef struct {
    int HOT_KEYSHIFT :1;    // 0x01
    int Unused4 :1;
    int HOT_KEYCTRL :1;     // 0x04
    int HOT_KEYALT :1;      // 0x08
    int Unused5 :4+8;
typedef struct {
    int AltTab286 :1;
    int AltEsc286 :1;
    int AltPrtScr286 :1;
    int PrtScr :1;
    int CtrlEsc286 :1;
    int SaveScreen :1;
    int Unused10 :2;
} FLAGS286;
typedef struct {
    int Unused11 :4+2;
    int Com3 :1;
    int Com4 :1;
typedef struct {
    /* The offsets are accurate only for Windows -- *NOT* NT! */
    short mem_limit; /* 19d */
    short mem_req; /* 19f */
    WORD for_pri; /* 1a1  */
    WORD back_pri; /* 1a3  */
    short ems_max; /* 1a5  */
    WORD ems_min; /* 1a7  */
    short xms_max; /* 1a9  */
    WORD xms_min; /* 1ab  */
    FLAGS386 flags_386; /* 1ad  */
    FLAGSXMS flags_XMS; /* 1af  */
    VIDEO video; /* 1b1  */
    WORD zero1; /* 1b3  */
    WORD hot_key_scan; /* 1b5 */
    /* any other legal ky on board, a scan code number. */
    HOTKEY hot_key_state; /* 1b7,  alt, ctrl, shift.              */
    WORD hot_key_flag; /* 1b9, 0=no hot key, ? f= hot key defined */
    WORD zero2[5]; /* 1ba  */
    char opt_params[64]; /* 1c5, 386 mode for opt params         */
} DATA386, FAR *fpDATA386;
typedef struct {
    WORD xmsLimit286; /* 237  */
    WORD xmsReq286; /* 239  */
    FLAGS286 flags_286; /* 23b  */
    COMPORT com_ports; /* 23c  */
} DATA286, FAR *fpDATA286;
typedef struct {
    /* from 0 -170 hex, not used by Windows, unless so indicated. */
    /* Note that in some cases the PIF editor fills in a value,     */
    /* even though it does not SEEM to be used        */
    BYTE resv1;
    BYTE checksum; /* used by Windows                     */
    char title[30]; /* 02 used by 286,386 mode for title   */
    short max_mem; /* 20h used byt 286, 386 mem size      */
    short min_mem; /* 22h, these 2 are duplicates see 19c */
    char prog_path[63]; /* 24h used by 286,386 mode for program & path*/
    CLOSEONEXIT close_onexit; /* 63h, 286 and 386 modes     */
    BYTE def_drv; /* 64h  */
    char def_dir[64]; /* 65h used by 286,386 mode for start dir */
    char prog_param[64]; /* a5, used by 286 */
    BYTE initial_screenMode; /* usually zero, sometimes 7F hex         */
    BYTE text_pages; /* always one                             */
    BYTE first_interrupt; /* always zero                            */
    BYTE last_interrupt; /* always FF hex                          */
    BYTE rows; /* always 25                              */
    BYTE cols; /* always 80                              */
    BYTE window_pos_row;
    BYTE window_pos_col;
    WORD sys_memory; /* always 7  */
    char shared_prog_name[64];
    char shared_prog_data_file[64];
    BYTE flags1; /* 16f, usually zero  */
    BYTE flags2; /* 170, usually zero  */
    /* Microsoft PIF editor reads up to 3FF hex bytes in. When writing back */
    /* out it writes same number of byte read. This means a PIF file can    */
    /* be up to 3FF hex bytes with the assumption that any 3rd party        */
    /* utilities take this into account. NOTE 400 hex WILL NOT WORK !!      */
    /* Tested under Win 3.1 and NT (Oct 92 beta).                           */
} PIF, FAR *fpPIF; /* PIF structure    */
/* ---   171h  Begin of Microsoft Windows Stuff */
SECTIONNAME pifex; /* 171,hard coded " MICROSOFT PIFEX"   */
SECTIONHDR section_zero; /* 181   */
SECTIONNAME first_name; /* 187, hard coded "WINDOWS 386 3.0", 286 if NT  */
SECTIONHDR section_one; /* 197, points to str_286A, or section_nameNT   */
#ifdef NT
DATA286 data_286; /* 19D   */
SECTIONNAME section_nameNT; /* 1A3, hard coded "WINDOWS 386 3.0"  */
SECTIONHDR section_hdrNT; /* 1B3   */
DATA386 data_386; /* 1B9   */
/* paded with zeros, from 220-22f hex.   */
DATA386 data_386; /* 19D   */
/* ---205 hex, end of 386 material   */
/* start of 286 specific stuff       */
SECTIONNAME str286A; /* 205, hard coded " INDOWS 286 3.0" */
SECTIONHDR section_286A; /* 215,  */
DATA286 data_286A; /* 21B   */
SECTIONNAME str286B;/* 221, hard coded "WINDOWS 286 3.0"  */
SECTIONHDR section_286B; /* 231   */
DATA286 data_286B; /* 237   */
/* ends at 23c   */
#endif   /* NT */
/* 23d   */
#endif   /* DOCUMENTATION   */
typedef struct {
    DATA386 D386;
} BLOCK386, *npBLOCK386, FAR *fpBLOCK386;
typedef struct {
    //DATA386 D386;
typedef struct {
    DATA286 D286;
} BLOCK286, *npBLOCK286, FAR *fpBLOCK286;
typedef char FAR *fpBLOCKCMNT;
typedef char *npBLOCKCMNT;
typedef struct {
typedef struct {
    char AuxName[8+1+3];
typedef struct {
    BYTE Hdr1[3];
    BYTE HChkSum;
typedef struct {

<a name="0210_000c">
<a name="0210_000c">

Copyright 1992,1993 Michael P. Maurice
This is very accurate and moderately tested.
This is originally based on documentation in Ralf Brown's interrupt list.

The bit structures that are passed to printf, etc, are not portable
and give a structure passed by value warning under Borland C.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef unsigned char BYTE;
typedef unsigned short WORD;

#define FAR     _far

#include "pifstruc.h"

void display_NT(BLOCKNT *pPIF);
void display_386(DATA386 * pPIF);
void display_286(DATA286 * pPIF);
void display_comment(char * pPIFCOMMENT);
void usage(void);
void argcheck(int, char **);
void help(void);

/* make sure this is in public space & cleared to ZERO! */
char tbuf[MAX_PIFFILE_SIZE + 10];
char *ifilename;
int lflag; /* list linked records                       */
int cflag; /* list comment records                      */
int nflag; /* NT records                                */
int vflag; /* verbose listing, including the kitchen sink */
int nfile;
int flag2; /* 286 records only                          */
int flag3; /* 386 records only                          */

main(int argc, char **argv)
    FILE *ifil;
    BYTE tChkSum;
    unsigned int offset;
    int i;
    PIF *pPIF;
    char *pPIFCOMMENT;
    DATA286 *pPIF286;
    DATA386 *pPIF386;
    char *pPIFEX;
    SECTIONHDR *pPrevSect, *pCurSect;
    char *pLastByte;

    if (argc < 2) usage();
    ifilename = *++argv;


    ifil = fopen(ifilename, "rb");
    if(!ifil) {
        printf("Failed Open: %s\n", ifilename);

    fputs("\n\nReading PIF: ", stdout);
    fputs(ifilename, stdout);
    fputs("\n", stdout);

    fread(tbuf, MAX_PIFFILE_SIZE ,1, ifil);

    for (i=2, tChkSum=0; i< PIFEX_OFFSET; i++)
        tChkSum += tbuf[i];
        printf("calc.chksum = 0x%x\n\n",(tChkSum & 0x00ff));

    pPIF = (PIF *)&tbuf;

#define PIF_String(a, b)  if(pPIF->a) printf("%s\n",b)

    if(tChkSum != pPIF->checksum)
        fputs("Checksum ERROR!!\n", stdout);

resv1        = 0x%x\n\
checksum     = 0x%x\n",
        pPIF->resv1, pPIF->checksum & 0x00ff);

        printf("close_onexit = 0x%x  ,",pPIF->close_onexit);
    }/* vflag     */

title        = %.30s\n\
max_mem      = %d dec.\n\
min_mem      = %d dec.\n\
prog_path    = %.63s\n",
    pPIF->title, pPIF->max_mem,
    pPIF->min_mem, pPIF->prog_path);

        PIF_String(close_onexit.Graph286, "Graphics 286");
        PIF_String(close_onexit.NoScreenExch, "No Screen Exchange 286");
        PIF_String(close_onexit.PreventSwitch, "Prevent Switch 286");
        PIF_String(close_onexit.Close_OnExit, "Close On exit");
        PIF_String(close_onexit.Com1, "Com 1 - 286");
        PIF_String(close_onexit.Com2, "Com 2 - 286");
    }/* flag2     */

    printf("def_drv = %s\n", pPIF->def_drv);
    printf("def_dir = %s\n", pPIF->def_dir);
        printf("286 opt. param = %.64s\n", pPIF->prog_param); /*vflag*/

initial_screenMode    = 0x%x\n\
text_pages            = 0x%x\n\
first_interrupt       = 0x%x\n\
last_interrupt        = 0x%x\n\
rows                  = %u dec.\n\
cols                  = %u dec.\n",
    pPIF->initial_screenMode & 0x00ff,pPIF->text_pages& 0x00ff,
    pPIF->first_interrupt& 0x00ff,pPIF->last_interrupt& 0x00ff,
    pPIF->rows & 0x00ff, pPIF->cols& 0x00ff);

    if(vflag) {
window_pos_row        = 0x%x\n\
window_pos_col        = 0x%x\n\
sys_memory            = 0x%x\n\
shared_prog_name      = %s\n\
shared_prog_data_file = %s\n\
flags1                = 0x%x\n\
flags2                = 0x%x\n",

        pPIF->window_pos_row& 0x00ff, pPIF->window_pos_col& 0x00ff,
        pPIF->sys_memory, pPIF->shared_prog_name,
        pPIF->flags1& 0x00ff, pPIF->flags2& 0x00ff);
    }/* vflag     */

    pPIFEX = &tbuf[PIFEX_OFFSET];
    if(strcmp(pPIFEX, "MICROSOFT PIFEX"))
        fputs("NOT a MICROSOFT PIF FILE\n", stdout);
        return 1;

    fputs(" ********** pifex = ",stdout);
    fputs(pPIFEX, stdout);
    fputs("  *********************\n", stdout);

    pPrevSect = (SECTIONHDR *)&tbuf[PIFEX_OFFSET+sizeof(SECTIONNAME)];
    pName = (SECTIONNAME *)&tbuf[pPrevSect->next_section];
    pLastByte = &tbuf[MAX_PIFFILE_SIZE];

    for (i=0;i<40;i++)  {
        if(lflag) {
next_offset = 0x%x, \
current_section = 0x%x, \
size_section = 0x%x\n",
            pPrevSect->next_section, pPrevSect->current_section,
        }/* sflag */

        if(pPrevSect->next_section == 0xFFFF)

        offset = pPrevSect->next_section+sizeof(SECTIONNAME);
        pCurSect =(SECTIONHDR *) &tbuf[offset];

        fputs("----------------- Record Type: ",stdout);
        if(pName->name_string[0] != 0)
            fputc(pName->name_string[0], stdout);
        else fputc('\x20', stdout);

        /* if the first char position is zero, the section is not in use */
        /* however, since this is a dump program, we will dump the section */

        fputs(pName->name_string+1, stdout);
        fputs(" ----\n", stdout);

        if(!strcmp(pName->name_string+1, "INDOWS 386 3.0"))
                pPIF386 = (DATA386 *)&tbuf[pCurSect->current_section];

                if(pPIF386->zero1 != 0) fputs("\7 386.zero1 != 0 \n", stdout);

                if((pPIF386->zero2[0] == 0) ||
                    (pPIF386->zero2[1] == 0) || (pPIF386->zero2[2] == 0) ||
                    (pPIF386->zero2[3] == 0) ||(pPIF386->zero2[4] == 0)) ;
                    fputs("\7 386.zero2 != 0 \n", stdout);

                if((pPIF386->hot_key_flag == 0) ||
                    (pPIF386->hot_key_flag == 0xF)) ;
                    fputs("\7 386.hot_key_flag has strange value\n", stdout);
            }/* flag3 */
        if(!strcmp(pName->name_string+1, "INDOWS 286 3.0"))
                pPIF286 = (DATA286 *)&tbuf[pCurSect->current_section];
            }/* vflag2 */

        if(!strcmp(pName->name_string+1, "INDOWS NT  3.1"))
                pPIFNT = (BLOCKNT *)&tbuf[pCurSect->current_section];
            }/* nflag */

        /* here we document a technique for supporting comments in PIFs */
        if(!strcmp(pName->name_string, "COMMENT"))
                pPIFCOMMENT =
                    (char *)&tbuf[pCurSect->current_section+sizeof(COMMENTS)];
            }/* cflag */

        pPrevSect = (SECTIONHDR *)&tbuf[pPrevSect->next_section +
        pName = (SECTIONNAME *)&tbuf[pPrevSect->next_section];
        if((char *)pPrevSect > pLastByte) break;

    }/* for */

    if(pPrevSect->next_section == 0xFFFF)
        fputs("\n\n----Success: Last Record Found---- \n\n", stdout);
        fputs("\n\n----ERROR: Last Record NOT Found---- \n\n", stdout);

    /* the offset defintions labeled unknown should have some kind of
       code to check for any deviation from the usual contents */

    return 0;

void usage(void)
    fputs("readpif infile \n", stdout);
    fputs("readpif -?, for help\n", stdout);

void display_NT(BLOCKNT *pPIF)
    char *ptr = (char *) pPIF;
    ptr += 12; /* padding ?? */
    fputs(ptr, stdout);
    fputs("\n", stdout);
    ptr += 64; /* start of next string */
    fputs(ptr, stdout);
    fputs("\n", stdout);

void display_386(DATA386 * pPIF)
mem_req   = %d dec.\n\
mem_limit = %d dec.\n\
for_pri   = %u\n\
back_pri  = %u\n\
ems_min   = %u\n\
ems_max   = %u\n\
xms_min   = %u\n\
xms_max   = %u\n",
    pPIF->mem_req, pPIF->mem_limit, pPIF->for_pri,
    pPIF->back_pri, pPIF->ems_min, pPIF->ems_max,
    pPIF->xms_min, pPIF->xms_max);

        printf("flags_386 = 0x%x  ,", pPIF->flags_386);

    PIF_String(flags_386.AllowCloseAct, "Allow Close while Active");
    PIF_String(flags_386.ExclusiveON,"Exclusive ON");
    PIF_String(flags_386.BackgroundON,"Background ON");
    PIF_String(flags_386.FullScreenYes,"Full Screen YES");
    PIF_String(flags_386.RSV_ALTESC,"RSV_ALT ESC");
    PIF_String(flags_386.RSV_ALTTAB,"RSV_ALT TAB");
    PIF_String(flags_386.RSV_ALTSPACE,"RSV_ALT SPACE");
    PIF_String(flags_386.RSV_ALTENTER,"Reserve ALT-ENTER");
    PIF_String(flags_386.RSV_ALTPRTSCR,"Reserve ALT-PRT-SCR");
    PIF_String(flags_386.RSV_PRTSCR,"Reserve PRT-SCR");
    PIF_String(flags_386.RSV_CTRLESC,"Reserve CTRL-ESC");
    PIF_String(flags_386.Detect_Idle, "Detect Idle");
    PIF_String(flags_386.EMS_Locked,"EMS Locked");

    if(pPIF->flags_386.UseHMA) ;
    else fputs("Use HMA\n", stdout);

        printf("flagsXMS = 0x%x  ", pPIF->flags_XMS);

        printf("video     = 0x%x, ", pPIF->video);

    if(pPIF->video.MonitorText) ;
    else fputs("Monitor Text\n", stdout);
    if(pPIF->video.MonitorMGr) ;
    else fputs("Monitor Med Gr\n", stdout);
    if(pPIF->video.MonitorHiGr) ;
    else fputs("Monitor Hi Gr\n", stdout);

    PIF_String(video.InitModeText,"Init Vid. Mode Text");
    PIF_String(video.InitModeHiGr,"Init Vid. Mode Gr");
    PIF_String(video.InitModeHiGr,"Init Vid. Mode Hi Gr");

    /* this test for a hot key defined may not be correct     */
    /* it may be that the test should be on hot_key_flag     */
        printf("hot key flag = 0x%x \n", pPIF->hot_key_flag);

    if(pPIF->hot_key_scan == 0)
        fputs("No Hot Key Defined\n",stdout);
    else {
            printf("hot_key_state = 0x%x\n", pPIF->hot_key_state);

        PIF_String(hot_key_state.HOT_KEYALT,"HOT-KEY ALT");
        PIF_String(hot_key_state.HOT_KEYCTRL,"HOT-KEY CTRL");
        PIF_String(hot_key_state.HOT_KEYSHIFT,"HOT-KEY SHIFT");

        printf(" - scan code = 0x%x hex\n", pPIF->hot_key_scan);

        printf(" zero1 = %x\n", pPIF->zero1);
        printf(" zero2 = %x %x %x %x %x\n", pPIF->zero2[0],
            (pPIF->zero2[1]), (pPIF->zero2[2]),
            (pPIF->zero2[3]), (pPIF->zero2[4]));
    }/* vflag */

    printf("386 optional parameters = %.64s\n",pPIF->opt_params );

void display_comment(char *p)
    fputs(p, stdout);
    fputs("\n", stdout);

void display_286(DATA286 * pPIF)
        printf("flags_286 = 0x%x  - ", pPIF->flags_286);

    PIF_String(flags_286.AltTab286,"286 ALT TAB");
    PIF_String(flags_286.AltEsc286,"286 ALT ESC");
    PIF_String(flags_286.AltPrtScr286,"286 ALT PRT SCR");
    PIF_String(flags_286.PrtScr, "286 PRT SCR");
    PIF_String(flags_286.CtrlEsc286,"286 CTRL ESC");
    PIF_String(flags_286.SaveScreen,"Save Screen");

    printf("286 xms limits=%d req=%d\n",pPIF->xmsLimit286, pPIF->xmsReq286);
    printf("com_ports = 0x%x  - \n", pPIF->com_ports);

    PIF_String(com_ports.Com3,"COM 3");
    PIF_String(com_ports.Com4,"COM 4");
    fputs("\n", stdout);

void help(void)
    fputs("readpif -v  -l -n -2 -3 -c filename(.pif)\n", stdout);
    fputs("where -v = verbose\n", stdout);
    fputs("where -l = list linked records\n", stdout);
    fputs("where -n = print comment records\n", stdout);
    fputs("where -2 = print 286 records\n", stdout);
    fputs("where -3 = print 386 records\n", stdout);
    fputs("where -c = print comment records\n", stdout);

#include <ctype.h>
void argcheck(int argc, char **argv)
    register char *p;
    register int c, i;
    int gotpattern;

    if (argc <= 1)
        fputs("No arguments\n", stdout);
    if (argc == 2 && argv[1][0] == '?' && argv[1][1] == 0) {
    nfile = argc-1;
    gotpattern = 0;
    for (i=1; i < argc; ++i) {
        p = argv[i];
        if (*p == '-') {
            while (c = *p++) {
                switch(tolower(c)) {

                case '?': help();   break;
                case 'l': ++lflag;  break;
                case '2': ++flag2;  break;
                case '3': ++flag3;  break;
                case 'c': ++cflag;  break;
                case 'n': ++nflag;  break;

                case 'v': ++vflag;  ++lflag;    ++flag2;
                          ++flag3;  ++cflag;    ++nflag;  break;

                    fputs("Unknown flag\n", stdout);
            argv[i] = 0;
        else if (!gotpattern) {
            ifilename = p;
            argv[i] = 0;

Copyright © 1993, Dr. Dobb's Journal

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.