/*
 * IBM/Microsoft Confidential
 *
 * Copyright (c) IBM Corporation 1987, 1991
 * Copyright (c) Microsoft Corporation 1987, 1991
 *
 * All Rights Reserved
 */

/*
 * loader.c - routines to load and fixup OS2KRNL.
 */

#include <os2def16.h>
#include <sysinvar.h>
#include <newexe.h>

// definitions needed for EXE386.H
typedef unsigned long DWORD;
typedef unsigned short WORD;
#define FOR_EXEHDR      1
#include <exe386.h>

#include <dos.h>
#include <dbprt.h>
#include <osconst.h>
#include <ostypes.h>
#include <ordinals.h>
#include <pgexport.h>
#include <vmowner.h>
#include <filetab.h>

#define FOR_MVDMEXP     1
#include <mvdmexp.h>

#include <mi.h>
#include <eisa.h>
#include <doshlp.h>
#include "loader.h"
#include <ndecomp.h>


/*
 *  Object flag overloading
 *
 *  The overloading of the following bits is a private communication
 *  mechanism between dosinc/dosseg.inc and bios/loader.c.  The overloading
 *  isn't and never will be part of the EXE format, and is thus not
 *  defined in EXE386.H.
 *
 *  The PRELOAD bit is overloaded to indicate that the GDT selector allocated
 *  for the object must match the paragraph address of the object.
 *
 *  The IOPL bit is overloaded to indicate that flat fixups cannot be allowed
 *  to this object (as it moves in virtual memory after fixups are made).
 *
 *  The DISCARD bit is overloaded to indicate that the memory object should
 *  reside in high memory. The FIXED bit means load into low memory.
 */

#define OBJTILE    OBJPRELOAD   /* overload PRELOAD bit with TILE */
#define OBJNOFLAT  OBJIOPL      /* No FLAT references allowed */
#define OBJHIMEM   OBJDISCARD   /* store in high memory */
#define OBJALIGN   OBJCONFORM   /* page align end of object */

#define OBJMAX  0xF


struct objinfo_s
{
    unsigned short  oi_sel;         /* final selector address */
    unsigned long   oi_vsize;       /* unrounded virtual memory size */
    unsigned long   oi_fladdr;      /* fixup linear address */
    struct o32_obj  oi_o32;         /* exact copy of object table */
    unsigned long   oi_oldbase;     /* old base address */
};

#define oi_size         oi_o32.o32_size
#define oi_base         oi_o32.o32_base
#define oi_flags        oi_o32.o32_flags
#define oi_pagemap      oi_o32.o32_pagemap
#define oi_mapsize      oi_o32.o32_mapsize
#define oi_reserved     oi_o32.o32_reserved

#define oi_paddr        oi_reserved     /* overlay reserved field */


#ifdef PMDEBUG
extern unsigned StackFence;   // Check for stack overflow. Note also used and
                              // defined in main.asm.
#endif

extern int (far *OS2Init)(void);
extern unsigned LowMem;
extern unsigned HighMem;
extern unsigned long ExtendMem;
extern int SysInitSeg;
extern struct filetable_s filetable;
extern unsigned char BootFlag;
extern unsigned char OS2KRNLname[];
extern unsigned char OS2KRNLIname[];
extern unsigned char OS2KRNLnul;
extern unsigned char SYSDUMPname[];    // dumpdump
extern unsigned short config_flags;
extern unsigned short doshlp_status;
extern unsigned short IODelayCnt;

PHYSMEMBLOCK PhysMemArray[PHYSMEMARRAYLIMIT];
extern unsigned PhysMemArrayQty;        // number of entries in PhysMemArray
extern unsigned long WhichCPUUsable;    // which CPU's can we control/run on
extern unsigned long WhichCPUAreWe;     // which CPU is this code running on
extern unsigned long WhichCPUXlateMem;  // which CPU does address translation

extern unsigned short movemsgs(char far *, char far *);
extern void far * fpResidentData(void *);
extern int fcopy(char far *, char far *, unsigned);
extern int bcopy(char far *, char far *, unsigned);
extern int MoveHigh(long, long, unsigned);

extern void fatal(void);
extern void BootErrFSD(void);
extern void BootErrMem(void);
extern void BootErrKernel(void);
extern void resetsegs(int);
extern void clearobj(void far *, unsigned);
extern void dprintf(char *,...);
extern void memsetf(void far *, int, int);

unsigned NameCmp( char far *,
                  char far *,
                  unsigned );

unsigned long GetExeInfo( unsigned long endlopaddr,
                          unsigned long *pendhipaddr,
                          unsigned long hipaddr );

void LoadObj( int obj );

void MoveObj( struct objinfo_s *poi,
              unsigned olp,
              unsigned cpp,
              unsigned long loadpaddr );

void ReadPages( struct o32_map far *OPTEFirst,
                unsigned short PageLimit,
                unsigned long  PagePAddr );

void UnpackPage( char far * pPage );

void FixPage( unsigned long far *fpte,
              struct o32_map far *opte,
              unsigned long loadpaddr,
              struct objinfo_s *poi );

void FixPageWorker( unsigned long fixaddr,
                    unsigned long size,
                    unsigned long srcpaddr,
                    struct objinfo_s *poi );

char *oftos( unsigned long );

// Global data used to load and relocate the kernel.
unsigned short R0DS;
unsigned short R0CS;

// start *AFTER* DosHlp code and data selectors
unsigned short selbase = ftoseg(DOSHLP_PADDR >> PAGESHIFT) + SELINCR * 3;

// dumpdump
#if 1
// support for system dump
unsigned short DumpSeg = 0;           // No selector yet assigned.
void near cdecl SaveDumpSeg( void );
#endif

unsigned short xBDALen = 0;      // required for new BIOS arena record entry //160195
extern unsigned short BIOSSeg;   // will be set for main.asm                 //160195
unsigned long laddrnext;
unsigned long paddrkernel;
unsigned long paddrZeroBuf;             /* zero fill buffer */
unsigned long ldrframe;

struct exe_hdr exehdr;
struct e32_exe e32exe;
unsigned long cbkernel;
struct arena_s *ArenaInfo;
struct objinfo_s objinfo[OBJMAX+1];
#ifdef PMDEBUG
unsigned long e32frectab;
#endif
unsigned PageShift = PAGESHIFT;    // PageShift support for /align: switch

// Define a working structure for ReadPages
struct PageDets_s
{
    unsigned long TargPage;  // Where we want it in memory
    unsigned long ActMem;    // Where it actually is in memory
};

#define MAXBLOCKEDREAD 8
struct PageDets_s PageDets[MAXBLOCKEDREAD];



/***    setldrframe - Sets the ldrframe needed by the loader
 *
 * FUNCTION:
 *
 *      Set the ldrframe needed by the loader after determining if there
 *      is memory > 16M (EISA machines).  Also sets the real mode segment
 *      for the os2ldr image.
 *
 * INPUT:
 *          ldrlen - length of loader image
 *
 * NOTE:
 *          This function is called during the boot process when the
 *          OS2LDR has not been relocated.  All accesses to data can
 *          be made without concern of where they are located with
 *          respect to their discardable or resident status.
 */

void setldrframe( unsigned short ldrlen )
{
    unsigned i;
    DBPRT(17,("SLFrm len %x\n",ldrlen));

    DBCHECK();

    i = BYTESTOPAGE( ldrlen + ARENASIZE * sizeof(struct arena_s) );

    if ( i > (unsigned)(_64K>>PAGESHIFT) )
    {
        ERRPRT(("AI 2 big"));
        BootErrMem();
    }

    ldrframe = (LowMem >> (PAGESHIFT - KSHIFT)) - i;

    SysInitSeg = ftoseg(ldrframe);

}



/***LP  loader - Load DOS/Initialize DOS & relocate mFSD
 *
 * FUNCTION:    OS2LDR relocates itself to the top of low memory.
 *
 *      Open and read OS2KRNL.
 *
 *      After the required fixups are applied to OS2KRNL, control is
 *      transferred to OS2KRNL.
 *
 *      When OS2LDR receives control for a FAT boot system, OS2LDR expects
 *      the memory map to be as as follows:
 *
 *              +-------------+ Top of High Memory
 *              |   unused    |
 *              +-------------+ 1Meg
 *              | ROM/screen  |
 *              +-------------+ Top of Low Memory
 *              |   unused    |
 *              +-------------+
 *              |   OS2LDR    |
 *              +-------------+ 20000h (128k)
 *              |  Root Dir.  |
 *              +-------------+ 10000h (64k)
 *              |  reserved   |
 *              +-------------+  0
 *
 *      When OS2LDR receives control for a non-FAT boot system, OS2LDR expects
 *      the memory map to be as as follows:
 *
 *              +-------------+ Top of High Memory
 *              |   unused    |
 *              +-------------+ 100000h (1Meg)
 *              | ROM/screen  |
 *              +-------------+ Top of Low Memory
 *              | 64k unused  |
 *              +-------------+ Top of Low Memory - 64k
 *              |  microFSD   |
 *              +-------------+
 *              |   unused    |
 *              +-------------+
 *              |   OS2LDR    |
 *              +-------------+ 10000h (64k)
 *              |  mFSD.FSD / |
 *              |  RIPL data  |
 *              +-------------+   800h (2k)
 *              |  reserved   |
 *              +-------------+  0
 *
 *      OS2LDR will relocate remote IPL data to the top of high memory
 *      (if RIPL required) and the mFSD to the top of available high
 *      memory (just below the remote IPL data if RIPL required), apply
 *      the required fixups to OS2KRNL, and transfer the control to OS2KRNL.
 *      The memory map will be as follows:
 *
 *              +-------------+ Top of High Memory
 *              |  RIPL data  | (non-FAT boot only)
 *              +-------------+
 *              |  mFSD.FSD   | (non-FAT boot only)
 *              +-------------+
 *              |    free     |
 *              +-------------+
 *              | OS2KRNL(hi) |
 *              +-------------+
 *              |    free     |
 *              +-------------+ 1Meg
 *              | ROM/screen  |
 *              +-------------+ Top of Low Memory
 *              |   OS2LDR    |
 *              +-------------+
 *              |    free     |
 *              +-------------+
 *              | OS2KRNL(lo) |
 *              +-------------+
 *              |  DosHlpSeg  |
 *              +-------------+ 1000h
 *              |  reserved   |
 *              +-------------+ 0
 *
 *      NOTES:  OS2LDR runs in real mode.
 *              The sum of the mFSD and RIPL data sizes must be less than 62k.
 *
 *              This function is called during the boot process when the
 *              OS2LDR has been relocated.  All accesses to discardable
 *              data can be made directly.  If access to resident data
 *              is descired, fpResidentData() should be used.
 *
 * ENTRY POINT: loader
 *    LINKAGE:  call (near) from main
 *
 * INPUT:
 *          LowMem   = Real mode memory (0-1M) size in 1k blocks
 *          HighMem  = Protected mode memory (1-16M) size in 1k blocks
 *          ExtendMem  = Protected mode memory (16M-4G) size in 1k blocks
 *          BootFlag = boot flags:
 *                          0 indicates FAT-based boot
 *                          BF_NOMFSHVOLIO 01h (bit 0)
 *                            set if mini-FSD does not use mFSH_DOVOLIO
 *                          BF_RIPL        02h (bit 1)
 *                            set if boot volume is not local (RIPL boot)
 *                          BF_MINIFSD     04h (bit 2)
 *                            set if mini-FSD is present
 *                            (required for non-FAT boot)
 *                          BF_MICROFSD    08h (bit 3)
 *                            set if micro-FSD is present
 *                            (new for cruiser - required for non-FAT boot)
 *                          BF_RESERVED    f0h (bits 4-7) must be 0
 *         filetable = address of file table for non-FAT boot system
 *                     defined as follows (see filetab.h):
 *                  WORD = number of entries in this table (3 or 4)
 *                  WORD = paragraph # where OS2LDR was loaded
 *                  DWORD= length of OS2LDR file image (in bytes)
 *                  WORD = paragraph # where micro-FSD was loaded
 *                  DWORD= length of micro-FSD file image (in bytes)
 *                  WORD = paragraph # where mini-FSD was loaded
 *                  DWORD= length of mini-FSD file image (in bytes)
 *                  WORD = paragraph # where RIPL data was loaded
 *                  DWORD= length of RIPL data file image (in bytes)
 *                  DWORD= 16:16 pointer to micro-FSD open routine
 *                  DWORD= 16:16 pointer to micro-FSD read routine
 *                  DWORD= 16:16 pointer to micro-FSD close routine
 *                  DWORD= 16:16 pointer to micro-FSD terminate routine
 *
 * EXIT-NORMAL:
 *               The arena information array is as follows:
 *  +-------------------------------------------------------------------+
 *  | unused | DosHlp |  kernel  |  kernel  | ... |  kernel  | Low Free |
 *  | block  |        | lo obj 1 | lo obj 2 |     | lo obj n |  memory  |
 *  +-------------------------------------------------------------------+
 *
 *  +---------------------------------------------------------------------+
 *  | OS2LDR | Screen, |  kernel  | ... |  kernel  | High Free | mini-FSD |
 *  |        | ROMs,.. | hi obj 1 |     | hi obj m |  memory   |          |
 *  +---------------------------------------------------------------------+
 *
 *  +------------------------------------+
 *  | RIPL |   16M-4G Free    |   end    |
 *  | data | memory (if any)  | sentinel |
 *  +------------------------------------+
 *
 * EXIT-ERROR:  None
 *
 * EFFECTS:     The objects within OS2KRNL are loaded into memory
 *
 * MESSAGES:
 *              The system does not have enough storage to
 *                  start IBM Operating System/2.
 *              The file OS2KRNL is not acceptable.
 *              The data to ipl the system is not acceptable.
 *
 * INTERNAL REFERENCES:
 *    ROUTINES:
 *
 * EXTERNAL REFERENCES:
 *    ROUTINES: kernel entry point - Kernel Initialization
 *
 ********************** START OF PSEUDO CODE *********************
 *
 * if there is not enough low or high memory to load OS2KRNL
 *    call BootErrMem to display message and stop system
 * endif
 *
 * if the boot flags are not acceptable
 *    call BootErrFSD to display message and stop system
 * endif
 * if mini-FSD exists
 *    if the data in file table is not acceptable
 *       call BootErrFSD to display message and stop system
 *    endif
 *    if remote IPL exists
 *       call MoveHigh to move RIPL data to the top of high memory
 *       call MoveHigh to move mini-FSD next to RIPL data
 *    else
 *       call MoveHigh to move mini-FSD to the top of high memory
 *    endif
 * endif
 * Move OS2LDR to the top of low memory
 * move DosHlp to low memory
 * if mini-FSD exists (non-FAT-boot system)
 *    nul-terminate OS2KRNL name and open the file
 * else
 *    initialize FAT interface to get kernel size & read function pointer
 * endif
 * call muRead to read OS2KRNL EXE header to highest low memory available
 * call GetExeInfo to initialize OS2KRNL global data structure
 * setup the first arena info entry for physical page 0
 * setup the arena info entry for DosHlp seg
 * set OS2Init to point to the kernel entry point
 * for each kernel object
 *     if a HIGH memory object
 *        call LoadObj to load the object
 *     else
 *        setup the arena info entry for the object
 *     endif
 * endfor
 * setup the arena info entry for free low memory
 * setup the arena info entry for OS2LDR
 * setup the arena info entry for the screen, rom, ... etc.
 * for each kernel object
 *     if a LOW memory object
 *        call LoadObj to load the object
 *     else
 *        setup the arena info entry for the object
 *     endif
 * endfor
 *
 * setup the arena info entry for the free high memory
 * if mini-FSD exists
 *    setup the arena info entry for mini-FSD
 * endif
 * if remote IPL exists
 *    setup the arena info entry for remote IPL data
 * endif
 * if memory from 16Meg to 4Gig was detected
 *    setup the arena info entry for the free extended high memory
 * endif
 * setup the end sentinel arena info entry
 * for each arena info entry
 *     if size is zero
 *        set size to the difference between the current and next entries'
 *        physical addresses
 *     endif
 * endfor
 */

