The road to code
Sean McManus shows you how to integrate machine code with your Amstrad Basic programs
Eventually Basic begins to get claustrophobic and limited. Versatile as it may be, Amstrad Basic is unable to perform all the functions the machine is capable of. Most Basic programmers are driven to using the odd machine code routine in their own programs.
464 Users may have already run the fill routine at the start of this package and all Amstrad users will come up against machine code routines for handling sprites in the next section. Machine code differs from Basic in many ways. Firstly, it's just a string of numbers. Whereas you can list your Basic program and edit it (even though the machine "remembers it" in a code form), machine code cannot be edited as such. A block of memory can be full of numbers which could easily be commands, sprite data or musical notes. The only way the computer knows is by what it is looking for when it stumbles upon the code. If it's looking for commands when it runs into the music data, it will execute it anyway.
Here is another problem with machine code. It has no error messages. When things go wrong, there's no return. The screen freaks, the machine freezes. Whatever happens, it usually sentences the machine to a reset, wiping the contents of memory. Using machine code routines, you need to be especially careful. RSXs are extra commands for Basic, preceded by the bar character. Mistyping the name of one of these will often freeze the machine. Likewise, if you pass silly parameters to them or stray from the correct usage you rely heavily on the programmer having error trapped extensively. This is rarely the case, since it can double the length of the code. Initialising the same RSXs twice can crash the machine, so be careful to only run an RSX loader once. RSXs are handled slightly differently on the 464 to later machines, as shown in the chapter on downgrading.
HIMEM is a system variable which marks the top of the amount of memory allocated to Basic (HIgh-MEMory). The MEMORY command is used to set HIMEM. To prevent Basic programs overwriting machine code routines, HIMEM is usually lowered to just below where the first machine code routine begins. If, for example, your machine code is poked into locations 40000 onwards, then MEMORY 39999 would prevent Basic stomping all over it. Due to an unexpected feature (i.e. bug) in Basic, the MEMORY command plays havoc with the SYMBOL AFTER command. SYMBOL AFTER is used to define how much of the character set is to be set aside for the user to redefine. The solution to this predicament is to precede the MEMORY command with SYMBOL AFTER 256:
SYMBOL AFTER 256:MEMORY 39999
After this, SYMBOL AFTER can be issued to the correct value as necessary.
You can specify where in memory you want to load machine code programs or data by putting the location after the filename, separated by a comma: LOAD"sprites.bnk",40000 will load the file "sprites.bnk" into memory, starting at memory location 40000. If the machine complains, try lowering HIMEM to just below where you are trying to load. In this case, we would use MEMORY 39999. Basic will only let you load a machine code file above HIMEM.
Compressing Basic programs
As HIMEM falls and the amount of memory allocated to Basic falls, it begins to feel a lot less spacious than it used to. With memory allocation at its default, you can usually get away with up to about 38k if the program is very lean on variables. If your program is anywhere near this length, you should consider multi-loading using the CHAIN command to run a program without clearing out the current variable settings. Alternatively, if it is a game you could look at the possibility of bridging sections using passwords between parts. With machine code routines in memory, the amount of space allocated to Basic can plummet and relatively small programs can benefit from compression. Here is a list of tips on how programs can be made to tip-toe around "Memory Full" crashes more daintily:
- If you don't use any user defined characters, issue a SYMBOL AFTER 256 to reclaim the memory. This command, like any others which only need be issued once, can be put in a loader program.
- Join lines together wherever possible.
This reduces the number of lines and saves a few bytes per line number that no longer exists.
Be careful not to join onto the end of an IF-THEN or similar statement and make sure that vital lines are not removed.
Vital lines include the start of subroutines.
As a guideline, if you keep all your subroutines in a block at the end, each one will be preceded by the RETURN command that closed the previous one.
- Delete all REM statements.
There is a program on the disc which will take the labour out of the process.
REM Stripper requires the name of the file to be stripped and the name of the file it is to be saved to.
Before it can work, the program to be stripped must be saved in ASCII format using SAVE"filename",A.
It is best to use a different filename for the ASCII version and leave the original untouched.
(Ascii files can be loaded into a word processor if you find editting in Basic clumsy, but need to be saved again before they can be run and tested).
REM stripper will not remove ' statements and you should make sure that none of your REMs are critical for the start of subroutines.
If they are, replace those with apostrophe statements.
- Use the CLEAR command to wipe out variables when all the previous values no longer matter.
For example, after drawing the title screen.
If you need to keep just one, poke its value into a memory location (location zero is okay for just the one variable: POKE 0,variable) and after the CLEAR, recover it with variable=PEEK(0).
- Keep the number of variables to a minimum.
I always use g and h for temporary variables whose value doesn't really matter once the loops they govern are finished.
These two grew on me because they live in the middle of the keyboard, but you will have your own preference.
Re-use variables as much as possible.
- Use subroutines where lines are frequently repeated.
- RENUM 1,1,1
You can check your progress by using PRINT FRE(0) to tell you how many free bytes there are between the end of your program and the top of memory. Arrays seriously eat space, so use them sparingly and ERASE them once their role is ended.
Relocating machine code programs
Because machine code programs are just lists of numbers, there is no equivalent of the renumber command. This causes problems when you want to use two different programs at the same time which both occupy the same memory locations. In most cases if you just load it into a lower memory location, a program will not work. All its "GOTO" commands (which are vital in machine code because they govern looping and conditional operations) will still be pointing to the old locations. There is a program on this disc which aims to try to solve this. Code Relocator will look at the code and when it finds a "GOTO" or "GOSUB" command will relocate where it points to if necessary. It expects to load the chunk of memory from tape/disc and to be told its start address, length and the address it is to be relocated to. (The word address is used to refer to the number of a memory location). Since the numbers could be anything, it will relocate graphics and sound data if it stumbles across it. It cannot handle self modifying code. This means it will not work for routines that use a score table or RSXs. It's a little hit-and-miss for more complex programs, but is worth trying. If the first program will not relocate, try the one it is conflicting with.
When you begin to use the sprite definer in the next section, you will be creating blocks of memory containing sprites.
These can be a little bit difficult to manage in Basic programs.
They can be saved using
where start is the start address and length is the length in bytes. Sprites are tiny in terms of memory occupied. It seems a bit silly to have forty files of sprites containing one sprite each because Basic pads each file to the nearest kilobyte. Putting machine code routines as close together as possible and saving as one file is always a good idea. The Sprite Definer package manages this itself.
Listings in magazines and books usually have the machine code program and data listed as numbers in DATA statements. These numbers are then read and poked into the right place, instead of needing to be loaded from disc. The Data Maker program will create a Basic loader for any sprite data or machine code programs you later write and will save it as a Basic program onto disc or tape.
The joy of hex
As you begin to use machine code routines, you will come into contact with hexadecimal. People count in blocks of ten, probably because it's how many fingers and toes we have. Computers count in units of eight because that's how many zeroes or ones make a byte. Hexadecimal is just a different way of representing the same numbers, which has grown from the computer world's obsession with multiples of eight. Basic precedes hex numbers with a & sign. To find the hex equivalent of a "normal" number, use PRINT HEX$(number). To convert it back again, use PRINT &hex version.
PRINT HEX$(12) gives the result C
PRINT &C gives the result 12.
As is clear from this example, hexadecimal uses letters as well as numbers: the letters A-F in fact. &E is fourteen, &F is fifteen, &10 is sixteen, &11 is seventeen. What our usual number system knows as the tens column is the sixteens column in hexadecimal. It takes a little getting used to, but in the context of Basic is rarely more difficult than using the program's instructions.
Pokes for games can sometimes be intercepted and used to get inside the game for a look around. The CALL command is used to start executing a machine code routine (always double check the number used), so removing this from a basic program will stop the machine code program being started once it has been loaded. Pokes which have to contend with complex fast loaders can also sometimes be intercepted. C3 is the code for JumP, which is the same as Basic GOTO and CD is the code for CALL (in machine code, a bit like GOSUB). These can be used to start execution of the game. The two hexadecimal numbers after the CD or C3 represent the address that execution begins from. It is calculated by address=(first number)+256*(second number). This is a good place to start looking around. To stop the program being run and the routine returning to Basic after loading, try replacing the CD or C3 with C9. This is a RETURN instruction and should return you to Basic. It is all highly dependent on the individual case, so you might have to try a couple of the CDs or C3s before it works.
CALL is a command with attitude.
It makes it child's play to totally crash the machine if its parameter is mistyped.
It is very difficult to cause any harm that will outlive a reset but this wastes anything in memory you had grown emotionally attached to.
There are some relatively useful CALLs which can be used from Basic quite easily:
- CALL 0 - Resets the machine
- CALL &BC02 - Resets the inks to their default
- CALL &BBFF - Resets the screen parameters
- CALL &BB4E - Resets the paper, pen, opaque, windows and TAGOFF.
- CALL &BBBA - Resets the graphics window, pens, papers, origin.
- CALL &BB00 - Resets all the keyboard parameters
- CALL &BB03 - Clears the keyboard buffer
- CALL &BB06 - Waits for a keypress. Press any key to continue type usage.
- CALL &BC65 - Resets the cassette manager
- CALL &BC6E - Starts the cassette motor (highly useful, I'm sure)
- CALL &BC71 - Stops the motor again
- CALL &BCA7 - Resets the sound manager, leaving envelopes intact.