Porting Meteor Mission II to Microbee

Part 2 - Basics and First Run

This article continues on from Part 1 - A Decent Disassembly.

Now that I had the disassembled listing from YAZD I spent perhaps 15 minutes just looking through the listing just trying to get a feel for how things were arranged and what external things it accessed. The best way to do this is with the cross reference information at the bottom of the file, starting with external calls, of which there were just two:

references to external address 002Bh:
        67F1 CALL 002Bh
        67F9 CALL 002Bh

references to external address 0033h:
        54D1 CALL 0033h
        67F6 CALL 0033h

Looking these up here I could see 002Bh is BASIC's Inkey function, while 0033h is the print character routine. OK, noted.

Next I looked at all the externally referenced memory addresses and put together a simple memory map of the program and environment:

0125h 			- Somewhere in BASIC ROM?
3800h - 3880h	- Keyboard mapping
3C00h - 4000h	- video RAM
4500h - 7300h   - the game itself
7300h - 7D00h   - external data area (ie: working RAM)
8000h           - top of stack.

The reference to 0125h was an interesting one so I took a closer look at the code that referenced it:

5509: 3A 25 01                          LD      A,(0125h)
550C: FE 49                             CP      49h     ; 'I'
550E: 20 08                             JR      NZ,L5518
5510: 3E FF                             LD      A,0FFh
5512: 32 EC 68                          LD      (L68EB+1),A     ; reference not aligned to instruction
5515: 32 48 69                          LD      (L6947+1),A     ; reference not aligned to instruction

From this I could tell it's reading one byte from inside the TRS80's ROM area and comparing it to the character 'I'. I can't find the reference again right now, but somewhere I found mention of a nearby entry point in the ROM that supposedly prints out the current version of BASIC. I think it's a pretty safe bet that the comparison to 'I' is a check for Level "I" vs "II" BASIC, or perhaps Model "I", "II", "III". What's more interesting is what it does with this information - the comments "reference not aligned to instruction" suggests self modifying code and in fact it's modifying this instruction:

68EB: 36 5B                      L68EB: LD      (HL),5Bh        ; '['

In other words, depending on the version of BASIC this instruction might become LD (HL),0FFh. Interesting.

Making a relocatable listing

The next step was to edit the disassembled listing into something that could be re-assembled and re-located. This is the most tedious part of the port. The problem stems from the fact that it's not always possible to discern if literal numeric values used in the code are just numbers, or references to memory addresses.

In order to get this program running on the Mirobee we're going to need to be able to make changes that will inevitably require inserting new instructions. If the assembly listing uses absolute memory addresses however, inserting those instructions will cause things to move around in memory and essentially break it. Also, we're probably going to want to move the program in memory (Microbee programs typically load at 0x0900, not 0x4500) so absolute memory addresses are not useful.

Take the following piece of code as an example:

                                        ; Referenced from 4547
454C: 06 0D                      L454C: LD      B,0Dh
454E: 21 57 6B                          LD      HL,6B57h        ; address or value?

                                        ; Referenced from 456D
4551: C5                         L4551: PUSH    BC
4552: D5                                PUSH    DE
4553: 4E                                LD      C,(HL)
4554: 06 00                             LD      B,00h

The instruction LD HL,6B57H has been annotated by YAZD with the comment "address of value?". It did this because of the '--mwr' ("Mark Word References") option I specified when generating the disassembly. Basically YAZD doesn't know if this is a memory address or just a numeric value. Looking down a little further we can see the instruction LD C,(HL), so obviously this is a memory address (since we're reading from that location). Looking at the memory map above, you can see that this address is within the program code and we can see it's a string containing the name of the game:

6B57: 0D                                DB      0Dh
6B58: 2A                                DB      2Ah     ; '*'
6B59: 20                                DB      20h     ; ' '
6B5A: 4D                                DB      4Dh     ; 'M'
6B5B: 45                                DB      45h     ; 'E'
6B5C: 54                                DB      54h     ; 'T'
6B5D: 45                                DB      45h     ; 'E'
6B5E: 4F                                DB      4Fh     ; 'O'
6B5F: 52                                DB      52h     ; 'R'
6B60: 20                                DB      20h     ; ' '
6B61: 4D                                DB      4Dh     ; 'M'
6B62: 49                                DB      49h     ; 'I'
6B63: 53                                DB      53h     ; 'S'
6B64: 53                                DB      53h     ; 'S'
6B65: 49                                DB      49h     ; 'I'
6B66: 4F                                DB      4Fh     ; 'O'
6B67: 4E                                DB      4Eh     ; 'N'
6B68: 20                                DB      20h     ; ' '
6B69: 49                                DB      49h     ; 'I'
6B6A: 49                                DB      49h     ; 'I'
6B6B: 20                                DB      20h     ; ' '
6B6C: 2A                                DB      2Ah     ; '*'
6B6D: 24                                DB      24h     ; '$'

