define(function() { return { colorRam: null, curLineScr: null, curLineCol: null, curLineSpr: null, spriteRasters: [], rasterModes: [], backCanvas: null, backContext: null, renderedFrames: {}, frames: null, thisFrame: null, SCREENPTR: null, CHARPTR: null, IRM: null, RSEL: null, CSEL: null, DISPLAY: null, HIRES: null, EXTCOLOR : null, MULTICOLOR: null, XSCROLL: null, YSCROLL: null, BORDER: null, BG0: null, BG1: null, BG2: null, BG3: null, RASTER: null, RASTERHIT: null, VC: null, VCBASE: null, RC: null, BADLINEEN: null, IDLE: null, stateVars: [ 'SCREENPTR', 'CHARPTR', 'IRM', 'RSEL', 'CSEL', 'DISPLAY', 'HIRES', 'EXTCOLOR', 'MULTICOLOR', 'XSCROLL', 'YSCROLL', 'BORDER', 'BG0', 'BG1', 'BG2', 'BG3', 'RASTER', 'RASTERHIT', 'VC', 'VCBASE', 'RC', 'BADLINEEN', 'IDLE' ], SPR: null, r: function(addr) { switch (this.owner.MMU.vicBank) { case 0: if (addr >= 0x1000 && addr < 0x2000) { return this.owner.MMU.charRom[addr & 0x0FFF]; } else { return this.owner.MMU.ram[addr & 0x3FFF]; } case 1: return this.owner.MMU.ram[addr & 0x3FFF]; case 2: if (addr >= 0x9000 && addr < 0xA000) { return this.owner.MMU.charRom[addr & 0x0FFF]; } else { return this.owner.MMU.ram[addr & 0x3FFF]; } case 3: return this.owner.MMU.ram[addr & 0x3FFF]; } }, w: function(addr, val) { // The VIC doesn't write to memory! }, io_r: function(addr) { addr &= 63; switch (addr) { case 17: // FLAGS1 this.registers[addr] = (this.RASTER & 256) ? (this.registers[addr] | 128) : (this.registers[addr] & 127); break; case 18: // RASTER this.registers[addr] = this.RASTER & 255; break; } return (this.registers[addr] !== undefined) ? this.registers[addr] : 0; }, io_w: function(addr, val) { var i, x; addr &= 63; switch (addr) { case 0: // SPRX0 case 2: // SPRX1 case 4: // SPRX2 case 6: // SPRX3 case 8: // SPRX4 case 10: // SPRX5 case 12: // SPRX6 case 14: // SPRX7 // X-coords are "real", as opposed to the register mapped // coordinate system, which is 68 pixels indented x = this.SPR[addr >> 1].x - this.sizes.HBLL - this.sizes.BORDER + 24; x = (x & 256) + val; this.SPR[addr >> 1].x = x + this.sizes.HBLL + this.sizes.BORDER - 24; break; case 1: // SPRY0 case 3: // SPRY1 case 5: // SPRY2 case 7: // SPRY3 case 9: // SPRY4 case 11: // SPRY5 case 13: // SPRY6 case 15: // SPRY7 // Y-coords are "real", as opposed to the register mapped // coordinate system, which is 29 rasters indented this.SPR[addr >> 1].y = val + this.sizes.VBLT + 12; this.fillSpriteRasters(); break; case 16: // SPRXHI for (i = 0; i < 8; i++) { x = this.SPR[i].x - this.sizes.HBLL - this.sizes.BORDER + 24; x = (x & 255) + ((val & (1 << i)) ? 256 : 0); this.SPR[i].x = x + this.sizes.HBLL + this.sizes.BORDER - 24; } break; case 17: // FLAGS1 this.YSCROLL = val & 7; this.RSEL = !!(val & 8); this.DISPLAY = !!(val & 16); this.HIRES = !!(val & 32); this.EXTCOLOR = !!(val & 64); if (val & 128) { this.RASTERHIT |= 256; } else { this.RASTERHIT &= 255; } if (this.RSEL) { this.sizes.BORDERV = 42; this.sizes.HEIGHT = 200; } else { this.sizes.BORDERV = 46; this.sizes.HEIGHT = 192; } this.fillRasterModes(); break; case 18: // RASTER this.RASTERHIT = (this.RASTERHIT & 256) | val; break; case 19: // LPX and case 20: // LPY not supported break; case 21: // SPREN for (i = 0; i < 8; i++) { this.SPR[i].on = !!(val & (1 << i)); } this.fillSpriteRasters(); break; case 22: // FLAGS2 this.XSCROLL = val & 7; this.CSEL = !!(val & 8); this.MULTICOLOR = !!(val & 16); if (this.CSEL) { this.sizes.BORDERL = 42; this.sizes.BORDERR = 42; this.sizes.WIDTH = 320; } else { this.sizes.BORDERL = 49; this.sizes.BORDERR = 51; this.sizes.WIDTH = 304; } break; case 23: // SPRDBLY for (i = 0; i < 8; i++) { this.SPR[i].double_y = !!(val & (1 << i)); } this.fillSpriteRasters(); break; case 24: // POINTERS this.SCREENPTR = (val & 240) >> 4; this.CHARPTR = (val & 14) >> 1; break; case 25: // IRQ clears when written this.registers[addr] = 0; return; case 26: // IRM this.IRM = val; break; case 27: // SPROVER for (i = 0; i < 8; i++) { this.SPR[i].below_bg = !!(val & (1 << i)); } break; case 28: // SPRMM for (i = 0; i < 8; i++) { this.SPR[i].multicolor = !!(val & (1 << i)); } break; case 29: // SPRDBLX for (i = 0; i < 8; i++) { this.SPR[i].double_x = !!(val & (1 << i)); } break; case 30: // SPRCOLL and case 31: // SPRBGCOLL are readonly break; case 32: // BORDER this.BORDER = val & 15; break; case 33: // BG0 this.BG0 = val & 15; break; case 34: // BG1 this.BG1 = val & 15; break; case 35: // BG2 this.BG2 = val & 15; break; case 36: // BG3 this.BG3 = val & 15; break; case 37: // SPRMM0 this.SPRMM0 = val & 15; break; case 38: // SPRMM1 this.SPRMM1 = val & 15; break; case 39: // SPRC0 case 40: // SPRC1 case 41: // SPRC2 case 42: // SPRC3 case 43: // SPRC4 case 44: // SPRC5 case 45: // SPRC6 case 46: // SPRC7 this.SPR[addr - 39].col = val & 15; break; } if (this.registers[addr] !== undefined) { this.registers[addr] = val; } }, renderPixels: function(pixels, skipFrames) { var i = 0, j, k, p; var x = 0, y = 0, pos = 0, row = 0, loc = 0; var sx, cx, px, py, pixel, pixmode, mode = 0, badline, left_border, right_border, left_hbl = this.sizes.HBLL, right_hbl = this.sizes.RASTER_LENGTH - this.sizes.HBLR, top_border = this.sizes.VBLT + this.sizes.BORDER, bottom_border = this.sizes.VBLT + this.sizes.BORDER + this.sizes.HEIGHT_ORIG, locBase = this.SCREENPTR * 1024, charBase = this.CHARPTR * 2048; if (skipFrames) { this.frames += (0|(pixels / this.sizes.FRAME_SIZE)); pixels %= this.sizes.FRAME_SIZE; } y = 0|(this.thisFrame / this.sizes.RASTER_LENGTH); x = this.thisFrame % this.sizes.RASTER_LENGTH; pos = this.thisFrame * 4; if (y >= top_border && y < bottom_border) { j = this.sizes.RASTER_LENGTH * 8; } var imageData = this.backContext.getImageData(0, 0, this.sizes.RASTER_LENGTH, this.sizes.RASTER_COUNT); this.RASTER = y; if (pixels) do { left_border = this.sizes.HBLL + this.sizes.BORDERL; right_border = this.sizes.RASTER_LENGTH - this.sizes.BORDERR - this.sizes.HBLR; // "A badline condition can only occur if the DISPLAY bit // has been set for at least one cycle in line 48" if (y == 48 && this.DISPLAY) { this.BADLINEEN = true; } // Badline condition badline = this.BADLINE; this.BADLINE = this.BADLINEEN && (y >= 48 && y <= 247) && ((y & 7) == this.YSCROLL); if (this.BADLINE != badline) { this.IDLE = false; } if (y >= 48 && y <= 247) { switch (x) { // Cycle 14: load VC, reset RC on badlines case 112: this.VC = this.VCBASE; if (this.BADLINE) { this.RC = 0; } break; // Cycles 15-54: Load line, tick VC along case 120: case 128: case 136: case 144: case 152: case 160: case 168: case 176: case 184: case 192: case 200: case 208: case 216: case 224: case 232: case 240: case 248: case 256: case 264: case 272: case 280: case 288: case 296: case 304: case 312: case 320: case 328: case 336: case 344: case 352: case 360: case 368: case 376: case 384: case 392: case 400: case 408: case 416: case 424: case 432: if (this.BADLINE) { this.owner.MMU.busLock++; j = 0|((x - 120) / 8); this.curLineScr[j] = this.r(locBase + this.VC); this.curLineCol[j] = this.colorRam[this.VC]; } this.VC = (this.VC + 1) & 1023; break; // Cycle 58: Tick RC, save VC if last char line case 464: if (this.RC == 7) { this.VCBASE = this.VC; if (!this.BADLINE) { this.IDLE = true; } } if (!this.IDLE) { this.RC = (this.RC + 1) & 7; } break; } } if (y >= top_border && y < bottom_border) { sx = x - this.sizes.HBLL - this.sizes.BORDER - this.XSCROLL; cx = sx & 7; } // Sprite data read, locks the bus for 2 phi-1's per sprite // Starts at HBL on the previous line if (y < 255) { if (x > right_hbl) { y++; for (j = 0; j < this.spriteRasters[y].length; j++) { this.owner.MMU.busLock += 2; k = this.spriteRasters[y][j]; p = this.r(locBase + 1016 + k); py = y - this.SPR[k].y; if (this.SPR[k].double_y) { py >>= 1; } py = p * 64 + py * 3; this.curLineSpr[k * 3 + 0] = this.r(py + 0); this.curLineSpr[k * 3 + 1] = this.r(py + 1); this.curLineSpr[k * 3 + 2] = this.r(py + 2); } y--; } } // BUGBUG: This obviously doesn't allow for hyperscreen switch (this.rasterModes[y]) { // VBlank case 1: pixel = ((y&4) ^ (x&4)) ? 15 : 12; break; // HBlank/border case 2: if (x < left_hbl || x >= right_hbl) { pixel = ((y&4) ^ (x&4)) ? 15 : 12; } else { pixel = this.BORDER; } break; // HBlank/border/screen data case 3: if (x < left_hbl || x >= right_hbl) { pixel = ((y&4) ^ (x&4)) ? 15 : 12; } else if ((!this.BADLINEEN) || x < left_border || x >= right_border) { pixel = this.BORDER; } else { // Background pixel = -1; // Sprites wot live below the text for (j = 7; j >= 0; j--) { if (this.SPR[j].on && this.SPR[j].below_bg) { px = this.SPR[j].x + (this.SPR[j].double_x ? 48 : 24); py = this.SPR[j].y + (this.SPR[j].double_y ? 42 : 21); if ( x >= this.SPR[j].x && x < px && y >= this.SPR[j].y && y < py ) { p = x - this.SPR[j].x; if (this.SPR[j].double_x) { p >>= 1; } if (this.curLineSpr[j * 3 + (p >> 3)] & this.bitPositions[p & 7]) { pixel = this.SPR[j].col; } } } } // Text if (y >= top_border && y < bottom_border) { pixmode = (this.EXTCOLOR ? 4 : 0) | (this.HIRES ? 2 : 0) | (this.MULTICOLOR ? 1 : 0); switch (pixmode) { case 0: // Standard text mode j = this.r(this.IDLE ? 0x3FFF : (charBase + this.curLineScr[sx >> 3] * 8 + this.RC) ); if (j & this.bitPositions[cx]) { pixel = this.curLineCol[sx >> 3]; } else if (pixel == -1) { pixel = this.BG0; } break; case 1: // Multicolor text mode j = this.r(this.IDLE ? 0x3FFF : (charBase + this.curLineScr[sx >> 3] * 8 + this.RC) ); if (this.curLineCol[sx >> 3] & 8) { cx &= 6; k = ((j & this.bitPositions[cx]) ? 2 : 0) | ((j & this.bitPositions[cx+1]) ? 1 : 0); if (k == 3) { pixel = this.curLineCol[sx >> 3] & 7; } else if (pixel == -1) { switch (k) { case 0: pixel = this.BG0; break; case 1: pixel = this.BG1; break; case 2: pixel = this.BG2; break; } } } else { if (j & this.bitPositions[cx]) { pixel = this.curLineCol[sx >> 3] & 7; } else if (pixel == -1) { pixel = this.BG0; } } break; case 2: // Bitmap mode j = this.r(this.IDLE ? 0x3FFF : ((charBase & 8192) + (this.VC * 8) + this.RC) ); pixel = this.curLineScr[sx >> 3]; if (j & this.bitPositions[cx]) { pixel >>= 4; } pixel &= 15; break; case 3: // Multicolor bitmap mode j = this.r(this.IDLE ? 0x3FFF : ((charBase & 8192) + (this.VC * 8) + this.RC) ); cx &= 6; k = ((j & this.bitPositions[cx]) ? 2 : 0) | ((j & this.bitPositions[cx+1]) ? 1 : 0); switch (k) { case 0: if (pixel == -1) { pixel = this.BG0; } break; case 1: pixel = this.curLineScr[sx >> 3] >> 4; break; case 2: pixel = this.curLineScr[sx >> 3]; break; case 3: pixel = this.curLineCol[sx >> 3]; break; } pixel &= 15; break; case 4: // Extended-color text mode j = this.r(this.IDLE ? 0x3FFF : (charBase + (this.curLineScr[sx >> 3] & 63) * 8 + this.RC) ); if (j & this.bitPositions[cx]) { pixel = this.curLineCol[sx >> 3]; } else if (pixel == -1) { switch (this.curLineScr[sx >> 3] & 192) { case 0: pixel = this.BG0; break; case 64: pixel = this.BG1; break; case 128: pixel = this.BG2; break; case 192: pixel = this.BG3; break; } } break; // Cases 5, 6, 7 are invalid modes case 5: case 6: case 7: break; } } // Sprites above the text for (j = 7; j >= 0; j--) { if (this.SPR[j].on && !this.SPR[j].below_bg) { px = this.SPR[j].x + (this.SPR[j].double_x ? 48 : 24); py = this.SPR[j].y + (this.SPR[j].double_y ? 42 : 21); if ( x >= this.SPR[j].x && x < px && y >= this.SPR[j].y && y < py ) { p = x - this.SPR[j].x; if (this.SPR[j].double_x) { p >>= 1; } if (this.curLineSpr[j * 3 + (p >> 3)] & this.bitPositions[p & 7]) { pixel = this.SPR[j].col; } } } } } break; } if (pixel == -1) { // Rendering failed, probably an invalid mode pixel = 0; } pixel = this.colors[0 | pixel]; imageData.data[pos++] = pixel[0]; imageData.data[pos++] = pixel[1]; imageData.data[pos++] = pixel[2]; imageData.data[pos++] = 0xFF; x++; i++; this.thisFrame++; if (x == this.sizes.RASTER_LENGTH) { x = 0; y++; this.RASTER++; if (y == this.sizes.RASTER_COUNT) { y = 0; this.backContext.putImageData(imageData, 0, 0); this.owner.saveFrame(++this.frames, 10); this.thisFrame = 0; this.RASTER = 0; this.VCBASE = 0; this.BADLINEEN = false; this.RC = 0; pos = 0; row = 0; loc = 0; // Bit of a hack... for (j = 0; j < 40; j++) { this.curLineScr[j] = 32; } } if ((this.IRM & 1) && this.RASTERHIT == y) { this.registers[this.rg.IRQ] |= 0x81; this.owner.CPU.signal('INT'); } } if (!(this.thisFrame & 7)) { this.owner.CIA.step(); this.owner.DISK.step(); this.owner.CPU.step(); } } while (i < pixels); this.backContext.putImageData(imageData, 0, 0); // Determine which mode we ended up at switch (this.rasterModes[y]) { case 1: mode = 1; break; case 2: mode = (x < 50 || x >= right_hbl) ? 2 : 3; break; case 3: mode = (x < left_hbl || x >= right_hbl) ? 2 : (x < left_border || x >= right_border || !this.DISPLAY) ? 3 : 4 ; break; } return { mode: mode, frames: this.frames, thisFrame: this.thisFrame }; }, getState: function() { var i, ret = { image: this.backContext.getImageData(0, 0, this.sizes.RASTER_LENGTH, this.sizes.RASTER_COUNT), colorRam: new Uint8Array(this.colorRam), registers: this.registers.slice(0), spriteRasters: this.spriteRasters.slice(0), rasterModes: this.rasterModes.slice(0), SPR: this.SPR.slice(0), frames: this.frames, thisFrame: this.thisFrame }; for (i in this.stateVars) { ret[this.stateVars[i]] = this[this.stateVars[i]]; } return ret; }, setState: function(state) { this.backContext.putImageData(state.image, 0, 0); this.colorRam = new Uint8Array(state.colorRam); this.spriteRasters = state.spriteRasters.slice(0); this.rasterModes = state.rasterModes.slice(0); this.SPR = state.SPR.slice(0); this.frames = state.frames; this.thisFrame = state.thisFrame; for (i in state.registers) { this.registers[i] = state.registers[i]; } for (i in this.stateVars) { this[this.stateVars[i]] = state[this.stateVars[i]]; } }, fillRasterModes: function() { var i, j; for (i = 0, j = 0; i < this.sizes.VBLT; i++, j++) { this.rasterModes[j] = 1; } for (i = 0; i < this.sizes.BORDERV; i++, j++) { this.rasterModes[j] = 2; } for (i = 0; i < this.sizes.HEIGHT; i++, j++) { this.rasterModes[j] = 3; } for (i = 0; i < this.sizes.BORDERV; i++, j++) { this.rasterModes[j] = 2; } for (i = 0; i < this.sizes.VBLB; i++, j++) { this.rasterModes[j] = 1; } }, fillSpriteRasters: function() { var i, j, k; for (i = 0; i < this.sizes.RASTER_COUNT; i++) { this.spriteRasters[i].length = 0; } for (i = 0; i < 8; i++) { if (this.SPR[i].on) { k = this.SPR[i].double_y ? 42 : 21; for (j = this.SPR[i].y; j < (this.SPR[i].y + k) && j < this.sizes.RASTER_COUNT; j++) { this.spriteRasters[j].push(i); } } } }, reset: function() { var i, j; for (i = 0; i < 8; i++) { this.SPR[i] = { x: this.sizes.HBLL + this.sizes.BORDER - 24, y: 0, col: 0, on: false, double_x: false, double_y: false, multicolor: false, below_bg: false, hit: false, hit_bg: false }; } for (i = 0; i < this.sizes.RASTER_COUNT; i++) { this.spriteRasters[i] = []; } this.fillRasterModes(); this.renderedFrames = {}; this.backContext.fillStyle = 'black'; this.backContext.fillRect(0, 0, this.sizes.RASTER_LENGTH, this.sizes.RASTER_COUNT); this.frames = 0; this.thisFrame = 0; this.VC = 0; this.RC = 0; this.VCBASE = 0; this.BADLINEEN = false; this.IDLE = true; }, init: function() { this.sizes.RASTER_LENGTH = this.sizes.HBLL + this.sizes.BORDERL + this.sizes.WIDTH + this.sizes.BORDERR + this.sizes.HBLR; this.sizes.RASTER_COUNT = this.sizes.VBLT + this.sizes.BORDERV + this.sizes.HEIGHT + this.sizes.BORDERV + this.sizes.VBLB; this.sizes.FRAME_SIZE = this.sizes.RASTER_LENGTH * this.sizes.RASTER_COUNT; this.colorRam = new Uint8Array(1000); this.curLineScr = new Uint8Array(40); this.curLineCol = new Uint8Array(40); this.curLineSpr = new Uint8Array(24); this.SPR = []; this.backCanvas = document.createElement('canvas'); this.backCanvas.width = this.sizes.RASTER_LENGTH; this.backCanvas.height = this.sizes.RASTER_COUNT; this.backContext = this.backCanvas.getContext('2d'); this.reset(); }, registers: [ 0, // Sprite 0: X 0, // Sprite 0: Y 0, // Sprite 1: X 0, // Sprite 1: Y 0, // Sprite 2: X 0, // Sprite 2: Y 0, // Sprite 3: X 0, // Sprite 3: Y 0, // Sprite 4: X 0, // Sprite 4: Y 0, // Sprite 5: X 0, // Sprite 5: Y 0, // Sprite 6: X 0, // Sprite 6: Y 0, // Sprite 7: X 0, // Sprite 7: Y 0, // Sprite X coordinate MSBs 0, // Flags One 0, // Current raster 0, // Light pen X 0, // Light pen Y 0, // Sprite enable flags 0, // Flags Two 0, // Sprite Y-double flags 0, // Pointers 0, // Interrupt flags 0, // Interrupt enables 0, // Sprite priority 0, // Sprite multicolor flags 0, // Sprite X-double flags 0, // Sprite-sprite collision 0, // Sprite-bg collision 0, // Color: border 0, // Color: BG 0 0, // Color: BG 1 0, // Color: BG 2 0, // Color: BG 3 0, // Color: Sprite multi 0 0, // Color: Sprite multi 1 0, // Color: Sprite 0 0, // Color: Sprite 1 0, // Color: Sprite 2 0, // Color: Sprite 3 0, // Color: Sprite 4 0, // Color: Sprite 5 0, // Color: Sprite 6 0 // Color: Sprite 7 ], rg: { SPRX0: 0, SPRY0: 1, SPRX1: 2, SPRY1: 3, SPRX2: 4, SPRY2: 5, SPRX3: 6, SPRY3: 7, SPRX4: 8, SPRY4: 9, SPRX5: 10, SPRY5: 11, SPRX6: 12, SPRY6: 13, SPRX7: 14, SPRY7: 15, SPRXHI: 16, FLAGS1: 17, RASTER: 18, LPX: 19, LPY: 20, SPREN: 21, FLAGS2: 22, SPRDBLY: 23, POINTERS: 24, IRQ: 25, IRM: 26, SPROVER: 27, SPRMM: 28, SPRDBLX: 29, SPRCOLL: 30, SPRBGCOLL:31, BORDER: 32, BG0: 33, BG1: 34, BG2: 35, BG3: 36, SPRMM0: 37, SPRMM1: 38, SPRC0: 39, SPRC1: 40, SPRC2: 41, SPRC3: 42, SPRC4: 43, SPRC5: 44, SPRC6: 45, SPRC7: 46 }, sizes: { HBLL: 78, HBLR: 22, VBLT: 9, VBLB: 19, BORDER: 42, BORDERV: 42, BORDERL: 42, BORDERR: 42, WIDTH: 320, HEIGHT: 200, WIDTH_ORIG: 320, HEIGHT_ORIG: 200 }, colors: [ [0x00, 0x00, 0x00], // black [0xFF, 0xFF, 0xFF], // white [0x68, 0x37, 0x2B], // red [0x70, 0xA4, 0xB2], // cyan [0x6F, 0x3D, 0x86], // magenta [0x58, 0x8D, 0x43], // green [0x35, 0x28, 0x79], // blue [0xB8, 0xC7, 0x6F], // yellow [0x6F, 0x4F, 0x25], // orange [0x43, 0x39, 0x00], // brown [0x9A, 0x67, 0x59], // light red [0x44, 0x44, 0x44], // grey 1 [0x6C, 0x6C, 0x6C], // grey 2 [0x9A, 0xD2, 0x84], // light green [0x6C, 0x5E, 0xB5], // light blue [0x95, 0x95, 0x95] // grey 3 ], bitPositions: [128, 64, 32, 16, 8, 4, 2, 1], endpointStrings: [ 'Offline', 'Vertical blanking', 'Horizontal blanking', 'Border', 'Screen' ] }; });