Atarimax "Maxflash" Flash MultiCarts

Preliminary Documentation, 07/10/2003

©2003 Steven J Tucker

Important Notice!

The software described below has been superceded by the new Maxflash Studio software application.

This software is still supported for advanced users, but all new users should try the Maxflash Studio suite located here before attempting to use these programs and scripts.

Maxflash Chapter 8: Conversion Tutorials (for -DISKPACKER option)

Table of Contents

  1. Introduction
  2. Minor change tutorial: Black Lamp
  3. Minor change tutorial: Zorro
  4. Major change tutorial: Non-XL/XE MULE Cartridge Conversion

Introduction

This chapter assumes you are knowledgeable in 6502 assembler, emulator use, and debugger use.

Since there are a finite number of Atari games available for conversion to cartridge, ask around before attempting anything more involved than automatic conversion, someone may have already done the work of conversion for you.

While many if not most games will work fine with the -DISKPACKER option, some that re-initialize PORT B or perform various other paranoia checks may need to be modified.

These are not intended to be comprehensive instructions on custom conversion, just a few examples to give you a place to start and an idea how its done.

If you don't plan on doing custom conversions and don't want to write a lot of 6502 assembler and such just for the pleasure of having done it, skip this chapter.

Minor change conversion: Black Lamp

This example uses the BlackLamp2.atr disk image from the Jellystone Park Archive.

Convert the disk to cartridge using the -DISKPACKER option of the maxflash.pl script.

>perl maxflash.pl -diskpacker BlackLamp2.atr test.bin
 
MAXFLASH Multicart Build/Utility Script - c2003 Steven J Tucker
Information and updates at http://www.atarimax.com/
 
New binary image [test.bin] created.

Just a normal automatic conversion here, no special options are needed for this game.

Fire up the emulator and load the test.bin image you created. If you need help creating the image or loading it, check Chapter 5: Using the Maxflash Software, or Chapter 7: Emulator Support before going on.

When the emulator is started, instead of booting the game, we are just greeted with black and yellow bars.

Since this is an emulator and not a real Atari we can see what went wrong just by stopping emulation and starting the built-in monitor/debugger.

Press F8 to bring up the monitor, then type show to display the current IP, and finally d 6c3 to show the code where we stopped the emulation. These actions are shown in the screen shot above.

Looks like were just locked in a loop that creates the color bars.

Since 0x600 is a common place to put the boot code for a disk, type d 600 and check out a few pages of the code to see how we might have ended up here.

0635    CLC                 ; 2cyc ; 18
0636    ADC #$80            ; 2cyc ; 69 80
0638    STA $0304 ;DBUFLO   ; 4cyc ; 8D 04 03
063B    LDA $0305 ;DBUFHI   ; 4cyc ; AD 05 03
063E    ADC #$00            ; 2cyc ; 69 00
0640    STA $0305 ;DBUFHI   ; 4cyc ; 8D 05 03
0643    LDA $030A ;DAUX1    ; 4cyc ; AD 0A 03
0646    CLC                 ; 2cyc ; 18
0647    ADC #$01            ; 2cyc ; 69 01
0649    STA $030A ;DAUX1    ; 4cyc ; 8D 0A 03
064C    LDA $030B ;DAUX2    ; 4cyc ; AD 0B 03
064F    ADC #$00            ; 2cyc ; 69 00
0651    STA $030B ;DAUX2    ; 4cyc ; 8D 0B 03
0654    DEC $F0 ;FCHRFLG    ; 5cyc ; C6 F0
0656    LDA $F0 ;FCHRFLG    ; 3cyc ; A5 F0
0658    BNE $062F           ; 2cyc ; D0 D5
065A    RTS                 ; 6cyc ; 60
065B    LDA #$00            ; 2cyc ; A9 00
065D    STA $022F ;SDMCTL   ; 4cyc ; 8D 2F 02
0660    LDA $D301 ;PORTB    ; 4cyc ; AD 01 D3   <---------- Its checking here to make sure the OS is in ROM
0663    CMP #$FF            ; 2cyc ; C9 FF      <---------- Its not when loaded from cartridge, this 
0665    BNE $06C3           ; 2cyc ; D0 5C      <---------- LDA will return FE not FF.
0667    JSR $0608           ; 6cyc ; 20 08 06
066A    JSR $A020           ; 6cyc ; 20 20 A0