void loader( unsigned short doshlplen,    /* length of doshlp segment */
             unsigned short ldrseg,
             unsigned short ldrlen )
{
    struct BASEMEMBLOCK
    {
        unsigned long BMAStart;   // flat start address of memory block
        unsigned long BMALen;     // length of memory block in bytes
    };

    struct BASEMEMBLOCK BaseMemArray[PHYSMEMARRAYLIMIT];

    register unsigned i;
    register struct arena_s *pa;
    unsigned short j;
    unsigned short msglen;
    unsigned short prevlen;
    unsigned long mfsdframe, riplframe;
    unsigned long endlopaddr, endhipaddr;
    unsigned long hipages, expages;
    unsigned short BMemCt;
    unsigned long KrnlLoadAddr;          // start addr of block for krnl obj's
    short LastSpeed, ThisSpeed;          // temp var used to find fastest block
    unsigned long SzBytes;               // temp var used to check block size
    unsigned short HiLoadBlock;          // hi kernel load block number
    unsigned short LastLoBlock;          // number of the last block loaded low

    // dumpdump
#if 1
    // support for system dump
    unsigned long DumpLen = 0x0000f000; // Assume dump code will be less than
                                        // 15 pages - initial alloc will be
                                        // for 15 pages, then we will shrink.
    void (far *pDump)(void) = 0L;       // 'Convention' with dump code :
                                        // initsystemdump - far call to seg:0
                                        // system dump    - far call to seg:3
                                        // getdhsysdmp    - far call to seg:6
    unsigned int DumpIdx;               // Index to dump entry in ArenaInfo
#endif

    DBCHECK();

    // make primitive copy of PhysMemArray for use in this block of code only
    //  -- contains entries for blocks of memory that only this particular
    //     copy of this operating system may use
    //  -- contains only contiguous blocks of memory with no regard to as to
    //     what is indicated by the flags in PhysMemArray

    for ( i = BMemCt = (unsigned short)( BaseMemArray[0].BMAStart
                                                = BaseMemArray[0].BMALen = 0L );
          i < PhysMemArrayQty;
          i++ )
    {
        // Are the blocks contiguous ?
        // Note: this also triggers on the first entry.
        if ( (PhysMemArray[i].BlockStartAddr ==   BaseMemArray[BMemCt].BMAStart
                                                + BaseMemArray[BMemCt].BMALen) )
        {
            BaseMemArray[BMemCt].BMALen += PhysMemArray[i].BlockByteLength;
        }
        else
        {
            BMemCt++;
            BaseMemArray[BMemCt].BMAStart = PhysMemArray[i].BlockStartAddr;
            BaseMemArray[BMemCt].BMALen = PhysMemArray[i].BlockByteLength;
        }
    }

#if PMDEBUG
    DBPRT(17,("BaseMem1:\n"));
    for ( i=0; i<=BMemCt; i++ )
    {
        DBPRT(17,("Base %lx sz %lx\n",
                   BaseMemArray[i].BMAStart,
                   BaseMemArray[i].BMALen ) );
    }
#endif

    for ( i = (short)KrnlLoadAddr = 0, LastSpeed = -1;
          i < PhysMemArrayQty;
          i++ )
    {
        // To use block for kernel, start address must be between 1 and 16 meg
        if (    (PhysMemArray[i].BlockStartAddr >= _1MEGB)
             && (PhysMemArray[i].BlockStartAddr < _16MEGB) )
        {
            // How big is this block ?
            SzBytes = ( (PhysMemArray[i].BlockByteLength +
                                   PhysMemArray[i].BlockStartAddr) >= _16MEGB )
                      ? _16MEGB - PhysMemArray[i].BlockStartAddr
                      : PhysMemArray[i].BlockByteLength;

            // Is this block big enough for the kernel ?
            if ( SzBytes >= _1MEGB )
            {
                // Work out speed rating - look for fastest block to put
                // the kernel in.

                ThisSpeed=0;

                // Assume memory local to the processor is fast
                if (PhysMemArray[i].WhichCPULocal & WhichCPUAreWe)
                {
                    ThisSpeed++;
                }

                // Assume cached memory is fast
                if (PhysMemArray[i].WhichCPUCached & WhichCPUAreWe)
                {
                    ThisSpeed++;
                }

                // Remember if this is the fastest suitable found so far
                if (ThisSpeed > LastSpeed)
                {
                    KrnlLoadAddr = PhysMemArray[i].BlockStartAddr;
                    LastSpeed = ThisSpeed;
                }
            }
        }
    }

    // Could not find a memory block to load the kernel into - abort.
    if (!KrnlLoadAddr)
    {
        BootErrMem();
    }

    DBPRT(17,("HiLoad=%lx\n",KrnlLoadAddr));
    DBPRT(17,("ldrseg=%x ldrlen=%x &filetbl=%x bootfl=%x\n",
               ldrseg, ldrlen, &filetable, BootFlag));

    hipages = HighMem >> (PAGESHIFT - KSHIFT);
    expages = ExtendMem >> (PAGESHIFT - KSHIFT);

    DBCHECK();

    DBPRT(17,("Low/High/Ex Mem = %lu/%lu/%lu K\n",
                (long) LowMem,
                (long) HighMem,
                (long) ExtendMem ));

    DBCHECK();

    // Open the OS2KRNL{I} file.
    // Note that the call to muOpen for the kernel MUST be
    // done before the MoveHigh of the RIPL code. The RIPL code
    // is dependant on this ordering.
    DBPRT(17,("open\n"));
    if ( filetable.ft_muOpen( (char far *)OS2KRNLname,
                              (unsigned long far *) &cbkernel ) )
    {
        DBPRT(17,("openI\n"));
        if ( filetable.ft_muOpen( (char far *) OS2KRNLIname,
                                  (unsigned long far *) &cbkernel) )
        {
            BootErrKernel();
        }
    }
    endlopaddr = segtop(filetable.ft_museg);
    DBPRT(17,("opened\n"));

    DBCHECK();

    // Find kernel load block
    HiLoadBlock = 0;
    while ( ( BaseMemArray[HiLoadBlock].BMAStart +
              BaseMemArray[HiLoadBlock].BMALen ) < KrnlLoadAddr )
        HiLoadBlock++;

    // Split the block if necessary for kernel load
    if ( BaseMemArray[HiLoadBlock].BMAStart != KrnlLoadAddr )
    {
        for ( j=BMemCt; j==HiLoadBlock; j-- )
        {
            BaseMemArray[j+1].BMAStart=BaseMemArray[j].BMAStart;
            BaseMemArray[j+1].BMALen=BaseMemArray[j].BMALen;
        }

        HiLoadBlock++;

        BaseMemArray[HiLoadBlock].BMAStart = KrnlLoadAddr;
        BaseMemArray[HiLoadBlock].BMALen = ( BaseMemArray[HiLoadBlock-1].BMAStart
                                             + BaseMemArray[HiLoadBlock-1].BMALen
                                           ) - KrnlLoadAddr;
        BaseMemArray[HiLoadBlock-1].BMALen = KrnlLoadAddr
                                             - BaseMemArray[HiLoadBlock-1].BMAStart;
        BMemCt++;
    }
    DBPRT(17,("KLBlock %x\n",HiLoadBlock));

    // Find highest block starting below 16 meg line
    for ( i = 0; ; )
    {
        if ( BaseMemArray[i].BMAStart >= _16MEGB )
        {
            i--;
            break;
        }
        if ( i == BMemCt )
           break;
        i++;
    }

    // WARNING !! - PRESERVE i ... used below ...

    DBPRT(17,("split\n"));
    // If the block straddles the 16 meg line, then split it
    if ( ( BaseMemArray[i].BMAStart + BaseMemArray[i].BMALen ) > _16MEGB )
    {
        for ( j=BMemCt; j==i; j-- )
        {
            BaseMemArray[j+1].BMAStart=BaseMemArray[j].BMAStart;
            BaseMemArray[j+1].BMALen=BaseMemArray[j].BMALen;
        }
        BaseMemArray[i+1].BMAStart = _16MEGB;
        BaseMemArray[i+1].BMALen = ( BaseMemArray[i].BMAStart
                                     + BaseMemArray[i].BMALen ) - _16MEGB;
        BaseMemArray[i].BMALen = _16MEGB - BaseMemArray[i].BMAStart;
        BMemCt++;
    }

    // WARNING !! - PRESERVE i ... used below ...

#ifdef PMDEBUG
    DBPRT(17,("BaseMem2:\n"));
    for ( j=0; j<=BMemCt; j++ )
    {
        DBPRT(17,("Base = %08lx Sz = %08lx\n",
                   BaseMemArray[j].BMAStart,
                   BaseMemArray[j].BMALen) );
    }
#endif

    if (BootFlag)       /* non-FAT boot system */
    {
        if ((BootFlag & (BF_RESERVED | BF_MICROFSD | BF_MINIFSD)) !=
                                                   (BF_MICROFSD | BF_MINIFSD))
        {
            ERRPRT(("\nb flgs"));
            BootErrFSD();
        }

        /*
         *  if the data in file table is not acceptable
         *      call BootErrFSD to display message and stop system
         */

        if (    filetable.ft_loaderlen == 0
             || filetable.ft_loaderseg != ptoseg(_64K)
             || filetable.ft_mulen == 0
             || filetable.ft_mulen+(unsigned long)segtop(filetable.ft_museg)
                                                     > (ldrframe << PAGESHIFT)
             || filetable.ft_museg < ftoseg(ldrframe) - ptoseg(_64K)
             || filetable.ft_mfsdlen == 0
             || filetable.ft_mfsdlen > _64K - MFSDBASE   /* room for MFSD? */
             || filetable.ft_mfsdseg < ptoseg(MFSDBASE)  /*less than MFSDBASE*/
             || segtop(filetable.ft_mfsdseg) + filetable.ft_mfsdlen > _64K
             ||    ( (BootFlag & BF_RIPL) == 0
                &&    filetable.ft_cfiles != 3 )
             ||    ( (BootFlag & BF_RIPL)
                && (
                         filetable.ft_cfiles != 4
                      || filetable.ft_mfsdlen + filetable.ft_ripllen > _64K - MFSDBASE
                                                      /* room for MFSD and RIPL? */
                      || filetable.ft_ripllen == 0
                      || filetable.ft_riplseg < ptoseg(RIPLBASE)    /* < RIPLBASE? */
                      || filetable.ft_riplseg >= ptoseg(_64K)       /* greater than 64k */
                      || segtop(filetable.ft_riplseg) + filetable.ft_ripllen > _64K
                   ) )
           )
        {
            // ERRPRT(("ftable"));
            BootErrFSD();
        }

        // Move the RIPL data to the top end of high memory.
        // Note that the call to muOpen for the kernel MUST MUST MUST be
        // done before the MoveHigh of the RIPL code. The RIPL code will
        // only work if we keep to this order.

        // WARNING !! - PRESERVE i ... used below ...

        if (filetable.ft_cfiles == 4)           /* RIPL data exists */
        {
            riplframe = BaseMemArray[i].BMAStart + BaseMemArray[i].BMALen
                                            - PAGEROUND(filetable.ft_ripllen);
            DBPRT(17,("ripl bs %lx\n", riplframe));

            MoveHigh( sotop(filetable.ft_riplseg, 0),
                      riplframe,
                      (unsigned) filetable.ft_ripllen );

            /*  mfsd is just below ripl data */

            mfsdframe = riplframe - PAGEROUND(filetable.ft_mfsdlen);

        }
        else          /* Relocate the mini-FSD to the top of high memory */
        {
            DBPRT(17,("MFSDF %x %lx %lx %lx\n",
                       i,
                       BaseMemArray[i].BMAStart,
                       BaseMemArray[i].BMALen,
                       filetable.ft_mfsdlen ));
            mfsdframe = BaseMemArray[i].BMAStart + BaseMemArray[i].BMALen
                                              - PAGEROUND(filetable.ft_mfsdlen);
        }

        DBPRT(17,("mini-FSD %lx\n", mfsdframe));

        MoveHigh( sotop(filetable.ft_mfsdseg, 0),
                  mfsdframe,
                  (unsigned) filetable.ft_mfsdlen );
    }

    DBCHECK();

    //  Move the loader code up to just below the top of low memory.
    DBPRT(17,("ldr @ %6lx => %6lx, len=%x\n",
              segtop(ldrseg),
              ldrframe << PAGESHIFT,
              ldrlen));
    fcopy( sotofar(ldrseg,0), ftofar(ldrframe), ldrlen );
    resetsegs(ftoseg(ldrframe));

    //  In the FAT case the code has been relocated and the addresses to the
    //  FAT functions must be modified
    if (!BootFlag)
    {
        ((struct FarPtr *) &(filetable.ft_muRead))->Segmt = SysInitSeg;
        ((struct FarPtr *) &(filetable.ft_muOpen))->Segmt = SysInitSeg;
        ((struct FarPtr *) &(filetable.ft_muClose))->Segmt = SysInitSeg;
        ((struct FarPtr *) &(filetable.ft_muTerminate))->Segmt = SysInitSeg;
    }

    DBCHECK();

    DEBUG();    /* ok to call debugger; code won't move again */

    // Zero fill the arena array
    ArenaInfo = (struct arena_s *)ldrlen;
    clearobj( sotofar( ftoseg(ldrframe), ArenaInfo ),
              ARENASIZE * sizeof(struct arena_s) );
#ifdef PMDEBUG
    ArenaInfo[ARENASIZE].a_paddr = 0xFECBA987;
#endif

    //  Move doshlps down in memory.
    fcopy( ftofar(ldrframe),
           ptofar(DOSHLP_PADDR),
           doshlplen );
    *((PSHORT)fpResidentData((VOID *)&doshlp_status)) |= DHSTAT_RELOCATED;

    DBPRT(17,("DHSeg=%x DHLen=%x\n", DOSHLP_CODESEL, doshlplen));

    msglen = movemsgs( sotofar( DOSHLP_CODESEL, doshlplen ),
                       sotofar(SysInitSeg,0) );
    DBPRT(17,("MLen=%x\n", msglen));

    prevlen = (doshlplen + 0x0F) & 0xFFF0;
    prevlen += (msglen + 0x0F) & 0xFFF0;

    paddrkernel = DOSHLP_PADDR + PAGEROUND( prevlen );

    //  Initialize the OS2KRNL load image global data structure, and assign
    //  a 32k buffer immediately above the highest low object for zero-padding
    //  objects that are moved to high physical memory.
    endlopaddr = paddrZeroBuf = GetExeInfo( endlopaddr,
                                            &endhipaddr,
                                            KrnlLoadAddr );

    DBPRT(17,("k end paddr lo/hi %lx/%lx, zpaddr %lx, zsize %x\n",
               endlopaddr,
               endhipaddr,
               paddrZeroBuf,
               CBMOVEMAX));

    //  If the object's destination physical address is in high memory,
    //  we cannot directly zero the memory from real mode.  We need to
    //  copy a zero-filled buffer in low memory using the MoveHigh function
    //  to copy the buffer to the destination high memory.  We can only
    //  handle CBMOVEMAX bytes of data at a time.
    clearobj( ptofar(paddrZeroBuf), CBMOVEMAX );

    // Set up page 0's arena info.
    // NOTE:  The arena info array is initialized to contain zeros above.
    pa = ArenaInfo;
    pa->a_oflags = OBJREAD | OBJWRITE;
    pa++;

    // Setup an arena pointer to the DosHlp segment.
    pa->a_paddr = DOSHLP_PADDR;
    pa->a_size = prevlen;
    pa->a_laddr = laddrnext -= PAGEROUND( prevlen );
    pa->a_sel = ptoseg(DOSHLP_PADDR);
    pa->a_aflags = AF_LADDR | AF_DOSHLP;
    pa->a_oflags = OBJEXEC | OBJALIAS16;
    pa->a_owner = DOSHLPOWNER;
    pa++;

    //  Set OS2Init to point to kernel entry point.
    FP_SEG(OS2Init) = ptoseg( objinfo[e32exe.e32_startobj].oi_paddr );
    FP_OFF(OS2Init) = (unsigned short) e32exe.e32_eip;
    DBPRT(2|16,("OS2Init=%lp\n", OS2Init));

    // Verify that the entry point to the kernel is not in high memory and
    // that the starting EIP is < 64K.
    if ( objinfo[e32exe.e32_startobj].oi_flags & OBJHIMEM )
    {
        ERRPRT(("entry hi"));
        fatal();
    }
    if ( (unsigned long)(unsigned)e32exe.e32_eip != e32exe.e32_eip )
    {
        ERRPRT(("eip >= 64k"));
        fatal();
    }

    /*
     *  Perform first of two passes to load the kernel objects.  This is
     *  necessary for two reasons:
     *
     *  (1)  We want to delay loading objects destined for low memory so
     *       that we can use their memory as an intermediate buffer for
     *       loading objects destined for high memory.
     *
     *  (2)  The physical addresses in the arena info array must be in
     *       ascending order.
     *
     *  Pass I - Load High Kernel Objects
     *
     *  Scan the kernel objects.  For each object that will reside in
     *  high physical memory, call LoadObj to load the object into low memory,
     *  perform fixups and move it high.  For each object that will reside
     *  in low memory, set up the arena info for the object.
     */

    for ( i = 1; i <= (unsigned)e32exe.e32_objcnt; i++ )
    {
        DBCHECK();
        if ((pa->a_paddr = objinfo[i].oi_paddr) < _1MEGB )
        {
            //  Object will reside in low memory.  Fill in arena info
            //  and delay loading till later.
            DBPRT(17,("wait %x lo\n", i));

            pa->a_size = objinfo[i].oi_size;
            pa->a_laddr = objinfo[i].oi_base;
            pa->a_sel = objinfo[i].oi_sel;
            pa->a_aflags = AF_LADDR | AF_OS2KRNL | (i << AF_OBJSHIFT);
            pa->a_oflags = objinfo[i].oi_flags;
            pa->a_owner = OS2KRNLOWNER;
            pa++;

            LastLoBlock = i;
        }
        else
        {
            //  Object will reside in high memory.  Load it into the
            //  lowest available low memory, then perform fixups and
            //  move it high.
            DBPRT(17,("load %x hi\n", i));

            LoadObj(i);
        }
    }

    //****************** 160195 BEGIN ******************
    /*
    **------------------------------------------------------------
    ** New arena record entry to save original IVT, BIOS Data Area,
    ** and Extended BIOS Data Area (XBDA)
    **
    ** Note that BIOSSeg is set for main.asm
    **------------------------------------------------------------
    */
    pa->a_paddr = ( (pa-1)->a_paddr + (pa-1)->a_size + 0x0fff ) & 0xfffff000;
    pa->a_sel = BIOSSeg = (unsigned short)( pa->a_paddr >> 4 );
    pa->a_size = 0x500 + xBDALen;
    pa->a_aflags = AF_DOSHLP | AF_LADDR;
    pa->a_laddr = laddrnext -= PAGEROUND(pa->a_size);
    pa->a_owner = DOSHLPOWNER;
    DBPRT(17,("BIOS Seg %x\n", BIOSSeg ));
    pa++;
    //**************** 160195 END **********************


    // dumpdump
#if 1
    // Dump segment entry
    pa->a_paddr = ( (pa-1)->a_paddr + (pa-1)->a_size + 0x0fff ) & 0xfffff000;
    pa->a_sel = DumpSeg = (unsigned short)( pa->a_paddr >> 4 );
    pa->a_size = DumpLen;
    pa->a_aflags = AF_DOSHLP | AF_LADDR;     // A bit of a lie
    pa->a_laddr = laddrnext -= PAGEROUND(DumpLen);
    pa->a_owner = OS2DUMPOWNER;
    DBPRT(17,("Dmp seg %x\n", DumpSeg ));
    DumpIdx = pa - ArenaInfo;
    pa++;
#endif

    // Setup the arena pointer to the free low memory.
    pa->a_paddr = ( (pa-1)->a_paddr + (pa-1)->a_size + 0x0fff ) & 0xfffff000;
    pa->a_aflags = AF_FREE;
    pa++;

    // Setup the arena pointer to OS2LDR.
    pa->a_paddr = ldrframe << PAGESHIFT;
    pa->a_size = ldrlen + ARENASIZE * sizeof(struct arena_s);
    pa->a_laddr = laddrnext -= PAGEROUND(ldrlen + ARENASIZE * sizeof(struct arena_s));
    pa->a_aflags = AF_LADDR | AF_OS2LDR;
    pa->a_oflags = OBJREAD | OBJWRITE | OBJALIAS16;
    pa->a_owner = OS2LDROWNER;
    pa++;

    // Setup the arena info for the screen memory, rom, ... etc.

    DBPRT(17,("rom\n"));
    pa->a_paddr = (unsigned long) LowMem << KSHIFT;
    if (pa->a_paddr & PAGEMASK)
    {
        /*
         *  The amount of low memory is not page granular.  Create one entry
         *  for the free fragment in the last frame and create another entry
         *  mapping a dummy object to the invalid fragment in the last frame.
         *  Boundaries between valid and invalid memory MUST fall on a page
         *  boundary.
         *
         *  Note that the loader does not extend into the last partial frame.
         */

        pa->a_size = pa->a_paddr & PAGEMASK;
        pa->a_paddr &= ~PAGEMASK;
        pa->a_aflags = AF_FREE;
        pa++;

        pa->a_paddr = (pa-1)->a_paddr + (pa-1)->a_size;
        pa->a_size = (PAGESIZE - pa->a_paddr) & PAGEMASK;
        pa->a_aflags = AF_LADDR;
        pa->a_laddr = (laddrnext -= PAGESIZE) + (pa->a_paddr & PAGEMASK);
        pa->a_owner = OS2ROMDATA;
        pa++;

        pa->a_paddr = (pa-1)->a_paddr + (pa-1)->a_size;
    }
    pa->a_aflags = AF_INVALID;
    pa++;

    // find first block above or equal to 1meg
    for ( i = 0;
          BaseMemArray[i].BMAStart < _1MEGB;
          i++ );

    // cycle through array until start of kernal
    // assumes kernel starts below 16meg  (but so does pginit)

    DBPRT(17,("free lo\n"));
    while ( BaseMemArray[i].BMAStart < KrnlLoadAddr )
    {
        pa->a_paddr = BaseMemArray[i].BMAStart;
        pa->a_aflags = AF_FREE;
        pa->a_size = BaseMemArray[i].BMALen;
        pa++;
        if ( ((pa-1)->a_paddr+(pa-1)->a_size) < BaseMemArray[i+1].BMAStart )
        {
            pa->a_paddr = (pa-1)->a_paddr + (pa-1)->a_size;
            pa->a_aflags = AF_INVALID;
            pa->a_size = BaseMemArray[i+1].BMAStart - (pa-1)->a_paddr
                                                             - (pa-1)->a_size;
            pa++;
        }
        i++;
    }

    /*
     *  Pass II - Load Low Kernel Objects
     *
     *  For each kernel object that will reside low, call LoadObj to
     *  load the object directly into its final location.  For each kernel
     *  object that will reside high, setup the arena info for the object.
     */

    for ( i = 1; i <= (unsigned)e32exe.e32_objcnt; i++ )
    {
        DBCHECK();
        if ((pa->a_paddr = objinfo[i].oi_paddr) >= _1MEGB)
        {
            DBPRT(17,("done %x hi\n", i));

            pa->a_size = objinfo[i].oi_size;
            pa->a_laddr = objinfo[i].oi_base;
            pa->a_sel = objinfo[i].oi_sel;
            pa->a_aflags = AF_LADDR | AF_OS2KRNL | (i << AF_OBJSHIFT);
            pa->a_oflags = objinfo[i].oi_flags;
            pa->a_owner = OS2KRNLOWNER;
            pa++;
        }
        else
        {
            DBPRT(17,("load %x lo\n", i));

            LoadObj(i);
        }
    }

    //  Finished loading kernel, close file.
    DBPRT(17,("close\n"));
    filetable.ft_muClose();

    // dumpdump
#if 1

    // Load and initialise the dump code - OS2DUMP in the root.
    if ( filetable.ft_muOpen( (char far *)SYSDUMPname,
                              &DumpLen ) )
    {
        // File not found
        DumpLen = 0L;
        DBPRT(17,("no dmp\n"));
    }
    else
    {
        DBPRT(17,("dmp len %lx xBDA len %x\n",DumpLen,xBDALen));

        SzBytes = filetable.ft_muRead( 0L,
                                       sotofar( DumpSeg, 0 ),
                                       ( DumpLen + 0xfff ) & 0xfffff000 );
#if PMDEBUG
        if ( SzBytes != DumpLen )
        {
            DBPRT(17,("B dmp\n"));
            DumpLen = 0L;
        }
#endif

        filetable.ft_muClose();
    }

    // Free up excess allocation of storage for dump (all for the no dump case)
    // Save dump segment for use later; init system dump at seg:6
    if (DumpLen)
    {
//Defect 96109. Extended Bios Data Area is no longer needed to be saved
//for the standalone dump. It was causing an extra page of memory to be
//allocated unnecessarily
//      ArenaInfo[DumpIdx].a_size = DumpLen + xBDALen;
        ArenaInfo[DumpIdx].a_size = DumpLen;
        ArenaInfo[DumpIdx+1].a_paddr = (   ArenaInfo[DumpIdx].a_paddr
                                         + ArenaInfo[DumpIdx].a_size
                                         + 0x0fff ) & 0xfffff000;

        // Need to force a far call as the doshlps have been moved, but
        // can not use the linker to do the fixups as this is a .com file.
        pDump = (void(far *)(void))
                       sotofar( DOSHLP_CODESEL, (unsigned short)SaveDumpSeg );
        DBPRT(17,( "sds init %lx\n", (unsigned long)pDump ));
        (*pDump)();

        pDump = (void(far *)(void)) sotofar( DumpSeg, 6 );
        DBPRT(17,( "dmp init %lx\n", (unsigned long)pDump ));
        (*pDump)();
    }
    else
    {
        ArenaInfo[DumpIdx].a_aflags = AF_FREE;
    }

#endif

    // terminate MicroFSD
    DBPRT(17,("term\n"));
    filetable.ft_muTerminate();
    DBPRT(17,("termed\n"));


    //  Setup arena info for free high memory remaining in hi load block
    pa->a_paddr = endhipaddr;
    pa->a_size = BaseMemArray[HiLoadBlock].BMAStart +
                                 BaseMemArray[HiLoadBlock].BMALen - pa->a_paddr;
    pa->a_aflags = AF_FREE;
    pa++;

    // free blocks between 1 and 16 meg
    DBPRT(17,("-16\n"));
    for ( i=HiLoadBlock; i <= BMemCt; i++ )
    {
        // found such a block
        if (    (BaseMemArray[i].BMAStart > KrnlLoadAddr)
             && (BaseMemArray[i].BMAStart < _16MEGB) )
        {
            pa->a_paddr = (pa-1)->a_paddr + (pa-1)->a_size;
            if (BaseMemArray[i].BMAStart != pa->a_paddr)
            {
                pa->a_aflags = AF_INVALID;
                pa++;
            }
            pa->a_paddr = BaseMemArray[i].BMAStart;
            pa->a_size = BaseMemArray[i].BMALen;
            pa->a_aflags = AF_FREE;
            pa++;
        }
    }

    DBCHECK();

    //  If mini-FSD exists, setup the arena pointer to mini-FSD

    if (BootFlag)           /* non-FAT boot system */
    {
        DBPRT(17,("nFAT\n"));

        // setup arena pointer to mini-FSD
        pa->a_paddr = mfsdframe;
        pa->a_size = PAGEROUND(filetable.ft_mfsdlen);
        if ((pa-1)->a_size)
        {
            (pa-1)->a_size -= pa->a_size;   // fix previous block's size
        }
        pa->a_laddr = laddrnext -= PAGEROUND(filetable.ft_mfsdlen);
        pa->a_aflags = AF_LADDR | AF_FSD;
        pa->a_oflags = OBJREAD | OBJWRITE | OBJALIAS16;
        pa->a_owner = FSMFSDOWNER;
        pa++;

        //  if remote IPL exists, setup the arena pointer to remote IPL data

        if (BootFlag & BF_RIPL)            /* Remote IPL boot system */
        {
            DBPRT(17,("RIPL\n"));

            /* setup arena pointer to ripl-data */
            pa->a_paddr = riplframe;
            pa->a_size = PAGEROUND(filetable.ft_ripllen);
            if ((pa-2)->a_size)
            {
                (pa-2)->a_size -= pa->a_size;   // fix last free size
            }
            pa->a_laddr = laddrnext -= PAGEROUND(filetable.ft_ripllen);
            pa->a_aflags = AF_LADDR | AF_RIPL;
            pa->a_oflags = OBJREAD | OBJWRITE | OBJALIAS16;
            pa->a_owner = OS2RIPLOWNER;
            pa++;
        }
    }

    // Setup the default physical end address.
    DBPRT(17,("DPEnd\n"));
    pa->a_paddr = (pa-1)->a_paddr + (pa-1)->a_size;
    pa->a_aflags = AF_INVALID;
    pa++;

    DBPRT(17,("+16\n"));

    // memory above 16 MEG from int 15/88 and int 15/c7
    i = 0;
    while (i <= BMemCt)
    {
        // found such a block
        if (BaseMemArray[i].BMAStart >= _16MEG)
        {
            pa->a_paddr = BaseMemArray[i].BMAStart;
            pa->a_size = BaseMemArray[i].BMALen;
            pa->a_aflags = AF_FREE;
            pa++;
            pa->a_paddr = (pa-1)->a_paddr + (pa-1)->a_size;
            pa->a_aflags = AF_INVALID;
            pa++;
        }
        i++;
    }

    // Handle memory above 16M not from int 15. EISA machines.
    if (*((PSHORT)fpResidentData((VOID *)&config_flags)) & CFG_EISA)
    {
        DBPRT(17,("+16E\n"));

        //  Fill in arena entries for memory blocks starting above the
        //  16Meg line on EISA machines.
//110871        pa += MakeEisaArena( pa,
//110871                             (((char *)pa) - ((char *)ArenaInfo)) / ARENASIZE );

        //110871 -> avail arena entries = # of entries -
        //110871                          ( (current entry - starting entry) /
        //110871                             size of entry )
        pa += MakeEisaArena( pa,
                             ARENASIZE - ((((char *)pa) - ((char *)ArenaInfo)) /
                             sizeof(struct arena_s)) );

    }

    DBCHECK();

    // Terminate array ...
    DBPRT(17,("MemT\n"));

    if ((pa-1)->a_aflags == AF_INVALID)
        pa--;

    pa->a_paddr = pa[-1].a_paddr + pa[-1].a_size;
    pa->a_size = 0L;
    pa->a_aflags = AF_PEND;
    pa++;

    //  Setup the virtual end sentinel.
    pa->a_aflags = AF_VEND;

    DBPRT(17,("MemX\n"));

    // For each object in the arena info array for which the size field
    // has not already been filled in, set the size equal to the difference
    // of physical addresses in the object's arena info record and the
    // succeeding arena info record.
    //
    // Also, mark all arena info entries under 640K as fast
    //
    // BUGBUG - should use fast info from physmem array here
    //
    for ( pa = ArenaInfo; (pa->a_aflags & AF_PEND) == 0; pa++ )
    {
        if (pa->a_size == 0)
            pa->a_size = pa[1].a_paddr - pa->a_paddr;
        if ((pa+1)->a_paddr <= 640 * _1KB)
            pa->a_aflags |= AF_FAST;
    }

#ifdef PMDEBUG

    // dump out arenainfo struct
    pa = ArenaInfo;
    while (!(pa->a_aflags & AF_VEND))
    {
        DBPRT(17,("pa=%8lx sz=%8lx va=%8lx sel=%4x fl=%4x of=%8lx ow=%4x\n",
                   pa->a_paddr,
                   pa->a_size,
                   pa->a_laddr,
                   pa->a_sel,
                   pa->a_aflags,
                   pa->a_oflags,
                   pa->a_owner ));
        pa++;
    }

    // ArenaInfo array overflow ?
    if (ArenaInfo[ARENASIZE].a_paddr != 0xFECBA987)
    {
        DBPRT(1+8,("AInf"));
        BootErrMem();
    }

    // Stack overflow ?
    if (StackFence != 0x5EE5)
    {
        DBPRT(1+8,("Stck"));
        BootErrMem();
    }
    else
    {
        DBPRT(1+8,("SFence OK\n"));
    }

#endif

}



