forked from Apple-2-Tools/jace
Compare commits
32 Commits
v2.0b
...
6502-timin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd6b2b8566 | ||
|
|
af1bdb6b35 | ||
|
|
97b367ccce | ||
|
|
d5d2424915 | ||
|
|
61356782b6 | ||
|
|
b9fcc6d82d | ||
|
|
8064a804eb | ||
|
|
fa7cab9866 | ||
|
|
9a6737e8f0 | ||
|
|
07c71f0117 | ||
|
|
24f6f7e23f | ||
|
|
bffe4eba22 | ||
|
|
34ef28be1f | ||
|
|
b5c7418b84 | ||
|
|
1548c1b327 | ||
|
|
4c8e809b46 | ||
|
|
676256b6fc | ||
|
|
292b84d1e9 | ||
|
|
c261f0f103 | ||
|
|
ca6a831020 | ||
|
|
732f4768a6 | ||
|
|
5f9352abb3 | ||
|
|
8bcf3a922a | ||
|
|
59ab31f433 | ||
|
|
6c4136841e | ||
|
|
17b8183a31 | ||
|
|
d03c1d9333 | ||
|
|
893052b004 | ||
|
|
9f838d11f1 | ||
|
|
633b514b38 | ||
|
|
4021af3ac6 | ||
|
|
cf87f30e35 |
19
README.md
19
README.md
@@ -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
101
build.sh
Executable 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
30
pom.xml
@@ -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
9
run.sh
Executable 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
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -335,4 +335,6 @@ public abstract class RAM implements Reconfigurable {
|
||||
abstract public void attach();
|
||||
|
||||
abstract public void detach();
|
||||
|
||||
abstract public void performExtendedCommand(int i);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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("\\\"", """);
|
||||
}
|
||||
|
||||
|
||||
public void log(String message) {
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
BIN
target/jace-2.0-SNAPSHOT.jar
Normal file
BIN
target/jace-2.0-SNAPSHOT.jar
Normal file
Binary file not shown.
Reference in New Issue
Block a user