Unix pages
Home -> UNIX software -> PSF Tools -> Amstrad Font Redefinition -> REDEFINE.RSX


REDEFINE.RSX is a module that can be attached to PCW programs allowing machine-code programs to redefine the character set. It was originally published in Amstrad Computer User issue 17 (April 1986), on Page XIX of the Amstrad Business Computing supplement.

The program as defined in this article is used as the basis of NEWCHARS.COM, distributed by various Amstrad public domain libraries. It is also used in Werner Cirsovius' PCW character definition article, which defines an expanded version of the sans-serif font used here.




Modified sans-serif font from this article
  • LAWSON.COM: Self-loading font for PCW, CPC, Spectrum +3 and PcW16
  • LAWSON.PSF: Modified font in PSF format
  • DOIT.COM: Original font loader (PCW only)
Modified sans-serif font from NEWCHARS.COM

Other files

  • DOIT.ASM: Source code for original font loader
  • REDEFINE.ASM: Source code for Cliff Lawson's original REDEFINE.RSX
  • REDEFINE.RSX: Original character redefinition RSX (PCW only)
  • REDEFINE.Z80: Source code for a revised REDEFINE.RSX that works on the PCW, CPC and Spectrum +3.


Here is the original article in full:

Cliff Lawson, the man from Amstrad technical, returns to present part 13 in his awe inspiring series of articles on the socio-economic effect of pink jelly on a settlement of lesser spotted wood voles. In fact it's really something to do with redefining the Joyce character set (on screen, not printer, sorry !), but that doesn't sound half as interesting.

Add character to your Joyce

Before we go any further, I think I had better make it clear that the rest of this article is unlikely to take any prisoners. Some of the sentences are of a highly technical nature and should be avoided by anyone scared of the word PEEK.

