/*++

Copyright (c) 1990, 1991  Microsoft Corporation


Module Name:

    hwheap.c

Abstract:

    This module goes through ROM area and tries to pick up all the ROM
    blocks.

Author:

    Shie-Lin Tzong (shielint) 21-Jan-92


Environment:

    Real mode.

Revision History:

--*/

#include "hwdetect.h"
#include "hwvbios.h"
#if defined(_GAMBIT_)
#include "ssc.h"
#endif

#if !defined(_GAMBIT_)
FPTEMPORARY_ROM_BLOCK BlockHead;
FPTEMPORARY_ROM_BLOCK BlockPointer;

BOOLEAN
AddRomBlock (
    ULONG RomAddress,
    ULONG RomSize
    )

/*++

Routine Description:

    This routine adds a ROM/RAM block to our ROM list.

Arguments:

    RomAddress - the starting address of the ROM/RAM block to be added.

    RomSize - the size of the ROM/RAM block (in byte).

Return Value:

    A value of TRUE is returned if success.  Otherwise, a value of
    FALSE is returned.

--*/

{
    LONG AddSize;
    ULONG AddAddress;
    FPTEMPORARY_ROM_BLOCK pCurrentBlock, pNextBlock;
    ULONG CurrentBlock, NextBlock, AddBlock;
    ULONG EndAddBlock, EndCurrentBlock, EndNextBlock;
    BOOLEAN  fOverlap=FALSE;

    pCurrentBlock = NULL;
    pNextBlock = NULL;
    AddSize = RomSize;
    AddAddress = RomAddress;
    AddBlock = RomAddress;

    //
    // If there are other blocks, make sure there is no overlap with them
    //

    if (BlockHead) {

        pCurrentBlock = BlockHead;
        pNextBlock = pCurrentBlock->Next;
        CurrentBlock = pCurrentBlock->RomBlock.Address;
        EndCurrentBlock = CurrentBlock + pCurrentBlock->RomBlock.Size;
        EndAddBlock = RomAddress + RomSize;

        while (pCurrentBlock!=NULL) {

            //
            // calculate location of next block (if it's there)
            //

            if(pNextBlock) {
                NextBlock = pNextBlock->RomBlock.Address;
                EndNextBlock = NextBlock + pNextBlock->RomBlock.Size;
            }

            //
            // if overlapping with current block, then stop and
            // resolve overlap
            //

            if((RomAddress < EndCurrentBlock)&& (RomAddress >= CurrentBlock)){
                fOverlap = TRUE;
                break;
            }

            //
            // if add block is lower than the current one,
            // or there is not a next block, then no need to search further
            //

            if((EndAddBlock <= CurrentBlock) || (pNextBlock == NULL)) {
                break;
            }

            //
            // if block is lower than next one, but greater than current
            // one, we have found the right area
            //

            if ((EndAddBlock <= NextBlock) && (AddBlock >= EndCurrentBlock)) {
                break;
            }

            //
            // if conflicting with next block, stop searching and
            // resolve conflict after this loop
            //

            if((EndAddBlock > NextBlock) && (EndAddBlock <= EndNextBlock)) {
                fOverlap = TRUE;
                break;
            }

            pCurrentBlock = pNextBlock;
            pNextBlock = pCurrentBlock->Next;
            CurrentBlock = NextBlock;
            EndCurrentBlock = EndNextBlock;
        }
    }

    //
    // if we have reached this point, there may be a conflict
    // with the current block.
    //

    if(fOverlap) {
        if(AddBlock < EndCurrentBlock) {
            AddAddress = EndCurrentBlock;
            AddSize = EndAddBlock - EndCurrentBlock;
            if(AddSize <= 0) {
                return TRUE;
            }
        }
        if((pNextBlock != NULL) && (EndAddBlock > NextBlock)) {
            AddSize = NextBlock - AddBlock;
            if(AddSize <= 0) {
                return TRUE;
            }
        }
    }

    BlockPointer->RomBlock.Address = AddAddress;
    BlockPointer->RomBlock.Size = AddSize;

    //
    // Put it on the list.
    // if it belongs on top, put it there
    //

    if ((pCurrentBlock == NULL)||
       ((pCurrentBlock == BlockHead) && (CurrentBlock > AddBlock))) {
        BlockPointer->Next = pCurrentBlock;
        BlockHead = BlockPointer;
    } else {

        //
        // else add to middle or bottom depending on NextBlock
        //

        BlockPointer->Next = pNextBlock;
        pCurrentBlock->Next = BlockPointer;
    }
    BlockPointer++;                         // Note that this works because
                                            // we know the offset part of
                                            // the addr is always < 64k.
    return TRUE;
}
VOID
AddPs2CardRomRam (
    VOID
    )