There is the jump into the color-bar loop at address 0x665.

When using the automatic conversion of the -DISKPACKER option, reading PORT B from an Atari program will return 0xFE instead of 0xFF because the Flash Cartridge places the OS in RAM.

Any code that checks or writes to PORT B can cause the problem seen above. Some games just re-initialize PORT B to be safe, some check it to make sure a ROM monitor is not installed, as seen above. We can just bypass these checks or writes since PORT B is already initialized.

Lets bypass this check and see if it will boot.

To do that, we need to stop the emulator BEFORE that code is executed and change the outcome of the PORT B check.

To start, set a breakpoint at 0x660, a few instructions before the actual check. Type break 660 and press ENTER.

Once you have set the breakpoint, we need to restart emulation. Type cont to return to the emulator, and then immediately press Shift+F5 to restart emulation.

When the emulator hits your breakpoint, it will actually display a dialog saying the Atari has crashed. Press YES in that dialog to bring up the debugger.

You should see the screen above, or at least the black window portion.

We have now stopped emulation just before PORT B is checked, lets see a little of the code at the current IP. Type d 660 to display a disassembly.

TIP: Type '?' for help, 'CONT' to exit
(breakpoint at 660)
> d 660
0660    LDA $D301 ;PORTB    ; 4cyc ; AD 01 D3
0663    CMP #$FF            ; 2cyc ; C9 FF
0665    BNE $06C3           ; 2cyc ; D0 5C      <--- Here it is again.
0667    JSR $0608           ; 6cyc ; 20 08 06
066A    JSR $A020           ; 6cyc ; 20 20 A0
066D    LDA $14 ;RTCLOK+2   ; 3cyc ; A5 14
066F    CMP $14 ;RTCLOK+2   ; 3cyc ; C5 14
0671    BEQ $066F           ; 2cyc ; F0 FC
0673    LDA #$2A            ; 2cyc ; A9 2A
0675    STA $F0 ;FCHRFLG    ; 3cyc ; 85 F0
0677    LDA #$07            ; 2cyc ; A9 07
0679    STA $0621           ; 4cyc ; 8D 21 06
067C    LDA #$49            ; 2cyc ; A9 49
067E    STA $0626           ; 4cyc ; 8D 26 06
0681    LDA #$00            ; 2cyc ; A9 00
0683    STA $062B           ; 4cyc ; 8D 2B 06
0686    JSR $060C           ; 6cyc ; 20 0C 06
0689    LDA #$FE            ; 2cyc ; A9 FE
068B    STA $F0 ;FCHRFLG    ; 3cyc ; 85 F0
068D    LDA #$1C            ; 2cyc ; A9 1C
068F    STA $0621           ; 4cyc ; 8D 21 06
0692    LDA #$73            ; 2cyc ; A9 73
0694    STA $0626           ; 4cyc ; 8D 26 06
0697    LDA #$00            ; 2cyc ; A9 00
> a 665
Simple assembler (enter empty line to exit)
665 : nop
666 : nop
667 :
> cont

We can just bypass the check at 0x665 by removing the branch (BNE) instruction.

Type a 665 to assemble code over that instruction, then insert two NOP instructions to cover the location the two byte BNE instruction occupied.

With our test patch in place, type cont to continue emulation and see if it works.

After a delay of a few seconds at the Atari logo screen, it loads right up. A little play-testing will confirm the game works fine off the cartridge now.

Now that we know what to patch, we need to modify the test.bin file so that the patch is part of the image and can be written to cartridge.

