'===========================================================================
' Subject: COMPLETE EMS ROUTINES FOR QB       Date: 10-02-99 (21:10)       
'  Author: Aaron Severn                       Code: QB, PDS                
'  Origin: rlsevern@idirect.com             Packet: MEMORY.ABC
'===========================================================================
'****************************************************************************
' EMS.BAS
' Programmed by Aaron Severn (bob_severn@msn.com)
' Oct 10/97 - Oct 11/97
'
' This program contains several routines for using EMS in QuickBasic.  The
' routines included are used for detecting EMS, allocating and deallocating
' EMS, and reading and writing to EMS.  Each is explained in detail below.
'
' It seems this program doesn't run properly in Windows, so run it from DOS.
' Also, you'll need run QB with the qb.qlb library loaded.
'
' This code is public domain.  I am in no way responsible for any damage of
' any kind caused by it.  If it doesn't cause any damage and you find it
' useful and use it in your own programs please give me the credit I deserve.
' If you have any questions on how to use these routines, feel free to e-mail
' me and I'll do my best to explain.
'
' SetUpEMS        - Details - This routine runs three routines need to detect
'                             and set up the EMS.  Use this routine instead
'                             using GetEMSStatus, GetPageCount, and
'                             GetPageFrameSeg separately.
' GetEMSStatus    - Details - Detects EMS and makes sure it's working
'                             properly.  This routine is run by SetUpEMS.
'                 - Returns - 0: EMS is present and working properly; <> 0:
'                             EMS is not present or not working properly.
' GetPageCount    - Details - Gets the total number of EMS pages and the
'                             total number of available EMS pages.  This
'                             routine is run by SetUpEMS.
'                 - Params  - free: the total number of available EMS pages
'                             is returned in this variable; total: the total
'                             number of EMS pages is returned in this
'                             variable.
'                 - Returns - 0: successful; <> 0: failed.
' GetPageFrameSeg - Details - Returns the segment address of the EMS physical
'                             page frame (in conventional memory).  This
'                             routine is run by SetUpEMS.
'                 - Returns - 0: successful; <> 0: failed.
' AllocatePages   - Details - Allocates the specified number of EMS pages
'                             to the program.
'                 - Params  - numPages: number of pages to be allocated.
'                 - Returns - If successful the handle of the EMS pages
'                             (used for referencing the pages later) is
'                             returned.  If failed 0 is returned.
' DeallocatePages - Details - Deallocates the pages associated with an EMS
'                             handle.  Must be run at the end of a program
'                             using EMS otherwise the computer will think
'                             the memory is still in use.
'                 - Params  - handle: the EMS handle returned by the
'                             AllocatePages routine.
'                 - Returns - 0: successful; <> 0: failed.
' MapPages        - Details - Maps conventional memory pages to EMS pages.
'                             See details on EMS for a better explanation.
'                 - Params  - physicalPage: the conventional memory page
'                             to be mapped to (0, 1, 2, or 3); logicalPage:
'                             the EMS page to be mapped to (0 to number of
'                             pages allocated - 1); handle: the EMS handle
'                             returned by AllocatePages.
'                 - Returns - 0: successful; <> 0: failed.
' WriteByte       - Details - Writes a byte into a conventional memory page
'                             that has been mapped to an expanded memory
'                             page, thus writing to EMS.
'                 - Params  - physicalPage: the conventional memory page to
'                             write the byte to; offset: the offset of where
'                             to write the byte in conventional memory; byte:
'                             the byte to be written.
' ReadByte        - Details - Reads a byte from a conventional memory page
'                             that has been mapped to an expanded memory
'                             page, thus reading from EMS.
'                 - Params  - physicalPage: the conventional memory page to
'                             read from; offset: the offset of where to read
'                             the byte from.
'                 - Returns - the byte at the desired location.
' CopyToEMS       - Details - Copies a portion of memory (up to 1MB) from
'                             conventional memory into expanded memory.
'                 - Params  - length&: the length in bytes of the region to
'                             be copied; startSeg: the segment address of the
'                             first byte to be copied; startOff: the offset
'                             address of the first byte to be copied; handle:
'                             the EMS handle of the expanded memory to copy
'                             to; logicPage: the EMS page to copy to; offset:
'                             the offset of where the first byte will be
'                             copied.
'                 - Returns - 0: successful; <> 0: failed.
' CopyFromEMS     - Details - Copies a portion of expanded memory (up to 1MB)
'                             into conventional memory.
'                 - Params  - length&: the length in bytes of the region to
'                             be copied; handle: the EMS handle of the
'                             expanded memory to copy from; logicPage: the
'                             EMS page to copy from; startOff: the offset
'                             of the first byte to be copied; endSeg: the
'                             segment address of where the memory will be
'                             copied; endOff: the offset address of where the
'                             memory will be copied.
'                 - Returns - 0: successful; <> 0: failed.
'
' The details of how EMS works.
'
' EMS is divided into hundreds (depending on how much memory you have) of
' 16k "pages" of memory.  Thus when you allocate one page you are allocating
' 16k of memory to your program.  In order to read and write to EMS you can
' either use a function provided by EMS to copy memory (useful for copying
' large amounts of memory) or read and write bytes manually (better for
' copying smaller amounts of memory).  The first one is easy to do using
' the functions provided with this code, the second is more difficult.
'
' When you write to EMS you actually write to conventional memory but the
' memory is placed into EMS because the conventional memory has been mapped
' to EMS.  So you must first map the conventional memory to EMS.
'
' Like EMS, the mappable conventional memory is divided into 16k pages.
' There are 4 of them available for use.  Use the MapPages function to
' map a physical page (conventional) to a logical page (EMS).  Then if
' you write a byte to the conventional memory using the WriteByte routine
' the byte will go straight into EMS.  Similarly, if you read from a
' mapped physical page, you read from the logical page that is mapped to it.
'****************************************************************************