/*++

Routine Description:

    This routine adds ROM/RAM block to our ROM list for missed MCA Adapters.

    Notes:  This function was constructed to recognize
            PS/2 cards that normally are missed.  Without
            this code, VBIOS will not recognize certain adapters
            since they have no ROM header, or use RAM.

            POS ID    Adapter Name       ROM     RAM
            -------   ------------       ---     ---
            E000      Token Ring          x       x
            E001      Token Ring          x       x
            E04F      3119 Scanner                x
            E1FF      3270 Ver B                  x
            E7FF      3270 Ver A                  x

Arguments:

    None.

Return Value:

    None.

--*/

{
    ULONG i;
    USHORT CardID;
    ULONG ROMAddr,RAMAddr;
    ULONG RAMSize, ROMSize;
    FPMCA_POS_DATA PosData;

    //
    // for every adapter slot, we search if the card is IBM Token Ring.
    // if yes, we will calculate RAM and ROM address from POS information
    // and add these blocks to our ROM BLOCK list.
    //

    for (i = 0L; i < POS_MAX_SLOT; i++) {

        ROMAddr = 0L;
        RAMAddr = 0L;
        RAMSize = 0L;
        ROMSize = 0L;

        //
        // get the POS ID of the card
        //

        PosData = HwMcaPosData + i;
        CardID = PosData->AdapterId;
        switch(CardID) {

        //
        // 4Mhz Token ring (0xE000) or 16/4Mhz Token ring (0xE001)
        //

        case 0xE000:
        case 0xE001:

            //
            // get ROM and RAM addresses of adapter
            //

            ROMAddr = ((ULONG)PosData->PosData3 & 0xfe)<<12;
            RAMAddr = ((ULONG)PosData->PosData1 & 0xfe)<<12;
            RAMSize = (ULONG) (1<<((PosData->PosData2 & 0x0c)>>2)) * 8192L;
            ROMSize = 8192L;
            break;

        //
        // 3119 Scanner
        // formula  (shl (and (not (shr POS[0] 4)) 0xf)  13) + 0xC0000
        //

        case 0xE04F:

            RAMAddr = (((~((ULONG)PosData->PosData1 >> 4)) & 0xf) << 13) + 0xC0000L;
            RAMSize = 0x2000L;              // size is fixed
            break;

        //
        // 3270 Version A
        //

        case 0xE7FF:

            RAMAddr = 0xCE000L;             // address is fixed
            RAMSize = 8192L;                // size is fixed
            break;

        //
        // 3270 Version B
        //

        case 0xE1FF:

            RAMAddr = ((ULONG)PosData->PosData2) << 12;
            RAMSize = 8192L;                // size is fixed
            break;

        default:
            break;
        }

        //
        // if adapter has ROM, then add it
        //

        if (ROMAddr) {
            AddRomBlock(ROMAddr, ROMSize);
        }

        //
        // if adapter has RAM, then add it
        //

        if (RAMAddr) {
            AddRomBlock(RAMAddr, RAMSize);
        }
    }
}

BOOLEAN
ScanRomBlocks(
    VOID
    )

/*++

Routine Description:

    This routine scans the ROM IO area and checks for 55AA at every
    512 bytes for valid ROM blocks.


    NOTES:

                -------------
                |           |
                |           |
           ------------------100000
             ^  |           |
             |  |           |
             |  -------------f0000  (ROMBIOS_START)              ---
             |  |           |                                     ^
             |  |           |                                     |
     EXTROM_LEN -------------e0000  (PS2BIOS_START)  ---          |
             |  |           |                         ^    Search |
             |  |           |                  Search |    Range  |
             |  -------------d0000             Range  |    on AT  |
             |  |           |                  on PS/2|           |
             V  |           |                         V           V
           ------------------c0000 (EXTROM_START)    ---         ---

        ON AT:
          Scan through EXTROM_START-effff for ROM Blocks
        ON PS2
          Scan through EXTROM_START-dffff for ROM Blocks

Arguments:


    None.

Return Value:

    None.

--*/