Lets take a look at the code before patching.

0660    LDA $D301 ;PORTB    ; 4cyc ; AD 01 D3
0663    CMP #$FF            ; 2cyc ; C9 FF
0665    BNE $06C3           ; 2cyc ; D0 5C      <--- Here it is again.
0667    JSR $0608           ; 6cyc ; 20 08 06
And after patching.
0660    LDA $D301 ;PORTB    ; 4cyc ; AD 01 D3
0663    CMP #$FF            ; 2cyc ; C9 FF
0665	 NOP				 ; 2cyc ; EA         <--- Our changes.
0666	 NOP                 ; 2cyc ; EA
0667    JSR $0608           ; 6cyc ; 20 08 06

In the emulator disassembly, in the far right column, you will find the actual assembler opcodes in hex for each instruction.

Only two bytes were actually changed. Bytes 0xD0 0X5C were changed to 0xEA 0xEA, NOP instructions.

Fire up your favorite hex editor and open the test.bin cartridge image. Once there, search an original code snippet near the patch site, 0xC9 0xFF 0xD0 0x5C 0x20.

Just overwrite the bytes 0xD0 0x5C, the BNE instruction shown above, with 0xEA 0xEA and save the image.

The file is now patched and ready to burn on cartridge. Just use the -BIN2ALL option of the maxflash.pl script to convert test.bin to both a .car image for testing, and a .atr disk image for programming your flash cartridge.

Click here to download the blacklamp2.atr disk image used in this tutorial.

Click here to download the finished flash cartridge image blacklamp.bin. (Patch already applied)

Minor change tutorial: Zorro

Convert Zorro to a cartridge image with the maxflash.pl script, using no special options.

>perl maxflash.pl -diskpacker zorro.atr zorro.bin
 
MAXFLASH Multicart Build/Utility Script - c2003 Steven J Tucker
Information and updates at http://www.atarimax.com/
 
New binary image [zorro.bin] created.

Now on to testing the image in Atari800Win.

Oops, it crashed. Notice at the bottom of the 'debug' window, it says the OS was changed back to ROM. In order for a -DISKPACKER cartridge to work the OS must remain in RAM, so we need to fix this.

The OS is changed back to ROM by writing bit 0 of PORT B to 1. We can search for a write to PORT B to find what code is causing the problem. Press 'YES' to bring up the monitor and search for a write to PORT B using s 0 c000 8d 01 d3.

Usually this will find the offending code, and patching it is simple, but in this case the search finds nothing. Since the switch occurs shortly after the disk boot starts, we will just check out the disk boot code and see if that's the culprit. Set a breakpoint on the SIO vector 0xE459 and press Shift+F5 to restart emulation.

As the disk boot starts, Atari800Win will catch the breakpoint and prompt you to open the monitor. Press 'YES'. With the monitor open, we can examine the SIO register to see what's being loaded. Do d 300 to display these registers.

This first call is just a status request (0x53 at location 0x0302), so type cont to continue. The breakpoint will fire again immediately, press 'YES' to enter the monitor.

Display the memory at 0x0300

TIP: Type '?' for help, 'CONT' to exit
(breakpoint at CC00)
> d 300
0300    AND ($01),Y ;NGFLAG ; 5cyc ; 31 01
0302    CIM                 ; 2cyc ; 52
0303    RTI                 ; 6cyc ; 40
0304    BRK                 ; 7cyc ; 00     <-- Destination buffer for 
0305    NOP $07 ;CMCMD      ; 3cyc ; 04 07  <-- disk access, 0x0400
0307    BRK                 ; 7cyc ; 00
0308    NOP #$00            ; 2cyc ; 80 00
030A    ORA ($00,X)         ; 6cyc ; 01 00
030C    BRK                 ; 7cyc ; 00
030D    BRK                 ; 7cyc ; 00

The XL/XE OS boot code always loads the boot sectors at location 0x0400. We have not let the OS actually load the sector yet, so type cont and enter the monitor when the breakpoint fires again.

