/*static char *SCCSID = "%W% %E%";*/ /* * Copyright Microsoft Corporation 1986,1987 * * This Module contains Proprietary Information of Microsoft * Corporation and should be treated as Confidential. */ /* * NEWMAP3.C * * Routines to set up load image map for DOS3 exes. */ #include /* Types and constants */ #include /* Types and constants */ #include /* Types and constants */ #include /* Error messages */ #include /* External declarations */ #include LOCAL SEGTYPE seg; /* Current seg number */ /* * FUNCTION PROTOTYPES */ LOCAL void NEAR SetSizes(unsigned short segPrev); LOCAL void NEAR PackCodeSegs(unsigned short segTop); #if OVERLAYS /* * SetupOverlays: * * Set up the overlay area. * Called by AssignAddresses. */ void NEAR SetupOverlays () { APROPSNPTR apropSn; WORD cbOvlData; /* Amount of overlay data */ if(osnMac > OSNMAX) osnMac = OSNMAX; apropSn = GenSeg("\014OVERLAY_DATA","\004DATA",ggrDGroup, (FTYPE) TRUE); /* Create (maybe) data segment */ apropSn->as_flags = dfData; /* Type data */ gsnOvlData = apropSn->as_gsn; /* Save SEGDEF number */ cbOvlData = (((WORD) apropSn->as_cbMx) + 0xF) & 0xFFF0; /* Round size up to paragraph bound */ /* We will have one word table indexed by overlay segment number, one * char table indexed by overlay seg. no., one long table indexed by * overlay number, 15 bytes for the file name, a word for the number of * overlays, a word for the number of overlay segs., and a byte for the * interrupt number. */ apropSn->as_cbMx = 20 + ((long) osnMac << 1) + (long) (fDynamic ? osnMac << 1 : osnMac) + ((long) iovMac << 2) + (long) cbOvlData; // For dynamic overlays add one table of longs indexed by overlay // number and one byte for overlay interrup number. if (fDynamic) apropSn->as_cbMx += ((long) iovMac << 2) + 1; MARKVP(); /* Page has been modified */ MkPubSym("\006$$CGSN",ggrDGroup,gsnOvlData,(RATYPE)cbOvlData); /* Count of segments */ cbOvlData += 2; /* Increment size */ MkPubSym("\006$$COVL",ggrDGroup,gsnOvlData,(RATYPE)cbOvlData); /* Count of overlays */ cbOvlData += 2; /* Increment size */ MkPubSym("\013$$MPGSNBASE",ggrDGroup,gsnOvlData,(RATYPE)cbOvlData); /* Gsn to base table */ cbOvlData += osnMac << 1; /* Accumulate size of data so far */ MkPubSym("\012$$MPGSNOVL",ggrDGroup,gsnOvlData,(RATYPE)cbOvlData); /* Gsn to overlay table */ if (fDynamic) cbOvlData += osnMac << 1; /* Accumulate size of data so far */ else cbOvlData += osnMac; MkPubSym("\012$$MPOVLLFA",ggrDGroup,gsnOvlData,(RATYPE)cbOvlData); /* Overlay to file address table */ cbOvlData += iovMac << 2; /* Accumulate size of data so far */ if (fDynamic) { MkPubSym("\013$$MPOVLSIZE",ggrDGroup,gsnOvlData,(RATYPE)cbOvlData); /* Overlay to size table */ cbOvlData += iovMac << 2; /* Accumulate size of data so far */ MkPubSym("\007$$INTNO",ggrDGroup,gsnOvlData, (RATYPE)cbOvlData); /* Overlay interrupt number */ cbOvlData++; MkPubSym("\010$$OVLEND", ggrDGroup, gsnOvlData, (RATYPE) cbOvlData); /* Last byte in overlay area */ apropSn = GenSeg("\016OVERLAY_THUNKS","\004CODE",GRNIL, TRUE); /* Create thunk segment */ apropSn->as_flags = dfCode; /* Code segment */ apropSn->as_cbMx = ovlThunkMax * OVLTHUNKSIZE; apropSn->as_tysn = apropSn->as_tysn & ~MASKTYSNCOMBINE; apropSn->as_tysn = apropSn->as_tysn | TYSNCOMMON; gsnOverlay = apropSn->as_gsn; /* Save thunks SEGDEF number */ MARKVP(); /* Page has changed */ MkPubSym("\015$$OVLTHUNKBEG", GRNIL, gsnOverlay,0); MkPubSym("\015$$OVLTHUNKEND", GRNIL, gsnOverlay,ovlThunkMax*OVLTHUNKSIZE); } else { MkPubSym("\010$$EXENAM",ggrDGroup,gsnOvlData,(RATYPE)cbOvlData); /* Executable file name */ cbOvlData += 15; /* 15-byte name field */ MkPubSym("\007$$INTNO",ggrDGroup,gsnOvlData,(RATYPE)cbOvlData); /* Overlay interrupt number */ apropSn = GenSeg("\014OVERLAY_AREA","\004CODE",GRNIL,FALSE); /* Create overlay area */ apropSn->as_flags = dfCode; /* Code segment */ gsnOverlay = apropSn->as_gsn; /* Save overlay SEGDEF number */ MARKVP(); /* Page has changed */ MkPubSym("\011$$OVLBASE",GRNIL,gsnOverlay,(RATYPE)0); /* First byte in overlay area */ apropSn = GenSeg("\013OVERLAY_END","\004CODE",GRNIL,FALSE); /* Create overlay end */ apropSn->as_flags = dfCode; /* Code segment */ MkPubSym("\010$$OVLEND",GRNIL,apropSn->as_gsn,(RATYPE)0); /* Last byte in overlay area */ MARKVP(); /* Page has changed */ } } #endif /* OVERLAYS */ /**************************************************************** * * * SetSizes: * * * * This function sets the starting address for the segth * * segment assuming the segment indexed by segPrev immediately * * precedes the segth segment. If there is a starting address * * for the segth segment already, then SetSizes will not * * change that address unless the new address it calculates * * is higher. * * * ****************************************************************/ LOCAL void NEAR SetSizes (segPrev) SEGTYPE segPrev; { long addr; /* 20-bit address */ /* Get address of end of previous segment */ addr = ((long) mpsegsa[segPrev] << 4) + mpsegcb[segPrev] + mpsegraFirst[segPrev]; /* Form 20-bit address of segment */ switch(B2W(mpsegalign[seg])) /* Align the address properly */ { case ALGNWRD: /* Word-aligned */ addr = (addr + 1) & ~1L; /* Round up to word offset */ break; #if OMF386 case ALGNDBL: /* Double word-aligned */ addr = (addr + 3) & ~3L; /* Round up to dword offset */ break; #endif case ALGNPAR: /* Paragraph-aligned */ addr = (addr + 0xF) & ~0xFL; /* Round up to paragraph offset */ break; case ALGNPAG: /* Page-aligned */ addr = (addr + 0xFF) & ~0xFFL; /* Round up to page offset */ default: /* Byte-aligned */ break; } /* Assign beginning of this segment */ if(addr > ((long) mpsegsa[seg] << 4) + (long) mpsegraFirst[seg]) { mpsegsa[seg] = (WORD)(addr >> 4); mpsegraFirst[seg] = (WORD) addr & 0xF; } } /* * PackCodeSegs : Pack adjacent code segments * * Pack as many adjacent code segments (which are in the same * overlay) together as possible. Start with the current * segment, seg, and stop when the packing limit is exceeded, * a data segment is reached, or the given highest segment is * reached. For DOS3, packing means assigning the same base * address and adjusting the offset of the first byte. * * Parameters: * segTop Number of highest segment which can be packed. * Returns: * Nothing. * Side effects: * seg is set to the last segment included in the packing group */ LOCAL void NEAR PackCodeSegs (segTop) SEGTYPE segTop; { DWORD sacb; /* Length of packing group */ SEGTYPE segi; /* Our private current segment no. */ RATYPE raSave; /* Original mpsegraFirst[segi] */ #if OVERLAYS IOVTYPE iov; /* Overlay of 1st seg in group */ iov = mpsegiov[seg]; /* Determine current overlay */ #endif sacb = mpsegcb[seg] + mpsegraFirst[seg]; /* Initialize group size */ for(segi = seg + 1; segi <= segTop; ++segi) { /* Loop until highest code seg */ #if OVERLAYS if(mpsegiov[segi] != iov) /* If not a member of this ovl, skip */ continue; #endif if(!(mpsegFlags[segi] & FCODE)) /* Stop if we hit a data segment */ break; /* Adjust alignment */ switch(mpsegalign[segi]) /* Switch on alignment type */ { case ALGNWRD: /* Word-aligned */ sacb = (sacb + 1) & ~1L; /* Round up size to word boundary */ break; #if OMF386 case ALGNDBL: /* Double word-aligned */ sacb = (sacb + 3) & ~3L; /* Round up to dword offset */ break; #endif case ALGNPAR: /* Paragraph-aligned */ sacb = (sacb + 0xF) & ~0xFL; /* Round up size to para boundary */ break; case ALGNPAG: /* Page-aligned */ sacb = (sacb + 0xFF) & ~0xFFL; /* Round up size to page boundary */ break; } raSave = mpsegraFirst[segi]; /* Save original value */ mpsegraFirst[segi] = sacb; /* Set new offset */ sacb += mpsegcb[segi]; /* Increment size of group */ if(sacb > packLim) /* If packing limit exceeded, stop */ { mpsegraFirst[segi] = raSave; /* Restore original value */ break; } mpsegsa[segi] = mpsegsa[seg]; /* Assign base address */ } } /* * AssignDos3Addr: * * Assign addresses for a DOS3-format program. * Called by AssignAddresses. */ void NEAR AssignDos3Addr(void) { APROPSNPTR apropSn; /* Pointer to a SEGDEF */ SNTYPE gsn; /* Current global SEGDEF no. */ ALIGNTYPE align; /* Alignment type */ GRTYPE ggr; /* Current global GRPDEF no. */ SEGTYPE segTop=0; /* Highest segment in DGROUP */ SNTYPE gsnTop=0; /* Highest segment in DGROUP */ SNTYPE gsnBottomDGroup;/* For DS-allocate */ SEGTYPE segBottomDGroup;/* For DS-allocate */ SATYPE saMaxDGroup; /* For DS-allocate */ SEGTYPE segOverlay; SEGTYPE segPrev; #if OVERLAYS SEGTYPE FAR *mpiovsegPrev; IOVTYPE iov; ALIGNTYPE alignOverlay; long cbOverlay; WORD segOvlSa; RATYPE segOvlRaFirst; #endif SEGTYPE segStack; /* Logical segment no. of stack */ #if OVERLAYS mpiovsegPrev = (SEGTYPE FAR *) GetMem(iovMac*sizeof(SEGTYPE)); #endif segTop = 0; /* We haven't yet assigned absolute segments (it is assumed * they are empty and are used only for addressing purposes), * but now we must assign them somewhere. */ csegsAbs = 0; /* Assume there are no absolute segs */ for(gsn = 1; gsn < gsnMac; ++gsn) /* Loop to initialize absolute segs */ { if(mpgsnseg[gsn] == SEGNIL) /* If we have an absolute segment */ { ++csegsAbs; /* Increment counter */ mpgsnseg[gsn] = ++segLast; /* Assign a segment order number */ } } if(vfDSAlloc) /* If doing DS allocation */ { if(gsnMac >= gsnMax) Fatal(ER_segmax); /* We implicitly use another segment */ gsnBottomDGroup = gsnMac; /* Fix the bottom of DGROUP */ ++csegsAbs; /* Inc absolute seg counter */ segBottomDGroup = ++segLast; /* Bottom segment in DGROUP */ mpgsnseg[gsnBottomDGroup] = segLast; /* Store entry in table */ } #if OVERLAYS alignOverlay = ALGNPAR; /* Overlays are para-aligned */ #endif segLast -= csegsAbs; /* Get no. of last non-abs seg */ /* Find lowest segment in groups, etc. */ for(gsn = 1; gsn < gsnMac; ++gsn) /* Loop to find lowest segs */ { seg = mpgsnseg[gsn]; /* Get segment number */ apropSn = (APROPSNPTR ) FetchSym(mpgsnrprop[gsn],TRUE); /* Get symbol table entry */ mpgsndra[gsn] = 0; #if OVERLAYS mpsegiov[seg] = apropSn->as_iov; /* Save overlay number */ #endif mpsegcb[seg] = apropSn->as_cbMx; /* Save segment size */ if(apropSn->as_tysn == TYSNABS) /* Assign absolute segs their loc. */ mpsegsa[seg] = (SATYPE) apropSn->as_cbMx; ggr = apropSn->as_ggr; /* Get GRPDEF number */ if(ggr != GRNIL) /* If segment is group member */ { if(mpggrgsn[ggr] == SNNIL || mpgsnseg[mpggrgsn[ggr]] > seg) mpggrgsn[ggr] = gsn; if(ggr == ggrDGroup && seg > segTop) { segTop = seg; gsnTop = gsn; } } align = (ALIGNTYPE) ((apropSn->as_tysn) >> 5); if((apropSn->as_tysn & MASKTYSNCOMBINE) == TYSNSTACK) align = ALGNPAR; if(align > mpsegalign[seg]) mpsegalign[seg] = align; #if OVERLAYS if(mpsegiov[seg] != IOVROOT && mpiovsegPrev[mpsegiov[seg]] == SEGNIL && align > alignOverlay) { mpiovsegPrev[mpsegiov[seg]] = SEGNIL + 1; alignOverlay = align; } #endif /* Define special symbols "_edata" and "_end" */ if (fSegOrder) Define_edata_end(apropSn); } if (fSegOrder) Check_edata_end(gsnTop, segTop); /* Now we assign actual addresses. The procedure is as follows: * For each code segment * (1) Assign all addresses of the root up to OVERLAY_AREA or THUNK_AREA. * (2) Assign all addresses of the overlays. * (3) If dynamic overlays then set the size of OVERLAY_AREA to zero * else set the start of the segment after OVERLAY_AREA to be * the greatest of all the overlays including the root * OVERLAY_AREA. * (4) Assign the rest of the root segments. * Repeat steps one through four for all remaining segments. * * Set limit of part (1): up to OVERLAY_AREA(if there are overlays) * or the end of the segment list. Do not assign OVERLAY_AREA until * after all the overlays have been taken care of. * * For dynamic overlays the DGROUP part of the root overlay * immediatelly follows the OVERLAY_THUNKS, since the OVERLAY_AREA * is dynamically allocated by the overlay manager at run-time. */ #if OVERLAYS if(fOverlays) /* If there are overlays */ { segOverlay = mpgsnseg[gsnOverlay]; /* Set limit at 1st overlay */ mpsegalign[segOverlay] = alignOverlay; } else #endif segOverlay = segLast; /* Look at all segments */ /* Set the sizes of all of the root up until the OVERLAY_AREA. */ segPrev = 0; /* No previous segment */ for(seg = 1; seg <= segOverlay; ++seg) { /* Loop thru segs up to overlay area */ #if OVERLAYS if(mpsegiov[seg] == IOVROOT) { /* If root member */ #endif SetSizes(segPrev); /* Set start address */ /* If packing code segs and this is one, pack until segOverlay */ if (!fDynamic && packLim != 0L && (mpsegFlags[seg] & FCODE)) PackCodeSegs(segOverlay); segPrev = seg; /* Save segment number */ #if OVERLAYS } #endif } #if OVERLAYS /* If there are no overlays, then we have assigned all * segments. Otherwise, the previous segment of the * beginning of the overlays is the OVERLAY_AREA in the * root. If the dynamic overlays were requested, then * the OVERLAY_THUNKS becomes the previous segment for * all overlay segments. */ if (fOverlays) /* If there are overlays */ { for (iov = IOVROOT + 1; iov < (IOVTYPE) iovMac; ++iov) mpiovsegPrev[iov] = segOverlay; /* Assign addresses to the overlays. We do not assign the * rest of the root because we may have to expand the size of * OVERLAY_AREA to accommodate a large overlay. */ if (fDynamic) { // All dymanic overlay are zero based segOvlSa = mpsegsa[segOverlay]; mpsegsa[segOverlay] = 0; segOvlRaFirst = mpsegraFirst[segOverlay]; mpsegraFirst[segOverlay] = 0; } cbOverlay = mpsegcb[segOverlay];/* Save size of overlay segment */ mpsegcb[segOverlay] = 0; /* Zero the size field for SetSizes */ for (seg = 1; seg <= segLast; ++seg) { if(mpsegiov[seg] != IOVROOT) { SetSizes(mpiovsegPrev[mpsegiov[seg]]); /* If packing code segs and this is one, pack until segLast */ if(packLim != 0L && (mpsegFlags[seg] & FCODE)) PackCodeSegs(segLast); mpiovsegPrev[mpsegiov[seg]] = seg; } } if (fDynamic) { mpsegsa[segOverlay] = segOvlSa; mpsegraFirst[segOverlay] = segOvlRaFirst; } mpsegcb[segOverlay] = cbOverlay;/* Reset the size field */ /* Determine first segment in root after OVERLAY_AREA or OVERLAY_THUNKS */ seg = segOverlay + 1; while (seg <= segLast && mpsegiov[seg] != IOVROOT) ++seg; /* * If there is a segment in the root after the overlays, * then go through all of the overlays as previous segments * and set its size with the previous one being the last seg * of each overlay. We won't initialize the Vm for that * segment because we won't know the maximum placement until * afterward. */ if (seg <= segLast) { for (iov = IOVROOT + 1; iov < (IOVTYPE) iovMac; ++iov) SetSizes(mpiovsegPrev[iov]); /* Assign the rest of the root */ segPrev = segOverlay; while (seg <= segLast) { if (mpsegiov[seg] == IOVROOT) { SetSizes(segPrev); /* If packing code segs and this is one, pack until segLast */ if(packLim != 0L && (mpsegFlags[seg] & FCODE)) PackCodeSegs(segLast); segPrev = seg; } ++seg; } } } #endif /* OVERLAYS */ if(vfDSAlloc) /* If doing DS allocation */ { saMaxDGroup = (SATYPE) (mpsegsa[segTop] + ((mpsegcb[segTop] + mpsegraFirst[segTop] + 0xF) >> 4)); mpggrgsn[ggrDGroup] = gsnBottomDGroup; mpsegsa[segBottomDGroup] = (SATYPE)((saMaxDGroup - 0x1000) & ~(~0 << WORDLN)); #if OVERLAYS mpsegiov[segBottomDGroup] = mpsegiov[segTop]; /* Top and bottom in same overlay */ #endif mpgsndra[gsnBottomDGroup] = 0; } /* If /DOSSEG enabled, stack segment defined, and DGROUP defined, * check for combined stack + DGROUP <= 64K. */ if(fSegOrder && gsnStack != SNNIL && mpggrgsn[ggrDGroup] != SNNIL) { segStack = mpgsnseg[gsnStack]; if ((((long) mpsegsa[segStack] << 4) + mpsegcb[segStack]) - ((long) mpsegsa[mpgsnseg[mpggrgsn[ggrDGroup]]] << 4) > LXIVK) Fatal(ER_stktoobig); } segResLast = segLast; for(gsn = 1; gsn < gsnMac; ++gsn) mpgsndra[gsn] += mpsegraFirst[mpgsnseg[gsn]]; #if OVERLAYS /* Set all absolute segs to the root overlay */ seg = segLast + 1; while(seg < (SEGTYPE) (segLast + csegsAbs)) mpsegiov[seg++] = IOVROOT; /* "Remember those absolute symbols, too !" */ mpsegiov[0] = IOVROOT; FFREE(mpiovsegPrev); #endif } #if OVERLAYS #pragma check_stack(on) /**************************************************************** * * * FixOvlData: * * * * Initialize overlay data tables. * * * ****************************************************************/ void NEAR FixOvlData() { APROPNAMEPTR apropName; /* Public symbol name */ AHTEPTR ahte; /* Pointer to hash table entry */ BYTE wrd[2]; /* Word as byte array */ long ra; /* Offset */ SNTYPE osn; /* Overlay segment index */ SEGTYPE seg; /* Segment number */ SATYPE sa; /* Segment base */ BYTE *pb; /* Byte pointer */ SBTYPE sb; /* String buffer */ SNTYPE gsn; apropName = (APROPNAMEPTR ) PropSymLookup("\006$$CGSN",ATTRPNM,FALSE); /* Look up public symbol */ mpsegFlags[mpgsnseg[apropName->an_gsn]] |= FNOTEMPTY; /* Segment is not empty */ wrd[0] = (BYTE) (osnMac & 0xff); /* Get lo byte */ wrd[1] = (BYTE) ((osnMac >> BYTELN) & 0xff); /* Get hi byte */ MoveToVm(2,wrd,mpgsnseg[apropName->an_gsn],apropName->an_ra); /* Store value */ wrd[0] = (BYTE) (iovMac & 0xff); /* Get lo byte */ wrd[1] = (BYTE) ((iovMac >> BYTELN) & 0xff); /* Get hi byte */ apropName = (APROPNAMEPTR ) PropSymLookup("\006$$COVL",ATTRPNM,FALSE); /* Look up public symbol */ MoveToVm(2,wrd,mpgsnseg[apropName->an_gsn],apropName->an_ra); /* Store value */ apropName = (APROPNAMEPTR )PropSymLookup("\013$$MPGSNBASE",ATTRPNM,FALSE); /* Look up public symbol */ ra = apropName->an_ra; /* Get table offset */ seg = mpgsnseg[apropName->an_gsn]; /* Get segment number */ vrectData = LEDATA; RecordSegmentReference(seg,ra,seg); /* Record load-time fixup */ ra += 2; /* Increment offset */ /* Entries 1 thru osnMac - 1 contain bases of segments at runtime */ for(osn = 1; osn < osnMac; ++osn) /* Loop thru segment definitions */ { sa = mpsegsa[mpgsnseg[mposngsn[osn]]]; /* Get segment base */ if (fDynamic) sa <<= 4; /* Convert para address to offset from overlay base */ wrd[0] = (BYTE) (sa & 0xff); /* Lo byte */ wrd[1] = (BYTE) ((sa >> BYTELN) & 0xff); /* Hi byte */ MoveToVm(2,wrd,seg,ra); /* Move to VM */ if (!fDynamic) RecordSegmentReference(seg,ra,seg); /* Record load-time fixup */ ra += 2; /* Increment offset */ } apropName = (APROPNAMEPTR ) PropSymLookup("\012$$MPGSNOVL",ATTRPNM,FALSE); /* Look up public symbol */ ra = apropName->an_ra; /* Get table offset */ seg = mpgsnseg[apropName->an_gsn]; /* Get segment number */ if (fDynamic) { ra += 2; /* First entry null */ for(osn = 1; osn < osnMac; ++osn) { /* Loop thru segment definitions */ wrd[0] = (BYTE) mpsegiov[mpgsnseg[mposngsn[osn]]]; wrd[1] = (BYTE) ((mpsegiov[mpgsnseg[mposngsn[osn]]] >> BYTELN) & 0xff); /* Get overlay number */ MoveToVm(2,wrd,seg,ra); /* Move to VM */ ra += 2; } } else { ++ra; /* First entry null */ for(osn = 1; osn < osnMac; ++osn)/* Loop thru segment definitions */ { wrd[0] = (BYTE) mpsegiov[mpgsnseg[mposngsn[osn]]]; /* Get overlay number */ MoveToVm(1,wrd,seg,ra++); /* Move to VM */ } apropName = (APROPNAMEPTR ) PropSymLookup("\010$$EXENAM",ATTRPNM,FALSE); /* Look up public symbol */ ra = apropName->an_ra; /* Get table offset */ seg = mpgsnseg[apropName->an_gsn]; /* Get segment number */ ahte = (AHTEPTR ) FetchSym(rhteRunfile,FALSE); memcpy(sb,GetFarSb(ahte->cch),1+B2W(ahte->cch[0])); /* Copy the filename */ pb = StripDrivePath(sb); /* Strip drive and path */ sb[sb[0] + 1] = '\0'; if (strrchr(&sb[1], '.') == NULL) UpdateFileParts(pb, sbDotExe); MoveToVm(B2W(pb[0]),pb+1,seg,ra); /* Move name to VM */ } apropName = (APROPNAMEPTR ) PropSymLookup("\007$$INTNO",ATTRPNM,FALSE); /* Look up public symbol */ MoveToVm(1,&vintno,mpgsnseg[apropName->an_gsn],apropName->an_ra); /* Move overlay number to VM */ /* If /PACKCODE enabled, redefine $$OVLBASE so it has an offset of 0, * which the overlay manager expects. Find 1st non-root segment * and use that. */ if(packLim) { apropName = (APROPNAMEPTR) PropSymLookup("\011$$OVLBASE",ATTRPNM, TRUE); for(gsn = 1; gsn < gsnMac && !mpsegiov[mpgsnseg[gsn]]; ++gsn); apropName->an_gsn = gsn; apropName->an_ra = 0; } } #pragma check_stack(off) #endif /* OVERLAYS */