{
    ULONG BlockSize;
    BOOLEAN Success = TRUE;
    FPUCHAR Current;
    ULONG RomAddr, RomEnd;

    //
    // As per the machine type restrict the search range
    //

    MAKE_FP(Current, EXTROM_START);
    RomAddr = EXTROM_START;

    if ((HwBusType == MACHINE_TYPE_MCA) ||
        (BiosSystemEnvironment.Model == PS2_L40) ||
        (BiosSystemEnvironment.Model == PS1_386) ||
        (BiosSystemEnvironment.Model == PS2_AT)) {

        RomEnd = PS2BIOS_START;
    } else {
        RomEnd = ROMBIOS_START;
    }

    while (RomAddr < RomEnd) {

        if (((FPROM_HEADER)Current)->Signature == ROM_HEADER_SIGNATURE) {

            BlockSize = (ULONG)((FPROM_HEADER)Current)->NumberBlocks * BLOCKSIZE;

            if ((RomAddr + BlockSize) > RomEnd) {
                BlockSize = RomEnd - RomAddr;
            }

            //
            // V7 VRAM card does not correctly report its BlockSize.  Since
            // this is a very popular video card, we provide a workaround
            // for it.
            //

            if ((RomAddr == 0xC0000) && (BlockSize < 0x8000)) {
                BlockSize = 0x8000;
            }
            if (!AddRomBlock(RomAddr, BlockSize)) {
                Success = FALSE;
                break;
            }
            RomAddr += BlockSize;
            RomAddr = ALIGN_UP(RomAddr, ROM_HEADER_INCREMENT);
            MAKE_FP(Current, RomAddr);
            continue;
        }
        RomAddr += ROM_HEADER_INCREMENT;
        MAKE_FP(Current, RomAddr);
    }

    //
    // Last but not least, add the system ROM to the list
    //

    if (Success) {

        RomAddr = ROMBIOS_START;
        BlockSize = ROMBIOS_LEN;
        if ((HwBusType == MACHINE_TYPE_MCA) ||
            (BiosSystemEnvironment.Model == PS2_L40) ||
            (BiosSystemEnvironment.Model == PS1_386) ||
            (BiosSystemEnvironment.Model == PS2_AT)) {
            RomAddr = PS2BIOS_START;
            BlockSize = PS2BIOS_LEN;
        }

        if (!AddRomBlock(RomAddr, BlockSize)) {
            Success = FALSE;
        }
    }

    return Success;
}

FPTEMPORARY_ROM_BLOCK
MatchRomBlock (
    ULONG PhysicalAddr
    )

/*++

Routine Description:

    This routine finds the ROM block which the 'PhysicalAddr' is in.

Arguments:

    PhysicalAddr - the physical address ...

Return Value:

    A pointer to the detected ROM block.

--*/

{
    FPTEMPORARY_ROM_BLOCK CurrentBlock;
    ROM_BLOCK RomBlock;

    CurrentBlock = BlockHead;
    while (CurrentBlock) {
        RomBlock = CurrentBlock->RomBlock;
        if (RomBlock.Address <= PhysicalAddr &&
            RomBlock.Address +  RomBlock.Size > PhysicalAddr) {
            break;
        } else {
            CurrentBlock = CurrentBlock->Next;
        }
    }
    return(CurrentBlock);
}

BOOLEAN
IsSameRomBlock (
    FPTEMPORARY_ROM_BLOCK Source,
    FPTEMPORARY_ROM_BLOCK Destination
    )

/*++

Routine Description:

    This routine checks if the passed in ROM blocks contain the same
    information.  This ususally happens when the two ROM blocks are for
    video ROM and shadowed video ROM.

Arguments:

    Source - the source ROM block.

    Destination - the ROM block to compare with.

Return Value:

    BOOLEAN TRUE if the two ROM blocks are the same else FALSE is returned.

--*/

{

    if (Source == NULL || Destination == NULL) {
        return(FALSE);
    }

    //
    // First make sure their sizes are the same.
    //

    if (Source->RomBlock.Size == Destination->RomBlock.Size) {
        if (!HwRomCompare(Source->RomBlock.Address,
                          Destination->RomBlock.Address,
                          Source->RomBlock.Size)){
            return(TRUE);
        }
    }
    return(FALSE);

}

VOID
CheckVideoRom (
    VOID
    )

/*++

Routine Description:

    This routine checks if the int 10h video handler is in the video
    ROM block detected by us.  If not, the video ROM must have been
    remapped/shadowed to other area (usually 0xE0000.)

    NOTE: In this function, I commented out the code which removes the
          Video ROM block if it has been shadowed.  I found out
          machine POST code does not modify ALL the VIDEO ROM related
          pointers.

Arguments:

    None.

Return Value:

    None.

--*/