Now display the memory at 0x0400, this is the first boot sector.

> d 400
0400    BRK                 ; 7cyc ; 00
0401    ASO ($00,X)         ; 8cyc ; 03 00  <-- Sectors to load (Offset +1) 
0403    BCS $0405           ; 2cyc ; B0 00  <-- Address to load at (Offset +2/+3)
0405    BMI $03B0 ;B7-ICHID ; 2cyc ; 30 A9
0407    BRK                 ; 7cyc ; 00
0408    STA $0A ;DOSVEC     ; 3cyc ; 85 0A
040A    LDA #$30            ; 2cyc ; A9 30

This first sector is telling the OS to load 3 sectors at location 0xB000

Set a breakpoint at 0xB008 and press Shift+F5 to restart emulation. When the breakpoint fires type d d008 to disassemble the code at the current IP.

TIP: Type '?' for help, 'CONT' to exit
(breakpoint at B008)
> d b008
B008    STA $0A ;DOSVEC     ; 3cyc ; 85 0A
B00A    LDA #$30            ; 2cyc ; A9 30
B00C    STA $0B ;DOSVEC+1   ; 3cyc ; 85 0B
B00E    LDA #$FF            ; 2cyc ; A9 FF
B010    STA $D301 ;PORTB    ; 4cyc ; 8D 01 D3  <-- Sets OS to ROM
B013    LDX #$01            ; 2cyc ; A2 01
B015    STX $D508           ; 4cyc ; 8E 08 D5  <-- OOPS
B018    DEX                 ; 2cyc ; CA
B019    STX $0244 ;COLDST   ; 4cyc ; 8E 44 02
B01C    LDA #$29            ; 2cyc ; A9 29
B01E    STA $BFFA           ; 4cyc ; 8D FA BF
B021    LDA #$B0            ; 2cyc ; A9 B0

There it is, its setting PORT B to 0xFF, which will change the OS back to ROM. We can just change the value loaded in the previous instruction to 0xFE to make it leave the OS in RAM.

Also of interest is the instruction STA $D508. This would turn off a Spartados X cartridge, but will turn ON a flash cartridge, so we will eliminate that too by overwriting that instruction with NOP instructions.

Use the following commands to make the required changes.

> a b00e
Simple assembler (enter empty line to exit)
B00E : lda #$FE
B010 :
> a b015
Simple assembler (enter empty line to exit)
B015 : nop
B016 : nop
B017 : nop
B018 :
>
The resulting code looks like this.
B008    STA $0A ;DOSVEC     ; 3cyc ; 85 0A
B00A    LDA #$30            ; 2cyc ; A9 30
B00C    STA $0B ;DOSVEC+1   ; 3cyc ; 85 0B
B00E    LDA #$FE            ; 2cyc ; A9 FE
B010    STA $D301 ;PORTB    ; 4cyc ; 8D 01 D3
B013    LDX #$01            ; 2cyc ; A2 01
B015    NOP                 ; 2cyc ; 1A
B016    NOP                 ; 2cyc ; 1A
B017    NOP                 ; 2cyc ; 1A
B018    DEX                 ; 2cyc ; CA
B019    STX $0244 ;COLDST   ; 4cyc ; 8E 44 02
B01C    LDA #$29            ; 2cyc ; A9 29
B01E    STA $BFFA           ; 4cyc ; 8D FA BF
B021    LDA #$B0            ; 2cyc ; A9 B0

Type cont to continue and test your patch. The result is a perfectly working Zorro cartridge, so all we have to do now is load zorro.bin into a hex editor, make the patch there, and the job is done.

Click here to download the zorro.atr disk image used in this tutorial.

Click here to download the finished zorro.bin flash cartridge image. (patch already applied)

Major surgery: The Mule Cartridge Tutorial (LONG)

The -DISPACKER option works fine with MULE, but if you want a MULE cartridge that runs on an Atari 800 as well as XL/XE computers, then some major surgery is required.

