Sunday, 17 November 2024

Fixing Autofire in VIC20 Bandits

Over the years of adding various games to the Penultimate cartridge, I have come across a few that behave oddly when you have a datasette or SD2IEC plugged into the VIC.

Games that automatically start or automatically fire, ones that don't register the joystick at all, and one that only moved right.

These games behave normally when there is not an SD2IEC connected, but play up when there is one.

One such game is Bandits. That seems to just autofire all the time, it does move correctly, but fire is stuck on.

For inexplicable reasons, joystick up, down, left and fire are read from port A on VIA #1. Joystick right is read from port B on VIA #2. I mean why? Who thought that was acceptable. Fire on a different port from the directions would have been more acceptable, maybe even useful, but why aren't they all just on the one port?

Port A on VIA#1 is arranged as follows:

  • VIA #1 Port A 0 -IEC Clock
  • VIA #1 Port A 1 -IEC Data
  • VIA #1 Port A 2 -Joystick Up
  • VIA #1 Port A 3 -Joystick Down
  • VIA #1 Port A 4 -Joystick Left
  • VIA #1 Port A 5 -Joystick Fire
  • VIA #1 Port A 6 -Cassette Sense
  • VIA #1 Port A 7 -IEC Attention

VIA2 Port B is keyboard column outputs

  • VIA #2 Port B 0 -Keyboard Column 0
  • VIA #2 Port B 1 -Keyboard Column 1
  • VIA #2 Port B 2 -Keyboard Column 2
  • VIA #2 Port B 3 -Keyboard Column 3 / Cassette Write
  • VIA #2 Port B 4 -Keyboard Column 4
  • VIA #2 Port B 5 -Keyboard Column 5
  • VIA #2 Port B 6 -Keyboard Column 6
  • VIA #2 Port B 7 -Keyboard Column 7 / Joystick Right

I am not sure if there were some last minute changes, but the arrangement of pins on that port, and the subset of those that appear on the userport are a little odd.

What is the point of cassette sense without the other cassette pins? What is the point of the bus side of the IEC ATN pin? Why have Up, Down, Left and Fire but not Right?

The multiple uses of VIA #1 Port A can cause problems with games, depending on how they test the port.

When you read that port, you get the joystick information you want, but also IEC and datasette signals that you don't.

It is fine if you work on the principle of testing if bit 5 is 0 to see if fire is pressed. But some games instead compare the value of the whole port against constant values. Those constant values are based on what they read back from their machines, which depends on the state of the things what they had plugged in, which does not necessarily match what you have plugged into your machine.

The standard resting state of that port on a VIC20 without a datasette or IEC device is $7E. If you press fire on a joystick, bit 5 changes, so the value now reads $5E. So you can write your game to check if the value changes to $5E, and if it does, fire must have been pressed, so you launch a rocket at a poor defenceless alien menace.

However, if the game was loaded from tape and the play button is still pressed down, the resting state would instead be $3E and when fire was pressed, would change to $1E. If you code was checking for $5E it would fail and you would be invaded by the aliens.

Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Value
IEC ATN
Cassette Sense
Joystick Fire
Joystick Left
Joystick Down
Joystick Up
IEC Data
IEC Clock
0
1
1
1
1
1
1
0
$7E
0
1
0
1
1
1
1
0
$5E
0
0
1
1
1
1
1
0
$3E
0
0
0
1
1
1
1
0
$1E

I have seen a few games with "please stop tape" messages, because you will get different values back from the port if the play button is still pressed down, and their code has been written assuming it was not. So rather than fixing their code, they just ask the user to press stop.

Aside: If I don't mention this, everyone will tell me about it in the comments, so yes, I am aware of the video by Robin (8 bit show and tell) about the C64 datasette controls.

And yes, I am aware of the Aquarius game from Aleksi Eeben that uses the datasette as a game controller (I was the one that arranged for TFW8b to duplicate the tapes for Aleksi).

End of aside.

With the IEC port, the power on state of the signals is usually the same, clock held low (asserted) by the VIC20, and data floating high.

That changes when you access the device, after that, the clock line is usually released by the VIC20, so both signals float high and stay high.

In that case, the resting value of $7E changes after an IEC access to $7F (or 126 changes to 127 in decimal in the BASIC demonstration below).

Again, if your code is checking for absolute values, then all your tests will fail because that one bit has changed.

Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Value
IEC ATN
Cassette Sense
Joystick Fire
Joystick Left
Joystick Down
Joystick Up
IEC Data
IEC Clock
0
1
1
1
1
1
1
0
$7E
0
1
0
1
1
1
1
0
$5E
0
1
1
1
1
1
1
1
$7F
0
1
0
1
1
1
1
1
$5F

An example of that is the game Multitron. It reads the port and compares against the resting state value it expects, if it does not see that, it assumes the user has pressed fire or moved the joystick and starts the game.

In the game, it tests for absolute values to see if you have moved left or pressed fire, all of which fail because bit 0 is now a 1 and on the developers machine it was a 0. Right is on a separate port, so reading that and comparing absolute values does work, so you can move right, but only right. In my testing notes, I had this down as a keyboard only game, as that is how it appeared both in the vice emulator and on a real VIC20.

