Previous chapter | Index | Appendix I |
Computer magazines and other such publications often print details of 'Machine code programs' and 'Patches' that you can make in order to give your computer system special facilities. For example, you may see a short machine code program that someone has written that will let you 'read' the date and time recorded by the clock built into your computer, or you might see details of a Patch that will let you modify the characters displayed on the screen.
The design of machine code programs and patches requires a lot of detailed technical knowledge about your computer and about the software you are using on it. Such technical detail is outside the scope of this manual: for that sort of information you will need to turn to a book on Machine-code or Assembler programming on the microprocessor in your computer.
However, making use of published programs and patches is really very straightforward and that is what we describe in this chapter.
All the software to run your computer is held in your computer's memory, as an array of individual codes. Each code takes up one byte of computer memory and has its own 'address' so it can be readily found.
When you switch on your computer and load the operating system, all the routines that make up the operating system are read into one area of the computer's memory - usually at the 'top' of the memory (high-numbered addresses). Similarly, when you load BASIC, the software that makes up the BASIC programming language is read into a separate area of memory. Any BASIC program you want to load and the variables this program generates are held in a third area, which BASIC automatically reserves as its working area.
'Patches' either change a piece of information or how a particular routine works, for example causing the program to 'jump to a particular routine. To implement a patch, you have to write new codes at the locations in memory of the information you want to change. The result of this patch will appear automatically whenever the affected code is accessed.
Machine code programs usually contain extra routines that you might want to make use of and to make such a routine available, you generally have to write the codes that make up the routine into an empty area of memory. Normally the easiest place to put these codes is at the top of the area BASIC reserves as its working area (your programs rarely need to use all of this area). Having stored the routine, you then have to arrange that the piece of memory you have used is no longer treated as part of BASIC's working area. (This is in fact essential if you will be calling the routine from BASIC.) To use the routine, you have to specifically direct your computer to execute the codes of the additional routine: to do this, you need to remember the address of the first code in the routine because theis is the entry point into the program.
So to use published patches and machine-code programs, you need to be able to:
Commands that do each of these actions are available within BASIC.
The BASIC command used to patch bytes in memory is the POKE command,
POKE address, new-code
. This writes the new code at the
address given in the command, replacing whatever code was there before. So you just need a
separate POKE command for each byte you want to change.
The address needed by the POKE is the location of the byte: this can be any integer between 0 and 65535. The new code is also expressed as an integer, and the code occupies one byte, this can take any value between 0 and 255.
The addresses and the new codes form the details of the patch; they will probably be written either as decimal numbers or as hexadecimal numbers. Either form is equally acceptable in the POKE command. You just have to copy these details into POKE commands - indeed, they may even be already presented as POKE commands. These POKE commands can then either form a short BASIC 'Set-up' program or be included in another BASIC program.
The following short program illustrates such a short 'Set-up' program. This is in fact the preparation program that users of Amstrad PCW computers need to run before using the routines of GSX, the Graphics Extension to CP/M. It sets the 'jump' to GSX.
100 GSX%=&H30 110 POKE GSX%+0, &H50 'LD D,B 120 POKE GSX%+1, &H59 'LD E,C 130 POKE GSX%+2, &HE 'LD C, 115 140 POKE GSX%+3, 115 150 POKE GSX%+4, &HC3 'JP &H0005 160 POKE GSX%+5, &H5 170 POKE GSX%+6, &H0
Loading a machine code program is very like applying a patch, but with a few extra steps that reserve the area of memory for it. The stages involved are therefore:
The amount of memory required for the routine is defined by the number of codes in the routine. For example the following routine (which will fetch the time when you are running Mallard BASIC on your Spectrum) needs 42 bytes to store it:
&HE5, &HD5, &HC5, &H21, &H00, &H00, &HE5, &HE5 &H39, &HEB, &H0E, &H69, &HCD, &H05, &H00, &HD1 &HD1, &H06, &H03, &H6F, &HE6, &HF0, &H67, &HAD &HCB, &H3C, &H84, &HCB, &H3C, &HCB, &H3C, &H84 &HE1, &H77, &H23, &H36, &H00, &H7A, &H53, &H10 &HEA, &HC9
This memory then needs to be taken off the top of BASIC's working area. To do this, you use
two more of BASIC's commands and functions - the HIMEM function to tell you the current address
of the top byte in this area of memory, and the MEMORY command to move the top of the area to
another address. In fact, you will typically put these together into the one instruction
MEMORY HIMEM-bytes
; so for example, to divide off a section of memory for
the time routine given above, you would use:
MEMORY HIMEM-42
As the whole point of loading an external routine is to use it, the area used to store an
external routine isn't re-absorbed into BASIC's working area at the end of the program. However,
this means that any program that carves off a section of memory from the working area should only
be run once per session - or, if it does need to be run again, the corresponding
MEMORY HIMEM+bytes
should be used first. Otherwise, the working area will
be gradually whittled away because the HIMEM isn't reset to its original value until you load
BASIC again.
Note: When you have more than one external routine or function you want to use, you need to divide off an area from BASIC's working area for each routine. This can be done either in one program or in a number of different programs - for example, one program for each routine. Each of these programs would have its own MEMORY instruction. Of course, the order in which you execute these programs will affect wheter each routine is actually stored in memory and so it is a good idea to keep careful track of this.
The process of loading the routine simply involves executing a series of POKE commands, which write the individual codes of the routine into memory - starting at the byte immediately above the new HIMEM.
Because the routine is to be inserted into consecutive bytes, these POKE commands can readily be handled by a FOR...NEXT loop. For example, the following could be used to load the above Time routine from a number of DATA statements:
100 FOR i = 1 TO 42 110 READ j 120 POKE HIMEM + i, j 130 NEXT 200 DATA &HE5, &HD5, &HC5, &H21, &H00, &H00, &HE5, &HE5 210 DATA &H39, &HEB, &H0E, &H69, &HCD, &H05, &H00, &HD1 220 DATA &HD1, &H06, &H03, &H6F, &HE6, &HF0, &H67, &HAD 230 DATA &HCB, &H3C, &H84, &HCB, &H3C, &HCB, &H3C, &H84 240 DATA &HE1, &H77, &H23, &H36, &H00, &H7A, &H53, &H10 250 DATA &HEA, &HC9
As well as listing the codes, the designer will normally give the total sum of the codes because this provides a simple check of whether you have typed all the codes into your program correctly. This total is known as the checksum.
The checksum for the time routine is 5177. You could put this into your routine as follows:
90 jsum = 0 100 FOR i = 1 TO 42 110 READ j : jsum=jsum+j 120 POKE HIMEM + i, j 130 NEXT 140 IF jsum<>5177 THEN PRINT "Error in DATA": STOP 200 DATA &HE5, &HD5, &HC5, &H21, &H00, &H00, &HE5, &HE5 210 DATA &H39, &HEB, &H0E, &H69, &HCD, &H05, &H00, &HD1 220 DATA &HD1, &H06, &H03, &H6F, &HE6, &HF0, &H67, &HAD 230 DATA &HCB, &H3C, &H84, &HCB, &H3C, &HCB, &H3C, &H84 240 DATA &HE1, &H77, &H23, &H36, &H00, &H7A, &H53, &H10 250 DATA &HEA, &HC9
The way you use a machine code program that you've loaded into memory depends on whether it forms a routine (a package of instructions) or a function (a packaged expression).
To use an external routine, you CALL it, giving the address of the entry point to the routine and the names of any variables you need to pass between the routine and the rest of your program.
The entry point to the routine is usually the address of its first byte, ie.
HIMEM+1
. To pass this address to the CALL command, you first record this value as
a variable and then use this variable in the CALL instruction. For example, you might record the
entry point to the time routine as get.time=HIMEM+1
.
The variables you pass are defined by the routine itself. For example, the time routine requires three integer variables, into which it will put the hour, the minute and the second. These variables can then be used in the rest of your program: for example, you might decide simply to print them out as follows:
300 get.time=HIMEM+1 310 CALL get.time(hour%,min%,secs%) 320 PRINT "Time is now ";USING "##:##:##"; hour%,min%,secs%
To use an external function, you first have to define it but then you can use it just like any other function.
The process of defining it associates the function with one of the 10 names reserved for external functions, USR0...USR9. The command used to do this is DEF USR, and again you need to give the entry point to the routine (the address of the first byte).
Suppose the machine code program you had just poked into memory formed an external function. Then the following instructions could be used to define it as the external function USR0:
300 address=HIMEM+1 310 DEF USR0=addressTo use this function, you might then have some instruction like:
320 result=USR0(value)where
result
is the result of the function USR0
on value
.
The following are examples of BASIC programs which use the techniques described in this chapter. Specifically, they show how you can these techniques to fetch the current date and time by running a Mallard BASIC program under CP/M on your Spectrum.
The first part of each program sets up a machine code routine to call the operating system, asking for details of the date or the time as appropriate: the second part of the program shows the way in which the machine-code routine should be used. The hexadecimal numbers in the DATA statements are the machine code of the routines themselves.
The machine code routines are stored in some of the memory which was initially allocated
to BASIC (reserved by the MEMORY HIMEM-bytes
statements).
The programs are designed to be run just once per session. If you need to run them again, remember to return the area of memory reserved for the machine code to BASIC first, otherwise the memory will be gradually whittled away.
The programs simply use the routines once and print the results. Clearly there are many more useful things that you might do, for example with extra CALLs to the machine code routine. Of course, CALLs must be in exactly the form given, with one DATE or three TIME parameters - all integers.
100 REM 110 REM Fetching the date 120 REM 130 REM Lines 190..260 Set up the machine code required and 140 REM should only be executed once 150 REM Lines 280..500 Give an example of use 160 REM NB: The number returned is the count of days with 170 REM Day 1 being Jan 1st 1978 180 REM 190 MEMORY HIMEM - 20 200 jsum = 0 210 FOR i = 1 TO 20 : READ j : jsum = jsum + j : POKE HIMEM + i, j : NEXT 220 IF jsum <>2465 THEN PRINT "Error in DATA statement" : STOP 230 get.date = HIMEM + 1 240 DATA &HE5, &H21, &H00, &H00, &HE5, &HE5, &H39, &HEB 250 DATA &H0E, &H69, &HCD, &H05, &H00, &HD1, &HE1, &HE1 260 DATA &H73, &H23, &H72, &HC9 270 REM 280 CALL get.date(day%) 290 REM 300 day% = day% + 365 + 366 - 1 : weekday% = day% MOD 7 310 IF weekday% = 0 THEN weekday$ = "Thursday" 320 IF weekday% = 1 THEN weekday$ = "Friday" 330 IF weekday% = 2 THEN weekday$ = "Saturday" 340 IF weekday% = 3 THEN weekday$ = "Sunday" 350 IF weekday% = 4 THEN weekday$ = "Monday" 360 IF weekday% = 5 THEN weekday$ = "Tuesday" 370 IF weekday% = 6 THEN weekday$ = "Wednesday" 380 year% = 1976 + 4 * (day% \ (365+365+365+366)) 390 day% = day% MOD (365+365+365+366) 400 IF day% = (31+29-1) THEN day% = 29 : month$ = "FEB" : GOTO 470 410 IF day% > (31+29-1) THEN day% = day% - 1 420 year% = year% + day% \ 365 430 day% = day% MOD 365 440 READ month.days%, month$ 460 IF day% >= month.days% THEN day% = day% - month.dayS% : GOTO 440 470 PRINT USING"& & ##/&/####";"Today is";weekday$,day%,month$,year% 480 END 490 DATA 31, JAN, 28, FEB, 31, MAR, 30, APR, 31, MAY, 30, JUN 500 DATA 31, JUL, 31, AUG, 30, SEP, 31, OCT, 30, NOV, 31, DEC
100 REM 110 REM Fetching the time 120 REM 130 REM Lines 210..310 Set up the machine code required and 140 REM should only be executed once 150 REM Lines 330..360 Give an example of use 160 REM NB: You must give all parameters in the order given 170 REM even if you only wish to use some of them. You can 180 REM change their names if you wish, but they must all be 190 REM integer variables. 200 REM 210 jsum = 0 220 MEMORY HIMEM-42 230 FOR i = 1 TO 42 240 READ j : jsum=jsum+j 250 POKE HIMEM + i, j 260 NEXT 270 IF jsum<>5177 THEN PRINT "Error in DATA": STOP 280 DATA &HE5, &HD5, &HC5, &H21, &H00, &H00, &HE5, &HE5 290 DATA &H39, &HEB, &H0E, &H69, &HCD, &H05, &H00, &HD1 300 DATA &HD1, &H06, &H03, &H6F, &HE6, &HF0, &H67, &HAD 310 DATA &HCB, &H3C, &H84, &HCB, &H3C, &HCB, &H3C, &H84 320 DATA &HE1, &H77, &H23, &H36, &H00, &H7A, &H53, &H10 330 DATA &HEA, &HC9 320 REM 330 WIDTH 255 340 CALL get.time(hour%,mins%,secs%) 350 PRINT USING "The time is now ##:##:## &";hour%;mins%,secs%;CHR$(13); 360 GOTO 340
Previous chapter | Index | Appendix I |