MULE is a good game for a tutorial on conversion, it is a multi-stage disk loading game with enough complexity to make it a small challenge, but still simple enough to do in a few hours.

There are any number of ways you might approach a custom conversion like this. This is just one way to do it.

If you don't have the emulator drive access and sector number indicators turned on, turn them on now, we will need them to monitor the progress of MULE loading itself into memory. These are on the View->Show menu of Atari800Win PLus.

Disable the SIO patch, we will want the game to load slowly so we can see what sector numbers its loading, etc. Set the computer type to XL/XE and the memory type to 64k.

There are a lot of different versions of MULE floating around, some that wont boot on an XL/XE some that only boot on an XL/XE, etc. The version of MULE used in this tutorial can be downloaded as an ATR image by clicking here.

To get started, load your MULE ATR disk image into the emulator and start it as you would normally start a disk game. After the game has started, press RESET. Notice how the game just re-starts without re-loading? This will make our job a lot easier since the first load segment is RESET tolerant.

All we need to do is find out where the program resides in memory and dump that part to disk.

Bring up the monitor by pressing F8. The warmstart vector in the XL/XE models is at memory location 0x000A, so display that location to get the address MULE starts itself at.

TIP: Type '?' for help, 'CONT' to exit
> d a
000A    NOP                 ; 2cyc ; 5A
000B    SAX $40 ;FREQ       ; 3cyc ; 87

Type d a to show the contents of memory at location 0x000A. The 16-bit address is stored in 2 8-bit locations in LSB/MSB format.

The warmstart vector is set by MULE to 0x875A. Write that bit down we will need it. Now all we need to do is figure out how much code MULE loads at startup, and where it puts it.

There is a lot of room for one game in a flash cartridge, if we were lazy we could just pack up everything but lets be a little selective.

Reboot MULE now and watch the sector display as the program loads. You will see it loads sectors 1-132 before the game title screen appears. A little quick math will show that 132 sectors at 128 bytes per sector, MULE is putting getting about 16k or 0x4200 bytes from the disk during its load.

Now we could find out where that information is put in memory by disassembling the boot sectors, but chances are its just putting one big lump into memory and running it, so we can just peek around in memory and see what's been written to after the game menu appears.

Restart emulation again and when MULE reaches the title screen, bring up the monitor. Type d 0 to disassemble at location 0x0000 and press ENTER. Hold down ENTER and you will see the contents of memory slowly display all the way up to 0xFFFF, the last location.

Since the OS uses most locations under 0x0500 for its own use, and everything above 0xC000 is the OS, any big block of 6502 code we find outside that area is likely to be the MULE game menu.

After a little rooting around you will find memory is mostly empty except for one big block from 0x7000-0xBFFF, which comes out to 0x4FFF bytes. Pretty close to the 0x4200 number that was actually loaded.

Lets test our theory of how mule loads by trying to manually load that part into memory without letting MULE access the disk.

First, dump that portion of memory into a binary file using the monitor command WRITE. For example, WRITE 7000 BFFF C:\test.bin would write the contents of Atari memory stored at 0x7000 up to 0xBFFF to a file called C:\test.bin.

Now that we have saved the portion we are interested in, remove the MULE.ATR disk image from drive 1 and restart emulation by pressing Shift+F5.

As soon as Atari800Win starts, press F8. Do this as fast as possible so we can get the machine under control before it goes into self test mode.

When the monitor appears, load the memory we saved back into the Atari using the READ command. READ C:\test.bin 7000 FFFF will load the entire contents of C:\test.bin into the Atari's memory starting at location 0x7000.

After we have reloaded the MULE memory dump, we need to make the Atari think it has already booted, so we will do c 244 0 and c 8 0 to cause reset to generate a warmstart instead of a coldstart.

Now we manually set the warmstart vector to the MULE entry point we recorded earlier by typing c a 5a 87. This changes memory location 0x000A to 0x5A, and 0x000B to 0x87.