/*===========================================================================*/


#ifdef PMDEBUG

struct oflag_s
{
    unsigned long  of_bit;
    char           of_set;
    char           of_clear;
} oflags[] =
{
    { OBJREAD,    'r', '-' },
    { OBJWRITE,   'w', '-' },
    { OBJEXEC,    'x', '-' },
    { OBJBIGDEF,  'B', '-' },
    { OBJSHARED,  's', 'P' },
    { OBJNOFLAT,  'N', 'f' },
    { OBJTILE,    'T', '-' },
    { OBJHIMEM,   'h', 'L' },
    { OBJALIAS16, 'a', '-' },
    { OBJALIGN,   'A', '-' },
    { 0,           0,   0  }
};


char * oftos(f)
unsigned long f;
{
    register struct oflag_s *pof;
    register char *p;
    static char buf[sizeof(oflags)/sizeof(oflags[0])];

    pof = oflags;
    p = buf;
    while (pof->of_bit)
    {
        *p++ = (f & pof->of_bit)? pof->of_set : pof->of_clear;
        pof++;
    }
    *p = '\0';
    return(buf);
}

#if 0

struct aflag_s
{
    unsigned int  af_mask;
    unsigned int  af_value;
    char         *af_name;
} aflags[] =
{
    { AF_INVALID,       AF_INVALID,     "invalid" },
    { AF_FREE,          AF_FREE,        "free"    },
    { AF_LADDR,         AF_LADDR,       "laddr"   },
    { AF_PADDR,         AF_PADDR,       "paddr"   },
    { AF_SRCMASK,       AF_DOSHLP,      "doshlp"  },
    { AF_SRCMASK,       AF_FSD,         "fsd"     },
    { AF_SRCMASK,       AF_RIPL,        "ripl"    },
    { AF_SRCMASK,       AF_OS2KRNL,     "os2krnl" },
    { AF_SRCMASK,       AF_OS2LDR,      "os2ldr"  },
    { AF_MAPPED,        AF_MAPPED,      "mapped"  },
    { AF_FAST,          AF_FAST,        "fast"    },
    { AF_PEND,          AF_PEND,        "pend"    },
    { AF_VEND,          AF_VEND,        "vend"    },
    { 0,                0,              0         }
};