{
    ULONG Vector, Handler, VectorAddr = 0x10 * sizeof(ULONG);
    FPULONG pVectorAddr;
    FPTEMPORARY_ROM_BLOCK RomBlock, VideoRomBlock;
    ULONG Size;

    MAKE_FP(pVectorAddr, VectorAddr);
    Vector = *pVectorAddr;
    Handler = ((Vector >> 16) << 4) + (Vector & 0xffff);
    RomBlock = MatchRomBlock(Handler);

    //
    // Check if the int 10h handler falls in one of our ROM blocks.
    //

    if (RomBlock) {
        if (RomBlock->RomBlock.Address >= 0xC0000 &&
            RomBlock->RomBlock.Address < 0xC8000) {

            //
            // if int 10h handler is in the standard video ROM area, we simply
            // return.  Either the video ROM is not shadowed or it
            // is a in-place shadow.
            //

            return;
        } else {

            //
            // The ROM block associated with the int 10h handler is not in
            // standard video bios ROM area.  It must have been mapped to
            // the current location.  We now need to make sure we have the
            // ROM block which contains the 40:a8 VGA parameter.
            //

            VectorAddr = VGA_PARAMETER_POINTER;
            MAKE_FP(pVectorAddr, VectorAddr);
            Vector = *pVectorAddr;
            Handler = ((Vector >> 16) << 4) + (Vector & 0xffff);
            VideoRomBlock = MatchRomBlock(Handler);
            if (VideoRomBlock == NULL) {

                //
                // We did not find the Video ROM associated with the
                // VGA parameters.  Try detect it.
                //

                //
                // In the following memory comparison, we skip the first 16 bytes.
                // Because most likely the reason we did not find the standard
                // Video ROM is because the signature word is missing.
                //

                Handler = (Handler & 0xF0000) +
                              (RomBlock->RomBlock.Address & 0xFFFF);
                if (!HwRomCompare(RomBlock->RomBlock.Address + 0x10,
                                  Handler + 0x10,
                                  RomBlock->RomBlock.Size - 0x10)) {
                    if ( ( ( Handler & 0xFFFF ) == 0 ) && ( RomBlock->RomBlock.Size < 0x8000 ) ) {
                        Size = 0x8000;
                    } else {
                        Size = RomBlock->RomBlock.Size;
                    }
                    AddRomBlock(Handler, Size);
                }
            }
        }
    } else {

        //
        // There is no ROM block associated with the int 10h handler.
        // We can find the shadowed video ROM block if:
        //   We detected the original video ROM in 0xC0000 - 0xC8000 range
        //

        VideoRomBlock = MatchRomBlock((Handler & 0xFFFF) + 0xC0000);
        if (VideoRomBlock != NULL) {

            //
            // In the following memory comparison, we skip the first 16 bytes.
            // Because most likely the reason we did not find the shadow rom
            // is the signature word is missing.
            //

            if (!HwRomCompare(VideoRomBlock->RomBlock.Address + 0x10,
                              (Handler & 0xF0000) +
                                (VideoRomBlock->RomBlock.Address & 0xFFFF) + 0x10,
                              VideoRomBlock->RomBlock.Size - 0x10)) {

                AddRomBlock((VideoRomBlock->RomBlock.Address & 0xFFFF) +
                                (Handler & 0xF0000),
                            VideoRomBlock->RomBlock.Size);
            }
        }
    }
}
#endif // _GAMBIT_

VOID
GetRomBlocks(
    FPUCHAR ReservedBuffer,
    PUSHORT Size
    )

/*++

Routine Description:

    This routine scans the ROM IO area and collects all the ROM blocks.

Arguments:

    ReservedBuffer - Supplies a far pointer to the buffer.

    Size - Supplies a near pointer to a variable to receive the size
           of the ROM block.

Return Value:

    None.

--*/