Now test the whole setup out by forcing a warmstart. We can do this by simply changing the current instruction pointer to the start of the OS warmstart routine at 0xE474. In the monitor do setpc e474 and then cont to resume execution in the warmstart vector.

It worked, MULE came right up without loading from the disk. Using the generic memory unpacker code in mempack.a65, we can start making a cartridge.

First we need to calculate how many banks the first stage of MULE will occupy in the cartridge. Since we have 0x4FFF bytes of code and each cartridge bank holds 0x2000 bytes, we will need 3 cartridge banks. A little space is wasted but that's not really an issue unless things get tight later.

Using the generic memory unpacker code in mempack.a65, we can make a CUSTOM memory unpacker for our MULE stage 1 dump. We will use bank 0 of the cartridge for our memory unpacker code, and banks 1-3 for the stage one MULE memory dump.

Click here to download all the source files used in this example. The stage 1 unpacker code is called mule.a65.

With a little hacking to unpack only 3 banks instead of 8, the mempack.a65 code becomes mule.a65. Build this source with TASM to get our bank 0 unpacker code. Since the unpacker code needs to occupy one whole bank (to make things easy on us), pad the resulting executable file to exactly 8192 bytes.

Pad the mule memory dump to exactly 3 banks in size, 8192 x 3, or 24576 bytes. Once everything is a easy to work with size (aligned to the cartridge bank size), copy the two files together using the DOS copy command, for example, copy /b mule.com+test.bin testcart.bin.

Pad the resulting cartridge image to the size of a raw 1Mb cartridge image, 131072 bytes. You can now test your unpacker code and memory dump by just loading the testcart.bin file as a cartridge image in the emulator.

It works great, looks like stage 1 is done!

Now remove the cartridge image and reset the emulator to load MULE.ATR from disk. Load MULE and start a game in beginner mode.

If you play MULE for awhile, you will see it only loads from the disk twice. Once when its loading the title screen, and once again when its loading the actual game program. All we need to do now is make a dump for the stage 2 load and we will have a working MULE cartridge!

This one is a little trickier, we wont be able to pull the RESET trick to make things easier. Load MULE once again and prepare to start a game. Once you start the game, watch the sector number indicator as it loads stage two.

Looks like its loading sectors 370-690 to start the actual game program. That's 0xA000 bytes, it will fit easily within the space left in the cartridge. What we need to know now is of course where that code is loaded. This requires a little more effort than the reset trick but it can still be done without too much hassle.

We know MULE is loading from the disk, so the easiest way to get a clue on where its loading is to find the code in MULE that does the disk access. At the MULE title screen bring up the monitor and search for JSR calls to the OS SIO vector. The OS SIO vector is 0xE459 and the JSR opcode is 0x20, so search memory for 0x20 0x59 0xE4.

> s 0 ffff 20 53 e4
Found at babb
Found at bb14
Found at c5a2

The hit above 0xC000 is in the OS so ignore that, but we did get two hits in the MULE code area at we previously unpacked, both around the same address 0xbab0 or so. Poking around in that area you will find the start of a disk read routine.

BAF0    STA $0A ;DOSVEC     ; 3cyc ; 85 0A
BAF2    LDA #$E4            ; 2cyc ; A9 E4
BAF4    STA $0B ;DOSVEC+1   ; 3cyc ; 85 0B
BAF6    LDA #$01            ; 2cyc ; A9 01      <-- Setting up SIO read starts here 0xBAF6
BAF8    STA $0301 ;DUNIT    ; 4cyc ; 8D 01 03    
BAFB    LDA #$52            ; 2cyc ; A9 52
BAFD    STA $0302 ;DCOMND   ; 4cyc ; 8D 02 03
BB00    LDA #$00            ; 2cyc ; A9 00      <-- The start address of the disk load.  
BB02    STA $0304 ;DBUFLO   ; 4cyc ; 8D 04 03
BB05    LDA #$10            ; 2cyc ; A9 10
BB07    STA $0305 ;DBUFHI   ; 4cyc ; 8D 05 03
BB0A    LDA #$90            ; 2cyc ; A9 90
BB0C    STA $030A ;DAUX1    ; 4cyc ; 8D 0A 03
BB0F    LDA #$01            ; 2cyc ; A9 01
BB11    STA $030B ;DAUX2    ; 4cyc ; 8D 0B 03
BB14    JSR $E453 ;DSKINV   ; 6cyc ; 20 53 E4    <-- OS Disk routines called