#endif

#endif



/***LP  GetExeInfo - Initialize global data structures describing a load image.
 *
 *      This function is called by the loader routine after opening the
 *      OS2KRNL[D|I] file.  GetExeInfo() reads in and validates the EXE header
 *      information, and copies the main header and object table into near
 *      memory (DGROUP), moves the fixup record table and fixup page table to
 *      the high end of available low memory, assigns the final physical and
 *      virtual addresses for each object and initializes a global data
 *      structure (objinfo) with this information.  Pointers to the ends of
 *      the kernel low and high memory objects are returned directly via
 *      supplied pointer parameters.
 *
 *      ENTRY   endlopaddr - input: *pendlopaddr holds the paddr of the high
 *                                   end of available low memory.
 *              pendhipaddr - input: nothing
 *                            output: *pendhipaddr holds the paddr of the end
 *                                    of the kernel high memory objects.
 *      EXIT    *pendhipaddr filled in.
 *              returns new endlopaddr.
 */

unsigned long GetExeInfo( unsigned long endlopaddr,   /* input low memory end addr */
                          unsigned long *pendhipaddr, /* final high memory end frame */
                          unsigned long hipaddr )
{
    register struct objinfo_s *poi;
    unsigned short us;
    int falign;
    unsigned long paddrSrc, paddrDest;
    unsigned long paddr, pgoffst, lopaddr, size;
    unsigned long paddrheader;
    unsigned long ltmp, ltmp2;
    unsigned long laddrdelta;           /* Constant diff virtual - physical */
    unsigned long instladdr;            /* MVDM res instance data laddr */
    unsigned long sinstladdr;           /* MVDM swap instance data laddr */
    char far *p;                        /* pointer into loaded image */

    DBPRT(16,("krnl sz=%lx hdrpaddr=%lx hdrlen=%lx\n",
             cbkernel,
             paddrkernel,
             CBKERNELHEADERMIN ));

    p = ptofar(paddrkernel);            /* point to header location */
    ltmp = filetable.ft_muRead( 0L, p, (long) CBKERNELHEADERMIN );

    if (ltmp != CBKERNELHEADERMIN)
    {
        ERRPRT(("b hdr"));
        fatal();
    }
    DBCHECK();

    //  Copy old exe header into local work space.
    fcopy( p, (char far *)&exehdr, sizeof(exehdr) );
    if (exehdr.e_magic != EMAGIC)
    {
        ERRPRT(("not MZ"));
        fatal();
    }

    //  Copy new exe header into near data (DGROUP).
    p += (unsigned) exehdr.e_lfanew;        /* loadpaddr+0x40, new EXE header*/
    paddrheader = paddrkernel + exehdr.e_lfanew;
    fcopy( p, (char far *)&e32exe, sizeof(e32exe) );

    //  Verify magic constants in OS2KRNLD EXE header.
    if ( e32exe.e32_magic[0] != E32MAGIC1 || e32exe.e32_magic[1] != E32MAGIC2 )
    {
        ERRPRT(("not LX"));
        fatal();
    }

    // Set up the global page shift now we have the exe header in memory.
    PageShift = (unsigned)e32exe.e32_pageshift;
    DBPRT(16,( "PgeShft=%02x\n", PageShift ));

    //  Read in the rest of the kernel header if it's gotten too big.
    if ( e32exe.e32_datapage > CBKERNELHEADERMIN )
    {
        DBPRT(8,("hdr too big: %6lx, second read required\n",
                  e32exe.e32_datapage));
        DBPRT(8,("next hdrpaddr=%6lx  next hdrlen=%6lx\n",
                 paddrkernel + CBKERNELHEADERMIN,
                 e32exe.e32_datapage - CBKERNELHEADERMIN));

        ltmp2 = ( e32exe.e32_datapage - CBKERNELHEADERMIN + 0x1FF )
                                                                & 0xFFFFFE00;
        ltmp = filetable.ft_muRead( CBKERNELHEADERMIN,
                                    ptofar(paddrkernel + CBKERNELHEADERMIN),
                                    ltmp2);
        DBPRT(8,("read %6lx hdr bytes\n", ltmp));

        if (ltmp==ltmp2)
            ltmp = e32exe.e32_datapage - CBKERNELHEADERMIN;
        else
        {
            ERRPRT(("b hdr2"));
            fatal();
        }
    }
    DBCHECK();

    //  Verify OS2KRNLD is a library module with no link errors.
    if ((e32exe.e32_mflags & (E32MODMASK | E32NOLOAD)) != E32MODDLL)
    {
        ERRPRT(("b mflag"));
        fatal();
    }

    //  Validate count of objects.
    if (e32exe.e32_objcnt > OBJMAX)
    {
        ERRPRT(("objs"));
        fatal();
    }

    //  Verify that the only imported module is DOSCALLS.
    //  If we don't have any imported modules, continue without
    //  further tests.  If an imported module does exists, ensure
    //  that it is DOSCALLS.
    if (e32exe.e32_impmodcnt)
    {
        if ( (e32exe.e32_impmodcnt != 1) ||
             NameCmp( ptofar( e32exe.e32_impmod + paddrkernel +
                              exehdr.e_lfanew+1 ),
                      (char far *) "doscalls",
                      *( (char far *)ptofar( e32exe.e32_impmod+ paddrkernel +
                                             exehdr.e_lfanew ) )
                    )
           )
        {
            ERRPRT(("imps"));
            fatal();
        }
    }

    //  Set base final physical addresses for low and high kernel objects.
    //  Protected mode selectors for non-tiled objects will be allocated
    //  immediately following the first tileable paragraph address; we count on
    //  those GDT selectors being available for mapping the kernel objects.
    lopaddr = paddrkernel;

    // Set the shared bit for all executable code objects, and copy
    // each object table entry from the header into the objinfo array.
    p = ptofar( paddrkernel + exehdr.e_lfanew + e32exe.e32_objtab );
    for ( poi = &objinfo[1]; poi <= &objinfo[e32exe.e32_objcnt]; poi++ )
    {
        poi->oi_o32 = *(struct o32_obj far *) p;
        if (poi->oi_flags & OBJEXEC)
            poi->oi_flags |= OBJSHARED;
        poi->oi_flags &= ~OBJALIGN;
        p += sizeof(struct o32_obj);
    }

    //  For each entry in the object table, verify the object page map
    //  and compute per-object information for subsequent loader use.
    for ( poi = &objinfo[1]; poi <= &objinfo[e32exe.e32_objcnt]; poi++ )
    {
        // VSize is rounded up to the 4K page
        poi->oi_vsize = poi->oi_size;
        DBPRT(16,("old vsize=%08lx, new vsize=%08lx\n",
                    poi->oi_size,
                    poi->oi_vsize));

        //  Assign a selector to the object if accessible from 16-bit code.
        //  For tiled objects, the selector is computed from the physical
        //  address.  For non-tiled objects, allocate the next available
        //  GDT selector.
        poi->oi_sel = 0;                /* assume no selector required */
        if (poi->oi_flags & OBJALIAS16)
        {
            if (poi->oi_flags & OBJTILE)
                poi->oi_sel = ptoseg(lopaddr);
            else  /* don't tile */
                poi->oi_sel = (selbase += SELINCR);
        }

        /*
         *  Assign a final physical address for the object (in high or low
         *  memory depending on object flags).  Compute the final physical
         *  address for the next object in the same category (high or low),
         *  rounding up to the next page boundary if the next object has
         *  differing attributes which prevent it residing on the same page
         *  as the current object, or the next object must be page aligned.
         *
         *  - A shared object may not share a page with an instance data
         *    object.
         *  - A writeable object may not share a page with a non-writeable
         *    object.
         *  - An object which moves in virtual memory (such as the GDT)
         *    must be page aligned, since the new object (created by
         *    VMAllocMem in the kernel) will be so aligned.  Note that
         *    such objects have the OBJNOFLAT bit set in the object flags;
         *    this bit overloads the OBJIOPL bit.
         *
         *    Pad OBJNOFLAT objects to the next destination page boundary
         *    in addition to page aligning them.  This means that they do
         *    not share memory pages with other objects and hence can be
         *    easily freed via VMFreeMem.
         */

        falign =
            ((poi->oi_flags ^ (poi+1)->oi_flags) & OBJWRITE) ||
            (poi->oi_flags & OBJNOFLAT) ||
            ((poi+1)->oi_flags & OBJNOFLAT);
        if (falign)
            poi->oi_flags |= OBJALIGN;

        if (poi->oi_flags & OBJHIMEM)
        {
            poi->oi_paddr = hipaddr;
            // if (falign)
            // {
                hipaddr = PAGEROUND(hipaddr + poi->oi_vsize);
            // } else {
            //     hipaddr = PAGEROUND(hipaddr + poi->oi_vsize);
            // }
        }
        else
        {
            poi->oi_paddr = lopaddr;
              // if (falign) {
                lopaddr = PAGEROUND(lopaddr + poi->oi_vsize);
              // } else {
              //     lopaddr = PAGEROUND(lopaddr + poi->oi_vsize);
              // }
        }
    }
    if ((unsigned long) (unsigned) e32exe.e32_mpages != e32exe.e32_mpages)
    {
        ERRPRT(("mpages"));
        fatal();
    }

    /*
     *  Compute the difference between physical and virtual addresses.
     *  This must be a constant for all objects, so that FLAT-relative
     *  addressing works when paging is both enabled and disabled.
     *  The address of the last object is (4g - 64k) minus the size
     *  of the last object.  We assume the last object is loaded into
     *  high memory.  Since other objects are loaded into low memory,
     *  this leaves a hole in the virtual address space that *exactly*
     *  corresponds to the hole in the physical address space.
     */

    /*
     *  Kernel objects loaded into high (virtual and physical) memory are
     *  to be placed at the top end of the virtual address space.  Also,
     *  the page offset of the virtual addresses assigned to objects must
     *  equal the page offset of the physical addresses assigned to the objects.
     *  The following diagram shows the positioning of the highest addressed
     *  object (= the last object in the object table) in the virtual address
     *  space at the top end of the system arena.
     *
     *          |-----------|  4G max virtual address
     *          |-----------|  MAXSYSPAGE (top of system arena)
     *          |           |
     *          |-----------|  B = virtual address of end of highest object
     *          |///////////|  Shaded portion contains object
     *          |///////////|
     *          |-----------|  A = virtual address of highest object
     *          |           |
     *          |-----------|  L = next lower virtual address page boundary
     *          |           |
     *
     *
     *  To compute the constant difference between the virtual and physical
     *  addresses of objects, we need to compute the virtual address A to be
     *  assigned to the highest object.  We want A as high as possible.
     *  Since A - L = virtual address offset = physical address offset, A is
     *  known once L is known.  L is given by
     *
     *  L = System arena upper boundary -
     *    PAGEROUND(physical addr offset of last object + size of last object)
     *  So A = L + (physical addr offset of last object) and
     *  laddrdelta = A - physical addr of last object.
     */

    paddr = objinfo[e32exe.e32_objcnt].oi_paddr;
    pgoffst = paddr & PAGEMASK;
    size = objinfo[e32exe.e32_objcnt].oi_size;
    laddrdelta = (MAXSYSPAGE << PAGESHIFT) - PAGEROUND(pgoffst + size)
                 + pgoffst - paddr;

    /*
     *  Instance data objects for VDMs are a special case.  They have
     *  special virtual addresses assigned.
     */

    instladdr = MVDM_INST_DATA_ADDR;
    sinstladdr = MVDM_SINST_DATA_ADDR;

    DBPRT(16,("laddrdelta=%04lx, instladdr=%04lx, sinstladdr=%04lx\n",
              laddrdelta,
              instladdr,
              sinstladdr));

    /*
     *  Assign a virtual address to each entry in the object table by adding
     *  the computed delta value to each final physical address.
     */

    DBPRT(1,("ob     flags    oi-flags   paddr/sel    glp     laddr/fladdr     msz/vsz\n"));

    for ( poi = &objinfo[1]; poi <= &objinfo[e32exe.e32_objcnt]; poi++ )
    {

        poi->oi_oldbase = poi->oi_base;

        poi->oi_fladdr = poi->oi_base = laddrdelta + poi->oi_paddr;

        /*
         *  Private data is MVDM instance data; assign special
         *  addresses for the purpose of processing fixups.
         */

        if ((poi->oi_flags & OBJSHARED) == 0)
        {
            if (poi->oi_flags & OBJRESIDENT)
            {
                poi->oi_fladdr = instladdr;
                instladdr += PAGEROUND(poi->oi_size);
            }
            else
            {
                poi->oi_fladdr = sinstladdr;
                sinstladdr += PAGEROUND(poi->oi_size);
            }
        }

        /* If the kernel is based in the wrong spot, */
        /* Then display message and stop             */
        if((e32exe.e32_mflags & E32NOINTFIX) &&  //Yes it's based
           poi->oi_oldbase &&                     //This object has base addr
           (poi->oi_oldbase != poi->oi_fladdr))   //Oops, base addr is wrong
        {
            ERRPRT(("\nb krnl basing"));
            fatal();
        }

        /*
         *  Record the low end of high virtual address space
         *  for subsequent loader virtual address assignments.
         */

        if ((poi->oi_flags & OBJHIMEM) && laddrnext == 0)
            laddrnext = poi->oi_base;
        DBPRT(1,("%2x  %s  %08lx  %6lx/%4x  %4lx  %8lx/%8lx  %3lx000/%6lx\n",
                  poi - objinfo,
                  oftos(poi->oi_flags),
                  poi->oi_flags,
                  poi->oi_paddr,
                  poi->oi_sel,
                  poi->oi_pagemap,
                  poi->oi_base,
                  poi->oi_fladdr,
                  poi->oi_mapsize,
                  poi->oi_vsize));
    }
    DBPRT(16,("laddrnext=%04lx\n", laddrnext));

    R0DS = (selbase += SELINCR);
    R0CS = (selbase += SELINCR);
    DBPRT(17,("R0DS=%4x R0CS=%4x\n", R0DS, R0CS));

    /*
     * Copy the object page map, fixup page table and fixup record table
     * to the high end of available low memory, and store the final paddr
     * of each in the appropriate field in e32exe.  For convenience, just
     * copy starting from the object page map up to the end of the fixup
     * section.
     *
     * The order of the tables in the file header are:
     *
     *     object table                 (already copied to near data)
     *                                  start fcopy()
     *     object pagemap                 ^
     *     iterated data map              |
     *     resource table                 |
     *     resident names table           |
     *     entry table                    |
     *                                    |         start fixup section
     *     fixup page table               |             ^
     *     imported module names table    |             |
     *     imported procedure names table v             v
     *                                  end fcopy(), end fixup section
     *     non-resident names table
     *
     * First assert that the resource table sections is empty, so we don't
     * waste a lot of memory.  The resident names and entry tables are
     * non-empty (but very small), so we can't make the same assertion,
     * even though we don't need them.  The module names section will
     * contain just one entry, and is tested prior to this point for
     * validity.
     */

    if (e32exe.e32_rsrctab)
    {
        ERRPRT(("rsrct"));
        fatal();
    }

    ltmp = (e32exe.e32_fpagetab - e32exe.e32_objmap) + e32exe.e32_fixupsize;
    paddrSrc = paddrheader + e32exe.e32_objmap;      /* source paddr */
    paddrDest = lopaddr + CBMOVEMAX;                 /* destination paddr */
    while (ltmp)
    {
        us = (ltmp > CBMOVEMAX) ? CBMOVEMAX
                                : (unsigned)ltmp;
        fcopy( ptofar(paddrSrc), ptofar(paddrDest), us );
        paddrSrc += us;
        paddrDest += us;
        ltmp -= us;
    }

#ifdef PMDEBUG
    e32frectab = e32exe.e32_frectab + exehdr.e_lfanew;  /* save for DBPRT */
#endif

    ltmp = lopaddr - e32exe.e32_objmap + CBMOVEMAX;

    // This code updates fields in the e32exe structure with the addresses
    // of the tables in memory once loaded.
    e32exe.e32_objmap = lopaddr + CBMOVEMAX;    // object page table
    e32exe.e32_fpagetab += ltmp;                // fixup page table
    e32exe.e32_frectab += ltmp;                 // fixup record table

    //  Display and save the upper boundaries of low and high physical memory
    //  reserved for the kernel objects.
    DBPRT(17,("endlopaddr %6lx => %6lx  endhipaddr %lx\n",
                endlopaddr,
                lopaddr,
                hipaddr));
    DBPRT(17,("pagemap %6lx, fixups %6lx, fixuppt %6lx\n",
                e32exe.e32_objmap,
                e32exe.e32_frectab,
                e32exe.e32_fpagetab));
    *pendhipaddr = hipaddr;

    return(lopaddr);  /* return value must be paragraph aligned */
}



