Part 7 - Finishing Touches
This article continues on from Part 6 - Replacing the TRS80 ROM Basic Calls.
In most TRS-80 (and many Microbee) games, the speed of the game is determined by hard-coded delay loops. Since the Microbee runs at 3.375Mhz compared to the TRS-80's 2Mhz the entire game needs to be slowed down.
As for sound, these are generated by toggling a single bit output signal to affect a speaker. The speed of this toggling controls the frequency of the sound generated.
Since there were no sound generation chips, sound cards or multi-threading to do the sounds in the background, the game needs to generate the sound and run the game at the same time - so sound and game speed are closely linked.
I deliberately left these two area's to the end as getting them right is really a matter of fine tuning.
Test run at a slower speed
Before starting on this the first thing I did was simply run the game under ubee512 at 2Mhz to check that the game timing was basically right and hadn't been affected by the other changes I'd done. It ran and sounded great.
The Big-5 Sound Routine
This is not the first time I've fixed up the speed/sound issue in a Big-5 game having been through a similar exercise for the Microbee port of Galaxy Invasion (not that I did the port, but I did fix it up a bit). The way these games seem to work is with a single sound output routine that gets calls from all over the place. It even gets called inside loops that are updating the screen - for example when the background screen buffer is copied to the video buffer, it's copied one line at a time with a call to the sound routine after each line.
Before addressing the speed, the first thing to do was update this routine to write to the Microbee speaker port instead of the TRS-80 cassette port. In Meteor Mission, the sound routine is L4c71 (which I renamed to L4C71_sndout to make it easily recognizable in other parts of the code).
So I replaced this:
INC A L4C96: OUT (0FFh),A
IN A,(02h) SET 6,A L4C96: OUT (02h),A
There were a few places like this and it was simply a matter of figuring out which way the sound bit was being flipped and producing equivalent Microbee code.
Still no sounds
After the above changes I expected the sound to start working but still nothing. It took a bit of studying the code but I soon realized there was some self-modification of code going on here. (Actually I was surprised at how much self-modifying code there was in this game - it would be a challenge to get this running in a ROM). Looking a bit further down the sound routine was this:
LD HL,0000h LD (L4C89),HL LD (L4C96),HL JR L4CD4
If you look back at L4C96 above you'll see it refers to the
OUT (0FFh),A instruction which is being overwritten by
0000h - ie: two
nop instructions. Looking down a little further:
LD HL,0FFD3h LD (L4C89),HL LD (L4C96),HL
0FFD3h - is the opcode for
out (0FFh),A and is converting the two nops back to the original code.
Running again and the sound worked.
Speed and Timing
Thinking about how to slow down the game it became obvious that the sound routine was a good place to insert some extra delay loops. First I looked for an existing delay loop which I found near the end of the sound routine:
LD B,06h L4CAD: DJNZ L4CAD
Experimenting with different values for B however resulted in the game slowing down - but only when there was no sound playing. For example watching the scrolling text along the bottom of the title screen would suddenly slow down once the intro tune stopped playing. (Tip: using a really big value here helps identify what's being affected). My guess is that this is a delay that should match the timing of the main sound routine when a sound is playing.
Next I tried simply inserting a delay loop at the top of the sound routine:
L4C71_sndout: PUSH BC LD B,46 DJNZ $ POP BC
This worked pretty well and slowed the game down nicely. I fine tuned the value to 46 by running the game side by side in ubee512 and the TRS80 emulator and syncing the time taken for the mother ship to go back and forth across the screen.
Music too slow
The above worked well during general game play and for most sound effects but I noticed that when one of the little music tunes was playing it was way too slow. I could adjust the value of the above loop to get the music right but then the game would be too fast.
To track this down I used some carefully timed breaking into the debugger while the music was playing and then stepping out to the place calling the sound routine and found this:
L4A2C: DEC BC LD A,B OR C RET Z CALL L4C71_sndout JR L4A2C
A routine that simply calls the sound routine BC times. By making this routine not call my newly introduced delay in the sound routine, I could speed this up but leave the other sounds at their new slower speed. So I changed the above routine to call L471_sndout_2:
L4C71_sndout: PUSH BC ; Introduced delay loop LD B,46 DJNZ $ POP BC L4C71_sndout_2: ; Old entry point into sound routine PUSH HL
Enough with the Square Brackets
The game was now pretty much done. Sound, music and speed were all fairly close. I spent a bit more time fine tuning the timing a little - including tweaking BC values passed to L4A2C. It could be better in some regards but its close enough. For example some of the title music/animations aren't quite the right speed. The important part - the game itself - was good and I'd already spent long enough on it.
One thing that was bothering me though was the '[' characters used to display the number of lives left. On the TRS80 these characters appear as little up arrows. Since the TRS80 lo-res graphics only use 64 of the Microbee's 128 PCG characters it was easy to setup something better. First load the PCG character data:
; Load the custom PCG characters (the up arrow used for lives left) LD HL,customChars LD DE,0F800h + (192-128)*16 LD BC,10h LDIR . . . customChars: ; Up arrow DB 0,0,0,0,8h,1ch,2ah,8,8,8,8,8,8,0,0,0
Then use character:
; LD (HL),5Bh ; '[' Replace this LD (HL),192 ; with this
Finally, the port was done. The amount of effort was about what I expected. Arguably this could have been simpler by patching the existing binary and leaving it in it's original memory location. I used that approach when I patched Galaxy Invasion but it's really tedious trying to changing things and making sure you don't shift everything even one byte in memory. So this approach was a bit more work, but it's handy to have a relocatable listing come out of the process. I also learning more about how the game works this way.
Some final thoughts and tips for anyone thinking of doing something similar:
- Start with a good disassembly and reassemble it back to a binary and compare to the original to make sure you're starting with the right thing.
- Setup a development environment where you can quickly run/test/debug. You'll be doing it a lot and it will pay off.
- Be very careful with the main task of making the dis-assambled listing relocatable. Write notes in the ASM file, especially anything that you're not sure about. Many of the issues I hit were related to simple mistakes I made in this stage.
- Keep the original address as part of the labels used in the relocated listing - these will be essential if you ever need to go back to the original disassembly and need to cross reference things.
- If possible, relocate the game to somewhere far away from it's original location - it will make it easier to identify whether an address is referring to the old location or the new location.
- Learn how to use the debugger, especially memory-write break points.
- Early on try setting read and write memory breakpoints for any address ranges in the old location of the program. This will pick up any missed addresses during the relocation process.
- Always attack the simplest, most easily debuggable problem first - often you inadvertently fix other more complex looking problems at the same time.
- Continuously review any notes you've left through the listing. If stuck on a problem set breakpoints on or near these notes and see if they get hit and if so, take a closer look.
That's it. I hope this has given some useful tips and a little insight into what's involved in porting a game like this.
Oh, and you can get the final game from the Microbee Software Preservation Project (MSPP).
This is the end of Porting Meteor Mission II to Microbee.