AppleIIGo/Source/EmAppleII.java

1187 lines
30 KiB
Java

/**
* 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();
}
}