If we were to simply re-assemble this program at a new address, HL would still be loaded with 6B57h, but the string would have moved to a new address. To fix this we need to insert a label. Change the 6B57h to L6B57 and remove the "address or value?" comment:

454E: 21 57 6B                          LD      HL,L6B57

and insert the label:

6B57: 0D                         L6B57: DB      0Dh
6B58: 2A                                DB      2Ah     ; '*'
6B59: 20                                DB      20h     ; ' '
6B5A: 4D                                DB      4Dh     ; 'M'
6B5B: 45                                DB      45h     ; 'E'

So: 1 down, 351 to go. Yikes! Yes, there were over 350 such references and they all need to be looked at. I did say this was tedious, but fortunately most of these are fairly simple. Some notes:

  1. At this stage I found it best to leave the left hand columns with the raw byte code in place in the listing. This is sometimes useful information (particularly when encountering self-modifying code - which we'll come to again shortly).
  2. I took my chances and assumed every reference between 0x7300 and 0x7D00 was to the working ram and use some regex search and replaces to fix them all up in one fell swoop.
  3. Similarly I guessed that any value less than 100h is probably not an address and much more likely to be a value. (say an increment value) and used a regex to remove the comment.
  4. Rather than using symbolic labels for internal references, at least for now I stuck to using a label based on the original address. eg: 'L6B57' and not 'game_name_str'.
  5. For external references I changed them to a base symbol plus an offset. So anything between 3C00h and 4000h I changed to vram+value. eg: 3c40h would become vram+0040h. This allows relocating these sections with an EQU later.
  6. Anything that could be determined not to be an address I simply removed the comment.

So code like this:

4591: 21 C0 3F                   L4591: LD      HL,3FC0h        ; address or value?
4594: 11 40 77                          LD      DE,7740h        ; address or value?
4597: 01 40 00                          LD      BC,0040h        ; address or value?
459A: ED B0                             LDIR

ended up like this:

4591: 21 C0 3F                   L4591: LD      HL,vram+03C0h
4594: 11 40 77                          LD      DE,data+0440h
4597: 01 40 00                          LD      BC,0040h
459A: ED B0                             LDIR

There were a few that I still couldn't figure out and so for the moment just left the comment in place as a reminder to come back and visit them again later.

eg: This one could go either way:

5807: 21 80 37                          LD      HL,3780h        ; address or value?
580A: 19                                ADD     HL,DE
580B: 7E                                LD      A,(HL)
580C: CD 0A 59                          CALL    L590A

These ones are obviously addresses, but I'm not sure what it's doing: (loading BC with 0 before a LDIR - what the?)

6591: 21 00 40                          LD      HL,4000h        ; address or value?
6594: 11 01 40                          LD      DE,4001h        ; address or value?
6597: 36 00                             LD      (HL),00h
6599: 01 00 00                          LD      BC,0000h        ; address or value?
659C: ED B0                             LDIR

All up it took a couple of hours to go through and fix up these address locations. I also removed the LMOFFSET stub at the end.


Now that I had what I hoped to be a mostly relocatable listing it was time to try and re-assemble it. Firstly I took a backup copy of what I had and then got rid of the byte listing on the left hand side which could probably be done with gawk, but I just opened it in Visual Studio and used it's column select mode to delete it. I also added in some EQU definitions for the base addresses I used and added an ORG statement to set the origin:

       ORG      900h

data:           equ     07300h
vram:           equ     03c00h
stacktop:       equ     08000h

       ; --- START PROC L4500 ---
L4500: DI
       XOR     A
       OUT     (02h),A

Note that at this point I'm not relocating anything. First I wanted to re-assemble it and make sure I get out the same as what went in:

Z:\retro\meteor>z80asm -o meteor.bin meteor.asm 

I then hex dumped the binary and the original binary and ran them through a text diff program, which turned up one mistake (I'd missed putting the 'L' on the start of a label and it was getting treated as a literal decimal number). After fixing it, I had the exact same output (except with the stub re-locator from the end removed).


Next I relocated the program by changing the EQU's and ORG statement:

       ORG      900h

data:           equ     03700h
vram:           equ     0f000h
stacktop:       equ     08000h

and rebuilt it:

Z:\retro\meteor>z80asm -o meteor.bin meteor.asm 

First run on the Microbee

Finally we're ready for the first run on the Microbee. Using ubee512 emulator is obviously the easiest way to do this. Interestingly, the fastest way to get it into the emulator seems to be as a .TAP file. So I converted the .bin file to a .tap file:

Z:\retro\meteor>bin2tap meteor.bin meteor.tap --loadaddr:0x900 --startaddr:0x900

and run it in ube512:

Z:\retro\meteor\ubee512 pc85b --tapfilei=.\meteor.tap

In the emulator, typed load and was rewarded with a bunch of crazy animated characters ending with this:


Obviously I need to setup the LORES graphic characters, but basically it's working!

Porting Meteor Mission II to Microbee continues with Part 3 - Development Environment and Title Screen.