/***LP  LoadObj - read, fixup, move and zerofills one entire kernel object.
 *
 *      This function reads a kernel object into low memory, performs
 *      fixups and then moves the kernel object into its final position
 *      in physical memory.  This final physical address may be in high
 *      memory (physical addresses >= 1 Meg) or low memory.  It has already
 *      been calculated and is contained in field oi_paddr in the object
 *      info.
 *
 *      ENTRY   obj       - Index into object info array to identify
 *                          which object to load
 *      EXIT    void
 */

void LoadObj( int obj )
{
    struct o32_map far *OPTBase;  // Object Page Table Base pointer.
                                  // Used to point to the first entry in the
                                  // object page table for this object.

    unsigned long far *FPTBase;   // Fixup Page Tabe Base pointer.
                                  // Used to point to the first entry in the
                                  // fixup page table for this object.

    // Note that the index into the object page table is in sync with the index
    // into the fixup page table, thus allowing the above 2 pointers to be used
    // with the same integer structure increment.

    unsigned First, NxtFirst, i, PagesInObj;
                               // Used to index thru the object page table
                               // (and also conveniently the fixup page table)
                               // based off the OPTBase and FPTBase pointers.

    struct objinfo_s *poi;     // Pointer into ObjInfo structure.
                               // Used to point to the objinfo structure for
                               // the particular object that we are dealing
                               // with. There is an array of such structures,
                               // 1 for each object in the file.

    unsigned long LoadPAddr, tLong;
                               // (Temp) Load Physical Address
                               // Pointer to RAM to tell us where to load
                               // the object.

    // Set up pointer to the object table copy
    poi = &objinfo[obj];

    // Set up pointer to the first entry in the object page table for this
    // object. Note that the address in the e32exe structure was modified
    // to be a RAM address by GetExeInfo.
    OPTBase = ( (struct o32_map far *) ptofar(e32exe.e32_objmap) )
                                                + poi->oi_o32.o32_pagemap - 1;

    // Likewise for the fixup page table.
    FPTBase = ( (unsigned long far *) ptofar(e32exe.e32_fpagetab) )
                                                + poi->oi_o32.o32_pagemap - 1;

    // Set up the starting indices into the page tables.
    First = 0, PagesInObj = (unsigned)poi->oi_o32.o32_mapsize;

    // Loop to process each block
    while ( First<PagesInObj )
    {

        // We must read into memory below the 1 meg line as we are in real mode.
        // If the object belongs above the 1 meg line, we will read into low
        // memory and perform the fixups, and then move it into high memory.
        if ( ( LoadPAddr = poi->oi_paddr + (First*PAGESIZE) ) >= _1MEGB )
            LoadPAddr = paddrkernel;

        // Are we now constrained by the size of memory or by the number
        // of pages left to process ?
        if ( ( NxtFirst = First + MAXBLOCKEDREAD ) > PagesInObj )
            NxtFirst = PagesInObj;

        // Read all the pages in the block into low memory
        DBPRT(16,("Call RPgs Fst=%04x, NFst=%04x, OPTE=%08lx, PLim=%04x, PAdr=%08lx\n",
                  First,
                  NxtFirst,
                  OPTBase+First,
                  NxtFirst-First,
                  LoadPAddr ));
        ReadPages( OPTBase+First, NxtFirst-First, LoadPAddr );

        // Zero out the first page of the zero buffer again - ReadPages and
        // and UnpackPage overwrite the first page of it.
        clearobj( ptofar(paddrZeroBuf), PAGESIZE );

        // Loop to fix up each page in the block
        for ( i = First, tLong = LoadPAddr ;
              i < NxtFirst ;
              i++, tLong += PAGESIZE )
        {
            DBPRT(16,("Call FixPage Obj=%04x, Fst=%04x, i=%04x, NFst=%04x, PAdr=%08lx\n",
                       obj,
                       First,
                       i,
                       NxtFirst,
                       tLong));
            FixPage( FPTBase+i, OPTBase+i, tLong, poi );
        }

        // Copy high memory objects to final destination.  Since low objects
        // are loaded in their final location, bump their load addresses.
        if (poi->oi_paddr >= _1MEGB)
            MoveObj( poi, First, NxtFirst-First, LoadPAddr );

        // Jump to the start of the next block. If this puts us after
        // the last page, we will terminate the loop.
        First += MAXBLOCKEDREAD;

    } // endwhile

    // We have now loaded and unpacked and fixed up all the pages in
    // the object page map table. We now have to zero fill to the end
    // of the object if we have any pages that are not in the object
    // page map table.

    // size in bytes of the pages filled so far
    tLong = (poi->oi_o32.o32_mapsize) << PAGESHIFT;

    if ( poi->oi_vsize > tLong )
    {
        // Work out start of area to zero fill
        LoadPAddr = poi->oi_paddr + tLong;

        // number of bytes to z-fill
        tLong = poi->oi_vsize - tLong;


        //  If destination is in high memory, use MoveHigh to copy
        //  buffer of zeros from low memory.
        if ( LoadPAddr >= _1MEGB )
        {
            while (tLong)
            {
                First = ( tLong > CBMOVEMAX ) ? CBMOVEMAX : (unsigned) tLong;
                MoveHigh( paddrZeroBuf, LoadPAddr, First );
                LoadPAddr += First;
                tLong -= First;
            }
        }
        else
        {
            // Else destination is in low memory.  We can use a simple
            // clear routine to clear the memory directly.

//          if ( LoadPAddr + tLong > paddrlolimit )
//          {
//              ERRPRT(("overw lo"));
//              BootErrMem();
//          }

            DBPRT(17,("LObj zfil %8lx for %8lx\n",
                       LoadPAddr,
                       tLong));
            memsetf( ptofar(LoadPAddr), 0, (int)tLong );

        }

    } // end of if need to zero to vsize

}