Much to the protestations of the Editor, I think it has to be said that any normal thinking human being will prefer Sans-serif text to that used for setting this magazine. (Sans-serif means that the ends of the lines of letters don't have ostentatious twiddly bits).

The eagle eyed among you may have noticed that your Joyce also has serifs on some of its characters and, as I don't like them, I figured that something needed to be done. The fruit of my labours is presented here, a simple (???) way to redefine the entire Joyce character set (or just selected characters).

When we sell you a Joyce, there probably aren't many people who realise that we also throw in several hundred pounds worth of software. Locoscript itself must be worth several hundred and in addition to this you get Mallard Basic (£100), GSX (£200ish), Dr Logo (£3.37), and Digital Research Programming tools (SID, ASM, MAC, RMAC, LINK) (another £200ish). So it seems like a good idea to make the most of this. The one drawback of the multiplicity of assemblers that we supply is that they are ail Intel 8080, rather than Zilog Z80 assemblers, but this isn't really a major problem if your head is screwed on the right way (your nose should point in the same direction as you walk).

Mission Possible

Your task, should you choose to accept it, is to type in the two assembler listings shown here. They must be entered into a couple of files on disc and by far the easiest way of doing this is to use RPED. If you have set your CP/M disc so that it has a PROFILE.SUB as described on page 15 of the CP/M section in Book 1, then just type the word RPED. Once the editor has loaded, hit f3 to create a new file. When asked for a name, type in either REDEFINE.ASM or DOIT.ASM, depending on whether you are typing in listing 1 or 2. The spacing in assembler programs is not exactly critical, but it does ease readability if there is a good sized space between each field of an instruction.

When you have typed in the file name, before hitting Return it would be a good idea to remove your system disc and insert a formatted blank that has a bit more space on it.

Putting it all together

Having entered these two files, the fun really starts. They both have to be assembled then linked and finally combined. To do this, you will need to get your (back up of) side 3/4 disc out of his box. Remove the disc onto which you have just saved DOIT and REDEFINE and insert side 3 to the left. Then type:


After a short pause, you will be prompted to insert the disc for drive B:. Remove side 3 and insert the disc with the two files on it. Once this operation has been successfully completed, you should be met by the following:


If anything else appears, then this almost certainly means that you have made a typing error, so go back to RPED to correct the file (DOIT.ASM).

Once the file has been successfully assembled, remove the disc and insert side 3 again. This time, type:


You will be asked to insert the disc for drive A:, just hit a key. You will then be asked to insert the disc for drive B:. Insert the disc with the assembler files and then hit a key. This should produce the following:

ABSOLUTE     0000
CODE SIZE    0251 (0100-0350)
DATA SIZE    0000

So far, so good, now you'll have to repeat the whole process for REDEFINE.ASM. Insert side 3 and type:


Helpful hint: RMAC is on the disc for drive A: (side 3) and REDEFINE is on the disc for drive B: (disc with assembler files). Then type:


This will produce a file called REDEFINE.PRL, which is a page relocatable file containing a CP/M RSX. This must be renamed:


This must be added on to DOIT.COM using:


(GENCOM is on side 3).

If you understood all of that, you should now have a disc containing a file called DOIT.COM. With this disc in the drive, type:


All being well, the character set will now have been changed (for the better?).

If you cast your eye over the file called DOIT.ASM. you will see that the majority of lines in it take the form:

	db 	'?',nn,nn,nn,nn,nn,nn,nn,nn

Where the '?' is the character to be defined (this can also be given as a numeric character number 0.. 255). This is then followed by 8 bytes of bit significant data that define the 8 successive lines of dots that make up a character. Anyone who has ever met user defined symbols of any sort (such as on other Amstrads, Sinclairs and Acorns) will no doubt see the exact method of use for this. Basically, if you draw the character to be defined on an 8 by 8 grid, then convert each line in turn into a binary number, these can then be used in such a definition.

If DOIT.ASM is changed, then it will have to be assembled and linked again (you don't need to do REDEFINE.ASM again). Then the RSX has to be added using GENCOM just as before. Once this is done, the command DOIT will once again redefine the selected characters.

The more enterprising amongst you may be wondering just how this rabbit was pulled out of the hat. (If your not one of these then retire satisfied at this point because the going is about to get really tough).

These programs demonstrate several useful lessons for the would be assembler programmer. Firstly it makes use of a CP/M RSX and therefore demonstrates how they are used.

In CP/M+, any modifications to the operation of the system can very easily be made using RSXs. Til assume you already know what a BDOS call is. An RSX allows one to patch location 5 (the BDOS entry point ) so that your external code can have a first look at the call being made before control is passed onto the original BDOS entry point. The beauty of this system is that these operating system modifications are contained within a relocatable module that can easily be attached to a .COM file so that it is able to make use of new BDOS calls or modified versions of the existing ones that can be used within a number of different driving programs. RSXs attached to a .COM file are loaded on page boundaries from the top of the TPA downwards. This gives a secondary benefit if a piece of code must reside in the common memory of plus (from #C000 to #FFFF), it need not be moved from an address at the lower end of the TPA to a fixed address at the top. Instead, CP/M+ will move it for you to an address that best suits it and will do any relocating needed into the bargain.

You may wonder why a piece of code would want to be in the top of memory in preference to being lower down. Well, the essence of operation of CP/M+ is that one bank (64k chunk) of memory contains the TPA while the rest of the CP/M and screen memory and so on are kept hidden within some other 64k chunk.

To communicate between TPA and screen memory, for instance when a character is to be printed, there must be a smallish piece of the memory shared between all banks so that values can be passed back and forth.

The 64k banks are really made up of four 16k blocks, one of which is common to all banks. There are various possible combinations used for the operation of CP/M. The BDOS bank is made up of blocks 7, 3, 1, 0, the TPA is 7, 6, 5, 4, there is an extra combination of 7, ?, 8, ? and the screen environment is blocks 7, 2, 1, 0. Block 7 (the top 16k of TPA) is common to all these environments. The blocks contain the following:

  1. BIOS extended jumpblock
  2. Screen memory
  3. Matrix RAM, roller RAM and some screen memory
  4. BIOS and BDOS
  5. Bottom 16k of TPA
  6. Next 16k chunk of TPA
  7. Third 16k chunk of TPA
  8. Common - top of TPA plus switching code
  9. CCP, hash tables and data buffers
  10. From within the TPA it is possible to call one of the extended BIOS routines in block 0 by calling the USERF entry in the BIOS. This is achieved by getting the address at location 1 (WBOOT) and adding an offset of 87. This gives the address of the USERF routine.

    This value can then be stored following a 0C3h opcode (JMP instruction) to produce a simple routine that can be called (just like ENTERFIRMWARE in Amstrad CP/M 2.2). The call is followed by an inline address that is the address of the routine to call.

    One particularly interesting routine is at address 00E9h, this is known as SCRRUNROUTINE and will allow the screen environment bank to be switched in, thus allowing access to the screen memory, the roller RAM and the character matrixes. The matrixes are held at 0B800h to OBFFFh and the effect of the program is to update a particular group of eight bytes that constitute one particular character.

    The actual position in this area is given by multiplying the characters value by 8 and adding this to the base address (OB800h). The area of memory from OB600h to 0B7FFh holds what is known as the roller RAM, this contains 256 words each of which in the address of a pixel line on the screen. This can be used for rolling the screen.

    If you didn't understand all that you'll be pleased to know that neither did I. A good look at the example program shown here will probably tell you a whole heap more than a multitude of fatuous waffle.

    REDEFINE.ASM contains the code to implement my RSX. This adds a new BDOS call (73, well why not?). The first 27 bytes at the top of this file will always be pretty similar whenever you want to implement a new RSX.

    The first six bytes are always 0 and are filled when the RSX is loaded. The next three contain a jump instruction to the start of your interception routine. The following couple will be followed by the address of the previous module. This is the address to call if a BDOS function is required within the RSX.

    This is followed by a single byte that should be 0FFh if the RSX should be removed from memory next time the RSX is loaded and 000h if it should remain. An eight byte name will follow this. The loader flag indicates whether or not this is the last RSX in the chain. Finally there are a couple of bytes that are reserved.

    The first thing the RSX code should do is check the value in register C. If this contains the value of the BDOS call to be intercepted the routine is entered, otherwise control is passed to the next: routine.

    In this example the RSX intercepts BDOS call 73. If this is called the nine bytes pointed to by HL are moved into a buffer in common memory. The address of USERF is calculated and stored then used to access the SCRRUNROUTINE.

    This calls a small section of program entitled code: which picks up the character number which is multiplied by 8 and added to the base address of the matrixes. Finally the new definition is moved into place with an LDIR operation accessed by a db in 8080 code.

    Well, my good God, that was interesting wasn't it, I am sure we can all sleep a little sounder in our beds tonight, happy in this new found knowledge. Seriously though if that's possible, the above has shown how values can be moved from CP/Ms TPA into the bank containing the screen memory and so on so there is no longer any excuse for people not to produce some amazing graphic effects on the machine. Yeah, I know you were all just about to do it in GSX anyway.

    wboot		equ	1
    charmat		equ	0b800h
    scrrunroutine	equ	000e9h
    	db	0,0,0,0,0,0
    	jmp	start
    next:	db	0c3h
    	dw	6
    prev:	dw	7
    remov:	db	0ffh
    nbank:	db	0
    	db	'NCHARSET'
    loader:	db	0
    	db	0,0
    	mov	a,c
    	cpi	73
    	jz	begin
    	jmp	next
    	lxi	d,buffer
    	lxi	b,9
    	db	0edh,0b0h ;move parms to high mem before switch
    	lhld	wboot
    	lxi	d,87
    	dad	d
    	shld	cjfirm
    	lxi	h,buffer
    	lxi	b,code
    	call	entfw
    	dw	scrrunroutine
    entfw:	db	0c3h
    cjfirm:	dw	0
    	mov	a,m		;get char number
    	inx	h
    	push	h
    	mov	l,a
    	mvi	h,0
    	dad	h
    	dad	h
    	dad	h
    	lxi	d,charmat
    	dad	d
    	push	h
    	pop	d
    	pop	h
    	lxi	b,8
    	db	0edh,0b0h	;ldir
    	ds	10
    	lhld	1
    	lxi	d,87
    	dad	d
    	shld	hidihi
    	call	efw
    	dw	0c2h		;TE RESET
    	lxi	h,table
    	mvi	b,64		;[BUG] table only has 63 entries
    fred:	mvi	c,73
    	push	h
    	push	b
    	call	5
    	pop	b
    	pop	h
    	lxi	d,9
    	dad	d
    	dcr	b	
    	jnz	fred
    	rst	0
    table:	db	'0',038h,06Ch,0C6h,0C6h,0C6h,06Ch,038h,00
    	db	'1',018h,038h,018h,018h,018h,018h,018h,00
    	db	'2',03Ch,066h,06h,03Ch,060h,060h,07Eh,00
    	db	'3',03Ch,066h,06h,01Ch,06h,066h,03Ch,00
    	db	'4',01Ch,03Ch,06Ch,0CCh,0FEh,0Ch,0Ch,00
    	db	'5',07Eh,060h,060h,07Ch,06h,066h,03Ch,00
    	db	'6',03Ch,066h,060h,07Ch,066h,066h,03Ch,00
    	db	'7',07Eh,06h,06h,0Ch,018h,018h,018h,00
    	db	'8',03Ch,066h,066h,03Ch,066h,066h,03Ch,00
    	db	'9',03Ch,066h,066h,03Eh,06h,066h,03Ch,00
    	db	'A',03Ch,066h,066h,07Eh,066h,066h,066h,00
    	db	'B',07Ch,066h,066h,07Ch,066h,066h,07Ch,00
    	db	'C',03Ch,066h,0C0h,0C0h,0C0h,066h,03Ch,00
    	db	'D',078h,06Ch,066h,066h,066h,06Ch,078h,00
    	db	'E',07Eh,060h,060h,078h,060h,060h,07Eh,00
    	db	'F',07Eh,060h,060h,078h,060h,060h,060h,00
    	db	'G',03Ch,066h,0C0h,0C0h,0CEh,066h,03Ch,00
    	db	'H',066h,066h,066h,07Eh,066h,066h,066h,00
    	db	'I',07Eh,018h,018h,018h,018h,018h,07Eh,00
    	db	'J',0Ch,0Ch,0Ch,0Ch,0CCh,0CCh,078h,00
    	db	'K',066h,066h,06Ch,078h,06Ch,066h,066h,00
    	db	'L',060h,060h,060h,060h,060h,060h,07Eh,00
    	db	'M',06Ch,0FEh,0FEh,0D6h,0D6h,0C6h,0C6h,00
    	db	'N',0C6h,0E6h,0F6h,0DEh,0CEh,0C6h,0C6h,00
    	db	'O',07Ch,0C6h,0C6h,0C6h,0C6h,0C6h,07Ch,00
    	db	'P',07Ch,066h,066h,07Ch,060h,060h,060h,00
    	db	'Q',07Ch,0C6h,0C6h,0C6h,0DAh,0CCh,076h,00
    	db	'R',07Ch,066h,066h,07Ch,06Ch,066h,066h,00
    	db	'S',03Ch,066h,060h,03Ch,06h,066h,03Ch,00
    	db	'T',07Eh,018h,018h,018h,018h,018h,018h,00
    	db	'U',066h,066h,066h,066h,066h,066h,03Ch,00
    	db	'V',066h,066h,066h,066h,066h,03Ch,018h,00
    	db	'W',0C6h,0C6h,0C6h,0D6h,0FEh,0FEh,06Ch,00
    	db	'X',0C6h,06Ch,038h,038h,06Ch,0C6h,0C6h,00
    	db	'Y',066h,066h,066h,03Ch,018h,018h,018h,00
    	db	'Z',0FEh,06h,0Ch,018h,030h,060h,0FEh,00
    	db	'a',00h,00h,078h,0CCh,0CCh,0CCh,076h,00
    	db	'b',060h,060h,07Ch,066h,066h,066h,07Ch,00
    	db	'c',00h,00h,03Ch,066h,060h,066h,03Ch,00
    	db	'd',0Ch,0Ch,07Ch,0CCh,0CCh,0CCh,074h,00
    	db	'e',00h,00h,03Ch,066h,07Eh,060h,03Ch,00
    	db	'f',03Ch,066h,060h,078h,060h,060h,060h,00
    	db	'g',00h,00h,03Ch,066h,066h,03Eh,06h,03Ch
    	db	'h',060h,060h,07ch,066h,066h,066h,066h,00
    	db	'i',018h,00h,018h,018h,018h,018h,018h,00
    	db	'j',06h,00h,06h,06h,06h,066h,066h,03Ch
    	db	'k',060h,060h,066h,06Ch,078h,06Ch,066h,00
    	db	'l',030h,030h,030h,030h,030h,036h,01Ch,00
    	db	'm',00h,00h,06Ch,0FEh,0D6h,0D6h,0C6h,00
    	db	'n',00h,00h,05Ch,066h,066h,066h,066h,00
    	db	'o',00h,00h,03Ch,066h,066h,066h,03Ch,00
    	db	'p',00h,00h,07Ch,066h,066h,07Ch,060h,060h
    	db	'q',00h,00h,07Ch,0CCh,0CCh,07Ch,0Ch,0Eh
    	db	'r',00h,00h,07Ch,066h,060h,060h,060h,00
    	db	's',00h,00h,03Ch,060h,03Ch,06h,07Ch,00
    	db	't',030h,030h,03Ch,030h,030h,036h,01Ch,00
    	db	'u',00h,00h,066h,066h,066h,066h,03Ch,00
    	db	'v',00h,00h,066h,066h,066h,03Ch,018h,00
    	db	'w',00h,00h,0C6h,0D6h,0D6h,0FEh,06Ch,00
    	db	'x',00h,00h,0C6h,06Ch,038h,06Ch,0C6h,00
    	db	'y',00h,00h,066h,066h,066h,03Eh,06h,03Ch
    	db	'z',00h,00h,07Eh,0Ch,018h,030h,07Eh,00
    	db	'#',03ch,066h,060h,0f8h,060h,060h,07eh,00h
    efw:	db	0c3h
    hidihi:	dw	0

John Elliott 2020-12-15