32 Commits

Author SHA1 Message Date
Brendan Robert
dd6b2b8566 Added profiler and 6502 timings switch -- it's not correct and needs a full junit test suite to prove out. 2016-02-06 15:10:15 -06:00
Brendan Robert
af1bdb6b35 Fixed issue #10 reported by Michael -- Looks like introspection works a little differently in 8.0.66 when there are multiple methods with the same name! 2016-01-14 21:29:40 -06:00
Brendan Robert
97b367ccce Updated binaries 2016-01-13 23:49:48 -06:00
Brendan Robert
d5d2424915 Adding a little delay to prevent the issue #8 reported by lifepillar. 2016-01-13 22:44:59 -06:00
Brendan Robert
61356782b6 Removed debug line 2016-01-13 22:25:35 -06:00
Brendan Robert
b9fcc6d82d Fixed ctrl sending key events by itself to the emulator. This fixes the ctrl-c issue reported by Michael as well. 2016-01-13 22:25:14 -06:00
Brendan Robert
8064a804eb Fixed outstanding bugs in VideoDHGR class. It still sucks compared to VideoNTSC but that's a known fact and not a defect. :D 2016-01-13 01:11:59 -06:00
Brendan Robert
fa7cab9866 Merge pull request #3 from Michaelangel007/master
Fix broken download link
2016-01-12 17:07:50 -06:00
Michaelangel007
9a6737e8f0 Updated REAME and run.sh 2016-01-10 11:39:18 -08:00
Michaelangel007
07c71f0117 Added binary jar 2016-01-10 11:35:25 -08:00
Michaelangel007
24f6f7e23f Update Maven project to include dependencies 2016-01-10 11:24:44 -08:00
Michaelangel007
bffe4eba22 Cleaned up build instructions 2016-01-10 11:19:59 -08:00
Michaelangel007
34ef28be1f Updated 2016-01-10 10:54:53 -08:00
Michaelangel007
b5c7418b84 Fixed and Verified script works for OSX 2016-01-10 10:51:56 -08:00
Michaelangel007
1548c1b327 Updated build shell script with troubleshooting steps 2016-01-10 10:12:16 -08:00
Michaelangel007
4c8e809b46 Added build shell script since some freaking instructions on how to compile would be nice 2016-01-10 08:55:04 -08:00
Michaelangel007
676256b6fc Download for 2.0 is source tarball only 2016-01-10 08:36:13 -08:00
Michaelangel007
292b84d1e9 Fix broken download link 2016-01-10 08:28:52 -08:00
Brendan Robert
c261f0f103 Cleared up funky behavior on lo-res graphics, and some improvement on rendering for Crazy Cycles demo 2016-01-03 16:44:09 -06:00
Brendan Robert
ca6a831020 Reset keyboard state when window is deactivated/reactivated 2016-01-03 16:43:31 -06:00
Brendan Robert
732f4768a6 Fixed subdirectory bugs, added support for A2GameServer data files suffix (#TTAAAA where TT is the hex code and AAAA is the aux data/load address). This should be very servicable now. 2015-12-31 21:48:48 -06:00
Brendan Robert
5f9352abb3 Fixed gremlin in directory node handling -- now there are no missing entries and no garbage files listed in Copy ][+ 2015-12-28 21:28:11 -06:00
Brendan Robert
8bcf3a922a Got all the bugs out of the directory handling and also removed a lot of bugs and side-effects through code refactoring 2015-12-28 20:58:48 -06:00
Brendan Robert
59ab31f433 Fixed extended filename parsing and broken sapling file issues 2015-12-28 14:44:24 -06:00
Brendan Robert
6c4136841e Changed packaging instructions in POM 2015-12-20 22:44:23 -06:00
Brendan Robert
17b8183a31 Miscellaneous changes 2015-12-20 22:44:01 -06:00
Brendan Robert
d03c1d9333 Fixed occasional NPE 2015-12-20 22:43:30 -06:00
Brendan Robert
893052b004 Added debugging NOP opcode support. 2015-12-20 22:43:00 -06:00
Brendan Robert
9f838d11f1 Allow writes to C100-C8FF go to cards even if reads are going to the ROM. 2015-12-20 22:42:37 -06:00
Brendan Robert
633b514b38 Removed debug statements from recent changes 2015-12-20 14:18:50 -06:00
Brendan Robert
4021af3ac6 Fixed bugs in the mass-storage implementation. Directories can be mounted via drag/drop, file information is now property reported including dates, and now large files >128k work correctly. 2015-12-20 12:45:56 -06:00
Brendan Robert
cf87f30e35 Added randomized gaps in NIB image generation code just to shake things up a little. :) 2015-11-10 13:52:15 -06:00
30 changed files with 853 additions and 361 deletions

View File

