Tank War is a pretty simple Atari 2600 Combat clone for the Commodore VIC20. One or two players drive tanks around a fixed maze shooting at each other.
Very slowly.
But then again, I guess tanks do move quite slowly anyway, so lets call it an accurate simulation.
And it has tank controls, but again, so do tanks.
When this was suggested to be added to the recent update to the Penultimate Cartridge, I found a PRG file in the archive, but that was missing the instructions screen, so I set about converting the cassette version.
You know the score with these by now, the memory on an unexpanded VIC20 is small, so you can't fit the instructions and the game in at the same time. Most games of the time got around that by loading in a program that contained only the title screen and instructions, and then loading the game over the top of that ready to play. Some were clever enough to do that in the background whilst you were reading the instructions, others left you hanging on a Loading.... screen.
The tape version of Tank War puts up a single screen of instructions, and then loads the game in the background. Should be nice and easy......
Step one then is to extract the programs. I loaded the first one from tape. It is BASIC as expected and listed it.
The code immediately jumps to line 110 (which prints the instructions), then jumps back to line 10 (which pokes a short machine code routine into memory and runs it).
The plan here is a cartridge conversion. Code I have used many times before copies a block of data from the ROM into RAM at the appropriate location and then runs it. For the multipart games, to get the second part, I replace the tape loading code with a jump back into the ROM. The code there then copies the second block of data into RAM over the top and the first and then runs that.
Here I just need to get ride of all the data statements at the start, the FOR loop and SYS command, and check which of the POKEs I need to keep. Finally I will add the SYS command in to jump back to the ROM.
Since this was quite a simple few changes, I just made the mods in the Vice VIC20 emulator I had been using to test things.
To test it, I just put a breakpoint on the jump address to check it gets called.
I tested it and .... it reset the VIC?
I was a little confused, thought I might have left some of the old SYS calls in or got the new SYS call wrong, but they all looked right.
Just to make sure I hadn't broken anything, I thought I would compare the files, and to do that, I used petcat, a useful command line utility the comes as part of the vice emulator. Pass it a PRG file and it print outs the program listing.
Hello, what's going on there.
Two magic hidden lines?
0 rem *** tank war ***
1 ifpeek(849)<>43thensys64802:rem"{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}
2 sys850:rem"{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}{del}
Those do not show in the listing as the code at the start is hidden by the REM statements full of backspace characters. That's quite neat really.
Let's see what it is doing.
1 if peek(849) <> 43 then sys 64802
This checks to see if the value at 849 is 43, if not is calls the routine at 64082 ($FD22), which is the system warm reset, which explains what I was seeing.
(I normally try to keep to talking about things in hexadecimal, as it is often easier to follow, but since a lot of this is BASIC, I will have to use a lot of decimals, the $ prefix is used to indicate hexadecimal values)
But what is it testing. Well, 849 is somewhere in the cassette buffer. Really?
So, the author knew after loading the unmodified version from tape, the value in the tape buffer at 849 would be 43. OK.
Let's check the other line.
2 sys 850
Oh, so now it has checked the right value is at 849, it starts to run code from 850.
Let's check the RAM
Oh, is that all?
849 ($0351) is 43 ($2B) and 850 ($0352) is $60, an RTS instruction. So if the right data is in the first bytes, it will execute the second byte, the RTS instruction, and return back to where it was called from. If it is wrong, it could do anything, but most likely lock up.
That means if you were to edit the file and re-SAVE it, the new version would not have those extra bytes, and so it would fail to work. Neat.
Now I know those are those lines are there, I can just delete them, along with the comment line (may as well, it makes the file smaller).
Let's see what's left.
10 data 169,1,162,1,160,1,32,186,255,169,0,162,255,160,255,32,189, 255,169,0,162,255,160
20 data 255,32,213,255,76,0,16
30 for a=0 to 29 : read b : poke 256+a, b : next : sys 256
The code in lines 10-30 contains a small machine code routine which is POKEd into addresses 256 ($100) upwards. This is the bottom of the stack. A reasonable place to put code, and one that is not exploited much.
The code is standard fare for this sort of things. Call SETLFS, SETNAM and LOADSP, which together are the equivalent of the BASIC "LOAD" command.
It then jumps to $1000 to run the newly load code, the game itself.
That means it never actually executes line 100?
100 poke649,0:poke37150,2:poke788,194:printchr$(142)chr$(8)
A little odd, maybe left over from a previous version? Let's investigate.
poke 649, 0
This sets the length of the keyboard buffer to 0. Not ideal as I need that for a "press any key" routine.
poke 37150, 2
This disables the restore key interrupt
poke 788, 194
Addresses 788 and 789 ($0314-$0315) contains the address of the routine used for hardware interrupts.
Normally this is $EABF, but this poke changes that to $EAC2. This jumps into the same routine slightly later, bypassing a call to $FFEA, which updates the jiffy clock and checks for the RUN/STOP key, so that poke will stop RUN/STOP being checked.
print chr$(142) chr$(8)
This sets uppercase font mode, and then locks it from being changed.
That all seems sensible, I presume the code originally jumped to 100 to do this before displaying the instructions, but it must have been bypassed at some point.
I changed line end form
210 print"{grn} {down}please wait...{red}"
220 goto10
to
210 print"{grn} {down}press a key...{red}"
220 poke198,0:wait198,1
230 sys41088
There is no line 10 to go back to, so I just wait for a key press and then just back into the ROM for part 2.
I borrowed a neat little "press a key" trick I have seen in several of these loaders.
poke 198, 0 : wait 198, 1
This clears the count of keys in the keyboard buffer and waits for it to change to 1. Quite neat really.
It doesn't consume the keypress, so it will still be in the keyboard buffer. (I was hoping it would mean if you pressed F1, that would be read by the game and it would start immediately, but that didn't happen, I guess it clears the queue when it starts up.)
Speaking of part 2. That was slightly unusual. The load address was $1000, rather than the usual $1001 for a BASIC program. Not really a problem, this is not BASIC, so it is using every last byte available from $1000 to $1DFF.
That $0E00 bytes plus $0200 from the intro and the small ROM loader code means it is slightly annoyingly a little over 4K. Ideally I would have fitted it all into an 4K cartridge, but I will just have to go for 8K.
In the Penultimate, I can crop it down to 4K + one extra 256 byte page, so I get most of that space back.
Time to test it out.
And there we go.
I wanted to test that it would loop back around to the start page correctly.
It took ages.
The AI is rubbish. It doesn't seem to track or intentionally fire at the player. The controls are also a bit lumpy, so it's also not that easy to wipe the AI player out.
I was hoping it would stop at 10, and it did indeed.
I guess it is first to 10 wins.
And back to the title page.
But wait, I hear you all cry (which is unlikely as there are probably only 3 of you who will ever read this.)
But wait, how did those magic bytes get added to the end of the file in the first place?
I am pleased you asked.
It is all to do with the filename.
Looking at the tape buffer after loading the Tank War.
What you get is 01 (not sure what that signifies?), then $1001, the load address and $12D0, the end address, then the filename, "TANK-WAR". The $2B and $60 are hiding amongst all the spaces on the line below.
But how do you do that?
Here I have entered a simple program and saved it with the name "NORMAL".
If I reset to clear the memory, and then load it, this is what is in the tape buffer.
The filename is there, followed by lots of zeroes.
What you need to do is make the filename longer than 16 characters.
Here you can see the "TS" I have added to the end (it wraps around to the next line on the screen). It is also shown in the "SAVING" message below that.
If I now do a reset and reload, the TS has been dropped from the filename, as only the first 16 characters are displayed.
But, if you look in the tape buffer:
There is the "TS" I secretly added.
But hang on, that's just letters, how do you get the 43 and the RTS?
Another good question.
CHR$(43) is a + in PETSCII, so that is easy
RTS is $60, which is a horizontal line in PETSCII, so you just press the right keys.
Well, not quite. There is a uneasy relationship between character codes and what is displayed on the screen PETSCII horizontal bar is $60, but when you press shift + * to get that, it shows, but in memory it is actually $40 and when you load it back, you get $C0 !?!?!?!
Even if you do POKE 7835, 96 (which should put the value $60 into address $1E9B), that puts the right value into screen RAM, but when you load it back, you get $A0.
It can drive you mad.
To save my pulling out what remaining hair I have, I tried the following poking in the values for $20, $60, $A0, $E0, all the variations + and - $40.
None of those worked.
I also tried $00 $10 $20 etc. up to $F0, and none of them end up with 60 in the file, I got $20 $40 $50 $20 $30 $C0 $D0 $A0 $B0....
It does not look like anything you can type into a BASIC SAVE command will result in $60 as required.
I think it must have been done with some assembler similar to that shown above to load the program, but setting a filename with a $60 at the end.
I think I will leave it there. There are already enough head shaped dents in my wall from dealing with Commodore screen codes vs character sets in the past. I will leave that as an exercise for the reader. I need to go for a lie down.
Advertisements
Tank War is one of the games that have been added to the upcoming release of the Penultimate, in it's normal +2 format and the new +3 version which has a built in SD2IEC drive.
More of those should be shipping this week, I will go into more detail in a full post to follow.
Minstrel 2 and 3 kits are available from my Tindie store, with worldwide shipping. Versions avaiable for ZX81 case or standalone with keyboard, and also Misntrel 3 with ZXpand microSD card interface.
I will slowly be moving things over there from my SellMyRetro store, so if there is anything that you want, let me know and I'll add it.
More info can be found here:
Patreon
You can support me via Patreon, and get access to advance previews of posts like this and behind the scenes updates. These are often in more detail than I can fit in here, and some of these posts contain bits from several Patreon posts. This also includes access to my Patreon only Discord server for even more regular updates.