BitNapper
Volume Number: | | 2
|
Issue Number: | | 5
|
Column Tag: | | Graphics Lab:Asm
|
BitNapper DA Steals Bit Maps!
By Chris Yerga, MacTutor Contributing Editor
Anguish and Fear
Welcome to the first installment of the Graphics Lab. This is where we really see what the graphics capability of the Macintosh is like. This month's column will discuss desk accessories and the techniques necessary to implement them using MDS, and in particular, the marvelous BitNapper DA, which will become very important to us next month. I have always had problems understanding what desk accessories really are and why mine never worked. Consequently, when I realized that I needed to write a DA to simplify the development of a game, I was wracked with apprehension. So my special thanks go to BMUG's Dave Burnard, who taught me how to write desk accessories without even realizing it. Thanks Dave.
Cutting Up
First, lets discuss what the desk accessory will do. The BitNapper DA's, function is to allow the user to "cut" rectangular reigons from the screen image. The DA will then create a bitMap out of the data, format it as an MDS source file, and write it out to disk. This allows us to cut graphics from MacPaint or any other application that supports desk accessories, and use them in our own MDS programs. More details on this technique will be presented next month when we use our BitNapper DA with another graphics program to create animated effects.
Since we want access to graphics anywhere on the screen, it is undesirable to have our desk accessory based in a window which will cover up some area of the screen. As a result, we will make our DA menu based. When the desk accessory is opened, it will install it's own menu in the menu bar, and it will remove the menu when it's closed.
The essence of desk accessories
Now lets take a look at the desk accessory itself. From a low-level veiwpoint, a desk accessory is a device driver. That is, desk accessories and device drivers are structured similarly. The structure that they share is somewhat different from that of a standard Macintosh application, and consequently, the programming approach is a bit different. To understand why this is, lets look at figure #1.
Figure #1 shows the structure of a DA header. The first word, drvrFlags, specifies what types of calls are supported by the desk accessory, whether it needs to be called periodically, etc. Our DA will set this to indicate that Control calls are supported, and that the accessory should be locked in memory after it's loaded. The following word is drvrDelay, which determines how often, in ticks, the desk accessory needs to be called. Since we don't need to update an on-screen clock or perform any similar task, we don't require periodic calls and will set this field to NUL.
The next word, drvvrEMask, is the event mask for our DA. We will set ours to NUL, because our DA doesn't respond to event manager events. Rather, our DA will respond to desk manager events that are sent when a menu item is selected. The ID of the menu belonging to our DA is stored in the next field, drvrMenu.
The following 5 words are offsets to the Open, Prime, Control, Status, and Close calls relative to the beginning of the desk accessory. The Open routine is called when the DA is first opened, and is responsible for defining and installing the menu, and other initialization tasks. The Prime routine is called when the accessory needs a periodic event, our Prime routine simply returns. The Control routine is the heart of the DA, and is responsible for handling events. Our DA has no Status routine, since it is not appropriate. Finally, the Close routine removes the DA's menu and frees up any storage allocated by the DA.
Following the offsets is an optional name for the desk accessory. This is not where the name that appears in the menu bar is stored; rather, this is available for internal use by the DA. For example, a DA could look here to find the title for it's window, etc. We don't use this name. The remaining data is the actual code for the DA's routines.
But wait, there's more...
There is another data structure that we must concern ourselves with. This is the Device Control Entry. The DCE is a 40 byte relocatable block that is allocated on the system heap when a desk accessory is opened. As illustrated in figure #2, the DCE contains various information about the DA, and copies of some of the data in the DA header. Although it is primarily intended for internal use by the device manager and the desk manager, there are a couple of fields relevant to our discussion.
The DCE contains a field which the DA may use to store a handle to some private storage which it allocates. This field, dCtlStorage, will be where we store the menuHandle for the DA's menu.
Another important field is dCtlWindow, in which the DA may store the windowPtr of it's main window. Its purpose is analogous to our use of dCtlStorage for DA's which are window based. For such DA's it is important to set the windowKind field of the window record to the DA's driver reference number, which is located in dCtlRefNum. This marks the window as belonging to a DA, and insures that Desk Manager and Event Manager routines work properly.
The tables are turned...
We have dealt with register-based calling conventions in the past. This is where arguments are passed in a data structure pointed to by an address register rather than on the stack. But this time we are on the receiving end. Instead of us having to set up the proper fields of a parameter block, we are passed parameter blocks to use or ignore as we wish. When a DA routine is called, A0 contains a pointer to the IOParamBlock and A1 contains a pointer to the DA's DCE. When a DA routine is finished, it must send an error code back in D0. If D0 is cleared, then no error has occurred.
For openers
The first thing the Open routine does is save the registers on the stack. Next, it copies the DCE pointer from A1 into a less volatile register. Then it makes sure this is the first call to the Open routine by testing the dCtlStorage field of the DCE. If there isn't a menuHandle stored here (it's NIL) then a menu is created and the menuHandle is stored, otherwise it just returns.
The Close routine does the opposite. It removes the menu from the menu bar, releases the memory occupied by it, and clears the dCtlStorage field.
The Prime and Status routines don't exist, so the offsets in the DA header point to a routine called Null which simply clears D0 to signify that no error has occurred and returns.
On with the show
The Control routine is the functional element of the DA. When it is called, it checks the csCode field of the IOParamBlock to see if an event of interest has occurred. There are 9 possible messages passed in csCode; they are listed on page 14 of the Desk Manager Programmer's Guide in Inside Macintosh. However, our DA only acknowledges 1 of these messages.
This is accMenu, which has a decimal value of 67. When an accMenu message is passed to a DA, the csParam field of the IOParamBlock contains the MenuID of the menu and csParam+2 contains the item number. SInce we only have one menu, we don't need to check the MenuID. Rather, we check the item number to determine which command was selected.
One of the menu items is Word Constraint, which is toggled on and off by repeated selctions. What this does is force the rectangular regions to have widths which occur on word boundaries; this is a convenient format for certain graphic applications. When this item is selected, the DA calls _GetItmMark to determine if the item is checked. It toggles the appearance of the item with _CheckItem and returns.
The main menu item is Steal Bits. When this is called, the cursor is hidden and is replaced by a small rectangle which inverts whatever it is currently over. The user positions this at the upper left corner of the region he wants to steal. Then the user drags to the lower right of the region. The routine inverts the rectangular region selected until the mouse button is released.
When the button is released, the DA calculates the relevant information needed to create a BitMap data structure containing the selected bits. When this is done, a nonrelocatable block of proper size is allocated on the application heap and the screen data is copied via _CopyBits. The user is then prompted for a filename via SFPutFile and so on. I won't go into detail about this portion of the DA, as it is very similar to my icon converter in Vol. 2 No. 1. of MacTutor. That issue also contains additional information about the structure of BitMaps, if the above seems unclear.
Get it where it counts
Before we can use the DA, we must install it into the System file. A DA is a resource of type DRVR. At the beginning of the source file we use the Resource directive of MDS to assemble the code into a DRVR resource. After linking, the DA can be installed into System via the font/DA mover by holding down the option key and clicking on the open button. This allows us to open the output file which contains our DA resource. Other more tedious options include using the resource editor, resource mover, etc. During development of a DA, I usually just link the .REL file of the DA into the resource fork of a shell application that does nothing else but support DA's. This saves me from the tedious work of installing the DA after every assembly.
I hope that I have shed some light onto desk accessories. If you are interested in this particular DA or graphic techniques in general, more specific information will be coming next month in our special animation issue. Enjoy!
;
; BitNapper
; by Chris Yerga
;
RESOURCE 'DRVR' 27 'BitNapper'
INCLUDE MacTraps.D
.TRAP _MakeFile $A008
MACRO CenterString,MidPt,Y =
LEA '{String}',A3;get stringPtr
MOVE.L #{MidPt},D4
MOVE #{Y},D5
BSR CenterText
|
MACRO ErrCheck =
TST.L D0;error code?
BNE IOErr ;yes...show a dialog
|
; Program constants ----
Chicago EQU 0
Geneva EQU 3
accMenu EQU 67 ;event code for a Menu event
csCode EQU 26 ;cntrl code offset in ParamBlock
screenBitsEQU $FFFFFF86 ;[BitMap]
dCtlStorage EQU $14 ;driver's private storage [handle]
dCtlRefNumEQU $18 ;refNum of this driver [word]
dCtlWindowEQU $1E ;driver's window (if any) [pointer]
JIODone EQU $8FC ;IODone entry location [pointer]
Start:
DC.W $4400 ;ctl-enable & lock it
DC.W 0 ;doesn't need time
DC.W $0000 ;no events
MenuID: DC.W-12548 ;menu
DC.W Open-Start ; open routine
DC.W Null-Start ; prime - unused
DC.W Control-Start; control
DC.W Null-Start ; status - unused
DC.W Close-Start; close
.ALIGN 2
Open:
MOVEM.L A0-A6/D0-D7,-(SP) ;Save the registers
MOVE.L A1,A4 ;get a copy of the DCE in A4
;Check if we already have a menu.
;If one exists, its handle would be stored in dCtlStorage
;Otherwise, dCtlStorage would be NIL
TST.L DCtlStorage(A4)
BNE Restore ;we have one...skip this code
;OK. We need to create our menu:
CLR.L -(SP) ;room for menu handle
LEA MenuID,A0
MOVE (A0),-(SP) ;menu ID
PEA 'BitNapper';menu title
_NewMenu
MOVE.L (SP),dCtlStorage(A4);save handle, on stack
PEA 'Steal Bits;Word Constraint;About BitNapper;-;Quit'
_AppendMenu
MOVE.L dCtlStorage(A4),-(SP) ;push handle
CLR -(SP) ;put at the end of menu bar
_InsertMenu
MOVE.L dCtlStorage(A4),-(SP) ;menu handle
MOVE #4,-(SP) ;item #3
_DisableItem ;disable it
_DrawMenuBar ;draw the new menuBar
Restore:
MOVEM.L (SP)+,A0-A6/D0-D7; restore regs
Null:
MOVE #0,D0 ;return noErr
RTS ;and return...
Close:
MOVEM.L A0-A6/D0-D7,-(SP);save registers
MOVE.L A1,A4 ;copy DCE to A4
LEA MenuID,A0 ;Get the Menu ID
MOVE (A0),-(SP) ;remove menu from list
_DeleteMenu;MenuHandle stored in dCtlStorage
MOVE.L DCtlStorage(A4),-(SP)
_DisposMenu;free up its memory
_DrawMenuBar ;draw the menubar
CLR.L DCtlStorage(A4) ;remove MenuHandle
MOVEM.L (SP)+,A0-A6/D0-D7;restore regs
MOVE #0,D0 ;return noErr
RTS ;and return...
Control:
MOVEM.L A0-A6/D0-D7,-(SP);save registers
MOVE.L A1,A4 ;copy DCE pointer to A4
MOVE csCode(A0),D0; get the control opCode
CMP #accMenu,D0; is it a menu event?
BEQ DoCtlEvent ; yes, do it...
CtlDone:
MOVEM.L (SP)+,A0-A6/D0-D7;restore the registers
MOVE #0,D0 ; return no error
MOVE.L JIODone,-(SP); jump to IODone
RTS
DoCtlEvent:
CMP #1,30(A0) ;is it Steal Bits?
BEQ GetBits
CMP #5,30(A0) ;is it quit?
BEQ ItsQuit
CMP #2,30(A0) ;is it constrain?
BEQ Constrain
CMP #3,30(A0) ;is it About?
BEQ About
BRA CtlDone
About:
CLR.L -(SP) ;room for WindowPtr
CLR.L -(SP) ;allocate storage in heap
PEA WindowRect ;bounds
PEA 'Title?' ;title..unused
MOVE #$0101,-(SP) ;visible
MOVE #1,-(SP) ;dialog-style window
MOVE.L #-1,-(SP);put it in front
CLR -(SP) ;noGoAway
CLR.L -(SP) ;refcon
_NewWindow
MOVE.L (SP)+,A2 ;save windowPtr
PEA thePort ;VAR thePort
_GetPort ;save current grafport
MOVE.L A2,-(SP) ;make help window current port
_SetPort
MOVE #Chicago,-(SP) ;set font
_TextFont
MOVE #12,-(SP) ;12 point
_TextSize
Center the BitNapper,178,20
MOVE #Geneva,-(SP)
_TextFont
MOVE #9,-(SP)
_TextSize
Center ©1986 by Chris Yerga,178,30
MOVE #12,-(SP)
_TextSize
Center The BitNapper is a tool which simplifies,178,50
Center the use of BitMapped graphics with the MDS system.,178,62
Center It allows the user to cut bitmaps directly off,178,74
Center the screen from any application that supports,178,86
Center desk accessories.,178,98
Center To learn about graphic techniques and all other,178,115
Center aspects of Mac programming read MacTutor magazine.,178,127
MOVE #1,-(SP)
_TextFace
Center call (714) 630-3730 to subscribe,178,150
@1 CLR -(SP)
_Button
MOVE (SP)+,D0
BEQ @1
MOVE.L thePort,-(SP);restore the old grafport
_SetPort
MOVE.L A2,-(SP) ;lose the window and free
_DisposWindow ;up it's memory
CLR -(SP) ;unHiLight the menu
_HiliteMenu
BRA CtlDone
CenterText:
CLR -(SP) ;room for INTEGER
MOVE.L A3,-(SP) ;push stringPtr
_StringWidth
CLR.L D3
MOVE (SP)+,D3 ;get string width
DIVU #2,D3
SUB D3,D4 ;subtract width/2 from center
MOVE D4,-(SP) ;push x
MOVE D5,-(SP) ;push y
_MoveTo
MOVE.L A3,-(SP)
_DrawString
RTS
Constrain:
MOVE.L dCtlStorage(A4),-(SP) ;menuHandle
MOVE #2,-(SP) ;item #2
PEA CheckMark ;VAR CheckMark
_GetItmMark
CLR D0;set marked to FALSE
LEA CheckMark,A1
TST (A1);is it marked?
BNE @1;Yes..lets unmark it
MOVE #$0101,D0 ;set marked to TRUE
@1 MOVE.L dCtlStorage(A4),-(SP) ;menuHandle
MOVE #2,-(SP) ;item #2
MOVE D0,-(SP) ;marked: BOOLEAN
_CheckItem
CLR -(SP) ;unHiLight the menu
_HiliteMenu
BRA CtlDone
Sync:
SUBQ #4,SP ;Room for result
_TickCount
@1 SUBQ #4,SP ;Get it again
_TickCount
MOVE.L (SP)+,D0 ;Get tickCount in D0
CMP.L (SP),D0 ;Has it changed?
BEQ @1;No, loop
ADDQ #4,SP ;else pop old tickCount
RTS ;and return
GetBits:
PEA thePort ;save the GrafPort
_GetPort
MOVE.L dCtlStorage(A4),-(SP) ;menuHandle
MOVE #2,-(SP) ;item #2
PEA CheckMark ;VAR CheckMark
_GetItmMark;see if it's marked
MOVE #$FFFF,D4 ;assume no constraint
LEA CheckMark,A1
TST (A1);is it marked?
BEQ @0;no...no constraint
MOVE #$FFF0,D4 ;yes...word constraint
@0 _HideCursor ;hide the cursor
@1 PEA StartPoint ;VAR MousePoint
_GetMouse;get the mouse location
PEA StartPoint
_LocalToGlobal ;convert to global coordinates
LEA StartPoint,A3
AND D4,2(A3) ;constrain the horiz coord
MOVE.L (A3),4(A3) ;TopLeft of rect
ADD #4,4(A3) ;add 4 to bottom
ADD #16,6(A3) ;add 16 to right
MOVE.L A3,-(SP)
_InverRect ;invert the rect
BSR Sync;wait for a couple VBL's
BSR Sync
MOVE.L A3,-(SP) ;invert it again
_InverRect
CLR -(SP) ;BOOLEAN
_Button;see if Button is pressed
MOVE (SP)+,D0 ;get result
BNE @2;button was pressed
BRA @1;not pressed...continue
@2 PEA StartPoint ;draw the rect
_InverRect
@3 PEA NewPoint ;VAR NewPoint
_GetMouse;get the mouse location
PEA NewPoint
_LocalToGlobal ;convert to global coordinates
LEA NewPoint,A3;get ptr to NewPoint
AND D4,2(A3) ;constrain the horiz
MOVE.L (A3),D0 ;get newpoint
LEA EndPoint,A3
CMP.L (A3),D0 ;has it changed?
BEQ @4;no...check for Button up
BSR Sync
PEA StartPoint ;yes...erase old rect
_InverRect
MOVE.L 4(A3),(A3) ;make Point the new EndPoint
PEA StartPoint
_InverRect ;and invert the new rect
@4 CLR -(SP)
_Button;Button up?
MOVE (SP)+,D0
BNE @3;no...keep going
PEA StartPoint
_InverRect ;erase the rect
CLR -(SP) ;room for BOOLEAN
PEA StartPoint
_EmptyRect ;is the rectangle empty?
MOVE (SP)+,D0 ;get result
BNE StealExit ;Yes...get lost
LEA StartPoint,A1;get pointer
LEA NewPoint,A0;get pointer
MOVE.L (A1),(A0);copy TopLeft
MOVE.L 4(A1),4(A0);copy BottomRight
MOVE.L (A0),-(SP) ;SrcPoint
PEA 4(A0) ;VAR DestPoint
_SubPt ;subtract SrcPt from DestPt
;and store result in DestPt
LEA NewPoint,A0;get pointer
CLR.L (A0);set topleft = 0,0
MOVE 6(A0),D0 ;get Right in D0
ADD #$F,D0
LSR #3,D0 ;divide by 8 to get rowBytes
AND #$FFFE,D0
MULU 4(A0),D0 ;multiply by the height
ADD #20,D0;make room for header
LEA BitMapLen,A0
MOVE.L D0,(A0) ;save the length
_NewPtr;get a nonrelocatable block
ErrCheck
LEA BitMapPtr,A1
MOVE.L A0,(A1) ;save pointer
MOVE.L A0,A4 ;copy to A6
LEA NewPoint,A0;get pointer
MOVE 6(A0),D0 ;get Right in D0
ADD #$F,D0
LSR #3,D0 ;divide by 8 to get rowBytes
AND #$FFFE,D0
CLR.L (A4)+ ;clear space for BasAddr ptr
MOVE D0,(A4)+ ;store rowBytes
CLR.L (A4)+ ;topLeft of Bounds
MOVE.L 4(A0),(A4)+;bottomRight...
MOVE.L (A1),A1 ;get pointer to storage again
MOVE.L A4,(A1) ;store basAddr
MOVE.L (A5),A0 ;get globalPtr
PEA ScreenBits(A0) ;srcBits
MOVE.L A1,-(SP) ;destBits
PEA StartPoint ;srcRect
PEA NewPoint ;destRect
CLR -(SP) ;mode = srcCopy
CLR.L -(SP) ;no maskRgn
_CopyBits;copy the data from the screen
;to our BitMap
PutFile:
_ShowCursor
MOVE #100,-(SP) ;x coordinate of dialog
MOVE #80,-(SP) ;y coordinate
PEA 'Save BitMap as:' ;prompt
PEA 'Untitled.BMAP';default name
CLR.L -(SP) ;standard dialog
PEA SFReply ;reply record
MOVE #1,-(SP) ;routine selector
_Pack3
LEA SFReply,A0
TST.B (A0);were we successful?
BEQ PurgeBitMap;no...
LEA IOParam,A2 ;ptr to ParamBlock
CLR.L 12(A2) ;no completion
LEA FileName,A0
MOVE.L A0,18(A2);filename ptr
MOVE VRef,22(A2);VrefNum
CLR.B 26(A2) ;vers #
MOVE.L A2,A0
_MakeFile;create the file
ErrCheck ;are we ok?
MOVE.B #2,27(A2);write-only access
LEA APBuffer,A0
MOVE.L A0,28(A2);access path buffer
MOVE.L A2,A0
_Open ;open the file
ErrCheck ;ok?
MOVE.L BitMapLen,D4 ;get length
LSR.L #3,D4 ;divide by 8
MOVE.L BitMapPtr,A3 ;get ptr to data
DoALine:
LEA LineBuffer,A1
MOVE #7,D3 ;8 bytes per line
MOVE.L #'DC.B',(A1)+;store the line header
MOVE.B #' ',(A1)+
@1 MOVE.B (A3)+,D0 ;get the next byte of data
BSR HexToAscii ;convert it
MOVE.B #'$',(A1)+ ;store it as $xx
MOVE D1,(A1)+
MOVE.B #',',(A1)+
DBRA D3,@1 ;have we done 7 bytes?
SUBA #1,A1 ;yes...finish the line
MOVE.L #$200D2020,(A1)+
LEA LineBuffer,A0;and write it to disk
MOVE.L A0,32(A2);ptr to our line of text
MOVE.L #38,36(A2) ;write 38 bytes
CLR 44(A2);standard positioning
CLR.L 46(A2)
MOVE.L A2,A0
_Write ;write it
ErrCheck
DBRA D4,DoALine ;are we done?
;yes...finish it off
CLR 28(A2)
MOVE.L A2,A0
_GetFileInfo
ErrCheck
MOVE.L #'TEXT',32(A2) ;set the filetype
MOVE.L #'EDIT',36(A2) ;and the creator
MOVE.L A2,A0
_SetFileInfo
ErrCheck
MOVE.L A2,A0 ;close the file
_Close
ErrCheck
MOVE.L A2,A0 ;flush out any buffers
_FlushVol
ErrCheck
PurgeBitMap:
LEA BitMapPtr,A0 ;get ptr
MOVE.L (A0),A0
_DisposPtr ;release the block
StealExit:
MOVE.L thePort,-(SP);restore the grafPort
_SetPort
_ShowCursor
CLR -(SP) ;unHiLight the menu
_HiliteMenu
BRA CtlDone
ItsQuit:
LEA MenuID,A1
MOVE (A1),-(SP) ;remove the menu from the list
_DeleteMenu
move.l DCtlStorage(A4),-(SP) ;push the handle
_DisposMenu;free up the memory
_DrawMenuBar
clr.l DCtlStorage(A4) ;no longer have a window.
MOVEM.L (SP)+,A0-A6/D0-D7;restore regs
MOVEQ #0,D0
RTS
; HexToAscii routine -> converts a hex byte to an ASCII word
;
;on entry:
;D0 = Hex byte to be converted
;
;on return:
;D1 = ASCII word result
;
;uses D0,D1,D4,A0
HexToAscii:
MOVE.B D0,D2 ;save copy of byte
LSR #4,D0 ;Get the high nibble
ANDI #$0F,D0 ;mask out all extraneous bits
LEA ByteTable,A0 ;Get base address of table
MOVE.B (A0,D0),D1 ;Move 1st ascii byte into D1
ASL #8,D1 ;move byte to the proper position
ANDI #$0F,D2 ;get the low nibble
MOVE.B (A0,D2),D0 ;Get 2nd ascii byte
OR.W D0,D1 ;store 2nd byte in word
RTS
ByteTable:
DC.B '0123456789ABCDEF'
IOErr:
MOVE D0,D6 ;get a copy of the error code
CLR.L -(SP) ;room for WindowPtr
CLR.L -(SP) ;allocate storage in heap
PEA ErrorRect ;bounds
PEA 'Title?' ;title..unused
MOVE #$0101,-(SP) ;visible
MOVE #1,-(SP) ;dialog-style window
MOVE.L #-1,-(SP);put it in front
CLR -(SP) ;noGoAway
CLR.L -(SP) ;refcon
_NewWindow
MOVE.L (SP)+,A2 ;save windowPtr
PEA thePort ;VAR thePort
_GetPort ;save current grafport
MOVE.L A2,-(SP) ;make help window current port
_SetPort
MOVE #Chicago,-(SP) ;set font
_TextFont
MOVE #12,-(SP) ;12 point
_TextSize
Center That operation was interrupted by an,158,30
Center I/O error. Make sure that your disk is,158,43
Center not write-protected.,158,56
MOVE.L #158,D4 ;midPoint
MOVE #75,D5;Y coordinate
LEA ErrString,A0 ;stringPtr
MOVE D6,D0 ;get the error code in D0
EXT.L D0;extend to 32 bit precision
MOVE #0,-(SP) ;routine selector for numtostring
_Pack7
MOVE.L A0,A3
BSR CenterText ;display the error code
@1 CLR -(SP) ;wait for a button press
_Button
MOVE (SP)+,D0
BEQ @1
MOVE.L thePort,-(SP);restore the old grafport
_SetPort
MOVE.L A2,-(SP) ;lose the window and free
_DisposWindow ;up it's memory
BRA PurgeBitMap;clean up and exit
CheckMark DC.W 0 ;determine if item is checked
StartPointDC.L 0 ;topLeft of rectangle
EndPointDC.L0 ;bottomRight
NewPointDC.L0,0 ;temp storage of points & rects
BitMapPtr DC.L 0 ;pointer to bitMap mem block
BitMapLen DC.L 0 ;length of bitMap mem block
thePort DC.L0 ;temp storage for grafPtr
WindowRectDC.W 60,78,220,434
ErrorRect DC.W 100,98,185,414
IOParam DCB.W 40,0;IO ParamBlock
SFReply DC.W0 ;Standard file reply record
DC.L '????'
VRef DC.W0
Vers DC.W0
FileNameDCB.B 64,0
APBufferDCB.B 522,0 ;access path buffer
LineBufferDCB.B 44,0;buffer for TEXT line
ErrString DCB.B 8,0 ;storage for string of errorcode
END