/** * This file is a part of JaC64 - a Java C64 Emulator * Main Developer: Joakim Eriksson (Dreamfabric.com) * Contact: joakime@sics.se * Web: http://www.dreamfabric.com/c64 * --------------------------------------------------- */ package com.dreamfabric.jac64; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.applet.*; import javax.swing.JPanel; /** * Implements the VIC chip + some other HW * * @author Joakim Eriksson (joakime@sics.se) / main developer, still active * @author Jan Blok (jblok@profdata.nl) / co-developer during ~2001 * @version $Revision: 1.11 $, $Date: 2006/05/02 16:26:26 $ */ public class C64Screen extends ExtChip implements Observer, MouseListener, MouseMotionListener { public static final String version = "1.11"; public static final int SERIAL_ATN = (1 << 3); public static final int SERIAL_CLK_OUT = (1 << 4); public static final int SERIAL_DATA_OUT = (1 << 5); public static final int SERIAL_CLK_IN = (1 << 6); public static final int SERIAL_DATA_IN = (1 << 7); public static final int RESID_6581 = 1; public static final int RESID_8580 = 2; public static final int JACSID = 3; public static final boolean IRQDEBUG = false; public static final boolean SPRITEDEBUG = false; public static final boolean IODEBUG = false; public static final boolean VIC_MEM_DEBUG = false; public static final boolean BAD_LINE_DEBUG = false; public static final boolean STATE_DEBUG = false; public static final boolean DEBUG_IEC = false; public static final boolean DEBUG_CYCLES = false; public static final int IO_UPDATE = 37; // This is PAL speed! - will be called each scan line... private static final int VIC_IRQ = 1; // This might be solved differently later!!! public static final int CYCLES_PER_LINE = VICConstants.SCAN_RATE; // ALoow the IO to write in same as RAM public static final int IO_OFFSET = CPU.IO_OFFSET; public static final boolean SOUND_AVAIABLE = true; public static final Color TRANSPARENT_BLACK = new Color(0, 0, 0x40, 0x40); public static final Color DARKER_0 = new Color(0, 0, 0x40, 0x20); public static final Color LIGHTER_0 = new Color(0xe0, 0xe0, 0xff, 0x30); public static final Color DARKER_N = new Color(0, 0, 0x40, 0x70); public static final Color LIGHTER_N = new Color(0xe0, 0xe0, 0xff, 0xa0); public static final Color LED_ON = new Color(0x60, 0xdf, 0x60, 0xc0); public static final Color LED_OFF = new Color(0x20, 0x60, 0x20, 0xc0); public static final Color LED_BORDER = new Color(0x40, 0x60, 0x40, 0xa0); public static final int LABEL_COUNT = 32; private Color[] darks = new Color[LABEL_COUNT]; private Color[] lites = new Color[LABEL_COUNT]; private int colIndex = 0; // This is the screen width and height used... private final static int SC_WIDTH = 384; //403; private final static int SC_HEIGHT = 284; private final int SC_XOFFS = 32; // Done: this should be - 24! private final int SC_SPXOFFS = SC_XOFFS - 24; private final int FIRST_VISIBLE_VBEAM = 15; private final int SC_SPYOFFS = FIRST_VISIBLE_VBEAM + 1; private IMonitor monitor; private int targetScanTime = 20000; private int actualScanTime = 20000; private long lastScan = 0; private long nextIOUpdate = 0; private boolean DOUBLE = false; private int reset = 100; private C64Canvas canvas; private int[] memory; private Keyboard keyboard; ExtChip sidChip; CIA cia[]; // C1541 c1541; C1541Chips c1541Chips; TFE_CS8900 tfe; int iecLines = 0; // for disk emulation... int cia2PRA = 0; int cia2DDRA = 0; AudioClip trackSound = null; AudioClip motorSound = null; private int lastTrack = 0; private int lastSector = 0; private boolean ledOn = false; private boolean motorOn = false; // This is an IEC emulation (non ROM based) boolean emulateDisk = false; //true; //!CPU.EMULATE_1541; // false; private int[] cbmcolor = VICConstants.COLOR_SETS[0]; // ------------------------------------------------------------------- // VIC-II variables // ------------------------------------------------------------------- public int vicBank; public int charSet; public int videoMatrix; public int videoMode; // VIC Registers int irqMask = 0; int irqFlags = 0; int control1 = 0; int control2 = 0; int sprXMSB = 0; int sprEN = 0; int sprYEX = 0; int sprXEX = 0; int sprPri = 0; int sprMul = 0; int sprCol = 0; int sprBgCol = 0; int sprMC0 = 0; int sprMC1 = 0; int vicMem = 0; int vicMemDDRA = 0; int vicMemDATA = 0; // Read for debugging on other places... public int vbeam = 0; // read at d012 public int raster = 0; int bCol = 0; int bgCol[] = new int[4]; private int vicBase = 0; private boolean badLine = false; private int spr0BlockSel; // New type of position in video matrix - Video Counter (VIC II docs) int vc = 0; int vcBase = 0; int rc = 0; int vmli = 0; // The current vBeam pos - 9... => used for keeping track of memory // position to write to... int vPos = 0; int mpos = 0; int displayWidth = SC_WIDTH; int displayHeight = SC_HEIGHT; int offsetX = 0; int offsetY = 0; // Cached variables... boolean gfxVisible = false; boolean paintBorder = false; boolean paintSideBorder = false; int borderColor = cbmcolor[0]; int bgColor = cbmcolor[1]; private boolean extended = false; private boolean multiCol = false; private boolean blankRow = false; private boolean hideColumn = false; int multiColor[] = new int[4]; // 48 extra for the case of an expanded sprite byte int collissionMask[] = new int[SC_WIDTH + 48]; Sprite sprites[] = new Sprite[8]; private Color colors[] = null; private int horizScroll = 0; private int vScroll = 0; private Image image; private Graphics g2; // The font is in a copy in "ROM"... private int charMemoryIndex = 0; // Caching all 40 chars (or whatever) each "bad-line" private int[] vicCharCache = new int[40]; private int[] vicColCache = new int[40]; public Image screen = null; private MemoryImageSource mis = null; private AudioDriver audioDriver; // The array to generate the screen in Extra rows for sprite clipping // And for clipping when scrolling (smooth) int mem[] = new int[SC_WIDTH * (SC_HEIGHT + 10)]; int rnd = 754; String message; String tmsg = ""; int frame = 0; private boolean updating = false; boolean displayEnabled = true; boolean irqTriggered = false; long lastLine = 0; long firstLine = 0; long lastIRQ = 0; int potx = 0; int poty = 0; boolean button1 = false; boolean button2 = false; // This variable changes when Kernal has installed // a working ISR that is reading the keyboard private boolean isrRunning = false; private int ciaWrites = 0; public C64Screen(IMonitor m, boolean dob) { monitor = m; DOUBLE = dob; setScanRate(50); makeColors(darks, DARKER_0, DARKER_N); makeColors(lites, LIGHTER_0, LIGHTER_N); } public void setAutoscale(boolean val) { DOUBLE = val; canvas.setAutoscale(val); } private void makeColors(Color[] colors, Color c1, Color c2) { int a0 = c1.getAlpha(); int r0 = c1.getRed(); int g0 = c1.getGreen(); int b0 = c1.getBlue(); int an = c2.getAlpha(); int rn = c2.getRed(); int gn = c2.getGreen(); int bn = c2.getBlue(); int lc = LABEL_COUNT / 2; for (int i = 0, n = lc; i < n; i++) { colors[i] = colors[LABEL_COUNT - i - 1] = new Color(((lc - i) * r0 + (i * rn)) / lc, ((lc - i) * g0 + (i * gn)) / lc, ((lc - i) * b0 + (i * bn)) / lc, ((lc - i) * a0 + (i * an)) / lc); } } public void setColorSet(int c) { if (c >= 0 && c < VICConstants.COLOR_SETS.length) { cbmcolor = VICConstants.COLOR_SETS[c]; borderColor = cbmcolor[bCol]; bgColor = cbmcolor[bgCol[0]]; for (int i = 0, n = 8; i < n; i++) { sprites[i].color[0] = bgColor; sprites[i].color[1] = cbmcolor[sprMC0]; sprites[i].color[3] = cbmcolor[sprMC1]; } } } public CIA[] getCIAs() { return cia; } public void setSID(int sid) { switch (sid) { case RESID_6581: case RESID_8580: if (!(sidChip instanceof RESIDChip)) { if (sidChip != null) sidChip.stop(); sidChip = new RESIDChip(cpu, audioDriver); } ((RESIDChip) sidChip).setChipVersion(sid); break; case JACSID: if (!(sidChip instanceof SIDChip)) { if (sidChip != null) sidChip.stop(); sidChip = new SIDChip(cpu, audioDriver); } break; } } public void setScanRate(double hertz) { // Scan time for 10 scans... targetScanTime = (int) (1000000 / hertz); float diff = 1.0f * VICConstants.SCAN_RATE / 65; } public int getScanRate() { return (1000000 / targetScanTime); } public int getActualScanRate() { // This should be calculated... if it is too slow it will be // shown here return (1000000 / actualScanTime); } public void setIntegerScaling(boolean yes) { canvas.setIntegerScaling(yes); } public JPanel getScreen() { return canvas; } public AudioDriver getAudioDriver() { return audioDriver; } public boolean ready() { return isrRunning; } public void setDisplayFactor(double f) { displayWidth = (int) (SC_WIDTH * f); displayHeight = (int) (SC_HEIGHT * f); crtImage = null; } public void setDisplayOffset(int x, int y) { offsetX = x; offsetY = y; } public void dumpGfxStat() { monitor.info("Char MemoryIndex: 0x" + Integer.toString(charMemoryIndex, 16)); monitor.info("CharSet adr: 0x" + Integer.toString(charSet, 16)); monitor.info("VideoMode: " + videoMode); monitor.info("Vic Bank: 0x" + Integer.toString(vicBank, 16)); monitor.info("Video Matrix: 0x" + Integer.toString(videoMatrix, 16)); monitor.info("Text: extended = " + extended + " multicol = " + multiCol); monitor.info("24 Rows on? " + (((control1 & 0x08) == 0) ? "yes" : "no")); monitor.info("YScroll = " + (control1 & 0x7)); monitor.info("$d011 = " + control1); monitor.info("IRQ Latch: " + Integer.toString(irqFlags, 16)); monitor.info("IRQ Mask: " + Integer.toString(irqMask, 16)); monitor.info("IRQ RPos : " + raster); for (int i = 0, n = 8; i < n; i++) { monitor.info("Sprite " + (i + 1) + " pos = " + sprites[i].x + ", " + sprites[i].y); } monitor.info("IRQFlags: " + getIRQFlags()); monitor.info("NMIFlags: " + getNMIFlags()); monitor.info("CPU IRQLow: " + cpu.getIRQLow()); monitor.info("CPU NMILow: " + cpu.NMILow); monitor.info("Current CPU cycles: " + cpu.cycles); monitor.info("Next IO update: " + nextIOUpdate); } public void setSoundOn(boolean on) { audioDriver.setSoundOn(on); } public void setStick(boolean one) { keyboard.setStick(one); } public void registerHotKey(int key, int mod, String script, Object o) { keyboard.registerHotKey(key, mod, script, o); } public void setKeyboardEmulation(boolean extended) { monitor.info("Keyboard extended: " + extended); keyboard.stickExits = !extended; keyboard.extendedKeyboardEmulation = extended; } public void init(CPU cpu) { super.init(cpu); this.memory = cpu.getMemory(); c1541Chips = cpu.getDrive().chips; c1541Chips.initIEC2(this); c1541Chips = cpu.getDrive().chips; c1541Chips.setObserver(this); for (int i = 0, n = sprites.length; i < n; i++) { sprites[i] = new Sprite(); sprites[i].spriteNo = i; } cia = new CIA[2]; cia[0] = new CIA(cpu, IO_OFFSET + 0xdc00, this); cia[1] = new CIA(cpu, IO_OFFSET + 0xdd00, this); tfe = new TFE_CS8900(IO_OFFSET + 0xde00); // c1541 = new C1541(memory); // c1541.addObserver(this); keyboard = new Keyboard(this, cia[0], memory); canvas = new C64Canvas(this, DOUBLE, keyboard); canvas.addMouseMotionListener(this); canvas.addMouseListener(this); audioDriver = new AudioDriverSE(); audioDriver.init(44000, 22000); setSID(RESID_6581); charMemoryIndex = CPU.CHAR_ROM2; for (int i = 0; i < SC_WIDTH * SC_HEIGHT; i++) { mem[i] = cbmcolor[6]; } mis = new MemoryImageSource(SC_WIDTH, SC_HEIGHT, mem, 0, SC_WIDTH); mis.setAnimated(true); mis.setFullBufferUpdates(true); screen = canvas.createImage(mis); //to fix bug http://developer.java.sun.com/developer/bugParade/bugs/4464723.html // setRequestFocusEnabled(true); // addMouseListener(new MouseAdapter() { // public void mousePressed(MouseEvent e) { // requestFocus(); // } // }); initUpdate(); } public void update(Object src, Object data) { if (src != c1541Chips) { // Print some kind of message... message = (String) data; } else { updateDisk(src, data); } } void restoreKey(boolean down) { if (down) setNMI(KEYBOARD_NMI); else clearNMI(KEYBOARD_NMI); } // Should be checked up!!! private static final int[] IO_ADDRAND = new int[] { 0xd03f, 0xd03f, 0xd03f, 0xd03f, 0xd41f, 0xd41f, 0xd41f, 0xd41f, 0xd8ff, 0xd9ff, 0xdaff, 0xdbff, // Color ram 0xdc0f, 0xdd0f, 0xdeff, 0xdfff, // CIA + Expansion... }; public int performRead(int address, long cycles) { // dX00 => and address // d000 - d3ff => &d063 int pos = (address >> 8) & 0xf; // monitor.info("Address before: " + address); address = address & IO_ADDRAND[pos]; int val = 0; switch (address) { case 0xd000: case 0xd002: case 0xd004: case 0xd006: case 0xd008: case 0xd00a: case 0xd00c: case 0xd00e: return sprites[(address - 0xd000) >> 1].x & 0xff; case 0xd001: case 0xd003: case 0xd005: case 0xd007: case 0xd009: case 0xd00b: case 0xd00d: case 0xd00f: return sprites[(address - 0xd000) >> 1].y; case 0xd010: return sprXMSB; case 0xd011: return control1 & 0x7f | ((vbeam & 0x100) >> 1); case 0xd012: return vbeam & 0xff; // Sprite collission registers - zeroed after read! case 0xd013: case 0xd014: // Lightpen x/y return 0; case 0xd015: return sprEN; case 0xd016: return control2; case 0xd017: return sprYEX; case 0xd018: return vicMem; case 0xd019: if (SPRITEDEBUG) monitor.info("Reading d019: " + memory[address + IO_OFFSET]); return irqFlags; case 0xd01a: return irqMask; case 0xd01b: return sprPri; case 0xd01c: return sprMul; case 0xd01d: return sprXEX; case 0xd01e: val = sprCol; if (SPRITEDEBUG) monitor.info("Reading sprite collission: " + Integer.toString(address, 16) + " => " + val); sprCol = 0; return val; case 0xd01f: val = sprBgCol; if (SPRITEDEBUG) monitor.info("Reading sprite collission: " + Integer.toString(address, 16) + " => " + val); sprBgCol = 0; return val; case 0xd020: return bCol | 0xf0; case 0xd021: case 0xd022: case 0xd023: case 0xd024: return bgCol[address - 0xd021] | 0xf0; case 0xd025: return sprMC0 | 0xf0; case 0xd026: return sprMC1 | 0xf0; case 0xd027: case 0xd028: case 0xd029: case 0xd02a: case 0xd02b: case 0xd02c: case 0xd02d: case 0xd02e: return sprites[address - 0xd027].col | 0xf0; case 0xd41b: case 0xd41c: return sidChip.performRead(IO_OFFSET + address, cycles); case 0xd419: return potx; case 0xd41A: return poty; case 0xdc00: return keyboard.readDC00(cpu.lastReadOP); case 0xdc01: return keyboard.readDC01(cpu.lastReadOP); case 0xdd00: // System.out.print("Read dd00 IEC1: "); // Try the frodo way... again... val = (cia2PRA | ~cia2DDRA) & 0x3f | iecLines & c1541Chips.iecLines; val &= 0xff; return val; default: if (pos == 0x4) { return sidChip.performRead(address + IO_OFFSET, cycles); } else if (pos == 0xd) { return cia[1].performRead(address + IO_OFFSET, cycles); } else if (pos == 0xc) { return cia[0].performRead(address + IO_OFFSET, cycles); } else if (pos == 0xe) { return tfe.performRead(address + IO_OFFSET, cycles); } else if (pos >= 0x8) { return memory[IO_OFFSET + address] | 0xf0; } return 0xff; } } public void performWrite(int address, int data, long cycles) { int pos = (address >> 8) & 0xf; address = address & IO_ADDRAND[pos]; // monitor.info("Wrote to Chips at " + Integer.toString(address, 16) // + " = " + Integer.toString(data, 16)); // Store in the memory given by "CPU" memory[address + IO_OFFSET] = data; // if (address >= 0xd800 && address < 0xdc00) { // int p = address - 0xd800; // System.out.println("### Write to color ram: " + (p % 40) + "," + p/40 + // " = " + data); // } switch (address) { // ------------------------------------------------------------------- // VIC related // ------------------------------------------------------------------- case 0xd000: case 0xd002: case 0xd004: case 0xd006: case 0xd008: case 0xd00a: case 0xd00c: case 0xd00e: int sprite = (address - 0xd000) >> 1; sprites[sprite].x &= 0x100; sprites[sprite].x += data; break; case 0xd001: case 0xd003: case 0xd005: case 0xd007: case 0xd009: case 0xd00b: case 0xd00d: case 0xd00f: sprites[(address - 0xd000) >> 1].y = data; // System.out.println("Setting sprite " + (address - 0xd000)/2 + " to " + data); break; case 0xd010: sprXMSB = data; for (int i = 0, m = 1, n = 8; i < n; i++, m = m << 1) { sprites[i].x &= 0xff; sprites[i].x |= (data & m) != 0 ? 0x100 : 0; } break; // d011 -> high address of raster pos case 0xd011 : raster = (raster & 0xff) | ((data << 1) & 0x100); control1 = data; // monitor.info("Setting blank: " + // ((memory[ + 0xd011] & 0x08) == 0) + // " at " + vbeam); if (vScroll != (data & 7)) { // update vScroll and badLine! vScroll = data & 0x7; boolean oldBadLine = badLine; badLine = (displayEnabled && vbeam >= 0x30 && vbeam <= 0xf7) && (vbeam & 0x7) == vScroll; if (BAD_LINE_DEBUG && oldBadLine != badLine) { monitor.info("#### BadLC diff@" + vbeam + " => " + badLine + " vScroll: " + vScroll + " vmli: " + vmli + " vc: " + vc + " rc: " + rc + " cyc line: " + (cpu.cycles - lastLine) + " cyc IRQ: " + (cpu.cycles - lastIRQ)); } } extended = (data & 0x40) != 0; blankRow = (data & 0x08) == 0; // 000 => normal text, 001 => multicolor text // 010 => extended text, 011 => illegal mode... // 100 => hires gfx, 101 => multi hires // 110, 111 => ? videoMode = (extended ? 0x02 : 0) | (multiCol ? 0x01 : 0) | (((data & 0x20) != 0) ? 0x04 : 0x00); // System.out.println("Extended set to: " + extended + " at " + // vbeam + " d011: " + Hex.hex2(data)); if (VIC_MEM_DEBUG || BAD_LINE_DEBUG) { monitor.info("d011 = " + data + " at " + vbeam + " => YScroll = " + (data & 0x7) + " cyc since line: " + (cpu.cycles-lastLine) + " cyc since IRQ: " + (cpu.cycles-lastIRQ)); } if (IRQDEBUG) monitor.info("Setting raster position (hi) to: " + (data & 0x80)); break; // d012 -> raster position case 0xd012 : raster = (raster & 0x100) | data; if (IRQDEBUG) monitor.info("Setting Raster Position (low) to " + data); break; case 0xd013: case 0xd014: // Write to lightpen... break; case 0xd015: sprEN = data; for (int i = 0, m = 1, n = 8; i < n; i++, m = m << 1) { sprites[i].enabled = (data & m) != 0; } // System.out.println("Setting sprite enable to " + data); break; case 0xd016: control2 = data; horizScroll = data & 0x7; multiCol = (data & 0x10) != 0; // if (hideColumn != ((data & 0x08) == 0)) { // System.out.println("38 chars on: " + hideColumn + " at " + vbeam + " cycle: " + // (cycles - lastLine) + " borderstate:" + borderState); // } hideColumn = (data & 0x08) == 0; // Set videmode... videoMode = (extended ? 0x02 : 0) | (multiCol ? 0x01 : 0) | (((control1 & 0x20) != 0) ? 0x04 : 0x00); // System.out.println("HorizScroll set to: " + horizScroll + " at " // + vbeam); // System.out.println("MultiColor set to: " + multiCol + " at " + vbeam); break; case 0xd017: sprYEX = data; for (int i = 0, m = 1, n = 8; i < n; i++, m = m << 1) { sprites[i].expandY = (data & m) != 0; } break; case 0xd018: vicMem = data; setVideoMem(); break; case 0xd019 : { if ((data & 0x80) != 0) data = 0xff; int latchval = 0xff ^ data; if (IRQDEBUG) monitor.info("Latching VIC-II: " + Integer.toString(data, 16) + " on " + Integer.toString(irqFlags, 16) + " latch: " + Integer.toString(latchval, 16)); irqFlags &= latchval; // Is this "flagged" off? if ((irqMask & 0x0f & irqFlags) == 0) { clearIRQ(VIC_IRQ); } } break; case 0xd01a: irqMask = data; // Check if IRQ should trigger or clear! if ((irqMask & 0x0f & irqFlags) != 0) { irqFlags |= 0x80; setIRQ(VIC_IRQ); } else { clearIRQ(VIC_IRQ); } if (IRQDEBUG) { monitor.info("Changing IRQ mask to: " + Integer.toString(irqMask, 16) + " vbeam: " + vbeam); } break; case 0xd01b: sprPri = data; for (int i = 0, m = 1, n = 8; i < n; i++, m = m << 1) { sprites[i].priority = (data & m) != 0; } break; case 0xd01c: sprMul = data; for (int i = 0, m = 1, n = 8; i < n; i++, m = m << 1) { sprites[i].multicolor = (data & m) != 0; } break; case 0xd01d: sprXEX = data; for (int i = 0, m = 1, n = 8; i < n; i++, m = m << 1) { sprites[i].expandX = (data & m) != 0; } break; case 0xd020: borderColor = cbmcolor[bCol = data & 15]; break; case 0xd021: bgColor = cbmcolor[bgCol[0] = data & 15]; for (int i = 0, n = 8; i < n; i++) { sprites[i].color[0] = bgColor; } break; case 0xd022: case 0xd023: case 0xd024: bgCol[address - 0xd021] = data & 15; break; case 0xd025: sprMC0 = data & 15; for (int i = 0, n = 8; i < n; i++) { sprites[i].color[1] = cbmcolor[sprMC0]; } break; case 0xd026: sprMC1 = data & 15; for (int i = 0, n = 8; i < n; i++) { sprites[i].color[3] = cbmcolor[sprMC1]; } break; case 0xd027: case 0xd028: case 0xd029: case 0xd02a: case 0xd02b: case 0xd02c: case 0xd02d: case 0xd02e: sprites[address - 0xd027].color[2] = cbmcolor[data & 15]; sprites[address - 0xd027].col = data & 15; // System.out.println("Sprite " + (address - 0xd027) + " color set to: " + (data & 15)); break; // CIA 1 & 2 - 'special' addresses case 0xdc00: case 0xdc01: case 0xdc02: case 0xdc03: // monitor.println("////////////==> Set Keyboard:" + // Integer.toString(address - ,16) + " = " + data + // " => ~" + ((~data) & 0xff)); cia[0].performWrite(address + IO_OFFSET, data, cpu.cycles); if (!isrRunning) { if (ciaWrites++ > 20) { isrRunning = true; ciaWrites = 0; } else { System.out.println("startup CIA write# " + ciaWrites + ": set " + address + " to " + data); } } break; case 0xdd00: if (DEBUG_IEC) monitor.info("C64: IEC Write: " + Integer.toHexString(data)); // if (emulateDisk) { // c1541.handleDisk(data, cpu.cycles); // } if (VIC_MEM_DEBUG) System.out.println("Set dd00 to " + Integer.toHexString(data)); cia[1].performWrite(address + IO_OFFSET, data, cpu.cycles); cia2PRA = data; data = ~cia2PRA & cia2DDRA; int oldLines = iecLines; iecLines = (data << 2) & 0x80 // DATA | (data << 2) & 0x40 // CLK | (data << 1) & 0x10; // ATN if (((oldLines ^ iecLines) & 0x10) != 0) { c1541Chips.atnChanged((iecLines & 0x10) == 0); } c1541Chips.updateIECLines(); if (DEBUG_IEC) printIECLines(); setVideoMem(); break; case 0xdd02: cia2DDRA = data; // System.out.println("C64: Wrote to DDRA (IEC): " + // Integer.toHexString(data)); cia[1].performWrite(address + IO_OFFSET, data, cpu.cycles); setVideoMem(); break; default: if (pos == 0x4) { sidChip.performWrite(address + IO_OFFSET, data, cycles); } else if (pos == 0xd) { cia[1].performWrite(address + IO_OFFSET, data, cycles); } else if (pos == 0xc) { cia[0].performWrite(address + IO_OFFSET, data, cycles); } else if (pos == 0xe) { tfe.performWrite(address + IO_OFFSET , data, cycles); } // handle color ram! } } private void printIECLines() { System.out.print("IEC/F: "); if ((iecLines & 0x10) == 0) { System.out.print("A1"); } else { System.out.print("A0"); } // The c64 has id = 1 int sdata = ((iecLines & 0x40) == 0) ? 1 : 0; System.out.print(" C" + sdata); sdata = ((iecLines & 0x80) == 0) ? 1 : 0; System.out.print(" D" + sdata); // The 1541 has id = 2 sdata = ((c1541Chips.iecLines & 0x40) == 0) ? 1 : 0; System.out.print(" c" + sdata); sdata = ((c1541Chips.iecLines & 0x80) == 0) ? 1 : 0; System.out.print(" d" + sdata); System.out.println(" => C" + ((iecLines & c1541Chips.iecLines & 0x80) == 0 ? 1 : 0) + " D" + ((iecLines & c1541Chips.iecLines & 0x40) == 0 ? 1 : 0)); } private void setVideoMem() { if (VIC_MEM_DEBUG) { monitor.info("setVideoMem() cycles since line: " + (cpu.cycles - lastLine) + " cycles since IRQ: " + (cpu.cycles-lastIRQ) + " at " + vbeam); } // Set-up vars for screen rendering vicBank = (~(~cia2DDRA | cia2PRA) & 3) << 14; charSet = vicBank | (vicMem & 0x0e) << 10; videoMatrix = vicBank | (vicMem & 0xf0) << 6; vicBase = vicBank | (vicMem & 0x08) << 10; spr0BlockSel = 0x03f8 + videoMatrix; // monitor.info("--------------------"); // monitor.info("0xdd00: 0x"+Integer.toHexString(memory[IO_OFFSET + 0xdd00])); // monitor.info("0xd018: 0x"+Integer.toHexString(memory[IO_OFFSET + 0xd018])); // monitor.info("vicBank: 0x"+Integer.toHexString(vicBank)); // monitor.info("videoMatrix: 0x"+Integer.toHexString(videoMatrix-vicBank)); // monitor.info("charSet: 0x"+Integer.toHexString(charSet-vicBank)); //check if vic not looking at char rom 1, 2, 4, 8 // This is not correct! Char Rom is not everywhere!!!! - find out! if ( (vicMem & 0x0c) != 4 || (vicBank & 0x4000) == 0x4000) { charMemoryIndex = charSet; } else { charMemoryIndex = (((vicMem & 0x02) == 0) ? 0 : 0x0800) + CPU.CHAR_ROM2; } } private void initUpdate() { vc = 0; vcBase = 0; vmli = 0; // rc = 0; updating = true; // // First rendered line will start at cpu.cycles - no at next_scan! // firstLine = nextScanLine; for (int i = 0; i < 8; i++) { sprites[i].nextByte = 0; sprites[i].painting = false; sprites[i].spriteReg = 0; } if (colors == null) { colors = new Color[16]; for (int i = 0; i < 16; i++) { colors[i] = new Color(cbmcolor[i]); } } canvas.setBackground(colors[memory[IO_OFFSET + 0xd020] & 15]); } // ------------------------------------------------------------------- // Screen rendering! // ------------------------------------------------------------------- // keep track of if the border is to be painted... private int borderState = 0; private boolean notVisible = false; private int xPos = 0; private long lastCycle = 0; public final void clock(long cycles) { if (DEBUG_CYCLES || true) { if (lastCycle + 1 < cycles) { System.out.println("More than one cycle passed: " + (cycles - lastCycle) + " at " + cycles + " PC: " + Integer.toHexString(cpu.pc)); } if (lastCycle == cycles) { System.out.println("No diff since last update!!!: " + (cycles - lastCycle) + " at " + cycles + " PC: " + Integer.toHexString(cpu.pc)); } lastCycle = cycles; } // Delta is cycles into the current raster line! int vicCycle = (int) (cycles - lastLine); if (notVisible) { if (vicCycle < 62) return; } // Each cycle is 8 pixels (a byte) // Cycle 16 (if first cycle is 0) is the first visible gfx cycle // Cycle 12 is first visible border cycle => 12 x 8 = 96 // Last to "draw" is cycle 59 => 59 * 8 = 472 => 376 visible pixels? if (badLine) { gfxVisible = true; } switch (vicCycle) { case 0: // Increase the vbeam - rendering is started vbeam = (vbeam + 1) % 312; if (vbeam == 0) frame++; vPos = vbeam - (FIRST_VISIBLE_VBEAM + 1); if (vbeam == FIRST_VISIBLE_VBEAM) { colIndex++; if (colIndex >= LABEL_COUNT) colIndex = 0; // Display enabled? initUpdate(); } // Check for interrupts, etc... // Sprite collission interrupts - why only once a line? if (((irqMask & 2) != 0) && (sprBgCol != 0) && (irqFlags & 2) == 0) { if (SPRITEDEBUG) monitor.info("*** Sprite collission IRQ (d01f): " + sprBgCol + " at " + vbeam); irqFlags |= 82; setIRQ(VIC_IRQ); } if (((irqMask & 4) != 0) && (sprCol != 0) && (irqFlags & 4) == 0) { if (SPRITEDEBUG) monitor.info("*** Sprite collission IRQ (d01e): " + sprCol + " at " + vbeam); irqFlags |= 84; setIRQ(VIC_IRQ); } int irqComp = raster; // Not nice... FIX THIS!!! if (irqComp > 312) irqComp &= 0xff; if ((irqFlags & 1) == 0 && (irqComp == vbeam)) { irqFlags |= 0x1; if ((irqMask & 1) != 0) { irqFlags |= 0x80; irqTriggered = true; setIRQ(VIC_IRQ); lastIRQ = cpu.cycles; if (IRQDEBUG) monitor.info("Generating IRQ at " + vbeam + " req:" + raster + " IRQs:" + cpu.interruptInExec + " flags: " + irqFlags + " delta: " + (cpu.cycles - lastLine)); } } else { irqTriggered = false; } notVisible = false; if (vPos < 0 || vPos >= 284) { cpu.baLowUntil = 0; notVisible = true; if (STATE_DEBUG) monitor.info("FINISH next at " + vbeam); // Jump directly to VS_FINISH and wait for end of line... break; } // Check if display should be enabled... if (vbeam == 0x30) { displayEnabled = (control1 & 0x10) != 0; if (displayEnabled) { borderState &= ~0x04; } else { borderState |= 0x04; } } badLine = (displayEnabled && vbeam >= 0x30 && vbeam <= 0xf7) && (vbeam & 0x7) == vScroll; // Clear the collission masks each line... - not needed??? for (int i = 0, n = SC_WIDTH; i < n; i++) { collissionMask[i] = 0; } break; case 1: // Sprite data - sprite 3 if (sprites[3].dma) { sprites[3].readSpriteData(); // reads all 3 bytes here (one should be prev). } if (sprites[5].dma) { cpu.baLowUntil = lastLine + VICConstants.BA_SP5; } break; case 2: // here some of the bytes for sprite 4 should be read... break; case 3: if (sprites[4].dma) { sprites[4].readSpriteData(); } if (sprites[6].dma) { cpu.baLowUntil = lastLine + VICConstants.BA_SP6; } break; case 4: // here some of the bytes for sprite 5 should be read... break; case 5: if (sprites[5].dma) { sprites[5].readSpriteData(); } if (sprites[7].dma) { cpu.baLowUntil = lastLine + VICConstants.BA_SP7; } break; case 6: // here some of the bytes for sprite 6 should be read.. break; case 7: if (sprites[6].dma) { sprites[6].readSpriteData(); } break; case 8: // here some of the bytes for sprite 7 should be read... break; case 9: if (sprites[7].dma) { sprites[7].readSpriteData(); } // Border management! (at another cycle maybe?) if (blankRow) { if (vbeam == 247) { borderState |= 1; } } else { if (vbeam == 251) { borderState |= 1; } if (vbeam == 51) { borderState &= 0xfe; // Reset sprite data to avoid garbage since they are not painted... for (int i = 0, n = 7; i < n; i++) { if (!sprites[i].painting) { sprites[i].lineFinished = true; } } } } // No border after vbeam 55 (ever?) if (vbeam == 55) { borderState &= 0xfe; // Reset sprite data to avoid garbage since they are not painted... for (int i = 0, n = 7; i < n; i++) { if (!sprites[i].painting) sprites[i].lineFinished = true; } } break; case 10: break; case 11: // Set badline fetching... if (badLine) { cpu.baLowUntil = lastLine + VICConstants.BA_BADLINE; } break; case 12: // First visible cycle (on screen) // calculate mpos before starting the rendering! mpos = vPos * SC_WIDTH; drawBackground(); xPos = 16; mpos += 8; break; case 13: drawBackground(); drawSprites(); mpos += 8; // Set vc, reset vmli... vc = vcBase; vmli = 0; if (badLine) { cpu.baLowUntil = lastLine + VICConstants.BA_BADLINE; if (BAD_LINE_DEBUG) System.out.println("#### RC = 0 (" + rc + ") at " + vbeam + " vc: " + vc); rc = 0; } break; case 14: drawBackground(); drawSprites(); mpos += 8; if (badLine) { cpu.baLowUntil = lastLine + VICConstants.BA_BADLINE; } break; case 15: drawBackground(); drawSprites(); mpos += 8; if (badLine) { cpu.baLowUntil = lastLine + VICConstants.BA_BADLINE; } // Turn off sprite DMA if finished reading! for (int i = 0, n = 8; i < n; i++) { if (sprites[i].nextByte == 63) sprites[i].dma = false; } break; case 16: if (!hideColumn) { borderState &= 0xfd; } if (badLine) { cpu.baLowUntil = lastLine + VICConstants.BA_BADLINE; // Fetch first char into cache! (for the below draw...) vicCharCache[vmli] = memory[videoMatrix + (vcBase & 0x3ff)]; vicColCache[vmli] = memory[IO_OFFSET + 0xd800 + (vcBase & 0x3ff)]; } // Draw one character here! drawGraphics(mpos + horizScroll); drawSprites(); if (borderState != 0) drawBackground(); mpos += 8; // System.out.println("Cycle: " + vicCycle + " VMLI: " + vmli + " => " + mpos); break; case 17: if (hideColumn) { borderState &= 0xfd; } if (badLine) { cpu.baLowUntil = lastLine + VICConstants.BA_BADLINE; // Fetch a some chars into cache! (for the below draw...) vicCharCache[vmli] = memory[videoMatrix + ((vcBase + vmli) & 0x3ff)]; vicColCache[vmli] = memory[IO_OFFSET + 0xd800 + ((vcBase + vmli) & 0x3ff)]; } // draw the graphics. (should probably handle sprites also??) drawGraphics(mpos + horizScroll); drawSprites(); mpos += 8; break; // Cycle 18 - 53 default: if (badLine) { cpu.baLowUntil = lastLine + VICConstants.BA_BADLINE; // Fetch a some chars into cache! (for the below draw...) vicCharCache[vmli] = memory[videoMatrix + ((vcBase + vmli) & 0x3ff)]; vicColCache[vmli] = memory[IO_OFFSET + 0xd800 + ((vcBase + vmli) & 0x3ff)]; } // draw the graphics. (should probably handle sprites also??) drawGraphics(mpos + horizScroll); drawSprites(); mpos += 8; // System.out.println("Cycle: " + vicCycle + " VMLI: " + vmli + " => " + mpos); break; case 54: // Then Check if it is time to start up the sprites! // Does not matter in which order this is done ?= if (badLine) { cpu.baLowUntil = lastLine + VICConstants.BA_BADLINE; // Fetch a some chars into cache! (for the below draw...) vicCharCache[vmli] = memory[videoMatrix + ((vcBase + vmli) & 0x3ff)]; vicColCache[vmli] = memory[IO_OFFSET + 0xd800 + ((vcBase + vmli) & 0x3ff)]; } int mult = 1; int ypos = vPos + SC_SPYOFFS; for (int i = 0, n = 8; i < n; i++) { Sprite sprite = sprites[i]; if (sprite.enabled) { // If it is time to start drawing this sprite! if (sprite.y == (ypos & 0xff) && (ypos < 270)) { sprite.nextByte = 0; sprite.dma = true; sprite.expFlipFlop = true; if (SPRITEDEBUG) System.out.println("Starting painting sprite " + i + " on " + vbeam + " first visible at " + (ypos + 1)); } } mult = mult << 1; } if (sprites[0].dma) { cpu.baLowUntil = lastLine + VICConstants.BA_SP0; } drawGraphics(mpos + horizScroll); drawSprites(); mpos += 8; // System.out.println("Cycle: " + vicCycle + " VMLI: " + vmli + " => " + mpos); break; case 55: if (hideColumn) { borderState |= 2; } if (badLine) { cpu.baLowUntil = lastLine + VICConstants.BA_BADLINE; // Fetch a some chars into cache! (for the below draw...) vicCharCache[vmli] = memory[videoMatrix + ((vcBase + vmli) & 0x3ff)]; vicColCache[vmli] = memory[IO_OFFSET + 0xd800 + ((vcBase + vmli) & 0x3ff)]; } drawGraphics(mpos + horizScroll); drawSprites(); if (borderState != 0) drawBackground(); mpos += 8; // System.out.println("Cycle: " + vicCycle + " VMLI: " + vmli + " => " + mpos); break; case 56: if (!hideColumn) { borderState |= 2; } drawBackground(); drawSprites(); mpos += 8; // If time to turn of sprite display... for (int i = 0, n = 8; i < n; i++) { Sprite sprite = sprites[i]; if (!sprite.dma) { sprite.painting = false; if (SPRITEDEBUG) System.out.println("Stopped painting sprite " + i + " at (after): " + vbeam); } } // Here we should check if sprite dma should start... // - probably need to add a dma variable to sprites, and not // only use the painting variable for better emulation // Bus not available if sp0 or sp1 is painting if (sprites[1].dma) { cpu.baLowUntil = lastLine + VICConstants.BA_SP1; } break; case 57: // Paint border, check sprite for display and read sprite 0 data. for (int i = 0, n = 8; i < n; i++) { Sprite sprite = sprites[i]; if (sprite.dma) sprite.painting = true; } drawBackground(); drawSprites(); mpos += 8; if (rc == 7) { vcBase = vc; gfxVisible = false; if (BAD_LINE_DEBUG) { monitor.info("#### RC7 ==> vc = " + vc + " at " + vbeam + " vicCycle = " + vicCycle); if (vc == 1000) { monitor.info("--------------- last line ----------------"); } } } if (badLine || gfxVisible) { rc = (rc + 1) & 7; gfxVisible = true; } if (sprites[0].painting) { sprites[0].readSpriteData(); } if (sprites[2].dma) { cpu.baLowUntil = lastLine + VICConstants.BA_SP2; } break; case 58: drawBackground(); drawSprites(); mpos += 8; break; case 59: drawBackground(); drawSprites(); mpos += 8; if (sprites[1].painting) { sprites[1].readSpriteData(); } break; case 60: drawSprites(); break; case 61: if (sprites[2].painting) { sprites[2].readSpriteData(); } if (sprites[3].dma) { cpu.baLowUntil = lastLine + VICConstants.BA_SP3; } break; case 62: // Should this be made??? or should sprite 0 be repaintable // same line? // Reset sprites so that they can be repainted again... for (int i = 0; i < sprites.length; i++) { sprites[i].reset(); } lastLine += VICConstants.SCAN_RATE; // Update screen if (updating) { if (vPos == 285) { mis.newPixels(); canvas.repaint(); actualScanTime = (actualScanTime * 9 + (int) ((audioDriver.getMicros() - lastScan))) / 10; lastScan = audioDriver.getMicros(); updating = false; } } notVisible = false; break; } } // Used to draw background where either border or background should be // painted... private void drawBackground() { int bpos = mpos; int currentBg = borderState > 0 ? borderColor : bgColor; for (int i = 0; i < 8; i++) { mem[bpos++] = currentBg; } } /** * <code>drawGraphics</code> - draw the VIC graphics (text/bitmap) * Note that sprites are not drawn here... (yet?) * * * @param mpos an <code>int</code> value representing position to * draw the graphics from (already fixed with hscroll) */ private final void drawGraphics(int mpos) { if (!gfxVisible || paintBorder || (borderState & 1) == 1) { // We know that display is not enabled, and that mpos is already // at a correct place, except horizScroll... mpos -= horizScroll; int color = (paintBorder || (borderState > 0)) ? borderColor : bgColor; for (int i = mpos, n = mpos + 8; i < n; i++) { mem[i] = color; } // trick to use vmli as a if var even when no gfx. vmli++; return; } int collX = (vmli << 3) + horizScroll + SC_XOFFS; // Paint background if first col (should maybe be made later also...) if (vmli == 0) { for (int i = mpos - horizScroll, n = i + 8; i < n; i++) { mem[i] = bgColor; } } int position = 0, data = 0, penColor = 0, bgcol = bgColor; if ((control1 & 0x20) == 0) { int tmp; int pcol; // This should be in a cache some where... if (multiCol) { multiColor[0] = bgColor; multiColor[1] = cbmcolor[bgCol[1]]; multiColor[2] = cbmcolor[bgCol[2]]; } penColor = cbmcolor[pcol = vicColCache[vmli] & 15]; if (extended) { position = charMemoryIndex + (((data = vicCharCache[vmli]) & 0x3f) << 3); bgcol = cbmcolor[bgCol[(data >> 6)]]; } else { position = charMemoryIndex + (vicCharCache[vmli] << 3); } data = memory[position + rc]; if (multiCol && pcol > 7) { multiColor[3] = cbmcolor[pcol & 7]; for (int pix = 0; pix < 8; pix += 2) { tmp = (data >> pix) & 3; mem[mpos + 6 - pix] = mem[mpos + 7 - pix] = multiColor[tmp]; // both 00 and 01 => no collission!? // but what about priority? if (tmp > 0x01) { tmp = 256; } else { tmp = 0; } collissionMask[collX + 7 - pix] = collissionMask[collX + 6 - pix] = tmp; } } else { for (int pix = 0; pix < 8; pix++) { if ((data & (1 << pix)) > 0) { mem[mpos + 7 - pix] = penColor; collissionMask[collX + 7 - pix] = 256; } else { mem[mpos + 7 - pix] = bgcol; collissionMask[collX + 7 - pix] = 0; } } } if (multiCol && extended) { // Illegal mode => all black! for (int pix = 0; pix < 8; pix++) { mem[mpos + 7 - pix] = 0xff000000; } } if (BAD_LINE_DEBUG && badLine) { for (int pix = 0; pix < 8; pix += 4) { mem[mpos + 7 - pix] = (mem[mpos + 7 - pix] & 0xff7f7f7f) | 0x0fff; } } } else { // ------------------------------------------------------------------- // Bitmap mode! // ------------------------------------------------------------------- position = vicBase + (vc & 0x3ff) * 8 + rc; if (multiCol) { multiColor[0] = bgColor; } int vmliData = vicCharCache[vmli]; penColor = cbmcolor[(vmliData & 0xf0) >> 4]; bgcol = cbmcolor[vmliData & 0x0f]; data = memory[position]; if (multiCol) { multiColor[1] = cbmcolor[(vmliData >> 4) & 0x0f]; multiColor[2] = cbmcolor[vmliData & 0x0f]; multiColor[3] = cbmcolor[vicColCache[vmli] & 0x0f]; // Multicolor int tmp; for (int pix = 0; pix < 8; pix += 2) { mem[mpos + 6 - pix] = mem[mpos + 7 - pix] = multiColor[tmp = (data >> pix) & 3]; if (tmp > 0x01) { tmp = 256; } else { tmp = 0; } collissionMask[collX + 7 - pix] = collissionMask[collX + 6 - pix] = tmp; } } else { // Non multicolor for (int pix = 0; pix < 8; pix++) { if ((data & (1 << pix)) > 0) { mem[7 - pix + mpos] = penColor; collissionMask[collX + 7 - pix] = 256; } else { mem[7 - pix + mpos] = bgcol; collissionMask[collX + 7 - pix] = 0; } } } if (extended) { // Illegal mode => all black! for (int pix = 0; pix < 8; pix++) { mem[mpos + 7 - pix] = 0xff000000; } } if (BAD_LINE_DEBUG && badLine) { for (int pix = 0; pix < 8; pix += 4) { mem[mpos + 7 - pix] = (mem[mpos + 7 - pix] & 0xff3f3f3f) | 0x0fff; } } } vc++; vmli++; } // ------------------------------------------------------------------- // Sprites... // ------------------------------------------------------------------- private final void drawSprites() { int smult = 0x100; int lastX = xPos - 8; for (int i = 7; i >= 0; i--) { Sprite sprite = sprites[i]; // Done before the continue... smult = smult >> 1; if (sprite.lineFinished || !sprite.painting) { continue; } int x = sprite.x + SC_SPXOFFS; // 0 in sprite x => xPos = 8 int mpos = vPos * SC_WIDTH; if (x < xPos) { // Ok, we should write some data... int minX = lastX > x ? lastX : x; // if (i == 0) monitor.info("Writing sprite " + i + " first pixel at " + // minX + " vPos = " + vPos); for (int j = minX, m = xPos; j < m; j++) { int c = sprite.getPixel(); if (c != 0 && borderState == 0) { int tmp = (collissionMask[j] |= smult); if (!sprite.priority || (tmp & 0x100) == 0) { mem[mpos + j] = sprite.color[c]; } if (tmp != smult) { // If collission with bg then notice! if ((tmp & 0x100) != 0) { sprBgCol |= smult; // monitor.info("***** Sprite x Bkg collission!"); } // If collission with sprite, all colls must // be registered! if ((tmp & 0xff) != smult) { sprCol |= tmp & 0xff; // monitor.info("***** Sprite x Sprite collission: d01e = " + // sprCol + " sprite: " + i + " => " + // smult + " at " + j + "," + vbeam); } } } if (SPRITEDEBUG) { if ((sprite.nextByte == 3) && ((j & 4) == 0)) { mem[mpos + j] = 0xff00ff00; } if ((sprite.nextByte == 63) && ((j & 4) == 0)) { mem[mpos + j] = 0xffff0000; } if (j == x) { mem[mpos + j] = 0xff000000 + sprite.pointer; } } } } } xPos += 8; } public void stop() { motorSound(false); sidChip.stop(); audioDriver.shutdown(); } public void reset() { // Clear a lot of stuff...??? initUpdate(); sidChip.reset(); lastLine = cpu.cycles; nextIOUpdate = cpu.cycles + 47; for (int i = 0; i < mem.length; i++) mem[i] = 0; reset = 100; sprCol = 0; sprBgCol = 0; cia[0].reset(); cia[1].reset(); // c1541.reset(); keyboard.reset(); ciaWrites = 0; isrRunning = false; motorSound(false); resetInterrupts(); } public static final int IMG_TOTWIDTH = SC_WIDTH; public static final int IMG_TOTHEIGHT = SC_HEIGHT; public Image crtImage; // Will be called from the c64canvas class long repaint = 0; public void paint(Graphics g) { if (g == null) return; if (image == null) { image = canvas.createImage(IMG_TOTWIDTH, IMG_TOTHEIGHT); g2 = image.getGraphics(); g2.setFont(new Font("Monospaced", Font.PLAIN, 11)); } if (crtImage == null) { crtImage = new BufferedImage(displayWidth, displayHeight, BufferedImage.TYPE_INT_ARGB); Graphics gcrt = crtImage.getGraphics(); gcrt.setColor(TRANSPARENT_BLACK); for (int i = 0, n = displayHeight; i < n; i += 2) { gcrt.drawLine(0, i, displayWidth, i); } } // Why is there transparency? g2.drawImage(screen, 0, 0, null); if (reset > 0) { g2.setColor(darks[colIndex]); int xp = 44; if (reset < 44) { xp = reset; } g2.drawString("JaC64 " + version + " - Java C64 - www.jac64.com", xp + 1, 9); g2.setColor(lites[colIndex]); g2.drawString("JaC64 " + version + " - Java C64 - www.jac64.com", xp, 8); reset--; } else { String msg = "JaC64 "; if ((message != null) && (message != "")) { msg += message; } else { colIndex = 0; } msg += tmsg; g2.setColor(darks[colIndex]); g2.drawString(msg, 1, 9); g2.setColor(lites[colIndex]); g2.drawString(msg, 0, 8); if (ledOn) { g2.setColor(LED_ON); } else { g2.setColor(LED_OFF); } g2.fillRect(372, 3, 7, 1); g2.setColor(LED_BORDER); g2.drawRect(371, 2, 8, 2); } g.fillRect(0, 0, offsetX, displayHeight + offsetY * 2); g.fillRect(offsetX + displayWidth, 0, offsetX, displayHeight + offsetY * 2); g.fillRect(0, 0, displayWidth + offsetX * 2, offsetY); g.fillRect(0, displayHeight + offsetY, displayWidth + offsetX * 2, offsetY); Graphics2D g2d = (Graphics2D) g; // g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2d.drawImage(image, offsetX, offsetY, displayWidth, displayHeight, null); // g.drawImage(crtImage, offsetX, offsetY, displayWidth, displayHeight, null); // monitor.info("Repaint: " + (System.currentTimeMillis() - repaint) + " " + memory[IO_OFFSET + 55296 + 6 * 40]); // repaint = System.currentTimeMillis(); } // ------------------------------------------------------------------- // Internal sprite class to handle all data for sprites // Just a collection of data registers... so far... // ------------------------------------------------------------------- private class Sprite { boolean painting = false; // If sprite is "on" or not (visible) boolean dma = false; // Sprite DMA on/off int nextByte; int pointer; int x; int y; int spriteNo; // Contains the sprite data to be outshifted int spriteReg; boolean enabled; boolean expFlipFlop; boolean multicolor = false; boolean expandX = false; boolean expandY = false; boolean priority = false; boolean lineFinished = false; int pixelsLeft = 0; int currentPixel = 0; // The sprites color value (col) int col; // Sprites real colors int[] color = new int[4]; int getPixel() { if (lineFinished) return 0; pixelsLeft--; if (pixelsLeft > 0) return currentPixel; // Indicate finished! if (pixelsLeft <= 0 && spriteReg == 0) { currentPixel = 0; lineFinished = true; return 0; } if (multicolor) { // The 23rd and 22nd pixel => data! currentPixel = (spriteReg & 0xc00000) >> 22; spriteReg = (spriteReg << 2) & 0xffffff; pixelsLeft = 2; } else { // Only the 23rd bit is pixel data! currentPixel = (spriteReg & 0x800000) >> 22; spriteReg = (spriteReg << 1) & 0xffffff; pixelsLeft = 1; } // Double the number of pixels if expanded! if (expandX) { pixelsLeft = pixelsLeft << 1; } return currentPixel; } void reset() { lineFinished = false; } void readSpriteData() { // Read pointer + the three sprite data pointers... pointer = vicBank + memory[spr0BlockSel + spriteNo] * 0x40; spriteReg = ((memory[pointer + nextByte++] & 0xff) << 16) | ((memory[pointer + nextByte++] & 0xff) << 8) | memory[pointer + nextByte++]; // For debugging... seems to be err on other place than the // Memoryfetch - since this is also looking very odd...??? // spriteReg = 0xf0f0f0; if (!expandY) expFlipFlop = false; if (expFlipFlop) { nextByte = nextByte - 3; } expFlipFlop = !expFlipFlop; pixelsLeft = 0; } } // ------------------------------------------------------------------- // Observer (1541) - should probably be in C64 screen later... // ------------------------------------------------------------------- public void updateDisk(Object obs, Object msg) { if (msg == C1541Chips.HEAD_MOVED) { if (lastTrack != c1541Chips.currentTrack) { lastTrack = c1541Chips.currentTrack; trackSound(); } else { // Head could not move any more... maybe other sound here? trackSound(); } } // add head beyond here... lastSector = c1541Chips.currentSector; if (motorOn != c1541Chips.motorOn) { motorSound(c1541Chips.motorOn); } tmsg = " track: " + lastTrack + " / " + lastSector; ledOn = c1541Chips.ledOn; motorOn = c1541Chips.motorOn; } private void trackSound() { if (trackSound != null) { trackSound.play(); } } public void motorSound(boolean on) { if (motorSound != null) { if (on) motorSound.loop(); else motorSound.stop(); } } public void setSounds(AudioClip track, AudioClip motor) { trackSound = track; motorSound = motor; } // ------------------------------------------------------------------- // MouseListener // ------------------------------------------------------------------- public void mouseDragged(MouseEvent e) { potx = e.getX() & 0xff; poty = 0xff - (e.getY() & 0xff); } public void mouseMoved(MouseEvent e) { potx = e.getX() & 0xff; poty = 0xff - (e.getY() & 0xff); } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { button1 = true; } else { button2 = true; } // Emulate stick button keyboard.setButtonval(0xff - (button1 | button2 ? 0x10 : 0)); //keyboard.setButtonval(0xff - (button1 ? 0x4 : 0) - (button2 ? 0x8 : 0)); } public void mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { button1 = false; } else { button2 = false; } // Emulate stick button keyboard.setButtonval(0xff - (button1 | button2 ? 0x10 : 0)); // keyboard.setButtonval(0xff - (button1 ? 0x4 : 0) - (button2 ? 0x8 : 0)); } }