DEFINT A-Z

DECLARE FUNCTION AllocatePages (numPages)
DECLARE FUNCTION CopyFromEMS (length&, handle, logicPage, startOff, endSeg, endOff)
DECLARE FUNCTION CopyToEMS (length&, startSeg, startOff, handle, logicPage, offset)
DECLARE FUNCTION DeallocatePages (handle)
DECLARE FUNCTION GetEMSStatus ()
DECLARE FUNCTION GetPageCount (free, total)
DECLARE FUNCTION GetPageFrameSeg ()
DECLARE FUNCTION MapPages (physicalPage, logicalPage, handle)
DECLARE SUB SetUpEMS ()
DECLARE FUNCTION ReadByte (physicalPage, offset)
DECLARE SUB WriteByte (physicalPage, offset, byte)

' pfSeg holds the segment address of the mappable conventional memory.  Don't
' forget to include this variable.
COMMON SHARED pfSeg

TYPE RegType                                    'Dos registers for BIOS calls.
    AX AS INTEGER
    BX AS INTEGER
    CX AS INTEGER
    DX AS INTEGER
    BP AS INTEGER
    SI AS INTEGER
    DI AS INTEGER
    FLAGS AS INTEGER
    DS AS INTEGER
    ES AS INTEGER
END TYPE

TYPE MemCopyType                                'Type for info that is sent
    length AS LONG                              'to the expanded memory
    sourceType AS STRING * 1                    'manager when memory copies
    sourceHandle AS INTEGER                     'are performed.
    sourceOffset AS INTEGER
    sourceSeg AS INTEGER
    destType AS STRING * 1
    destHandle AS INTEGER
    destOffset AS INTEGER
    destSeg AS INTEGER
END TYPE

' Since the program plays around in memory, variables should be dynamic to
' make sure it's all consistent.
'$DYNAMIC

DIM SHARED inRegs AS RegType                    'Dos registers.
DIM SHARED outRegs AS RegType

' This makes sure that if an error occurs the expanded memory that the
' program is using will be returned to the expanded memory manager.
ON ERROR GOTO endHere

CLS

SetUpEMS

EMSHandle = AllocatePages(12)                   'Allocate some EMS pages

IF EMSHandle THEN
  PRINT "Expanded memory pages allocated successfully."
ELSE
  PRINT "Could not allocate expanded memory pages."
  SYSTEM
END IF

SLEEP

SCREEN 13
CLS

ctr = 0
FOR y = 0 TO 199
   FOR x = 0 TO 319
      ctr = (ctr + 1) MOD 256
      PSET (x, y), ctr
   NEXT
NEXT
                                                                      
SLEEP

IF CopyToEMS(&HFA00&, &HA000, 0, EMSHandle, 0, 0) THEN
  PRINT "Could not copy to EMS."
  isGood = DeallocatePages(handle)              'Attempt to copy what's on
  SYSTEM                                        'the screen into the EMS
