diff --git a/Resources/Character Set.png b/Resources/Character Set.png new file mode 100644 index 0000000..7456b9a Binary files /dev/null and b/Resources/Character Set.png differ diff --git a/Resources/Glare.png b/Resources/Glare.png new file mode 100644 index 0000000..3446ef8 Binary files /dev/null and b/Resources/Glare.png differ diff --git a/Resources/Paused.png b/Resources/Paused.png new file mode 100644 index 0000000..397aad4 Binary files /dev/null and b/Resources/Paused.png differ diff --git a/Source/AppleDisplay.java b/Source/AppleDisplay.java new file mode 100644 index 0000000..d35a2b2 --- /dev/null +++ b/Source/AppleDisplay.java @@ -0,0 +1,1119 @@ + +/** + * AppleIIGo + * Display processing + * (C) 2006 by Marc S. Ressl (ressl@lonetree.com) + * Released under the GPL + */ + +import java.io.*; +import java.applet.*; +import java.awt.*; +import java.awt.image.*; + +import javax.imageio.ImageIO; + +/** + * AppleDisplay class
+ * Refreshes the display + */ +public class AppleDisplay implements Runnable { + // Instances of other classes + private Applet applet; + private EmAppleII apple; + + // Configuration variables + public static final int COLORMODE_GREEN = 0; + public static final int COLORMODE_COLOR = 1; + + private int colorMode; + private boolean isGlare; + private boolean isStatMode; + + // Refresh + private int refreshRate; + private long refreshInterval; + private long refreshDelayCumulative; + private long refreshDelayPerSecond; + private long refreshCycle; + + private boolean isPrecalcRequested = true; + private boolean isRefreshRequested = true; + + // Graphics interface variables + private boolean[] graphicsDirty = new boolean[0x6000 >> 7]; + private int graphicsMode; + + // Display + private static final int DISPLAY_CHAR_SIZE_X = 7; + private static final int DISPLAY_CHAR_SIZE_Y = 8; + private static final int DISPLAY_CHAR_COUNT_X = 80; + private static final int DISPLAY_CHAR_COUNT_Y = 24; + private static final int DISPLAY_SIZE_X = DISPLAY_CHAR_COUNT_X * DISPLAY_CHAR_SIZE_X; + private static final int DISPLAY_SIZE_Y = DISPLAY_CHAR_COUNT_Y * DISPLAY_CHAR_SIZE_Y; + + // Display composition + private BufferedImage displayImage; + private int[] displayImageBuffer; + private BufferedImage displayImagePaused; + private BufferedImage displayImageGlare; + + // Display scale + private float displayScale; + private int displayScaledSizeX; + private int displayScaledSizeY; + + // Display palette + private int[] displayPalette; + private static final int[] displayPaletteGreen = { + 0x000000, 0x0e470e, 0x041204, 0x166e16, + 0x0f4a0f, 0x115411, 0x0c3b0c, 0x1f9e1f, + 0x125c12, 0x1b8a1b, 0x22ab22, 0x24b524, + 0x1A871a, 0x2de32d, 0x25bd25, 0x32ff32 + }; + private static final int[] displayPaletteColor = { + 0x000000, 0xdd0033, 0x000099, 0xdd22dd, + 0x007722, 0x555555, 0x2222ff, 0x66aaff, + 0x885500, 0xff6600, 0xaaaaaa, 0xff9988, + 0x11dd00, 0xffff00, 0x44ff99, 0xffffff + }; + + // Character stuff + private final int CHARSET_CHAR_SIZE_X = 8; + private final int CHARSET_CHAR_SIZE_Y = 8; + private final int CHARSET_SIZE_X = 0x100 * CHARSET_CHAR_SIZE_X; + private final int CHARSET_SIZE_Y = CHARSET_CHAR_SIZE_Y; + + private static final int CHARMAP_NORMAL = 0; + private static final int CHARMAP_FLASH = 1; + private static final int CHARMAP_ALT = 2; + + private int[] charSet = new int[CHARSET_SIZE_X * CHARSET_CHAR_SIZE_Y]; + private int[] charMap; + private int[][] charMaps = new int[3][0x100]; + private long charMapFlashCycle = 0; + private boolean isCharMapFlash = false; + + private int[][] charMapLookup = { + {0xc0,0xa0,0x40,0x20,0x40,0x20,0x40,0x60}, + {0xc0,0xa0,0xc0,0xa0,0x40,0x20,0x40,0x60}, + {0xc0,0xa0,0x00,0xe0,0x40,0x20,0x40,0x60}, + }; + + private static final int[] textLineAddress = { + 0x0000,0x0080,0x0100,0x0180,0x0200,0x0280,0x0300,0x0380, + 0x0028,0x00a8,0x0128,0x01a8,0x0228,0x02a8,0x0328,0x03a8, + 0x0050,0x00d0,0x0150,0x01d0,0x0250,0x02d0,0x0350,0x03d0, + }; + + // Hires stuff + private int hiresEvenOddToWord[] = new int[0x200]; + private int hiresWord[] = new int[8]; + private int hiresWordNext[] = new int[8]; + private int hiresLookup[] = new int[0x100]; // Bits: [NNccccPP] - Next, current, Previous bits + private static final int hiresLookupColor[] = { + // Bits: [PPNNcccc] - Previous, Next, current bits => 4 pixel @ 4 bit color output +// Color-bleeding algorithm + 0x0000,0x0111,0x2222,0x2333,0x4440,0x4551,0x6662,0x6773,0x8800,0x8911,0xaa22,0xab33,0xcc40,0xcd51,0xee62,0xef73, // 00cccc00 + 0x1000,0x1111,0x3222,0x3333,0x5440,0x5551,0x7662,0x7773,0x9800,0x9911,0xba22,0xbb33,0xdc40,0xdd51,0xfe62,0xff73, // 01cccc00 + 0x0000,0x0111,0x2222,0x2333,0x4440,0x4551,0x6662,0x6773,0x8800,0x8911,0xaa22,0xab33,0xcc40,0xcd51,0xee62,0xef73, // 10cccc00 + 0x1000,0x1111,0x3222,0x3333,0x5440,0x5551,0x7662,0x7773,0x9800,0x9911,0xba22,0xbb33,0xdc40,0xdd51,0xfe62,0xff73, // 11cccc00 + 0x0004,0x0115,0x2226,0x2337,0x4444,0x4555,0x6666,0x6777,0x8804,0x8915,0xaa26,0xab37,0xcc44,0xcd55,0xee66,0xef77, // 00cccc01 + 0x1004,0x1115,0x3226,0x3337,0x5444,0x5555,0x7666,0x7777,0x9804,0x9915,0xba26,0xbb37,0xdc44,0xdd55,0xfe66,0xff77, // 01cccc01 + 0x0004,0x0115,0x2226,0x2337,0x4444,0x4555,0x6666,0x6777,0x8804,0x8915,0xaa26,0xab37,0xcc44,0xcd55,0xee66,0xef77, // 10cccc01 + 0x1004,0x1115,0x3226,0x3337,0x5444,0x5555,0x7666,0x7777,0x9804,0x9915,0xba26,0xbb37,0xdc44,0xdd55,0xfe66,0xff77, // 11cccc01 + 0x0088,0x0199,0x22aa,0x23bb,0x44c8,0x45d9,0x66ea,0x67fb,0x8888,0x8999,0xaaaa,0xabbb,0xccc8,0xcdd9,0xeeea,0xeffb, // 00cccc10 + 0x1088,0x1199,0x32aa,0x33bb,0x54c8,0x55d9,0x76ea,0x77fb,0x9888,0x9999,0xbaaa,0xbbbb,0xdcc8,0xddd9,0xfeea,0xfffb, // 01cccc10 + 0x0088,0x0199,0x22aa,0x23bb,0x44c8,0x45d9,0x66ea,0x67fb,0x8888,0x8999,0xaaaa,0xabbb,0xccc8,0xcdd9,0xeeea,0xeffb, // 10cccc10 + 0x1088,0x1199,0x32aa,0x33bb,0x54c8,0x55d9,0x76ea,0x77fb,0x9888,0x9999,0xbaaa,0xbbbb,0xdcc8,0xddd9,0xfeea,0xfffb, // 11cccc10 + 0x008c,0x019d,0x22ae,0x23bf,0x44cc,0x45dd,0x66ee,0x67ff,0x888c,0x899d,0xaaae,0xabbf,0xcccc,0xcddd,0xeeee,0xefff, // 00cccc11 + 0x108c,0x119d,0x32ae,0x33bf,0x54cc,0x55dd,0x76ee,0x77ff,0x988c,0x999d,0xbaae,0xbbbf,0xdccc,0xdddd,0xfeee,0xffff, // 01cccc11 + 0x008c,0x019d,0x22ae,0x23bf,0x44cc,0x45dd,0x66ee,0x67ff,0x888c,0x899d,0xaaae,0xabbf,0xcccc,0xcddd,0xeeee,0xefff, // 10cccc11 + 0x108c,0x119d,0x32ae,0x33bf,0x54cc,0x55dd,0x76ee,0x77ff,0x988c,0x999d,0xbaae,0xbbbf,0xdccc,0xdddd,0xfeee,0xffff, // 11cccc11 + +// First table + 0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 00cccc00 + 0x0000,0x1111,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 01cccc00 + 0x0000,0x0001,0x2222,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 10cccc00 + 0x0000,0x0001,0x0020,0x3333,0x0400,0x0505,0x0660,0x0777,0xf000,0x9009,0xa0a0,0xb0bb,0xff00,0xdd0d,0xfff0,0xffff, // 11cccc00 + 0x0000,0x0001,0x0020,0x0033,0x4444,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 00cccc01 + 0x0000,0x0001,0x0020,0x0033,0x0400,0x5555,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 01cccc01 + 0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x6666,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 10cccc01 + 0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x7777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 11cccc01 + 0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0fff,0x8888,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 00cccc10 + 0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9999,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 01cccc10 + 0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xaaaa,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 10cccc10 + 0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xbbbb,0xcc00,0xdd0d,0xeee0,0xffff, // 11cccc10 + 0x0000,0x000f,0x0020,0x00ff,0x0400,0x0505,0x0660,0x0fff,0x8000,0x9009,0xa0a0,0xb0bb,0xcccc,0xdd0d,0xeee0,0xffff, // 00cccc11 + 0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdddd,0xeee0,0xffff, // 01cccc11 + 0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeeee,0xffff, // 10cccc11 + 0x0000,0x000f,0x00f0,0x00ff,0x0f00,0x0f0f,0x0ff0,0x0fff,0xf000,0xf00f,0xf0f0,0xf0ff,0xff00,0xff0f,0xfff0,0xffff, // 11cccc11 + + }; + private int[] doubleHiresPalette; + private static final int[] doubleHiresPaletteColor = { + 0x000000, 0x000099, 0x007722, 0x2222ff, + 0x885500, 0xaaaaaa, 0x11dd00, 0x44ff99, + 0xdd0033, 0xdd22dd, 0x555555, 0x66aaff, + 0xff6600, 0xff9988, 0xffff00, 0xffffff, + }; + + + // Thread stuff + private boolean isPaused = true; + private Thread thread; + private String threadError = null; + + + + /** + * AppleDisplay class constructor + * + * @param apple The EmAppleII instance + */ + public AppleDisplay(Applet applet, EmAppleII apple) { + this.applet = applet; + this.apple = apple; + + // Create display image + displayImage = new BufferedImage( + DISPLAY_SIZE_X, + DISPLAY_SIZE_Y, + BufferedImage.TYPE_INT_RGB); + displayImageBuffer = ((DataBufferInt) displayImage.getRaster().getDataBuffer()).getData(); + + // Load glare and pause images + try { + displayImageGlare = ImageIO.read(this.getClass().getResourceAsStream( + "/Resources/Glare.png")); + displayImagePaused = ImageIO.read(this.getClass().getResourceAsStream( + "/Resources/Paused.png")); + } catch (IOException e) { + threadError = e.toString(); + } + + // Character maps + precalcCharMaps(); + + // Hires + precalcHiresEvenEddToWord(); + + // Set parameters + setScale(1.0f); + setRefreshRate(10); + setColorMode(COLORMODE_GREEN); + setGlare(false); + setStatMode(false); + } + + /** + * Set scale + */ + public void setScale(float value) { + if (value <= 0.0f) + return; + + displayScale = value; + isPrecalcRequested = true; + } + + /** + * Get scale + */ + public float getScale() { + return displayScale; + } + + /** + * Set refresh rate + * + * @param value Display refresh rate in mHz + */ + public void setRefreshRate(int value) { + if (value <= 0.0f) + return; + + this.refreshRate = value; + refreshInterval = (int) (1000.0 / value); + } + + /** + * Get refresh rate + */ + public int getRefreshRate() { + return refreshRate; + } + + /** + * Set color mode + */ + public void setColorMode(int value) { + colorMode = value; + isPrecalcRequested = true; + } + + /** + * Get color mode + */ + public int getColorMode() { + return colorMode; + } + + /** + * Set paused + */ + public void setPaused(boolean value) { + if (isPaused == value) + return; + + isPaused = value; + if (isPaused) { + try { + thread.join(1000); + } catch (InterruptedException e) { + } + applet.repaint(); + } else { + isRefreshRequested = true; + thread = new Thread(this); + thread.start(); + } + } + + /** + * Get glare + */ + public boolean getPaused() { + return isPaused; + } + + /** + * Set glare + */ + public void setGlare(boolean value) { + isGlare = value; + isRefreshRequested = true; + } + + /** + * Get glare + */ + public boolean getGlare() { + return isGlare; + } + + /** + * Set stat mode + */ + public void setStatMode(boolean value) { + isStatMode = value; + isRefreshRequested = true; + } + + /** + * Get stat mode + */ + public boolean getStatMode() { + return isStatMode; + } + + /** + * Step instruction in debug mode + */ + public String getStatInfo() { + String statInfo = ""; + long refreshRateCurrent; + + // Calculate effective CPU speed + if (refreshDelayPerSecond > 1000) + refreshRateCurrent = refreshRate * 1000 / refreshDelayPerSecond; + else + refreshRateCurrent = refreshRate; + + // Return FPS + statInfo += " FPS=" + refreshRateCurrent + " [" + refreshDelayPerSecond + " ms/s]\n"; + statInfo += " GM=" + graphicsMode + "\n"; + if (threadError != null) + statInfo = statInfo.concat(threadError + "\n"); + + return statInfo; + } + + /** + * Paint function + * + * @param g Graphics object + */ + public void paint(Graphics g) { + if (displayImage != null) + g.drawImage(displayImage, + 0, 0, displayScaledSizeX, displayScaledSizeY, + 0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y, + applet); + + if (isStatMode) { + g.setColor(Color.black); + g.fillRect(0,0,256,128); + drawStatInfo(g); + } + if ((displayImagePaused != null) && isPaused) + g.drawImage(displayImagePaused, + 0, 0, displayScaledSizeX, displayScaledSizeY, + 0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y, + applet); + + if (isGlare && (displayImageGlare != null)) + g.drawImage(displayImageGlare, + 0, 0, displayScaledSizeX, displayScaledSizeY, + 0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y, + applet); + } + + /** + * Display refresh thread + */ + public void run() { + try { + while (!isPaused) { + long refreshStart = System.currentTimeMillis(); + long refreshDelay; + + refreshDisplay(); + + refreshDelay = System.currentTimeMillis() - refreshStart; + + refreshDelayCumulative += refreshDelay; + refreshCycle++; + if (refreshCycle >= refreshRate) { + refreshDelayPerSecond = refreshDelayCumulative; + refreshDelayCumulative = refreshCycle = 0; + } + + if (refreshDelay < refreshInterval) + Thread.sleep(refreshInterval - refreshDelay); + } + } catch (InterruptedException e) { + }; + } + + + + /** + * Paint stat info + */ + private void drawStatInfo(Graphics g) { + final int fontSize = 16; + + g.setColor(Color.WHITE); + + String statInfo = apple.getStatInfo() + "\n" + getStatInfo(); + String[] lines = statInfo.split("\n"); + + int drawPosY = fontSize; + for(int lineNo = 0; lineNo < lines.length; lineNo++) { + String line = lines[lineNo]; + g.drawString(line, 0, drawPosY); + drawPosY += fontSize; + } + } + + /** + * Display refresh + */ + private void refreshDisplay() { + boolean isCharsetUpdateRequested = false; + boolean isSetDirtyRequested = false; + boolean isSetHiresDirtyRequested = false; + boolean isRenderRequested = false; + + // Precalculation + if (isPrecalcRequested) { + isPrecalcRequested = false; + precalcDisplay(); + graphicsMode = -1; + } + + // Repaint if graphics mode changes + if (graphicsMode != apple.graphicsMode) { + graphicsMode = apple.graphicsMode; + isCharsetUpdateRequested = true; + isSetHiresDirtyRequested = true; + } + + // Periodic refresh + if (charMapFlashCycle <= 0) { + charMapFlashCycle = (int) (refreshRate / 4 - 1); + isCharMapFlash = !isCharMapFlash; + isCharsetUpdateRequested = true; + } else + charMapFlashCycle--; + + // Some internal variables + boolean isSomeText = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_MIXMODE)) != 0); + boolean isSomeLores = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_HIRES)) == 0); + boolean isSomeHires = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_HIRES)) == EmAppleII.GR_HIRES); + + boolean isMixedMode = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_MIXMODE)) == EmAppleII.GR_MIXMODE); + boolean isPage2 = ((graphicsMode & (EmAppleII.GR_80STORE | EmAppleII.GR_PAGE2)) == EmAppleII.GR_PAGE2); + boolean isAltChar = ((graphicsMode & EmAppleII.GR_ALTCHAR) != 0); + boolean isDoubleTextMode = ((graphicsMode & EmAppleII.GR_80CHAR) == EmAppleII.GR_80CHAR); + boolean isDoubleGraphicsMode = ((graphicsMode & (EmAppleII.GR_80CHAR | EmAppleII.GR_DHIRES)) == (EmAppleII.GR_80CHAR | EmAppleII.GR_DHIRES)); + + int baseAddressText = isPage2 ? apple.MEM_MAIN_RAM2 : apple.MEM_MAIN_TEXT; + int baseAddressHires = isPage2 ? apple.MEM_MAIN_RAM3 : apple.MEM_MAIN_HIRES; + + // Set char map + if (isCharsetUpdateRequested) { + if (isAltChar) + setCharMap(CHARMAP_ALT); + else if (isCharMapFlash) + setCharMap(CHARMAP_FLASH); + else + setCharMap(CHARMAP_NORMAL); + + isSetDirtyRequested = true; + isRefreshRequested = true; + } + + // Refresh dirty buffers? + if (isSetDirtyRequested) { + if (isSomeText || isSomeLores) + setTextBufferDirty(baseAddressText); + + if (isSetHiresDirtyRequested && isSomeHires) + setHiresBufferDirty(baseAddressHires); + + isRenderRequested = true; + } else { + if ((isSomeText || isSomeLores) && isTextBufferDirty(baseAddressText)) + isRenderRequested = true; + + if (isSomeHires && isHiresBufferDirty(baseAddressHires)) + isRenderRequested = true; + } + + // Draw + if (isRenderRequested) { + if (isSomeText) { + if (isDoubleTextMode) + renderDoubleText(baseAddressText, isMixedMode); + else + renderText(baseAddressText, isMixedMode); + } + + if (isSomeHires) { + if (isDoubleGraphicsMode) + renderDoubleHires(baseAddressHires, isMixedMode); + else + renderHires(baseAddressHires, isMixedMode); + } else if (isSomeLores) { + if (isDoubleGraphicsMode) + renderDoubleLores(baseAddressText, isMixedMode); + else + renderLores(baseAddressText, isMixedMode); + } + + isRefreshRequested = true; + } + + if (isRefreshRequested) { + isRefreshRequested = false; + applet.repaint(); + } + } + + /** + * Set text buffer dirty + */ + private void setTextBufferDirty(int baseAddress) { + // Update dirty + int addressStart = baseAddress >> 7; + int addressEnd = addressStart + 8; + for (int address = addressStart; address < addressEnd; address++) { + graphicsDirty[address] = true; + apple.graphicsDirty[address] = false; + } + } + + /** + * Set hires buffer dirty + */ + private void setHiresBufferDirty(int baseAddress) { + // Update dirty + int addressStart = baseAddress >> 7; + int addressEnd = addressStart + 8; + for (int address = addressStart; address < addressEnd; address++) { + graphicsDirty[address] = true; + apple.graphicsDirty[address + (0x0000 >> 7)] = false; + apple.graphicsDirty[address + (0x0400 >> 7)] = false; + apple.graphicsDirty[address + (0x0800 >> 7)] = false; + apple.graphicsDirty[address + (0x0c00 >> 7)] = false; + apple.graphicsDirty[address + (0x1000 >> 7)] = false; + apple.graphicsDirty[address + (0x1400 >> 7)] = false; + apple.graphicsDirty[address + (0x1800 >> 7)] = false; + apple.graphicsDirty[address + (0x1c00 >> 7)] = false; + } + } + + /** + * Is text buffer dirty? + */ + private boolean isTextBufferDirty(int baseAddress) { + boolean isDirty = false; + + // Update dirty + int addressStart = baseAddress >> 7; + int addressEnd = addressStart + 8; + for (int address = addressStart; address < addressEnd; address++) { + graphicsDirty[address] = apple.graphicsDirty[address]; + apple.graphicsDirty[address] = false; + if (graphicsDirty[address]) + isDirty = true; + } + + return isDirty; + } + + /** + * Is hires buffer dirty? + */ + private boolean isHiresBufferDirty(int baseAddress) { + boolean isDirty = false; + + // Update dirty + int addressStart = baseAddress >> 7; + int addressEnd = addressStart + 8; + for (int address = addressStart; address < addressEnd; address++) { + graphicsDirty[address] = + apple.graphicsDirty[address + (0x0000 >> 7)] || + apple.graphicsDirty[address + (0x0400 >> 7)] || + apple.graphicsDirty[address + (0x0800 >> 7)] || + apple.graphicsDirty[address + (0x0c00 >> 7)] || + apple.graphicsDirty[address + (0x1000 >> 7)] || + apple.graphicsDirty[address + (0x1400 >> 7)] || + apple.graphicsDirty[address + (0x1800 >> 7)] || + apple.graphicsDirty[address + (0x1c00 >> 7)]; + apple.graphicsDirty[address + (0x0000 >> 7)] = false; + apple.graphicsDirty[address + (0x0400 >> 7)] = false; + apple.graphicsDirty[address + (0x0800 >> 7)] = false; + apple.graphicsDirty[address + (0x0c00 >> 7)] = false; + apple.graphicsDirty[address + (0x1000 >> 7)] = false; + apple.graphicsDirty[address + (0x1400 >> 7)] = false; + apple.graphicsDirty[address + (0x1800 >> 7)] = false; + apple.graphicsDirty[address + (0x1c00 >> 7)] = false; + if (graphicsDirty[address]) + isDirty = true; + } + + return isDirty; + } + + + + /** + * Display precalculation + */ + private void precalcDisplay() { + // Display scaled size + displayScaledSizeX = (int) (DISPLAY_SIZE_X * displayScale / 2); + displayScaledSizeY = (int) (DISPLAY_SIZE_Y * displayScale); + + // Prepare display palette + setDisplayPalette(); + + // Prepare character set + loadCharSet(); + + // Prepare hires graphics + precalcHiresLookup(); + } + + /** + * Precalculate charSet + */ + private void loadCharSet() { + final int CHARSET_SOURCE_CHAR_COUNT = 128; + final int CHARSET_SOURCE_SIZE_X = CHARSET_SOURCE_CHAR_COUNT * CHARSET_CHAR_SIZE_X; + final int CHARSET_SOURCE_SIZE_Y = CHARSET_CHAR_SIZE_Y; + + int charSetOffset = 0; + + // Get RGB image + try { + BufferedImage charSetSource = ImageIO.read(this.getClass().getResourceAsStream( + "/Resources/Character Set.png")); + + charSetSource.getRGB( + 0, 0, + CHARSET_SOURCE_SIZE_X, CHARSET_SOURCE_SIZE_Y, + charSet, + 0, CHARSET_SIZE_X); + } catch (IOException e) { + threadError = e.toString(); + } + + // Duplicate and invert + for(int charSetPosY = 0; charSetPosY < CHARSET_SIZE_Y; charSetPosY++) { + for(int charSetPosX = 0; charSetPosX < CHARSET_SOURCE_SIZE_X; charSetPosX++) { + charSet[charSetOffset + CHARSET_SOURCE_SIZE_X + charSetPosX] = + charSet[charSetOffset + charSetPosX] ^ 0xffffff; + } + charSetOffset += CHARSET_SIZE_X; + } + + // Colorize + for(charSetOffset = 0; charSetOffset < (CHARSET_SIZE_X * CHARSET_SIZE_Y); charSetOffset++) + charSet[charSetOffset] &= (displayPalette[0xf] | 0xff000000); + } + + /** + * Precalculate char map + */ + private void precalcCharMaps() { + for(int index = 0; index < 3; index++) { + for(int character = 0; character < 0x100; character++) + charMaps[index][character] = charMapLookup[index][character >> 5] + (character & 0x1f); + } + } + + /** + * Set char map + */ + private void setCharMap(int value) { + charMap = charMaps[value]; + } + + /** + * Set lores palette + */ + private void setDisplayPalette() { + displayPalette = (colorMode == COLORMODE_COLOR) ? displayPaletteColor : displayPaletteGreen; + doubleHiresPalette = (colorMode == COLORMODE_COLOR) ? doubleHiresPaletteColor : displayPaletteGreen; + } + + /** + * Precalculate hires even odd to word + */ + private void precalcHiresEvenEddToWord() { + for(int value = 0; value < 0x200; value++) { + hiresEvenOddToWord[value] = + ((value & 0x01) << 0) | + ((value & 0x01) << 1) | + ((value & 0x02) << 1) | + ((value & 0x02) << 2) | + ((value & 0x04) << 2) | + ((value & 0x04) << 3) | + ((value & 0x08) << 3) | + ((value & 0x08) << 4) | + ((value & 0x10) << 4) | + ((value & 0x10) << 5) | + ((value & 0x20) << 5) | + ((value & 0x20) << 6) | + ((value & 0x40) << 6) | + ((value & 0x40) << 7); + + if ((value & 0x80) != 0) { + hiresEvenOddToWord[value] <<= 1; + hiresEvenOddToWord[value] |= ((value & 0x100) >> 8); + } + } + } + + /** + * Precalculate hires + */ + private void precalcHiresLookup() { + if (colorMode == COLORMODE_COLOR) { + for(int value = 0; value < 0x100; value++) + hiresLookup[value] = hiresLookupColor[((value << 6) & 0xff) | (value >> 2)]; + } else { + for(int value = 0; value < 0x100; value++) + hiresLookup[value] = + (((value & 0x04) != 0) ? 0x000f : 0) | + (((value & 0x08) != 0) ? 0x00f0 : 0) | + (((value & 0x10) != 0) ? 0x0f00 : 0) | + (((value & 0x20) != 0) ? 0xf000 : 0); + } + } + + + + /** + * Render text canvas + */ + private final void renderTextScanLine(int destOffset, int sourceOffset) { + displayImageBuffer[destOffset + 0] = displayImageBuffer[destOffset + 1] = charSet[sourceOffset]; + displayImageBuffer[destOffset + 2] = displayImageBuffer[destOffset + 3] = charSet[sourceOffset + 1]; + displayImageBuffer[destOffset + 4] = displayImageBuffer[destOffset + 5] = charSet[sourceOffset + 2]; + displayImageBuffer[destOffset + 6] = displayImageBuffer[destOffset + 7] = charSet[sourceOffset + 3]; + displayImageBuffer[destOffset + 8] = displayImageBuffer[destOffset + 9] = charSet[sourceOffset + 4]; + displayImageBuffer[destOffset + 10] = displayImageBuffer[destOffset + 11] = charSet[sourceOffset + 5]; + displayImageBuffer[destOffset + 12] = displayImageBuffer[destOffset + 13] = charSet[sourceOffset + 6]; + } + private final void renderTextCharacter(int destOffset, int sourceOffset) { + renderTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderTextScanLine(destOffset, sourceOffset); + } + private void renderText(int baseAddress, boolean isMixedMode) { + int screenCharY, screenCharYStart = isMixedMode ? 20 : 0; + int displayOffset, sourceOffset; + int address, addressEnd, addressStart; + + displayOffset = screenCharYStart * DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X; + for (screenCharY = screenCharYStart; screenCharY < 24; screenCharY++) { + addressStart = baseAddress + textLineAddress[screenCharY]; + + if (graphicsDirty[addressStart >> 7]) { + addressEnd = addressStart + 40; + + for (address = addressStart; address < addressEnd; address++) { + renderTextCharacter(displayOffset, charMap[apple.mem[address] & 0xff] << 3); + displayOffset += DISPLAY_CHAR_SIZE_X * 2; + } + displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X; + } else + displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X; + } + } + + /** + * Render double text canvas + */ + private final void renderDoubleTextScanLine(int destOffset, int sourceOffset) { + displayImageBuffer[destOffset + 0] = charSet[sourceOffset]; + displayImageBuffer[destOffset + 1] = charSet[sourceOffset + 1]; + displayImageBuffer[destOffset + 2] = charSet[sourceOffset + 2]; + displayImageBuffer[destOffset + 3] = charSet[sourceOffset + 3]; + displayImageBuffer[destOffset + 4] = charSet[sourceOffset + 4]; + displayImageBuffer[destOffset + 5] = charSet[sourceOffset + 5]; + displayImageBuffer[destOffset + 6] = charSet[sourceOffset + 6]; + } + private final void renderDoubleTextCharacter(int destOffset, int sourceOffset) { + renderDoubleTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderDoubleTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderDoubleTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderDoubleTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderDoubleTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderDoubleTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderDoubleTextScanLine(destOffset, sourceOffset); + destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X; + renderDoubleTextScanLine(destOffset, sourceOffset); + } + private void renderDoubleText(int baseAddress, boolean isMixedMode) { + int screenCharY, screenCharYStart = isMixedMode ? 20 : 0; + int displayOffset, sourceOffset; + int address, addressEnd, addressStart; + + displayOffset = screenCharYStart * DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X; + for (screenCharY = screenCharYStart; screenCharY < 24; screenCharY++) { + addressStart = baseAddress + textLineAddress[screenCharY]; + + if (graphicsDirty[addressStart >> 7]) { + addressEnd = addressStart + 40; + + for (address = addressStart; address < addressEnd; address++) { + renderDoubleTextCharacter(displayOffset, charMap[apple.mem[address + 0x10000] & 0xff] << 3); + displayOffset += DISPLAY_CHAR_SIZE_X; + renderDoubleTextCharacter(displayOffset, charMap[apple.mem[address + 0x00000] & 0xff] << 3); + displayOffset += DISPLAY_CHAR_SIZE_X; + } + displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X; + } else + displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X; + } + } + + /** + * Render lores canvas + */ + private final void renderLoresScanLine(int destOffset, int color) { + displayImageBuffer[destOffset + 0] = color; + displayImageBuffer[destOffset + 1] = color; + displayImageBuffer[destOffset + 2] = color; + displayImageBuffer[destOffset + 3] = color; + displayImageBuffer[destOffset + 4] = color; + displayImageBuffer[destOffset + 5] = color; + displayImageBuffer[destOffset + 6] = color; + } + private final void renderLoresBlock(int destOffset, int colorTop, int colorBottom) { + renderLoresScanLine(destOffset, colorTop); + destOffset += DISPLAY_SIZE_X; + renderLoresScanLine(destOffset, colorTop); + destOffset += DISPLAY_SIZE_X; + renderLoresScanLine(destOffset, colorTop); + destOffset += DISPLAY_SIZE_X; + renderLoresScanLine(destOffset, colorTop); + destOffset += DISPLAY_SIZE_X; + renderLoresScanLine(destOffset, colorBottom); + destOffset += DISPLAY_SIZE_X; + renderLoresScanLine(destOffset, colorBottom); + destOffset += DISPLAY_SIZE_X; + renderLoresScanLine(destOffset, colorBottom); + destOffset += DISPLAY_SIZE_X; + renderLoresScanLine(destOffset, colorBottom); + } + private void renderLores(int baseAddress, boolean isMixedMode) { + int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24; + int displayOffset, sourceOffset; + int address, addressEnd, addressStart; + + displayOffset = 0; + for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) { + addressStart = baseAddress + textLineAddress[screenCharY]; + + if (graphicsDirty[addressStart >> 7]) { + addressEnd = addressStart + 40; + + for (address = addressStart; address < addressEnd; address++) { + renderLoresBlock(displayOffset, + displayPalette[apple.mem[address] & 0xf], + displayPalette[(apple.mem[address] & 0xf0) >> 4]); + displayOffset += DISPLAY_CHAR_SIZE_X; + renderLoresBlock(displayOffset, + displayPalette[apple.mem[address] & 0xf], + displayPalette[(apple.mem[address] & 0xf0) >> 4]); + displayOffset += DISPLAY_CHAR_SIZE_X; + } + displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X; + } else + displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X; + } + } + + /** + * Render double lores canvas + */ + private void renderDoubleLores(int baseAddress, boolean isMixedMode) { + int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24; + int displayOffset, sourceOffset; + int address, addressEnd, addressStart; + + displayOffset = 0; + for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) { + addressStart = baseAddress + textLineAddress[screenCharY]; + + if (graphicsDirty[addressStart >> 7]) { + addressEnd = addressStart + 40; + + for (address = addressStart; address < addressEnd; address++) { + renderLoresBlock(displayOffset, + displayPalette[apple.mem[address + 0x10000] & 0xf], + displayPalette[(apple.mem[address + 0x10000] & 0xf0) >> 4]); + displayOffset += DISPLAY_CHAR_SIZE_X; + renderLoresBlock(displayOffset, + displayPalette[apple.mem[address] & 0xf], + displayPalette[(apple.mem[address] & 0xf0) >> 4]); + displayOffset += DISPLAY_CHAR_SIZE_X; + } + displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X; + } else + displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X; + } + } + + /** + * Render hires canvas + */ + private final void renderHiresWord(int destOffset, int hiresNibble) { + displayImageBuffer[destOffset + 0] = displayPalette[(hiresNibble >> 0) & 0xf]; + displayImageBuffer[destOffset + 1] = displayPalette[(hiresNibble >> 4) & 0xf]; + displayImageBuffer[destOffset + 2] = displayPalette[(hiresNibble >> 8) & 0xf]; + displayImageBuffer[destOffset + 3] = displayPalette[(hiresNibble >> 12) & 0xf]; + } + private final void renderHiresScanLine(int destOffset, int hiresWord) { + renderHiresWord(destOffset + 0, hiresLookup[(hiresWord >> 0) & 0xff]); + renderHiresWord(destOffset + 4, hiresLookup[(hiresWord >> 4) & 0xff]); + renderHiresWord(destOffset + 8, hiresLookup[(hiresWord >> 8) & 0xff]); + renderHiresWord(destOffset + 12, hiresLookup[(hiresWord >> 12) & 0xff]); + renderHiresWord(destOffset + 16, hiresLookup[(hiresWord >> 16) & 0xff]); + renderHiresWord(destOffset + 20, hiresLookup[(hiresWord >> 20) & 0xff]); + renderHiresWord(destOffset + 24, hiresLookup[(hiresWord >> 24) & 0xff]); + } + private final void renderHiresBlock(int destOffset) { + renderHiresScanLine(destOffset, hiresWord[0]); destOffset += DISPLAY_SIZE_X; + renderHiresScanLine(destOffset, hiresWord[1]); destOffset += DISPLAY_SIZE_X; + renderHiresScanLine(destOffset, hiresWord[2]); destOffset += DISPLAY_SIZE_X; + renderHiresScanLine(destOffset, hiresWord[3]); destOffset += DISPLAY_SIZE_X; + renderHiresScanLine(destOffset, hiresWord[4]); destOffset += DISPLAY_SIZE_X; + renderHiresScanLine(destOffset, hiresWord[5]); destOffset += DISPLAY_SIZE_X; + renderHiresScanLine(destOffset, hiresWord[6]); destOffset += DISPLAY_SIZE_X; + renderHiresScanLine(destOffset, hiresWord[7]); + } + private final void resetHiresWords() { + hiresWord[0] = 0; + hiresWord[1] = 0; + hiresWord[2] = 0; + hiresWord[3] = 0; + hiresWord[4] = 0; + hiresWord[5] = 0; + hiresWord[6] = 0; + hiresWord[7] = 0; + } + private final void bufferHiresWords() { + hiresWord[0] = hiresWordNext[0]; + hiresWord[1] = hiresWordNext[1]; + hiresWord[2] = hiresWordNext[2]; + hiresWord[3] = hiresWordNext[3]; + hiresWord[4] = hiresWordNext[4]; + hiresWord[5] = hiresWordNext[5]; + hiresWord[6] = hiresWordNext[6]; + hiresWord[7] = hiresWordNext[7]; + } + private final void calcNextHiresWord(int hiresWordIndex, int byteEven, int byteOdd) { + hiresWordNext[hiresWordIndex] = hiresWord[hiresWordIndex] >> 28; + hiresWordNext[hiresWordIndex] |= + hiresEvenOddToWord[(byteEven & 0xff) | ((hiresWordNext[hiresWordIndex] & 0x2) << 7)] << 2; + hiresWordNext[hiresWordIndex] |= + hiresEvenOddToWord[(byteOdd & 0xff) | ((hiresWordNext[hiresWordIndex] & 0x8000) >> 7)] << 16; + hiresWord[hiresWordIndex] |= (hiresWordNext[hiresWordIndex] << 28); + } + private final void calcNextHiresWords(int address) { + calcNextHiresWord(0, apple.mem[address + 0x00000], apple.mem[address + 0x00001]); + calcNextHiresWord(1, apple.mem[address + 0x00400], apple.mem[address + 0x00401]); + calcNextHiresWord(2, apple.mem[address + 0x00800], apple.mem[address + 0x00801]); + calcNextHiresWord(3, apple.mem[address + 0x00c00], apple.mem[address + 0x00c01]); + calcNextHiresWord(4, apple.mem[address + 0x01000], apple.mem[address + 0x01001]); + calcNextHiresWord(5, apple.mem[address + 0x01400], apple.mem[address + 0x01401]); + calcNextHiresWord(6, apple.mem[address + 0x01800], apple.mem[address + 0x01801]); + calcNextHiresWord(7, apple.mem[address + 0x01c00], apple.mem[address + 0x01c01]); + } + private void renderHires(int baseAddress, boolean isMixedMode) { + int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24; + int displayOffset, sourceOffset; + int address, addressEnd, addressStart; + + displayOffset = 0; + for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) { + addressStart = baseAddress + textLineAddress[screenCharY]; + + if (graphicsDirty[addressStart >> 7]) { + addressEnd = addressStart + 40; + + resetHiresWords(); + calcNextHiresWords(addressStart); + for (address = (addressStart + 2); address < addressEnd; address += 2) { + bufferHiresWords(); + calcNextHiresWords(address); + renderHiresBlock(displayOffset); + displayOffset += DISPLAY_CHAR_SIZE_X * 4; + } + bufferHiresWords(); + renderHiresBlock(displayOffset); + displayOffset += DISPLAY_CHAR_SIZE_X * 4; + + displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X; + } else + displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X; + } + } + + /** + * Render double hires canvas + */ + private final void renderDoubleHiresWord(int destOffset, int hiresNibble) { + displayImageBuffer[destOffset + 0] = doubleHiresPalette[(hiresNibble >> 0) & 0xf]; + displayImageBuffer[destOffset + 1] = doubleHiresPalette[(hiresNibble >> 4) & 0xf]; + displayImageBuffer[destOffset + 2] = doubleHiresPalette[(hiresNibble >> 8) & 0xf]; + displayImageBuffer[destOffset + 3] = doubleHiresPalette[(hiresNibble >> 12) & 0xf]; + } + private final void renderDoubleHiresScanLine(int destOffset, int hiresWord) { + renderDoubleHiresWord(destOffset + 0, hiresLookup[(hiresWord >> 0) & 0xff]); + renderDoubleHiresWord(destOffset + 4, hiresLookup[(hiresWord >> 4) & 0xff]); + renderDoubleHiresWord(destOffset + 8, hiresLookup[(hiresWord >> 8) & 0xff]); + renderDoubleHiresWord(destOffset + 12, hiresLookup[(hiresWord >> 12) & 0xff]); + renderDoubleHiresWord(destOffset + 16, hiresLookup[(hiresWord >> 16) & 0xff]); + renderDoubleHiresWord(destOffset + 20, hiresLookup[(hiresWord >> 20) & 0xff]); + renderDoubleHiresWord(destOffset + 24, hiresLookup[(hiresWord >> 24) & 0xff]); + } + private final void renderDoubleHiresBlock(int destOffset) { + renderDoubleHiresScanLine(destOffset, hiresWord[0]); destOffset += DISPLAY_SIZE_X; + renderDoubleHiresScanLine(destOffset, hiresWord[1]); destOffset += DISPLAY_SIZE_X; + renderDoubleHiresScanLine(destOffset, hiresWord[2]); destOffset += DISPLAY_SIZE_X; + renderDoubleHiresScanLine(destOffset, hiresWord[3]); destOffset += DISPLAY_SIZE_X; + renderDoubleHiresScanLine(destOffset, hiresWord[4]); destOffset += DISPLAY_SIZE_X; + renderDoubleHiresScanLine(destOffset, hiresWord[5]); destOffset += DISPLAY_SIZE_X; + renderDoubleHiresScanLine(destOffset, hiresWord[6]); destOffset += DISPLAY_SIZE_X; + renderDoubleHiresScanLine(destOffset, hiresWord[7]); + } + private final void calcNextDoubleHiresWord(int hiresWordIndex, int byte1, int byte2, int byte3, int byte4) { + hiresWordNext[hiresWordIndex] = ( + ((byte1 & 0x7f) << 2) | ((byte2 & 0x7f) << 9) | + ((byte3 & 0x7f) << 16) | ((byte4 & 0x7f) << 23) | + (hiresWord[hiresWordIndex] >> 28)); + hiresWord[hiresWordIndex] |= (hiresWordNext[hiresWordIndex] << 28); + } + private final void calcNextDoubleHiresWords(int address) { + calcNextDoubleHiresWord(0, + apple.mem[address + 0x10000], apple.mem[address + 0x00000], + apple.mem[address + 0x10001], apple.mem[address + 0x00001]); + calcNextDoubleHiresWord(1, + apple.mem[address + 0x10400], apple.mem[address + 0x00400], + apple.mem[address + 0x10401], apple.mem[address + 0x00401]); + calcNextDoubleHiresWord(2, + apple.mem[address + 0x10800], apple.mem[address + 0x00800], + apple.mem[address + 0x10801], apple.mem[address + 0x00801]); + calcNextDoubleHiresWord(3, + apple.mem[address + 0x10c00], apple.mem[address + 0x00c00], + apple.mem[address + 0x10c01], apple.mem[address + 0x00c01]); + calcNextDoubleHiresWord(4, + apple.mem[address + 0x11000], apple.mem[address + 0x01000], + apple.mem[address + 0x11001], apple.mem[address + 0x01001]); + calcNextDoubleHiresWord(5, + apple.mem[address + 0x11400], apple.mem[address + 0x01400], + apple.mem[address + 0x11401], apple.mem[address + 0x01401]); + calcNextDoubleHiresWord(6, + apple.mem[address + 0x11800], apple.mem[address + 0x01800], + apple.mem[address + 0x11801], apple.mem[address + 0x01801]); + calcNextDoubleHiresWord(7, + apple.mem[address + 0x11c00], apple.mem[address + 0x01c00], + apple.mem[address + 0x11c01], apple.mem[address + 0x01c01]); + } + private void renderDoubleHires(int baseAddress, boolean isMixedMode) { + int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24; + int displayOffset, sourceOffset; + int address, addressEnd, addressStart; + + displayOffset = 0; + for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) { + addressStart = baseAddress + textLineAddress[screenCharY]; + + if (graphicsDirty[addressStart >> 7]) { + addressEnd = addressStart + 40; + + resetHiresWords(); + calcNextDoubleHiresWords(addressStart); + for (address = (addressStart + 2); address < addressEnd; address += 2) { + bufferHiresWords(); + calcNextDoubleHiresWords(address); + renderDoubleHiresBlock(displayOffset); + displayOffset += DISPLAY_CHAR_SIZE_X * 4; + } + bufferHiresWords(); + renderDoubleHiresBlock(displayOffset); + displayOffset += DISPLAY_CHAR_SIZE_X * 4; + + displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X; + } else + displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X; + } + } +} diff --git a/Source/AppleIIGo.java b/Source/AppleIIGo.java new file mode 100644 index 0000000..3ef8e9b --- /dev/null +++ b/Source/AppleIIGo.java @@ -0,0 +1,445 @@ + +/** + * AppleIIGo + * The Java Apple II Emulator + * (C) 2006 by Marc S. Ressl(ressl@lonetree.com) + * Released under the GPL + */ + +import java.applet.*; +import java.io.*; +import java.net.*; +import java.util.zip.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.Graphics2D; + +/** + * AppleIIGo class
+ * Connects EmAppleII, AppleCanvas
+ */
+public class AppleIIGo extends Applet implements KeyListener, ComponentListener,
+ MouseListener, MouseMotionListener {
+ // Class instances
+ private EmAppleII apple;
+ private AppleDisplay display;
+ private AppleSpeaker speaker;
+ private DiskII disk;
+
+ // Machine variables
+ private boolean isCpuPaused;
+ private boolean isCpuDebugEnabled;
+
+ // Keyboard variables
+ private boolean keyboardUppercaseOnly;
+
+ // Paddle variables
+ private boolean isPaddleInverted;
+
+ // Disk variables
+ private String diskDriveResource[] = new String[2];
+ private boolean diskWritable;
+
+ /**
+ * Debug
+ */
+ private void debug(String message) {
+// System.out.println(message);
+ }
+
+ /**
+ * Parameters
+ */
+ private String getAppletParameter(String parameter, String defaultValue) {
+ String value = getParameter(parameter);
+ if ((value == null) || (value.length() == 0))
+ return defaultValue;
+ return value;
+ }
+
+ /**
+ * On applet initialization
+ */
+ public void init() {
+ debug("init()");
+
+ // Activate listeners
+ addKeyListener(this);
+ addMouseListener(this);
+ addMouseMotionListener(this);
+
+ if (getAppletParameter("displayFocusOnStart", "true").equals("true"))
+ addComponentListener(this);
+
+ // Initialize Apple II emulator
+ apple = new EmAppleII();
+ loadRom(getAppletParameter("cpuRom", ""));
+ apple.setCpuSpeed(new Integer(getAppletParameter("cpuSpeed", "1000")).intValue());
+ isCpuPaused = getAppletParameter("cpuPaused", "false").equals("true");
+ isCpuDebugEnabled = getAppletParameter("cpuDebugEnabled", "false").equals("true");
+ apple.setStepMode(getAppletParameter("cpuStepMode", "false").equals("true"));
+
+ // Keyboard
+ keyboardUppercaseOnly = getAppletParameter("keyboardUppercaseOnly", "true").equals("true");
+
+ // Display
+ display = new AppleDisplay(this, apple);
+ display.setScale(new Float(getAppletParameter("displayScale", "1")).floatValue());
+ display.setRefreshRate(new Integer(getAppletParameter("displayRefreshRate", "10")).intValue());
+ display.setColorMode(new Integer(getAppletParameter("displayColorMode", "0")).intValue());
+ display.setStatMode(getAppletParameter("displayStatMode", "false").equals("true"));
+ display.setGlare(getAppletParameter("displayGlare", "false").equals("true"));
+
+ // Speaker
+ speaker = new AppleSpeaker(apple);
+ speaker.setVolume(new Integer(getAppletParameter("speakerVolume", "3")).intValue());
+
+ // Peripherals
+ disk = new DiskII();
+ apple.setPeripheral(disk, 6);
+
+ // Initialize disk drives
+ diskWritable = getAppletParameter("diskWritable", "false").equals("true");
+ mountDisk(0, getAppletParameter("diskDrive1", ""));
+ mountDisk(1, getAppletParameter("diskDrive2", ""));
+
+ // Start CPU
+ if (!isCpuPaused)
+ resume();
+ }
+
+ /**
+ * Start applet
+ */
+ public void start() {
+ debug("start()");
+ }
+
+ /**
+ * Stop applet
+ */
+ public void stop() {
+ debug("stop()");
+ }
+
+ /**
+ * On applet destruction
+ */
+ public void destroy() {
+ debug("destroy()");
+ unmountDisk(0);
+ unmountDisk(1);
+ }
+
+
+
+ // Public Java interface
+
+ /**
+ * Javascript interface
+ */
+ public void focus() {
+ debug("focus()");
+ requestFocus();
+ }
+
+ /**
+ * Pause emulator
+ */
+ public void pause() {
+ debug("pause()");
+ isCpuPaused = true;
+ apple.setPaused(isCpuPaused);
+ display.setPaused(isCpuPaused);
+// speaker.setPaused(isCpuPaused);
+ }
+
+ /**
+ * Resume emulator
+ */
+ public void resume() {
+ debug("resume()");
+ isCpuPaused = false;
+// speaker.setPaused(isCpuPaused);
+ display.setPaused(isCpuPaused);
+ apple.setPaused(isCpuPaused);
+ }
+
+ /**
+ * Restarts emulator
+ */
+ public void restart() {
+ debug("restart()");
+ apple.restart();
+ }
+
+ /**
+ * Open input stream
+ */
+ private InputStream openInputStream(String resource) {
+ InputStream is = null;
+
+ try {
+ URL url = new URL(getCodeBase(), resource);
+ is = url.openStream();
+
+ if (resource.toLowerCase().endsWith(".gz"))
+ is = new GZIPInputStream(is);
+ } catch (Exception e) {
+ }
+
+ return is;
+ }
+
+ /**
+ * Open output stream
+ */
+ private OutputStream openOutputStream(String resource) {
+ OutputStream os = null;
+
+ try {
+ if (!(resource.substring(0, 6).equals("http://")))
+ os = new FileOutputStream(resource);
+ } catch (Exception e) {
+ }
+
+ return os;
+ }
+
+ /**
+ * Load ROM
+ */
+ public boolean loadRom(String resource) {
+ debug("loadRom(resource: " + resource + ")");
+ boolean success = false;
+
+ try {
+ InputStream is = openInputStream(resource);
+ success = apple.loadRom(is);
+ is.close();
+ } catch (Exception e) {
+ }
+
+ return success;
+ }
+
+ /**
+ * Mount a disk
+ */
+ public boolean mountDisk(int drive, String resource) {
+ debug("mountDisk(drive: " + drive + ", resource: " + resource + ")");
+ boolean success = false;
+
+ if ((drive < 0) || (drive > 2))
+ return success;
+
+ try {
+ unmountDisk(drive);
+
+ diskDriveResource[drive] = resource;
+
+ InputStream is = openInputStream(resource);
+ success = disk.readDisk(drive, is, 254, false);
+ is.close();
+ } catch (Exception e) {
+ }
+
+ return success;
+ }
+
+ /**
+ * Unmount a disk
+ */
+ public void unmountDisk(int drive) {
+ debug("unmountDisk(drive: " + drive + ")");
+ if ((drive < 0) || (drive > 2))
+ return;
+
+ if (!diskWritable)
+ return;
+
+ try {
+ OutputStream os = openOutputStream(diskDriveResource[drive]);
+ disk.writeDisk(drive, os);
+ os.close();
+ } catch (Exception e) {
+ }
+ }
+
+ /**
+ * Set color mode
+ */
+ public void setColorMode(int value) {
+ debug("setColorMode(value: " + value + ")");
+ display.setColorMode(value);
+ }
+
+ /**
+ * Get disk activity
+ */
+ public boolean getDiskActivity() {
+ return (!isCpuPaused && disk.isMotorOn());
+ }
+
+
+
+ /**
+ * KeyListener event handling
+ */
+ public void keyTyped(KeyEvent e) {
+ // Send to emulator
+ int key = e.getKeyChar();
+ if (key == 10)
+ apple.setKeyLatch(13);
+ else if (key < 128) {
+ if (keyboardUppercaseOnly && (key >= 97) && (key <= 122))
+ key -= 32;
+
+ apple.setKeyLatch(key);
+ }
+ }
+
+ public void keyPressed(KeyEvent e) {
+ switch(e.getKeyCode()) {
+ case KeyEvent.VK_BACK_SPACE:
+ case KeyEvent.VK_LEFT:
+ apple.setKeyLatch(8);
+ break;
+ case KeyEvent.VK_RIGHT:
+ apple.setKeyLatch(21);
+ break;
+ case KeyEvent.VK_UP:
+ if (e.isControlDown())
+ speaker.setVolume(speaker.getVolume() + 1);
+ else
+ apple.setKeyLatch(11);
+ break;
+ case KeyEvent.VK_DOWN:
+ if (e.isControlDown())
+ speaker.setVolume(speaker.getVolume() - 1);
+ else
+ apple.setKeyLatch(10);
+ break;
+ case KeyEvent.VK_ESCAPE:
+ apple.setKeyLatch(27);
+ break;
+ case KeyEvent.VK_DELETE:
+ apple.setKeyLatch(127);
+ break;
+ case KeyEvent.VK_HOME:
+ if (e.isControlDown())
+ apple.restart();
+ else
+ apple.reset();
+ break;
+ case KeyEvent.VK_F5:
+ if (isCpuDebugEnabled)
+ display.setStatMode(!display.getStatMode());
+ break;
+ case KeyEvent.VK_F6:
+ if (isCpuDebugEnabled)
+ apple.setStepMode(!apple.getStepMode());
+ break;
+ case KeyEvent.VK_F7:
+ if (isCpuDebugEnabled) {
+ apple.setStepMode(apple.getStepMode());
+ apple.stepInstructions(1);
+ }
+ break;
+ case KeyEvent.VK_F8:
+ if (isCpuDebugEnabled) {
+ apple.setStepMode(apple.getStepMode());
+ apple.stepInstructions(128);
+ }
+ break;
+ }
+ }
+
+ public void keyReleased(KeyEvent e) {
+ }
+
+ /**
+ * ComponentListener event handling
+ */
+ public void componentHidden(ComponentEvent e) {
+ }
+
+ public void componentMoved(ComponentEvent e) {
+ }
+
+ public void componentResized(ComponentEvent e) {
+ }
+
+ public void componentShown(ComponentEvent e) {
+ debug("componentShown()");
+
+ removeComponentListener(this);
+ requestFocus();
+ }
+
+ /**
+ * MouseListener, MouseMotionListener event handling
+ */
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ try {
+ getAppletContext().showDocument(new URL("javascript:flipMouseOver();"));
+ } catch (MalformedURLException ex) {
+ }
+ }
+
+ public void mouseExited(MouseEvent e) {
+ try {
+ getAppletContext().showDocument(new URL("javascript:flipMouseOut();"));
+ } catch (MalformedURLException ex) {
+ }
+ }
+
+ public void mousePressed(MouseEvent e) {
+ int modifiers = e.getModifiers();
+
+ if ((modifiers & InputEvent.BUTTON1_MASK) != 0)
+ apple.paddle.setButton(0, true);
+ if ((modifiers & InputEvent.BUTTON3_MASK) != 0)
+ apple.paddle.setButton(1, true);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ int modifiers = e.getModifiers();
+
+ if ((modifiers & InputEvent.BUTTON1_MASK) != 0)
+ apple.paddle.setButton(0, false);
+ if ((modifiers & InputEvent.BUTTON3_MASK) != 0)
+ apple.paddle.setButton(1, false);
+ }
+
+ public void mouseDragged(MouseEvent e) {
+ mouseMoved(e);
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ if (isPaddleInverted) {
+ apple.paddle.setPaddlePos(0, (int) (display.getScale() * (255 - e.getY() * 256 / 192)));
+ apple.paddle.setPaddlePos(1, (int) (display.getScale() * (255 - e.getX() * 256 / 280)));
+ } else {
+ apple.paddle.setPaddlePos(0, (int) (e.getX() * display.getScale() * 256 / 280));
+ apple.paddle.setPaddlePos(1, (int) (e.getY() * display.getScale() * 256 / 192));
+ }
+ }
+
+ /**
+ * Applet paint function
+ */
+ public void paint(Graphics g) {
+ display.paint(g);
+ }
+
+ /**
+ * Applet update function
+ */
+ public void update(Graphics g) {
+ display.paint(g);
+ }
+}
diff --git a/Source/AppleSpeaker.java b/Source/AppleSpeaker.java
new file mode 100644
index 0000000..4edcc38
--- /dev/null
+++ b/Source/AppleSpeaker.java
@@ -0,0 +1,240 @@
+
+/**
+ * AppleIIGo
+ * Speaker processing
+ * (C) 2006 by Marc S. Ressl(ressl@lonetree.com)
+ * Released under the GPL
+ */
+
+import javax.sound.sampled.*;
+
+public class AppleSpeaker implements Runnable {
+ // Instances of other classes
+ private EmAppleII apple;
+
+ // Refresh
+ private int refreshRate;
+ private long refreshInterval;
+
+ // Sound stuff
+ private static final int SPEAKER_BITS = 16;
+ private static final int SPEAKER_SAMPLERATE = 44100;
+ private static final int SPEAKER_CHANNELS = 1;
+ private static final int SPEAKER_SAMPLESIZE = (SPEAKER_BITS * SPEAKER_CHANNELS / 8);
+ private static final boolean SPEAKER_SIGNED = true;
+ private static final boolean SPEAKER_BIGENDIAN = false;
+
+ private int clock, clockNextFlip, clockEnd;
+ private boolean isFlipsBufferEmpty = true;
+
+ private SourceDataLine line;
+
+ private int bufferSamples;
+ private int bufferSize;
+ private byte[] buffer;
+
+ private int speakerVolume;
+ private int speakerFlipsPointer;
+ private int speakerFlipState;
+
+ private int[] speakerFlipStateToVolume = new int[2];
+ private int speakerClocksPerSample;
+
+ // Thread stuff
+ private boolean isPaused = true;
+ private Thread thread;
+ private String threadError = null;
+
+
+
+ public AppleSpeaker(EmAppleII apple) {
+ this.apple = apple;
+
+ setVolume(4);
+ }
+
+ /**
+ * Set refresh rate
+ *
+ * @param value Speaker refresh rate in mHz
+ */
+ private void setRefreshRate(int value) {
+ if (value <= 0.0f)
+ return;
+
+ this.refreshRate = value;
+ refreshInterval = (int) (1000.0 / value);
+
+ speakerClocksPerSample = (int) (apple.getCpuSpeed() * 1000.0f / SPEAKER_SAMPLERATE);
+ }
+
+ /**
+ * Get refresh rate
+ */
+ private int getRefreshRate() {
+ return refreshRate;
+ }
+
+ /**
+ * Set speaker volume
+ */
+ public void setVolume(int value) {
+ if ((value < 0) || (value > 7))
+ return;
+
+ speakerVolume = value;
+
+ int absVolume = 1 << (value + 8);
+ speakerFlipStateToVolume[0] = -absVolume;
+ speakerFlipStateToVolume[1] = absVolume;
+ }
+
+ /**
+ * Get speaker volume
+ */
+ public int getVolume() {
+ return speakerVolume;
+ }
+
+ /**
+ * Set pause state
+ */
+ public void setPaused(boolean value) {
+ if (isPaused == value)
+ return;
+
+ isPaused = value;
+ if (isPaused) {
+ try {
+ thread.join(1000);
+ } catch (InterruptedException e) {
+ }
+ if (line != null) {
+ line.stop();
+ line.close();
+ }
+ } else {
+ setRefreshRate(apple.getRefreshRate());
+
+ AudioFormat audioFormat = new AudioFormat(
+ SPEAKER_SAMPLERATE,
+ SPEAKER_BITS,
+ SPEAKER_CHANNELS,
+ SPEAKER_SIGNED,
+ SPEAKER_BIGENDIAN);
+
+ DataLine.Info info = new DataLine.Info(
+ DataLine.class,
+ audioFormat);
+
+ try {
+ line = (SourceDataLine) AudioSystem.getLine(info);
+ bufferSize = line.getBufferSize();
+ bufferSamples = bufferSize / SPEAKER_SAMPLESIZE;
+
+ buffer = new byte[bufferSize];
+
+ line.open(audioFormat);
+ line.start();
+ } catch (LineUnavailableException e) {
+ }
+
+ thread = new Thread(this);
+ thread.start();
+ }
+ }
+
+ /**
+ * Speaker refresh thread
+ */
+ public void run() {
+ try {
+ while (!isPaused) {
+ long refreshStart = System.currentTimeMillis();
+ long refreshDelay;
+
+ refreshSpeaker();
+
+ refreshDelay = System.currentTimeMillis() - refreshStart;
+
+ if (refreshDelay < refreshInterval)
+ Thread.sleep(refreshInterval - refreshDelay);
+ }
+ } catch (InterruptedException e) {
+ };
+ }
+
+
+
+ /**
+ * Speaker refresh
+ */
+ private void refreshSpeaker() {
+ clockEnd = apple.clock;
+ int bytes;
+
+ if (line == null)
+ return;
+
+ while ((bytes = fillBuffer()) > 0) {
+ line.write(buffer, 0, bytes);
+ }
+ }
+
+ /**
+ * Fill buffer
+ */
+ private int fillBuffer() {
+ int value = speakerFlipStateToVolume[speakerFlipState];
+ int clockEndSample = clockEnd - speakerClocksPerSample;
+ int bufferPointer = 0;
+
+ initNextFlip();
+ while (bufferPointer < bufferSize) {
+ if (clockEndSample == clock)
+ break;
+
+ if (((clockEndSample - clock) & 0x7fffffff) > 0x3fffffff)
+ break;
+
+ // Find all flips on current sample
+ while (((clockNextFlip - clock) & 0x7fffffff) < speakerClocksPerSample) {
+ getNextFlip();
+ speakerFlipState = (speakerFlipState ^ 1);
+ value = speakerFlipStateToVolume[speakerFlipState];
+ }
+
+ // Write sample
+ buffer[bufferPointer] = (byte) (value & 0xff);
+ buffer[bufferPointer + 1] = (byte) (value >> 8);
+ bufferPointer += SPEAKER_SAMPLESIZE;
+
+ clock += speakerClocksPerSample;
+ }
+
+ return bufferPointer;
+ }
+
+ /**
+ * Reset next flip
+ */
+ private void initNextFlip() {
+ if (isFlipsBufferEmpty) {
+ isFlipsBufferEmpty = false;
+ getNextFlip();
+ }
+ }
+
+ /**
+ * Gets next flip
+ */
+ private void getNextFlip() {
+ if (speakerFlipsPointer == apple.speakerFlipsPointer) {
+ clockNextFlip = clock + 0x3fffffff;
+ isFlipsBufferEmpty = true;
+ } else {
+ clockNextFlip = apple.speakerFlips[speakerFlipsPointer];
+ speakerFlipsPointer = (speakerFlipsPointer + 1) & apple.SPEAKER_FLIPS_MASK;
+ }
+ }
+}
diff --git a/Source/DiskII.java b/Source/DiskII.java
new file mode 100644
index 0000000..1cc0dbb
--- /dev/null
+++ b/Source/DiskII.java
@@ -0,0 +1,503 @@
+
+/**
+ * AppleIIGo
+ * Disk II Emulator
+ * (C) 2006 by Marc S. Ressl(ressl@lonetree.com)
+ * Released under the GPL
+ * Based on work by Doug Kwan
+ */
+
+import java.io.*;
+
+public class DiskII extends Peripheral {
+ // ROM (with boot wait cycle optimization)
+ private static final int[] rom = {
+ 0xA2,0x20,0xA0,0x00,0xA2,0x03,0x86,0x3C,0x8A,0x0A,0x24,0x3C,0xF0,0x10,0x05,0x3C,
+ 0x49,0xFF,0x29,0x7E,0xB0,0x08,0x4A,0xD0,0xFB,0x98,0x9D,0x56,0x03,0xC8,0xE8,0x10,
+ 0xE5,0x20,0x58,0xFF,0xBA,0xBD,0x00,0x01,0x0A,0x0A,0x0A,0x0A,0x85,0x2B,0xAA,0xBD,
+ 0x8E,0xC0,0xBD,0x8C,0xC0,0xBD,0x8A,0xC0,0xBD,0x89,0xC0,0xA0,0x50,0xBD,0x80,0xC0,
+ 0x98,0x29,0x03,0x0A,0x05,0x2B,0xAA,0xBD,0x81,0xC0,0xA9,0x56,0xa9,0x00,0xea,0x88,
+ 0x10,0xEB,0x85,0x26,0x85,0x3D,0x85,0x41,0xA9,0x08,0x85,0x27,0x18,0x08,0xBD,0x8C,
+ 0xC0,0x10,0xFB,0x49,0xD5,0xD0,0xF7,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0xAA,0xD0,0xF3,
+ 0xEA,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0x96,0xF0,0x09,0x28,0x90,0xDF,0x49,0xAD,0xF0,
+ 0x25,0xD0,0xD9,0xA0,0x03,0x85,0x40,0xBD,0x8C,0xC0,0x10,0xFB,0x2A,0x85,0x3C,0xBD,
+ 0x8C,0xC0,0x10,0xFB,0x25,0x3C,0x88,0xD0,0xEC,0x28,0xC5,0x3D,0xD0,0xBE,0xA5,0x40,
+ 0xC5,0x41,0xD0,0xB8,0xB0,0xB7,0xA0,0x56,0x84,0x3C,0xBC,0x8C,0xC0,0x10,0xFB,0x59,
+ 0xD6,0x02,0xA4,0x3C,0x88,0x99,0x00,0x03,0xD0,0xEE,0x84,0x3C,0xBC,0x8C,0xC0,0x10,
+ 0xFB,0x59,0xD6,0x02,0xA4,0x3C,0x91,0x26,0xC8,0xD0,0xEF,0xBC,0x8C,0xC0,0x10,0xFB,
+ 0x59,0xD6,0x02,0xD0,0x87,0xA0,0x00,0xA2,0x56,0xCA,0x30,0xFB,0xB1,0x26,0x5E,0x00,
+ 0x03,0x2A,0x5E,0x00,0x03,0x2A,0x91,0x26,0xC8,0xD0,0xEE,0xE6,0x27,0xE6,0x3D,0xA5,
+ 0x3D,0xCD,0x00,0x08,0xA6,0x2B,0x90,0xDB,0x4C,0x01,0x08,0x00,0x00,0x00,0x00,0x00,
+ };
+
+ // Constants
+ private static final int NUM_DRIVES = 2;
+ private static final int DOS_NUM_SECTORS = 16;
+ private static final int DOS_NUM_TRACKS = 35;
+ private static final int DOS_TRACK_BYTES = 256 * DOS_NUM_SECTORS;
+ private static final int RAW_TRACK_BYTES = 6250;
+
+ // Disk II direct access variables
+ private int drive = 0;
+ private boolean isMotorOn = false;
+
+ private byte[][][] disk = new byte[NUM_DRIVES][DOS_NUM_TRACKS][];
+ private boolean[] isWriteProtected = new boolean[NUM_DRIVES];
+
+ private int currPhysTrack;
+ private int currNibble;
+
+ // Caches
+ private int[] driveCurrPhysTrack = new int[NUM_DRIVES];
+ private byte[] realTrack;
+
+ /*
+ * Disk II emulation:
+ *
+ * C0xD, C0xE -> Read write protect
+ * C0xE, C0xC -> Read data from disk
+ * Write data to disk -> C0xF, C0xC
+ * Write data to disk -> C0xD, C0xC
+ *
+ * We use 'fast mode', i.e. no 65(C)02 clock reference
+ * We use simplified track handling (only adjacent phases)
+ */
+
+ // Internal registers
+ private int latchAddress;
+ private int latchData;
+ private boolean writeMode;
+
+ // GCR encoding and decoding tables
+ private static final int[] gcrEncodingTable = {
+ 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
+ 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
+ 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
+ 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
+ 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
+ 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
+ 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
+ 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+ };
+ private int[] gcrDecodingTable = new int[256];
+ private int[] gcrSwapBit = {0, 2, 1, 3};
+ private int[] gcrBuffer = new int[256];
+ private int[] gcrBuffer2 = new int[86];
+
+ // Physical sector to DOS 3.3 logical sector table
+ private static final int[] gcrLogicalSector = {
+ 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4,
+ 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF };
+
+ // Temporary variables for conversion
+ private byte[] gcrNibbles = new byte[RAW_TRACK_BYTES];
+ private int gcrNibblesPos;
+
+
+
+ /**
+ * Constructor
+ */
+ public DiskII() {
+ super();
+
+ readDisk(0, null, 254, false);
+ readDisk(1, null, 254, false);
+ }
+
+ /**
+ * I/O read
+ *
+ * @param address Address
+ */
+ public int ioRead(int address) {
+ int phase;
+
+ switch (address & 0xf) {
+ case 0x0:
+ case 0x2:
+ case 0x4:
+ case 0x6:
+ // Q0, Q1, Q2, Q3 off
+ break;
+ case 0x1:
+ // Q0 on
+ phase = currPhysTrack & 3;
+ if (phase == 1) {
+ if (currPhysTrack > 0)
+ currPhysTrack--;
+ } else if (phase == 3) {
+ if (currPhysTrack < ((2 * DOS_NUM_TRACKS) - 1))
+ currPhysTrack++;
+ }
+ realTrack = disk[drive][currPhysTrack >> 1];
+ break;
+ case 0x3:
+ // Q1 on
+ phase = currPhysTrack & 3;
+ if (phase == 2) {
+ if (currPhysTrack > 0)
+ currPhysTrack--;
+ } else if (phase == 0) {
+ if (currPhysTrack < ((2 * DOS_NUM_TRACKS) - 1))
+ currPhysTrack++;
+ }
+ realTrack = disk[drive][currPhysTrack >> 1];
+ break;
+ case 0x5:
+ // Q2 on
+ phase = currPhysTrack & 3;
+ if (phase == 3) {
+ if (currPhysTrack > 0)
+ currPhysTrack--;
+ } else if (phase == 1) {
+ if (currPhysTrack < ((2 * DOS_NUM_TRACKS) - 1))
+ currPhysTrack++;
+ }
+ realTrack = disk[drive][currPhysTrack >> 1];
+ break;
+ case 0x7:
+ // Q3 on
+ phase = currPhysTrack & 3;
+ if (phase == 0) {
+ if (currPhysTrack > 0)
+ currPhysTrack--;
+ } else if (phase == 2) {
+ if (currPhysTrack < ((2 * DOS_NUM_TRACKS) - 1))
+ currPhysTrack++;
+ }
+ realTrack = disk[drive][currPhysTrack >> 1];
+ break;
+ case 0x8:
+ // Motor off
+ isMotorOn = false;
+ break;
+ case 0x9:
+ // Motor on
+ isMotorOn = true;
+ break;
+ case 0xa:
+ // Drive 1
+ driveCurrPhysTrack[drive] = currPhysTrack;
+ drive = 0;
+ currPhysTrack = driveCurrPhysTrack[drive];
+
+ realTrack = disk[drive][currPhysTrack >> 1];
+ break;
+ case 0xb:
+ // Drive 2
+ driveCurrPhysTrack[drive] = currPhysTrack;
+ drive = 1;
+ currPhysTrack = driveCurrPhysTrack[drive];
+
+ realTrack = disk[drive][currPhysTrack >> 1];
+ break;
+ case 0xc:
+ return ioLatchC();
+ case 0xd:
+ ioLatchD(0xff);
+ break;
+ case 0xe:
+ return ioLatchE();
+ case 0xf:
+ ioLatchF(0xff);
+ break;
+ }
+
+ return rand.nextInt(256);
+ }
+
+ /**
+ * I/O write
+ *
+ * @param address Address
+ */
+ public void ioWrite(int address, int value) {
+ switch (address & 0xf) {
+ case 0x0:
+ case 0x1:
+ case 0x2:
+ case 0x3:
+ case 0x4:
+ case 0x5:
+ case 0x6:
+ case 0x7:
+ case 0x8:
+ case 0x9:
+ case 0xa:
+ case 0xb:
+ ioRead(address);
+ break;
+ case 0xc:
+ ioLatchC();
+ break;
+ case 0xd:
+ ioLatchD(value);
+ break;
+ case 0xe:
+ ioLatchE();
+ break;
+ case 0xf:
+ ioLatchF(value);
+ break;
+ }
+ }
+
+ /**
+ * Memory read
+ *
+ * @param address Address
+ */
+ public int memoryRead(int address) {
+ return rom[address & 0xff];
+ }
+
+ /**
+ * Reset peripheral
+ */
+ public void reset() {
+ ioRead(0x8);
+ }
+
+ /**
+ * Loads a disk
+ *
+ * @param is InputStream
+ * @param drive Disk II drive
+ */
+ public boolean readDisk(int drive, InputStream is, int volume, boolean isWriteProtected) {
+ byte[] track = new byte[RAW_TRACK_BYTES];
+
+ try {
+ for (int trackNum = 0; trackNum < DOS_NUM_TRACKS; trackNum++) {
+ disk[drive][trackNum] = new byte[RAW_TRACK_BYTES];
+
+ if (is != null) {
+ is.read(track, 0, DOS_TRACK_BYTES);
+ trackToNibbles(track, disk[drive][trackNum], volume, trackNum);
+ }
+ }
+
+ this.realTrack = disk[drive][currPhysTrack >> 1];
+ this.isWriteProtected[drive] = isWriteProtected;
+
+ return true;
+ } catch (IOException e) {
+ }
+
+ return false;
+ }
+
+ /**
+ * Writes a disk
+ *
+ * @param is InputStream
+ * @param drive Disk II drive
+ */
+ public boolean writeDisk(int drive, OutputStream os) {
+ return true;
+ }
+
+ /**
+ * Motor on indicator
+ */
+ public boolean isMotorOn() {
+ return isMotorOn;
+ }
+
+
+
+ /**
+ * I/O read Latch C
+ *
+ * @param address Address
+ */
+ private int ioLatchC() {
+ if (writeMode)
+ // Write data: C0xD, C0xC
+ realTrack[currNibble] = (byte) latchData;
+ else
+ // Read data: C0xE, C0xC
+ latchData = (realTrack[currNibble] & 0xff);
+
+ currNibble++;
+ if (currNibble >= RAW_TRACK_BYTES)
+ currNibble = 0;
+
+ latchAddress = 0xc;
+ return latchData;
+ }
+
+ /**
+ * I/O write Latch D
+ *
+ * @param address Address
+ */
+ private void ioLatchD(int value) {
+ // Prepare write
+ writeMode = true;
+ latchData = value;
+ latchAddress = 0xd;
+ }
+
+ /**
+ * I/O read Latch E
+ *
+ * @param address Address
+ */
+ private int ioLatchE() {
+ // Read write-protect: C0xD, C0xE
+ if (latchAddress == 0xd) {
+ latchAddress = 0xe;
+ return isWriteProtected[drive] ? 0x80 : 0x00;
+ }
+
+ writeMode = false;
+ latchAddress = 0xe;
+ return 0x3c;
+ }
+
+ /**
+ * I/O write Latch F
+ *
+ * @param address Address
+ */
+ private void ioLatchF(int value) {
+ // Prepare write
+ writeMode = true;
+ latchData = value;
+ latchAddress = 0xf;
+ }
+
+ /**
+ * TRACK CONVERSION ROUTINES
+ */
+
+ /**
+ * Writes a nibble
+ *
+ * @param value Value
+ */
+ private final void gcrWriteNibble(int value) {
+ gcrNibbles[gcrNibblesPos] = (byte) value;
+ gcrNibblesPos++;
+ }
+
+ /**
+ * Writes sync bits
+ *
+ * @param length Number of bits
+ */
+ private final void writeSync(int length) {
+ while(length > 0) {
+ length--;
+ gcrWriteNibble(0xff);
+ }
+ }
+
+ /**
+ * Write an FM encoded value, used in writing address fields
+ *
+ * @param value Value
+ */
+ private final void encode44(int value) {
+ gcrWriteNibble((value >> 1) | 0xaa);
+ gcrWriteNibble(value | 0xaa);
+ }
+
+ /**
+ * Encode in 6:2
+ *
+ * @param track Sectorized track data
+ * @param offset Offset in this data
+ */
+ private void encode62(byte[] track, int offset) {
+ // 86 * 3 = 258, so the first two byte are encoded twice
+ gcrBuffer2[0] = gcrSwapBit[track[offset + 1] & 0x03];
+ gcrBuffer2[1] = gcrSwapBit[track[offset] & 0x03];
+
+ // Save higher 6 bits in gcrBuffer and lower 2 bits in gcrBuffer2
+ for(int i = 255, j = 2; i >= 0; i--, j = j == 85 ? 0: j + 1) {
+ gcrBuffer2[j] = ((gcrBuffer2[j] << 2) | gcrSwapBit[track[offset + i] & 0x03]);
+ gcrBuffer[i] = (track[offset + i] & 0xff) >> 2;
+ }
+
+ // Clear off higher 2 bits of GCR_buffer2 set in the last call
+ for(int i = 0; i < 86; i++)
+ gcrBuffer2[i] &= 0x3f;
+ }
+
+ /**
+ * Write address field
+ *
+ * @param track Sectorized track data
+ * @param offset Offset in this data
+ */
+ private final void writeAddressField(int volumeNum, int trackNum, int sectorNum) {
+ // Write address mark
+ gcrWriteNibble(0xd5);
+ gcrWriteNibble(0xaa);
+ gcrWriteNibble(0x96);
+
+ // Write volume, trackNum, sector & checksum
+ encode44(volumeNum);
+ encode44(trackNum);
+ encode44(sectorNum);
+ encode44(volumeNum ^ trackNum ^ sectorNum);
+
+ // Write epilogue
+ gcrWriteNibble(0xde);
+ gcrWriteNibble(0xaa);
+ gcrWriteNibble(0xeb);
+ }
+
+ /**
+ * Write data field
+ */
+ private void writeDataField() {
+ int last = 0;
+ int checksum;
+
+ // Write prologue
+ gcrWriteNibble(0xd5);
+ gcrWriteNibble(0xaa);
+ gcrWriteNibble(0xad);
+
+ // Write GCR encoded data
+ for(int i = 0x55; i >= 0; i--) {
+ checksum = last ^ gcrBuffer2[i];
+ gcrWriteNibble(gcrEncodingTable[checksum]);
+ last = gcrBuffer2[i];
+ }
+ for(int i = 0; i < 256; i++) {
+ checksum = last ^ gcrBuffer[i];
+ gcrWriteNibble(gcrEncodingTable[checksum]);
+ last = gcrBuffer[i];
+ }
+
+ // Write checksum
+ gcrWriteNibble(gcrEncodingTable[last]);
+
+ // Write epilogue
+ gcrWriteNibble(0xde);
+ gcrWriteNibble(0xaa);
+ gcrWriteNibble(0xeb);
+ }
+
+ /**
+ * Converts a track to nibbles
+ */
+ private void trackToNibbles(byte[] track, byte[] nibbles, int volumeNum, int trackNum) {
+ this.gcrNibbles = nibbles;
+ gcrNibblesPos = 0;
+
+ for (int sectorNum = 0; sectorNum < DOS_NUM_SECTORS; sectorNum++) {
+ encode62(track, gcrLogicalSector[sectorNum] << 8);
+ writeSync(12);
+ writeAddressField(volumeNum, trackNum, sectorNum);
+ writeSync(8);
+ writeDataField();
+ }
+ writeSync(RAW_TRACK_BYTES - gcrNibblesPos);
+ }
+}
diff --git a/Source/Em6502.java b/Source/Em6502.java
new file mode 100644
index 0000000..b2df6e2
--- /dev/null
+++ b/Source/Em6502.java
@@ -0,0 +1,1617 @@
+
+/**
+ * AppleIIGo
+ * Apple II Emulator for J2SE
+ * (C) 2006 by Marc S. Ressl(ressl@lonetree.com)
+ * Released under the GPL
+ * Adapted from code by Doug Kwan
+ * Adapted from code by Randy Frank randy@tessa.iaf.uiowa.edu
+ * Adapted from code (C) 1989 Ben Koning [556498717 408/738-1763 ben@apple.com]
+ */
+
+public class Em6502 {
+ /**
+ * Base memory (including zero page and stack)
+ */
+ public byte[] mem = null;
+
+ /**
+ * Generic memory read & write (0x0000-0xffff)
+ */
+ protected int memoryRead(int addr) {
+ return (mem[addr] & 0xff);
+ }
+ protected void memoryWrite(int addr, int value) {
+ mem[addr] = (byte) value;
+ }
+
+ /*
+ * Zero page read & write
+ */
+ private final int zeroPageRead(int addr) {
+ return (mem[addr] & 0xff);
+ }
+ private final void zeroPageWrite(int addr, int value) {
+ mem[addr] = (byte) value;
+ }
+
+ /**
+ * Userspace interrupts
+ */
+ public final void assertReset() {
+ exceptionRegister |= SIG_6502_RESET;
+ }
+ public final void assertNMI() {
+ exceptionRegister |= SIG_6502_NMI;
+ }
+ public final void assertIRQ() {
+ exceptionRegister |= SIG_6502_IRQ;
+ }
+
+ /**
+ * Userspace interrupt handlers
+ */
+ protected void onReset() {
+ };
+
+ protected void onNMI() {
+ };
+
+ protected void onIRQ() {
+ };
+
+ /**
+ * CPU Registers
+ */
+ public int A, X, Y, P, S, PC;
+
+ /**
+ * CPU Clock
+ */
+ protected int clock;
+
+ /**
+ * CPU Flags
+ */
+ public static final int FLAG_C = (1 << 0);
+ public static final int FLAG_Z = (1 << 1);
+ public static final int FLAG_I = (1 << 2);
+ public static final int FLAG_D = (1 << 3);
+ public static final int FLAG_B = (1 << 4);
+ public static final int FLAG_V = (1 << 6);
+ public static final int FLAG_N = (1 << 7);
+ /*
+ * owing to a bug in 6502, the bit 5 must be always 1;
+ * otherwise, programs like DOS 3.3 will break down
+ * see instructions in $9FF4-$9FF5 of DOS 3.3
+ */
+
+ /**
+ * CPU Signals
+ */
+ private int exceptionRegister = 0;
+
+ public static final int SIG_6502_RESET = (1 << 0);
+ public static final int SIG_6502_NMI = (1 << 1);
+ public static final int SIG_6502_IRQ = (1 << 2);
+
+ /**
+ * CPU IRQ State
+ */
+ private int pendingIRQ;
+
+ /**
+ * Emulator registers
+ */
+ private int easp1, easp2;
+ private int operandAddress;
+ private int opcode;
+ private int operand;
+ private int result;
+ private int NZFlags;
+
+ /**
+ * ALU look up tables
+ */
+ private int BCDTableAdd[]; // addition correction
+ private int BCDTableSub[]; // subtraction correction
+
+ /**
+ * Constructor
+ */
+ public void Em6502() {
+ // Init BCD tables
+ BCDTableAdd = new int[512];
+ BCDTableSub = new int[512];
+
+ for (int i = 0; i < 512; i++) {
+ BCDTableAdd[i] = ((i & 0x0f) <= 0x09) ? i : (i + 0x06);
+ BCDTableAdd[i] += ((BCDTableAdd[i] & 0xf0) <= 0x90) ? 0 : 0x60;
+ if (BCDTableAdd[i] > 0x1ff)
+ BCDTableAdd[i] -= 0x100;
+
+ BCDTableSub[i] = ((i & 0x0f) <= 0x09) ? i : (i - 0x06);
+ BCDTableSub[i] -= ((BCDTableSub[i] & 0xf0) <= 0x90) ? 0 : 0x60;
+ }
+ }
+
+ /*
+ * Stack macros
+ */
+ private final int pop() {
+ S++;
+ S &= 0xff;
+ return (mem[S | 0x100] & 0xff);
+ }
+ private final void push(int value) {
+ mem[S | 0x100] = (byte) value;
+ S--;
+ S &= 0xff;
+ }
+
+ /*
+ * Macros for P flags
+ */
+ private final void setN(boolean b) {if (b) P |= FLAG_N; else P &= ~FLAG_N;}
+ private final void setV(boolean b) {if (b) P |= FLAG_V; else P &= ~FLAG_V;}
+ private final void setB(boolean b) {if (b) P |= FLAG_B; else P &= ~FLAG_B;}
+ private final void setD(boolean b) {if (b) P |= FLAG_D; else P &= ~FLAG_D;}
+ private final void setI(boolean b) {if (b) P |= FLAG_I; else P &= ~FLAG_I;}
+ private final void setZ(boolean b) {if (b) P |= FLAG_Z; else P &= ~FLAG_Z;}
+ private final void setC(boolean b) {if (b) P |= FLAG_C; else P &= ~FLAG_C;}
+ private final boolean getN() {return ((P & FLAG_N) != 0);}
+ private final boolean getV() {return ((P & FLAG_V) != 0);}
+ private final boolean getB() {return ((P & FLAG_B) != 0);}
+ private final boolean getD() {return ((P & FLAG_D) != 0);}
+ private final boolean getI() {return ((P & FLAG_I) != 0);}
+ private final boolean getZ() {return ((P & FLAG_Z) != 0);}
+ private final boolean getC() {return ((P & FLAG_C) != 0);}
+
+ /**
+ * Fast condition codes. Instead of using bits to encode condition codes,
+ * recent ALU results are cached to that the condition codes can be
+ * handled more easily by the emulator's native hardware.
+ */
+ private final boolean getFN() {return ((NZFlags & 0x280) != 0);}
+ private final boolean getFNotN() {return ((NZFlags & 0x280) == 0);}
+ private final boolean getFZ() {return ((NZFlags & 0xff) == 0);}
+ private final boolean getFNotZ() {return ((NZFlags & 0xff) != 0);}
+ private final void setFNZ(boolean n, boolean z) {NZFlags = ((n) ? 0x200 : 0x00) | ((z) ? 0x00 : 0x01);}
+ private final boolean getFC() {return (result >> 8) != 0;}
+ private final boolean getFNotC() {return (result >> 8) == 0;}
+ private final int getFC_() {return result >> 8;}
+ private final void setFC(boolean c) {result = (c ? 0x100 : 0x00);}
+
+ /*
+ * Macro for page crossing cycle regulation
+ */
+ private final void checkCrossPage(int addr, int offset) {
+ if ((((addr + offset) ^ addr) & 0xff00) != 0) clock++;
+ }
+
+ /*
+ * Macros for effective address calculation
+ * (Macros whose names end with NC do not check for page crossing)
+ */
+ private final int eaimm() {
+ easp1 = memoryRead(PC); PC++; return easp1;
+ }
+ private final int eazp() {
+ easp1 = memoryRead(PC); PC++; return easp1;
+ }
+ private final int eazpx() {
+ easp1 = (memoryRead(PC) + X) & 0xff; PC++; return easp1;
+ }
+ private final int eazpy() {
+ easp1 = (memoryRead(PC) + Y) & 0xff; PC++; return easp1;
+ }
+ private final int eaabs() {
+ easp1 = memoryRead(PC); PC++;
+ easp1 += (memoryRead(PC) << 8); PC++;
+ return easp1;
+ }
+ private final int earel() {
+// easp1 = memoryRead(PC); PC++;
+// return ((easp1 & 0x80) != 0) ? easp1 - 256 : easp1;
+ easp1 = (byte) memoryRead(PC); PC++; return easp1;
+ }
+ private final int eaabsx() {
+ // No cross page check...
+ // easp1 = eaabs();
+ // checkCrossPage(easp1, X);
+ // return easp1 + X;
+ return eaabs() + X;
+ }
+ private final int eaabsxNC() {
+ return eaabs() + X;
+ }
+ private final int eaabsy() {
+ // No cross page check...
+ // easp1 = eaabs();
+ // checkCrossPage(easp1, Y);
+ // return easp1 + Y;
+ return eaabs() + Y;
+ }
+ private final int eaabsyNC() {
+ return eaabs() + Y;
+ }
+
+ /*
+ * Indirect addressing
+ */
+ private final int eaabsind() {
+ easp1 = eaabs();
+ easp2 = memoryRead(easp1);
+ return easp2 + (memoryRead(easp1 + 1) << 8);
+ }
+ private final int eazpxind() {
+ easp1 = eazpx();
+ easp2 = zeroPageRead(easp1);
+ return easp2 + (zeroPageRead((easp1 + 1) & 0xff) << 8);
+ }
+ private final int eazpindy() {
+ easp1 = eaimm();
+ easp2 = zeroPageRead(easp1);
+ // No cross page check...
+ // easp2 += (zeroPageRead((easp1 + 1) & 0xff) << 8);
+ // checkCrossPage(easp2,Y);
+ // return easp2 + Y;
+ return easp2 + (zeroPageRead((easp1 + 1) & 0xff) << 8) + Y;
+ }
+ private final int eazpindyNC() {
+ easp1 = eaimm();
+ easp2 = zeroPageRead(easp1);
+ return easp2 + (zeroPageRead((easp1 + 1) & 0xff) << 8) + Y;
+ }
+
+ /*
+ * New 65C02 addressing mode
+ */
+ private final int eazpind() {
+ easp1 = eazp();
+ easp2 = zeroPageRead(easp1);
+ return easp2 + (zeroPageRead((easp1 + 1) & 0xff) << 8);
+ }
+ private final int eaabsxind() {
+ easp1 = eaabs();
+ easp2 = memoryRead(easp1);
+ return easp2 + (memoryRead(easp1 + 1) << 8) + X;
+ }
+
+ /*
+ * Misc. macros
+ */
+ private final void adcBCDAdjust() {
+ if (getD()) result = BCDTableAdd[result];
+ }
+ private final void sbcBCDAdjust() {
+ if (getD()) result = BCDTableSub[result];
+ }
+ private final void branch(int operand) {
+ // No cross page check...
+ // checkCrossPage(PC, operand);
+ PC += operand;
+ clock++;
+ }
+
+ /** This executes a single instruction. */
+ private final void executeInstruction() {
+ opcode = memoryRead(PC);
+ PC++;
+
+ switch(opcode) {
+ case 0x69: // ADC #imm
+ operand = eaimm();
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ adcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0x6D: // ADC abs
+ operand = memoryRead(eaabs());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ adcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x65: // ADC zp
+ operand = zeroPageRead(eazp());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ adcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 3;
+ break;
+
+ case 0x61: // ADC (zp,X)
+ operand = memoryRead(eazpxind());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ adcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 6;
+ break;
+
+ case 0x71: // ADC (zp),Y
+ operandAddress = eazpindy();
+ operand = memoryRead(operandAddress);
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ adcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0x75: // ADC zp,X
+ operandAddress = eazpx();
+ operand = zeroPageRead(operandAddress);
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ adcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x7D: // ADC abs,X
+ operand = memoryRead(eaabsx());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ adcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x79: // ADC abs,Y
+ operand = memoryRead(eaabsy());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ adcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x29: // AND #imm
+ A &= eaimm();
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0x2D: // AND abs
+ A &= memoryRead(eaabs());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x25: // AND zp
+ A &= zeroPageRead(eazp());
+ NZFlags = A;
+ clock += 3;
+ break;
+
+ case 0x21: // AND (zp,X)
+ A &= memoryRead(eazpxind());
+ NZFlags = A;
+ clock += 6;
+ break;
+
+ case 0x31: // AND (zp),Y
+ A &= memoryRead(eazpindy());
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0x35: // AND zp,X
+ A &= zeroPageRead(eazpx());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x3D: // AND abs,X
+ A &= memoryRead(eaabsx());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x39: // AND abs,Y
+ A &= memoryRead(eaabsy());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x0E: // ASL abs
+ operandAddress = eaabs();
+ operand = memoryRead(operandAddress);
+ result = operand << 1;
+ NZFlags = result;
+ memoryWrite(operandAddress, result);
+ clock += 6;
+ break;
+
+ case 0x06: // ASL zp
+ operandAddress = eazp();
+ operand = zeroPageRead(operandAddress);
+ result = operand << 1;
+ NZFlags = result;
+ zeroPageWrite(operandAddress, result);
+ clock += 5;
+ break;
+
+ case 0x0A: // ASL acc
+ result = A << 1;
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0x16: // ASL zp,X
+ operandAddress = eazpx();
+ operand = zeroPageRead(operandAddress);
+ result = operand << 1;
+ NZFlags = result;
+ zeroPageWrite(operandAddress, result);
+ clock += 6;
+ break;
+
+ case 0x1E: // ASL abs,X
+ operandAddress = eaabsx();
+ operand = memoryRead(operandAddress);
+ result = operand << 1;
+ NZFlags = result;
+ memoryWrite(operandAddress, result);
+ clock += 7;
+ break;
+
+ case 0x90: // BCC rr
+ operand = earel();
+ clock += 2;
+ if (getFNotC())
+ branch(operand);
+ break;
+
+ case 0xB0: // BCS rr
+ operand = earel();
+ clock += 2;
+ if (getFC())
+ branch(operand);
+ break;
+
+ case 0xF0: // BEQ rr
+ operand = earel();
+ clock += 2;
+ if (getFZ())
+ branch(operand);
+ break;
+
+ case 0x2C: // BIT abs
+ operand = memoryRead(eaabs());
+ setV((operand & 0x40) != 0);
+ NZFlags = ((operand & 0x80) << 2) | (A & operand);
+ clock += 4;
+ break;
+
+ case 0x24: // BIT zp
+ operand = zeroPageRead(eazp());
+ setV((operand & 0x40) != 0);
+ NZFlags = ((operand & 0x80) << 2) | (A & operand);
+ clock += 3;
+ break;
+
+ case 0x30: // BMI rr
+ operand = earel();
+ clock += 2;
+ if (getFN())
+ branch(operand);
+ break;
+
+ case 0xD0: // BNE rr
+ operand = earel();
+ clock += 2;
+ if (getFNotZ())
+ branch(operand);
+ break;
+
+ case 0x10: // BPL rr
+ operand = earel();
+ clock += 2;
+ if (getFNotN())
+ branch(operand);
+ break;
+
+ case 0x00: // BRK
+ push(PC >> 8); // save PCH, PCL & P
+ push(PC);
+ setN(getFN());
+ setZ(getFZ());
+ setC(getFC());
+ setB(true);
+ push(P);
+ setI(true);
+ PC = memoryRead(0xfffe);
+ PC |= memoryRead(0xffff) << 8;
+ clock += 7;
+ break;
+
+ case 0x50: // BVC rr
+ operand = earel();
+ clock += 2;
+ if (!getV())
+ branch(operand);
+ break;
+
+ case 0x70: // BVS rr
+ operand = earel();
+ clock += 2;
+ if (getV())
+ branch(operand);
+ break;
+
+ case 0x18: // CLC rr
+ setFC(false);
+ clock += 2;
+ break;
+
+ case 0xD8: // CLD
+ setD(false);
+ clock += 2;
+ break;
+
+ case 0x58: // CLI
+ setI(false);
+ clock += 2;
+ if (pendingIRQ > 0) {
+ pendingIRQ--;
+ assertIRQ();
+ }
+ break;
+
+ case 0xB8: // CLV
+ setV(false);
+ clock += 2;
+ break;
+
+ case 0xC9: // CMP #imm
+ result = 0x100 + A - eaimm();
+ NZFlags = result;
+ clock += 2;
+ break;
+
+ case 0xCD: // CMP abs
+ result = 0x100 + A - memoryRead(eaabs());
+ NZFlags = result;
+ clock += 4;
+ break;
+
+ case 0xC5: // CMP zp
+ result = 0x100 + A - zeroPageRead(eazp());
+ NZFlags = result;
+ clock += 3;
+ break;
+
+ case 0xC1: // CMP (zp,X)
+ result = 0x100 + A - memoryRead(eazpxind());
+ NZFlags = result;
+ clock += 6;
+ break;
+
+ case 0xD1: // CMP (zp),Y
+ result = 0x100 + A - memoryRead(eazpindy());
+ NZFlags = result;
+ clock += 5;
+ break;
+
+ case 0xD5: // CMP zp,X
+ result = 0x100 + A - zeroPageRead(eazpx());
+ NZFlags = result;
+ clock += 4;
+ break;
+
+ case 0xDD: // CMP abs,X
+ result = 0x100 + A - memoryRead(eaabsx());
+ NZFlags = result;
+ clock += 4;
+ break;
+
+ case 0xD9: // CMP abs,Y
+ result = 0x100 + A - memoryRead(eaabsy());
+ NZFlags = result;
+ clock += 4;
+ break;
+
+ case 0xE0: // CPX #imm
+ result = 0x100 + X - eaimm();
+ NZFlags = result;
+ clock += 2;
+ break;
+
+ case 0xEC: // CPX abs
+ result = 0x100 + X - memoryRead(eaabs());
+ NZFlags = result;
+ clock += 4;
+ break;
+
+ case 0xE4: // CPX zp
+ result = 0x100 + X - zeroPageRead(eazp());
+ NZFlags = result;
+ clock += 3;
+ break;
+
+ case 0xC0: // CPY #imm
+ result = 0x100 + Y - eaimm();
+ NZFlags = result;
+ clock += 2;
+ break;
+
+ case 0xCC: // CPY abs
+ result = 0x100 + Y - memoryRead(eaabs());
+ NZFlags = result;
+ clock += 4;
+ break;
+
+ case 0xC4: // CPY zp
+ result = 0x100+ Y - zeroPageRead(eazp());
+ NZFlags = result;
+ clock += 3;
+ break;
+
+ case 0xCE: // DEC abs
+ operandAddress = eaabs();
+ operand = memoryRead(operandAddress);
+ NZFlags = operand + 0xff;
+ memoryWrite(operandAddress, NZFlags);
+ clock += 6;
+ break;
+
+ case 0xC6: // DEC zp
+ operandAddress = eazp();
+ operand = zeroPageRead(operandAddress);
+ NZFlags = operand + 0xff;
+ zeroPageWrite(operandAddress, NZFlags);
+ clock += 5;
+ break;
+
+ case 0xD6: // DEC zp,X
+ operandAddress = eazpx();
+ operand = zeroPageRead(operandAddress);
+ NZFlags = operand + 0xff;
+ zeroPageWrite(operandAddress, NZFlags);
+ clock += 6;
+ break;
+
+ case 0xDE: // DEC abs,X
+ operandAddress = eaabsx();
+ operand = memoryRead(operandAddress);
+ NZFlags = operand + 0xff;
+ memoryWrite(operandAddress, NZFlags);
+ clock += 7;
+ break;
+
+ case 0xCA: // DEX
+ NZFlags = X + 0xff;
+ X = NZFlags & 0xff;
+ clock += 2;
+ break;
+
+ case 0x88: // DEY
+ NZFlags = Y + 0xff;
+ Y = NZFlags & 0xff;
+ clock += 2;
+ break;
+
+ case 0x49: // EOR #imm
+ A ^= eaimm();
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0x4D: // EOR abs
+ A ^= memoryRead(eaabs());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x45: // EOR zp
+ A ^= zeroPageRead(eazp());
+ NZFlags = A;
+ clock += 3;
+ break;
+
+ case 0x41: // EOR (zp,X)
+ A ^= memoryRead(eazpxind());
+ NZFlags = A;
+ clock += 6;
+ break;
+
+ case 0x51: // EOR (zp),Y
+ A ^= memoryRead(eazpindy());
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0x55: // EOR zp,X
+ A ^= zeroPageRead(eazpx());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x5D: // EOR abs,X
+ A ^= memoryRead(eaabsx());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x59: // EOR abs,Y
+ A ^= memoryRead(eaabsy());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0xEE: // INC abs
+ operandAddress = eaabs();
+ operand = memoryRead(operandAddress);
+ NZFlags = operand + 1;
+ memoryWrite(operandAddress, NZFlags);
+ clock += 6;
+ break;
+
+ case 0xE6: // INC zp
+ operandAddress = eazp();
+ operand = zeroPageRead(operandAddress);
+ NZFlags = operand + 1;
+ zeroPageWrite(operandAddress, NZFlags);
+ clock += 5;
+ break;
+
+ case 0xF6: // INC zp,X
+ operandAddress = eazpx();
+ operand = zeroPageRead(operandAddress);
+ NZFlags = operand + 1;
+ zeroPageWrite(operandAddress, NZFlags);
+ clock += 6;
+ break;
+
+ case 0xFE: // INC abs,X
+ operandAddress = eaabsxNC();
+ operand = memoryRead(operandAddress);
+ NZFlags = operand + 1;
+ memoryWrite(operandAddress, NZFlags);
+ clock += 7;
+ break;
+
+ case 0xE8: // INX
+ NZFlags = X + 1;
+ X = NZFlags & 0xff;
+ clock += 2;
+ break;
+
+ case 0xC8: // INY
+ NZFlags = Y + 1;
+ Y = NZFlags & 0xff;
+ clock += 2;
+ break;
+
+ case 0x4C: // JMP abs
+ PC = eaabs();
+ clock += 3;
+ break;
+
+ case 0x6C: // JMP (abs)
+ PC = eaabsind();
+ clock += 5;
+ break;
+
+ case 0x20: // JSR abs
+ operandAddress = eaabs();
+ PC--;
+ push(PC >> 8);
+ push(PC);
+ PC = operandAddress;
+ clock += 6;
+ break;
+
+ case 0xA9: // LDA #imm
+ A = eaimm();
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0xAD: // LDA abs
+ A = memoryRead(eaabs());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0xA5: // LDA zp
+ A = zeroPageRead(eazp());
+ NZFlags = A;
+ clock += 3;
+ break;
+
+ case 0xA1: // LDA (zp,X)
+ A = memoryRead(eazpxind());
+ NZFlags = A;
+ clock += 6;
+ break;
+
+ case 0xB1: // LDA (zp),Y
+ A = memoryRead(eazpindy());
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0xB5: // LDA zp,X
+ A = zeroPageRead(eazpx());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0xBD: // LDA abs,X
+ A = memoryRead(eaabsx());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0xB9: // LDA abs,Y
+ A = memoryRead(eaabsy());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0xA2: // LDX #imm
+ X = eaimm();
+ NZFlags = X;
+ clock += 2;
+ break;
+
+ case 0xAE: // LDX abs
+ X = memoryRead(eaabs());
+ NZFlags = X;
+ clock += 4;
+ break;
+
+ case 0xA6: // LDX zp
+ X = zeroPageRead(eazp());
+ NZFlags = X;
+ clock += 3;
+ break;
+
+ case 0xBE: // LDX abs,Y
+ X = memoryRead(eaabsy());
+ NZFlags = X;
+ clock += 4;
+ break;
+
+ case 0xB6: // LDX zp,Y
+ X = zeroPageRead(eazpy());
+ NZFlags = X;
+ clock += 4;
+ break;
+
+ case 0xA0: // LDY #imm
+ Y = eaimm();
+ NZFlags = Y;
+ clock += 2;
+ break;
+
+ case 0xAC: // LDY abs
+ Y = memoryRead(eaabs());
+ NZFlags = Y;
+ clock += 4;
+ break;
+
+ case 0xA4: // LDY zp
+ Y = zeroPageRead(eazp());
+ NZFlags = Y;
+ clock += 3;
+ break;
+
+ case 0xB4: // LDY zp,X
+ Y = zeroPageRead(eazpx());
+ NZFlags = Y;
+ clock += 4;
+ break;
+
+ case 0xBC: // LDY abs,X
+ Y = memoryRead(eaabsx());
+ NZFlags = Y;
+ clock += 4;
+ break;
+
+ case 0x4E: // LSR abs
+ operandAddress = eaabs();
+ operand = memoryRead(operandAddress);
+ result = (operand & 0x01) << 8; // just get the C bit
+ NZFlags = operand >> 1; // result in NZFlags
+ memoryWrite(operandAddress, NZFlags);
+ clock += 6;
+ break;
+
+ case 0x46: // LSR zp
+ operandAddress = eazp();
+ operand = zeroPageRead(operandAddress);
+ result = (operand & 0x01) << 8; // just get the C bit
+ NZFlags = operand >> 1; // result in NZFlags
+ zeroPageWrite(operandAddress, NZFlags);
+ clock += 5;
+ break;
+
+ case 0x4A: // LSR acc
+ result = (A & 0x01) << 8; // just get the C bit
+ A >>= 1;
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0x56: // LSR zp,X
+ operandAddress = eazpx();
+ operand = zeroPageRead(operandAddress);
+ result = (operand & 0x01) << 8; // just get the C bit
+ NZFlags = operand >> 1; // result in NZFlags
+ zeroPageWrite(operandAddress, NZFlags);
+ clock += 6;
+ break;
+
+ case 0x5E: // LSR abs,X
+ operandAddress = eaabsx();
+ operand = memoryRead(operandAddress);
+ result = (operand & 0x01) << 8; // just get the C bit
+ NZFlags = operand >> 1; // result in NZFlags
+ memoryWrite(operandAddress, NZFlags);
+ clock += 7;
+ break;
+
+ case 0xEA: // NOP
+ clock += 2;
+ break;
+
+ case 0x09: // ORA #imm
+ A |= eaimm();
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0x0D: // ORA abs
+ A |= memoryRead(eaabs());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x05: // ORA zp
+ A |= zeroPageRead(eazp());
+ NZFlags = A;
+ clock += 3;
+ break;
+
+ case 0x01: // ORA (zp,X)
+ A |= memoryRead(eazpxind());
+ NZFlags = A;
+ clock += 6;
+ break;
+
+ case 0x11: // ORA (zp),Y
+ A |= memoryRead(eazpindy());
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0x15: // ORA zp,X
+ A |= zeroPageRead(eazpx());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x1D: // ORA abs,X
+ A |= memoryRead(eaabsx());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x19: // ORA abs,Y
+ A |= memoryRead(eaabsy());
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x48: // PHA
+ push(A);
+ clock += 3;
+ break;
+
+ case 0x08: // PHP
+ setN(getFN());
+ setZ(getFZ());
+ setC(getFC());
+ push(P);
+ clock += 3;
+ break;
+
+ case 0x68: // PLA
+ A = pop();
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x28: // PLP
+ P = pop() | 0x20; // fix bug in bit5 of P
+ setFC(getC());
+ setFNZ(getN(), getZ());
+ clock += 4;
+ if ((pendingIRQ > 0) && !getI()) {
+ pendingIRQ--;
+ assertIRQ();
+ }
+ break;
+
+ case 0x2E: // ROL abs
+ operandAddress = eaabs();
+ operand = memoryRead(operandAddress);
+ result = (operand << 1) | getFC_();
+ NZFlags = result;
+ memoryWrite(operandAddress, result);
+ clock += 6;
+ break;
+
+ case 0x26: // ROL zp
+ operandAddress = eazp();
+ operand = zeroPageRead(operandAddress);
+ result = (operand << 1) | getFC_();
+ NZFlags = result;
+ zeroPageWrite(operandAddress, result);
+ clock += 5;
+ break;
+
+ case 0x2A: // ROL acc
+ result = (A << 1) | getFC_();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0x36: // ROL zp,X
+ operandAddress = eazpx();
+ operand = zeroPageRead(operandAddress);
+ result = (operand << 1) | getFC_();
+ NZFlags = result;
+ zeroPageWrite(operandAddress, result);
+ clock += 6;
+ break;
+
+ case 0x3E: // ROL abs,X
+ operandAddress = eaabsx();
+ operand = memoryRead(operandAddress);
+ result = (operand << 1) | getFC_();
+ NZFlags = result;
+ memoryWrite(operandAddress, result);
+ clock += 7;
+ break;
+
+ case 0x6E: // ROR abs
+ operandAddress = eaabs();
+ operand = memoryRead(operandAddress);
+ result = ((operand & 0x01) << 8) | (getFC_() << 7) |
+ (operand >> 1);
+ NZFlags = result;
+ memoryWrite(operandAddress, result);
+ clock += 6;
+ break;
+
+ case 0x66: // ROR zp
+ operandAddress = eazp();
+ operand = zeroPageRead(operandAddress);
+ result = ((operand & 0x01) << 8) | (getFC_() << 7) |
+ (operand >> 1);
+ NZFlags = result;
+ zeroPageWrite(operandAddress, result);
+ clock += 5;
+ break;
+
+ case 0x6A: // ROR acc
+ result = ((A & 0x01) << 8) | (getFC_() << 7) | (A >> 1);
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0x76: // ROR zp,X
+ operandAddress = eazpx();
+ operand = zeroPageRead(operandAddress);
+ result = ((operand & 0x01) << 8) | (getFC_() << 7) |
+ (operand >> 1);
+ NZFlags = result;
+ zeroPageWrite(operandAddress, result);
+ clock += 6;
+ break;
+
+ case 0x7E: // ROR abs,X
+ operandAddress = eaabsx();
+ operand = memoryRead(operandAddress);
+ result = ((operand & 0x01) << 8) | (getFC_() << 7) |
+ (operand >> 1);
+ NZFlags = result;
+ memoryWrite(operandAddress, result);
+ clock += 7;
+ break;
+
+ case 0x40: // RTI
+ P = pop() | 0x20; // bit 5 bug of 6502
+ setFC(getC());
+ setFNZ(getN(), getZ());
+ PC = pop(); // splitting is necessary
+ PC += pop() << 8; // because of nested macros
+ clock += 6;
+ break;
+
+ case 0x60: // RTS
+ PC = pop(); // splitting is necessary
+ PC += pop() << 8; // because of nested macros
+ PC++;
+ clock += 6;
+ break;
+
+ case 0xE9: // SBC #imm
+ operand = 255 - eaimm();
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ sbcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0xED: // SBC abs
+ operand = 255 - memoryRead(eaabs());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ sbcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0xE5: // SBC zp
+ operand = 255 - zeroPageRead(eazp());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ sbcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 3;
+ break;
+
+ case 0xE1: // SBC (zp,X)
+ operand = 255 - memoryRead(eazpxind());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ sbcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 6;
+ break;
+
+ case 0xF1: // SBC (zp),Y
+ operand = 255 - memoryRead(eazpindy());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ sbcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0xF5: // SBC zp,X
+ operand = 255 - zeroPageRead(eazpx());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ sbcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0xFD: // SBC abs,X
+ operand = 255 - memoryRead(eaabsx());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ sbcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0xF9: // SBC abs,Y
+ operand = 255 - memoryRead(eaabsy());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ sbcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 4;
+ break;
+
+ case 0x38: // SEC
+ setFC(true);
+ clock += 2;
+ break;
+
+ case 0xF8: // SED
+ setD(true);
+ clock += 2;
+ break;
+
+ case 0x78: // SEI
+ setI(true);
+ clock += 2;
+ break;
+
+ case 0x8D: // STA abs
+ memoryWrite(eaabs(), A);
+ clock += 4;
+ break;
+
+ case 0x85: // STA zp
+ zeroPageWrite(eazp(), A);
+ clock += 3;
+ break;
+
+ case 0x81: // STA (zp,X)
+ memoryWrite(eazpxind(), A);
+ clock += 6;
+ break;
+
+ case 0x91: // STA (zp),Y
+ memoryWrite(eazpindy(), A);
+ clock += 6;
+ break;
+
+ case 0x95: // STA zp,X
+ zeroPageWrite(eazpx(), A);
+ clock += 4;
+ break;
+
+ case 0x9D: // STA abs,X
+ memoryWrite(eaabsx(), A);
+ clock += 5;
+ break;
+
+ case 0x99: // STA abs,Y
+ memoryWrite(eaabsy(), A);
+ clock += 5;
+ break;
+
+ case 0x8E: // STX abs
+ memoryWrite(eaabs(), X);
+ clock += 4;
+ break;
+
+ case 0x86: // STX zp
+ zeroPageWrite(eazp(), X);
+ clock += 3;
+ break;
+
+ case 0x96: // STX zp,Y
+ zeroPageWrite(eazpy(), X);
+ clock += 4;
+ break;
+
+ case 0x8C: // STY abs
+ memoryWrite(eaabs(), Y);
+ clock += 4;
+ break;
+
+ case 0x84: // STY zp
+ zeroPageWrite(eazp(), Y);
+ clock += 3;
+ break;
+
+ case 0x94: // STY zp,X
+ zeroPageWrite(eazpx(), Y);
+ clock += 4;
+ break;
+
+ case 0xAA: // TAX
+ X = A;
+ NZFlags = X;
+ clock += 2;
+ break;
+
+ case 0xA8: // TAY
+ Y = A;
+ NZFlags = Y;
+ clock += 2;
+ break;
+
+ case 0xBA: // TSX
+ X = S;
+ NZFlags = X;
+ clock += 2;
+ break;
+
+ case 0x8A: // TXA
+ A = X;
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ case 0x9A: // TXS
+ S = X;
+ clock += 2;
+ break;
+
+ case 0x98: // TYA
+ A = Y;
+ NZFlags = A;
+ clock += 2;
+ break;
+
+ /*
+ * 65C02 instructions
+ * note: timing is not correct
+ */
+
+ case 0x72: // ADC (zp)
+ operand = memoryRead(eazpind());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ adcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0x32: // AND (zp)
+ A &= memoryRead(eazpind());
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0x34: // BIT zp,X
+ operand = zeroPageRead(eazpx());
+ setV((operand & 0x40) != 0);
+ NZFlags = ((operand & 0x80) << 2) | (A & operand);
+ clock += 3;
+ break;
+
+ case 0x89: // BIT #imm
+ operand = eaimm();
+ setV((operand & 0x40) != 0);
+ NZFlags = ((operand & 0x80) << 2) | (A & operand);
+ clock += 2;
+ break;
+
+ case 0x3C: // BIT abs,X
+ operand = eaabsx();
+ setV((operand & 0x40) != 0);
+ NZFlags = ((operand & 0x80) << 2) | (A & operand);
+ clock += 4;
+ break;
+
+ case 0x80: // BRA rr
+ operand = earel();
+ clock += 2;
+ branch(operand);
+ break;
+
+ case 0xD2: // CMP (zp)
+ result = 0x100 + A - memoryRead(eazpind());
+ NZFlags = result;
+ clock += 5;
+ break;
+
+ case 0x3A: // DEA acc
+ NZFlags = A + 0xff;
+ A = NZFlags & 0xff;
+ clock += 2;
+ break;
+
+ case 0x52: // EOR (zp)
+ A ^= memoryRead(eazpind());
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0x1A: // INA acc
+ NZFlags = A + 1;
+ A = NZFlags & 0xff;
+ clock += 2;
+ break;
+
+ case 0x7C: // JMP (abs,X)
+ PC = eaabsxind();
+ clock += 6;
+ break;
+
+ case 0xB2: // LDA (zp)
+ A = memoryRead(eazpind());
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0x12: // ORA (zp)
+ A |= memoryRead(eazpind());
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0xDA: // PHX
+ push(X);
+ clock += 3;
+ break;
+
+ case 0xFA: // PLX
+ X = pop();
+ NZFlags = X;
+ clock += 4;
+ break;
+
+ case 0x5A: // PHY
+ push(Y);
+ clock += 3;
+ break;
+
+ case 0x7A: // PLY
+ Y = pop();
+ NZFlags = Y;
+ clock += 4;
+ break;
+
+ case 0xF2: // SBC (zp)
+ operand = 255 - memoryRead(eazpind());
+ result = operand + A + getFC_();
+ setV(!(((operand ^ A) & 0x80) != 0) && (((A ^ result) & 0x80) != 0));
+ sbcBCDAdjust();
+ A = result & 0xff;
+ NZFlags = A;
+ clock += 5;
+ break;
+
+ case 0x92: // STA (zp)
+ memoryWrite(eazpind(), A);
+ clock += 6;
+ break;
+
+ case 0x9C: // STZ abs
+ memoryWrite(eaabs(), 0);
+ clock += 4;
+ break;
+
+ case 0x64: // STZ zp
+ zeroPageWrite(eazp(), 0);
+ clock += 3;
+ break;
+
+ case 0x74: // STZ zp,X
+ zeroPageWrite(eazpx(), 0);
+ clock += 3;
+ break;
+
+ case 0x9E: // STZ abs,X
+ memoryWrite(eaabsx(), 0);
+ clock += 4;
+ break;
+
+ case 0x1C: // TRB abs
+ operandAddress = eaabs();
+ operand = memoryRead(operandAddress);
+ setV((operand & 0x40) != 0);
+ NZFlags = ((operand & 0x80) << 2) | (A & operand);
+ memoryWrite(operandAddress, (operand & ~A) & 0xff);
+ clock += 5;
+ break;
+
+ case 0x14: // TRB zp
+ operandAddress = eazp();
+ operand = zeroPageRead(operandAddress);
+ setV((operand & 0x40) != 0);
+ NZFlags = ((operand & 0x80) << 2) | (A & operand);
+ zeroPageWrite(operandAddress, (operand & ~A) & 0xff);
+ clock += 5;
+ break;
+
+ case 0x0C: // TSB abs
+ operandAddress = eaabs();
+ operand = memoryRead(operandAddress);
+ setV((operand & 0x40) != 0);
+ NZFlags = ((operand & 0x80) << 2) | (A & operand);
+ memoryWrite(operandAddress, operand | A);
+ clock += 5;
+ break;
+
+ case 0x04: // TSB zp
+ operandAddress = eazp();
+ operand = zeroPageRead(operandAddress);
+ setV((operand & 0x40) != 0);
+ NZFlags = ((operand & 0x80) << 2) | (A & operand);
+ zeroPageWrite(operandAddress, operand | A);
+ clock += 5;
+ break;
+
+ default: // unknown instructions
+ clock += 2;
+ }
+ }
+
+ public final int executeInstructions(int num) {
+ // Initialize
+ int clockStart = clock;
+
+ for (; num >= 16; num -= 16) {
+ PC &= 0xffff; // Keep PC "sort of" bounded
+ executeInstruction(); executeInstruction();
+ executeInstruction(); executeInstruction();
+ executeInstruction(); executeInstruction();
+ executeInstruction(); executeInstruction();
+ executeInstruction(); executeInstruction();
+ executeInstruction(); executeInstruction();
+ executeInstruction(); executeInstruction();
+ }
+ PC &= 0xffff;
+ for (; num > 0; num--)
+ executeInstruction();
+
+ return (clock - clockStart) & 0x7fffffff;
+ }
+
+ public final void checkInterrupts() {
+ // Reset
+ if ((exceptionRegister & SIG_6502_RESET) != 0) {
+ onReset();
+
+ A = X = Y = 0;
+ P = 0x20;
+ setFC(getC());
+ setFNZ(getN(), getZ());
+ S = 0xff;
+ PC = memoryRead(0xfffc);
+ PC |= (memoryRead(0xfffd) << 8);
+ exceptionRegister &= ~SIG_6502_RESET;
+ }
+
+ // No NMI nor IRQ...
+ if ((exceptionRegister & SIG_6502_NMI) != 0) {
+ onNMI();
+
+ push(PC >> 8);
+ push(PC);
+ setN(getFN());
+ setZ(getFZ());
+ setC(getFC());
+ push(P);
+ PC = memoryRead(0xfffa);
+ PC |= memoryRead(0xfffb) << 8;
+ clock += 7;
+ exceptionRegister ^= SIG_6502_NMI;
+ }
+
+ if ((exceptionRegister & SIG_6502_IRQ) != 0) {
+ onIRQ();
+
+ if (getI())
+ pendingIRQ++;
+ else {
+ push(PC >> 8);
+ push(PC);
+ setN(getFN());
+ setZ(getFZ());
+ setC(getFC());
+ setB(false);
+ push(P);
+ setI(true);
+ PC = memoryRead(0xfffe);
+ PC |= memoryRead(0xffff) << 8;
+ clock += 7;
+ }
+ exceptionRegister ^= SIG_6502_IRQ;
+ }
+ }
+}
diff --git a/Source/EmAppleII.java b/Source/EmAppleII.java
new file mode 100644
index 0000000..1d82c33
--- /dev/null
+++ b/Source/EmAppleII.java
@@ -0,0 +1,1186 @@
+
+/**
+ * AppleIIGo
+ * Apple II Emulator for J2ME
+ * (C) 2006 by Marc S. Ressl(ressl@lonetree.com)
+ * Released under the GPL
+ */
+
+import java.io.*;
+
+public class EmAppleII extends Em6502 implements Runnable {
+ /*
+ * Apple II memory map
+ */
+ public static final int MEM_PHYS_ZP = 0x00000;
+ public static final int MEM_PHYS_STACK = 0x00100;
+ public static final int MEM_PHYS_RAM1 = 0x00200;
+ public static final int MEM_PHYS_TEXT = 0x00400;
+ public static final int MEM_PHYS_RAM2 = 0x00800;
+ public static final int MEM_PHYS_HIRES = 0x02000;
+ public static final int MEM_PHYS_RAM3 = 0x04000;
+ public static final int MEM_PHYS_IO = 0x0c000;
+ public static final int MEM_PHYS_ROM_LOW = 0x0d000;
+ public static final int MEM_PHYS_ROM_HIGH = 0x0e000;
+
+ public static final int MEM_MAIN_RAM1 = 0x00200;
+ public static final int MEM_MAIN_TEXT = 0x00400;
+ public static final int MEM_MAIN_RAM2 = 0x00800;
+ public static final int MEM_MAIN_HIRES = 0x02000;
+ public static final int MEM_MAIN_RAM3 = 0x04000;
+ public static final int MEM_MAIN_LC1 = 0x0c000;
+ public static final int MEM_MAIN_LC2 = 0x0d000;
+ public static final int MEM_MAIN_LC_HIGH = 0x0e000;
+
+ public static final int MEM_AUX_ZP = 0x10000;
+ public static final int MEM_AUX_STACK = 0x10100;
+ public static final int MEM_AUX_RAM1 = 0x10200;
+ public static final int MEM_AUX_TEXT = 0x10400;
+ public static final int MEM_AUX_RAM2 = 0x10800;
+ public static final int MEM_AUX_HIRES = 0x12000;
+ public static final int MEM_AUX_RAM3 = 0x14000;
+ public static final int MEM_AUX_LC1 = 0x1c000;
+ public static final int MEM_AUX_LC2 = 0x1d000;
+ public static final int MEM_AUX_LC_HIGH = 0x1e000;
+
+ public static final int MEM_ROM_MAIN_LOW = 0x20000;
+ public static final int MEM_ROM_MAIN_HIGH = 0x21000;
+ public static final int MEM_ROM_INTERNAL = 0x23000;
+ public static final int MEM_ROM_EXTERNAL = 0x24000;
+
+ public static final int MEM_MAIN_ZP = 0x25000;
+ public static final int MEM_MAIN_STACK = 0x25100;
+
+ public static final int MEM_WASTE = 0x25200;
+
+ public static final int MEM_END = 0x28000;
+
+ // Peripherals
+ public Paddle paddle;
+ public Peripheral[] slots;
+
+ // Graphics (dirty buffer every 0x80 bytes)
+ public int graphicsMode;
+ public boolean[] graphicsDirty = new boolean[0x10000 >> 7];
+
+ public static final int GR_TEXT = (1 << 0);
+ public static final int GR_MIXMODE = (1 << 1);
+ public static final int GR_PAGE2 = (1 << 2);
+ public static final int GR_HIRES = (1 << 3);
+ public static final int GR_80STORE = (1 << 4);
+ public static final int GR_80CHAR = (1 << 5);
+ public static final int GR_ALTCHAR = (1 << 6);
+ public static final int GR_DHIRES = (1 << 7);
+
+ // Sound
+ public static final int SPEAKER_FLIPS_SIZE = 4096;
+ public static final int SPEAKER_FLIPS_MASK = 4095;
+
+ public int speakerFlips[] = new int[SPEAKER_FLIPS_SIZE];
+ public int speakerFlipsPointer = 0;
+
+ // Default ROM
+ private static final int[] defaultRom = {
+ // Reset routine
+ 0xad,0x51,0xc0,
+ 0xa9,0xa0,
+
+ 0xa2,0xff,0x9d,0xff,0x03,0xca,0xd0,0xfa,
+ 0xa2,0xff,0x9d,0xff,0x04,0xca,0xd0,0xfa,
+ 0xa2,0xff,0x9d,0xff,0x05,0xca,0xd0,0xfa,
+ 0xa2,0xff,0x9d,0xff,0x06,0xca,0xd0,0xfa,
+
+ 0xa2,0x27,0xbd,0xb8,0xfe,0x9d,0x80,0x04,0xca,0x10,0xf7,
+ 0xa2,0x27,0xbd,0xe0,0xfe,0x9d,0x00,0x05,0xca,0x10,0xf7,
+ 0xa2,0x27,0xbd,0x08,0xff,0x9d,0x80,0x05,0xca,0x10,0xf7,
+ 0xa2,0x27,0xbd,0x30,0xff,0x9d,0x00,0x06,0xca,0x10,0xf7,
+ 0xa2,0x27,0xbd,0x58,0xff,0x9d,0x80,0x07,0xca,0x10,0xf7,
+ 0xa2,0x27,0xbd,0x80,0xff,0x9d,0x28,0x04,0xca,0x10,0xf7,
+ 0xa2,0x27,0xbd,0xa8,0xff,0x9d,0x00,0x07,0xca,0x10,0xf7,
+ 0xa2,0x27,0xbd,0xd0,0xff,0x9d,0x00,0x07,0xca,0x10,0xf7,
+
+ 0x4c,0xad,0xfe,
+ 0xea,0xea,0xea,0xea,0xea,0xea,0xea,0xea,
+
+ // Text message
+ 0xa0,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xa0,
+
+ 0xa0,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x01,0x10,0x10,0x0c,0x05,0x09,0x09,
+ 0x07,0x0f,0x20,0x12,0x05,0x11,0x15,0x09,0x12,0x05,0x13,0x20,0x01,0x0e,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xa0, // APPLEIIGO REQUIRES AN
+
+ 0xa0,0x20,0x20,0x20,0x20,0x20,0x20,0x01,0x10,0x10,0x0c,0x05,0x20,0x09,0x09,0x20,
+ 0x12,0x0f,0x0d,0x20,0x09,0x0d,0x01,0x07,0x05,0x20,0x14,0x0f,0x20,0x12,0x15,0x0e,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xa0, // APPLE II ROM IMAGE TO RUN
+
+ 0xa0,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xa0,
+
+ 0xa0,0xc6,0xcf,0xd2,0xa0,0xcd,0xcf,0xd2,0xc5,0xa0,0xc9,0xce,0xc6,0xcf,0xd2,0xcd,
+ 0xc1,0xd4,0xc9,0xcf,0xce,0xa0,0xd0,0xcc,0xc5,0xc1,0xd3,0xc5,0xa0,0xc3,0xcc,0xc9,
+ 0xc3,0xcb,0xa0,0xcf,0xce,0xa0,0xa0,0xa0, // FOR MORE INFORMATION PLEASE CLICK ON
+
+ 0xa0,0xd4,0xc8,0xc5,0xa0,0xc1,0xd0,0xd0,0xcc,0xc5,0xc9,0xc9,0xc7,0xcf,0xa0,0xcc,
+ 0xcf,0xc7,0xcf,0xa0,0xc2,0xc5,0xcc,0xcf,0xd7,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0, // THE APPLEIIGO LOGO BELOW
+
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+ 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,
+
+ // Interrupt vectors
+ 0x00,0x00,0x30,0xfe,0x30,0xfe,0x30,0xfe
+ };
+
+ // Emulator
+ private boolean isRestart;
+
+ private int cpuSpeed;
+ private int clocksPerInterval;
+
+ private int refreshRate;
+ private long refreshInterval;
+ private long refreshDelayCumulative;
+ private long refreshDelayPerSecond;
+ private long refreshCycle;
+
+ // Keyboard
+ private int keyboardLatch;
+
+ // Memory offsets
+ private int[] memoryReadOffset = new int[0x101];
+ private int[] memoryWriteOffset = new int[0x101];
+
+ // Language card state
+ private boolean isLcReadEnable;
+ private boolean isLcWriteEnable;
+ private boolean isLcBank2;
+
+ // Apple IIe state
+ private boolean isRomInternal;
+ private boolean isRomC3External;
+ private boolean isAuxRead;
+ private boolean isAuxWrite;
+ private boolean isAuxZeroPage;
+ private boolean isVideoVBL;
+
+ // Thread stuff
+ private boolean isPaused = true;
+ private Thread thread;
+ private String threadError = null;
+
+ // Step mode
+ private boolean isStepMode = false;
+ private boolean isNextStep = false;
+ private int stepCount;
+
+
+
+ /**
+ * Apple II class constructor
+ */
+ public EmAppleII() {
+ Em6502();
+
+ // Allocate compute memory
+ mem = new byte[MEM_END];
+
+ // Initialize CPU
+ initMemoryMap();
+ setRandomSeed();
+ setCpuSpeed(1000);
+ reset();
+
+ // Setup default ROM
+ loadDefaultRom();
+
+ // Setup paddles
+ paddle = new Paddle(this);
+
+ // Setup expansion slots
+ slots = new Peripheral[8];
+ for (int slot = 1; slot < 8; slot++)
+ setPeripheral(new Peripheral(), slot);
+ }
+
+ /**
+ * Set random seed (so programs start randomly)
+ */
+ public void setRandomSeed() {
+ mem[0xcd] = (byte) System.currentTimeMillis();
+ }
+
+ /**
+ * Load default ROM
+ */
+ public void loadDefaultRom() {
+ for(int offset = 0; offset < 0x1d0; offset++)
+ mem[(MEM_ROM_MAIN_LOW + 0x3000 - 0x1d0) + offset] = (byte) defaultRom[offset];
+ }
+
+ /**
+ * Loads ROM
+ */
+ private boolean isValidRom(byte[] rom, int offset) {
+ // Integer BASIC?
+ if ((rom[offset + 0x1000] & 0xff) == 0x20)
+ return true;
+
+ // Applesoft BASIC?
+ if ((rom[offset + 0x1000] & 0xff) == 0x4c)
+ return true;
+
+ return false;
+ }
+
+ public boolean loadRom(InputStream is) {
+ byte[] rom = new byte[0x8000];
+ int offset = 0;
+
+ try {
+ is.read(rom, 0, 0x08000);
+ } catch (IOException e) {
+ return false;
+ }
+
+ if (isValidRom(rom, 0x0))
+ offset = 0x0;
+ else if (isValidRom(rom, 0x1000))
+ offset = 0x1000;
+ else if (isValidRom(rom, 0x2000))
+ offset = 0x2000;
+ else
+ return false;
+
+ // Copy main ROM
+ System.arraycopy(rom, offset, mem, MEM_ROM_MAIN_LOW, 0x03000);
+
+ // Copy internal ROM
+ System.arraycopy(rom, offset + 0x3000, mem, MEM_ROM_INTERNAL + 0x00000, 0x01000);
+ System.arraycopy(rom, offset + 0x3800, mem, MEM_ROM_EXTERNAL + 0x00800, 0x00800);
+
+ return true;
+ }
+
+ /**
+ * Set peripheral
+ */
+ public void setPeripheral(Peripheral peripheral, int slot) {
+ slots[slot] = peripheral;
+
+ int offset = MEM_ROM_EXTERNAL + (slot << 8);
+ for(int i = 0; i < 0x100; i++)
+ mem[offset + i] = (byte) peripheral.memoryRead(i);
+ }
+
+ /**
+ * Set pause state
+ */
+ public void setPaused(boolean value) {
+ if (isPaused == value)
+ return;
+
+ isPaused = value;
+ if (isPaused) {
+ try {
+ thread.join(1000);
+ } catch (InterruptedException e) {
+ }
+ } else {
+ thread = new Thread(this);
+ thread.start();
+ }
+ }
+
+ /**
+ * Get pause state
+ */
+ public boolean getPaused() {
+ return isPaused;
+ }
+
+ /**
+ * Keyboard push key function
+ */
+ public void setKeyLatch(int key) {
+ key &= 0x7f;
+ keyboardLatch = (key | 0x80);
+ }
+
+ /**
+ * Restart
+ */
+ public void restart() {
+ isRestart = true;
+ assertReset();
+ }
+
+ /**
+ * Reset
+ */
+ public void reset() {
+ isRestart = false;
+ assertReset();
+ }
+
+ /**
+ * Set CPU speed
+ */
+ public void setCpuSpeed(int value) {
+ if (value < 0)
+ return;
+
+ cpuSpeed = value;
+
+ refreshRate = 20;
+ refreshInterval = (int) (1000.0 / refreshRate);
+ clocksPerInterval = (int) (cpuSpeed * refreshInterval);
+ }
+
+ /**
+ * Get debug mode
+ */
+ public int getCpuSpeed() {
+ return cpuSpeed;
+ }
+
+ /**
+ * Get refresh rate
+ */
+ public int getRefreshRate() {
+ return refreshRate;
+ }
+
+ /**
+ * Set debug mode
+ */
+ public void setStepMode(boolean value) {
+ isNextStep = false;
+ isStepMode = value;
+ }
+
+ /**
+ * Get debug mode
+ */
+ public boolean getStepMode() {
+ return isStepMode;
+ }
+
+ /**
+ * Step instruction in debug mode
+ */
+ public void stepInstructions(int value) {
+ if (value <= 0)
+ return;
+
+ stepCount = value;
+ isNextStep = true;
+ }
+
+ /**
+ * Zero pad
+ */
+ private String zeroPad(String value, int length) {
+ length -= value.length();
+
+ while (length > 0) {
+ value = "0" + value;
+ length--;
+ }
+
+ return value;
+ }
+
+ /**
+ * Print a hex value
+ */
+ private String formatHex(int value, int length) {
+ return zeroPad(Integer.toString(value, 16), length);
+ }
+
+ /**
+ * Print a decimal value
+ */
+ private String formatDec(int value, int commaPos) {
+ String valueString = zeroPad(Integer.toString(value), commaPos + 1);
+ int length = valueString.length();
+
+ return valueString.substring(0, length - commaPos) +
+ "." + valueString.substring(length - commaPos, length);
+ }
+
+ /**
+ * Step instruction in debug mode
+ */
+ public String getStatInfo() {
+ String statInfo = "";
+ long cpuSpeedCurrent;
+
+ // Calculate effective CPU speed
+ if (isPaused || isStepMode)
+ cpuSpeedCurrent = 0;
+ else if (refreshDelayPerSecond > 1000)
+ cpuSpeedCurrent = cpuSpeed * 1000 / refreshDelayPerSecond;
+ else
+ cpuSpeedCurrent = cpuSpeed;
+
+ // Return A, X, Y, S, P, PC
+ statInfo += " A=" + formatHex(A, 2);
+ statInfo += " X=" + formatHex(X, 2);
+ statInfo += " Y=" + formatHex(Y, 2);
+ statInfo += " P=" + formatHex(P, 2);
+ statInfo += " S=" + formatHex(S, 2);
+ statInfo += "\n";
+ statInfo += " PC=" + formatHex(PC, 4) + "\n";
+ statInfo += " [PC]=";
+ statInfo += " " + formatHex(memoryRead(PC + 0), 2);
+ statInfo += " " + formatHex(memoryRead(PC + 1), 2);
+ statInfo += " " + formatHex(memoryRead(PC + 2), 2);
+ statInfo += " " + formatHex(memoryRead(PC + 3), 2);
+ statInfo += "\n";
+ statInfo += " MHZ=" + formatDec((int) cpuSpeedCurrent, 3) + " [" + refreshDelayPerSecond + " ms/s]\n";
+ if (threadError != null)
+ statInfo += threadError + "\n";
+
+ return statInfo;
+ }
+
+ /**
+ * Noise function
+ *
+ * We assume a "sort-of" floating bus
+ * (spanning memory locations 0000-3FFF)
+ *
+ * Correct way: We should look up the current video mode,
+ * and sample according to what is being shown.
+ */
+ public int noise() {
+ return mem[clock & 0x3fff];
+ }
+
+
+
+ /**
+ * Read memory function
+ *
+ * @param address Address
+ */
+ protected int memoryRead(int address) {
+ if ((address & 0xff00) == 0xc000)
+ return ioRead(address);
+
+ return (mem[address + memoryReadOffset[address >> 8]] & 0xff);
+ }
+
+ /**
+ * Write memory function
+ *
+ * @param adderss Address
+ * @param value value
+ */
+ protected void memoryWrite(int address, int value) {
+ if ((address & 0xff00) == 0xc000)
+ ioWrite(address, value);
+ else {
+ mem[address + memoryWriteOffset[address >> 8]] = (byte) value;
+ graphicsDirty[address >> 7] = true;
+ }
+ }
+
+ /**
+ * Update memory maps
+ */
+ private void updateMainMemoryMap() {
+ int ramReadOffset, textReadOffset, hiresReadOffset;
+ int ramWriteOffset, textWriteOffset, hiresWriteOffset;
+ boolean isPage2 = ((graphicsMode & GR_PAGE2) != 0);
+ boolean is80STORE = ((graphicsMode & GR_80STORE) != 0);
+ boolean isHires = ((graphicsMode & GR_HIRES) != 0);
+
+ textReadOffset = hiresReadOffset = ramReadOffset = isAuxRead ?
+ (MEM_AUX_RAM1 - MEM_PHYS_RAM1) : (MEM_MAIN_RAM1 - MEM_PHYS_RAM1);
+ textWriteOffset = hiresWriteOffset = ramWriteOffset = isAuxWrite ?
+ (MEM_AUX_RAM1 - MEM_PHYS_RAM1) : (MEM_MAIN_RAM1 - MEM_PHYS_RAM1);
+
+ if (is80STORE) {
+ textWriteOffset = textReadOffset = isPage2 ? (MEM_AUX_TEXT - MEM_PHYS_TEXT) :
+ (MEM_MAIN_TEXT - MEM_PHYS_TEXT);
+
+ if (isHires)
+ hiresWriteOffset = hiresReadOffset = textReadOffset;
+ }
+
+ memoryReadOffset[0x02] = memoryReadOffset[0x03] = ramReadOffset;
+ memoryWriteOffset[0x02] = memoryWriteOffset[0x03] = ramWriteOffset;
+ memoryReadOffset[0x04] = memoryReadOffset[0x05] = memoryReadOffset[0x06] = memoryReadOffset[0x07] = textReadOffset;
+ memoryWriteOffset[0x04] = memoryWriteOffset[0x05] = memoryWriteOffset[0x06] = memoryWriteOffset[0x07] = textWriteOffset;
+ for (int offset = 0x08; offset < 0x20; offset++) {
+ memoryReadOffset[offset] = ramReadOffset;
+ memoryWriteOffset[offset] = ramWriteOffset;
+ }
+ for (int offset = 0x20; offset < 0x40; offset++) {
+ memoryReadOffset[offset] = hiresReadOffset;
+ memoryWriteOffset[offset] = hiresWriteOffset;
+ }
+ for (int offset = 0x40; offset < 0xc0; offset++) {
+ memoryReadOffset[offset] = ramReadOffset;
+ memoryWriteOffset[offset] = ramWriteOffset;
+ }
+ }
+
+ private void updateIOMemoryMap() {
+ int romOffset;
+
+ if (isRomInternal)
+ romOffset = (MEM_ROM_INTERNAL - MEM_PHYS_IO);
+ else
+ romOffset = (MEM_ROM_EXTERNAL - MEM_PHYS_IO);
+
+ for (int offset = 0xc1; offset < 0xd0; offset++)
+ memoryReadOffset[offset] = romOffset;
+
+ if (isRomC3External)
+ memoryReadOffset[0xc3] = (MEM_ROM_EXTERNAL - MEM_PHYS_IO);
+ else
+ memoryReadOffset[0xc3] = (MEM_ROM_INTERNAL - MEM_PHYS_IO);
+ }
+
+ private void initIOMemoryMap() {
+ for (int offset = 0xc1; offset < 0xd0; offset++)
+ memoryWriteOffset[offset] = (MEM_WASTE - MEM_PHYS_IO);
+ }
+
+ private void updateLCMemoryMap() {
+ int lcReadOffset, lcReadOffsetHigh;
+ int lcWriteOffset, lcWriteOffsetHigh;
+
+ if (!isLcReadEnable) {
+ lcReadOffset = (MEM_ROM_MAIN_LOW - MEM_PHYS_ROM_LOW);
+ lcReadOffsetHigh = (MEM_ROM_MAIN_LOW - MEM_PHYS_ROM_LOW);
+ } else if (isAuxZeroPage) {
+ lcReadOffset = isLcBank2 ? (MEM_AUX_LC2 - MEM_PHYS_ROM_LOW) : (MEM_AUX_LC1 - MEM_PHYS_ROM_LOW);
+ lcReadOffsetHigh = (MEM_AUX_LC_HIGH - MEM_PHYS_ROM_HIGH);
+ } else {
+ lcReadOffset = isLcBank2 ? (MEM_MAIN_LC2 - MEM_PHYS_ROM_LOW) : (MEM_MAIN_LC1 - MEM_PHYS_ROM_LOW);
+ lcReadOffsetHigh = (MEM_MAIN_LC_HIGH - MEM_PHYS_ROM_HIGH);
+ }
+
+ if (!isLcWriteEnable) {
+ lcWriteOffset = (MEM_WASTE - MEM_PHYS_ROM_LOW);
+ lcWriteOffsetHigh = (MEM_WASTE - MEM_PHYS_ROM_HIGH);
+ } else if (isAuxZeroPage) {
+ lcWriteOffset = isLcBank2 ? (MEM_AUX_LC2 - MEM_PHYS_ROM_LOW) : (MEM_AUX_LC1 - MEM_PHYS_ROM_LOW);
+ lcWriteOffsetHigh = (MEM_AUX_LC_HIGH - MEM_PHYS_ROM_HIGH);
+ } else {
+ lcWriteOffset = isLcBank2 ? (MEM_MAIN_LC2 - MEM_PHYS_ROM_LOW) : (MEM_MAIN_LC1 - MEM_PHYS_ROM_LOW);
+ lcWriteOffsetHigh = (MEM_MAIN_LC_HIGH - MEM_PHYS_ROM_HIGH);
+ }
+
+ for (int offset = 0xd0; offset < 0xe0; offset++) {
+ memoryReadOffset[offset] = lcReadOffset;
+ memoryWriteOffset[offset] = lcWriteOffset;
+ }
+ for (int offset = 0xe0; offset < 0x100; offset++) {
+ memoryReadOffset[offset] = lcReadOffsetHigh;
+ memoryWriteOffset[offset] = lcWriteOffsetHigh;
+ }
+ }
+
+ void initMemoryMap() {
+ initIOMemoryMap();
+
+ updateMainMemoryMap();
+ updateIOMemoryMap();
+ updateLCMemoryMap();
+ }
+
+ /**
+ * Apple I/O reads
+ *
+ * @param address Address
+ */
+ private int ioRead(int address) {
+ address &= 0xff;
+
+ if (address >= 0x90)
+ return slots[(address & 0x70) >> 4].ioRead(address);
+
+ switch (address) {
+ case 0x00: case 0x01: case 0x02: case 0x03:
+ case 0x04: case 0x05: case 0x06: case 0x07:
+ case 0x08: case 0x09: case 0x0a: case 0x0b:
+ case 0x0c: case 0x0d: case 0x0e: case 0x0f:
+ // Keyboard data
+ return keyboardLatch;
+
+ case 0x10:
+ // Keyboard strobe
+ keyboardLatch &= 0x7f;
+ return keyboardLatch;
+ case 0x11:
+ // Reading from LC Bank 2?
+ return (keyboardLatch & 0x7f) | (isLcBank2 ? 0x80 : 0x00);
+ case 0x12:
+ // Reading from LC?
+ return (keyboardLatch & 0x7f) | (isLcReadEnable ? 0x80 : 0x00);
+ case 0x13:
+ // Reading aux memory?
+ return (keyboardLatch & 0x7f) | (isAuxRead ? 0x80 : 0x00);
+ case 0x14:
+ // Writing aux memory?
+ return (keyboardLatch & 0x7f) | (isAuxWrite ? 0x80 : 0x00);
+ case 0x15:
+ // Using internal slot ROM?
+ return (keyboardLatch & 0x7f) | (isRomInternal ? 0x80 : 0x00);
+ case 0x16:
+ // Using slot zero page+stack+LC?
+ return (keyboardLatch & 0x7f) | (isAuxZeroPage ? 0x80 : 0x00);
+ case 0x17:
+ // Using external slot 3 ROM?
+ return (keyboardLatch & 0x7f) | (isRomC3External ? 0x80 : 0x00);
+ case 0x18:
+ // 80STORE?
+ return (keyboardLatch & 0x7f) | (((graphicsMode & GR_80STORE) != 0) ? 0x80 : 0x00);
+ case 0x19:
+ // VBL Signal low?
+ isVideoVBL = !isVideoVBL;
+ return (keyboardLatch & 0x7f) | (isVideoVBL ? 0x80 : 0x00);
+ case 0x1a:
+ // Using text mode?
+ return (keyboardLatch & 0x7f) | (((graphicsMode & GR_TEXT) != 0) ? 0x80 : 0x00);
+ case 0x1b:
+ // Using mixed mode?
+ return (keyboardLatch & 0x7f) | (((graphicsMode & GR_MIXMODE) != 0) ? 0x80 : 0x00);
+ case 0x1c:
+ // Using page 2?
+ return (keyboardLatch & 0x7f) | (((graphicsMode & GR_PAGE2) != 0) ? 0x80 : 0x00);
+ case 0x1d:
+ // Using hires?
+ return (keyboardLatch & 0x7f) | (((graphicsMode & GR_HIRES) != 0) ? 0x80 : 0x00);
+ case 0x1e:
+ // Using alt charset?
+ return (keyboardLatch & 0x7f) | (((graphicsMode & GR_ALTCHAR) != 0) ? 0x80 : 0x00);
+ case 0x1f:
+ // Using 80-column display mode?
+ return (keyboardLatch & 0x7f) | (((graphicsMode & GR_80CHAR) != 0) ? 0x80 : 0x00);
+
+ case 0x20: case 0x21: case 0x22: case 0x23:
+ case 0x24: case 0x25: case 0x26: case 0x27:
+ case 0x28: case 0x29: case 0x2a: case 0x2b:
+ case 0x2c: case 0x2d: case 0x2e: case 0x2f:
+ // Cassette output
+ break;
+
+ case 0x30: case 0x31: case 0x32: case 0x33:
+ case 0x34: case 0x35: case 0x36: case 0x37:
+ case 0x38: case 0x39: case 0x3a: case 0x3b:
+ case 0x3c: case 0x3d: case 0x3e: case 0x3f:
+ // Speaker
+ speakerFlips[speakerFlipsPointer] = clock;
+ speakerFlipsPointer = (speakerFlipsPointer + 1) & SPEAKER_FLIPS_MASK;
+ break;
+
+ case 0x40: case 0x41: case 0x42: case 0x43:
+ case 0x44: case 0x45: case 0x46: case 0x47:
+ case 0x48: case 0x49: case 0x4a: case 0x4b:
+ case 0x4c: case 0x4d: case 0x4e: case 0x4f:
+ // Game strobe
+ break;
+
+ case 0x50:
+ graphicsMode &= ~GR_TEXT;
+ break;
+ case 0x51:
+ graphicsMode |= GR_TEXT;
+ break;
+ case 0x52:
+ graphicsMode &= ~GR_MIXMODE;
+ break;
+ case 0x53:
+ graphicsMode |= GR_MIXMODE;
+ break;
+ case 0x54:
+ graphicsMode &= ~GR_PAGE2;
+ updateMainMemoryMap();
+ break;
+ case 0x55:
+ graphicsMode |= GR_PAGE2;
+ updateMainMemoryMap();
+ break;
+ case 0x56:
+ graphicsMode &= ~GR_HIRES;
+ updateMainMemoryMap();
+ break;
+ case 0x57:
+ graphicsMode |= GR_HIRES;
+ updateMainMemoryMap();
+ break;
+ case 0x58: case 0x59: case 0x5a: case 0x5b:
+ case 0x5c: case 0x5d:
+ // Annunciators
+ break;
+ case 0x5e:
+ graphicsMode |= GR_DHIRES;
+ break;
+ case 0x5f:
+ graphicsMode &= ~GR_DHIRES;
+ break;
+
+ case 0x60:
+ case 0x68:
+ // (Also cassette input)
+ return paddle.getButtonRegister(3);
+ case 0x61:
+ case 0x69:
+ return paddle.getButtonRegister(0);
+ case 0x62:
+ case 0x6a:
+ return paddle.getButtonRegister(1);
+ case 0x63:
+ case 0x6b:
+ return paddle.getButtonRegister(2);
+ case 0x64:
+ case 0x6c:
+ return paddle.getPaddleRegister(0);
+ case 0x65:
+ case 0x6d:
+ return paddle.getPaddleRegister(1);
+ case 0x66:
+ case 0x6e:
+ return paddle.getPaddleRegister(2);
+ case 0x67:
+ case 0x6f:
+ return paddle.getPaddleRegister(3);
+
+ case 0x70: case 0x71: case 0x72: case 0x73:
+ case 0x74: case 0x75: case 0x76: case 0x77:
+ case 0x78: case 0x79: case 0x7a: case 0x7b:
+ case 0x7c: case 0x7d: case 0x7e: case 0x7f:
+ paddle.triggerRegister();
+ break;
+
+ case 0x80:
+ case 0x84:
+ isLcBank2 = true;
+ isLcReadEnable = true;
+ isLcWriteEnable = false;
+ updateLCMemoryMap();
+ break;
+ case 0x81:
+ case 0x85:
+ isLcBank2 = true;
+ isLcReadEnable = false;
+ isLcWriteEnable = true;
+ updateLCMemoryMap();
+ break;
+ case 0x82:
+ case 0x86:
+ isLcBank2 = true;
+ isLcReadEnable = false;
+ isLcWriteEnable = false;
+ updateLCMemoryMap();
+ break;
+ case 0x83:
+ case 0x87:
+ isLcBank2 = true;
+ isLcReadEnable = true;
+ isLcWriteEnable = true;
+ updateLCMemoryMap();
+ break;
+ case 0x88:
+ case 0x8c:
+ isLcBank2 = false;
+ isLcReadEnable = true;
+ isLcWriteEnable = false;
+ updateLCMemoryMap();
+ break;
+ case 0x89:
+ case 0x8d:
+ isLcBank2 = false;
+ isLcReadEnable = false;
+ isLcWriteEnable = true;
+ updateLCMemoryMap();
+ break;
+ case 0x8a:
+ case 0x8e:
+ isLcBank2 = false;
+ isLcReadEnable = false;
+ isLcWriteEnable = false;
+ updateLCMemoryMap();
+ break;
+ case 0x8b:
+ case 0x8f:
+ isLcBank2 = false;
+ isLcReadEnable = true;
+ isLcWriteEnable = true;
+ updateLCMemoryMap();
+ break;
+ case 0x90: case 0x91: case 0x92: case 0x93:
+ case 0x94: case 0x95: case 0x96: case 0x97:
+ case 0x98: case 0x99: case 0x9a: case 0x9b:
+ case 0x9c: case 0x9d: case 0x9e: case 0x9f:
+ return slots[1].ioRead(address);
+
+ case 0xa0: case 0xa1: case 0xa2: case 0xa3:
+ case 0xa4: case 0xa5: case 0xa6: case 0xa7:
+ case 0xa8: case 0xa9: case 0xaa: case 0xab:
+ case 0xac: case 0xad: case 0xae: case 0xaf:
+ return slots[2].ioRead(address);
+
+ case 0xb0: case 0xb1: case 0xb2: case 0xb3:
+ case 0xb4: case 0xb5: case 0xb6: case 0xb7:
+ case 0xb8: case 0xb9: case 0xba: case 0xbb:
+ case 0xbc: case 0xbd: case 0xbe: case 0xbf:
+ return slots[3].ioRead(address);
+
+ case 0xc0: case 0xc1: case 0xc2: case 0xc3:
+ case 0xc4: case 0xc5: case 0xc6: case 0xc7:
+ case 0xc8: case 0xc9: case 0xca: case 0xcb:
+ case 0xcc: case 0xcd: case 0xce: case 0xcf:
+ return slots[4].ioRead(address);
+
+ case 0xd0: case 0xd1: case 0xd2: case 0xd3:
+ case 0xd4: case 0xd5: case 0xd6: case 0xd7:
+ case 0xd8: case 0xd9: case 0xda: case 0xdb:
+ case 0xdc: case 0xdd: case 0xde: case 0xdf:
+ return slots[5].ioRead(address);
+
+ case 0xe0: case 0xe1: case 0xe2: case 0xe3:
+ case 0xe4: case 0xe5: case 0xe6: case 0xe7:
+ case 0xe8: case 0xe9: case 0xea: case 0xeb:
+ case 0xec: case 0xed: case 0xee: case 0xef:
+ return slots[6].ioRead(address);
+
+ case 0xf0: case 0xf1: case 0xf2: case 0xf3:
+ case 0xf4: case 0xf5: case 0xf6: case 0xf7:
+ case 0xf8: case 0xf9: case 0xfa: case 0xfb:
+ case 0xfc: case 0xfd: case 0xfe: case 0xff:
+ return slots[7].ioRead(address);
+ }
+
+ return noise();
+ }
+
+ /**
+ * Apple I/O writes
+ *
+ * @param address Address
+ * @param value Value
+ */
+ private void ioWrite(int address, int value) {
+ address &= 0xff;
+
+ if (address >= 0x90) {
+ slots[(address & 0x70) >> 4].ioWrite(address, value);
+ return;
+ }
+
+ switch (address) {
+ case 0x00:
+ // 80STORE off
+ graphicsMode &= ~GR_80STORE;
+ updateMainMemoryMap();
+ return;
+ case 0x01:
+ // 80STORE on
+ graphicsMode |= GR_80STORE;
+ updateMainMemoryMap();
+ return;
+ case 0x02:
+ // Read aux mem off
+ isAuxRead = false;
+ updateMainMemoryMap();
+ return;
+ case 0x03:
+ // Read aux mem on
+ isAuxRead = true;
+ updateMainMemoryMap();
+ return;
+ case 0x04:
+ // Write aux mem off
+ isAuxWrite = false;
+ updateMainMemoryMap();
+ return;
+ case 0x05:
+ // Write aux mem on
+ isAuxWrite = true;
+ updateMainMemoryMap();
+ return;
+ case 0x06:
+ // Do not use internal ROM
+ isRomInternal = false;
+ updateIOMemoryMap();
+ return;
+ case 0x07:
+ // Use internal ROM
+ isRomInternal = true;
+ updateIOMemoryMap();
+ return;
+ case 0x08:
+ if (isAuxZeroPage) {
+ // Physically get main zero page
+ System.arraycopy(mem, MEM_PHYS_ZP, mem, MEM_AUX_ZP, 0x200);
+ System.arraycopy(mem, MEM_MAIN_ZP, mem, MEM_PHYS_ZP, 0x200);
+ }
+ // Aux zero page off
+ isAuxZeroPage = false;
+ updateLCMemoryMap();
+ return;
+ case 0x09:
+ if (!isAuxZeroPage) {
+ // Physically get aux zero page
+ System.arraycopy(mem, MEM_PHYS_ZP, mem, MEM_MAIN_ZP, 0x200);
+ System.arraycopy(mem, MEM_AUX_ZP, mem, MEM_PHYS_ZP, 0x200);
+ }
+ // Aux zero page on
+ isAuxZeroPage = true;
+ updateLCMemoryMap();
+ return;
+ case 0x0a:
+ // Do not use external slot 3 ROM
+ isRomC3External = false;
+ updateIOMemoryMap();
+ return;
+ case 0x0b:
+ // Use external slot 3 ROM
+ isRomC3External = true;
+ updateIOMemoryMap();
+ return;
+ case 0x0c:
+ // 80c off
+ graphicsMode &= ~GR_80CHAR;
+ return;
+ case 0x0d:
+ // 80c on
+ graphicsMode |= GR_80CHAR;
+ return;
+ case 0x0e:
+ // Alt charset off
+ graphicsMode &= ~GR_ALTCHAR;
+ return;
+ case 0x0f:
+ // Alt charset on
+ graphicsMode |= GR_ALTCHAR;
+ return;
+
+ case 0x10: case 0x11: case 0x12: case 0x13:
+ case 0x14: case 0x15: case 0x16: case 0x17:
+ case 0x18: case 0x19: case 0x1a: case 0x1b:
+ case 0x1c: case 0x1d: case 0x1e: case 0x1f:
+ // Keyboard strobe
+ keyboardLatch &= 0x7f;
+ return;
+
+ case 0x20: case 0x21: case 0x22: case 0x23:
+ case 0x24: case 0x25: case 0x26: case 0x27:
+ case 0x28: case 0x29: case 0x2a: case 0x2b:
+ case 0x2c: case 0x2d: case 0x2e: case 0x2f:
+ // Cassette output
+ return;
+
+ case 0x30: case 0x31: case 0x32: case 0x33:
+ case 0x34: case 0x35: case 0x36: case 0x37:
+ case 0x38: case 0x39: case 0x3a: case 0x3b:
+ case 0x3c: case 0x3d: case 0x3e: case 0x3f:
+ // Speaker
+ speakerFlips[speakerFlipsPointer] = clock;
+ speakerFlipsPointer = (speakerFlipsPointer + 1) & SPEAKER_FLIPS_MASK;
+ return;
+
+ case 0x40: case 0x41: case 0x42: case 0x43:
+ case 0x44: case 0x45: case 0x46: case 0x47:
+ case 0x48: case 0x49: case 0x4a: case 0x4b:
+ case 0x4c: case 0x4d: case 0x4e: case 0x4f:
+ // Game strobe
+ return;
+
+ case 0x50:
+ graphicsMode &= ~GR_TEXT;
+ return;
+ case 0x51:
+ graphicsMode |= GR_TEXT;
+ return;
+ case 0x52:
+ graphicsMode &= ~GR_MIXMODE;
+ return;
+ case 0x53:
+ graphicsMode |= GR_MIXMODE;
+ return;
+ case 0x54:
+ graphicsMode &= ~GR_PAGE2;
+ updateMainMemoryMap();
+ return;
+ case 0x55:
+ graphicsMode |= GR_PAGE2;
+ updateMainMemoryMap();
+ return;
+ case 0x56:
+ graphicsMode &= ~GR_HIRES;
+ updateMainMemoryMap();
+ return;
+ case 0x57:
+ graphicsMode |= GR_HIRES;
+ updateMainMemoryMap();
+ return;
+ case 0x58: case 0x59: case 0x5a: case 0x5b:
+ case 0x5c: case 0x5d:
+ // Annunciators
+ return;
+ case 0x5e:
+ graphicsMode |= GR_DHIRES;
+ break;
+ case 0x5f:
+ graphicsMode &= ~GR_DHIRES;
+ break;
+
+ case 0x60: case 0x61: case 0x62: case 0x63:
+ case 0x64: case 0x65: case 0x66: case 0x67:
+ case 0x68: case 0x69: case 0x6a: case 0x6b:
+ case 0x6c: case 0x6d: case 0x6e: case 0x6f:
+ // Cassette input/paddle data
+ return;
+
+ case 0x70: case 0x71: case 0x72: case 0x73:
+ case 0x74: case 0x75: case 0x76: case 0x77:
+ case 0x78: case 0x79: case 0x7a: case 0x7b:
+ case 0x7c: case 0x7d: case 0x7e: case 0x7f:
+ paddle.triggerRegister();
+ return;
+
+ case 0x80:
+ case 0x84:
+ isLcBank2 = true;
+ isLcReadEnable = true;
+ isLcWriteEnable = false;
+ updateLCMemoryMap();
+ break;
+ case 0x81:
+ case 0x85:
+ isLcBank2 = true;
+ isLcReadEnable = false;
+ isLcWriteEnable = true;
+ updateLCMemoryMap();
+ break;
+ case 0x82:
+ case 0x86:
+ isLcBank2 = true;
+ isLcReadEnable = false;
+ isLcWriteEnable = false;
+ updateLCMemoryMap();
+ break;
+ case 0x83:
+ case 0x87:
+ isLcBank2 = true;
+ isLcReadEnable = true;
+ isLcWriteEnable = true;
+ updateLCMemoryMap();
+ break;
+ case 0x88:
+ case 0x8c:
+ isLcBank2 = false;
+ isLcReadEnable = true;
+ isLcWriteEnable = false;
+ updateLCMemoryMap();
+ break;
+ case 0x89:
+ case 0x8d:
+ isLcBank2 = false;
+ isLcReadEnable = false;
+ isLcWriteEnable = true;
+ updateLCMemoryMap();
+ break;
+ case 0x8a:
+ case 0x8e:
+ isLcBank2 = false;
+ isLcReadEnable = false;
+ isLcWriteEnable = false;
+ updateLCMemoryMap();
+ break;
+ case 0x8b:
+ case 0x8f:
+ isLcBank2 = false;
+ isLcReadEnable = true;
+ isLcWriteEnable = true;
+ updateLCMemoryMap();
+ break;
+ }
+ }
+
+ /**
+ * Emulator thread
+ */
+ public void run() {
+ try {
+ while (!isPaused) {
+ long refreshStart = System.currentTimeMillis();
+ long refreshDelay;
+
+ checkInterrupts();
+
+ if (isStepMode) {
+ if (isNextStep) {
+ isNextStep = false;
+ executeInstructions(stepCount);
+ }
+ } else {
+ int clocksNeeded = clocksPerInterval;
+ while (clocksNeeded > 0)
+ clocksNeeded -= executeInstructions(1 + (clocksNeeded >> 3));
+ }
+
+ refreshDelay = System.currentTimeMillis() - refreshStart;
+
+ refreshDelayCumulative += refreshDelay;
+ refreshCycle++;
+ if (refreshCycle >= refreshRate) {
+ refreshDelayPerSecond = refreshDelayCumulative;
+ refreshDelayCumulative = refreshCycle = 0;
+ }
+
+ if (refreshDelay < refreshInterval)
+ Thread.sleep(refreshInterval - refreshDelay);
+ }
+ } catch (InterruptedException e) {
+ };
+ }
+
+ /**
+ * Reset assertion code
+ */
+ protected void onReset() {
+ // Reset IOU + MMU
+ ioWrite(0x00, 0);
+ ioWrite(0x02, 0);
+ ioWrite(0x04, 0);
+ ioWrite(0x06, 0);
+ ioWrite(0x08, 0);
+ ioWrite(0x0a, 0);
+ ioWrite(0x0c, 0);
+ ioWrite(0x0e, 0);
+ ioWrite(0x50, 0);
+ ioWrite(0x52, 0);
+ ioWrite(0x54, 0);
+ ioWrite(0x56, 0);
+ ioWrite(0x5F, 0);
+ ioWrite(0x82, 0);
+
+ if (isRestart) {
+ // Clear RAM
+ for (int i = 0; i < MEM_ROM_MAIN_LOW; i++)
+ mem[i] = 0;
+
+ setRandomSeed();
+ }
+
+ // Reset devices
+ for (int slot = 1; slot < 8; slot++)
+ slots[slot].reset();
+ }
+}
diff --git a/Source/Paddle.java b/Source/Paddle.java
new file mode 100644
index 0000000..43472d9
--- /dev/null
+++ b/Source/Paddle.java
@@ -0,0 +1,100 @@
+
+/**
+* AppleIIGo
+ * Apple II Emulator for J2ME
+ * (C) 2006 by Marc S. Ressl(ressl@lonetree.com)
+ * Released under the GPL
+ */
+
+public class Paddle {
+ // Public variables
+ public static final int PADDLE_LOW = 0;
+ public static final int PADDLE_CENTER = 127;
+ public static final int PADDLE_HIGH = 255;
+
+ public static final int PADDLEMODE_DIRECT = 0;
+ public static final int PADDLEMODE_FILTERED = 1;
+
+ // Instances of other classes
+ private EmAppleII apple;
+
+ // Button variables
+ private int[] buttonRegister = new int[4];
+
+ // Paddle variables
+ private int paddleMode;
+
+ private int[] paddleClockEvent = new int[4];
+ private int[] paddleClockInc = new int[4];
+
+ /**
+ * Paddle class constructor
+ *
+ * @param apple The EmAppleII instance
+ */
+ public Paddle(EmAppleII apple) {
+ this.apple = apple;
+
+ setPaddlePos(0, PADDLE_CENTER);
+ setPaddlePos(1, PADDLE_CENTER);
+ setPaddlePos(2, PADDLE_CENTER);
+ setPaddlePos(3, PADDLE_CENTER);
+ }
+
+ /**
+ * Set button state
+ *
+ * @param button Paddle button
+ * @param state State
+ */
+ public void setButton(int button, boolean pressed) {
+ buttonRegister[button] = (pressed ? 0x80 : 0x00);
+ }
+
+ /**
+ * Button register
+ *
+ * @param button Paddle button
+ */
+ public int getButtonRegister(int button) {
+ return buttonRegister[button];
+ }
+
+ /**
+ * Set paddle position
+ *
+ * @param address Address
+ * @param value Value
+ */
+ public void setPaddlePos(int paddle, int value) {
+ /*
+ * Magic formula, see ROM $FB1E-$FB2E,
+ * We calculate the numbers of cycles after which
+ * the RC circuit of a triggered paddle will discharge.
+ */
+ paddleClockInc[paddle] = value * 11 + 8;
+ }
+
+ /**
+ * Trigger paddle register
+ *
+ * @param address Address
+ * @param value Value
+ */
+ public void triggerRegister() {
+ paddleClockEvent[0] = apple.clock + paddleClockInc[0];
+ paddleClockEvent[1] = apple.clock + paddleClockInc[1];
+ paddleClockEvent[2] = apple.clock + paddleClockInc[2];
+ paddleClockEvent[3] = apple.clock + paddleClockInc[3];
+ }
+
+ /**
+ * Get paddle register
+ *
+ * @param address Address
+ * @param value Value
+ */
+ public int getPaddleRegister(int paddle) {
+ return ((((paddleClockEvent[paddle] - apple.clock) & 0x7fffffff) < 0x40000000) ? 0x80 : 0x00);
+ }
+}
diff --git a/Source/Peripheral.java b/Source/Peripheral.java
new file mode 100644
index 0000000..4b4e4d6
--- /dev/null
+++ b/Source/Peripheral.java
@@ -0,0 +1,34 @@
+
+/**
+ * AppleIIGo
+ * Slot interface
+ * (C) 2006 by Marc S. Ressl(ressl@lonetree.com)
+ * Released under the GPL
+ * Based on work by Steven E. Hugg
+ */
+
+import java.util.Random;
+
+public class Peripheral {
+ protected Random rand = new Random();
+
+ public Peripheral() {
+ }
+
+ public int ioRead(int address) {
+ return rand.nextInt(256);
+ }
+
+ public void ioWrite(int address, int value) {
+ }
+
+ public int memoryRead(int address) {
+ return 0;
+ }
+
+ public void memoryWrite(int address, int value) {
+ }
+
+ public void reset() {
+ }
+}
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..523613c
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,19 @@
+