/***LP  ReadPages - reads multiple pages from the OS2KRNL(I/D) file.
 *
 * Handles the unpack of any iterated pages.
 * Reads ranges of multiple pages from the file when it can.
 *
 * No return value passed back.
 */

void ReadPages( OPTEFirst, PageLimit, PagePAddr )
struct o32_map far *OPTEFirst; // Pointer into the Object Page Map Table Entry
                               // for the first page that we are to load.
unsigned short PageLimit;      // The number of pages that we are to load.
unsigned long PagePAddr;       // Physical address of the first page in memory
                               // that we are to load into.
{

    int i, j, First, Last;
    unsigned long k;
    unsigned long Size, SizeR, StartPos, StartPosR;
    char far * pSrc;
    char far * pWrk;
    char far * pTrg;

    // Find a range of pages that we can read in one go ... the page data
    // offsets must be contiguous and the pages must actually exist in the
    // file (ie iterdata or valid).
    Last = -1;
    while ( Last < (int)PageLimit - 1 )
    {
        // Starting conditions for next scan
        i = First = Last + 1;

        // Scan up through the allocated pages to identify a range
        while ( 1 )
        {
            // check the flags of the current page
            if (   ( OPTEFirst[i].o32_pageflags != VALID )
                && ( OPTEFirst[i].o32_pageflags != ITERDATA)
                && ( OPTEFirst[i].o32_pageflags != ITERDATA2) )
            {
                Last = i-1;
                if (Last<First)
                    Last = First;
                break;
            }

            // check that we have some more pages in the block
            if ( i >= ( (int)PageLimit) - 1 )
            {
                Last = i;
                break;
            }

            // check that the next page is in sequence
            if ( OPTEFirst[i+1].o32_pagedataoffset
                 != ( OPTEFirst[i].o32_pagedataoffset + (
                          ( OPTEFirst[i].o32_pagesize + (1<<PageShift) -1 )
                                                               >> PageShift ) )
               )
            {
                Last = i;
                break;
            }

            // good range so far ... go round for the next page
            i++;

        }  // end of while 1

        DBPRT(16,("Read range Fst=%04x, Lst=%04x\n", First, Last));

        // Did we hit a single zero fill or invalid and not get
        // a range ??
        if (   ( OPTEFirst[First].o32_pageflags != VALID )
            && ( OPTEFirst[First].o32_pageflags != ITERDATA)
            && ( OPTEFirst[First].o32_pageflags != ITERDATA2) )
        {
            // expand the single zero fill
            DBPRT(16,("ZFil Fst=%04x, SAdr=%08lx\n",
                  First,
                  ptofar(PagePAddr) ));
            clearobj( ptofar(PagePAddr), PAGESIZE );

            // increment the page pointer for next time round
            PagePAddr += PAGESIZE;
        }
        else  // we have a range to read in
        {

            // Where do we start reading from ?
            StartPos = e32exe.e32_datapage +
                         ( (OPTEFirst[First].o32_pagedataoffset) << PageShift );

            // Round to start reading on a 512 byte boundry. This means
            // we could actually start reading earlier than we need to.
            StartPosR = StartPos & 0xFFFFFE00;

            // Set up the PageDets array
            for ( j=First; j<=Last; j++ )
            {
                i = j - First;

                // Work out where we want this page to end up
                PageDets[i].TargPage = PagePAddr
                                          + ( ((unsigned long)i) << PAGESHIFT );

                // Work out where the data will be once read
                PageDets[i].ActMem = PagePAddr  // + StartPos - StartPosR
                                + ( ( OPTEFirst[j].o32_pagedataoffset
                                        - OPTEFirst[First].o32_pagedataoffset)
                                                                << PageShift );

                DBPRT(16,("         PDOj=%08lx, PDOFirst=%08lx\n",
                      OPTEFirst[j].o32_pagedataoffset,
                      OPTEFirst[First].o32_pagedataoffset));
                DBPRT(16,("PageDets j=%04x, PTrg=%08lx, Siz=%04x, PAct=%08lx\n",
                      j,
                      PageDets[i].TargPage,
                      OPTEFirst[j].o32_pagesize,
                      PageDets[i].ActMem));

            }  // end for set up PageDets array

            // Work out number of bytes to read in
            Size = ( (OPTEFirst[Last].o32_pagedataoffset
                            - OPTEFirst[First].o32_pagedataoffset)
                                                      << PageShift )
                                               + OPTEFirst[Last].o32_pagesize;

            // Round read size up to be a multiple of 512 bytes.
            // Add on the number of bytes we rounded the start down by.
            SizeR = ( Size + 0x01FF + StartPos - StartPosR ) & 0xFFFFFE00;

            DBPRT(16,("_muRead  Pos %08lx  Siz %08lx PAadr %08lx\n",
                        StartPos,
                        Size,
                        PagePAddr));
            DBPRT(16,("        PosR %08lx SizR %08lx\n",
                        StartPosR,
                        SizeR));
            SizeR = filetable.ft_muRead( StartPosR, ptofar(PagePAddr), SizeR );
            DBPRT(16,("Read %08lx\n", SizeR));

            // Ok, we have now read the block of pages in ... now we need to
            // shuffle them down in memory to their correct locations.

            // Move everything up to eliminate the bytes read too early
            if ( StartPos != StartPosR )
            {
                pSrc = ptofar( PagePAddr + StartPos - StartPosR );
                pTrg = ptofar( PagePAddr );
                fcopy(pSrc,pTrg,(unsigned)Size);
            }

            // For each page in the read, in reverse order. Decrementing
            // as we may have source and destinations overlapping.
            for ( j=Last; j>=First; j-- )
            {
                i = j - First;

                // Move the data into the correct place.

                if ( OPTEFirst[j].o32_pageflags == VALID )
                {
                    // Do not execute any of the move code unless we have to.
                    // Just bypass it if the source and target are the same.
                    if ( PageDets[i].ActMem != PageDets[i].TargPage )
                    {

                        //Warning!! This only works because TargPage
                        //will start at least 2 bytes above Actmem
                        // (see /ALIGN flag in kernel makefile)

                        pSrc = ptofar( PageDets[i].ActMem );
                        pTrg = ptofar( PageDets[i].TargPage );
                        bcopy(pSrc, pTrg, OPTEFirst[j].o32_pagesize);

                    }

                    // and then zero fill the rest of the page
                    k = PAGESIZE - OPTEFirst[j].o32_pagesize;
                    if (k)
                    {
                        pTrg = ptofar( PageDets[i].TargPage
                                       + OPTEFirst[j].o32_pagesize);
                        memsetf(pTrg,0,(int)k);
                    }
                }
                else
                {
                    pSrc = ptofar( PageDets[i].ActMem );
                    pWrk = ptofar(paddrZeroBuf);
                    pTrg = ptofar(PageDets[i].TargPage);

                    fcopy( pSrc, pWrk, OPTEFirst[j].o32_pagesize);

                    // and then zero fill the rest of the page
                    k = PAGESIZE - OPTEFirst[j].o32_pagesize;
                    if (k)
                    {
                        memsetf(pWrk+OPTEFirst[j].o32_pagesize,0,(int)k);
                        memsetf(pTrg+OPTEFirst[j].o32_pagesize,0,(int)k);
                    }

                    if ( OPTEFirst[j].o32_pageflags == ITERDATA )
                    {

                        DBPRT(16,("Call unpack j=%04x, PAdr=%08lx\n",
                                  j,
                                  PageDets[i].TargPage));
                        UnpackPage( pTrg );
                    }

                    else if ( OPTEFirst[j].o32_pageflags == ITERDATA2 )
                    {

                        DBPRT(16,("Call unpack2 j=%04x, PAdr=%08lx\n",
                                  j,
                                  PageDets[i].TargPage));
                       if( !( nEP2_16_Decompress(
                                 OPTEFirst[j].o32_pagesize,    //Size of SrcBuf
                                 (ulong_t)PAGESIZE,    //Size of DstBuf
                                  pWrk,                //Source buffer
                                  pTrg )))             //Destination Buffer
                       {
                          DBPRT(16,("unpack2  error\n"));
                          ERRPRT(("b unpackp"));
                          fatal();
                       }
                    }
                }

            }  // end of move pages in reverse order

            // All done - increment page pointer for next time round
            PagePAddr += ( (Last-First+1) * PAGESIZE );

        } // end of if range to read in

    } // end of while more in the block

    return;
}