ELSE                                            'that was allocated.
  CLS
  PRINT "The screen has been copied to EMS."
END IF

SLEEP
CLS

FOR i = 0 TO 99: LINE (0 + i * 1.6, i)-(319 - i * 1.6, 199 - i), i, BF: NEXT

SLEEP

IF CopyToEMS(&HFA00&, &HA000, 0, EMSHandle, 4, 0) THEN
  PRINT "Could not copy to EMS."
  isGood = DeallocatePages(handle)              'Attempt to copy what's on
  SYSTEM                                        'the screen into the EMS
ELSE                                            'that was allocated.
  CLS
  PRINT "The screen has been copied to EMS."
END IF

SLEEP
CLS

FOR i = 319 TO 0 STEP -1
   LINE (0, i)-(ABS(i - 319), 199), i
   LINE (319, ABS(i - 319))-(319 - ABS(i - 319), 0), i
NEXT i

SLEEP

IF CopyToEMS(&HFA00&, &HA000, 0, EMSHandle, 8, 0) THEN
  PRINT "Could not copy to EMS."
  isGood = DeallocatePages(handle)              'Attempt to copy what's on
  SYSTEM                                        'the screen into the EMS
ELSE                                            'that was allocated.
  CLS
  PRINT "The screen has been copied to EMS."
END IF

SLEEP

startPage = 0
DO
  IF CopyFromEMS(&HFA00&, EMSHandle, startPage, 0, &HA000, 0) THEN
    PRINT "Could not copy from EMS."
    isGood = DeallocatePages(EMSHandle)         'Attempt to copy what was
    SYSTEM                                      'just copied into EMS back
  END IF                                        'on to the screen.
 
  startPage = (startPage + 4) MOD 12

  endTime! = TIMER + .5
  DO: LOOP WHILE endTime! > TIMER AND INP(&H60) <> 1
LOOP UNTIL INP(&H60) = 1

SCREEN 0: WIDTH 80: CLS

' This section of code demonstrates reading and writing individual bytes.
' Note that two different values are written to the same place in
' conventional memory, the only difference is the memory is mapped to a
' different location in expanded memory.  When the bytes are read (both from
' the same location in conventional memory) the first byte has not been
' overwritten since it was stored in EMS, not conventional memory.

physPage = 0: logicPage = 0
IF MapPages(physPage, logicPage, EMSHandle) THEN GOTO endHere
byte = 64
WriteByte physPage, 0, byte
PRINT "A value of"; byte; "was written to physical page"; physPage; "mapped to logical page"; logicPage; "at offset 0."

physPage = 0: logicPage = 1
IF MapPages(physPage, logicPage, EMSHandle) THEN GOTO endHere
byte = 16
WriteByte physPage, 0, byte
PRINT "A value of"; byte; "was written to physical page"; physPage; "mapped to logical page"; logicPage; "at offset 0."

physPage = 0: logicPage = 0
IF MapPages(physPage, logicPage, EMSHandle) THEN GOTO endHere
byte = ReadByte(physPage, 0)
PRINT "A value of"; byte; "was read from physical page"; physPage; "mapped to logical page"; logicPage; "at offset 0."

physPage = 0: logicPage = 1
IF MapPages(physPage, logicPage, EMSHandle) THEN GOTO endHere
byte = ReadByte(physPage, 0)
PRINT "A value of"; byte; "was read from physical page"; physPage; "mapped to logical page"; logicPage; "at offset 0."

endHere:

' Call the DeallocatePages function to give the memory back to the expanded
' memory manager.  It is important that you do this at the end of the program
' otherwise EMM will think the memory is still in use and other programs
' won't be able to use it.  You could end up running out of memory.
IF DeallocatePages(EMSHandle) THEN
  PRINT "Could not deallocate expanded memory pages."
  SYSTEM
ELSE
  PRINT "Expanded memory pages deallocated successfully."
END IF

REM $STATIC
' BIOS call to allocate EMS pages.
FUNCTION AllocatePages (numPages)

inRegs.AX = &H4300
inRegs.BX = numPages
CALL interrupt(&H67, inRegs, outRegs)
AllocatePages = outRegs.DX
IF (outRegs.AX \ 256) THEN AllocatePages = 0

END FUNCTION

' BIOS call to run an extremely quick memory copy routine that is provided
' by EMS.
FUNCTION CopyFromEMS (length&, handle, logicPage, startOff, endSeg, endOff)

