Foreword
In the previous blog we managed to emulate the flashing borders of the loader. In this blog we will emulate the splash screen.The screenshot of the splash screen might be copyrighted. I am not sure. If contacted by the publisher of the Dan Dare I will remove the screenshot by request.
Theory on the VIC-II graphic Modes
Before we figure out how to emulate rendering the splash screen, lets cover a bit of theory on the graphic modes of the VIC II.Let us first zoom into what defines the graphic modes in the VIC II. There is two important bits:
- Bit 5 of register D011: Switch bitmap mode on/off
- Bit 4 of register D016: Switch multi-color mode on/off
- Bit#0: not used
- Bit#1-#3: Address Bits 11-13 of the Character Set (*2048)
- Bit#4-#7: Address Bits 10-13 of the Screen RAM (*1024)
You might notice that Most Significant bit (MSB) of these two base addresses is bit number 13 and not bit number 15. So, does this mean that the VIC can only acess the first 16KB of memory? Luckily not! Bits 14 and 15 is supplied by register DD00 (a register of CIA 2).
The following image from http://dustlayer.com/vic-ii/2013/4/22/when-visibility-matters explains very nicely how tese two bits work:
Notice the inverse bit order.
Emulating the Splash Screen
To emulate the splash screen we need to find out what is the graphics mode while the splash screen is displaying.So, lets kick off the loading process and wait for the following screen to appear:
Add this point click the stop button. We now need to inspect the contents of location D011 and D016 to determine the graphics mode:
- Location D011 has value 3B. Bit 5 is switched on, meaning bitmap mode is enabled
- Location D016 has value D8. Bit 4 is switched on, meaning multi color mode is enabled.
The four colors used in text-multicolor mode differs a bit from the four colours used in bitmap multi-colour mode. For now, we will only worry about bitmap multi-colour mode.
The following table, again taken from the dustlayer website, explain the four colors of multi-color bitmap mode:
Next, lets determine the bitmap/ screen RAM base addresses. First lets get hold of bit 14 & 15 via location DD00. The value we get via inspection is 4. The lowest two bits are zero. Via the lookup table from the previous section we determine that the VIC II operate in the bank C000-FFFF.
Lets try to put everything in code.The magic will happen with the method drawCharline:
function drawCharline() { var bitmapMode = ((localMem.readMem(0xd011) & 0x20) != 0) ? 1 : 0; var multicolorMode = ((localMem.readMem(0xd016) & 0x10) != 0) ? 1 : 0; var screenMode = (multicolorMode << 1) | bitmapMode; switch (screenMode) { //text mode, normal case 0: drawTextModeNormal(charPosInMem + cycleInLine - 5); break; //bitmap mode, normal case 1: break; //text mode, multi color case 2: break; //bitmap mode, multi color case 3: drawBitmapModeMultiColor(charPosInMem + cycleInLine - 5); break; }
}
We start by throwing the bitmap-bit and the multi-color bit together to form a number. We then use a switch statement to serve the applicable graphics mode. For now, only Textmode-normal, and bitmap-multicolor is defined.
Text mode normal looks as follows:
function drawTextModeNormal(charPos) { var screenCode = localMem.readMem(1024 + charPos); var currentLine = localMem.readCharRom((screenCode << 3) + ((cycleline - 42) & 7)); var textColor = localMem.readMem(0xd800 + charPos) & 0xf; var backgroundColor = localMem.readMem(0xd021) & 0xf; var currentCol = 0; for (currentCol = 0; currentCol < 8; currentCol++) { var pixelSet = (currentLine & 0x80) == 0x80; if (pixelSet) { imgData.data[posInCanvas + 0] = colors[textColor][0]; imgData.data[posInCanvas + 1] = colors[textColor][1]; imgData.data[posInCanvas + 2] = colors[textColor][2]; imgData.data[posInCanvas + 3] = 255; } else { imgData.data[posInCanvas + 0] = colors[backgroundColor][0]; imgData.data[posInCanvas + 1] = colors[backgroundColor][1]; imgData.data[posInCanvas + 2] = colors[backgroundColor][2]; imgData.data[posInCanvas + 3] = 255; } currentLine = currentLine << 1; posInCanvas = posInCanvas + 4; } }
Pretty straightforward!
Next look at the bitmap-multicolor mode:
function drawBitmapModeMultiColor(charPos) { var currentLine = localMem.readMem(0xe000+(charPos << 3) + ((cycleline - 42) & 7)); var textColor = localMem.readMem(0xd800 + charPos); var backgroundColor = localMem.readMem(0xd021); var color1 = (localMem.readMem(49152 + charPos) & 0xf0) >> 4; var color2 = localMem.readMem(49152 + charPos) & 0xf; var color3 = localMem.readMem(0xd800 + charPos) & 0xf; var colorArray = [backgroundColor, color1, color2, color3]; var pixPair = 0; for (pixPair = 0; pixPair < 4; pixPair++) { var colorValue = (currentLine >> 6) & 3; imgData.data[posInCanvas + 0] = colors[colorArray[colorValue]][0]; imgData.data[posInCanvas + 1] = colors[colorArray[colorValue]][1]; imgData.data[posInCanvas + 2] = colors[colorArray[colorValue]][2]; imgData.data[posInCanvas + 3] = 255; imgData.data[posInCanvas + 4] = colors[colorArray[colorValue]][0]; imgData.data[posInCanvas + 5] = colors[colorArray[colorValue]][1]; imgData.data[posInCanvas + 6] = colors[colorArray[colorValue]][2]; imgData.data[posInCanvas + 7] = 255; currentLine = currentLine << 2; posInCanvas = posInCanvas + 8; } }
A couple of things happening:
- A 4 colour tablet is created for the pixels to be drawn
- 2 pixels is shifted at a time. The resulting pixel color is drawn twice
- You will also notice that we are adding 49152 to the address when we are reading from memory. This is a bit of hardcoding just to get the splash screen displayed
What went wrong? In the next section we will do some investigations...
Investigating why game loading got stuck
Time for some debugging again. We will start by waiting for the moment when the splash screen has just disappeared then we will stop our emulator.At this point we need to add a breakpoint at a suitable place to avoid endless stepping. A typical place would be where the bit read gets shifted into a temporary memory location. Here is the piece of assembly where it is happening:
0363 AD 0D DC LDA $DC0D 0366 8E 0F DC STX $DC0F 0369 4A LSR A 036A 4A LSR A 036B 26 C0 ROL $C0 036D A5 C0 LDA $C0 036F 88 DEY 0370 F0 00 BEQ $0372
We will place a breakpoint at location 36B.
The Y register plays a crucial role in this piece of code. Since, at this point in time we are reading bytes, the Y register decrements from 8 each time till it reaches 0. So, we RUN a couple of times till Y has the value of one.When Y is one we slowly step through...
The interesting line is as follows:
03ba STA ($f9),Y
From this we see that F9/FA stores the current address where the next byte read should be written to.
If you read further down the assembly code, you will also discover that the end address is stored at FB/FC for the current block:
03BA 91 F9 STA ($F9),Y 03BC A0 05 LDY #$05 03BE 84 01 STY $01 03C0 45 AF EOR $AF 03C2 85 AF STA $AF 03C4 E6 F9 INC $F9 03C6 D0 05 BNE $03CD 03C8 EE 20 D0 INC $D020 03CB E6 FA INC $FA 03CD A5 F9 LDA $F9 03CF C5 FB CMP $FB 03D1 D0 0B BNE $03DE 03D3 A5 FA LDA $FA 03D5 C5 FC CMP $FC 03D7 D0 05 BNE $03DE
So lets remove the breakpoint and let it run for intervals of about two seconds or so.
At one point you will notice that the game loader starts to load information in the peripheral region (D000-E000). This sounds dangerous. If you, however, look at the assembly instruction at location 3B8, we will realise that the peripheral region is disabled when the write happens:
03B8 84 01 STY $01
At this point a zero gets written to location 1. When you write a zero to this location, RAM will be visible at all three areas (e.g. BASIC ROM, KERNEL ROM and IO region).
Our emulator, however, doesn't check for this. To cater for this scenario, we need to make the following adjustment to our memory class:
function IOEnabled() { var temp = mainMem[1] & 3; var temp2 = mainMem[1] & 4; return (temp2 != 0) & (temp != 0); } this.readMem = function (address) { if ((address >= 0xa000) & (address <=0xbfff)) return basicRom[address & 0x1fff]; else if ((address >= 0xe000) & (address <=0xffff) & kernelEnabled()) return kernalRom[address & 0x1fff]; else if ((address >= 0xdc00) & (address <= 0xdcff) & IOEnabled()) { return ciaRead(address); } else if (address == 1) { var temp = mainMem[address] & 239; if (!playPressed) temp = temp | 16; return temp; } return mainMem[address]; } this.writeMem = function (address, byteval) { if ((address >= 0xdc00) & (address <= 0xdcff) & IOEnabled()) { ciaWrite(address, byteval); return; } else if (address == 1) { var temp = byteval & 32; temp = temp >> 5; tape.setMotorOn(temp); } mainMem[address] = byteval; }
Lets restart our emulator with these changes. This time we past the stuck screen:
You can sort of identify the intro screen between the garbled characters. We will fix the intro screen in the next blog.
In Summary
In this blog we managed to emulate the spalsh screen. In the next blog I will fix up the Intro screen.Till next time!
No comments:
Post a Comment