/***LP  UnpackPage -
 *
 * WARNING - UnpackPage trashes the zero buffer.
 *
 * No return value passed back.
 */

void UnpackPage( char far * pPage )
{
    // Pointer to temporary work area to hold the iteration records
    // while we unpack them into the page.
    char far *pWorkArea;

    // Iteration record overlay
    register struct LX_Iter far *pItRec;

    unsigned i;

#if 0
    short IterBytesUnpacked = 0;
#endif

    // Use the zero buffer as a workarea - but, remember to zero
    // it out over again when we have finished with it. It is reset to
    // zero in LoadObj, which is the only caller of UnpackPage
    pWorkArea = ptofar(paddrZeroBuf);

    // Initialise pointer to the data to unpack
    pItRec = (struct LX_Iter far *)pWorkArea;

    // Loop around processing each iteration record in turn until
    // there are no more left - termination condition is that the
    // rest of the page after all the iteration records is zero filled.
    while (pItRec->LX_nIter)
    {

        DBPRT(16,("pItRec nIter=%x, nBytes=%x\n",
                    pItRec->LX_nIter,
                    pItRec->LX_nBytes));

#if 0
        IterBytesUnpacked += pItRec->nIter + pItRec->nBytes - 1;
#endif

        // If the pattern is one byte long
        if (pItRec->LX_nBytes==1)
        {
            // This is a special case of the n-byte code, and the n-byte
            // code will actually work for this case, but this case is
            // so much the normal case that it is worth special casing.

            memsetf( pPage, pItRec->LX_Iterdata, pItRec->LX_nIter );
            pPage += pItRec->LX_nIter;
            pItRec++;

        }
        else   // pattern is longer than one byte
        {

            for ( i=0; i < pItRec->LX_nIter; i++ )
          //RAR for ( j=0; j < pItRec->LX_nBytes; j++ )
          //RAR     *pPage++ = *( ((char far *)(pItRec)) + j + 4 );
          //RAR This should be able to replace the inner FOR loop above...
          //RAR But it doesn't work.... Now it does, cast pItRec to PCHAR
                 {                                               //RAR
                 fcopy(((char far *)pItRec+4),pPage,pItRec->LX_nBytes);
                 pPage += pItRec->LX_nBytes;                     //RAR
                 }                                               //RAR

            pItRec = (struct LX_Iter far *)( ( (char far *)pItRec )
                    + sizeof(unsigned) + sizeof(unsigned) + pItRec->LX_nBytes );
        }

    } // end of while pItRec->LX_nIter

#if 0
        DBPRT(16+1,("Iter bytes=%x\n", IterBytesUnpacked));
#endif

    return;
}

/***LP  MoveObj - Move a portion of an object into its final memory location.
 *
 *      Given an index into the global object info array objinfo, this function
 *      moves the specified pages from the source physical address to the
 *      destination physical address specified in the object info.
 *
 *      ENTRY   poi       - object info array entry
 *              olp       - object-relative starting logical page
 *              cpp       - count of physical pages to be moved
 *              loadpaddr - low memory physical load address
 *      EXIT    Nothing
 */

void MoveObj( struct objinfo_s *poi,
              unsigned olp,
              unsigned cpp,
              unsigned long loadpaddr )
{
    unsigned cbtmp;
    unsigned long cbremain;   // remaining size to be moved/zero-filled
    unsigned long cbskip;     // count of bytes to skip
    unsigned long paddrSrc;   // initial phys addr
    unsigned long paddrDest;  // final phys addr

    /*
     *  An object's file data is always a whole multiple of PAGESIZE and the
     *  virtual size of the object is the byte granular amount of memory
     *  actually needed for the object.  The size of data to be moved is
     *  clamped (if necessary) to the object's vsize.
     */

    cbskip = (long) olp << PAGESHIFT;
    paddrSrc = loadpaddr;
    paddrDest = poi->oi_paddr + cbskip;

    cbremain = (unsigned long) cpp << PAGESHIFT;

    while (cbremain)
    {
        cbtmp = (cbremain>CBMOVEMAX) ? CBMOVEMAX : (unsigned)cbremain;

        DBPRT(17,("MoveHigh src=%lx, dst=%lx, size=%x, remain=%lx\n",
                 paddrSrc,
                 paddrDest,
                 cbtmp,
                 cbremain-cbtmp));
        MoveHigh( paddrSrc, paddrDest, cbtmp );
        paddrSrc += cbtmp;      /*  Update pointers and remaining count */
        paddrDest += cbtmp;
        cbremain -= cbtmp;
    }
}


void FixPage( unsigned long far *fpte,   // Pointer to the entry in the fixup page table
              struct o32_map far *opte,  // Pointer to the entry in the object page table
              unsigned long loadpaddr,   // Location of the read page in low memory
              struct objinfo_s *poi )    // Pointer to the element in the object info array
{
    unsigned long cbfrec;  // Size in bytes of the fixups in the fixup
                           // record table

    DBCHECK();
    switch (opte->o32_pageflags)
    {

    // No fixups to do here, so just drop thru
    case INVALID + RANGE:
    case INVALID:
        break;

    // Zerofill or valid pages could have fixups, as could unpacked iterdata
    case ITERDATA2:
    case ITERDATA:
    case ZEROED:
    case VALID:
        // Check the number of bytes of fixup record infomation for this page.
        // If there are no fixup records, just drop thru, otherwise call
        // the worker routine to apply the fixups.
        if ( cbfrec = fpte[1] - fpte[0] )
        {
            DBPRT(16,("  frectab %8lx offset %8lx  size %4lx\n",
                 e32exe.e32_frectab,
                 (e32exe.e32_frectab + *fpte),
                 cbfrec));
            FixPageWorker( e32exe.e32_frectab + *fpte,
                           cbfrec,
                           loadpaddr,
                           poi );
        }
        break;

    // Fatal error if we do not know what type of page this is.
    default:
        ERRPRT(("bad page"));
        fatal();

    }

}


// #ifdef PMDEBUG
#if 0
static int first = 0;
#define BADFIX(s) { \
    int x; \
    if (first++ == 0) { \
      dprintf("\n fbuf/ofbuf/efbuf   atoff  to:tsel:toff   stype f so:soff"); \
    } \
    dprintf("\n"); \
    for (x = 0; x < sizeof(fbuf); x++) { \
        dprintf("%02x ", *((unsigned char *) fbuf + x)); \
    } \
    dprintf("\n"); \
    for (x = 0; x < sizeof(ofbuf); x++) { \
        dprintf("%02x ", *((unsigned char *) ofbuf + x)); \
    } \
    dprintf("\n"); \
    for (x = 0; x < sizeof(efbuf); x++) { \
        dprintf("%02x ", *((unsigned char *) efbuf + x)); \
    } \
    dprintf("%08lx %2x:%04x:%08lx %2x %2x %2x:%04lx", \
            atoff, \
            tobj, \
            tsel, \
            toff, \
            stype, \
            flags, \
            0, \
            0 ); \
    if (soff < 0) { \
        dprintf("-%04x ", -soff); \
    } else { \
        dprintf("+%04x ", soff); \
    } \
    dprintf(s); \
    }
#else
#define BADFIX(s)       ERRPRT((s));
#endif



