From 7f130be8ec3533a26130f0d9206345bd65b3cdbb Mon Sep 17 00:00:00 2001 From: sicklittlemonkey Date: Sat, 1 Aug 2015 18:21:16 +1200 Subject: [PATCH] Marc's last version 1.0.1 --- Resources/Character Set.png | Bin 0 -> 1397 bytes Resources/Glare.png | Bin 0 -> 1001 bytes Resources/Paused.png | Bin 0 -> 537 bytes Source/AppleDisplay.java | 1119 ++++++++++++++++++++++++ Source/AppleIIGo.java | 445 ++++++++++ Source/AppleSpeaker.java | 240 ++++++ Source/DiskII.java | 503 +++++++++++ Source/Em6502.java | 1617 +++++++++++++++++++++++++++++++++++ Source/EmAppleII.java | 1186 +++++++++++++++++++++++++ Source/Paddle.java | 100 +++ Source/Peripheral.java | 34 + build.xml | 19 + 12 files changed, 5263 insertions(+) create mode 100644 Resources/Character Set.png create mode 100644 Resources/Glare.png create mode 100644 Resources/Paused.png create mode 100644 Source/AppleDisplay.java create mode 100644 Source/AppleIIGo.java create mode 100644 Source/AppleSpeaker.java create mode 100644 Source/DiskII.java create mode 100644 Source/Em6502.java create mode 100644 Source/EmAppleII.java create mode 100644 Source/Paddle.java create mode 100644 Source/Peripheral.java create mode 100644 build.xml diff --git a/Resources/Character Set.png b/Resources/Character Set.png new file mode 100644 index 0000000000000000000000000000000000000000..7456b9aa0482486cc722480a573a3c1dc050f2ea GIT binary patch literal 1397 zcmV-*1&aEKP)~WD3 zxQ<7HS?5n5b3}uOpWMMbk$4IwhPmN%UHrv?PRj>o?aW&2#@WBoMZ4t6MyNNj#N@^# zw{iyRUd04&Ik>zKUv8Z3t^vbiyxukRNwb*qa@qASMd^!`>FEZMU z-+vF`f{n=C>uyxRVlP zSd5KTG)77{MBWH5hu>d@ercDnEtO1JQ5w5iry7%>>-4LUpHq2TXZqZAMeD7j|1>|< zDN>H6(kjLPPzkVNIsq^MVF}5%X#h;N#;Ks0&ypOq24IFcYStL{2dZ?Q&*j1DK&5CJyQjHuWvmo)OD^bnpKFQ~$z#T7v zCr0hz27tRal34_}bCOBUN>{F!Y{CjVycDn|=xOOGSV|`V`agmM5?dD8`NVz5^}3+q zSFiZGfF8dV8M(3Hoz9qPb+0}R5+^%Xn^6-%7%C0UpD9pwEbKQg7W(Ukzx#M}A$3lCFz_IJ!* z>#|uhX}uRSD{s&xT4Vw4jb3BhAKOcXGnb|YZF;?H&DL{kw{E}iS zKBu1+$+am0`PpAju+&)YJ@T<)&F;JF-hBUECFi&6OwYQl)4Go)Nwp1xSF`p3)xLlCZrRr=+y2KHO^kM1 zZ|0mT+Igm<<8NL3w%fUR+i&YEO#z0}wWx2k_WXw@golTJuG%~A{rmUNPoMVQ&R+BC z>({U0G4tln&%Xb?I9oS+?ajTlA=$m3UkCO)o-uv;^up@uY@oScfBc9zR(R%G)SSbg zUYnnHInXaC;gG=4$S6Pw#li;GP6-8c0!TY5)!;w1naAm6EzTN?Z?^ zTU}CG)ZOuKPg_UCj0A@IiG$VosGcFn1IGgk8}ZbG^QF)C&Izw6VEb{R{qKu=vafbe pea?URF5`U;pTD_NJ2d_?oNl;#v?0!&7np?^JYD@<);T3K0RUQvYfS(E literal 0 HcmV?d00001 diff --git a/Resources/Paused.png b/Resources/Paused.png new file mode 100644 index 0000000000000000000000000000000000000000..397aad451c1bdb0d215177aaf3ecb62edffa058e GIT binary patch literal 537 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|4{)#n$@^1ks(=CyJY5_^DsH{KV#vr~z;Wb2 zP;x}|%M}Ta{G8fCCe<-;?_+5=$iT*&U_b#S!2{M#2?cZlNIOLo*as3k6j2m~12h;Z i2?wC#NePF7gACz%%=TLzbus~?oWax8&t;ucLK6VeR(-z! literal 0 HcmV?d00001 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 @@ + + + + + + + + + + + + + + + + + + +