@@ -1,6 +1,25 @@
Java Apple Computer Emulator
====
Download:
* [Version 2.0 Snapshot](target/jace-2.0-SNAPSHOT.jar)
Or:
* [Version 2.0 Snapshot with dependencies](target/jace-2.0-SNAPSHOT-jar-with-dependencies.jar)
To Run:
* See [run.sh](run.sh)
or `java -jar target/jace-2.0-SNAPSHOT-jar-with-dependencies.jar`
To Build:
* See [build.sh](build.sh)
[Download current version with built-in IDE](https://github.com/badvision/jace/releases/download/v2.0b/jace-2.0-SNAPSHOT.zip)
Jace is a java-based Apple //e emulator with many compelling features:

101
build.sh Executable file
View File

@@ -0,0 +1,101 @@
#!/bin/sh
# Building Jace requies:
#
# * Maven
# * Java 1.8 JDK
#
# On OSX the easiest way to install Maven is to use brew
#
# brew install maven
#
#
# Troubleshooting:
# ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile (default-compile) on project jace: Fatal error compiling: invalid target release: 1.8 -> [Help 1]
# org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile (default-compile) on project jace: Fatal error compiling
#
# Cause: You probably have the Java 1.8 RUNTIME installed but Maven is (trying to) use the Java 1.7 COMPILER.
# OR : You probably have Java 1.7 installed but Maven is (trying to) use Java 1.8
# Reference: http://stackoverflow.com/questions/24705877/cant-get-maven-to-recognize-java-1-8
#
# 0. Here is some information to clear up the confusion about Java:
#
# The JRE (runtime) is needed to RUN Java programs.
# The JDK (compiler) is needed to COMPILTE Java programs.
#
# Solution:
#
# 1. Check which verison of Java that Maven is using
#
# mvn -version
#
# 2. Check which version of the Java JRE is installed:
#
# java -version
#
# You should see something like this:
#
# java version "1.8.0_66"
# Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
#
# 3. Check which version of the Java JDK is installed:
#
# javac -version
#
# If you see something like this:
#
# javac 1.7.0_75
#
# Then you will need to proceed to the next step, else you can skip it.
#
# 4. Install Java 1.8 JDK (if necessary)
#
# You can download the JDK either via the GUI or the command line:
# http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
#
# To download from the command line:
#
# For OSX
# curl -L -O -H "Cookie: oraclelicense=accept-securebackup-cookie" -k "https://edelivery.oracle.com/otn-pub/java/jdk/8u66-b17/jdk-8u66-macosx-x64.dmg"
# open jdk-8u66-macosx-x64.dmg
# Double-click on the .pkg
#
# For Linux
# curl -L -O -H "Cookie: oraclelicense=accept-securebackup-cookie" -k "https://edelivery.oracle.com/otn-pub/java/jdk/8u20-b26/jdk-8u20-linux-i586.tar.gz"
#
# Reference:
# Commands / shell script to download JDK / JRE / Java binaries from Oracle website from terminal / shell / command line / command prompt.
# https://gist.github.com/P7h/9741922
#
# 5. Lastly, verify that JAVA_HOME is set:
#
# echo ${JAVA_HOME}
#
# If it is blank (or not set), set it via:
#
# export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
#
# Then you can (finally!) build JACE. Whew!
#
# Note: Changing the maven project file 'pom.xml' to use Java 1.7 *won't* work:
# <plugin>
# <groupId>org.apache.maven.plugins</groupId>
# <artifactId>maven-compiler-plugin</artifactId>
# <version>3.3</version>
# <configuration>
# <source>1.7</source>
# <target>1.7</target>
#
# As the source code is using Java 1.8 langauge features.
if [[ -z "$JAVA_HOME" ]]; then
echo "WARNING: JAVA_HOME was not set"
echo "... Defaulting to Java 1.8..."
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
echo "... JAVA_HOME=${JAVA_HOME}"
fi
#mvn clean install -X
mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V

30
pom.xml
View File

@@ -43,7 +43,7 @@
</execution>
</executions>
</plugin>
<!-- <plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
@@ -61,7 +61,7 @@
</configuration>
</execution>
</executions>
</plugin>-->
</plugin>
<!-- <plugin>
<groupId>org.codehaus.mojo</groupId>
@@ -125,6 +125,32 @@
</additionalClasspathElements>
</configuration>
</plugin>
<!-- BEGIN -->
<!-- https://ivan-site.com/2012/05/download-oracle-java-jre-jdk-using-a-script/ -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>jace.JaceApplication</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- END -->
</plugins>
</build>

9
run.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/sh
#java -jar target/jace-2.0-SNAPSHOT.jar
# http://stackoverflow.com/questions/9689793/cant-execute-jar-file-no-main-manifest-attribute
#java -cp target/jace-2.0-SNAPSHOT.jar jace.JaceApplication
java -jar target/jace-2.0-SNAPSHOT-jar-with-dependencies.jar

View File

@@ -204,10 +204,10 @@ public class EmulatorUILogic implements Reconfigurable {
Emulator.computer.resume();
return;
}
runFile(binary);
runFileNamed(binary);
}
public static void runFile(File binary) {
public static void runFileNamed(File binary) {
String fileName = binary.getName().toLowerCase();
try {
if (fileName.contains("#06")) {

View File

@@ -58,7 +58,7 @@ public class JaceApplication extends Application {
while (Emulator.computer.getVideo() == null || Emulator.computer.getVideo().getFrameBuffer() == null) {
Thread.yield();
}
controller.connectComputer(Emulator.computer);
controller.connectComputer(Emulator.computer, primaryStage);
bootWatchdog();
});
primaryStage.setOnCloseRequest(event -> {

View File

@@ -10,6 +10,7 @@ import com.sun.glass.ui.Application;
import jace.cheat.MetaCheat;
import jace.core.Card;
import jace.core.Computer;
import jace.core.Keyboard;
import jace.library.MediaCache;
import jace.library.MediaConsumer;
import jace.library.MediaConsumerParent;
@@ -43,6 +44,7 @@ import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
*
@@ -79,10 +81,11 @@ public class JaceUIController {
rootPane.setOnDragExited(this::processDragExitedEvent);
}
public void connectComputer(Computer computer) {
public void connectComputer(Computer computer, Stage primaryStage) {
this.computer = computer;
appleScreen.setImage(computer.getVideo().getFrameBuffer());
EventHandler<KeyEvent> keyboardHandler = computer.getKeyboard().getListener();
primaryStage.setOnShowing(evt->computer.getKeyboard().resetState());
rootPane.setFocusTraversable(true);
rootPane.setOnKeyPressed(keyboardHandler);
rootPane.setOnKeyReleased(keyboardHandler);
@@ -123,7 +126,7 @@ public class JaceUIController {
return null;
}
for (File f : files) {
if (f.isFile()) return f;
if (f.exists()) return f;
}
return null;
}

View File

@@ -24,6 +24,9 @@ import jace.core.Computer;
import jace.core.RAM;
import jace.core.RAMEvent.TYPE;
import jace.state.Stateful;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
@@ -37,6 +40,8 @@ import java.util.logging.Logger;
@Stateful
public class MOS65C02 extends CPU {
private static final Logger LOG = Logger.getLogger(MOS65C02.class.getName());
public boolean readAddressTriggersEvent = true;
static int RESET_VECTOR = 0x00FFFC;
static int INT_VECTOR = 0x00FFFE;
@@ -81,30 +86,32 @@ public class MOS65C02 extends CPU {
public void reconfigure() {
}
public static final boolean USE_6502_TIMINGS = true;
public enum OPCODE {
ADC_IMM(0x0069, COMMAND.ADC, MODE.IMMEDIATE, 2),
ADC_ZP(0x0065, COMMAND.ADC, MODE.ZEROPAGE, 3),
ADC_ZP_X(0x0075, COMMAND.ADC, MODE.ZEROPAGE_X, 4),
ADC_AB(0x006D, COMMAND.ADC, MODE.ABSOLUTE, 4),
ADC_IND_ZP(0x0072, COMMAND.ADC, MODE.INDIRECT_ZP, 5, true),
ADC_IND_ZP_X(0x0061, COMMAND.ADC, MODE.INDIRECT_ZP_X, 6),
ADC_AB_X(0x007D, COMMAND.ADC, MODE.ABSOLUTE_X, 4),
ADC_AB_Y(0x0079, COMMAND.ADC, MODE.ABSOLUTE_Y, 4),
ADC_IND_ZP(0x0072, COMMAND.ADC, MODE.INDIRECT_ZP, 5, true),
ADC_IND_ZP_X(0x0061, COMMAND.ADC, MODE.INDIRECT_ZP_X, 6),
ADC_IND_ZP_Y(0x0071, COMMAND.ADC, MODE.INDIRECT_ZP_Y, 5),
AND_IMM(0x0029, COMMAND.AND, MODE.IMMEDIATE, 2),
AND_ZP(0x0025, COMMAND.AND, MODE.ZEROPAGE, 3),
AND_ZP_X(0x0035, COMMAND.AND, MODE.ZEROPAGE_X, 4),
AND_AB(0x002D, COMMAND.AND, MODE.ABSOLUTE, 4),
AND_IND_ZP(0x0032, COMMAND.AND, MODE.INDIRECT_ZP, 5, true),
AND_IND_ZP_X(0x0021, COMMAND.AND, MODE.INDIRECT_ZP_X, 6),
AND_AB_X(0x003D, COMMAND.AND, MODE.ABSOLUTE_X, 4),
AND_AB_Y(0x0039, COMMAND.AND, MODE.ABSOLUTE_Y, 4),
AND_IND_ZP(0x0032, COMMAND.AND, MODE.INDIRECT_ZP, 5, true),
AND_IND_ZP_X(0x0021, COMMAND.AND, MODE.INDIRECT_ZP_X, 6),
AND_IND_ZP_Y(0x0031, COMMAND.AND, MODE.INDIRECT_ZP_Y, 5),
ASL(0x000A, COMMAND.ASL_A, MODE.IMPLIED, 2),
ASL_ZP(0x0006, COMMAND.ASL, MODE.ZEROPAGE, 5),
ASL_ZP_X(0x0016, COMMAND.ASL, MODE.ZEROPAGE_X, 6),
ASL_AB(0x000E, COMMAND.ASL, MODE.ABSOLUTE, 6),
ASL_AB_X(0x001E, COMMAND.ASL, MODE.ABSOLUTE_X, 7),
ASL_AB_X(0x001E, COMMAND.ASL, MODE.ABSOLUTE_X, USE_6502_TIMINGS ? 7 : 6),
BCC_REL(0x0090, COMMAND.BCC, MODE.RELATIVE, 2),
BCS_REL(0x00B0, COMMAND.BCS, MODE.RELATIVE, 2),
BBR0(0x00f, COMMAND.BBR0, MODE.ZP_REL, 5, true),
@@ -123,16 +130,16 @@ public class MOS65C02 extends CPU {
BBS5(0x0df, COMMAND.BBS5, MODE.ZP_REL, 5, true),
BBS6(0x0ef, COMMAND.BBS6, MODE.ZP_REL, 5, true),
BBS7(0x0ff, COMMAND.BBS7, MODE.ZP_REL, 5, true),
BEQ_REL0(0x00F0, COMMAND.BEQ, MODE.RELATIVE, 2),
BEQ_REL(0x00F0, COMMAND.BEQ, MODE.RELATIVE, 2),
BIT_IMM(0x0089, COMMAND.BIT, MODE.IMMEDIATE, 3, true),
BIT_ZP(0x0024, COMMAND.BIT, MODE.ZEROPAGE, 3),
BIT_ZP_X(0x0034, COMMAND.BIT, MODE.ZEROPAGE_X, 3, true),
BIT_ZP_X(0x0034, COMMAND.BIT, MODE.ZEROPAGE_X, 4, true),
BIT_AB(0x002C, COMMAND.BIT, MODE.ABSOLUTE, 4),
BIT_AB_X(0x003C, COMMAND.BIT, MODE.ABSOLUTE_X, 4, true),
BMI_REL(0x0030, COMMAND.BMI, MODE.RELATIVE, 2),
BNE_REL(0x00D0, COMMAND.BNE, MODE.RELATIVE, 2),
BPL_REL(0x0010, COMMAND.BPL, MODE.RELATIVE, 2),
BRA_REL(0x0080, COMMAND.BRA, MODE.RELATIVE, 2, true),
BPL_REL(0x0010, COMMAND.BPL, MODE.RELATIVE, 2), //?
BRA_REL(0x0080, COMMAND.BRA, MODE.RELATIVE, 2),
// BRK(0x0000, COMMAND.BRK, MODE.IMPLIED, 7),
// Do this so that BRK is treated as a two-byte instruction
BRK(0x0000, COMMAND.BRK, MODE.IMMEDIATE, 7),
@@ -158,10 +165,10 @@ public class MOS65C02 extends CPU {
CPY_ZP(0x00C4, COMMAND.CPY, MODE.ZEROPAGE, 3),
CPY_AB(0x00CC, COMMAND.CPY, MODE.ABSOLUTE, 4),
DEC(0x003A, COMMAND.DEA, MODE.IMPLIED, 2, true),
DEC_ZP(0x00C6, COMMAND.DEC, MODE.ZEROPAGE, 5),
DEC_ZP(0x00C6, COMMAND.DEC, MODE.ZEROPAGE, 5), // ??
DEC_ZP_X(0x00D6, COMMAND.DEC, MODE.ZEROPAGE_X, 6),
DEC_AB(0x00CE, COMMAND.DEC, MODE.ABSOLUTE, 6),
DEC_AB_X(0x00DE, COMMAND.DEC, MODE.ABSOLUTE_X, 7),
DEC_AB_X(0x00DE, COMMAND.DEC, MODE.ABSOLUTE_X_NODELAY, 7),
DEX(0x00CA, COMMAND.DEX, MODE.IMPLIED, 2),
DEY(0x0088, COMMAND.DEY, MODE.IMPLIED, 2),
EOR_IMM(0x0049, COMMAND.EOR, MODE.IMMEDIATE, 2),
@@ -177,11 +184,11 @@ public class MOS65C02 extends CPU {
INC_ZP(0x00E6, COMMAND.INC, MODE.ZEROPAGE, 5),
INC_ZP_X(0x00F6, COMMAND.INC, MODE.ZEROPAGE_X, 6),
INC_AB(0x00EE, COMMAND.INC, MODE.ABSOLUTE, 6),
INC_AB_X(0x00FE, COMMAND.INC, MODE.ABSOLUTE_X, 7),
INC_AB_X(0x00FE, COMMAND.INC, MODE.ABSOLUTE_X_NODELAY, 7),
INX(0x00E8, COMMAND.INX, MODE.IMPLIED, 2),
INY(0x00C8, COMMAND.INY, MODE.IMPLIED, 2),
JMP_AB(0x004C, COMMAND.JMP, MODE.ABSOLUTE, 3, false, false),
JMP_IND(0x006C, COMMAND.JMP, MODE.INDIRECT, 5),
JMP_AB(0x004C, COMMAND.JMP, MODE.ABSOLUTE, 3, false, false), // ??
JMP_IND(0x006C, COMMAND.JMP, MODE.INDIRECT, 6),
JMP_IND_X(0x007C, COMMAND.JMP, MODE.INDIRECT_X, 6, true),
JSR_AB(0x0020, COMMAND.JSR, MODE.ABSOLUTE, 6, false, false),
LDA_IMM(0x00A9, COMMAND.LDA, MODE.IMMEDIATE, 2),
@@ -189,7 +196,7 @@ public class MOS65C02 extends CPU {
LDA_ZP_X(0x00B5, COMMAND.LDA, MODE.ZEROPAGE_X, 4),
LDA_AB(0x00AD, COMMAND.LDA, MODE.ABSOLUTE, 4),
LDA_IND_ZP_X(0x00A1, COMMAND.LDA, MODE.INDIRECT_ZP_X, 6),
LDA_AB_X(0x00BD, COMMAND.LDA, MODE.ABSOLUTE_X, 4),
LDA_AB_X(0x00BD, COMMAND.LDA, MODE.ABSOLUTE_X, 4, true), //?
LDA_AB_Y(0x00B9, COMMAND.LDA, MODE.ABSOLUTE_Y, 4),
LDA_IND_ZP_Y(0x00B1, COMMAND.LDA, MODE.INDIRECT_ZP_Y, 5),
LDA_IND_ZP(0x00B2, COMMAND.LDA, MODE.INDIRECT_ZP, 5, true),
@@ -207,8 +214,9 @@ public class MOS65C02 extends CPU {
LSR_ZP(0x0046, COMMAND.LSR, MODE.ZEROPAGE, 5),
LSR_ZP_X(0x0056, COMMAND.LSR, MODE.ZEROPAGE_X, 6),
LSR_AB(0x004E, COMMAND.LSR, MODE.ABSOLUTE, 6),
LSR_AB_X(0x005E, COMMAND.LSR, MODE.ABSOLUTE_X, 7),
LSR_AB_X(0x005E, COMMAND.LSR, MODE.ABSOLUTE_X, USE_6502_TIMINGS ? 7 : 6),
NOP(0x00EA, COMMAND.NOP, MODE.IMPLIED, 2),
SPECIAL(0x00FC, COMMAND.NOP_SPECIAL, MODE.ABSOLUTE, 4),
ORA_IMM(0x0009, COMMAND.ORA, MODE.IMMEDIATE, 2),
ORA_ZP(0x0005, COMMAND.ORA, MODE.ZEROPAGE, 3),
ORA_ZP_X(0x0015, COMMAND.ORA, MODE.ZEROPAGE_X, 4),
@@ -238,22 +246,22 @@ public class MOS65C02 extends CPU {
ROL_ZP(0x0026, COMMAND.ROL, MODE.ZEROPAGE, 5),
ROL_ZP_X(0x0036, COMMAND.ROL, MODE.ZEROPAGE_X, 6),
ROL_AB(0x002E, COMMAND.ROL, MODE.ABSOLUTE, 6),
ROL_AB_X(0x003E, COMMAND.ROL, MODE.ABSOLUTE_X, 7),
ROL_AB_X(0x003E, COMMAND.ROL, USE_6502_TIMINGS ? MODE.ABSOLUTE_X_NODELAY : MODE.ABSOLUTE_X, USE_6502_TIMINGS ? 7 : 6),
ROR(0x006A, COMMAND.ROR_A, MODE.IMPLIED, 2),
ROR_ZP(0x0066, COMMAND.ROR, MODE.ZEROPAGE, 5),
ROR_ZP_X(0x0076, COMMAND.ROR, MODE.ZEROPAGE_X, 6),
ROR_AB(0x006E, COMMAND.ROR, MODE.ABSOLUTE, 6),
ROR_AB_X(0x007E, COMMAND.ROR, MODE.ABSOLUTE_X, 7),
ROR_AB_X(0x007E, COMMAND.ROR, USE_6502_TIMINGS ? MODE.ABSOLUTE_X_NODELAY : MODE.ABSOLUTE_X, USE_6502_TIMINGS ? 7 : 6),
RTI(0x0040, COMMAND.RTI, MODE.IMPLIED, 6),
RTS(0x0060, COMMAND.RTS, MODE.IMPLIED, 6),
SBC_IMM(0x00E9, COMMAND.SBC, MODE.IMMEDIATE, 2),
SBC_ZP(0x00E5, COMMAND.SBC, MODE.ZEROPAGE, 3),
SBC_ZP_X(0x00F5, COMMAND.SBC, MODE.ZEROPAGE_X, 4),
SBC_AB(0x00ED, COMMAND.SBC, MODE.ABSOLUTE, 4),
SBC_IND_ZP(0x00F2, COMMAND.SBC, MODE.INDIRECT_ZP, 5, true),
SBC_IND_ZP_X(0x00E1, COMMAND.SBC, MODE.INDIRECT_ZP_X, 6),
SBC_AB_X(0x00FD, COMMAND.SBC, MODE.ABSOLUTE_X, 4),
SBC_AB_Y(0x00F9, COMMAND.SBC, MODE.ABSOLUTE_Y, 4),
SBC_IND_ZP(0x00F2, COMMAND.SBC, MODE.INDIRECT_ZP, 5, true),
SBC_IND_ZP_X(0x00E1, COMMAND.SBC, MODE.INDIRECT_ZP_X, 6),
SBC_IND_ZP_Y(0x00F1, COMMAND.SBC, MODE.INDIRECT_ZP_Y, 5),
SEC(0x0038, COMMAND.SEC, MODE.IMPLIED, 2),
SED(0x00F8, COMMAND.SED, MODE.IMPLIED, 2),
@@ -267,13 +275,13 @@ public class MOS65C02 extends CPU {
SMB6(0x0e7, COMMAND.SMB6, MODE.ZEROPAGE, 5, true),
SMB7(0x0f7, COMMAND.SMB7, MODE.ZEROPAGE, 5, true),
STA_ZP(0x0085, COMMAND.STA, MODE.ZEROPAGE, 3),
STA_ZP_X(0x0095, COMMAND.STA, MODE.ZEROPAGE_X, 4),
STA_ZP_X(0x0095, COMMAND.STA, MODE.ZEROPAGE_X, 4), // ??
STA_AB(0x008D, COMMAND.STA, MODE.ABSOLUTE, 4),
STA_AB_X(0x009D, COMMAND.STA, MODE.ABSOLUTE_X, 5),
STA_AB_Y(0x0099, COMMAND.STA, MODE.ABSOLUTE_Y, 5),
STA_AB_X(0x009D, COMMAND.STA, MODE.ABSOLUTE_X_NODELAY, 5),
STA_AB_Y(0x0099, COMMAND.STA, MODE.ABSOLUTE_Y_NODELAY, 5),
STA_IND_ZP(0x0092, COMMAND.STA, MODE.INDIRECT_ZP, 5, true),
STA_IND_ZP_X(0x0081, COMMAND.STA, MODE.INDIRECT_ZP_X, 6),
STA_IND_ZP_Y(0x0091, COMMAND.STA, MODE.INDIRECT_ZP_Y, 6),
STA_IND_ZP_Y(0x0091, COMMAND.STA, MODE.INDIRECT_ZP_Y_NODELAY, 6),
STP(0x00DB, COMMAND.STP, MODE.IMPLIED, 3, true),
STX_ZP(0x0086, COMMAND.STX, MODE.ZEROPAGE, 3),
STX_ZP_Y(0x0096, COMMAND.STX, MODE.ZEROPAGE_Y, 4),
@@ -285,7 +293,7 @@ public class MOS65C02 extends CPU {
STZ_ZP_X(0x0074, COMMAND.STZ, MODE.ZEROPAGE_X, 4, true),
STZ_AB(0x009C, COMMAND.STZ, MODE.ABSOLUTE, 4, true),
STZ_AB_X(0x009E, COMMAND.STZ, MODE.ABSOLUTE_X, 5, true),
TAX(0x00AA, COMMAND.TAX, MODE.IMPLIED, 2),
TAX(0x00AA, COMMAND.TAX, MODE.IMPLIED, 2), // ??
TAY(0x00A8, COMMAND.TAY, MODE.IMPLIED, 2),
TRB_ZP(0x0014, COMMAND.TRB, MODE.ZEROPAGE, 5, true),
TRB_AB(0x001C, COMMAND.TRB, MODE.ABSOLUTE, 6, true),
@@ -398,6 +406,12 @@ public class MOS65C02 extends CPU {
}
return address2;
}),
INDIRECT_ZP_Y_NODELAY(2, "$(~1),Y", (cpu) -> {
int address = 0x00FF & cpu.getMemory().read(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
address = cpu.getMemory().readWord(address, TYPE.READ_DATA, true, false);
int address2 = address + cpu.Y;
return address2;
}),
ABSOLUTE(3, "$~2~1", (cpu) -> cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false)),
ABSOLUTE_X(3, "$~2~1,X", (cpu) -> {
int address2 = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
@@ -407,6 +421,11 @@ public class MOS65C02 extends CPU {
}
return address;
}),
ABSOLUTE_X_NODELAY(3, "$~2~1,X", (cpu) -> {
int address2 = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
int address = 0x0FFFF & (address2 + cpu.X);
return address;
}),
ABSOLUTE_Y(3, "$~2~1,Y", (cpu) -> {
int address2 = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
int address = 0x0FFFF & (address2 + cpu.Y);
@@ -415,6 +434,11 @@ public class MOS65C02 extends CPU {
}
return address;
}),
ABSOLUTE_Y_NODELAY(3, "$~2~1,Y", (cpu) -> {
int address2 = cpu.getMemory().readWord(cpu.getProgramCounter() + 1, TYPE.READ_OPERAND, cpu.readAddressTriggersEvent, false);
int address = 0x0FFFF & (address2 + cpu.Y);
return address;
}),
ZP_REL(2, "$~1,$R", new AddressCalculator() {
@Override
public int calculateAddress(MOS65C02 cpu) {
@@ -463,6 +487,7 @@ public class MOS65C02 extends CPU {
private MODE(int size, String fmt, AddressCalculator calc) {
this(size, fmt, calc, true);
}
private MODE(int size, String fmt, AddressCalculator calc, boolean fetch) {
this.fetchValue = fetch;
this.size = size;
@@ -583,11 +608,11 @@ public class MOS65C02 extends CPU {
}
public enum COMMAND {
ADC((address, value, addressMode, cpu) -> {
int w;
cpu.V = ((cpu.A ^ value) & 0x080) == 0;
if (cpu.D) {
cpu.addWaitCycles(1);
// Decimal Mode
w = (cpu.A & 0x0f) + (value & 0x0f) + cpu.C;
if (w >= 10) {
@@ -705,7 +730,7 @@ public class MOS65C02 extends CPU {
}),
BRA((address, value, addressMode, cpu) -> {
cpu.setProgramCounter(address);
cpu.addWaitCycles(cpu.pageBoundaryPenalty ? 1 : 0);
cpu.addWaitCycles(cpu.pageBoundaryPenalty ? 2 : 1);
}),
BRK((address, value, addressMode, cpu) -> {
cpu.BRK();
@@ -824,6 +849,11 @@ public class MOS65C02 extends CPU {
}),
NOP((address, value, addressMode, cpu) -> {
}),
NOP_SPECIAL((address, value, addressMode, cpu) -> {
byte param1 = (byte) (address & 0x0ff);
byte param2 = (byte) (address >> 8);
cpu.performExtendedCommand(param1, param2);
}),
ORA((address, value, addressMode, cpu) -> {
cpu.A |= value;
cpu.setNZ(cpu.A);
@@ -903,6 +933,7 @@ public class MOS65C02 extends CPU {
cpu.V = ((cpu.A ^ value) & 0x080) != 0;
int w;
if (cpu.D) {
cpu.addWaitCycles(1);
int temp = 0x0f + (cpu.A & 0x0f) - (value & 0x0f) + cpu.C;
if (temp < 0x10) {
w = 0;
@@ -1037,6 +1068,54 @@ public class MOS65C02 extends CPU {
}
}
Map<OPCODE, Map<Integer, Integer>> histogram = null;
int ellapsedSeconds = 0;
long startTime = System.currentTimeMillis();
long nextInterval = System.currentTimeMillis() + 1000;
private void addToHistogram(OPCODE o) {
if (histogram == null) {
return;
}
long now = System.currentTimeMillis();
if (now >= nextInterval) {
ellapsedSeconds = (int) ((now - startTime) / 1000);
nextInterval = ellapsedSeconds * 1000 + startTime;
}
Map<Integer, Integer> countPerTimestamp = histogram.get(o);
if (countPerTimestamp == null) {
countPerTimestamp = new HashMap<>();
histogram.put(o, countPerTimestamp);
}
Integer count = countPerTimestamp.get(ellapsedSeconds);
if (count == null) {
count = 0;
}
countPerTimestamp.put(ellapsedSeconds, count+1);
}
private void dumpHistogram(Map<OPCODE, Map<Integer, Integer>> histogram) {
System.out.println("\nOpcode");
for (int i = 0; i <= ellapsedSeconds; i++) {
System.out.print("\t");
System.out.print(i);
}
System.out.println();
for (OPCODE o : histogram.keySet()) {
System.out.print(o.name());
Map<Integer, Integer> countPerTimestamp = histogram.get(o);
for (int i = 0; i <= ellapsedSeconds; i++) {
Integer count = countPerTimestamp.get(i);
if (count == null) {
count = 0;
}
System.out.print("\t");
System.out.print(count);
}
System.out.println();
}
}
@Override
protected void executeOpcode() {
if (interruptSignalled) {
@@ -1049,7 +1128,7 @@ public class MOS65C02 extends CPU {
traceEntry = getState().toUpperCase() + " " + Integer.toString(pc, 16) + " : " + disassemble();
captureSingleTrace(traceEntry);
if (isTraceEnabled()) {
Logger.getLogger(getClass().getName()).info(traceEntry);
LOG.log(Level.INFO, traceEntry);
}
if (isLogEnabled()) {
log(traceEntry);
@@ -1058,14 +1137,15 @@ public class MOS65C02 extends CPU {
// This makes it possible to trap the memory read of an opcode, when PC == Address, you know it is executing that opcode.
int op = 0x00ff & getMemory().read(pc, TYPE.EXECUTE, true, false);
OPCODE opcode = opcodes[op];
addToHistogram(opcode);
if (traceEntry != null && warnAboutExtendedOpcodes && opcode != null && opcode.isExtendedOpcode) {
System.out.println(">>EXTENDED OPCODE DETECTED " + Integer.toHexString(opcode.code) + "<<");
System.out.println(traceEntry);
LOG.log(Level.WARNING, ">>EXTENDED OPCODE DETECTED {0}<<", Integer.toHexString(opcode.code));
LOG.log(Level.WARNING, traceEntry);
if (isLogEnabled()) {
log(">>EXTENDED OPCODE DETECTED " + Integer.toHexString(opcode.code) + "<<");
log(traceEntry);
}
}
}
if (opcode == null) {
// handle bad opcode as a NOP
int wait = 0;
@@ -1088,23 +1168,23 @@ public class MOS65C02 extends CPU {
wait = 3;
} else {
wait = 4;
} break;
}
break;
case 0x0c:
bytes = 3;
if ((op & 0x0f0) == 0x050) {
wait = 8;
} else {
wait = 4;
} break;
}
break;
default:
}
incrementProgramCounter(bytes);
addWaitCycles(wait);
if (isLogEnabled() || breakOnBadOpcode) {
System.out.println("Unrecognized opcode "
+ Integer.toHexString(op)
+ " at " + Integer.toHexString(pc));
LOG.log(Level.WARNING, "Unrecognized opcode {0} at {1}", new Object[]{Integer.toHexString(op), Integer.toHexString(pc)});
}
if (breakOnBadOpcode) {
OPCODE.BRK.execute(this);
@@ -1175,13 +1255,13 @@ public class MOS65C02 extends CPU {
@Override
public void JSR(int address) {
pushWord(getProgramCounter() - 1);
setProgramCounter(address);
pushPC();
setProgramCounter(address);
}
public void BRK() {
if (isLogEnabled()) {
System.out.println("BRK at $" + Integer.toString(getProgramCounter(), 16));
LOG.log(Level.WARNING, "BRK at ${0}", Integer.toString(getProgramCounter(), 16));
dumpTrace();
}
B = true;
@@ -1221,6 +1301,14 @@ public class MOS65C02 extends CPU {
// Cold/Warm boot procedure
@Override
public void reset() {
if (histogram != null) {
Map<OPCODE, Map<Integer, Integer>> oldHistogram = histogram;
histogram = null;
dumpHistogram(oldHistogram);
}
startTime = System.currentTimeMillis();
ellapsedSeconds = 0;
histogram = new HashMap<>();
pushWord(getProgramCounter());
push(getStatus());
// STACK = 0x0ff;
@@ -1233,7 +1321,7 @@ public class MOS65C02 extends CPU {
// V = true;
// Z = true;
int newPC = getMemory().readWord(RESET_VECTOR, TYPE.READ_DATA, true, false);
System.out.println("Reset called, setting PC to (" + Integer.toString(RESET_VECTOR, 16) + ") = " + Integer.toString(newPC, 16));
LOG.log(Level.WARNING, "Reset called, setting PC to ({0}) = {1}", new Object[]{Integer.toString(RESET_VECTOR, 16), Integer.toString(newPC, 16)});
setProgramCounter(newPC);
}
@@ -1321,4 +1409,35 @@ public class MOS65C02 extends CPU {
public void pushPC() {
pushWord(getProgramCounter() - 1);
}
/**
* Special commands -- these are usually treated as NOP but can be reused
* for emulator controls !byte $fc, $65, $00 ; Turn off tracing !byte $fc,
* $65, $01 ; Turn on tracing
*
* @param param1
* @param param2
*/
public void performExtendedCommand(byte param1, byte param2) {
LOG.log(Level.INFO, "Extended command {0},{1}", new Object[]{Integer.toHexString(param1), Integer.toHexString(param2)});
switch (param1 & 0x0ff) {
case 0x65:
// CPU functions
switch (param2 & 0x0ff) {
case 0x00:
// Turn off tracing
trace = false;
break;
case 0x01:
// Turn on tracing
trace = true;
break;
}
break;
case 0x64:
// Memory functions
getMemory().performExtendedCommand(param2 & 0x0ff);
default:
}
}
}

View File

@@ -26,6 +26,8 @@ import jace.core.RAM;
import jace.state.Stateful;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -38,6 +40,74 @@ import java.util.logging.Logger;
*/
@Stateful
abstract public class RAM128k extends RAM {
Logger LOG = Logger.getLogger(RAM128k.class.getName());
Map<String, PagedMemory> banks;
private Map<String, PagedMemory> getBanks() {
if (banks == null) {
banks = new HashMap<>();
banks.put("main", mainMemory);
banks.put("lc", languageCard);
banks.put("lc2", languageCard2);
banks.put("//e rom (80-col)", cPageRom);
banks.put("//e rom", rom);
banks.put("blank", blank);
banks.put("aux", getAuxMemory());
banks.put("aux lc", getAuxLanguageCard());
banks.put("aux lc2", getAuxLanguageCard2());
cards[1].ifPresent(c -> banks.put("card1a", c.getCxRom()));
cards[1].ifPresent(c -> banks.put("card1b", c.getC8Rom()));
cards[2].ifPresent(c -> banks.put("card2a", c.getCxRom()));
cards[2].ifPresent(c -> banks.put("card2b", c.getC8Rom()));
cards[3].ifPresent(c -> banks.put("card3a", c.getCxRom()));
cards[3].ifPresent(c -> banks.put("card3b", c.getC8Rom()));
cards[4].ifPresent(c -> banks.put("card4a", c.getCxRom()));
cards[4].ifPresent(c -> banks.put("card4b", c.getC8Rom()));
cards[5].ifPresent(c -> banks.put("card5a", c.getCxRom()));
cards[5].ifPresent(c -> banks.put("card5b", c.getC8Rom()));
cards[6].ifPresent(c -> banks.put("card6a", c.getCxRom()));
cards[6].ifPresent(c -> banks.put("card6b", c.getC8Rom()));
cards[7].ifPresent(c -> banks.put("card7a", c.getCxRom()));
cards[7].ifPresent(c -> banks.put("card7b", c.getC8Rom()));
}
return banks;
}
@Override
public void performExtendedCommand(int param) {
switch (param) {
case 0xda:
// 64 da : Dump all memory mappings
System.out.println("Active banks");
for (int i = 0; i < 256; i++) {
byte[] read = activeRead.get(i);
byte[] write = activeWrite.get(i);
String readBank = getBanks().keySet().stream().filter(bank->{
PagedMemory mem = getBanks().get(bank);
for (byte[] page : mem.getMemory()) {
if (page == read) {
return true;
}
}
return false;
}).findFirst().orElse("unknown");
String writeBank = getBanks().keySet().stream().filter(bank->{
PagedMemory mem = getBanks().get(bank);
for (byte[] page : mem.getMemory()) {
if (page == write) {
return true;
}
}
return false;
}).findFirst().orElse("unknown");
LOG.log(Level.INFO,"Bank {0}\t{1}\t{2}", new Object[]{Integer.toHexString(i), readBank, writeBank});
}
default:
}
}
@Stateful
public PagedMemory mainMemory;
@@ -90,7 +160,7 @@ abstract public class RAM128k extends RAM {
// First off, set up read/write for main memory (might get changed later on)
activeRead.fillBanks(SoftSwitches.RAMRD.getState() ? getAuxMemory() : mainMemory);
activeWrite.fillBanks(SoftSwitches.RAMWRT.getState() ? getAuxMemory() : mainMemory);
// Handle language card softswitches
activeRead.fillBanks(rom);
//activeRead.fillBanks(cPageRom);
@@ -110,7 +180,7 @@ abstract public class RAM128k extends RAM {
}
}
}
if (SoftSwitches.LCWRITE.isOn()) {
if (SoftSwitches.AUXZP.isOff()) {
activeWrite.fillBanks(languageCard);
@@ -129,7 +199,7 @@ abstract public class RAM128k extends RAM {
activeWrite.set(i, null);
}
}
// Handle 80STORE logic for bankswitching video ram
if (SoftSwitches._80STORE.isOn()) {
activeRead.setBanks(0x04, 0x04, 0x04,
@@ -143,7 +213,7 @@ abstract public class RAM128k extends RAM {
SoftSwitches.PAGE2.isOn() ? getAuxMemory() : mainMemory);
}
}
// Handle zero-page bankswitching
if (SoftSwitches.AUXZP.getState()) {
// Aux pages 0 and 1
@@ -154,13 +224,13 @@ abstract public class RAM128k extends RAM {
activeRead.setBanks(0, 2, 0, mainMemory);
activeWrite.setBanks(0, 2, 0, mainMemory);
}
/*
INTCXROM SLOTC3ROM C1,C2,C4-CF C3
0 0 slot rom
0 1 slot slot
1 - rom rom
*/
*/
if (SoftSwitches.CXROM.getState()) {
// Enable C1-CF to point to rom
activeRead.setBanks(0, 0x0F, 0x0C1, cPageRom);

View File

@@ -389,7 +389,7 @@ public class VideoDHGR extends Video {
Color color = Palette.color[c1];
// Unrolled loop, faster
PixelWriter writer = screen.getPixelWriter();
int xx = xOffset * 7;
int xx = xOffset * 14;
writer.setColor(xx++, y, color);
writer.setColor(xx++, y, color);
writer.setColor(xx++, y, color);
@@ -418,9 +418,9 @@ public class VideoDHGR extends Video {
}
PixelWriter writer = screen.getPixelWriter();
// int yOffset = xyOffset[y][times14[xOffset]];
Color color = Palette.color[c1];
Color color = Palette.color[FLIP_NYBBLE[c1]];
// Unrolled loop, faster
int xx = xOffset * 7;
int xx = xOffset * 14;
writer.setColor(xx++, y, color);
writer.setColor(xx++, y, color);
writer.setColor(xx++, y, color);
@@ -615,16 +615,19 @@ public class VideoDHGR extends Video {
}
protected void showDhgr(WritableImage screen, int xOffset, int y, int dhgrWord) {
//Graphics2D g = (Graphics2D) screen.getGraphics();
int xx = xOffset * 7;
PixelWriter writer = screen.getPixelWriter();
try {
for (int i = 0; i < 7; i++) {
Color color = Palette.color[FLIP_NYBBLE[dhgrWord & 15]];
writer.setColor(xx++, y, color);
writer.setColor(xx++, y, color);
writer.setColor(xx++, y, color);
writer.setColor(xx++, y, color);
Color color;
if (!dhgrMode && hiresMode) {
color = Palette.color[dhgrWord & 15];
} else {
color = Palette.color[FLIP_NYBBLE[dhgrWord & 15]];
}
writer.setColor(xOffset++, y, color);
writer.setColor(xOffset++, y, color);
writer.setColor(xOffset++, y, color);
writer.setColor(xOffset++, y, color);
dhgrWord >>= 4;
}
} catch (ArrayIndexOutOfBoundsException ex) {
@@ -652,11 +655,13 @@ public class VideoDHGR extends Video {
// Also, adding xOffset now makes it additionally 5% faster
//int yOffset = ((y << 4) + (y << 5) + (y << 9))+xOffset;
int xx = xOffset * 7;
int xx = xOffset;
PixelWriter writer = screen.getPixelWriter();
for (int i = 0; i < 28; i++) {
if (xx < 560) {
// yOffset++ is used instead of yOffset+i, because it is faster
writer.setColor(xx++, y, (dhgrWord & 1) == 1 ? WHITE : BLACK);
}
dhgrWord >>= 1;
}
}

View File

@@ -90,22 +90,24 @@ public class VideoNTSC extends VideoDHGR {
@Override
protected void displayLores(WritableImage screen, int xOffset, int y, int rowAddress) {
int data = ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
int pos = xOffset >> 1;
if (rowStart < 0) {
rowStart = pos;
}
colorActive[xOffset * 2] = colorActive[xOffset * 2 + 1] = true;
int data = ((RAM128k) computer.getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
colorActive[xOffset * 2] = true;
colorActive[xOffset * 2 + 1] = true;
if ((xOffset & 1) == 0) {
int pat = scanline[pos] & 0x0fffc000;
if ((y & 7) < 4) {
data &= 15;
} else {
data >>= 4;
}
int pat = data | data << 4 | data << 8 | (data & 3) << 12;
pat |= data | data << 4 | data << 8 | (data & 3) << 12;
scanline[pos] = pat;
} else {
int pat = scanline[pos];
int pat = scanline[pos] & 0x03fff;
if ((y & 7) < 4) {
data &= 15;
} else {

View File

@@ -2,9 +2,9 @@ package jace.assembly;
import jace.Emulator;
import jace.core.RAM;
import jace.ide.Program;
import jace.ide.CompileResult;
import jace.ide.LanguageHandler;
import jace.ide.Program;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -53,6 +53,8 @@ public class AssemblyHandler implements LanguageHandler<File> {
@Override
public void clean(CompileResult<File> lastResult) {
lastResult.getCompiledAsset().delete();
if (lastResult.getCompiledAsset() != null) {
lastResult.getCompiledAsset().delete();
}
}
}

View File

@@ -128,7 +128,8 @@ public abstract class Card extends Device {
firmwareListener = memory.observe(RAMEvent.TYPE.ANY, baseRom, baseRom + 255, (e) -> {
computer.getMemory().setActiveCard(slot);
if (SoftSwitches.CXROM.isOff()) {
// Sather 6-4: Writes will still go through even when CXROM inhibits slot ROM
if (SoftSwitches.CXROM.isOff() || !e.getType().isRead()) {
handleFirmwareAccess(e.getAddress() & 0x0ff, e.getType(), e.getNewValue(), e);
}
});

View File

@@ -39,6 +39,7 @@ import java.util.logging.Logger;
import javafx.event.EventHandler;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.WindowEvent;
/**
* Keyboard manages all keyboard-related activities. For now, all hotkeys are
@@ -50,6 +51,12 @@ import javafx.scene.input.KeyEvent;
*/
public class Keyboard implements Reconfigurable {
public void resetState() {
clearStrobe();
openApple(false);
solidApple(false);
}
private Computer computer;
public Keyboard(Computer computer) {
@@ -246,6 +253,9 @@ public class Keyboard implements Reconfigurable {
}
if (e.isControlDown()) {
if (c == 255) {
return;
}
c = (char) (c & 0x01f);
}

View File

@@ -335,4 +335,6 @@ public abstract class RAM implements Reconfigurable {
abstract public void attach();
abstract public void detach();
abstract public void performExtendedCommand(int i);
}

View File

@@ -156,18 +156,18 @@ public abstract class Video extends Device {
@Override
public void tick() {
setScannerLocation(currentWriter.getYOffset(y));
setFloatingBus(computer.getMemory().readRaw(scannerAddress + x));
if (hPeriod > 0) {
hPeriod--;
if (hPeriod <= 1) {
if (hPeriod == 0) {
x = -1;
setScannerLocation(currentWriter.getYOffset(y));
}
} else {
if (!isVblank && x < (APPLE_CYCLES_PER_LINE)) {
if (!isVblank && x < APPLE_CYCLES_PER_LINE) {
draw();
}
if (x >= APPLE_CYCLES_PER_LINE) {
if (x >= APPLE_CYCLES_PER_LINE - 1) {
int yy = y + hblankOffsetY;
if (yy < 0) {
yy += APPLE_SCREEN_LINES;
@@ -175,8 +175,7 @@ public abstract class Video extends Device {
if (yy >= APPLE_SCREEN_LINES) {
yy -= (TOTAL_LINES - APPLE_SCREEN_LINES);
}
setScannerLocation(currentWriter.getYOffset(yy) + hblankOffsetX + (yy < 64 ? 128 : 0));
x = -1;
x = hblankOffsetX - 1;
if (!isVblank) {
if (lineDirty) {
screenDirty = true;

View File

@@ -74,7 +74,7 @@ public class CardMockingboard extends Card implements Runnable {
public PSG[] chips;
// The 6522 controllr chips (always 2)
public R6522[] controllers;
static private int ticksBeteenPlayback = 200;
static private int ticksBetweenPlayback = 200;
Lock timerSync = new ReentrantLock();
Condition cpuCountReached = timerSync.newCondition();
Condition playbackFinished = timerSync.newCondition();
@@ -189,11 +189,11 @@ public class CardMockingboard extends Card implements Runnable {
timerSync.lock();
try {
ticksSinceLastPlayback++;
if (ticksSinceLastPlayback >= ticksBeteenPlayback) {
if (ticksSinceLastPlayback >= ticksBetweenPlayback) {
cpuCountReached.signalAll();
while (isRunning() && ticksSinceLastPlayback >= ticksBeteenPlayback) {
while (isRunning() && ticksSinceLastPlayback >= ticksBetweenPlayback) {
if (!playbackFinished.await(1, TimeUnit.SECONDS)) {
gripe("The mockingboard playback thread has stalled. Disabling mockingboard.");
// gripe("The mockingboard playback thread has stalled. Disabling mockingboard.");
suspend();
}
}
@@ -319,7 +319,8 @@ public class CardMockingboard extends Card implements Runnable {
System.out.println("Mockingboard playback started");
int bytesPerSample = frameSize / 2;
buildMixerTable();
ticksBeteenPlayback = (int) ((Motherboard.SPEED * BUFFER_LENGTH) / SAMPLE_RATE);
ticksBetweenPlayback = (int) ((Motherboard.SPEED * BUFFER_LENGTH) / SAMPLE_RATE);
System.out.println("Ticks between playback: "+ticksBetweenPlayback);
ticksSinceLastPlayback = 0;
int zeroSamples = 0;
setRun(true);
@@ -329,7 +330,6 @@ public class CardMockingboard extends Card implements Runnable {
Thread.currentThread().yield();
}
if (isRunning()) {
computer.getMotherboard().requestSpeed(this);
playSound(leftBuffer, rightBuffer);
int p = 0;
for (int idx = 0; idx < BUFFER_LENGTH; idx++) {
@@ -349,7 +349,7 @@ public class CardMockingboard extends Card implements Runnable {
}
try {
timerSync.lock();
ticksSinceLastPlayback -= ticksBeteenPlayback;
ticksSinceLastPlayback -= ticksBetweenPlayback;
} finally {
timerSync.unlock();
}
@@ -379,8 +379,10 @@ public class CardMockingboard extends Card implements Runnable {
try {
timerSync.lock();
playbackFinished.signalAll();
while (isRunning() && ticksSinceLastPlayback < ticksBeteenPlayback) {
while (isRunning() && ticksSinceLastPlayback < ticksBetweenPlayback) {
computer.getMotherboard().requestSpeed(this);
cpuCountReached.await();
computer.getMotherboard().cancelSpeedRequest(this);
}
} catch (InterruptedException ex) {
// Do nothing, probably killing playback thread on purpose

View File

@@ -167,16 +167,17 @@ public class FloppyDisk {
ByteArrayOutputStream output = new ByteArrayOutputStream();
for (int track = 0; track < TRACK_COUNT; track++) {
for (int sector = 0; sector < SECTOR_COUNT; sector++) {
int gap2 = (int) ((Math.random() * 5.0) + 4);
// 15 junk bytes
writeJunkBytes(output, 15);
// Address block
writeAddressBlock(output, track, sector);
// 4 junk bytes
writeJunkBytes(output, 4);
writeJunkBytes(output, gap2);
// Data block
nibblizeBlock(output, track, currentSectorOrder[sector], nibbles);
// 34 junk bytes
writeJunkBytes(output, 34);
writeJunkBytes(output, 38 - gap2);
}
}
return output.toByteArray();

View File

@@ -80,7 +80,7 @@ public abstract class ProdosDriver {
MOS65C02 cpu = (MOS65C02) computer.getCpu();
cpu.A = returnCode;
// Clear carry flag if no error, otherwise set carry flag
cpu.C = (returnCode == 0x00) ? 00 : 01;
cpu.C = (returnCode == 0x00) ? 00 : 01;
}
private MLI_RETURN prodosMLI() {

View File

@@ -18,38 +18,52 @@
*/
package jace.hardware.massStorage;
import static jace.hardware.massStorage.IDisk.BLOCK_SIZE;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Prodos directory node
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class DirectoryNode extends DiskNode implements FileFilter {
// public static int FILE_ENTRY_SIZE = 38;
public static int FILE_ENTRY_SIZE = 0x027;
public DirectoryNode(ProdosVirtualDisk ownerFilesystem, File physicalDir, int baseBlock) throws IOException {
setBaseBlock(baseBlock);
init(ownerFilesystem, physicalDir);
public static final byte STANDARD_PERMISSIONS = (byte) 0x0c3;
public static final int PRODOS_VERSION = 0x023;
public static final int FILE_ENTRY_SIZE = 0x027;
public static final int ENTRIES_PER_BLOCK = (ProdosVirtualDisk.BLOCK_SIZE - 4) / FILE_ENTRY_SIZE;
private boolean isRoot;
private List<DiskNode> directoryEntries;
public DirectoryNode(ProdosVirtualDisk ownerFilesystem, File physicalDir, int baseBlock, boolean root) throws IOException {
super(ownerFilesystem, baseBlock);
init(ownerFilesystem, physicalDir, root);
}
public DirectoryNode(ProdosVirtualDisk ownerFilesystem, File physicalDir) throws IOException {
init(ownerFilesystem, physicalDir);
public DirectoryNode(ProdosVirtualDisk ownerFilesystem, File physicalDir, boolean root) throws IOException {
super(ownerFilesystem);
init(ownerFilesystem, physicalDir, root);
}
private void init(ProdosVirtualDisk ownerFilesystem, File physicalFile) throws IOException {
private void init(ProdosVirtualDisk ownerFilesystem, File physicalFile, boolean root) throws IOException {
isRoot = root;
directoryEntries = new ArrayList<>();
setPhysicalFile(physicalFile);
setType(EntryType.SUBDIRECTORY);
setName(physicalFile.getName());
setOwnerFilesystem(ownerFilesystem);
allocate();
}
@Override
@@ -57,16 +71,16 @@ public class DirectoryNode extends DiskNode implements FileFilter {
}
@Override
public void doAllocate() {
File[] files = physicalFile.listFiles(this);
int numEntries = files.length;
int numBlocks = 1;
// First block has 12 entries, subsequent blocks have 13 entries
if (numEntries > 12) {
numBlocks += (numEntries - 12) / 13;
public void doAllocate() throws IOException {
for (int i = 1; i < getBlockCount(); i++) {
if (isRoot) {
new SubNode(i, this, getOwnerFilesystem().getNextFreeBlock(3));
} else {
new SubNode(i, this);
}
}
for (File f : files) {
for (File f : physicalFile.listFiles()) {
addFile(f);
}
Collections.sort(children, (DiskNode o1, DiskNode o2) -> o1.getName().compareTo(o2.getName()));
@@ -78,65 +92,79 @@ public class DirectoryNode extends DiskNode implements FileFilter {
@Override
/**
* Checks contents of subdirectory for changes as well as directory itself (super class)
* Checks contents of subdirectory for changes as well as directory itself
* (super class)
*/
public boolean checkFile() throws IOException {
boolean success = true;
if (!super.checkFile()) {
return false;
}
HashSet<String> realFiles = new HashSet<>();
File[] realFileList = physicalFile.listFiles(this);
for (File f : realFileList) {
realFiles.add(f.getName());
}
for (Iterator<DiskNode> i = getChildren().iterator(); i.hasNext(); ) {
DiskNode node = i.next();
if (realFiles.contains(node.getPhysicalFile().getName())) {
realFiles.remove(node.getPhysicalFile().getName());
} else {
i.remove();
success = false;
}
if (node.isAllocated()) {
if (!(node instanceof DirectoryNode) && !node.checkFile()) {
success = false;
if (!allocated) {
allocate();
} else {
try {
if (!super.checkFile()) {
return false;
}
HashSet<String> realFiles = new HashSet<>();
File[] realFileList = physicalFile.listFiles(this);
for (File f : realFileList) {
realFiles.add(f.getName());
}
for (Iterator<DiskNode> i = directoryEntries.iterator(); i.hasNext();) {
DiskNode node = i.next();
if (realFiles.contains(node.getPhysicalFile().getName())) {
realFiles.remove(node.getPhysicalFile().getName());
} else {
i.remove();
success = false;
}
if (node.isAllocated()) {
if (!(node instanceof DirectoryNode) && !node.checkFile()) {
success = false;
}
}
}
if (!realFiles.isEmpty()) {
success = false;
// New files showed up -- deal with them!
realFiles.stream().forEach((fileName) -> {
addFile(new File(physicalFile, fileName));
});
}
} catch (IOException ex) {
return false;
}
}
if (!realFiles.isEmpty()) {
success = false;
// New files showed up -- deal with them!
realFiles.stream().forEach((fileName) -> {
addFile(new File(physicalFile, fileName));
});
}
return success;
}
@Override
public void readBlock(int block, byte[] buffer) throws IOException {
checkFile();
int start = 0;
int end = 0;
int offset = 4;
generatePointers(buffer, block);
// System.out.println("Directory "+getName()+" sequence "+block+"; physical block "+getNodeSequence(block).getBaseBlock());
if (block == 0) {
generateHeader(buffer);
for (int i=0; i < 12 && i < children.size(); i++)
generateFileEntry(buffer, 4 + (i+1) * FILE_ENTRY_SIZE, i);
offset += FILE_ENTRY_SIZE;
end = ENTRIES_PER_BLOCK - 1;
} else {
int start = (block * 13) - 1;
int end = start + 13;
int offset = 4;
for (int i=start; i < end && i < children.size(); i++) {
// TODO: Add any parts that are not file entries.
generateFileEntry(buffer, offset, i);
offset += FILE_ENTRY_SIZE;
}
start = (block * ENTRIES_PER_BLOCK) - 1;
end = start + ENTRIES_PER_BLOCK;
}
for (int i = start; i < end && i < directoryEntries.size(); i++, offset += FILE_ENTRY_SIZE) {
// TODO: Add any parts that are not file entries.
// System.out.println("Entry "+i+": "+children.get(i).getName()+"; offset "+offset);
generateFileEntry(buffer, offset, i);
}
}
@Override
public boolean accept(File file) {
if (file.getName().endsWith("~")) return false;
if (file.getName().endsWith("~")) {
return false;
}
char c = file.getName().charAt(0);
if (c == '.' || c == '~') {
return false;
@@ -144,81 +172,99 @@ public class DirectoryNode extends DiskNode implements FileFilter {
return !file.isHidden();
}
private void generatePointers(byte[] buffer, int sequence) {
DiskNode prev = getNodeSequence(sequence - 1);
DiskNode next = getNodeSequence(sequence + 1);
// System.out.println("Sequence "+sequence+" prev="+(prev != null ? prev.getBaseBlock() : 0)+"; next="+(next != null ? next.getBaseBlock() : 0));
// Previous block (or 0)
generateWord(buffer, 0, prev != null ? prev.getBaseBlock() : 0);
// Next block (or 0)
generateWord(buffer, 0x02, next != null ? next.getBaseBlock() : 0);
}
/**
* Generate the directory header found in the base block of a directory
*
* @param buffer where to write data
*/
@SuppressWarnings("static-access")
private void generateHeader(byte[] buffer) {
// System.out.println("Generating directory header");
// Previous block = 0
generateWord(buffer, 0,0);
// Next block
int nextBlock = 0;
if (!additionalNodes.isEmpty())
nextBlock = additionalNodes.get(0).baseBlock;
generateWord(buffer, 0x02, nextBlock);
// Directory header + name length
// Volumme header = 0x0f0; Subdirectory header = 0x0e0
buffer[4]= (byte) ((baseBlock == 0x02 ? 0x0f0 : 0x0E0) + getName().length());
buffer[4] = (byte) ((isRoot ? 0x0F0 : 0x0E0) | getName().length());
generateName(buffer, 5, this);
for (int i=0x014 ; i <= 0x01b; i++)
buffer[i] = 0;
if (!isRoot) {
buffer[0x014] = 0x075;
buffer[0x015] = PRODOS_VERSION;
buffer[0x017] = STANDARD_PERMISSIONS;
buffer[0x018] = FILE_ENTRY_SIZE;
buffer[0x019] = ENTRIES_PER_BLOCK;
}
generateTimestamp(buffer, 0x01c, getPhysicalFile().lastModified());
// Prodos 1.9
buffer[0x020] = 0x019;
// Prodos 1.0 = 0
buffer[0x020] = PRODOS_VERSION;
// Minimum version = 0 (no min)
buffer[0x021] = 0x000;
// Directory may be read/written to, may not be destroyed or renamed
buffer[0x022] = 0x03;
buffer[0x022] = STANDARD_PERMISSIONS;
// Entry size
buffer[0x023] = (byte) FILE_ENTRY_SIZE;
// Entries per block
buffer[0x024] = (byte) 0x0d;
buffer[0x024] = (byte) ENTRIES_PER_BLOCK;
// Directory items count
generateWord(buffer, 0x025, children.size());
// Volume bitmap pointer
generateWord(buffer, 0x027, ownerFilesystem.freespaceBitmap.baseBlock);
// Total number of blocks
generateWord(buffer, 0x029, ownerFilesystem.MAX_BLOCK);
generateWord(buffer, 0x025, directoryEntries.size()+1);
if (isRoot) {
// Volume bitmap pointer
generateWord(buffer, 0x027, ownerFilesystem.freespaceBitmap.getBaseBlock());
// Total number of blocks
generateWord(buffer, 0x029, ownerFilesystem.MAX_BLOCK);
} else {
// According to the Beneath Apple Prodos supplement
int indexInParent = getParent().getChildren().indexOf(this) + 2;
int parentBlock = getParent().getNodeSequence(indexInParent / ENTRIES_PER_BLOCK).getBaseBlock();
// Parent pointer
generateWord(buffer, 0x027, parentBlock);
buffer[0x029] = (byte) (indexInParent % ENTRIES_PER_BLOCK);
buffer[0x02a] = (byte) FILE_ENTRY_SIZE;
}
}
/**
* Generate the entry of a directory
*
* @param buffer where to write data
* @param offset starting offset in buffer to write
* @param fileNumber number of file (indexed in Children array) to write
*/
private void generateFileEntry(byte[] buffer, int offset, int fileNumber) throws IOException {
// System.out.println("Generating entry for "+children.get(fileNumber).getName());
DiskNode child = children.get(fileNumber);
DiskNode child = directoryEntries.get(fileNumber);
// Entry Type and length
buffer[offset] = (byte) ((child.getType().code << 4) + child.getName().length());
buffer[offset] = (byte) ((child.getType().code << 4) | child.getName().length());
// Name
generateName(buffer, offset+1, child);
generateName(buffer, offset + 1, child);
// File type
buffer[offset + 0x010] = (byte) ((child instanceof DirectoryNode) ? 0x0f : ((FileNode) child).fileType);
// Key pointer
generateWord(buffer, offset + 0x011, child.getBaseBlock());
// Blocks used -- will report only one unless file is actually allocated
// child.allocate();
generateWord(buffer, offset + 0x013, 1 + child.additionalNodes.size());
// EOF
// TODO: Verify this is the right thing to do -- is EOF total length or a modulo?
int length = ((int) child.physicalFile.length()) & 0x0ffffff;
generateWord(buffer, offset + 0x013, child.additionalNodes.size() + 1);
// EOF (file size or directory structure size
int length = child.getLength();
length &= 0x0ffffff;
generateWord(buffer, offset + 0x015, length & 0x0ffff);
buffer[offset + 0x017] = (byte) ((length >> 16) & 0x0ff);
// Creation date
generateTimestamp(buffer, offset + 0x018, child.physicalFile.lastModified());
// Version = 1.9
buffer[offset + 0x01c] = 0x19;
// Version = 1.0
buffer[offset + 0x01c] = PRODOS_VERSION;
// Minimum version = 0
buffer[offset + 0x01d] = 0;
// Access = all granted
buffer[offset + 0x01e] = (byte) 0x0ff;
// Access = Read-only
buffer[offset + 0x01e] = STANDARD_PERMISSIONS;
// AUX type
if (child instanceof FileNode)
if (child instanceof FileNode) {
generateWord(buffer, offset + 0x01f, ((FileNode) child).loadAddress);
}
// Modification date
generateTimestamp(buffer, offset + 0x021, child.physicalFile.lastModified());
// Key pointer for directory
@@ -231,38 +277,53 @@ public class DirectoryNode extends DiskNode implements FileFilter {
// yyyyyyym mmmddddd - Byte 0,1
// ---hhhhh --mmmmmm - Byte 2,3
// buffer[offset+1] = (byte) (((c.get(Calendar.YEAR) - 1990) << 1) + ((c.get(Calendar.MONTH)>> 3) & 1));
buffer[offset+0] = 0;
buffer[offset+1] = 0;
buffer[offset+2] = 0;
buffer[offset+3] = 0;
// buffer[offset+2] = (byte) ((c.get(Calendar.MONTH)>> 3) & 1);
// buffer[offset+3] = (byte) (((c.get(Calendar.MONTH)&7) + c.get(Calendar.DAY_OF_MONTH)) & 0x0ff);
// buffer[offset+0] = (byte) c.get(Calendar.HOUR_OF_DAY);
// buffer[offset+1] = (byte) c.get(Calendar.MINUTE);
buffer[offset + 0] = (byte) (((((c.get(Calendar.MONTH) + 1) & 7) << 5) | c.get(Calendar.DAY_OF_MONTH)) & 0x0ff);
buffer[offset + 1] = (byte) (((c.get(Calendar.YEAR) - 2000) << 1) | ((c.get(Calendar.MONTH) + 1) >> 3));
buffer[offset + 2] = (byte) c.get(Calendar.MINUTE);
buffer[offset + 3] = (byte) c.get(Calendar.HOUR_OF_DAY);
}
private void generateWord(byte[] buffer, int i, int value) {
// Little endian format
buffer[i] = (byte) (value & 0x0ff);
buffer[i+1] = (byte) ((value >> 8) & 0x0ff);
buffer[i + 1] = (byte) ((value >> 8) & 0x0ff);
}
private void generateName(byte[] buffer, int offset, DiskNode node) {
for (int i=0; i < node.getName().length(); i++) {
buffer[offset+i] = (byte) node.getName().charAt(i);
for (int i = 0; i < node.getName().length() && i < 15; i++) {
buffer[offset + i] = (byte) node.getName().charAt(i);
}
}
private Optional<DiskNode> findChildByFilename(String name) {
return directoryEntries.stream().filter((child) -> child.getPhysicalFile().getName().equals(name)).findFirst();
}
private void addFile(File file) {
if (!hasChildNamed(file.getName())) {
try {
if (file.isDirectory()) {
addFileEntry(new DirectoryNode(getOwnerFilesystem(), file, false));
} else {
addFileEntry(new FileNode(getOwnerFilesystem(), file));
}
} catch (IOException ex) {
Logger.getLogger(DirectoryNode.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private void addFileEntry(DiskNode entry) {
directoryEntries.add(entry);
entry.setParent(this);
}
private void addFile(File file) {
try {
if (file.isDirectory()) {
addChild(new DirectoryNode(getOwnerFilesystem(), file));
} else {
addChild(new FileNode(getOwnerFilesystem(), file));
}
} catch (IOException ex) {
Logger.getLogger(DirectoryNode.class.getName()).log(Level.SEVERE, null, ex);
}
@Override
public int getLength() {
return getBlockCount() * BLOCK_SIZE;
}
private int getBlockCount() {
return isRoot ? 4 : 1 + (physicalFile.listFiles().length / ENTRIES_PER_BLOCK);
}
}

View File

@@ -22,6 +22,7 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Prodos file/directory node abstraction. This provides a lot of the glue for
@@ -33,7 +34,6 @@ import java.util.List;
public abstract class DiskNode {
public enum EntryType {
DELETED(0),
SEEDLING(1),
SAPLING(2),
@@ -50,7 +50,7 @@ public abstract class DiskNode {
boolean allocated = false;
long allocationTime = -1L;
long lastCheckTime = -1L;
int baseBlock = -1;
private int baseBlock = -1;
List<DiskNode> additionalNodes;
ProdosVirtualDisk ownerFilesystem;
File physicalFile;
@@ -59,13 +59,23 @@ public abstract class DiskNode {
private EntryType type;
private String name;
public DiskNode() {
public DiskNode(ProdosVirtualDisk fs) throws IOException {
init(fs);
fs.allocateEntry(this);
}
public DiskNode(ProdosVirtualDisk fs, int blockNumber) throws IOException {
init(fs);
fs.allocateEntryNear(this, blockNumber);
}
private void init(ProdosVirtualDisk fs) throws IOException {
additionalNodes = new ArrayList<>();
children = new ArrayList<>();
setOwnerFilesystem(fs);
}
public boolean checkFile() throws IOException {
allocate();
if (physicalFile == null) {
return false;
}
@@ -82,7 +92,6 @@ public abstract class DiskNode {
doAllocate();
allocationTime = System.currentTimeMillis();
allocated = true;
ownerFilesystem.allocateEntry(this);
}
}
@@ -100,14 +109,22 @@ public abstract class DiskNode {
}
}
public void refresh() {
ownerFilesystem.deallocateEntry(this);
public void refresh() throws IOException {
deallocate();
doRefresh();
allocationTime = System.currentTimeMillis();
allocated = true;
ownerFilesystem.allocateEntry(this);
allocate();
}
public DiskNode getNodeSequence(int num) {
if (num == 0) {
return this;
} else if (num > 0 && num <= additionalNodes.size()) {
return additionalNodes.get(num-1);
} else {
return null;
}
}
/**
* @return the allocated
*/
@@ -154,12 +171,8 @@ public abstract class DiskNode {
* @param ownerFilesystem the ownerFilesystem to set
* @throws IOException
*/
public void setOwnerFilesystem(ProdosVirtualDisk ownerFilesystem) throws IOException {
private void setOwnerFilesystem(ProdosVirtualDisk ownerFilesystem) throws IOException {
this.ownerFilesystem = ownerFilesystem;
if (baseBlock == -1) {
setBaseBlock(ownerFilesystem.getNextFreeBlock());
}
ownerFilesystem.allocateEntry(this);
}
/**
@@ -175,6 +188,7 @@ public abstract class DiskNode {
public void setPhysicalFile(File physicalFile) {
this.physicalFile = physicalFile;
setName(physicalFile.getName());
lastCheckTime = physicalFile.lastModified();
}
/**
@@ -206,12 +220,21 @@ public abstract class DiskNode {
}
public void addChild(DiskNode child) {
child.setParent(this);
children.add(child);
}
public void removeChild(DiskNode child) {
children.remove(child);
}
public boolean hasChildNamed(String name) {
return findChildByFilename(name).isPresent();
}
private Optional<DiskNode> findChildByFilename(String name) {
return getChildren().stream().filter((child) -> child.getPhysicalFile().getName().equals(name)).findFirst();
}
/**
* @return the type
@@ -238,10 +261,7 @@ public abstract class DiskNode {
* @param name the name to set
*/
public void setName(String name) {
if (name.length() > 15) {
name = name.substring(0, 15);
}
this.name = name.toUpperCase();
this.name = (name.length() > 15 ? name.substring(0, 15) : name).toUpperCase();
}
public abstract void doDeallocate();
@@ -251,6 +271,8 @@ public abstract class DiskNode {
public abstract void doRefresh();
public abstract void readBlock(int sequence, byte[] buffer) throws IOException;
public abstract int getLength();
public void readBlock(byte[] buffer) throws IOException {
checkFile();

View File

@@ -26,10 +26,15 @@ import java.io.IOException;
* Representation of a prodos file with a known file type and having a known
* size (either seedling, sapling or tree)
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class FileNode extends DiskNode {
@Override
public int getLength() {
return (int) getPhysicalFile().length();
}
public enum FileType {
UNKNOWN(0x00, 0x0000),
@@ -66,6 +71,15 @@ public class FileNode extends DiskNode {
this.code = code;
this.defaultLoadAddress = addr;
}
public static FileType findByCode(int code) {
for (FileType t : FileType.values()) {
if (t.code == code) {
return t;
}
}
return UNKNOWN;
}
}
public int fileType = 0x00;
public int loadAddress = 0x00;
@@ -81,38 +95,44 @@ public class FileNode extends DiskNode {
} else if (fileSize <= SAPLING_MAX_SIZE) {
setType(EntryType.SAPLING);
return EntryType.SAPLING;
} else {
setType(EntryType.TREE);
return EntryType.TREE;
}
setType(EntryType.TREE);
return EntryType.TREE;
}
@Override
public void setName(String name) {
String[] parts = name.split("\\.");
FileType t = null;
FileType t = FileType.UNKNOWN;
int offset = 0;
if (parts.length > 1) {
String extension = parts[parts.length - 1].toUpperCase();
String[] extParts = extension.split("#");
if (extParts.length == 2) {
offset = Integer.parseInt(extParts[1], 16);
extension = extParts[0];
String prodosName = name;
if (name.matches("^.*?#[0-9A-Fa-f]{6}$")) {
int type = Integer.parseInt(name.substring(name.length() - 6, name.length() - 4), 16);
offset = Integer.parseInt(name.substring(name.length() - 4), 16);
t = FileType.findByCode(type);
prodosName = name.substring(0, name.length()-7).replaceAll("[^A-Za-z0-9#]", ".").toUpperCase();
} else {
String[] parts = name.replaceAll("[^A-Za-z0-9#]", ".").split("\\.");
if (parts.length > 1) {
String extension = parts[parts.length - 1].toUpperCase();
String[] extParts = extension.split("\\#");
if (extParts.length == 2) {
offset = Integer.parseInt(extParts[1], 16);
extension = extParts[0];
}
try {
t = FileType.valueOf(extension);
} catch (IllegalArgumentException ex) {
System.out.println("Not sure what extension " + extension + " is!");
}
prodosName = "";
for (int i = 0; i < parts.length - 1; i++) {
prodosName += (i > 0 ? "." + parts[i] : parts[i]);
}
if (extParts[extParts.length - 1].equals("SYSTEM")) {
prodosName += ".SYSTEM";
}
}
try {
t = FileType.valueOf(extension);
} catch (IllegalArgumentException ex) {
System.out.println("Not sure what extension " + extension + " is!");
}
name = "";
for (int i = 0; i < parts.length - 1; i++) {
name += (i > 0 ? "." + parts[i] : parts[i]);
}
if (extParts[extParts.length - 1].equals("SYSTEM")) {
name += ".SYSTEM";
}
}
if (t == null) {
t = FileType.UNKNOWN;
}
if (offset == 0) {
offset = t.defaultLoadAddress;
@@ -121,12 +141,14 @@ public class FileNode extends DiskNode {
loadAddress = offset;
// Pass usable name (stripped of file extension and other type info) as name
super.setName(name);
super.setName(prodosName);
}
public FileNode(ProdosVirtualDisk ownerFilesystem, File file) throws IOException {
setOwnerFilesystem(ownerFilesystem);
super(ownerFilesystem);
setPhysicalFile(file);
setName(file.getName());
allocate();
}
@Override
@@ -135,15 +157,13 @@ public class FileNode extends DiskNode {
@Override
public void doAllocate() throws IOException {
int dataBlocks = (int) ((getPhysicalFile().length() / ProdosVirtualDisk.BLOCK_SIZE) + 1);
int treeBlocks;
if (dataBlocks > 1 && dataBlocks < 257) {
treeBlocks = 1;
} else {
treeBlocks = 1 + (dataBlocks / 256);
int dataBlocks = (int) ((getPhysicalFile().length() + ProdosVirtualDisk.BLOCK_SIZE - 1) / ProdosVirtualDisk.BLOCK_SIZE);
int treeBlocks = (((dataBlocks * 2) + (ProdosVirtualDisk.BLOCK_SIZE - 2)) / ProdosVirtualDisk.BLOCK_SIZE);
if (treeBlocks > 1) {
treeBlocks++;
}
for (int i = 1; i < dataBlocks + treeBlocks; i++) {
SubNode subNode = new SubNode(i, this);
new SubNode(i, this);
}
}
@@ -153,7 +173,12 @@ public class FileNode extends DiskNode {
@Override
public void readBlock(int block, byte[] buffer) throws IOException {
// System.out.println("Read block "+block+" of file "+getName());
allocate();
int dataBlocks = (int) ((getPhysicalFile().length() + ProdosVirtualDisk.BLOCK_SIZE - 1) / ProdosVirtualDisk.BLOCK_SIZE);
int treeBlocks = (((dataBlocks * 2) + (ProdosVirtualDisk.BLOCK_SIZE - 2)) / ProdosVirtualDisk.BLOCK_SIZE);
if (treeBlocks > 1) {
treeBlocks++;
}
switch (this.getType()) {
case SEEDLING:
readFile(buffer, 0);
@@ -163,20 +188,18 @@ public class FileNode extends DiskNode {
readFile(buffer, (block - 1));
} else {
// Generate seedling index block
generateIndex(buffer, 0, 256);
generateIndex(buffer, 1, dataBlocks + 1);
}
break;
case TREE:
int dataBlocks = (int) ((getPhysicalFile().length() / ProdosVirtualDisk.BLOCK_SIZE) + 1);
int treeBlocks = (dataBlocks / 256);
if (block == 0) {
generateIndex(buffer, 0, treeBlocks);
} else if (block < treeBlocks) {
int start = treeBlocks + (block - 1 * 256);
int end = Math.min(start + 256, treeBlocks);
generateIndex(buffer, treeBlocks, end);
generateIndex(buffer, 1, treeBlocks);
} else if (block <= treeBlocks) {
int start = treeBlocks + ((block - 1) * 256);
int end = treeBlocks + dataBlocks;
generateIndex(buffer, start, end);
} else {
readFile(buffer, (block - treeBlocks));
readFile(buffer, (block - treeBlocks - 1));
}
break;
}
@@ -190,10 +213,10 @@ public class FileNode extends DiskNode {
}
private void generateIndex(byte[] buffer, int indexStart, int indexLimit) {
int pos = 0;
for (int i = indexStart; pos < 256 && i < indexLimit && i < additionalNodes.size(); i++, pos++) {
buffer[pos] = (byte) (additionalNodes.get(i).baseBlock & 0x0ff);
buffer[pos + 256] = (byte) ((additionalNodes.get(i).baseBlock >> 8) & 0x0ff);
for (int i = indexStart, count = 0; count < 256 && i < indexLimit && i <= additionalNodes.size(); i++, count++) {
int base = getNodeSequence(i).getBaseBlock();
buffer[count] = (byte) (base & 0x0ff);
buffer[count + 256] = (byte) (base >> 8);
}
}
}

View File

@@ -22,26 +22,28 @@ import java.io.IOException;
/**
* Maintain freespace and node allocation
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class FreespaceBitmap extends DiskNode {
int size = (ProdosVirtualDisk.MAX_BLOCK + 1) / 8 / ProdosVirtualDisk.BLOCK_SIZE;
public FreespaceBitmap(ProdosVirtualDisk fs, int start) throws IOException {
setBaseBlock(start);
setOwnerFilesystem(fs);
for (int i=1; i < size; i++) {
SubNode subNode = new SubNode(i, this, start+i);
}
int size = (ProdosVirtualDisk.MAX_BLOCK + 1) / 8 / ProdosVirtualDisk.BLOCK_SIZE;
public FreespaceBitmap(ProdosVirtualDisk fs, int start) throws IOException {
super(fs, start);
allocate();
}
@Override
public void doDeallocate() {
//
}
@Override
public void doAllocate() {
///
public void doAllocate() throws IOException {
for (int i = 1; i < size; i++) {
SubNode subNode = new SubNode(i, this, getBaseBlock());
}
}
@Override
@@ -52,22 +54,18 @@ public class FreespaceBitmap extends DiskNode {
@Override
public void readBlock(int sequence, byte[] buffer) throws IOException {
int startBlock = sequence * ProdosVirtualDisk.BLOCK_SIZE * 8;
int endBlock = (sequence+1)* ProdosVirtualDisk.BLOCK_SIZE * 8;
int bitCounter=0;
int pos=0;
int value=0;
for (int i=startBlock; i < endBlock; i++) {
if (!getOwnerFilesystem().isAllocated(i)) {
value++;
}
bitCounter++;
if (bitCounter < 8) {
value *= 2;
} else {
bitCounter = 0;
buffer[pos++]=(byte) value;
value = 0;
int endBlock = (sequence + 1) * ProdosVirtualDisk.BLOCK_SIZE * 8;
for (int i = startBlock; i < endBlock; i++) {
if (!getOwnerFilesystem().isBlockAllocated(i)) {
int pos = (i - startBlock) / 8;
int bit = 1 << (i % 8);
buffer[pos] |= bit;
}
}
}
}
@Override
public int getLength() {
return (1 + getChildren().size()) * IDisk.BLOCK_SIZE;
}
}

View File

@@ -28,7 +28,7 @@ import java.io.IOException;
*/
public interface IDisk {
public static int BLOCK_SIZE = 512;
public static int MAX_BLOCK = 65535;
public static int MAX_BLOCK = 0x07fff;
public void mliFormat() throws IOException;
public void mliRead(int block, int bufferAddress, RAM memory) throws IOException;

View File

@@ -18,6 +18,7 @@
*/
package jace.hardware.massStorage;
import jace.Emulator;
import jace.EmulatorUILogic;
import jace.apple2e.MOS65C02;
import jace.core.Computer;
@@ -27,8 +28,6 @@ import jace.hardware.ProdosDriver.MLI_COMMAND_TYPE;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -38,49 +37,50 @@ import java.util.logging.Logger;
* is a folder and not a disk image. FreespaceBitmap and the various Node
* classes are used to represent the filesystem structure.
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class ProdosVirtualDisk implements IDisk {
public static int VOLUME_START = 2;
public static int FREESPACE_BITMAP_START = 6;
public static final int VOLUME_START = 2;
public static final int FREESPACE_BITMAP_START = 6;
byte[] ioBuffer;
File physicalRoot;
Map<Integer, DiskNode> physicalMap;
private final DiskNode[] physicalMap;
DirectoryNode rootDirectory;
FreespaceBitmap freespaceBitmap;
public ProdosVirtualDisk(File rootPath) throws IOException {
ioBuffer = new byte[BLOCK_SIZE];
physicalMap = new DiskNode[MAX_BLOCK];
initDiskStructure();
setPhysicalPath(rootPath);
}
@Override
public void mliRead(int block, int bufferAddress, RAM memory) throws IOException {
// System.out.println("Read block " + block + " to " + Integer.toHexString(bufferAddress));
DiskNode node = physicalMap.get(block);
Arrays.fill(ioBuffer, (byte) (block & 0x0ff));
DiskNode node = physicalMap[block];
Arrays.fill(ioBuffer, (byte) 0);
if (node == null) {
System.out.println("Reading unknown block?!");
for (int i = 0; i < BLOCK_SIZE; i++) {
memory.write(bufferAddress + i, (byte) 0, false, false);
}
System.out.println("Unknown block " + Integer.toHexString(block));
} else {
// if (node.getPhysicalFile() == null) {
// System.out.println("reading block "+block+ " from directory structure to "+Integer.toHexString(bufferAddress));
// } else {
// System.out.println("reading block "+block+ " from "+node.getPhysicalFile().getName()+" to "+Integer.toHexString(bufferAddress));
// }
node.readBlock(ioBuffer);
for (int i = 0; i < BLOCK_SIZE; i++) {
memory.write(bufferAddress + i, ioBuffer[i], false, false);
}
}
// for (int i=0; i < 512; i++) {
// if (i % 32 == 0 && i > 0) System.out.println();
// System.out.print(((ioBuffer[i]&0x0ff)<16 ? "0" : "") + Integer.toHexString(ioBuffer[i] & 0x0ff) + " ");
for (int i = 0; i < ioBuffer.length; i++) {
memory.write(bufferAddress + i, ioBuffer[i], false, false);
}
// System.out.println("Block " + Integer.toHexString(block));
// for (int i = 0; i < 32; i++) {
// String hex = "";
// String text = "";
// for (int j = 0; j < 16; j++) {
// int val = 0x0ff & memory.readRaw(bufferAddress + i * 16 + j);
// char show = (char) (((val & 0x7f) < ' ') ? '.' : val & 0x7f);
// hex += (val < 16 ? "0" : "") + Integer.toString(val, 16) + " ";
// text += show;
// }
// System.out.println(hex + " " + text);
// }
// System.out.println();
}
@Override
@@ -120,41 +120,51 @@ public class ProdosVirtualDisk implements IDisk {
return mostLikelyMatch;
}
public int getNextFreeBlock() throws IOException {
public int getNextFreeBlock(int start) throws IOException {
// Don't allocate Zero block for anything!
// for (int i = 0; i < MAX_BLOCK; i++) {
for (int i = 2; i < MAX_BLOCK; i++) {
if (!physicalMap.containsKey(i)) {
for (int i = start; i < MAX_BLOCK; i++) {
if (physicalMap[i] == null) {
return i;
}
}
throw new IOException("Virtual Disk Full!");
}
// Mark space occupied by node
public void allocateEntry(DiskNode node) {
physicalMap.put(node.baseBlock, node);
node.additionalNodes.stream().forEach((sub) -> {
physicalMap.put(sub.getBaseBlock(), sub);
});
public int allocateEntry(DiskNode node) throws IOException {
return allocateEntryNear(node, FREESPACE_BITMAP_START);
}
public int allocateEntryNear(DiskNode node, int start) throws IOException {
if (isNodeAllocated(node)) {
return node.getBaseBlock();
}
int block = getNextFreeBlock(start);
node.setBaseBlock(block);
physicalMap[block] = node;
return block;
}
public boolean isNodeAllocated(DiskNode node) {
return node.getBaseBlock() >= 0 && physicalMap[node.getBaseBlock()] == node;
}
// Mark space occupied by nodes as free (remove allocation mapping)
public void deallocateEntry(DiskNode node) {
// Only de-map nodes if the allocation table is actually pointing to the nodes!
if (physicalMap.get(node.baseBlock) != null && physicalMap.get(node.baseBlock).equals(node)) {
physicalMap.remove(node.baseBlock);
if (physicalMap[node.getBaseBlock()] != null && physicalMap[node.getBaseBlock()].equals(node)) {
physicalMap[node.getBaseBlock()] = null;
}
node.additionalNodes.stream().filter((sub) ->
(physicalMap.get(sub.getBaseBlock()) != null && physicalMap.get(sub.baseBlock).equals(sub))).
node.additionalNodes.stream().filter((sub)
-> (physicalMap[sub.getBaseBlock()] != null && physicalMap[sub.getBaseBlock()].equals(sub))).
forEach((sub) -> {
physicalMap.remove(sub.getBaseBlock());
});
physicalMap[sub.getBaseBlock()] = null;
});
}
// Is the specified block in use?
public boolean isAllocated(int i) {
return (physicalMap.containsKey(i));
public boolean isBlockAllocated(int i) {
return (i >= physicalMap.length || physicalMap[i] != null);
}
@Override
@@ -180,12 +190,15 @@ public class ProdosVirtualDisk implements IDisk {
return physicalRoot;
}
public void setPhysicalPath(File f) throws IOException {
private void initDiskStructure() throws IOException {
freespaceBitmap = new FreespaceBitmap(this, FREESPACE_BITMAP_START);
}
private void setPhysicalPath(File f) throws IOException {
if (physicalRoot != null && physicalRoot.equals(f)) {
return;
}
physicalRoot = f;
physicalMap = new HashMap<>();
if (!physicalRoot.exists() || !physicalRoot.isDirectory()) {
try {
throw new IOException("Root path must be a directory that exists!");
@@ -194,13 +207,8 @@ public class ProdosVirtualDisk implements IDisk {
}
}
// Root directory ALWAYS starts on block 2!
rootDirectory = new DirectoryNode(this, physicalRoot, VOLUME_START);
rootDirectory = new DirectoryNode(this, physicalRoot, VOLUME_START, true);
rootDirectory.setName("VIRTUAL");
allocateEntry(rootDirectory);
freespaceBitmap = new FreespaceBitmap(this, FREESPACE_BITMAP_START);
allocateEntry(freespaceBitmap);
}
@Override
@@ -215,6 +223,6 @@ public class ProdosVirtualDisk implements IDisk {
@Override
public int getSize() {
return 0x0ffff;
return MAX_BLOCK;
}
}

View File

@@ -31,24 +31,28 @@ import java.io.IOException;
public class SubNode extends DiskNode {
int sequenceNumber;
private int seq;
public SubNode(int seq, DiskNode parent) throws IOException {
super(parent.getOwnerFilesystem());
init(seq, parent);
}
public SubNode(int seq, DiskNode parent, int baseBlock) throws IOException {
setBaseBlock(baseBlock);
super(parent.getOwnerFilesystem(), baseBlock);
init(seq, parent);
}
private void init(int seq, DiskNode parent) throws IOException {
sequenceNumber = seq;
setParent(parent);
setOwnerFilesystem(parent.getOwnerFilesystem());
parent.additionalNodes.add(this);
}
@Override
public String getName() {
return parent.getName() + "; block "+sequenceNumber;
}
@Override
public void doDeallocate() {
}
@@ -65,4 +69,9 @@ public class SubNode extends DiskNode {
public void readBlock(int sequence, byte[] buffer) throws IOException {
parent.readBlock(sequenceNumber, buffer);
}
@Override
public int getLength() {
return IDisk.BLOCK_SIZE;
}
}

View File

@@ -61,7 +61,6 @@ public class SoundGenerator extends TimedGenerator {
double amp = stateChanges == 0 ? 1 : 1.0 / Math.max(stateChanges-1, 1);
int vol = useEnvGen ? envGen.getEffectiveAmplitude() : amplitude;
boolean on = noiseActive && noiseGen.isOn() || (active && inverted);
// return invert ? -CardMockingboard.VolTable[vol] : CardMockingboard.VolTable[vol];
return on ? (int) (CardMockingboard.VolTable[vol] * amp) : 0;
}

View File

@@ -14,6 +14,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.concurrent.Worker;
import javafx.scene.control.TextInputDialog;
import javafx.scene.web.PromptData;
@@ -116,7 +117,7 @@ public class Program {
if (newState == Worker.State.SUCCEEDED) {
JSObject document = (JSObject) editor.getEngine().executeScript("window");
document.setMember("java", this);
createEditor();
Platform.runLater(this::createEditor);
}
});
@@ -136,7 +137,8 @@ public class Program {
String optionString = buildOptions();
editor.getEngine().executeScript("var codeMirror = CodeMirror(document.body, " + optionString + ");");
codeMirror = (JSObject) editor.getEngine().executeScript("codeMirror");
setValue(document);
Platform.runLater(() -> setValue(document));
// setValue(document);
}
public String getFileContents(File sourceFile) {
@@ -165,7 +167,6 @@ public class Program {
return "";
}
public void save(File newTarget) {
FileWriter writer = null;
if (newTarget == null && targetFile == null) {
@@ -232,18 +233,18 @@ public class Program {
private void manageCompileResult(CompileResult lastResult) {
editor.getEngine().executeScript("clearHighlights()");
lastResult.getWarnings().forEach((line,message) ->
editor.getEngine().executeScript("highlightLine("+line+",false,\""+escapeString(message)+"\");")
lastResult.getWarnings().forEach((line, message)
-> editor.getEngine().executeScript("highlightLine(" + line + ",false,\"" + escapeString(message) + "\");")
);
lastResult.getErrors().forEach((line,message) ->
editor.getEngine().executeScript("highlightLine("+line+",true,\""+escapeString(message)+"\");")
lastResult.getErrors().forEach((line, message)
-> editor.getEngine().executeScript("highlightLine(" + line + ",true,\"" + escapeString(message) + "\");")
);
}
}
private String escapeString(Object message) {
return String.valueOf(message).replaceAll("\\\"", "&quot;");
}
public void log(String message) {
System.out.println(message);
}

View File

@@ -49,7 +49,7 @@ public enum DiskType {
}
static public DiskType determineType(File file) {
if (!file.exists()) return null;
if (file == null || !file.exists()) return null;
if (file.isDirectory()) return VIRTUAL;
if (file.getName().toLowerCase().endsWith("hdv")) {
return LARGE;

Binary file not shown.