{

#if defined(_GAMBIT_)
    *Size = 0;
#else
    FPTEMPORARY_ROM_BLOCK Source;
    ULONG StartAddr, EndAddr;
    FPUSHORT TestAddr;
    FPROM_BLOCK Destination;
    USHORT BufferSize;
    ULONG EBiosAddress = 0, EBiosLength = 0;
    ULONG far *EBiosInformation = (ULONG far *)
                          ((DOS_BEGIN_SEGMENT << 4) + EBIOS_INFO_OFFSET);

    //
    // First we reserve the max space needed and build our temporary rom
    // block list in the heap space below the space reservedand.  After
    // the temporary list is built, we then copy it to the caller supplied
    // reserved space.
    //

    BlockPointer = (FPTEMPORARY_ROM_BLOCK)HwAllocateHeap(0, FALSE);
    BlockHead = NULL;
    *Size = 0;

    GetBiosSystemEnvironment((PUCHAR)&BiosSystemEnvironment);
    if (BiosSystemEnvironment.ConfigurationFlags & 0x4) {

        //
        // If extened BIOS data area is allocated, we will find out its
        // location and size and save in ROM blocks.
        //

        _asm {
              push   es
              mov    ah, 0xC1
              int    15h
              jc     short Exit

              cmp    ah, 0x86
              je     short Exit

              mov    bx, 0
              mov    dx, 0
              mov    ax, 0
              mov    al, es:[bx]
              shl    ax, 10
              mov    word ptr EBiosLength, ax
              mov    ax, es
              mov    dx, es
              shl    ax, 4
              shr    dx, 12
              mov    word ptr EBiosAddress, ax
              mov    word ptr EBiosAddress + 2, dx
        Exit:
              pop    es
        }
    }

    //
    // Save the Extended BIOS data area address and size at 700:40
    //

    if (EBiosLength) {
        *EBiosInformation++ = EBiosAddress;
        *EBiosInformation = EBiosLength;
    } else {
        *EBiosInformation++ = 0L;
        *EBiosInformation = 0L;
    }
    if (!ScanRomBlocks()) {
        return;
    }

    if (HwBusType == MACHINE_TYPE_MCA) {
        AddPs2CardRomRam();
    }

    //
    // On some machines, when they shadow video ROM from 0xC0000 to
    // 0xE0000, they copy code only (no signature.)  So, we need
    // special code to work around the problem.
    //

    CheckVideoRom();

    //
    // Now do our special hack for IBM.  On SOME IBM PCs, they use
    // E0000-FFFFF for system BIOS (even on non PS/2 machines.) Since
    // system BIOS has no ROM header, it is very hard to know the starting
    // address of system ROM.  So we:
    //
    // 1. Make sure there is no ROM block in E0000-EFFFF area.
    // 2. and E0000-EFFFF contains valid data.
    //
    // If both 1 and 2 are true, we assume E0000-EFFFF is part of system
    // ROM.
    //

    Source = BlockHead;
    while (Source) {
        StartAddr = Source->RomBlock.Address;
        EndAddr = StartAddr + Source->RomBlock.Size - 1;
        if ((StartAddr < 0xE0000 && EndAddr < 0xE0000) ||
            (StartAddr >= 0xF0000)) {
            Source = Source->Next;
        } else {
            break;
        }
    }
    if (Source == NULL) {
        for (StartAddr = 0xE0000; StartAddr < 0xF0000; StartAddr += 0x800) {
            MAKE_FP(TestAddr, StartAddr);
            if (*TestAddr != 0xffff) {
                AddRomBlock(0xE0000, 0x10000);
                break;
            }
        }
    }

    //
    // Now copy the rom block list to our reserved space and release
    // the extra space we reserved.
    //

    Source = BlockHead;
    Destination = (FPROM_BLOCK)ReservedBuffer;
    BufferSize = 0;
    while (Source) {
        *Destination = *((FPROM_BLOCK)&Source->RomBlock);
        BufferSize += sizeof(ROM_BLOCK);
        Source = Source->Next;
        Destination++;
    }
    *Size = BufferSize;
#endif // _GAMBIT_
}

VOID
HwGetBiosDate(
    ULONG   StartingAddress,
    USHORT  Length,
    PUSHORT Year,
    PUSHORT Month,
    PUSHORT Day
    )
