The Multiface Two is a multi-purpose expansion devices produced by Romantic Robot UK Ltd. in 1988. It has two push button: RESET and STOP. The RESET button simply reset the CPC/Plus at any time whatever the program running is. The STOP button is much more interesting, it will stop the execution of the current program and run instead the built-in MF2 toolkit (or a third party custom program).
The built-in toolkit allow you to view and modify the contents of memory and hardware registers (CRTC, Gate Array, CPU, …), save a screenshots or a complete snapshot of the CPC to tape/disc and finally, return to the program just like nothing happened.
The snapshot feature is obviously what most people were after, it made possible to save your position in a game and continue playing it later (and, of course, to make “backup copy” for your friends :). It was also widely used to cheat in games by modifying the game program in memory to get infinite energy/life/whatever.
Further informations about the Multiface Two can be found on these websites:
|
|
The Multiface Two plugs in the expansion port of the Amstrad and was manufactured with a double-sided edge connector (for the Amstrad CPC) or Centronic connector (for the Amstrad Plus). Some resellers (eg. Jessico) modded themselves the MF2 with a Centronic connector so they could sell it to CPC owners with Centronic connectors (eg. Schneider CPC). The MF2 also have a thru-port, so that additional devices can be plugged into the back of the MF2. This thru-port is a double sided edge connector.
The Multiface Two has 8Kb of EPROM (where is stored the MF2 Firmware and toolkit), 8Kb of RAM and 3 custom chips (PAL) mainly programmed to monitor the I/O operations of the CPU and record them in the MF2 RAM. There are also two push-buttons, STOP and RESET. The first MF2 design had a physical toggle switch to make the device visible or invisible but the latest versions automatically handle the stealth mechanism with the RESET/STOP push-buttons.
To STOP any program, at any time, with the STOP button, here is what the Multiface Two does:
A Non-Maskable Interruption (NMI) is a high-priority interrupt which can not be ignored by the CPU (eg. like the 300Hz interrupt produced by the Gate Array). When the CPU detects an active NMI signal, it will save it's current interrupt status (the IFF flags), push it's PC onto the stack and jump to the NMI trap at &0066. Since the MF2 paged it's ROM from &0000 to &1FFF, the CPU will jump right into it and execute the Firmware boot-routine of the MF2.
If a Direct Jump program is installed in the MF2 (such as The Insider), it will be executed right after the MF2 firmware-boot, otherwise the built-in toolkit will appear as usual. Note that to disable a direct jump program, just press any key while pressing the STOP button.
The Multiface Two can RESET the Amstrad at any time. It's useful with programs that can not be exited (eg. with CTRL+SHIFT+ESC
). Using the RESET button also resets the stealth mechanism of the MF2 to visible mode.
Romantic Robot used various colors for the RESET push-button (white, yellow, green, blue) but the STOP button is always the red one.
The snapshot feature of the Multiface Two obviously pissed off many software companies, thus they started implementing counter-measures to prevent their games from running on a CPC/Plus with such evil device. This was highly ineffective as the MF2 featured a stealth mechanism to make the device invisible.
When you switch ON your Amstrad or press the RESET button, the MF2 is in visible mode. Anything requiring the MF2 will work (such as running MF2 snapshots or third party softwares like The Insider). The MF2 ROM/RAM bank can be accessed by the CPU at any time.
To switch to the invisible mode, you just have to press the STOP button and simply return (press R in the toolkit menu) to the main program. The MF2 ROM/RAM bank is not available anymore. Since most detection routines rely on the MF2 ROM/RAM detection, this does the trick. However there's other means to detect an MF2 or undermine it's features…
The screenshot feature of the Multiface Two allow you to take a screen capture of a running program at any time. The screenshot can be reloaded from BASIC and doesn't require any MF2 to be displayed.
The screenshot file is &408C bytes long: The first &4000 bytes hold the uncompressed screen-data and the last &8C bytes hold a small routine to copy the screen-data in the usual video RAM address (&C000-&FFFF), setup screen mode, palette and CRTC configuration (the loader disassembly is shown below).
You can reload a screenshot file from BASIC as follow:
MEMORY &3FFF:LOAD"SCREEN":CALL &8000
The default load address is &4000. The embedded routine at &8000 will copy the screenshot-data to &C000 (which is, with the default firmware configuration, where is located the video-RAM), setup the video mode, palette and CRTC configuration.
There are several third-party tools to convert MF2 screenshots into OCP Art Studio file-format (.SCR and .PAL).
Here are a few of them:
You can also do it very easily from BASIC:
MEMORY &3FFF:LOAD"SCREEN":SAVE"SCREEN.SCR",B,&4000,&4000
This will save the 16Kb screen-datas into a .SCR file that can be loaded with OCP Art Studio (but you will have to restore the palette manually with OCP, whereas most of the tools listed above will also a create the corresponding .PAL along with the .SCR file).
Here is a disassembly of the MF2 screenshot loader.
org &4000 _mf2_data_screenshot_bitmap ; Here are the 16Kb screenshot data org &8000 mf2_screenshot_display: ; Disable the interrupts di ; Copy the screen-data to the usual ; video RAM address (&C000). ld hl,_mf2_data_screenshot_bitmap ld de,&c000 push hl pop bc ldir ; Overwrite the current firmware palettes with ; the screenshot palette. Note that the firmware ; has two set of colors to manage blinking ink. ; ; Both sets will be overwritten with the same ; palette from the screenshot (no blinking colors) ld hl,_mf2_data_screenshot_palette push hl ld bc,17 push bc ; Get the firmware palette entry ld de,(_mf2_data_firmware_palette) ; overwrite the first ink set ldir pop bc pop hl ; overwrite the second ink set ldir ; Right after the palette data, are stored ; the CRTC registers data. ; Update the CRTC with these register values. ex de,hl call _mf2_screenshot_setCRTC ; Set the RMR register to the saved value ld b,&7f ex de,hl ld c,(hl) out (c),c ; Filter the saved value to keep only the ; video-mode bits 0-1 ld a,c and &03 ; Update the CPU register BC' used by the firmware ; to store the current RMR configuration. exx ; clear the previous video-mode bits res 0,c res 1,c ; merge with the filtered value or c ld c,a exx ; That's it, restore the interrupts and exit. ei ret ; The firmware will automagically update the ; video mode and palette on the next VSync interrupt. _mf2_screenshot_setCRTC: ; Setup the 16 CRTC Registers ld hl,&bcbd ld b,&10 jr _mf2_out _mf2_screenshot_setPalette: ; Unused routine entry. ld hl,&7f7f _mf2_out: ld c,&00 _mf2_out_loop push bc ld b,h out (c),c ld a,(de) inc de ; detect Gate Array or CRTC I/O access ld b,l rl b ld b,l ; If CRTC is accessed, do not filter the value jr c,_mf2_out_raw ; Filter the 8bits value sent to the Gate Array ; to set a valid INKR command bit-code. and &1f or &40 _mf2_out_raw out (c),a pop bc inc c djnz _mf2_out_loop ret ; *** Unused by the screenshot routine! **************************************** _mf2_data_firmware_crtc ; CRTC Reg0 to Reg15 with the default firmware ; configuration (40*25). db &3f,&28,&2e,&8e,&26,&00,&19,&1e db &00,&07,&00,&00,&30,&00,&c0,&00 ; *** PALETTE ****************************************************************** ; Hardware color for the screenshot, stored as follow ; BORDER, PEN0, ..., PEN15 _mf2_data_screenshot_palette ds 17,0 ; *** CRTC Configuration ******************************************************* ; CRTC Reg0 to Reg15 as set when the screenshot was taken. _mf2_data_screenshot_crtc ds 16,0 ; *** FIRMWARE PALETTE ENTRY *************************************************** ; This address will vary depending on the machine the screenshot was taken on. ; Firmware v1.0 (CPC464) = &B1D9 ; Firmware v2.0 and later = &B7D4 _mf2_data_firmware_palette dw 0 ; *** SCREENMODE *************************************************************** _mf2_data_screenshot_rmr db 0
A Multiface Two snapshot is a complete dump of the Amstrad's memory and register state saved to tape or disc for later use (just like the snapshot feature of emulators). To achieve this, the Multiface Two is continually monitoring the CPU I/O operations to record all changes made to I/O devices registers (eg. CRTC, PPI, Gate Array, …).
To reload a snapshot, the Multiface Two must be visible since the loader routine is mostly located in it's ROM.
RUN"SNAPSHOT
That's it, a small boot-code is loaded. It will copy the hardware context (all CPU, CRTC, etc registers state when the program was stopped) into the MF2 RAM and call the snapshot loader routine located in the MF2 ROM.
(might be done later, anyone care?)
org &AF80 ; Disable the interrupt di ; Enable the lower ROM mapping exx ld bc,&7F89 out (c),c exx ; Enable the MF2 memory mapping ld bc,&FEE8 out (c),c ; Copy the hardware registers dump ; into the MF2 RAM. ld hl,_mf2_data_snapshot ld de,&3A0F ld bc,&00FA ldir ; Copy the Z80 registers dump into ; the MF2 RAM. ld de,&3EE6 ld bc,&001A ldir ; Call the Snapshot loader in the MF2 ROM. jp &002B _mf2_data_snapshot
The Multiface Two has a built-in routine to detect 128Kb machines (in order to save their extra 64Kb RAM with the snapshot feature). It is executed every time the STOP button is pressed (during the MF2 firmware boot process). This RAM detection routine has a bug on 128Kb+ RAM machines (and only 128Kb+ machines! 64Kb machines are not affected) which makes possible to detect if the MF2 stopped your program or not (it is a post-STOP detection).
The routine performs two tests, the bug is lying in the later. On 128Kb machine, when you press the STOP button, a byte in the extra RAM (in bank 0 of page 0 at &4000) will be overwritten with the value &55 if it's original value was &AA. If the program being stopped is monitoring this particular byte in extra RAM, it can detect if it was stopped or not!
Here is a commented disassembly of the RAM detection routine used by the MF2:
; Disassembly of the Multiface Two RAM detection routine. org &02C5 mf2_ramCheck: ; Page out any extended RAM banks call _mf2_mmr_reset ; Save the current byte at &4000 in the base64k RAM ld hl,&4000 ld a,(hl) push af ; *** Test 1 ******************************************************************* ; Put a marker (&AA) at &4000 in the base 64K RAM ld (hl),&AA ; Try to page in the extra bank 0 (from &4000 to &7FFF) ld bc,&7fc4 out (c),c ; Compare the value now at &4000 with the marker's value ld a,(hl) cp &AA ; If the values don't match, assume there's 128k jr nz,_mf2_ramCheck_set128k ; Since the value at &4000 in the extra bank was ; not initialized, it might matches the marker. ; That would be unfortunate, therefore a second ; test must be done to make sure there's definitely ; no extra RAM available. ; *** Test 2 ******************************************************************* ; *************** EPIC FAIL ******************** ; Put a marker (&55) at &4000 in the extra bank. ; And here's the flaw! The original value is not ; saved but directly over-written! ld (hl),&55 ; ********************************************** ; Disable any RAM mapping call _mf2_mmr_reset ; Compare the value at &4000 with the marker's value ld a,(hl) cp &55 ; If the values match, there's definitely no extra RAM. jr z,_mf2_ramCheck_set64k ; Otherwise, the MF2 assume there's 128k RAM ; *** Set result and exit ****************************************************** _mf2_ramCheck_set128k ; Page out any extra bank call _mf2_mmr_reset ; Set the 128k code-result (2) ld a,2 _mf2_ramCheck_exit ; Save the code-result ld (&3a96),a ; Restore the value at &4000 in the base 64k RAM pop af ld (hl),a ret ; Set the 64k code-result _mf2_ramCheck_set64k xor a jr _mf2_ramCheck_exit ; Disable all RAM mapping ; Output ; B=&7F ; MMR register (if available) set to &C0 ; All other registers preserved. _mf2_mmr_reset: push af ld a,&c0 ld b,&7f out (c),a pop af ret
The Multiface Two monitors the I/O operations of the CPU and records all changes made to the hardware registers (CRTC, Gate Array, PPI, etc). But there's a flaw in this monitoring system, it does a complete I/O decoding of the upper 8bits of the I/O address instead of a partial I/O decoding like the devices in the CPC/Plus do.
Therefore it is possible to cheat the MF2 by using ghost I/O addresses to access the devices (CRTC, Gate Array, PPI, etc) furtively, thus undermining the snapshot and screenshot features.
From the BASIC interpreter, type and RUN the following program:
10 OUT &BC00,1 ; Select CRTC register 1 20 OUT &BD00,0 ; Set CRTC Reg. 1 to 0 (blank the screen with BORDER everywhere) 30 OUT &0D00,40 ; Set CRTC Reg. 1 to 40 (standard screen width) using ghost I/O address.
Then press the STOP button and Return to the BASIC (press R in the toolkit main menu). The screen is now totally blank!
This is because the MF2 registered that the CRTC Register 1 was set to 0 but missed it was set back to 40 with a ghost I/O address (&0D00
), therefore it restored 0 when you pressed R. We could also use a ghost address to select the register, so the MF2 couldn't even tell which register you modified!
And here we have a winner! This is probably the most awful flaw of the Multiface Two (just my opinion). When the ASIC I/O page is enabled and you press the STOP button (or call manually the MF2 Firmware), it will crash the machine!
In order to display the toolkit on the screen, the MF2 uses the font stored in the Amstrad Firmware which is located in the Lower ROM. On a CPC, the Lower ROM is usually paged from &0000 to &3FFF. But the MF2 ROM/RAM is also paged from &0000 to &3FFF and overlay the Lower ROM, thus the Amstrad Firmware is simply not accessible to the CPU when the MF2 ROM/RAM is paged in.
To circumvent this problem, every-time the MF2 toolkit wants to read the firmware font, a small routine is copied to the base64Kb RAM at &4000 and called. The aim of this routine is to page out the MF2 ROM/RAM (so the Firmware in the Lower ROM is accessible for reading), fetch the font data, page the MF2 ROM/RAM back in and exits.
What's wrong with that? (besides an obvious speed issue) The ASIC is paged from &4000 to &7FFF! When the MF2 is copying the above mentioned routine at &4000, it will in fact copy it in the ASIC I/O page, more precisely, right into the hardware sprite data area. And that, is the problem. This area of the ASIC I/O page only stores the lower 4bits of any bytes written to it (the upper 4bits are automatically cleared to zero). When the CPU will read the opcodes written there to execute the routine, it will just get garbage. That's an epic failure! Besides corrupting the hardware sprite data, the machine will crash.
Here's what the routine should be and what the CPU can decode when it jump to it:
MF2 Routine in RAMAddress Opcodes Instruction &4000 ED 49 out (c),c &4002 06 08 ld b,#08 &4004 4E ld c,(hl) &4005 23 inc hl &4006 E5 push hl &4007 C5 push bc &4008 06 04 ld b,#04 &400A 21 04 00 ld hl,&4032 &400D AF xor a &400E CB 01 rlc c &4010 30 01 jr nc,&4013 &4012 B6 or (hl) &4013 23 inc hl &4014 10 F8 djnz &400e &4016 12 ld (de),a &4017 13 inc de &4018 06 04 ld b,#04 &401A 21 04 00 ld hl,&4032 &401D AF xor a &401E CB 01 rlc c &4020 30 01 jr nc,&4023 &4022 B6 or (hl) &4023 23 inc hl &4024 10 F8 djnz &401e &4026 12 ld (de),a &4027 13 inc de &4028 C1 pop bc &4029 E1 pop hl &402A 10 D8 djnz &4004 &402C D9 exx &402D ED 49 out (c),c &402F C3 91 07 jp #0791 |
MF2 routine in the sprite RAMAddress Opcodes Instruction &4000 0D dec c &4001 09 add hl,bc &4002 06 08 ld b,&08 &4004 0E 03 ld c,&03 &4006 05 dec b &4007 05 dec b &4008 06 04 ld b,&04 &400A 01 02 00 ld bc,&0002 &400D 0F rrca &400E 0B dec bc &400F 01 00 01 ld bc,&0100 &4012 06 03 ld b,&03 &4014 00 nop &4015 08 ex af,af &4016 02 ld (bc),a &4017 03 inc bc &4018 06 04 ld b,&04 ; And I cut the crap here, you get the idea... ; All the upper four bits of the opcodes are ; set to zero... FAIL! |
For a ~80€ device claiming to be Plus compatible, well… that sucks. (just my opinion, again :)
The MF2 Firmware is 8Kb long and located into a socketed EPROM inside the interface. It is not known how many different MF2 Firmware versions exist and the differences between the known MF2 Firmware are not yet documented, except for their AMSDOS compatibility:
All call addresses to the AMSDOS ROM are hard-coded into the MF2 Firmware. There are two AMSDOS versions (v0.5 on CPC and v0.7 on Plus), therefore two major MF2 Firmware versions also exist. If the MF2 Firmware doesn't match with the AMSDOS version installed in the CPC or Plus, all disc operations of the MF2 will crash the machine.
You can check your MF2 firmware version by pressing f0
(numeric pad) in the toolkit main menu, a two digits hexadecimal number will be displayed on the screen. This version number corresponds to a simple checksum of the MF2 ROM. The checksum routine can be found at the address &1B7D
in the MF2 ROM:
org &1b7d ; MF2 Checksum routine ld hl,&2000 xor a ex af,af mf2_checksum ex af,af dec hl xor (hl) ex af,af ld a,l or h jr nz,mf2_checksum ex af,af ; Output: ; A = version number
For Amstrad CPC (AMSDOS v0.5) |
For Amstrad Plus (AMSDOS v0.7) |
Notes:
&8D
was found on an early MF2 using a toggle-switch for the stealth mode.&78
are the perfect example of a checksum collision. They both report the same version number but they actually require a different AMSDOS version for their disk operations!Thanks to SyX and CPCManiaco for the bunch of MF2 ROM dumps!
RUN”MF2DUMP”
The program will detect the Multiface Two and dump it's ROM to disc (as MF2.ROM
file). You just have to send me this file (or directly a DSK dump of your disc with the MF2.ROM
file in it).
For a very very long time, I've heard of many (French-)people to believe that the 8Kb EPROM (which contains the MF2 Firmware) is encrypted, thus it can't be replaced or rewritten with your own stuff. This is a myth. You can do whatever you want with the EPROM on the MF2 as you would do with a regular EPROM!
It was also said that bit 7 of the MF2 Firmware version indicated which AMSDOS version the MF2 was using for all it's disc operations. This is not true. To determine the AMSDOS version required for a specific MF2 Firmware version, refer to the download list above. Thanks to NoCash for exposing that!
The screenshot feature of the MF2 directly overwrites the Amstrad Firmware ink buffers to restore the colors of the picture. However, these buffers are not located at the same addresses depending on the Amstrad Firmware version. Before saving the screenshot, the MF2 determines which firmware version is available to select the appropriate addresses to use for the ink buffers and save them into the screenshot file. When the screenshot is later reloaded, it doesn't check anything and uses the saved addresses directly. That's why a MF2 screenshot saved on a Firmware v1 (ie. CPC 464) does not work properly when used on later Amstrad Firmware.
This problem can be easily fixed by modifying two bytes in the screenshot loader located at the label _mf2_data_firmware_palette
:
10 REM Load a screenshot and fix it for 20 REM a v2 or greater Amstrad Firmware 30 MEMORY &3FFF 40 LOAD "mf2screen.bin",&4000 50 REM Set Palette entry to &B7D4 60 POKE &8089,&B7:POKE &8088,&D4 70 CALL &8000
As already said, two major versions of the MF2 Firmware exist, one for the Amstrad CPC and one for the Amstrad Plus. To perform it's disc operations, the MF2 Firmware jumps directly into the DOS ROM (Upper ROM 7). This, usually, is considered bad programming practice because of possible compatibility problems. And that exactly what happened with the Amstrad Plus (released much later than the MF2), which has a different (updated) AmsDOS ROM, many addresses to the disc routines have changed… bummer!
Doing the right things™, ie. rewriting complete DOS routines into the MF2 firmware to not depend on third party software, would have taken much more work and also a bigger EPROM to fit it all. The interface was already quite expansive at the time, so dealing with compatibility problems, ie. updating all the DOS call addresses in the MF2 ROM, was probably cheaper and faster.
The Multiface Two, designed in 1986 (ie. loooong before the Amstrad Plus was released), is really a neat and cleverly made interface for the Amstrad CPC. When the Plus range came out, Romantic Robot released an Amstrad Plus compatible version of it, but they just recompiled the MF2 Firmware for the AmsDOS v0.7 and… well, that's it! The whole interface design remains solely targeted for the Amstrad CPC.
The MF2 Toolkit assumes an English keyboard layout, therefore some keys on a French keyboard don't match (key A produces a Q, etc). Not a big deal, but still annoying when you're used to the French AZERTY thing.
Here is a non exhaustive list of software for the Multiface Two:
The Multiface Two has two I/O ports to bank switch it's ROM/RAM.
Like any regular I/O device, the Multiface Two monitors the CPU I/O requests. To do that, the MF2 performs a bit-wise decoding of the I/O address to detect if an I/O request hits one of it's I/O ports. It should be noted that most devices only check for a few bits (only one in most cases) of the 16bits I/O address whereas the MF2 checks for almost all of them!
MF2 I/O Port | I/O Address mask | I/O R/W |
---|---|---|
&FEE8: Enable ROM/RAM | 11111110 1110100x | W |
&FEEA: Disable ROM/RAM | 11111110 1110110x | W |
x
indicate a bit ignored by the I/O decoding.0
indicate the bit must be clear to select the device.1
indicate the bit must be set to select the device.W
indicate the device will respond only to an I/O Write operation.
The Multiface Two uses the Lower ROM mechanism of the CPC to show it's ROM/RAM bank from &0000
to &3FFF
. Therefore, you must clear bit 2 of the RMR register to enable the Lower ROM and also tell the MF2 to page it's ROM/RAM using it's dedicated I/O port (&FFE8
).
When it's done, the MF2 ROM will be paged from &0000
to &1FFF
(8Kb) and it's RAM from &2000
to &3FFF
(8Kb).
&0038
, therefore you should disable the interrupts before paging the MF2 if your program is running in IM1.
On the Amstrad Plus, for some unknown reasons, the MF2 ROM/RAM can be paged directly using it's I/O ports, it is not required to enable the Lower ROM with the RMR register.
It is not recommended to modify directly the RMR register when the Amstrad Firmware is running (it might crash the machine). The Firmware provides a complete API to deal with ROMs and we just have to use it.
; Accessing the MF2 ROM/RAM ; in a Firmware environment ; The two Firmware routines we need _KL_LROM_ENABLE equ &B906 _KL_ROM_RESTORE equ &B90C ; For obvious reasons, your program must be located ; outside the address range &0000-&3FFF! org &4000 ; enable the lower ROM call _KL_LROM_ENABLE ; Save the previous ROM state for later push af ; Ignore interrupts, so we can safely ; page the MF2 ROM/RAM di ; Enable the MF2 ROM/RAM from &0000-&3FFF ld bc,&FEE8 out (c),c ; that's it, now you can do whatever your want ; with the MF2 ROM/RAM here! ; The MF2 ROM is available from &0000 to &1FFF ; The MF2 RAM is available from &2000 to &3FFF ; And now we exit properly ; Disable the MF2 ROM/RAM ld bc,&FEEA out (c),c ; Acknowledge Interrupts ei ; Restore the ROM state as it was pop af call _KL_ROM_RESTORE ; We're done ret
Instead of using the Firmware API to control the Lower ROM, you could also hack the CPU register (BC
') reserved for the Firmware too, but this is beyond the scope of this article :)
If the Amstrad Firmware is not available, then you can bang directly onto the hardware:
; Ignore the interrupts di ; Configure the RMR Register ; - Disable Upper ROM ; - Enable Lower ROM ; - Select video mode 2 ld bc,&7F00+%10001010 out (c),c ; Enable the MF2 ROM/RAM ld bc,&FEE8 out (c),c ; The MF2 ROM is available from &0000 to &1FFF ; The MF2 RAM is available from &2000 to &3FFF ; Disable the MF2 ROM/RAM ld bc,&FEEA out (c),c ; Configure the RMR Register ; - Disable Upper ROM ; - Disable Lower ROM ; - Select video mode 2 ld bc,&7F00+%10001110 out (c),c ; Acknowledge Interrupts ei
There are several methods to detect a Multiface Two depending on what you want to achieve. A simple ROM/RAM paging detection is the most appropriate method when you just need to use the features of the MF2. A STOP button detection is most likely used when you want to protect your software from the MF2 (in this case, it should be just one part of a bigger protection scheme, otherwise it won't take long to crack and that would be spoiling the fun! :).
In case of a software protection, you should not poorly crash/stop your program right after it detected it has been stopped. This is like shouting at the (lame cart-)cracker he still have some stuff to hack in your program! Thus you're helping this lousy bastard! Instead, silently hack your own program to spoil its fun (eg. in a game, enemies get harder to kill, level bosses are invulnerable, progressively slow-down the frame-rate of the game, don't save the hi-scores, or whatever… be creative! But more importantly, be evil! :).
The easiest one is simply to check if the MF2 ROM/RAM can be paged from &0000 to &3FFF. If it can't, there is no MF2 or it's stealth mechanism is activated.
def_mf2_io_enable equ &FEE8 def_mf2_io_disable equ &FEEA ; Usage example call mf2_detect jr z,no_mf2 ; The MF2 ROM/RAM is available ; Do your things accordingly here. ret no_mf2 ; The MF2 ROM/RAM is not available. ; There is no MF2 or it's stealth mechanism is activated. ; Do your thing accordingly here. ret ; Detect if the MF2 ROM/RAM is available ; This routine MUST NOT be compiled within &0000-&3FFF! ; ; Input ; Lower ROM should be enabled. ; ; Output ; If Flag Z is clear, the MF2 ROM/RAM is available. ; HL,DE,BC,F are modified. ; Interrupts are enabled. mf2_detect: ; Interrupt must be disabled first since the MF2 ; ROM/RAM might be mapped from &0000-&3FFF, it will ; takeover the IM1 interrupt vector and do unwanted ; things. di ; Save the word at &0000 ld hl,(&0000) push hl ; Write a word-tag at &0000 ld hl,&6128 ld (&0000),hl ; Try to map the MF2 ROM/RAM ld bc,def_mf2_io_enable out (c),c ; Then check if the tag changed ld de,(&0000) or a sbc hl,de ; If we get the same value, we assume the MF2 ROM/RAM ; was not mapped, thus no MF2 is available. jr z,mf2_not_available ; If we get something else, then we assume the MF2 ; ROM/RAM is actually mapped from &0000 to &3FFF, ; therefore an MF2 is available! ; ; We could do some more tests here, like verifying in the MF2 ; RAM if we can find matches to the hardware registers ;(eg. modifying CRTC Register 14 and checking if the value at ; &3A8E in the MF2 RAM matches the change) ; ; Before leaving, we disable the MF2 ROM/RAM mapping. ld bc,def_mf2_io_disable out (c),c mf2_not_available ; We restore the modified word to it's original value. pop hl ld (&0000),hl ; Flag Z hold the MF2 ROM/RAM status ; Z is set - Not available ; Z is clear - Available ei ret
If the MF2 stealth mechanism is enabled, there's no way of telling if there is a MF2 available or not… until it is activated to stop your program. You can not prevent your program from being stopped. All you can do is to look for a few trails the MF2 leaves after having stopped a program.
Here are just a few methods to detect if a program has been stopped. You could find more of them with a bit of imagination and curiosity.
This detection method is relying on a flaw in the MF2 firmware. When the user presses the STOP button, the MF2 automatically checks the amount of RAM available to detect 128Kb+ machines and might corrupt a byte in the extra RAM. This detection is fairly easy to implement but only works on 128Kb+ RAM machines.
Compile the following example and execute it (CALL &8000). The program will loop forever. If you stop it with your multiface, it will exit as soon as you return to it:
; Must be located outside the &4000-&7FFF range ; used to map the extra RAM bank. org &8000 ; Initialize HL with the address tested by the MF2 ld hl,&4000 ; Map the extra RAM bank tested by the MF2 ld bc,&7FC4 out (c),c ; Put a special tag (&AA) at &4000 to trick the ; first test of the MF2 ld (hl),&AA ; Now we just have to monitor this tag _mf2_monitor_extraRAM ; Read the tag back ld a,(hl) ; If it's value changed to &55, the program ; has been stopped cp &55 jr nz,_mf2_monitor_extraRAM _mf2_stopped ; The program has been stopped, ; Exit immediately ret
The STOP button of the Multiface Two produces an interrupt, thus the Z80 will push it's PC (Program Counter) onto the stack before dealing with the interrupt service routine. Therefore, monitoring the values on the stack can reveal if your program has been stopped. This is not an easy detection to set up, you must know exactly what's going on (which can be hard when the Amstrad Firmware is running in the background…) and you have to deal carefully with the stack to keep the detection working.
The following example is very simple but illustrate the idea. Compile it and call it from BASIC. It will loop forever and exit as soon as you return back to it (after you stopped it of course).
; MF2 Stop detection by monitoring the stack ; Ignore the (maskable) interrupts, that will make things a lot ; simpler in this example. di ; Push a marker value (eg. zero) into the stack ld hl,0 push hl ; Get back to the initial stack pointer position pop hl mainloop ; ************************************************************ ; *** Do some stuff here but it must not modify the stack! *** ; ************************************************************ ; Then we check if our marker value is still there dec sp dec sp pop hl ld a,h or l jr z,mainloop ; if it's not, the CPU obviously did something that wasn't expected. ; Exit the program immediately ei ret
This is another detection method relying on a flaw of the Multiface Two, especially it's I/O monitoring system. We know that the MF2 doesn't catch accesses to I/O devices when using ghost addresses. The idea is very simple, just write to an hardware register a first value that the MF2 will register (thus using a standard I/O address) and right after write a second different value that the MF2 won't catch (using a ghost address). When the MF2 will return to your program after it stopped it, it will restore the first value. All your program have to do is to monitor if there's somethin' strange in your neighborhood or if the value of the hardware register is modified (so your program know who ya gonna call).
In the following example, CRTC register 14 is used. It's a light-pen related register that is both readable and writable, so it fits perfectly for our test (but not all CRTC registers are R/W!). We could also use PPI Port A. The point is that the hardware register used must be readable, so we can easily detect when it changes (but you could also use a write-only register and indirectly detect if it has been modified by monitoring timings or anything else that depends on it, but this is a little bit too complicated for an example here :).
; MF2 Stop detection by monitoring hardware register(s) ; Select CRTC Register 14 (lightpen) ld bc,&BC00+14 out (c),c ; Set CRTC R14 to &55 with the regular ; CRTC Write register I/O address so the ; MF2 will catch and record it. ld bc,&BD55 out (c),c ; Set CRTC R14 to &AA with a ghost I/O address ; (note that this ghost I/O address also hit the ; printer and upper ROM ports). ld bc,&8DAA out (c),c ; Then we monitor the CRTC R14 and exit the loop ; as soon as it's value change (ie. after a STOP). loop ld b,&BF in a,(c) cp c ; if R14=&AA jr z,loop ; we keep looping ; Exit ret
The direct jump is a neat feature of the MF2 that allow programmers to customize the behaviour of the interface with their own program. As said before, when the STOP button is pressed, the MF2 firmware is executed. It starts by initializing some stuff and then checks if a Direct Program is configured or not. If there's actually one configured it will be executed, whatever it is, otherwise the MF2 proceeds as usual and starts it's built-in toolkit.
Installing a Direct Jump program in the MF2 is piece of cake. It takes 3 parameters:
These 3 parameters must be written in the MF2 RAM along with a 3 characters ASCII string ”RUN
” to indicate that your Direct Jump is ready, and that's it.
The RMR and MMR parameters allow the MF2 to call your direct jump program wherever it is located, to some extent. As said before, the MF2 ROM/RAM is paged from &0000 to &3FFF. Therefore, the MF2 can call your direct jump program from &4000 to &FFFF whatever is paged in within this address range (according to the RMR/MMR registers, it can be the base 64Kb RAM, extended RAM, Upper ROM, …). From &0000 to &3FFF however, there's the MF2 ROM/RAM and nothing else.
When your direct jump program is called:
For your direct jump program to exit properly:
The MF2 will automatically restore all the hardware and CPU registers and let the CPC/Plus proceeds just like nothing happened (assuming that your direct program was properly designed :).
The following example installs a direct jump program that will change the border color to red for a few seconds when the stop button is pressed.
mf2.djump.example.asm
in the assembler call&4000
to install the direct jump program in the MF2.Note that pressing the STOP button again, before the first direct jump has exited, will lead to a stack overflow (crash). It's up to you to take care of that.
; Multiface Two - Direct Jump example ; For obvious reasons, your program must be located ; outside the address range &0000-&3FFF! org &4000 ; Setup a direct jump ld hl,mf2_djp ld de,&C000+%10001011 jp mf2_kl_set_djump ; A dummy direct jump program example ; It will set the border to red for a few ; seconds and exit. mf2_djp: ; set border red ld bc,&7F10 out (c),c ld c,&4C out (c),c ; wait a bit mf2_djps ld b,&F5 in a,(c) rra jr nc,mf2_djps djnz $ dec c jr nz,mf2_djps ; exit ret ; Include the direct jump library read "mf2.kl.djump.asm"
And here is the source code for the mf2.kl.djump.asm
:
; The two Firmware routines used to bank-switch the lower ROM _KL_LROM_ENABLE equ &B906 _KL_ROM_RESTORE equ &B90C ; Setup a direct jump ; Input ; Amstrad Firmware must be available. ; ; HL = Direct Jump address ; D = MMR ; E = RMR ; ; Output ; Zero flag is set if no MF2 was found mf2_kl_set_djump: ; Enable the lower ROM call _KL_LROM_ENABLE ; Save the previous ROM state for later push af ; Save input parameter push hl ; Read a word (16bits) from the Lower ROM ld hl,(&0464) ; Enable the MF2 ROM/RAM from &0000-&3FFF (8Kb) di ld bc,&FEE8 out (c),c ; Read a word (16bits) from the MF2 ROM hopefuly ld bc,(&0464) ; Compare the bytes from Lower ROM and MF2 ROM xor a sbc hl,bc ; If they are not equal, we assume the MF2 ROM is paged, ; we can proceed and setup the direct jump, otherwise we ; exit immediately. pop hl jr z,_mf2_kl_set_djump_exit ; Set the Jump address ld (&2000),hl ; Set the RMR/MMR values ld (&2002),de ; Set the Direct Jump "RUN" key ld a,"R" ld hl,"N"*256 + "U" ld (&2005),a ld (&2006),hl ; Disable the MF2 ROM/RAM ld bc,&FEEA out (c),c _mf2_kl_set_djump_exit ; Acknowledge Interrupts ei ; Restore the ROM state as it was pop hl push af ; Save the flags ld a,h call _KL_ROM_RESTORE ; We're done, restore the flag pop af ret
Address | Size | Type | Description |
&2000 | 8 | Direct Jump boot configuration | |
---|---|---|---|
&2000 | 2 | USR | Address of the Direct Jump program |
&2002 | 1 | USR | RMR configuration |
&2003 | 1 | USR | MMR Configuration |
&2004 | 1 | ROM | Reserved. |
&2005 | 3 | USR | 3 bytes sequence to enable/disable the Direct Jump mode (put the ASCII string “RUN” to enable) |
&2008 | 6135 | RAM free to use | |
&37FF | 2049 | MF2 Reserved area (stack, I/O monitoring, firmware variable/buffers, …) | |
A fully detailled address map is on it's way and should be published soon. Until then, you can find one on QuasarNet (in french). |
One of the most original (and somehow funny :) direct jump usage I've ever seen is in Zap't'Balls (a game by the former demomaker Elmsoft). If your MF2 is visible, the game installs a direct jump program which, if you try to stop the game, will display the usual MF2 toolkit menu but… :)
(If Flash is installed JavaScript is activated, you can watch a video inside this web page.)
(might be done later)