DIM moveStruct AS MemCopyType

moveStruct.length = length&
moveStruct.sourceType = CHR$(1)
moveStruct.sourceHandle = handle
moveStruct.sourceOffset = startOff
moveStruct.sourceSeg = logicPage
moveStruct.destType = CHR$(0)
moveStruct.destHandle = 0
moveStruct.destOffset = endOffset
moveStruct.destSeg = endSeg

inRegs.AX = &H5700
inRegs.DS = VARSEG(moveStruct)
inRegs.SI = VARPTR(moveStruct)
CALL interruptx(&H67, inRegs, outRegs)
CopyFromEMS = outRegs.AX \ 256

END FUNCTION

' BIOS call to run an extremely quick memory copy routine that is provided by
' EMS.
FUNCTION CopyToEMS (length&, startSeg, startOff, handle, logicPage, endOffset)

DIM moveStruct AS MemCopyType

moveStruct.length = length&
moveStruct.sourceType = CHR$(0)
moveStruct.sourceHandle = 0
moveStruct.sourceOffset = startOff
moveStruct.sourceSeg = startSeg
moveStruct.destType = CHR$(1)
moveStruct.destHandle = handle
moveStruct.destOffset = endOffset
moveStruct.destSeg = logicPage

inRegs.AX = &H5700
inRegs.DS = VARSEG(moveStruct)
inRegs.SI = VARPTR(moveStruct)
CALL interruptx(&H67, inRegs, outRegs)
CopyToEMS = outRegs.AX \ 256

END FUNCTION

' BIOS call to deallocate EMS pages.
FUNCTION DeallocatePages (handle)

inRegs.AX = &H4500
inRegs.DX = handle
CALL interrupt(&H67, inRegs, outRegs)
DeallocatePages = outRegs.AX \ 256

END FUNCTION

' BIOS call to check the status of EMS.
FUNCTION GetEMSStatus

inRegs.AX = &H4000
CALL interrupt(&H67, inRegs, outRegs)
GetEMSStatus = outRegs.AX \ 256

END FUNCTION

' BIOS call to get the total and available number of EMS pages.
FUNCTION GetPageCount (free, total)

inRegs.AX = &H4200
CALL interrupt(&H67, inRegs, outRegs)
free = outRegs.BX
total = outRegs.DX
GetPageCount = outRegs.AX \ 256

END FUNCTION

' BIOS call to get the segment address of the mappable conventional memory.
FUNCTION GetPageFrameSeg

inRegs.AX = &H4100
CALL interrupt(&H67, inRegs, outRegs)
pfSeg = outRegs.BX
GetPageFrameSeg = outRegs.AX \ 256

END FUNCTION

' BIOS call to map EMS to conventional memory.
FUNCTION MapPages (physicalPage, logicalPage, handle)

inRegs.AX = &H4400 OR (physicalPage MOD 256)
inRegs.BX = logicalPage
inRegs.DX = handle
CALL interrupt(&H67, inRegs, outRegs)
MapPages = outRegs.AX \ 256

END FUNCTION

' Pull a byte out of conventional memory that has been mapped to EMS.
FUNCTION ReadByte (physicalPage, offset)

realOffset& = offset + physicalPage * &H4000&
DEF SEG = pfSeg
ReadByte = PEEK(realOffset&)

END FUNCTION

' Function to run all necessary set up stuff for EMS.
SUB SetUpEMS

IF GetEMSStatus THEN
  PRINT "EMS not present or not working properly."
  SYSTEM
ELSE
  PRINT "EMS detected and working properly."
END IF

IF GetPageCount(free, total) THEN
  PRINT "Error returning page count."
  SYSTEM
ELSE
  PRINT "Free pages:"; free
  PRINT "Total pages:"; total
  IF total = 0 THEN
    PRINT "No expanded memory pages have been detected.  If you are running Windows please"
    PRINT "drop to DOS."
    SYSTEM
  END IF
END IF

IF GetPageFrameSeg THEN
  PRINT "Error returning page frame segment address."
  SYSTEM
ELSE
  PRINT "Page frame segment address: &H"; HEX$(pfSeg)
END IF

END SUB

' Put a byte into conventional memory that has been mapped to EMS.
SUB WriteByte (physicalPage, offset, byte)

realOffset& = offset + physicalPage * &H4000&
DEF SEG = pfSeg
POKE realOffset&, byte

END SUB