/*++

Routine Description:

    Scans the specified area for the most recent date of the
    form xx/xx/xx.

Arguments:

    StartingAddress - First address to scan
    Length          - Length of area to scan

Return Value:

    Year            - If non-zero, the year of the date  (1991, 1992, ...)
    Month           - If non-zero, then month of the date found
    Day             - If non-zero, the day of the date found


--*/
{
    FPUCHAR fp, date;
    USHORT  y, m, d;
    UCHAR   c;
    ULONG   i, temp;

#if defined(_GAMBIT_)
    {
        SSC_TIME_FIELDS TimeFields;

        SscQueryRealTimeClock(&TimeFields);

        *Year  = (USHORT) TimeFields.Year;
        *Month = (USHORT) TimeFields.Month;
        *Day   = (USHORT) TimeFields.Day;
    }
#else
    //
    //
    // Zero return values
    //

    *Year  = 0;
    *Month = 0;
    *Day   = 0;

    //
    // Search for date with the format MM/DD/YY or M1M1M2M2//D1D1D2D2//Y1Y1Y2Y2
    //

    MAKE_FP(fp, StartingAddress);   //  initialize fp pointer
    while (Length > 8) {

        c = fp[7];
        if ((c < '0' ||  c > '9')  &&  (c != '/'  &&  c != '-')) {
            // these 8 bytes are not a date, next location

            fp     += 8;
            Length -= 8;
            continue;
        }

        date = fp;                  // check for date at this pointer
        fp += 1;                    // skip to next byte
        Length -= 1;

        //
        // Check for date of the form MM/DD/YY
        //

        y = 0;
        if (date[0] >= '0'  &&  date[0] <= '9'  &&
            date[1] >= '0'  &&  date[1] <= '9'  &&
           (date[2] == '/'  ||  date[2] == '-') &&
            date[3] >= '0'  &&  date[3] <= '9'  &&
            date[4] >= '0'  &&  date[4] <= '9'  &&
           (date[5] == '/'  ||  date[5] == '-') &&
            date[6] >= '0'  &&  date[6] <= '9'  &&
            date[7] >= '0'  &&  date[7] <= '9' ) {


            //
            // A valid looking date field at date, crack it
            //

            y = (date[6] - '0') * 10 + date[7] - '0' + 1900;
            m = (date[0] - '0') * 10 + date[1] - '0';
            d = (date[3] - '0') * 10 + date[4] - '0';
        }

        //
        // Check for date of the form M1M1M2M2//D1D1D2D2//Y1Y1Y2Y2
        //

        if (Length >= 15 &&
            date[ 0] >= '0'  &&  date[ 0] <= '9'  &&  date[ 0] == date[ 1]  &&
            date[ 2] >= '0'  &&  date[ 2] <= '9'  &&  date[ 2] == date[ 3]  &&
           (date[ 4] == '/'  ||  date[ 4] == '-') &&  date[ 4] == date[ 5]  &&
            date[ 6] >= '0'  &&  date[ 6] <= '9'  &&  date[ 6] == date[ 7]  &&
            date[ 8] >= '0'  &&  date[ 8] <= '9'  &&  date[ 8] == date[ 9]  &&
           (date[10] == '/'  ||  date[10] == '-') &&  date[10] == date[11]  &&
            date[12] >= '0'  &&  date[12] <= '9'  &&  date[12] == date[13]  &&
            date[14] >= '0'  &&  date[14] <= '9'  &&  date[14] == date[15]) {

            //
            // A valid looking date field at date, crack it
            //

            y = (date[12] - '0') * 10 + date[14] - '0' + 1900;
            m = (date[ 0] - '0') * 10 + date[ 2] - '0';
            d = (date[ 6] - '0') * 10 + date[ 8] - '0';
        }

        if (y != 0) {
            if (m < 1  ||  m > 12  ||  d < 1  ||  d > 31) {
                y = 0;          // bad field in date, skip it
            } else {
                if (y < 1980) {

                    //
                    // Roll to next century.
                    //

                    y += 100;
                }
            }
        }

        //
        // Check for date of the form 19xx or 20xx
        //
        // First, check the 5th character is not a digit.
        //

#define IS_DIGIT(x) (((x) >= '0') && ((x) <= '9'))

        if (!IS_DIGIT(date[4])) {
            for (i = 0, temp = 0; i < 4; i++) {
                if (!IS_DIGIT(date[i])) {
                    temp = 0;
                    break;
                }
                temp = (temp * 10) + date[i] - '0';
            }
            if ((temp >= 1980) || (temp < 2599)) {

                //
                // Looks like a reasonable date, use it.
                //

                y = temp;
                m = 0;
                d = 0;
            }
        }

        if (!y) {
            // not a date - skip it
            continue;
        }

        if ((y >  *Year) ||
            (y == *Year  &&  m >  *Month)  ||
            (y == *Year  &&  m == *Month  &&  d > *Day) ) {

            //
            // This date is more recent
            //

            *Year  = y;
            *Month = m;
            *Day   = d;
        }
    }
#endif // _GAMBIT_
}