void FixPageWorker( unsigned long fixaddr,   // physical address of fixups for this page
                    unsigned long size,      // size in bytes of fixups for this page
                    unsigned long srcpaddr,  // src page physical address
                    struct objinfo_s *poi )  // pointer to element in object info array
{
    register int i;
    int scount;                 /* src chain count */
    int soff;                   /* page relative source offset */
    register int slen;          /* src length (number of bytes modified) */
    unsigned short tsel;        /* target selector */
    int tobj;                   /* target object */
    unsigned long toff, atoff;  /* target offset, adjusted target offset */
    unsigned long add;          /* additive value */
    int stype, flags;           /* source type and flags */
    int mord, iord;             /* module and import ordinals */
    int rsize;
    unsigned char far *fpsrc;   /* far pointer to src */
    unsigned char far *fpfix;
    unsigned short fbuf[3];
    unsigned char *pfbuf;
#ifdef PMDEBUG
    unsigned short efbuf[3];    /* expected fixup location contents */
    unsigned char *pefbuf;
    unsigned short ofbuf[3];    /* old fixup location contents */
    unsigned char *pofbuf;
#endif

    DBPRT(16,("  fixaddr=%6lx, size=%4lx, srcpaddr=%lx\n",
              fixaddr,
              size,
              srcpaddr));

    while (size)
    {
        DBPRT(16,("    "));
        fpfix = (unsigned char far *) ptofar(fixaddr);
        rsize = 2;
        stype = *fpfix++;
        flags = *fpfix++;
#if 0
// #ifdef PMDEBUG
        if (dbflag & (17))
        {
            switch (stype & (NRSTYP | NRALIAS))
            {
            case NRSBYT:          DBPRT(17,("low byte         "));  break;
            case NRSBYT|NRALIAS:  DBPRT(17,("low byte  - tiled"));  break;
            case NRSSEG:          DBPRT(17,("selector         "));  break;
            case NRSSEG|NRALIAS:  DBPRT(17,("selector  - tiled"));  break;
            case NRSPTR:          DBPRT(17,("16:16 ptr        "));  break;
            case NRSPTR|NRALIAS:  DBPRT(17,("16:16 ptr - tiled"));  break;
            case NRSOFF:          DBPRT(17,("offset16         "));  break;
            case NRSOFF|NRALIAS:  DBPRT(17,("offset16  - tiled"));  break;
            case NRPTR48:         DBPRT(17,("pointer          "));  break;
            case NRPTR48|NRALIAS: DBPRT(17,("pointer   - tiled"));  break;
            case NROFF32:         DBPRT(17,("offset           "));  break;
            case NROFF32|NRALIAS: DBPRT(17,("offset    - tiled"));  break;
            case NRSOFF32:        DBPRT(17,("self-relative    "));  break;
            default:              DBPRT(17,("BAD source type  "));  break;
            }
        }
#endif
        if (stype & NRCHAIN)
        {
            scount = *fpfix++;
            rsize++;
            DBPRT(16,("    #%02x  ", scount));
        }
        else
        {
            scount = 1;
            soff = ptoshort(fpfix);
            rsize += sizeof(short);
            fpfix += sizeof(short);
#if 0
// #ifdef PMDEBUG
            if (soff < 0)
                DBPRT(17,("  @-%03x  ", -soff));
            else
                DBPRT(17,("  @%04x  ", soff));
#endif
        }
        if (flags & NR16OBJMOD)
        {
            tobj = ptoshort(fpfix);             /* target ID */
            rsize += sizeof(short);             /* 16 bit obj/module ordinal */
            fpfix += sizeof(short);
        }
        else if ((flags & NRRTYP) != NRRINT)
        {
            if ((flags & NRRTYP) != NRRORD)
            {
                ERRPRT(("BAD reference type: %2x", flags & NRRTYP));
                fatal();
            }
            else
            {
                if (flags & NR16OBJMOD)
                {
                    mord = *((PSHORT)fpfix)++;
                    rsize += sizeof(short);
                }
                else
                {
                    mord = *fpfix++;
                    rsize++;
                }
                if (flags & NR8BITORD)
                {
                    iord = *fpfix++;
                    rsize++;
                }
                else
                {
                    if (flags & NR32BITOFF)
                    {
                        ERRPRT(("BAD reference type: %2x", flags & NRRTYP));
                        fatal();
                    }
                    else
                    {
                        iord = *((PSHORT)fpfix)++;
                        rsize += sizeof(short);
                    }
                }
                if (iord != ORD_DOSIODELAYCNT)
                {
                    ERRPRT(("BAD ref type: %2x\n", flags & NRRTYP));
                    ERRPRT(("Ord num: %2x", iord));
                    fatal();
                }
                toff = IODelayCnt;
                tsel = 0;
            }
        }
        else
        {
            tobj = *fpfix++;                    /* target ID */
            rsize++;                            /* 8 bit obj/module ordinal */
            DBPRT(16,("  internal  %4x:", tobj)); /* target ID */
            if ((stype & NRSRCMASK) == NRSSEG)
            {
                toff = 0;                         /* target offset */
                DBPRT(16,("0"));
            }
            else if (flags & NR32BITOFF)
            {
                toff = ptolong(fpfix);          /* target offset */
                rsize += sizeof(long);
                fpfix += sizeof(long);
                DBPRT(16,("%8lx", toff));
            }
            else
            {
                toff = (long) ptoshort(fpfix);  /* target offset */
                rsize += sizeof(short);
                fpfix += sizeof(short);
                DBPRT(16,("%4lx", toff));
            }
            if (flags & NRADD)
            {
                if (flags & NR32BITADD)
                {
                    add = ptolong(fpfix);               /* 32 bit additive */
                    rsize += sizeof(long);
                    fpfix += sizeof(long);
                    DBPRT(16,(" + %8lx", add));
                }
                else
                {
                    add = (long) ptoshort(fpfix);        /* 16 bit additive */
                    rsize += sizeof(short);
                    fpfix += sizeof(short);
                    DBPRT(16,(" + %4lx", add));
                }
                toff += add;
            }
            DBPRT(16,("\n\tstype=%2x, flags=%2x, soff=%4x, tobj:toff=%2x:%4lx\n",
                       stype, flags, soff, tobj, toff));
            tsel = 0;
            if ((stype & (NRSTYP | NRALIAS)) == NRSOFF ||
                (stype & (NRSTYP | NRALIAS)) == NRSPTR)
            {
                BADFIX("FLAT off/FLAT ptr16");
                fatal();
            }
            if (tobj <= 0 || tobj > (int) e32exe.e32_objcnt)
            {
                ERRPRT(("BAD tobj: %2x\n", tobj));
                fatal();
            }
            if (stype & NRALIAS)
            {
                tsel = objinfo[tobj].oi_sel;
            }
            else
            {
                if (objinfo[tobj].oi_flags & OBJNOFLAT)
                {
                    BADFIX("BAD FLAT fix");
                    fatal();
                }
                toff += objinfo[tobj].oi_fladdr;
                tsel = (objinfo[tobj].oi_flags & OBJEXEC)? R0CS : R0DS;
            }
        }
        DBPRT(16,("\tspaddr=%6lx, tsel:toff=%2x:%4lx\n", srcpaddr, tsel, toff));
        atoff = toff;

        for (i = 0; i < scount; i++)
        {
            if (stype & NRCHAIN)
            {
                soff = ptoshort(fpfix);
                rsize += sizeof(short);
                fpfix += sizeof(short);
#if 0
// #if PMDEBUG
                if ((i % 8) == 0)
                    DBPRT(17,("\n    "));
                if (soff < 0)
                    DBPRT(17,("\t\t-%03x", -soff));
                else
                    DBPRT(17,("\t\t%04x", soff));
#endif
            }
#ifdef PMDEBUG
            memsetf( (char far *)efbuf, 0, sizeof(efbuf) );
            memsetf( (char far *)ofbuf, 0, sizeof(ofbuf) );
#endif
            slen = 4;
            switch (stype & NRSTYP)
            {

            case NRSSEG:                /* selector fixup */
#ifdef PMDEBUG
                efbuf[0] = 0;           /* linker stores 0 for selectors */
#endif
                fbuf[0] = tsel;
                slen = 2;
                break;

            case NRSOFF:                /* 16 bit offset fixup */
                                        /* also case for ordinal
                                           fixup for constant */
                slen = 2;
                /*FALLTHROUGH*/

            case NRSPTR:                /* 16:16 pointer fixup */
#ifdef PMDEBUG
                if ((flags & NRRTYP) != NRRORD)
                    efbuf[0] = (unsigned short) atoff;
                efbuf[1] = 0;           /* linker stores 0 for selectors */
#endif
                atoff = (long) (unsigned short) atoff;
                fbuf[0] = (unsigned short) atoff;
                fbuf[1] = tsel;
                break;

            case NRPTR48:               /* 16:32 pointer fixup */
#ifdef PMDEBUG
                efbuf[2] = 0;           /* linker stores 0 for selectors */
#endif
                fbuf[2] = tsel;
                slen = 6;
                /*FALLTHROUGH*/

            case NROFF32:               /* 32 bit offset fixup */
                *(unsigned long far *) &fbuf[0] = toff;
#ifdef PMDEBUG
                *(unsigned long far *) &efbuf[0] =
                    toff + objinfo[tobj].oi_oldbase - objinfo[tobj].oi_fladdr;
#endif
                break;

            case NRSOFF32:              /* 32 bit self-relative offset fixup */
                *(unsigned long far *) &fbuf[0] =
                    toff - (poi->oi_fladdr +
                            soff + 4);
                     // ((long) olp << PAGESHIFT) + soff + 4);
                     // not tested - not used in kernel load
                     DBPRT(16,("krnl"));
#ifdef PMDEBUG
                *(unsigned long far *) &efbuf[0] =
                    *(unsigned long far *) &fbuf[0] +
                    (objinfo[tobj].oi_oldbase - objinfo[tobj].oi_fladdr) -
                    (poi->oi_oldbase - poi->oi_fladdr);
#endif
                break;

            default:
                ERRPRT(("BAD src type: %2x\n", stype));
                fatal();

            }

            if ( (soff>=PAGESIZE) || (soff+slen<=0) )
            {
                BADFIX("BAD soff\n");
                fatal();
            }
#ifdef PMDEBUG
            pefbuf = (unsigned char *) &efbuf[0];
            pofbuf = (unsigned char *) &ofbuf[0];
#endif
            pfbuf = (unsigned char *) &fbuf[0];
            fpsrc = (unsigned char far *) ptofar(srcpaddr + soff);
            if (soff < 0)   /* if fixup starts on previous page: */
            {
                DBPRT(16,("\nstart page boundary soff: %x", soff));
                slen += soff;           /* reduce number of bytes to edit */
                fpsrc -= soff;          /* bump source pointer */
                pfbuf -= soff;          /* bump fixup buffer pointer */
#ifdef PMDEBUG
                pefbuf -= soff;         /* bump expected fixup buf pointer */
                pofbuf -= soff;         /* bump old fixup buf pointer */
#endif
            }
            else if ( soff+slen > PAGESIZE)
            { /* else if it runs past end of page: */
                DBPRT(16,("\nend page boundary soff: %x", soff));
                slen -= soff + slen - PAGESIZE; /* reduce edit count */
            }
            DBPRT(16,("\tfpsrc=%lp, &pfbuf[%d], slen=%d\n",
                      fpsrc,
                      pfbuf - (unsigned char *) fbuf,
                      slen));
#if 0
// #if PMDEBUG
            fcopy(fpsrc, (char far *) pofbuf, slen);
            if (dbflag & 32)
            {
                unsigned char *ptmp;

                dprintf("\n\tfbuf=");
                ptmp = (unsigned char *) &fbuf[0];
                while (ptmp < (unsigned char *) fbuf + slen)
                    dprintf("%02x ", *ptmp++);
                dprintf("ofbuf=");
                ptmp = (unsigned char *) &ofbuf[0];
                while (ptmp < (unsigned char *) ofbuf + slen)
                    dprintf("%02x ", *ptmp++);
                dprintf("efbuf=");
                ptmp = (unsigned char *) &efbuf[0];
                while (ptmp < (unsigned char *) efbuf + slen)
                    dprintf("%02x ", *ptmp++);
            }
#endif

            fcopy(pfbuf,fpsrc, slen);
  //4MEG    while (slen-- > 0)
  //4MEG    {
  //4MEG        *fpsrc++ = *pfbuf++;
  //4MEG    }
        }
        if (atoff != toff)
        {
            BADFIX("f overfl");
            fatal();
        }
        size -= rsize;
        fixaddr += rsize;

    } // end of while size

}



/***    NameCmp - Compare Module name with given string
 *
 *      Performs a case insensitive comparison with the
 *      module name string and the given string.  The
 *      size of the module name string is specified by the
 *      first byte in the Module Entry.
 *
 *      Entry:  pModuleString
 *              pString
 *              cbLen
 *
 *      Exit:   0    - Strings are the same
 *              <> 0 - Strings are different
 */

unsigned NameCmp( char far *fpModuleString,
                  char far *fpString,
                  unsigned cbLen )
{
    while (cbLen--)
    {
        if ((*fpModuleString++ | 0x20) != (*fpString++ | 0x20))
            return(1);  /* Strings unequal */
    }
    return(0);      /* Strings are equal */
}