It looks good but we need to be sure this is the actual code called when a game load starts. Set a breakpoint at 0xBAF6, then restart emulation by pressing Shift+F5 and start a game of MULE.

If we were correct, the emulator should catch the breakpoint the instant MULE starts trying to load stage 2 from the disk. It does, so this is defiantly the location of the disk read for stage 2. If you look closely at the disassembly above, you will notice that the disk load code above pre-loads the memory pointer for disk access, 0x0304 with a pointer to memory location 0x1000.

This could mean the disk load starts bringing the game into memory at 0x1000. Since we know from watching the disk load that 0xA000 bytes are loaded from disk, we can now try and get a dump of the stage 2 code by saving memory locations 0x1000-0xB000 (0xA000 bytes total) to disk.

Restart emulation and start a game of MULE in beginner mode. Once stage 2 has completely loaded and the message 'Now landing on the planet Irata' appears, press F8 to bring up the monitor. Save the contents of the stage 2 memory from 0x1000-0xB000 using the command WRITE 1000 B000 C:\stage2.bin.

Now we need to see if loading that from memory and not disk will actually work, and if we actually found the correct disk load code. Set a breakpoint at 0xBAF6, the start of the stage 2 disk load shown above and go through the process of starting a beginner level game of MULE until the breakpoint is reached, then bring up the monitor.

Once in the monitor, reload the memory we saved to disk as stage2.bin. We will simulate bypassing the disk read manually here. Do READ C:\stage2.bin 1000 ffff to load the memory dump.

Now we have the memory loaded, but we are still at the beginning of the disk load routine. We need to bypass the disk load routine since we already loaded the required portion from our memory dump. If we look at the disassembly, just past the end of the disk load routine, we will see this code.

BB43    LDA #$09                     <-- End of disk load, start of prep for stage 2.
BB45    STA $0A ;DOSVEC
BB47    LDA #$10
BB49    STA $0B ;DOSVEC+1
BB4B    LDA #$03
BB4D    STA $0232 ;SSKCTL
BB50    STA $D20F ;SKCTL
BB53    LDA #$00
BB55    STA $D208 ;AUDCTL
BB58    LDA $14 ;RTCLOK+2
BB5A    CLC
BB5B    ADC #$01
BB5D    CMP $14 ;RTCLOK+2
BB5F    BNE $BB5D
BB61    LDA #$00
BB63    STA $02C8 ;COLOR4
BB66    LDA $D20A ;RANDOM
BB69    STA $092F
BB6C    JMP $E474 ;WARMSV

The code is loaded, then control is passed to it at location 0x1009 via a warmstart.

After loading stage2.bin into memory, we just do setpc bb43 to set the current IP to this point, then cont to test our work. If all went well you should see MULE start a game normally.

Since we have a working loader for stage 1, and now a working example of how to create a loader for stage 2, all we need to do is pack the stage 2 memory dump onto the cartridge as well, and replace the disk load code at BAF6 with a modified copy of our memory unpacker code.

The tutorial ends here, but the complete code and binary for both stages and the scripts needed to build the cart can be studied if you want more in depth information.

Click here to download the memory dumps, source code, and build scripts.

Click here to download the finished 1Mb cartridge image in .bin format.

Use the -BIN2ATR option of the maxflash.pl script if you wish to use the binary to program a flash cartridge.