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
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
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:
- 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).
- 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.
- 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.
- 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'.
- 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.
- 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!