They did have code in place to fix the datasette sense problem. When they read the value in, they OR it with $40. That forces bit 6 (datasette sense) to be a 1, so their comparisons become valid.

Fixing the IEC as well was just a case of changing that to OR the value read with $c3, this forces all the IEC and datasette bits high. I also had to adjust all the comparison values to expect the new resting state of $ff.

Rather unexpectedly, the game now has a title screen. This was never displayed before (or only for a blink of an eye) as the first time the game checked, it looked like fire was pressed, so started the game immediately.

It is quite a nice space invaders with a different mechanic for the invaders movements and limited firepower that takes a while to recharge, and plays much better with joystick.

I have fixed a couple of games like that, and a couple of others just needed the IEC port initialising correctly before starting the game.

Bandits, however, is proving to be more of a challenge. The test it is doing for fire is failing, so it is always firing. The other directions are fine, only fire is a problem.

The code is rather convoluted. I did go through it all in an excessively long Patreon post on the subject, but I decided to cut a lot of that out.

   lda $9111

   asl

   asl

   asl

   bcs label_b410

   and #$ef

label_b410

   asl $9120

   ror

   ora #$04

What it is doing is shifting bits around to rearrange what has been read from the port, and then doing comparisons on that.

The first two left shifts get rid of the IEC ATN and datasette sense signals, so that's good. It then reads the other port, extracts the joystick right and rolls that back into the first result, so they are all in the same byte. The OR at the end gets rid of the IEC clock signal.

For some reason, it tests the actual fire signal by shifting it out into the carry. If fire is pressed, it then sets one of the remaining bits low and uses that for future tests.

Unfortunately, that spare bit is the one used for the IEC data signal. It assumes that it will be high at the start, and sets it low if fire was pressed.

With an IEC device attached, that line ends up low, so the value ends up as a 0 whether fire is pressed or not, so the game thinks fire is pressed all the time.

The autofire can score quite well just sitting there, but ideally you would have control.

The code is quite tight, so there is no space to alter that in place to fix the issue. I did get it to work by changing the read port command to jump elsewhere, read the port, OR the data line high and return, but that adds quite a few cycles and I din't want to alter the timing in the middle of the game loop.

Checking out the signals on the scope, this is how Bandits starts up with no IEC device present. All the lines go low during reset, and then data (yellow, top) and ATN (blue, bottom) are released and float high. Clock (green, middle) stays low throughout.

However, if there is an IEC device present, the startup looks like this.

ATN goes back high, but for some reason, the data line does not. And that line reading 0 is what makes Bandits autofire.

VIC20 cartridges are started very early on in the boot process, so nothing is initialised. (frustratingly there is an entirely unnecessary jump to a routine a few bytes away and back that means the RAM which means the stack has to be working to do this, so VIC20 Dead Test cartridges need that RAM to be working before they can start)

Most cartridges just call the standard KERNAL routines to initialise the IO devices etc.

The normal start up those looks like this.

Do you see the little blip on the middle trace, the clock line? Let's zoom in.

It first releases the data line, then ATN, then releases the clock for 25uS and then goes back to the usual state of ATN and data high and clock low.

If there is an IEC device present, that will see the combination of ATN low and clock low as the start of a conversation, and will pull data low to acknowledge that it is ready to talk.

The normal VIC20 initialisation process pulses the clock line high to indicate to the IEC device that the conversation has ended, and as you can see, it releases the data line and we get back to normal.

Bandits is setting up various timers, so it makes sense to just initialise all the values in one go, just with the unfortunate effect of the IEC bus initialiation being skipped.

It initialises the port, releasing the data line but still driving the clock line low (like the normal VIC20 initialisation), and then releases ATN.

That leaves the "start of a conversation" signal in place, so the drive is duty bound to respond by pulling the data line low. And it stays low. There is no pulse on the clock line to tidy things up.

One of the odd things about Bandits, is that you can "fix" the problem by pressing the drive reset button. That snaps it out of this "waiting for a conversation" state and it releases the data line and the auto-firing stops.

There didn't seem an appropriate place I could add in the clock pulse that would clear things, so I went for the alternate option of changing the initialisation values so that it released the clock line and then releasing the ATN line. That means it should never enter the "start of a conversation" state, and so the drive should not assert the data line, and the guns will be silenced.

Hurrah.

All it took was changing two of the 65536 bits in the 8K ROM - it just took a while to work out which ones.

Can I take the rest of the week off now?


Advertisements

The patched versions of Bandits and Multitron are included in the new Penultimate +3 version which also 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 (andn now available as PCB only).

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:

Bluesky

For those interested in such things, I can now be found on Bluesky, and I expect to be posting more there.

Patreon

You can support me via Patreon, and get access to advance previews of posts like this and behind the scenes updates. This particular post was originally about twice as long before I cut it down, so if you want all that detail, join the Patreon. This also includes access to my Patreon only Discord server for even more regular updates.

Sunday, 10 November 2024

VIC20 Multipart Game Conversions - Tank War

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.