forked from Apple-2-Tools/jace
Initial version: Based on last source available in SourceForge, converted to a Maven project format
This commit is contained in:
parent
c41c87c17a
commit
6341001743
|
@ -10,3 +10,5 @@
|
|||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
/jace/nbproject/private/
|
||||
/jace/target/
|
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
X-COMMENT: Main-Class will be added automatically by build
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<actions>
|
||||
<action>
|
||||
<actionName>run</actionName>
|
||||
<packagings>
|
||||
<packaging>jar</packaging>
|
||||
</packagings>
|
||||
<goals>
|
||||
<goal>-X</goal>
|
||||
<goal>-e</goal>
|
||||
<goal>process-classes</goal>
|
||||
<goal>org.codehaus.mojo:exec-maven-plugin:1.2.1:exec</goal>
|
||||
</goals>
|
||||
<properties>
|
||||
<exec.args>-classpath target/jace-2.0-SNAPSHOT.jar jace.Emulator</exec.args>
|
||||
<exec.executable>java</exec.executable>
|
||||
</properties>
|
||||
</action>
|
||||
<action>
|
||||
<actionName>debug</actionName>
|
||||
<packagings>
|
||||
<packaging>jar</packaging>
|
||||
</packagings>
|
||||
<goals>
|
||||
<goal>process-classes</goal>
|
||||
<goal>org.codehaus.mojo:exec-maven-plugin:1.2.1:exec</goal>
|
||||
</goals>
|
||||
<properties>
|
||||
<exec.args>-Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} -classpath %classpath jace.Emulator</exec.args>
|
||||
<exec.executable>java</exec.executable>
|
||||
<jpda.listen>true</jpda.listen>
|
||||
</properties>
|
||||
</action>
|
||||
<action>
|
||||
<actionName>profile</actionName>
|
||||
<packagings>
|
||||
<packaging>jar</packaging>
|
||||
</packagings>
|
||||
<goals>
|
||||
<goal>process-classes</goal>
|
||||
<goal>org.codehaus.mojo:exec-maven-plugin:1.2.1:exec</goal>
|
||||
</goals>
|
||||
<properties>
|
||||
<exec.args>-classpath %classpath jace.Emulator</exec.args>
|
||||
<exec.executable>java</exec.executable>
|
||||
</properties>
|
||||
</action>
|
||||
</actions>
|
|
@ -0,0 +1,108 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.badvision</groupId>
|
||||
<artifactId>jace</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>jace</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<mainClass>jace.MainApp</mainClass>
|
||||
</properties>
|
||||
|
||||
<organization>
|
||||
<!-- Used as the 'Vendor' for JNLP generation -->
|
||||
<name>Your Organisation</name>
|
||||
</organization>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>unpack-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<excludeScope>system</excludeScope>
|
||||
<excludeGroupIds>junit,org.mockito,org.hamcrest</excludeGroupIds>
|
||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack-dependencies</id>
|
||||
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>${java.home}/../bin/javafxpackager</executable>
|
||||
<arguments>
|
||||
<argument>-createjar</argument>
|
||||
<argument>-nocss2bin</argument>
|
||||
<argument>-appclass</argument>
|
||||
<argument>${mainClass}</argument>
|
||||
<argument>-srcdir</argument>
|
||||
<argument>${project.build.directory}/classes</argument>
|
||||
<argument>-outdir</argument>
|
||||
<argument>${project.build.directory}</argument>
|
||||
<argument>-outfile</argument>
|
||||
<argument>${project.build.finalName}.jar</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>default-cli</id>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>${java.home}/bin/java</executable>
|
||||
<commandlineArgs>${runfx.args}</commandlineArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<compilerArguments>
|
||||
<bootclasspath>${sun.boot.class.path}${path.separator}${java.home}/lib/jfxrt.jar</bootclasspath>
|
||||
</compilerArguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.16</version>
|
||||
<configuration>
|
||||
<additionalClasspathElements>
|
||||
<additionalClasspathElement>${java.home}/lib/jfxrt.jar</additionalClasspathElement>
|
||||
</additionalClasspathElements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace;
|
||||
|
||||
import jace.hardware.FloppyDisk;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Generic disk conversion utility, using the FloppyDisk nibblize/denibblize to
|
||||
* convert between DSK and NIB formats (wherever possible anyway)
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class ConvertDiskImage {
|
||||
|
||||
public static void main(String... args) {
|
||||
if (args.length != 2) {
|
||||
showHelp();
|
||||
return;
|
||||
}
|
||||
File in = new File(args[0]);
|
||||
File out = new File(args[1]);
|
||||
if (!in.exists()) {
|
||||
showHelp();
|
||||
System.out.println("Cannot find input file: " + args[0]);
|
||||
return;
|
||||
}
|
||||
if (out.exists()) {
|
||||
showHelp();
|
||||
System.out.println("Output file already exists!: " + args[1]);
|
||||
return;
|
||||
}
|
||||
String ext = args[1].substring(args[1].length() - 3);
|
||||
boolean writeNibblized = false;
|
||||
boolean writeProdosOrdered = false;
|
||||
if (ext.equalsIgnoreCase("NIB")) {
|
||||
System.out.println("Preparing to write NIB image");
|
||||
writeNibblized = true;
|
||||
} else if (ext.equalsIgnoreCase(".DO") || ext.equalsIgnoreCase("DSK")) {
|
||||
System.out.println("Preparing to write DOS 3.3 ordered disk image");
|
||||
writeNibblized = false;
|
||||
writeProdosOrdered = false;
|
||||
} else if (ext.equalsIgnoreCase(".PO")) {
|
||||
System.out.println("Preparing to write Prodos ordered image");
|
||||
writeNibblized = false;
|
||||
writeProdosOrdered = true;
|
||||
} else {
|
||||
showHelp();
|
||||
System.out.println("Could not understand desired output format");
|
||||
return;
|
||||
}
|
||||
|
||||
// First read in the disk image, this decodes the disk as necessary
|
||||
FloppyDisk theDisk = null;
|
||||
try {
|
||||
theDisk = new FloppyDisk(in);
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Couldn't read disk image");
|
||||
ex.printStackTrace();
|
||||
return;
|
||||
}
|
||||
if (!writeNibblized) {
|
||||
// Now change the disk image to point to a new file and adjust the sector ordering
|
||||
System.out.println("Writing disk image with " + (writeProdosOrdered ? "prodos" : "dos 3.3") + " sector ordering");
|
||||
theDisk.diskPath = out;
|
||||
theDisk.isNibblizedImage = true;
|
||||
theDisk.currentSectorOrder = writeProdosOrdered ? FloppyDisk.PRODOS_SECTOR_ORDER : FloppyDisk.DOS_33_SECTOR_ORDER;
|
||||
theDisk.headerLength = 0;
|
||||
for (int i = 0; i < FloppyDisk.TRACK_COUNT; i++) {
|
||||
theDisk.updateTrack(i);
|
||||
}
|
||||
} else {
|
||||
FileOutputStream fos = null;
|
||||
System.out.println("Writing NIB image");
|
||||
try {
|
||||
fos = new FileOutputStream(out);
|
||||
fos.write(theDisk.nibbles);
|
||||
fos.close();
|
||||
} catch (IOException ex) {
|
||||
System.err.println("Error writing NIB image: " + ex.getMessage());
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException ex) {
|
||||
System.err.println("Error closing NIB image: " + ex.getMessage());
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("Finished converting disk image.");
|
||||
}
|
||||
|
||||
private static void showHelp() {
|
||||
for (String s : new String[]{
|
||||
"ConvertDiskImage",
|
||||
"----------------",
|
||||
"Usage: java -cp jace.jar jace.ConvertDiskImage DISK_INPUT_NAME DISK_OUTPUT_NAME",
|
||||
"where DISK_INPUT_NAME is the path of a valid disk image, ",
|
||||
"and DISK_OUTPUT_NAME is the path where you want to ",
|
||||
"save the converted disk image.",
|
||||
"Supported input formats: ",
|
||||
" DSK (assumes DO), DO, PO, 2MG (140kb), NIB",
|
||||
"Supported output formats: ",
|
||||
" DO/DSK, PO, NIB"
|
||||
}) {
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace;
|
||||
|
||||
import jace.apple2e.Apple2e;
|
||||
import jace.config.Configuration;
|
||||
import jace.core.Computer;
|
||||
import jace.ui.AbstractEmulatorFrame;
|
||||
import jace.ui.EmulatorFrame;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JFrame;
|
||||
|
||||
/**
|
||||
* Created on January 15, 2007, 10:10 PM
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Emulator {
|
||||
|
||||
public static Emulator instance;
|
||||
public static Thread mainThread;
|
||||
|
||||
public static void main(String... args) {
|
||||
mainThread = Thread.currentThread();
|
||||
instance = new Emulator(args);
|
||||
}
|
||||
|
||||
public static AbstractEmulatorFrame getFrame() {
|
||||
if (instance != null) {
|
||||
return instance.theApp;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public Apple2e computer;
|
||||
public AbstractEmulatorFrame theApp;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Emulator
|
||||
* @param args
|
||||
*/
|
||||
public Emulator(String... args) {
|
||||
computer = new Apple2e();
|
||||
Configuration.loadSettings();
|
||||
Map<String, String> settings = new HashMap<>();
|
||||
if (args != null) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].startsWith("-")) {
|
||||
String key = args[i].substring(1);
|
||||
if ((i + 1) < args.length) {
|
||||
String val = args[i + 1];
|
||||
if (!val.startsWith("-")) {
|
||||
settings.put(key, val);
|
||||
i++;
|
||||
} else {
|
||||
settings.put(key, "true");
|
||||
}
|
||||
} else {
|
||||
settings.put(key, "true");
|
||||
}
|
||||
} else {
|
||||
System.err.println("Did not understand parameter " + args[i] + ", skipping.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Configuration.applySettings(settings);
|
||||
|
||||
// theApp = new MainFrame();
|
||||
theApp = new EmulatorFrame();
|
||||
try {
|
||||
theApp.setIconImage(ImageIO.read(Emulator.class.getClassLoader().getResourceAsStream("jace/data/woz_figure.gif")));
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Emulator.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
//theApp.setBounds(new Rectangle((140*6),(192*3)));
|
||||
theApp.setVisible(true);
|
||||
theApp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
theApp.setFocusTraversalKeysEnabled(false);
|
||||
theApp.setTitle("Java Apple Computer Emulator");
|
||||
theApp.addKeyListener(computer.getKeyboard().getListener());
|
||||
theApp.addComponentListener(new ComponentListener() {
|
||||
// theApp.screen.addComponentListener(new ComponentListener() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
// System.out.println("Screen resized");
|
||||
resizeVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
resizeVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
}
|
||||
});
|
||||
theApp.addWindowListener(new WindowListener() {
|
||||
@Override
|
||||
public void windowOpened(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowIconified(WindowEvent e) {
|
||||
Computer.getComputer().getVideo().suspend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDeiconified(WindowEvent e) {
|
||||
Computer.getComputer().getVideo().resume();
|
||||
resizeVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowActivated(WindowEvent e) {
|
||||
resizeVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDeactivated(WindowEvent e) {
|
||||
resizeVideo();
|
||||
}
|
||||
});
|
||||
EmulatorUILogic.registerDebugger();
|
||||
computer.getVideo().setScreen(theApp.getScreenGraphics());
|
||||
computer.coldStart();
|
||||
}
|
||||
|
||||
public static void resizeVideo() {
|
||||
AbstractEmulatorFrame window = getFrame();
|
||||
if (window != null) {
|
||||
window.resizeVideo();
|
||||
}
|
||||
}
|
||||
|
||||
public static Component getScreen() {
|
||||
AbstractEmulatorFrame window = getFrame();
|
||||
if (window != null) {
|
||||
return window.getScreen();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,372 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace;
|
||||
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.config.ConfigurationPanel;
|
||||
import jace.config.InvokableAction;
|
||||
import jace.core.CPU;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Debugger;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import static jace.core.Utility.*;
|
||||
import jace.library.MediaLibrary;
|
||||
import jace.ui.AbstractEmulatorFrame;
|
||||
import jace.ui.DebuggerPanel;
|
||||
import java.awt.Color;
|
||||
import java.awt.HeadlessException;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
/**
|
||||
* This class contains miscellaneous user-invoked actions such as debugger
|
||||
* operations and running arbitrary files in the emulator. It is possible for
|
||||
* these methods to be later refactored into more sensible locations. Created on
|
||||
* April 16, 2007, 10:30 PM
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class EmulatorUILogic {
|
||||
|
||||
static Debugger debugger;
|
||||
|
||||
static {
|
||||
debugger = new Debugger() {
|
||||
@Override
|
||||
public void updateStatus() {
|
||||
enableDebug(true);
|
||||
MOS65C02 cpu = (MOS65C02) Computer.getComputer().getCpu();
|
||||
updateCPURegisters(cpu);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void updateCPURegisters(MOS65C02 cpu) {
|
||||
DebuggerPanel debuggerPanel = Emulator.getFrame().getDebuggerPanel();
|
||||
debuggerPanel.valueA.setText(Integer.toHexString(cpu.A));
|
||||
debuggerPanel.valueX.setText(Integer.toHexString(cpu.X));
|
||||
debuggerPanel.valueY.setText(Integer.toHexString(cpu.Y));
|
||||
debuggerPanel.valuePC.setText(Integer.toHexString(cpu.getProgramCounter()));
|
||||
debuggerPanel.valueSP.setText(Integer.toHexString(cpu.getSTACK()));
|
||||
debuggerPanel.valuePC2.setText(cpu.getFlags());
|
||||
debuggerPanel.valueINST.setText(cpu.disassemble());
|
||||
}
|
||||
|
||||
public static void enableDebug(boolean b) {
|
||||
DebuggerPanel debuggerPanel = Emulator.getFrame().getDebuggerPanel();
|
||||
debugger.setActive(b);
|
||||
debuggerPanel.enableDebug.setSelected(b);
|
||||
debuggerPanel.setBackground(
|
||||
b ? Color.RED : new Color(0, 0, 0x040));
|
||||
}
|
||||
|
||||
public static void enableTrace(boolean b) {
|
||||
Computer.getComputer().getCpu().setTraceEnabled(b);
|
||||
}
|
||||
|
||||
public static void stepForward() {
|
||||
debugger.step = true;
|
||||
}
|
||||
|
||||
static void registerDebugger() {
|
||||
Computer.getComputer().getCpu().setDebug(debugger);
|
||||
}
|
||||
|
||||
public static Integer getValidAddress(String s) {
|
||||
try {
|
||||
int addr = Integer.parseInt(s.toUpperCase(), 16);
|
||||
if (addr >= 0 && addr < 0x10000) {
|
||||
return addr;
|
||||
}
|
||||
return null;
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static List<RAMListener> watches = new ArrayList<>();
|
||||
|
||||
public static void updateWatchList(final DebuggerPanel panel) {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
watches.stream().forEach((oldWatch) -> {
|
||||
Computer.getComputer().getMemory().removeListener(oldWatch);
|
||||
});
|
||||
if (panel == null) {
|
||||
return;
|
||||
}
|
||||
addWatch(panel.textW1, panel.valueW1);
|
||||
addWatch(panel.textW2, panel.valueW2);
|
||||
addWatch(panel.textW3, panel.valueW3);
|
||||
addWatch(panel.textW4, panel.valueW4);
|
||||
});
|
||||
}
|
||||
|
||||
private static void addWatch(JTextField watch, final JLabel watchValue) {
|
||||
final Integer address = getValidAddress(watch.getText());
|
||||
if (address != null) {
|
||||
//System.out.println("Adding watch for "+Integer.toString(address, 16));
|
||||
RAMListener newListener = new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
watchValue.setText(Integer.toHexString(e.getNewValue() & 0x0FF));
|
||||
}
|
||||
};
|
||||
Computer.getComputer().getMemory().addListener(newListener);
|
||||
watches.add(newListener);
|
||||
// Print out the current value right away
|
||||
byte b = Computer.getComputer().getMemory().readRaw(address);
|
||||
watchValue.setText(Integer.toString(b & 0x0ff, 16));
|
||||
} else {
|
||||
watchValue.setText("00");
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateBreakpointList(final DebuggerPanel panel) {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
Integer address;
|
||||
debugger.getBreakpoints().clear();
|
||||
if (panel == null) {
|
||||
return;
|
||||
}
|
||||
address = getValidAddress(panel.textBP1.getText());
|
||||
if (address != null) {
|
||||
debugger.getBreakpoints().add(address);
|
||||
}
|
||||
address = getValidAddress(panel.textBP2.getText());
|
||||
if (address != null) {
|
||||
debugger.getBreakpoints().add(address);
|
||||
}
|
||||
address = getValidAddress(panel.textBP3.getText());
|
||||
if (address != null) {
|
||||
debugger.getBreakpoints().add(address);
|
||||
}
|
||||
address = getValidAddress(panel.textBP4.getText());
|
||||
if (address != null) {
|
||||
debugger.getBreakpoints().add(address);
|
||||
}
|
||||
debugger.updateBreakpoints();
|
||||
});
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
name = "BRUN file",
|
||||
category = "file",
|
||||
description = "Loads a binary file in memory and executes it. File should end with #06xxxx, where xxxx is the start address in hex",
|
||||
alternatives = "Execute program;Load binary;Load program;Load rom;Play single-load game")
|
||||
public static void runFile() {
|
||||
Computer.pause();
|
||||
JFileChooser select = new JFileChooser();
|
||||
select.showDialog(Emulator.getFrame(), "Execute binary file");
|
||||
File binary = select.getSelectedFile();
|
||||
if (binary == null) {
|
||||
Computer.resume();
|
||||
return;
|
||||
}
|
||||
runFile(binary);
|
||||
}
|
||||
|
||||
public static void runFile(File binary) {
|
||||
String fileName = binary.getName().toLowerCase();
|
||||
try {
|
||||
if (fileName.contains("#06")) {
|
||||
String addressStr = fileName.substring(fileName.length() - 4);
|
||||
int address = Integer.parseInt(addressStr, 16);
|
||||
brun(binary, address);
|
||||
} else if (fileName.contains("#fc")) {
|
||||
gripe("BASIC not supported yet");
|
||||
}
|
||||
} catch (NumberFormatException | IOException ex) {
|
||||
}
|
||||
Computer.getComputer().getCpu().resume();
|
||||
}
|
||||
|
||||
public static void brun(File binary, int address) throws FileNotFoundException, IOException {
|
||||
// If it was halted already, then it was initiated outside of an opcode execution
|
||||
// If it was not yet halted, then it is the case that the CPU is processing another opcode
|
||||
// So if that is the case, the program counter will need to be decremented here to compensate
|
||||
// TODO: Find a better mousetrap for this one -- it's an ugly hack
|
||||
Computer.pause();
|
||||
FileInputStream in = new FileInputStream(binary);
|
||||
byte[] data = new byte[in.available()];
|
||||
in.read(data);
|
||||
RAM ram = Computer.getComputer().getMemory();
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
ram.write(address + i, data[i], false, true);
|
||||
}
|
||||
CPU cpu = Computer.getComputer().getCpu();
|
||||
Computer.getComputer().getCpu().setProgramCounter(address);
|
||||
Computer.resume();
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
name = "Adjust display",
|
||||
category = "display",
|
||||
description = "Adjusts window size to 1:1 aspect ratio for optimal viewing.",
|
||||
alternatives = "Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;")
|
||||
static public void scaleIntegerRatio() {
|
||||
AbstractEmulatorFrame frame = Emulator.getFrame();
|
||||
if (frame == null) {
|
||||
return;
|
||||
}
|
||||
Computer.pause();
|
||||
frame.enforceIntegerRatio();
|
||||
Computer.resume();
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
name = "Toggle Debug",
|
||||
category = "debug",
|
||||
description = "Show/hide the debug panel",
|
||||
alternatives = "Show Debug;Hide Debug")
|
||||
public static void toggleDebugPanel() {
|
||||
AbstractEmulatorFrame frame = Emulator.getFrame();
|
||||
if (frame == null) {
|
||||
return;
|
||||
}
|
||||
frame.setShowDebug(!frame.isShowDebug());
|
||||
frame.reconfigure();
|
||||
Emulator.resizeVideo();
|
||||
}
|
||||
|
||||
public static void toggleFullscreen() {
|
||||
AbstractEmulatorFrame frame = Emulator.getFrame();
|
||||
if (frame == null) {
|
||||
return;
|
||||
}
|
||||
Computer.pause();
|
||||
frame.toggleFullscreen();
|
||||
Computer.resume();
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
name = "Save Raw Screenshot",
|
||||
category = "general",
|
||||
description = "Save raw (RAM) format of visible screen",
|
||||
alternatives = "screendump, raw screenshot")
|
||||
public static void saveScreenshotRaw() throws FileNotFoundException, IOException {
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss");
|
||||
String timestamp = df.format(new Date());
|
||||
String type;
|
||||
int start = Computer.getComputer().getVideo().getCurrentWriter().actualWriter().getYOffset(0);
|
||||
int len = 0;
|
||||
if (start < 0x02000) {
|
||||
// Lo-res or double-lores
|
||||
len = 0x0400;
|
||||
type = "gr";
|
||||
} else {
|
||||
// Hi-res or double-hires
|
||||
len = 0x02000;
|
||||
type = "hgr";
|
||||
}
|
||||
boolean dres = SoftSwitches._80COL.getState() && (SoftSwitches.DHIRES.getState() || start < 0x02000);
|
||||
if (dres) {
|
||||
type = "d" + type;
|
||||
}
|
||||
File outFile = new File("screen_" + type + "_a" + Integer.toHexString(start) + "_" + timestamp);
|
||||
try (FileOutputStream out = new FileOutputStream(outFile)) {
|
||||
RAM128k ram = (RAM128k) Computer.getComputer().memory;
|
||||
Computer.pause();
|
||||
if (dres) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
out.write(ram.getAuxVideoMemory().readByte(start + i));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
out.write(ram.getMainMemory().readByte(start + i));
|
||||
}
|
||||
}
|
||||
System.out.println("Wrote screenshot to " + outFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
name = "Save Screenshot",
|
||||
category = "general",
|
||||
description = "Save image of visible screen",
|
||||
alternatives = "Save image,save framebuffer,screenshot")
|
||||
public static void saveScreenshot() throws HeadlessException, IOException {
|
||||
JFileChooser select = new JFileChooser();
|
||||
Computer.pause();
|
||||
BufferedImage i = Computer.getComputer().getVideo().getFrameBuffer();
|
||||
BufferedImage j = new BufferedImage(i.getWidth(), i.getHeight(), i.getType());
|
||||
j.getGraphics().drawImage(i, 0, 0, null);
|
||||
select.showSaveDialog(Emulator.getFrame());
|
||||
File targetFile = select.getSelectedFile();
|
||||
if (targetFile == null) {
|
||||
return;
|
||||
}
|
||||
String filename = targetFile.getName();
|
||||
System.out.println("Writing screenshot to " + filename);
|
||||
String extension = filename.substring(filename.lastIndexOf(".") + 1);
|
||||
ImageIO.write(j, extension, targetFile);
|
||||
}
|
||||
|
||||
public static final String CONFIGURATION_DIALOG_NAME = "Configuration";
|
||||
@InvokableAction(
|
||||
name = "Configuration",
|
||||
category = "general",
|
||||
description = "Edit emulator configuraion",
|
||||
alternatives = "Reconfigure,Preferences,Settings")
|
||||
public static void showConfig() {
|
||||
if (Emulator.getFrame().getModalDialogUI(CONFIGURATION_DIALOG_NAME) == null) {
|
||||
JPanel ui = new ConfigurationPanel();
|
||||
Emulator.getFrame().registerModalDialog(ui, CONFIGURATION_DIALOG_NAME, null, false);
|
||||
}
|
||||
Emulator.getFrame().showDialog(CONFIGURATION_DIALOG_NAME);
|
||||
}
|
||||
|
||||
public static final String MEDIA_MANAGER_DIALOG_NAME = "Media Manager";
|
||||
public static final String MEDIA_MANAGER_EDIT_DIALOG_NAME = "Media Details";
|
||||
@InvokableAction(
|
||||
name = "Media Manager",
|
||||
category = "general",
|
||||
description = "Show the media manager",
|
||||
alternatives = "Insert disk;Eject disk;Browse;Download;Select")
|
||||
public static void showMediaManager() {
|
||||
if (Emulator.getFrame().getModalDialogUI(MEDIA_MANAGER_DIALOG_NAME) == null) {
|
||||
Emulator.getFrame().registerModalDialog(MediaLibrary.getInstance().buildUserInterface(), MEDIA_MANAGER_DIALOG_NAME, null, false);
|
||||
}
|
||||
Emulator.getFrame().showDialog(MEDIA_MANAGER_DIALOG_NAME);
|
||||
}
|
||||
|
||||
public static boolean confirm(String message) {
|
||||
return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(Emulator.getFrame(), message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package jace;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author blurry
|
||||
*/
|
||||
public class JaceApplication extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) throws Exception {
|
||||
Parent root = FXMLLoader.load(getClass().getResource("fxml/JaceUI.fxml"));
|
||||
|
||||
Scene scene = new Scene(root);
|
||||
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package jace;
|
||||
|
||||
import java.awt.Canvas;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author blurry
|
||||
*/
|
||||
public class JaceUIController {
|
||||
@FXML
|
||||
private ResourceBundle resources;
|
||||
|
||||
@FXML
|
||||
private Canvas displayCanvas;
|
||||
|
||||
@FXML
|
||||
private Region notificationRegion;
|
||||
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
assert displayCanvas != null : "fx:id=\"displayCanvas\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||
assert notificationRegion != null : "fx:id=\"notificationRegion\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,433 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.cheat.Cheats;
|
||||
import jace.config.ClassSelection;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.state.Stateful;
|
||||
import jace.core.Video;
|
||||
import jace.hardware.CardDiskII;
|
||||
import jace.hardware.CardExt80Col;
|
||||
import jace.hardware.ConsoleProbe;
|
||||
import jace.hardware.Joystick;
|
||||
import jace.hardware.massStorage.CardMassStorage;
|
||||
import java.awt.Graphics;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Apple2e is a computer with a 65c02 CPU, 128k of bankswitched ram,
|
||||
* double-hires graphics, and up to seven peripheral I/O cards installed. Pause
|
||||
* and resume are implemented by the Motherboard class. This class provides
|
||||
* overall configuration of the computer, but the actual operation of the
|
||||
* computer and its timing characteristics are managed in the Motherboard class.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
public class Apple2e extends Computer {
|
||||
|
||||
static int IRQ_VECTOR = 0x003F2;
|
||||
public Motherboard motherboard;
|
||||
@ConfigurableField(name = "Slot 1", shortName = "s1card")
|
||||
public ClassSelection card1 = new ClassSelection(Card.class, null);
|
||||
@ConfigurableField(name = "Slot 2", shortName = "s2card")
|
||||
// public Class<? extends Card> card2 = CardSSC.class;
|
||||
public ClassSelection card2 = new ClassSelection(Card.class, null);
|
||||
@ConfigurableField(name = "Slot 3", shortName = "s3card")
|
||||
public ClassSelection card3 = new ClassSelection(Card.class, null);
|
||||
@ConfigurableField(name = "Slot 4", shortName = "s4card")
|
||||
public ClassSelection card4 = new ClassSelection(Card.class, null);
|
||||
@ConfigurableField(name = "Slot 5", shortName = "s5card")
|
||||
public ClassSelection card5 = new ClassSelection(Card.class, null);
|
||||
@ConfigurableField(name = "Slot 6", shortName = "s6card")
|
||||
public ClassSelection card6 = new ClassSelection(Card.class, CardDiskII.class);
|
||||
@ConfigurableField(name = "Slot 7", shortName = "s7card")
|
||||
public ClassSelection card7 = new ClassSelection(Card.class, CardMassStorage.class);
|
||||
@ConfigurableField(name = "Debug rom", shortName = "debugRom", description = "Use debugger //e rom")
|
||||
public boolean useDebugRom = false;
|
||||
@ConfigurableField(name = "Console probe", description = "Enable console redirection (experimental!)")
|
||||
public boolean useConsoleProbe = false;
|
||||
private ConsoleProbe probe = new ConsoleProbe();
|
||||
@ConfigurableField(name = "Helpful hints", shortName = "hints")
|
||||
public boolean enableHints = true;
|
||||
@ConfigurableField(name = "Renderer", shortName = "video", description = "Video rendering implementation")
|
||||
public ClassSelection videoRenderer = new ClassSelection(Video.class, VideoNTSC.class);
|
||||
@ConfigurableField(name = "Aux Ram", shortName = "ram", description = "Aux ram card")
|
||||
public ClassSelection ramCard = new ClassSelection(RAM128k.class, CardExt80Col.class);
|
||||
public Joystick joystick1;
|
||||
public Joystick joystick2;
|
||||
@ConfigurableField(name = "Activate Cheats", shortName = "cheat", defaultValue = "")
|
||||
public ClassSelection cheatEngine = new ClassSelection(Cheats.class, null);
|
||||
public Cheats activeCheatEngine = null;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Apple2e
|
||||
*/
|
||||
public Apple2e() {
|
||||
super();
|
||||
try {
|
||||
reconfigure();
|
||||
// Setup core resources
|
||||
joystick1 = new Joystick(0);
|
||||
joystick2 = new Joystick(1);
|
||||
setCpu(new MOS65C02());
|
||||
reinitMotherboard();
|
||||
} catch (Throwable t) {
|
||||
System.err.println("Unable to initalize virtual machine");
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Computer (Apple //e)";
|
||||
}
|
||||
|
||||
private void reinitMotherboard() {
|
||||
if (motherboard != null && motherboard.isRunning()) {
|
||||
motherboard.suspend();
|
||||
}
|
||||
motherboard = new Motherboard();
|
||||
motherboard.reconfigure();
|
||||
Motherboard.miscDevices.add(joystick1);
|
||||
Motherboard.miscDevices.add(joystick2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void coldStart() {
|
||||
Computer.pause();
|
||||
reinitMotherboard();
|
||||
reboot();
|
||||
//getMemory().dump();
|
||||
for (SoftSwitches s : SoftSwitches.values()) {
|
||||
s.getSwitch().reset();
|
||||
}
|
||||
getMemory().configureActiveMemory();
|
||||
getVideo().configureVideoMode();
|
||||
getCpu().reset();
|
||||
for (Card c : getMemory().getAllCards()) {
|
||||
if (c != null) {
|
||||
c.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Computer.resume();
|
||||
/*
|
||||
getCpu().resume();
|
||||
getVideo().resume();
|
||||
*/
|
||||
}
|
||||
|
||||
public void reboot() {
|
||||
RAM r = getMemory();
|
||||
r.write(IRQ_VECTOR, (byte) 0x00, false, true);
|
||||
r.write(IRQ_VECTOR + 1, (byte) 0x00, false, true);
|
||||
r.write(IRQ_VECTOR + 2, (byte) 0x00, false, true);
|
||||
warmStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warmStart() {
|
||||
boolean restart = Computer.pause();
|
||||
for (SoftSwitches s : SoftSwitches.values()) {
|
||||
s.getSwitch().reset();
|
||||
}
|
||||
getMemory().configureActiveMemory();
|
||||
getVideo().configureVideoMode();
|
||||
getCpu().reset();
|
||||
for (Card c : getMemory().getAllCards()) {
|
||||
if (c != null) {
|
||||
c.reset();
|
||||
}
|
||||
}
|
||||
getCpu().resume();
|
||||
Computer.resume();
|
||||
}
|
||||
|
||||
private void insertCard(Class<? extends Card> type, int slot) {
|
||||
if (getMemory().getCard(slot) != null) {
|
||||
if (getMemory().getCard(slot).getClass().equals(type)) {
|
||||
return;
|
||||
}
|
||||
getMemory().removeCard(slot);
|
||||
}
|
||||
if (type != null) {
|
||||
try {
|
||||
getMemory().addCard(type.newInstance(), slot);
|
||||
} catch (InstantiationException | IllegalAccessException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void reconfigure() {
|
||||
boolean restart = Computer.pause();
|
||||
|
||||
super.reconfigure();
|
||||
|
||||
RAM128k currentMemory = (RAM128k) getMemory();
|
||||
if (currentMemory != null && !(currentMemory.getClass().equals(ramCard.getValue()))) {
|
||||
try {
|
||||
RAM128k newMemory = (RAM128k) ramCard.getValue().newInstance();
|
||||
newMemory.copyFrom(currentMemory);
|
||||
setMemory(newMemory);
|
||||
} catch (InstantiationException | IllegalAccessException ex) {
|
||||
}
|
||||
}
|
||||
if (getMemory() == null) {
|
||||
try {
|
||||
currentMemory = (RAM128k) ramCard.getValue().newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
try {
|
||||
setMemory(currentMemory);
|
||||
for (SoftSwitches s : SoftSwitches.values()) {
|
||||
s.getSwitch().register();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
}
|
||||
}
|
||||
currentMemory.reconfigure();
|
||||
|
||||
try {
|
||||
if (useConsoleProbe) {
|
||||
probe.init(this);
|
||||
} else {
|
||||
probe.shutdown();
|
||||
}
|
||||
|
||||
if (useDebugRom) {
|
||||
loadRom("jace/data/apple2e_debug.rom");
|
||||
} else {
|
||||
loadRom("jace/data/apple2e.rom");
|
||||
}
|
||||
|
||||
if (getVideo() == null || getVideo().getClass() != videoRenderer.getValue()) {
|
||||
Graphics g = null;
|
||||
if (getVideo() != null) {
|
||||
getVideo().suspend();
|
||||
g = getVideo().getScreen();
|
||||
}
|
||||
try {
|
||||
setVideo((Video) videoRenderer.getValue().newInstance());
|
||||
getVideo().configureVideoMode();
|
||||
getVideo().reconfigure();
|
||||
getVideo().setScreen(g);
|
||||
Emulator.resizeVideo();
|
||||
getVideo().resume();
|
||||
} catch (InstantiationException | IllegalAccessException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all new cards
|
||||
insertCard(card1.getValue(), 1);
|
||||
insertCard(card2.getValue(), 2);
|
||||
insertCard(card3.getValue(), 3);
|
||||
insertCard(card4.getValue(), 4);
|
||||
insertCard(card5.getValue(), 5);
|
||||
insertCard(card6.getValue(), 6);
|
||||
insertCard(card7.getValue(), 7);
|
||||
if (enableHints) {
|
||||
enableHints();
|
||||
} else {
|
||||
disableHints();
|
||||
}
|
||||
getMemory().configureActiveMemory();
|
||||
|
||||
if (cheatEngine.getValue() == null) {
|
||||
if (activeCheatEngine != null) {
|
||||
activeCheatEngine.detach();
|
||||
}
|
||||
activeCheatEngine = null;
|
||||
} else {
|
||||
boolean startCheats = true;
|
||||
if (activeCheatEngine != null) {
|
||||
if (activeCheatEngine.getClass().equals(cheatEngine.getValue())) {
|
||||
startCheats = false;
|
||||
} else {
|
||||
activeCheatEngine.detach();
|
||||
activeCheatEngine = null;
|
||||
}
|
||||
}
|
||||
if (startCheats) {
|
||||
try {
|
||||
activeCheatEngine = (Cheats) cheatEngine.getValue().newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
activeCheatEngine.attach();
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
if (restart) {
|
||||
Computer.resume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPause() {
|
||||
if (motherboard == null) {
|
||||
return;
|
||||
}
|
||||
motherboard.pause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doResume() {
|
||||
if (motherboard == null) {
|
||||
return;
|
||||
}
|
||||
motherboard.resume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRunning() {
|
||||
if (motherboard == null) {
|
||||
return false;
|
||||
}
|
||||
return motherboard.isRunning() && !motherboard.isPaused;
|
||||
}
|
||||
private List<RAMListener> hints = new ArrayList<>();
|
||||
|
||||
private void enableHints() {
|
||||
if (hints.isEmpty()) {
|
||||
hints.add(new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0FB63);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (getCpu().getProgramCounter() != getScopeStart()) {
|
||||
return;
|
||||
}
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
// Give the floppy drive time to start
|
||||
Thread.sleep(1000);
|
||||
if (getCpu().getProgramCounter() >> 8 != 0x0c6) {
|
||||
return;
|
||||
}
|
||||
|
||||
int row = 2;
|
||||
for (String s : new String[]{
|
||||
" Welcome to",
|
||||
" _ __ ___ ____ ",
|
||||
" | | / /\\ / / ` | |_ ",
|
||||
" \\_|_| /_/--\\ \\_\\_, |_|__ ",
|
||||
"",
|
||||
" Java Apple Computer Emulator",
|
||||
"",
|
||||
" Presented by BLuRry",
|
||||
" http://goo.gl/SnzqG",
|
||||
"",
|
||||
"Press F1 to insert disk in Slot 6, D1",
|
||||
"Press F2 to insert disk in Slot 6, D2",
|
||||
"Press F3 to insert HDV or 2MG in slot 7",
|
||||
"Press F4 to open configuration",
|
||||
"Press F5 to run raw binary program",
|
||||
"Press F8 to correct the aspect ratio",
|
||||
"Press F9 to toggle fullscreen",
|
||||
"Press F10 to open/close the debugger",
|
||||
"",
|
||||
" If metacheat is enabled:",
|
||||
"Press HOME to activate memory heatmap",
|
||||
"Press END to activate metacheat search"
|
||||
}) {
|
||||
int addr = 0x0401 + VideoDHGR.calculateTextOffset(row++);
|
||||
for (char c : s.toCharArray()) {
|
||||
getMemory().write(addr++, (byte) (c | 0x080), false, true);
|
||||
}
|
||||
}
|
||||
while (getCpu().getProgramCounter() >> 8 == 0x0c6) {
|
||||
int x = (int) (Math.random() * 26.0) + 7;
|
||||
int y = (int) (Math.random() * 4.0) + 3;
|
||||
int addr = 0x0400 + VideoDHGR.calculateTextOffset(y) + x;
|
||||
byte old = getMemory().readRaw(addr);
|
||||
for (char c : "+xX*+".toCharArray()) {
|
||||
if (getCpu().getProgramCounter() >> 8 != 0x0c6) {
|
||||
break;
|
||||
}
|
||||
getMemory().write(addr, (byte) (c | 0x080), true, true);
|
||||
Thread.sleep(100);
|
||||
}
|
||||
getMemory().write(addr, old, true, true);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
});
|
||||
t.setName("Startup Animation");
|
||||
t.start();
|
||||
}
|
||||
});
|
||||
// Latch to the PRODOS SYNTAX CHECK parser
|
||||
/*
|
||||
hints.add(new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {setScopeStart(0x0a685);}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
String in = "";
|
||||
for (int i=0x0200; i < 0x0300; i++) {
|
||||
char c = (char) (getMemory().readRaw(i) & 0x07f);
|
||||
if (c == 0x0d) break;
|
||||
in += c;
|
||||
}
|
||||
|
||||
System.err.println("Intercepted command: "+in);
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
hints.stream().forEach((hint) -> {
|
||||
getMemory().addListener(hint);
|
||||
});
|
||||
}
|
||||
|
||||
private void disableHints() {
|
||||
hints.stream().forEach((hint) -> {
|
||||
getMemory().removeListener(hint);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "computer";
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e;
|
||||
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.PagedMemory;
|
||||
import jace.core.RAM;
|
||||
import jace.state.Stateful;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Implementation of a 128k memory space and the MMU found in an Apple //e. The
|
||||
* MMU behavior is mimicked by configureActiveMemory.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
abstract public class RAM128k extends RAM {
|
||||
@Stateful
|
||||
public PagedMemory mainMemory;
|
||||
@Stateful
|
||||
public PagedMemory languageCard;
|
||||
@Stateful
|
||||
public PagedMemory languageCard2;
|
||||
public PagedMemory cPageRom;
|
||||
public PagedMemory rom;
|
||||
public PagedMemory blank;
|
||||
|
||||
public RAM128k() {
|
||||
super();
|
||||
mainMemory = new PagedMemory(0xc000, PagedMemory.Type.ram);
|
||||
rom = new PagedMemory(0x3000, PagedMemory.Type.firmwareMain);
|
||||
cPageRom = new PagedMemory(0x1000, PagedMemory.Type.slotRom);
|
||||
languageCard = new PagedMemory(0x3000, PagedMemory.Type.languageCard);
|
||||
languageCard2 = new PagedMemory(0x1000, PagedMemory.Type.languageCard);
|
||||
activeRead = new PagedMemory(0x10000, PagedMemory.Type.ram);
|
||||
activeWrite = new PagedMemory(0x10000, PagedMemory.Type.ram);
|
||||
blank = new PagedMemory(0x100, PagedMemory.Type.ram);
|
||||
|
||||
// Format memory with FF FF 00 00 pattern
|
||||
for (int i = 0; i < 0x0100; i++) {
|
||||
blank.get(0)[i] = (byte) 0x0FF;
|
||||
}
|
||||
initMemoryPattern(mainMemory);
|
||||
}
|
||||
|
||||
public final void initMemoryPattern(PagedMemory mem) {
|
||||
// Format memory with FF FF 00 00 pattern
|
||||
for (int i = 0; i < 0x0100; i++) {
|
||||
for (int j = 0; j < 0x0c0; j++) {
|
||||
byte use = (byte) ((i % 4) > 1 ? 0x0FF : 0x00);
|
||||
mem.get(j)[i] = use;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void configureActiveMemory() {
|
||||
log("MMU Switches");
|
||||
synchronized (this) {
|
||||
// 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);
|
||||
for (int i = 0x0c0; i < 0x0d0; i++) {
|
||||
activeWrite.set(i, null);
|
||||
}
|
||||
if (SoftSwitches.LCRAM.getState()) {
|
||||
if (!SoftSwitches.AUXZP.getState()) {
|
||||
activeRead.fillBanks(languageCard);
|
||||
if (!SoftSwitches.LCBANK1.getState()) {
|
||||
activeRead.fillBanks(languageCard2);
|
||||
}
|
||||
} else {
|
||||
activeRead.fillBanks(getAuxLanguageCard());
|
||||
if (!SoftSwitches.LCBANK1.getState()) {
|
||||
activeRead.fillBanks(getAuxLanguageCard2());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SoftSwitches.LCWRITE.getState()) {
|
||||
if (!SoftSwitches.AUXZP.getState()) {
|
||||
activeWrite.fillBanks(languageCard);
|
||||
if (!SoftSwitches.LCBANK1.getState()) {
|
||||
activeWrite.fillBanks(languageCard2);
|
||||
}
|
||||
} else {
|
||||
activeWrite.fillBanks(getAuxLanguageCard());
|
||||
if (!SoftSwitches.LCBANK1.getState()) {
|
||||
activeWrite.fillBanks(getAuxLanguageCard2());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Make 0xd000 - 0xffff non-writable!
|
||||
for (int i = 0x0d0; i < 0x0100; i++) {
|
||||
activeWrite.set(i, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 80STORE logic for bankswitching video ram
|
||||
if (SoftSwitches._80STORE.isOn()) {
|
||||
activeRead.setBanks(0x04, 0x04, 0x04,
|
||||
SoftSwitches.PAGE2.isOn() ? getAuxMemory() : mainMemory);
|
||||
activeWrite.setBanks(0x04, 0x04, 0x04,
|
||||
SoftSwitches.PAGE2.isOn() ? getAuxMemory() : mainMemory);
|
||||
if (SoftSwitches.HIRES.isOn()) {
|
||||
activeRead.setBanks(0x020, 0x020, 0x020,
|
||||
SoftSwitches.PAGE2.isOn() ? getAuxMemory() : mainMemory);
|
||||
activeWrite.setBanks(0x020, 0x020, 0x020,
|
||||
SoftSwitches.PAGE2.isOn() ? getAuxMemory() : mainMemory);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle zero-page bankswitching
|
||||
if (SoftSwitches.AUXZP.getState()) {
|
||||
// Aux pages 0 and 1
|
||||
activeRead.setBanks(0, 2, 0, getAuxMemory());
|
||||
activeWrite.setBanks(0, 2, 0, getAuxMemory());
|
||||
} else {
|
||||
// Main pages 0 and 1
|
||||
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);
|
||||
} else {
|
||||
// Enable C1-CF to point to slots
|
||||
for (int slot = 1; slot <= 7; slot++) {
|
||||
Card c = getCard(slot);
|
||||
if (c != null) {
|
||||
// Enable card slot ROM
|
||||
activeRead.setBanks(0, 1, 0x0C0 + slot, c.getCxRom());
|
||||
if (getActiveSlot() == slot) {
|
||||
activeRead.setBanks(0, 8, 0x0C8, c.getC8Rom());
|
||||
}
|
||||
} else {
|
||||
// Disable card slot ROM (TODO: floating bus)
|
||||
activeRead.set(0x0C0 + slot, blank.get(0));
|
||||
}
|
||||
}
|
||||
if (getActiveSlot() == 0) {
|
||||
for (int i = 0x0C8; i < 0x0D0; i++) {
|
||||
activeRead.set(i, blank.get(0));
|
||||
}
|
||||
}
|
||||
if (SoftSwitches.SLOTC3ROM.isOff()) {
|
||||
// Enable C3 to point to internal ROM
|
||||
activeRead.setBanks(2, 1, 0x0C3, cPageRom);
|
||||
}
|
||||
if (SoftSwitches.INTC8ROM.getState()) {
|
||||
// Enable C8-CF to point to internal ROM
|
||||
activeRead.setBanks(7, 8, 0x0C8, cPageRom);
|
||||
}
|
||||
}
|
||||
}
|
||||
// All ROM reads not intecepted will return 0xFF! (TODO: floating bus)
|
||||
activeRead.set(0x0c0, blank.get(0));
|
||||
}
|
||||
|
||||
public void log(String message) {
|
||||
if (Computer.getComputer().cpu != null && Computer.getComputer().cpu.isLogEnabled()) {
|
||||
String stack = "";
|
||||
for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
|
||||
stack += e.getClassName() + "." + e.getMethodName() + "(" + e.getLineNumber() + ");";
|
||||
}
|
||||
Computer.getComputer().cpu.log(stack);
|
||||
Computer.getComputer().cpu.log(message + ";" + SoftSwitches.RAMRD + ";" + SoftSwitches.RAMWRT + ";" + SoftSwitches.AUXZP + ";" + SoftSwitches._80STORE + ";" + SoftSwitches.HIRES + ";" + SoftSwitches.PAGE2 + ";" + SoftSwitches.LCBANK1 + ";" + SoftSwitches.LCRAM + ";" + SoftSwitches.LCWRITE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param path
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
@Override
|
||||
protected void loadRom(String path) throws IOException {
|
||||
// Remap writable ram to reflect rom file structure
|
||||
byte[] ignore = new byte[256];
|
||||
activeWrite.set(0, ignore); // Ignore first bank of data
|
||||
for (int i = 1; i < 17; i++) {
|
||||
activeWrite.set(i, ignore);
|
||||
}
|
||||
activeWrite.setBanks(0, cPageRom.getMemory().length, 0x011, cPageRom);
|
||||
activeWrite.setBanks(0, rom.getMemory().length, 0x020, rom);
|
||||
//----------------------
|
||||
InputStream inputRom = getClass().getClassLoader().getResourceAsStream(path);
|
||||
int read = 0;
|
||||
int addr = 0;
|
||||
byte[] in = new byte[1024];
|
||||
while (addr < 0x00FFFF && (read = inputRom.read(in)) > 0) {
|
||||
for (int i = 0; i < read; i++) {
|
||||
write(addr++, in[i], false, false);
|
||||
}
|
||||
}
|
||||
// System.out.println("Finished reading rom with " + inputRom.available() + " bytes left unread!");
|
||||
//dump();
|
||||
configureActiveMemory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mainMemory
|
||||
*/
|
||||
public PagedMemory getMainMemory() {
|
||||
return mainMemory;
|
||||
}
|
||||
|
||||
abstract public PagedMemory getAuxVideoMemory();
|
||||
abstract public PagedMemory getAuxMemory();
|
||||
abstract public PagedMemory getAuxLanguageCard();
|
||||
abstract public PagedMemory getAuxLanguageCard2();
|
||||
|
||||
/**
|
||||
* @return the languageCard
|
||||
*/
|
||||
public PagedMemory getLanguageCard() {
|
||||
return languageCard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the languageCard2
|
||||
*/
|
||||
public PagedMemory getLanguageCard2() {
|
||||
return languageCard2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the cPageRom
|
||||
*/
|
||||
public PagedMemory getcPageRom() {
|
||||
return cPageRom;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the rom
|
||||
*/
|
||||
public PagedMemory getRom() {
|
||||
return rom;
|
||||
}
|
||||
|
||||
void copyFrom(RAM128k currentMemory) {
|
||||
// This is really quick and dirty but should be sufficient to avoid most crashes...
|
||||
blank = currentMemory.blank;
|
||||
cPageRom = currentMemory.cPageRom;
|
||||
rom = currentMemory.rom;
|
||||
listeners = currentMemory.listeners;
|
||||
mainMemory = currentMemory.mainMemory;
|
||||
languageCard = currentMemory.languageCard;
|
||||
languageCard2 = currentMemory.languageCard2;
|
||||
cards = currentMemory.cards;
|
||||
activeSlot = currentMemory.activeSlot;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e;
|
||||
|
||||
import jace.apple2e.softswitch.IntC8SoftSwitch;
|
||||
import jace.apple2e.softswitch.KeyboardSoftSwitch;
|
||||
import jace.apple2e.softswitch.Memory2SoftSwitch;
|
||||
import jace.apple2e.softswitch.MemorySoftSwitch;
|
||||
import jace.apple2e.softswitch.VideoSoftSwitch;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.SoftSwitch;
|
||||
|
||||
/**
|
||||
* Softswitches reside in the addresses C000-C07f and control everything from
|
||||
* memory management to speaker sound and keyboard. Other I/O ports (c080-C0ff)
|
||||
* are managed by any registered Cards. This enumeration serves as a convenient
|
||||
* way to represent the different softswitches as well as provide a clean
|
||||
* enumeration.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public enum SoftSwitches {
|
||||
|
||||
_80STORE(new MemorySoftSwitch("80Store", 0x0c000, 0x0c001, 0x0c018, RAMEvent.TYPE.WRITE, false)),
|
||||
RAMRD(new MemorySoftSwitch("AuxRead (RAMRD)", 0x0c002, 0x0c003, 0x0c013, RAMEvent.TYPE.WRITE, false)),
|
||||
RAMWRT(new MemorySoftSwitch("AuxWrite (RAMWRT)", 0x0c004, 0x0c005, 0x0c014, RAMEvent.TYPE.WRITE, false)),
|
||||
CXROM(new MemorySoftSwitch("IntCXROM", 0x0c006, 0x0c007, 0x0c015, RAMEvent.TYPE.WRITE, false)),
|
||||
AUXZP(new MemorySoftSwitch("AuxZeroPage", 0x0c008, 0x0c009, 0x0c016, RAMEvent.TYPE.WRITE, false)),
|
||||
SLOTC3ROM(new MemorySoftSwitch("C3ROM", 0x0c00a, 0x0c00b, 0x0c017, RAMEvent.TYPE.WRITE, false)),
|
||||
INTC8ROM(new IntC8SoftSwitch()),
|
||||
LCBANK1(new MemorySoftSwitch("LangCardBank1",
|
||||
new int[]{0x0c088, 0x0c089, 0x0c08a, 0x0c08b, 0x0c08c, 0x0c08d, 0x0c08e, 0x0c08f},
|
||||
new int[]{0x0c080, 0x0c081, 0x0c082, 0x0c083, 0x0c084, 0x0c085, 0x0c086, 0x0c087},
|
||||
new int[]{0x0c011}, RAMEvent.TYPE.ANY, false)),
|
||||
LCRAM(new MemorySoftSwitch("LangCardRam/HRAMRD'",
|
||||
new int[]{0x0c081, 0x0c082, 0x0c085, 0x0c086, 0x0c089, 0x0c08a, 0x0c08d, 0x0c08e},
|
||||
new int[]{0x0c080, 0x0c083, 0x0c084, 0x0c087, 0x0c088, 0x0c08b, 0x0c08c, 0x0c08f},
|
||||
new int[]{0x0c012}, RAMEvent.TYPE.ANY, false)),
|
||||
LCWRITE(new Memory2SoftSwitch("LangCardWrite",
|
||||
new int[]{0x0c080, 0x0c082, 0x0c084, 0x0c086, 0x0c088, 0x0c08a, 0x0c08c, 0x0c08e},
|
||||
new int[]{0x0c081, 0x0c083, 0x0c085, 0x0c087, 0x0c089, 0x0c08b, 0x0c08d, 0x0c08f},
|
||||
null, RAMEvent.TYPE.ANY, true)),
|
||||
//Renamed as per Sather 5-7
|
||||
_80COL(new VideoSoftSwitch("80ColumnVideo (80COL/80VID)", 0x0c00c, 0x0c00d, 0x0c01f, RAMEvent.TYPE.WRITE, false)),
|
||||
ALTCH(new VideoSoftSwitch("Mousetext", 0x0c00e, 0x0c00f, 0x0c01e, RAMEvent.TYPE.WRITE, false)),
|
||||
TEXT(new VideoSoftSwitch("Text", 0x0c050, 0x0c051, 0x0c01a, RAMEvent.TYPE.ANY, true)),
|
||||
MIXED(new VideoSoftSwitch("Mixed", 0x0c052, 0x0c053, 0x0c01b, RAMEvent.TYPE.ANY, false)),
|
||||
PAGE2(new VideoSoftSwitch("Page2", 0x0c054, 0x0c055, 0x0c01c, RAMEvent.TYPE.ANY, false) {
|
||||
@Override
|
||||
public void stateChanged() {
|
||||
// if (Computer.getComputer() == null) {
|
||||
// return;
|
||||
// }
|
||||
// if (Computer.getComputer() == null && Computer.getComputer().getMemory() == null) {
|
||||
// return;
|
||||
// }
|
||||
// if (Computer.getComputer() == null && Computer.getComputer().getVideo() == null) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// PAGE2 is a hybrid switch; 80STORE ? memory : video
|
||||
if (_80STORE.isOn()) {
|
||||
Computer.getComputer().getMemory().configureActiveMemory();
|
||||
} else {
|
||||
Computer.getComputer().getVideo().configureVideoMode();
|
||||
}
|
||||
}
|
||||
}),
|
||||
HIRES(new VideoSoftSwitch("Hires", 0x0c056, 0x0c057, 0x0c01d, RAMEvent.TYPE.ANY, false)),
|
||||
DHIRES(new VideoSoftSwitch("Double-hires", 0x0c05f, 0x0c05e, 0x0c07f, RAMEvent.TYPE.ANY, false)),
|
||||
PB0(new MemorySoftSwitch("Pushbutton0", -1, -1, 0x0c061, RAMEvent.TYPE.ANY, null)),
|
||||
PB1(new MemorySoftSwitch("Pushbutton1", -1, -1, 0x0c062, RAMEvent.TYPE.ANY, null)),
|
||||
PB2(new MemorySoftSwitch("Pushbutton2", -1, -1, 0x0c063, RAMEvent.TYPE.ANY, null)),
|
||||
PDLTRIG(new MemorySoftSwitch(
|
||||
"PaddleTrigger",
|
||||
null,
|
||||
new int[]{0x0c070, 0x0c071, 0x0c072, 0x0c073, 0x0c074, 0x0c075, 0x0c076, 0x0c077,
|
||||
0x0c078, 0x0c079, 0x0c07a, 0x0c07b, 0x0c07c, 0x0c07d, 0x0c07e, 0x0c07f},
|
||||
null, RAMEvent.TYPE.ANY, false)),
|
||||
PDL0(new MemorySoftSwitch("Paddle0", -1, -1, 0x0c064, RAMEvent.TYPE.ANY, false)),
|
||||
PDL1(new MemorySoftSwitch("Paddle1", -1, -1, 0x0c065, RAMEvent.TYPE.ANY, false)),
|
||||
PDL2(new MemorySoftSwitch("Paddle2", -1, -1, 0x0c066, RAMEvent.TYPE.ANY, false)),
|
||||
PDL3(new MemorySoftSwitch("Paddle3", -1, -1, 0x0c067, RAMEvent.TYPE.ANY, false)),
|
||||
AN0(new MemorySoftSwitch("Annunciator0", 0x0c058, 0x0c059, -1, RAMEvent.TYPE.ANY, false)),
|
||||
AN1(new MemorySoftSwitch("Annunciator1", 0x0c05a, 0x0c05b, -1, RAMEvent.TYPE.ANY, false)),
|
||||
AN2(new MemorySoftSwitch("Annunciator2", 0x0c05c, 0x0c05d, -1, RAMEvent.TYPE.ANY, false)),
|
||||
AN3(new MemorySoftSwitch("Annunciator3", 0x0c05e, 0x0c05f, -1, RAMEvent.TYPE.ANY, false)),
|
||||
KEYBOARD(new KeyboardSoftSwitch(
|
||||
"Keyboard",
|
||||
new int[]{0x0c010, 0x0c11, 0x0c012, 0x0c013, 0x0c014, 0x0c015, 0x0c016, 0x0c017,
|
||||
0x0c018, 0x0c019, 0x0c01a, 0x0c01b, 0x0c01c, 0x0c01d, 0x0c01e, 0x0c01f},
|
||||
null,
|
||||
new int[]{0x0c000, 0x0c001, 0x0c002, 0x0c003, 0x0c004, 0x0c005, 0x0c006, 0x0c007,
|
||||
0x0c008, 0x0c009, 0x0c00a, 0x0c00b, 0x0c00c, 0x0c00d, 0x0c00e, 0x0c00f, 0x0c010},
|
||||
RAMEvent.TYPE.WRITE, false)),
|
||||
//C010 should clear keyboard strobe when read as well
|
||||
KEYBOARD_STROBE_READ(new SoftSwitch("KeyStrobe_Read", 0x0c010, -1, -1, RAMEvent.TYPE.READ, false) {
|
||||
@Override
|
||||
protected byte readSwitch() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged() {
|
||||
KEYBOARD.getSwitch().setState(false);
|
||||
}
|
||||
}),
|
||||
TAPEOUT(new MemorySoftSwitch("TapeOut", 0x0c020, 0x0c020, 0x0c060, RAMEvent.TYPE.ANY, false)),
|
||||
VBL(new VideoSoftSwitch("VBL", -1, -1, 0x0c019, RAMEvent.TYPE.ANY, false)),
|
||||
FLOATING_BUS(new SoftSwitch("FloatingBus", null, null, new int[]{0x0C050, 0x0C051, 0x0C052, 0x0C053, 0x0C054}, RAMEvent.TYPE.READ, null) {
|
||||
@Override
|
||||
protected byte readSwitch() {
|
||||
if (Computer.getComputer().getVideo() == null) {
|
||||
return 0;
|
||||
}
|
||||
return Computer.getComputer().getVideo().getFloatingBus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged() {
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
2C:VBL (new MemorySoftSwitch(0x0c070, 0x0*, 0x0c041, RAMEvent.TYPE.ANY, false)),
|
||||
2C:VBLENABLE (new MemorySoftSwitch(0x0c05a, 0x0c05b, 0x0-, RAMEvent.TYPE.ANY, false)),
|
||||
2C:XINT (new MemorySoftSwitch(0x0c015 (r), c048-c04f (r/w), 0x0-, 0x0-, RAMEvent.TYPE.ANY, false)),
|
||||
2C:YINT (new MemorySoftSwitch(0x0c017 (r), c048-c04f (r/w), 0x0-, 0x0-, RAMEvent.TYPE.ANY, false)),
|
||||
2C:MBUTTON (new MemorySoftSwitch(0x0*, 0x0*, 0x0c063, RAMEvent.TYPE.ANY, false)),
|
||||
2C:80/40 switch (new MemorySoftSwitch(0x0*, 0x0*, 0x0c060, RAMEvent.TYPE.ANY, false)),
|
||||
2C:XDirection (new MemorySoftSwitch(0x0*, 0x0*, 0x0c066, RAMEvent.TYPE.ANY, false)),
|
||||
2C:YDirection (new MemorySoftSwitch(0x0*, 0x0*, 0x0c067, RAMEvent.TYPE.ANY, false)),
|
||||
*/
|
||||
private final SoftSwitch softswitch;
|
||||
|
||||
/**
|
||||
* Creates a new instance of SoftSwitches
|
||||
*/
|
||||
private SoftSwitches(SoftSwitch softswitch) {
|
||||
this.softswitch = softswitch;
|
||||
}
|
||||
|
||||
public SoftSwitch getSwitch() {
|
||||
return softswitch;
|
||||
}
|
||||
|
||||
public boolean getState() {
|
||||
return softswitch.getState();
|
||||
}
|
||||
|
||||
public final boolean isOn() {
|
||||
return softswitch.getState();
|
||||
}
|
||||
|
||||
public final boolean isOff() {
|
||||
return !softswitch.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return softswitch.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e;
|
||||
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Device;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.core.SoundMixer;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
import static jace.core.Utility.*;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Apple // Speaker Emulation Created on May 9, 2007, 9:55 PM
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Speaker extends Device {
|
||||
|
||||
static boolean fileOutputActive = false;
|
||||
static OutputStream out;
|
||||
|
||||
public static void toggleFileOutput() {
|
||||
if (fileOutputActive) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Speaker.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
out = null;
|
||||
fileOutputActive = false;
|
||||
} else {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.showSaveDialog(null);
|
||||
File f = fileChooser.getSelectedFile();
|
||||
if (f == null) {
|
||||
return;
|
||||
}
|
||||
if (f.exists()) {
|
||||
int i = JOptionPane.showConfirmDialog(null, "Overwrite existing file?");
|
||||
if (i != JOptionPane.OK_OPTION && i != JOptionPane.YES_OPTION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
out = new FileOutputStream(f);
|
||||
fileOutputActive = true;
|
||||
} catch (FileNotFoundException ex) {
|
||||
Logger.getLogger(Speaker.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Counter tracks the number of cycles between sampling
|
||||
*/
|
||||
private double counter = 0;
|
||||
/**
|
||||
* Level is the number of cycles the speaker has been on
|
||||
*/
|
||||
private int level = 0;
|
||||
/**
|
||||
* Idle cycles counts the number of cycles the speaker has not been changed
|
||||
* (used to deactivate sound when not in use)
|
||||
*/
|
||||
private int idleCycles = 0;
|
||||
/**
|
||||
* Number of samples in buffer
|
||||
*/
|
||||
static int BUFFER_SIZE = (int) (((float) SoundMixer.RATE) * 0.4);
|
||||
// Number of samples available in output stream before playback happens (avoid extra blocking)
|
||||
// static int MIN_PLAYBACK_BUFFER = BUFFER_SIZE / 2;
|
||||
static int MIN_PLAYBACK_BUFFER = 64;
|
||||
// Number of samples in buffer to wait until playback (avoid underrun)
|
||||
private int MIN_SAMPLE_PLAYBACK = 64;
|
||||
/**
|
||||
* Playback volume (should be < 1423)
|
||||
*/
|
||||
@ConfigurableField(name = "Speaker Volume", shortName = "vol", description = "Should be under 1400")
|
||||
public static int VOLUME = 600;
|
||||
/**
|
||||
* Number of idle cycles until speaker playback is deactivated
|
||||
*/
|
||||
@ConfigurableField(name = "Idle cycles before sleep", shortName = "idle")
|
||||
public static int MAX_IDLE_CYCLES = 100000;
|
||||
/**
|
||||
* Java sound output
|
||||
*/
|
||||
private SourceDataLine sdl;
|
||||
/**
|
||||
* Manifestation of the apple speaker softswitch
|
||||
*/
|
||||
private boolean speakerBit = false;
|
||||
//
|
||||
/**
|
||||
* Locking semaphore to prevent race conditions when working with buffer or
|
||||
* related variables
|
||||
*/
|
||||
private final Object bufferLock = new Object();
|
||||
/**
|
||||
* Double-buffer used for playing processed sound -- as one is played the
|
||||
* other fills up.
|
||||
*/
|
||||
byte[] soundBuffer1;
|
||||
byte[] soundBuffer2;
|
||||
int currentBuffer = 1;
|
||||
int bufferPos = 0;
|
||||
private double TICKS_PER_SAMPLE = ((double) Motherboard.SPEED) / ((double) SoundMixer.RATE);
|
||||
private double TICKS_PER_SAMPLE_FLOOR = Math.floor(TICKS_PER_SAMPLE);
|
||||
Thread playbackThread;
|
||||
private final RAMListener listener
|
||||
= new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
|
||||
@Override
|
||||
public boolean isRelevant(RAMEvent e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0C030);
|
||||
setScopeEnd(0x0C03F);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (e.getType() == RAMEvent.TYPE.WRITE) {
|
||||
level += 2;
|
||||
} else {
|
||||
speakerBit = !speakerBit;
|
||||
}
|
||||
resetIdle();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new instance of Speaker
|
||||
*/
|
||||
public Speaker() {
|
||||
configureListener();
|
||||
reconfigure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend playback of sound
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean suspend() {
|
||||
boolean result = super.suspend();
|
||||
speakerBit = false;
|
||||
if (playbackThread != null && playbackThread.isAlive()) {
|
||||
playbackThread = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start or resume playback of sound
|
||||
*/
|
||||
@Override
|
||||
public void resume() {
|
||||
sdl = null;
|
||||
try {
|
||||
sdl = Motherboard.mixer.getLine(this);
|
||||
} catch (LineUnavailableException ex) {
|
||||
System.out.println("ERROR: Could not output sound: " + ex.getMessage());
|
||||
}
|
||||
if (sdl != null) {
|
||||
setRun(true);
|
||||
counter = 0;
|
||||
idleCycles = 0;
|
||||
level = 0;
|
||||
bufferPos = 0;
|
||||
if (playbackThread == null || !playbackThread.isAlive()) {
|
||||
playbackThread = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
int len;
|
||||
while (isRunning()) {
|
||||
// Motherboard.requestSpeed(this);
|
||||
len = bufferPos;
|
||||
if (len >= MIN_SAMPLE_PLAYBACK) {
|
||||
byte[] buffer;
|
||||
synchronized (bufferLock) {
|
||||
len = bufferPos;
|
||||
buffer = (currentBuffer == 1) ? soundBuffer1 : soundBuffer2;
|
||||
currentBuffer = (currentBuffer == 1) ? 2 : 1;
|
||||
bufferPos = 0;
|
||||
}
|
||||
sdl.write(buffer, 0, len);
|
||||
if (fileOutputActive && out != null) {
|
||||
try {
|
||||
out.write(buffer, 0, len);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Speaker.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// Wait 12.5 ms, which is 1/8 the total duration of the buffer
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(Speaker.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
Motherboard.cancelSpeedRequest(this);
|
||||
Motherboard.mixer.returnLine(this);
|
||||
|
||||
}
|
||||
});
|
||||
playbackThread.setName("Speaker playback");
|
||||
playbackThread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset idle counter whenever sound playback occurs
|
||||
*/
|
||||
public void resetIdle() {
|
||||
idleCycles = 0;
|
||||
if (!isRunning()) {
|
||||
resume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Motherboard cycle tick Every 23 ticks a sample will be added to the
|
||||
* buffer If the buffer is full, this will block until there is room in the
|
||||
* buffer, thus keeping the emulation in sync with the sound
|
||||
*/
|
||||
@Override
|
||||
public void tick() {
|
||||
if (!isRunning() || playbackThread == null) {
|
||||
return;
|
||||
}
|
||||
if (idleCycles++ >= MAX_IDLE_CYCLES) {
|
||||
suspend();
|
||||
}
|
||||
if (speakerBit) {
|
||||
level++;
|
||||
}
|
||||
counter += 1.0d;
|
||||
if (counter >= TICKS_PER_SAMPLE) {
|
||||
int sample = level * VOLUME;
|
||||
int bytes = SoundMixer.BITS >> 3;
|
||||
int shift = SoundMixer.BITS;
|
||||
|
||||
// Force emulator to wait until sound buffer has been processed
|
||||
int wait = 0;
|
||||
while (bufferPos >= BUFFER_SIZE) {
|
||||
if (wait++ > 1000) {
|
||||
Computer.pause();
|
||||
detach();
|
||||
Computer.resume();
|
||||
Motherboard.enableSpeaker = false;
|
||||
gripe("Sound playback is not working properly. Check your configuration and sound system to ensure they are set up properly.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Yield to other threads (e.g. sound) so that the buffer can drain
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException ex) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
byte[] buf;
|
||||
synchronized (bufferLock) {
|
||||
if (currentBuffer == 1) {
|
||||
buf = soundBuffer1;
|
||||
} else {
|
||||
buf = soundBuffer2;
|
||||
}
|
||||
int index = bufferPos;
|
||||
for (int i = 0; i < SoundMixer.BITS; i += 8, index++) {
|
||||
shift -= 8;
|
||||
buf[index] = buf[index + bytes] = (byte) ((sample >> shift) & 0x0ff);
|
||||
}
|
||||
|
||||
bufferPos += bytes * 2;
|
||||
}
|
||||
|
||||
// Set level back to 0
|
||||
level = 0;
|
||||
// Set counter to 0
|
||||
counter -= TICKS_PER_SAMPLE_FLOOR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a memory event listener for C03x for capturing speaker events
|
||||
*/
|
||||
private void configureListener() {
|
||||
Computer.getComputer().getMemory().addListener(listener);
|
||||
}
|
||||
|
||||
private void removeListener() {
|
||||
Computer.getComputer().getMemory().removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns "Speaker"
|
||||
*
|
||||
* @return "Speaker"
|
||||
*/
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "Speaker";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "spk";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void reconfigure() {
|
||||
if (soundBuffer1 != null && soundBuffer2 != null) {
|
||||
return;
|
||||
}
|
||||
BUFFER_SIZE = 10000 * (SoundMixer.BITS >> 3);
|
||||
MIN_SAMPLE_PLAYBACK = SoundMixer.BITS * 8;
|
||||
soundBuffer1 = new byte[BUFFER_SIZE];
|
||||
soundBuffer2 = new byte[BUFFER_SIZE];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
configureListener();
|
||||
resume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
removeListener();
|
||||
suspend();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,747 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.core.Font;
|
||||
import jace.core.Palette;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.core.Video;
|
||||
import jace.core.VideoWriter;
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This is the primary video rendering class, which provides all necessary video
|
||||
* writers for every display mode as well as managing the display mode (via
|
||||
* configureVideoMode). The quality of the color rendering is sub-par compared
|
||||
* to VideoNTSC.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class VideoDHGR extends Video {
|
||||
// Reorder bits 3,2,1,0 -> 0,3,2,1
|
||||
// Fixes double-hires color palette
|
||||
|
||||
public final static int flipNybble[] = {
|
||||
0, 2, 4, 6,
|
||||
8, 10, 12, 14,
|
||||
1, 3, 5, 7,
|
||||
9, 11, 13, 15
|
||||
};
|
||||
private static final boolean USE_GS_MOUSETEXT = false;
|
||||
private VideoWriter textPage1;
|
||||
private VideoWriter textPage2;
|
||||
private VideoWriter loresPage1;
|
||||
private VideoWriter loresPage2;
|
||||
private VideoWriter hiresPage1;
|
||||
private VideoWriter hiresPage2;
|
||||
// Special 80-column modes
|
||||
private VideoWriter text80Page1;
|
||||
private VideoWriter text80Page2;
|
||||
private VideoWriter dloresPage1;
|
||||
private VideoWriter dloresPage2;
|
||||
private VideoWriter dhiresPage1;
|
||||
private VideoWriter dhiresPage2;
|
||||
// Mixed mode
|
||||
private VideoWriter mixed;
|
||||
private VideoWriter currentGraphicsWriter = null;
|
||||
private VideoWriter currentTextWriter = null;
|
||||
|
||||
/**
|
||||
* Creates a new instance of VideoDHGR
|
||||
*/
|
||||
public VideoDHGR() {
|
||||
hiresPage1 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (hiresOffset[y] + 0x02000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayHires(screen, xOffset, y, yGraphicsOffset + 0x02000);
|
||||
}
|
||||
};
|
||||
hiresPage2 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (hiresOffset[y] + 0x04000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayHires(screen, xOffset, y, yGraphicsOffset + 0x04000);
|
||||
}
|
||||
};
|
||||
dhiresPage1 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (hiresOffset[y] + 0x02000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayDoubleHires(screen, xOffset, y, yGraphicsOffset + 0x02000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoWriter actualWriter() {
|
||||
return hiresPage1;
|
||||
}
|
||||
};
|
||||
dhiresPage2 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (hiresOffset[y] + 0x04000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayDoubleHires(screen, xOffset, y, yGraphicsOffset + 0x04000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoWriter actualWriter() {
|
||||
return hiresPage2;
|
||||
}
|
||||
};
|
||||
textPage1 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (textOffset[y] + 0x0400);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayText(screen, xOffset, y, yTextOffset + 0x0400);
|
||||
}
|
||||
};
|
||||
textPage2 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (textOffset[y] + 0x0800);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayText(screen, xOffset, y, yTextOffset + 0x0800);
|
||||
}
|
||||
};
|
||||
text80Page1 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (textOffset[y] + 0x0400);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayText80(screen, xOffset, y, yTextOffset + 0x0400);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoWriter actualWriter() {
|
||||
return textPage1;
|
||||
}
|
||||
};
|
||||
text80Page2 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (textOffset[y] + 0x0800);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayText80(screen, xOffset, y, yTextOffset + 0x0800);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoWriter actualWriter() {
|
||||
return textPage2;
|
||||
}
|
||||
};
|
||||
loresPage1 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (textOffset[y] + 0x0400);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayLores(screen, xOffset, y, yTextOffset + 0x0400);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoWriter actualWriter() {
|
||||
return textPage1;
|
||||
}
|
||||
};
|
||||
loresPage2 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (textOffset[y] + 0x0800);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayLores(screen, xOffset, y, yTextOffset + 0x0800);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoWriter actualWriter() {
|
||||
return textPage2;
|
||||
}
|
||||
};
|
||||
dloresPage1 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (textOffset[y] + 0x0400);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayDoubleLores(screen, xOffset, y, yTextOffset + 0x0400);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoWriter actualWriter() {
|
||||
return textPage1;
|
||||
}
|
||||
};
|
||||
dloresPage2 = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return (textOffset[y] + 0x0800);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayDoubleLores(screen, xOffset, y, yTextOffset + 0x0800);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoWriter actualWriter() {
|
||||
return textPage2;
|
||||
}
|
||||
};
|
||||
mixed = new VideoWriter() {
|
||||
@Override
|
||||
public int getYOffset(int y) {
|
||||
return actualWriter().getYOffset(y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset) {
|
||||
displayMixed(screen, xOffset, y, yTextOffset, yGraphicsOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markDirty(int y) {
|
||||
actualWriter().actualWriter().markDirty(y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearDirty(int y) {
|
||||
actualWriter().actualWriter().clearDirty(y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRowDirty(int y) {
|
||||
return actualWriter().actualWriter().isRowDirty(y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoWriter actualWriter() {
|
||||
if (y < 160) {
|
||||
return currentGraphicsWriter;
|
||||
}
|
||||
return currentTextWriter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMixed() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
registerDirtyFlagChecks();
|
||||
}
|
||||
// color burst per byte (chat mauve compatibility)
|
||||
boolean[] useColor = new boolean[80];
|
||||
|
||||
protected void displayDoubleHires(BufferedImage screen, int xOffset, int y, int rowAddress) {
|
||||
// Skip odd columns since this does two at once
|
||||
if ((xOffset & 0x01) == 1) {
|
||||
return;
|
||||
}
|
||||
int b1 = ((RAM128k) Computer.getComputer().getMemory()).getAuxVideoMemory().readByte(rowAddress + xOffset);
|
||||
int b2 = ((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset);
|
||||
int b3 = ((RAM128k) Computer.getComputer().getMemory()).getAuxVideoMemory().readByte(rowAddress + xOffset + 1);
|
||||
int b4 = ((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset + 1);
|
||||
int useColOffset = xOffset << 1;
|
||||
// This shouldn't be necessary but prevents an index bounds exception when graphics modes are flipped (Race condition?)
|
||||
if (useColOffset >= 77) {
|
||||
useColOffset = 76;
|
||||
}
|
||||
useColor[useColOffset] = (b1 & 0x80) != 0;
|
||||
useColor[useColOffset + 1] = (b2 & 0x80) != 0;
|
||||
useColor[useColOffset + 2] = (b3 & 0x80) != 0;
|
||||
useColor[useColOffset + 3] = (b4 & 0x80) != 0;
|
||||
int dhgrWord = 0x07f & b1;
|
||||
dhgrWord |= (0x07f & b2) << 7;
|
||||
dhgrWord |= (0x07f & b3) << 14;
|
||||
dhgrWord |= (0x07f & b4) << 21;
|
||||
showDhgr(screen, times14[xOffset], y, dhgrWord);
|
||||
}
|
||||
boolean extraHalfBit = false;
|
||||
|
||||
protected void displayHires(BufferedImage screen, int xOffset, int y, int rowAddress) {
|
||||
// Skip odd columns since this does two at once
|
||||
if ((xOffset & 0x01) == 1) {
|
||||
return;
|
||||
}
|
||||
int b1 = 0x0ff & ((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset);
|
||||
int b2 = 0x0ff & ((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset + 1);
|
||||
int dhgrWord = hgrToDhgr[(extraHalfBit && xOffset > 0) ? b1 | 0x0100 : b1][b2];
|
||||
extraHalfBit = (dhgrWord & 0x10000000) != 0;
|
||||
showDhgr(screen, times14[xOffset], y, dhgrWord & 0xfffffff);
|
||||
// If you want monochrome, use this instead...
|
||||
// showBW(screen, times14[xOffset], y, dhgrWord);
|
||||
}
|
||||
// Take two consecutive bytes and double them, taking hi-bit into account
|
||||
// This should yield a 28-bit word of 7 color dhgr pixels
|
||||
// This looks like crap on text...
|
||||
static final int[][] hgrToDhgr;
|
||||
// Take two consecutive bytes and double them, disregarding hi-bit
|
||||
// Useful for text mode
|
||||
static final int[][] hgrToDhgrBW;
|
||||
static final int[] times14;
|
||||
static final int[] flipBits;
|
||||
|
||||
static {
|
||||
// complete reverse of 8 bits
|
||||
flipBits = new int[256];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
flipBits[i] = (((i * 0x0802 & 0x22110) | (i * 0x8020 & 0x88440)) * 0x10101 >> 16) & 0x0ff;
|
||||
}
|
||||
|
||||
times14 = new int[40];
|
||||
for (int i = 0; i < 40; i++) {
|
||||
times14[i] = i * 14;
|
||||
}
|
||||
hgrToDhgr = new int[512][256];
|
||||
hgrToDhgrBW = new int[256][256];
|
||||
for (int bb1 = 0; bb1 < 512; bb1++) {
|
||||
for (int bb2 = 0; bb2 < 256; bb2++) {
|
||||
int value = ((bb1 & 0x0181) >= 0x0101) ? 1 : 0;
|
||||
int b1 = byteDoubler((byte) (bb1 & 0x07f));
|
||||
if ((bb1 & 0x080) != 0) {
|
||||
b1 <<= 1;
|
||||
}
|
||||
int b2 = byteDoubler((byte) (bb2 & 0x07f));
|
||||
if ((bb2 & 0x080) != 0) {
|
||||
b2 <<= 1;
|
||||
}
|
||||
if ((bb1 & 0x040) == 0x040 && (bb2 & 1) != 0) {
|
||||
b2 |= 1;
|
||||
}
|
||||
value |= b1 | (b2 << 14);
|
||||
if ((bb2 & 0x040) != 0) {
|
||||
value |= 0x10000000;
|
||||
}
|
||||
hgrToDhgr[bb1][bb2] = value;
|
||||
hgrToDhgrBW[bb1 & 0x0ff][bb2] =
|
||||
byteDoubler((byte) bb1) | (byteDoubler((byte) bb2) << 14);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void displayLores(BufferedImage screen, int xOffset, int y, int rowAddress) {
|
||||
int c1 = ((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
|
||||
if ((y & 7) < 4) {
|
||||
c1 &= 15;
|
||||
} else {
|
||||
c1 >>= 4;
|
||||
}
|
||||
DataBuffer b = screen.getRaster().getDataBuffer();
|
||||
int yOffset = xyOffset[y][times14[xOffset]];
|
||||
int color = Palette.color[c1].getRGB();
|
||||
// Unrolled loop, faster
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
}
|
||||
|
||||
private void displayDoubleLores(BufferedImage screen, int xOffset, int y, int rowAddress) {
|
||||
int c1 = ((RAM128k) Computer.getComputer().getMemory()).getAuxVideoMemory().readByte(rowAddress + xOffset) & 0x0FF;
|
||||
int c2 = ((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
|
||||
if ((y & 7) < 4) {
|
||||
c1 &= 15;
|
||||
c2 &= 15;
|
||||
} else {
|
||||
c1 >>= 4;
|
||||
c2 >>= 4;
|
||||
}
|
||||
DataBuffer b = screen.getRaster().getDataBuffer();
|
||||
int yOffset = xyOffset[y][times14[xOffset]];
|
||||
int color = Palette.color[c1].getRGB();
|
||||
int color2 = Palette.color[c2].getRGB();
|
||||
// Unrolled loop, faster
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color2);
|
||||
b.setElem(yOffset++, color2);
|
||||
b.setElem(yOffset++, color2);
|
||||
b.setElem(yOffset++, color2);
|
||||
b.setElem(yOffset++, color2);
|
||||
b.setElem(yOffset++, color2);
|
||||
b.setElem(yOffset++, color2);
|
||||
}
|
||||
boolean flashInverse = false;
|
||||
int flashTimer = 0;
|
||||
int FLASH_SPEED = 16; // UTAIIe:8-13,P7 - FLASH toggles every 16 scans
|
||||
int[] currentCharMap = CHAR_MAP1;
|
||||
static final int[] CHAR_MAP1;
|
||||
static final int[] CHAR_MAP2;
|
||||
static final int[] CHAR_MAP3;
|
||||
|
||||
static {
|
||||
// Generate screen text lookup maps ahead of time
|
||||
// ALTCHR clear
|
||||
// 00-3F - Inverse characters (uppercase only) "@P 0"
|
||||
// 40-7F - Flashing characters (uppercase only) "@P 0"
|
||||
// 80-BF - Normal characters (uppercase only) "@P 0"
|
||||
// C0-DF - Normal characters (repeat 80-9F) "@P"
|
||||
// E0-FF - Normal characters (lowercase) "`p"
|
||||
|
||||
// ALTCHR set
|
||||
// 00-3f - Inverse characters (uppercase only) "@P 0"
|
||||
// 40-5f - Mousetext (//gs alts are at 0x46 and 0x47, swap with 0x11 and 0x12 for //e and //c)
|
||||
// 60-7f - Inverse characters (lowercase only)
|
||||
// 80-BF - Normal characters (uppercase only)
|
||||
// C0-DF - Normal characters (repeat 80-9F)
|
||||
// E0-FF - Normal characters (lowercase)
|
||||
|
||||
|
||||
// MAP1: Normal map, flash inverse = false
|
||||
CHAR_MAP1 = new int[256];
|
||||
// MAP2: Normal map, flash inverse = true
|
||||
CHAR_MAP2 = new int[256];
|
||||
// MAP3: Alt map, mousetext mode
|
||||
CHAR_MAP3 = new int[256];
|
||||
for (int b = 0; b < 256; b++) {
|
||||
int mod = b % 0x020;
|
||||
// Inverse
|
||||
if (b < 0x020) {
|
||||
CHAR_MAP1[b] = mod + 0x0c0;
|
||||
CHAR_MAP2[b] = mod + 0x0c0;
|
||||
CHAR_MAP3[b] = mod + 0x0c0;
|
||||
} else if (b < 0x040) {
|
||||
CHAR_MAP1[b] = mod + 0x0a0;
|
||||
CHAR_MAP2[b] = mod + 0x0a0;
|
||||
CHAR_MAP3[b] = mod + 0x0a0;
|
||||
} else if (b < 0x060) {
|
||||
// Flash/Mouse
|
||||
CHAR_MAP1[b] = mod + 0x0c0;
|
||||
CHAR_MAP2[b] = mod + 0x040;
|
||||
if (!USE_GS_MOUSETEXT && mod == 6) {
|
||||
CHAR_MAP3[b] = 0x011;
|
||||
} else if (!USE_GS_MOUSETEXT && mod == 7) {
|
||||
CHAR_MAP3[b] = 0x012;
|
||||
} else {
|
||||
CHAR_MAP3[b] = mod + 0x080;
|
||||
}
|
||||
} else if (b < 0x080) {
|
||||
// Flash/Inverse lowercase
|
||||
CHAR_MAP1[b] = mod + 0x0a0;
|
||||
CHAR_MAP2[b] = mod + 0x020;
|
||||
CHAR_MAP3[b] = mod + 0x0e0;
|
||||
} else if (b < 0x0a0) {
|
||||
// Normal uppercase
|
||||
CHAR_MAP1[b] = mod + 0x040;
|
||||
CHAR_MAP2[b] = mod + 0x040;
|
||||
CHAR_MAP3[b] = mod + 0x040;
|
||||
} else if (b < 0x0c0) {
|
||||
// Normal uppercase
|
||||
CHAR_MAP1[b] = mod + 0x020;
|
||||
CHAR_MAP2[b] = mod + 0x020;
|
||||
CHAR_MAP3[b] = mod + 0x020;
|
||||
} else if (b < 0x0e0) {
|
||||
// Normal uppercase (repeat)
|
||||
CHAR_MAP1[b] = mod + 0x040;
|
||||
CHAR_MAP2[b] = mod + 0x040;
|
||||
CHAR_MAP3[b] = mod + 0x040;
|
||||
} else {
|
||||
// Normal lowercase
|
||||
CHAR_MAP1[b] = mod + 0x060;
|
||||
CHAR_MAP2[b] = mod + 0x060;
|
||||
CHAR_MAP3[b] = mod + 0x060;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vblankStart() {
|
||||
// ALTCHR set only affects character mapping and disables FLASH.
|
||||
if (SoftSwitches.ALTCH.isOn()) {
|
||||
currentCharMap = CHAR_MAP3;
|
||||
} else {
|
||||
flashTimer--;
|
||||
if (flashTimer <= 0) {
|
||||
markFlashDirtyBits();
|
||||
flashTimer = FLASH_SPEED;
|
||||
flashInverse = !flashInverse;
|
||||
if (flashInverse) {
|
||||
currentCharMap = CHAR_MAP2;
|
||||
} else {
|
||||
currentCharMap = CHAR_MAP1;
|
||||
}
|
||||
}
|
||||
}
|
||||
super.vblankStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vblankEnd() {
|
||||
}
|
||||
|
||||
private int getFontChar(byte b) {
|
||||
return currentCharMap[b & 0x0ff];
|
||||
}
|
||||
|
||||
protected void displayText(BufferedImage screen, int xOffset, int y, int rowAddress) {
|
||||
// Skip odd columns since this does two at once
|
||||
if ((xOffset & 0x01) == 1) {
|
||||
return;
|
||||
}
|
||||
int yOffset = y & 7;
|
||||
byte byte2 = ((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset + 1);
|
||||
int c1 = getFontChar(((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset));
|
||||
int c2 = getFontChar(byte2);
|
||||
int b1 = Font.getByte(c1, yOffset);
|
||||
int b2 = Font.getByte(c2, yOffset);
|
||||
// Why is this getting inversed now? Bug in hgrToDhgrBW?
|
||||
// Nick says: are you getting confused because the //e video ROM is inverted? (1=black)
|
||||
int out = hgrToDhgrBW[b1][b2];
|
||||
showBW(screen, times14[xOffset], y, out);
|
||||
}
|
||||
|
||||
protected void displayText80(BufferedImage screen, int xOffset, int y, int rowAddress) {
|
||||
// Skip odd columns since this does two at once
|
||||
if ((xOffset & 0x01) == 1) {
|
||||
return;
|
||||
}
|
||||
int yOffset = y & 7;
|
||||
int c1 = getFontChar(((RAM128k) Computer.getComputer().getMemory()).getAuxVideoMemory().readByte(rowAddress + xOffset));
|
||||
int c2 = getFontChar(((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset));
|
||||
int c3 = getFontChar(((RAM128k) Computer.getComputer().getMemory()).getAuxVideoMemory().readByte(rowAddress + xOffset + 1));
|
||||
int c4 = getFontChar(((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset + 1));
|
||||
int bits = Font.getByte(c1, yOffset) | (Font.getByte(c2, yOffset) << 7)
|
||||
| (Font.getByte(c3, yOffset) << 14) | (Font.getByte(c4, yOffset) << 21);
|
||||
showBW(screen, times14[xOffset], y, bits);
|
||||
}
|
||||
|
||||
private void displayMixed(BufferedImage screen, int xOffset, int y, int textOffset, int graphicsOffset) {
|
||||
mixed.actualWriter().displayByte(screen, xOffset, y, textOffset, graphicsOffset);
|
||||
}
|
||||
protected boolean hiresMode = false;
|
||||
public boolean dhgrMode = false;
|
||||
|
||||
@Override
|
||||
public void configureVideoMode() {
|
||||
boolean page2 = SoftSwitches.PAGE2.isOn() && SoftSwitches._80STORE.isOff();
|
||||
dhgrMode = SoftSwitches._80COL.getState() && SoftSwitches.DHIRES.getState() && SoftSwitches.HIRES.getState();
|
||||
currentTextWriter =
|
||||
SoftSwitches._80COL.getState()
|
||||
? page2
|
||||
? text80Page2 : text80Page1
|
||||
: page2
|
||||
? textPage2 : textPage1;
|
||||
currentGraphicsWriter =
|
||||
SoftSwitches._80COL.getState() && SoftSwitches.DHIRES.getState()
|
||||
? SoftSwitches.HIRES.getState()
|
||||
? page2
|
||||
? dhiresPage2 : dhiresPage1
|
||||
: page2
|
||||
? dloresPage2 : dloresPage1
|
||||
: SoftSwitches.HIRES.getState()
|
||||
? page2
|
||||
? hiresPage2 : hiresPage1
|
||||
: page2
|
||||
? loresPage2 : loresPage1;
|
||||
setCurrentWriter(
|
||||
SoftSwitches.TEXT.getState() ? currentTextWriter
|
||||
: SoftSwitches.MIXED.getState() ? mixed
|
||||
: currentGraphicsWriter);
|
||||
hiresMode = !SoftSwitches.DHIRES.getState();
|
||||
}
|
||||
|
||||
protected void showDhgr(BufferedImage screen, int xOffset, int y, int dhgrWord) {
|
||||
//Graphics2D g = (Graphics2D) screen.getGraphics();
|
||||
DataBuffer b = screen.getRaster().getDataBuffer();
|
||||
int yOffset = xyOffset[y][xOffset];
|
||||
try {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
int color = Palette.color[flipNybble[dhgrWord & 15]].getRGB();
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
b.setElem(yOffset++, color);
|
||||
dhgrWord >>= 4;
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
Logger.getLogger(getClass().getName()).warning("Went out of bounds in video display");
|
||||
}
|
||||
}
|
||||
static final int BLACK = Color.BLACK.getRGB();
|
||||
static final int WHITE = Color.WHITE.getRGB();
|
||||
static final int[][] xyOffset;
|
||||
|
||||
static {
|
||||
xyOffset = new int[192][560];
|
||||
for (int y = 0; y < 192; y++) {
|
||||
for (int x = 0; x < 560; x++) {
|
||||
xyOffset[y][x] = y * 560 + x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void showBW(BufferedImage screen, int xOffset, int y, int dhgrWord) {
|
||||
int color = 0;
|
||||
// Using the data buffer directly is about 15 times faster than setRGB
|
||||
// This is because setRGB does extra (useless) color model logic
|
||||
// For that matter even Graphics.drawLine is faster than setRGB!
|
||||
DataBuffer b = screen.getRaster().getDataBuffer();
|
||||
// This is equivilant to y*560 but is 5% faster
|
||||
// Also, adding xOffset now makes it additionally 5% faster
|
||||
//int yOffset = ((y << 4) + (y << 5) + (y << 9))+xOffset;
|
||||
|
||||
//is this lookup faster?
|
||||
int yOffset = xyOffset[y][xOffset];
|
||||
for (int i = 0; i < 28; i++) {
|
||||
// yOffset++ is used instead of yOffset+i, because it is faster
|
||||
b.setElem(yOffset++, (dhgrWord & 1) == 1 ? WHITE : BLACK);
|
||||
dhgrWord >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void doPostDraw() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "DHGR-Capable Video";
|
||||
}
|
||||
|
||||
private void markFlashDirtyBits() {
|
||||
// TODO: Be smarter about detecting where flash is used... one day...
|
||||
for (int row = 0; row < 192; row++) {
|
||||
currentTextWriter.markDirty(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerDirtyFlagChecks() {
|
||||
((RAM128k) Computer.getComputer().getMemory()).addListener(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0400);
|
||||
setScopeEnd(0x0bff);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
int row = textRowLookup[e.getAddress() & 0x03ff];
|
||||
// int row = identifyTextRow(e.getAddress() & 0x03ff);
|
||||
if (row > 23) {
|
||||
return;
|
||||
}
|
||||
VideoWriter tmark = (e.getAddress() < 0x0800) ? textPage1 : textPage2;
|
||||
row <<= 3;
|
||||
int yy = row + 8;
|
||||
for (int y = row; y < yy; y++) {
|
||||
tmark.markDirty(y);
|
||||
}
|
||||
}
|
||||
});
|
||||
((RAM128k) Computer.getComputer().getMemory()).addListener(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x2000);
|
||||
setScopeEnd(0x5fff);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
int row = hiresRowLookup[e.getAddress() & 0x01fff];
|
||||
// int row = identifyHiresRow(e.getAddress() & 0x03fff);
|
||||
if (row < 0 || row >= 192) {
|
||||
return;
|
||||
}
|
||||
VideoWriter mark = (e.getAddress() < 0x04000) ? hiresPage1 : hiresPage2;
|
||||
mark.markDirty(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
// Do nothing (for now)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hblankStart(BufferedImage screen, int y, boolean isDirty) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
|
@ -0,0 +1,433 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e;
|
||||
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.core.Video;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides a clean color monitor simulation, complete with text-friendly
|
||||
* palette and mixed color/bw (mode 7) rendering. This class extends the
|
||||
* VideoDHGR class to provide all necessary video writers and other rendering
|
||||
* mechanics, and then overrides the actual output routines (showBW, showDhgr) with more suitable
|
||||
* (and much prettier) alternatives. Rather than draw to the video buffer every
|
||||
* cycle, rendered screen info is pushed into a buffer with mask bits (to
|
||||
* indicate B&W vs color) And the actual conversion happens at the end of the
|
||||
* scanline during the HBLANK period. This video rendering was inspired by
|
||||
* Blargg but was ultimately rewritten from scratch once the color palette was
|
||||
* implemented.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class VideoNTSC extends VideoDHGR {
|
||||
|
||||
@ConfigurableField(name = "Text palette", shortName = "textPalette", defaultValue = "false", description = "Use text-friendly color palette")
|
||||
public static boolean useTextPalette = false;
|
||||
static int activePalette[][];
|
||||
@ConfigurableField(name = "Video 7", shortName = "video7", defaultValue = "true", description = "Enable Video 7 RGB rendering support")
|
||||
public static boolean enableVideo7 = false;
|
||||
// Scanline represents 560 bits, divided up into 28-bit words
|
||||
int[] scanline = new int[20];
|
||||
static int[] divBy28 = new int[560];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 560; i++) {
|
||||
divBy28[i] = i / 28;
|
||||
}
|
||||
}
|
||||
int pos = 0;
|
||||
int lastKnownY = -1;
|
||||
boolean colorActive = false;
|
||||
int rowStart = 0;
|
||||
|
||||
@Override
|
||||
protected void showBW(BufferedImage screen, int xOffset, int y, int dhgrWord) {
|
||||
if (lastKnownY != y) {
|
||||
lastKnownY = y;
|
||||
pos = rowStart = divBy28[xOffset];
|
||||
colorActive = false;
|
||||
} else {
|
||||
if (pos > 20) pos-=20;
|
||||
}
|
||||
doDisplay(screen, xOffset, y, dhgrWord);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showDhgr(BufferedImage screen, int xOffset, int y, int dhgrWord) {
|
||||
if (lastKnownY != y) {
|
||||
lastKnownY = y;
|
||||
pos = rowStart = divBy28[xOffset];
|
||||
colorActive = true;
|
||||
}
|
||||
doDisplay(screen, xOffset, y, dhgrWord);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayLores(BufferedImage screen, int xOffset, int y, int rowAddress) {
|
||||
// Skip odd columns since this does two at once
|
||||
if ((xOffset & 0x01) == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastKnownY != y) {
|
||||
lastKnownY = y;
|
||||
pos = rowStart = divBy28[xOffset];
|
||||
colorActive = true;
|
||||
}
|
||||
int c1 = ((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset) & 0x0FF;
|
||||
if ((y & 7) < 4) {
|
||||
c1 &= 15;
|
||||
} else {
|
||||
c1 >>= 4;
|
||||
}
|
||||
int c2 = ((RAM128k) Computer.getComputer().getMemory()).getMainMemory().readByte(rowAddress + xOffset + 1) & 0x0FF;
|
||||
if ((y & 7) < 4) {
|
||||
c2 &= 15;
|
||||
} else {
|
||||
c2 >>= 4;
|
||||
}
|
||||
int pat = c1 | c1 << 4 | c1 << 8 | (c1 & 3) << 12;
|
||||
pat |= (c2 & 12) << 12 | c2 << 16 | c2 << 20 | c2 << 24;
|
||||
scanline[pos++] = pat;
|
||||
}
|
||||
|
||||
private void doDisplay(BufferedImage screen, int xOffset, int y, int dhgrWord) {
|
||||
if (pos >= 20) pos -= 20;
|
||||
scanline[pos] = dhgrWord;
|
||||
pos++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hblankStart(BufferedImage screen, int y, boolean isDirty) {
|
||||
if (isDirty) {
|
||||
renderScanline(screen, y);
|
||||
}
|
||||
lastKnownY = -1;
|
||||
}
|
||||
// Offset is based on location in graphics buffer that corresponds with the row and
|
||||
// a number (0-20) that represents how much of the scanline was rendered
|
||||
// This is based off the xyOffset but is different because of P
|
||||
static int pyOffset[][];
|
||||
|
||||
static {
|
||||
pyOffset = new int[192][21];
|
||||
for (int y = 0; y < 192; y++) {
|
||||
for (int p = 0; p < 21; p++) {
|
||||
pyOffset[y][p] = (y * 560) + (p * 28);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderScanline(BufferedImage screen, int y) {
|
||||
DataBuffer b = screen.getRaster().getDataBuffer();
|
||||
try {
|
||||
// This is equivilant to y*560 but is 5% faster
|
||||
//int yOffset = ((y << 4) + (y << 5) + (y << 9))+xOffset;
|
||||
|
||||
// For some reason this jumps up to 40 in the wayout title screen (?)
|
||||
int p = pyOffset[y][rowStart];
|
||||
if (rowStart > 0) {
|
||||
getCurrentWriter().markDirty(y);
|
||||
}
|
||||
// Reset scanline position
|
||||
if (colorActive && (!dhgrMode || !enableVideo7 || graphicsMode.isColor())) {
|
||||
int byteCounter = 0;
|
||||
for (int s = rowStart; s < 20; s++) {
|
||||
int add = 0;
|
||||
int bits;
|
||||
if (hiresMode) {
|
||||
bits = scanline[s] << 2;
|
||||
if (s > 0) {
|
||||
bits |= (scanline[s - 1] >> 26) & 3;
|
||||
}
|
||||
} else {
|
||||
bits = scanline[s] << 3;
|
||||
if (s > 0) {
|
||||
bits |= (scanline[s - 1] >> 25) & 7;
|
||||
}
|
||||
}
|
||||
if (s < 19) {
|
||||
add = (scanline[s + 1] & 7);
|
||||
}
|
||||
boolean isBW = false;
|
||||
if (enableVideo7 && dhgrMode && graphicsMode == rgbMode.mix) {
|
||||
for (int i = 0; i < 28; i++) {
|
||||
if (i % 7 == 0) {
|
||||
isBW = !hiresMode && !useColor[byteCounter];
|
||||
byteCounter++;
|
||||
}
|
||||
if (isBW) {
|
||||
b.setElem(p++, ((bits & 0x8) == 0) ? BLACK : WHITE);
|
||||
} else {
|
||||
b.setElem(p++, activePalette[i % 4][bits & 0x07f]);
|
||||
}
|
||||
bits >>= 1;
|
||||
if (i == 20) {
|
||||
bits |= add << (hiresMode ? 9 : 10);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 28; i++) {
|
||||
b.setElem(p++, activePalette[i % 4][bits & 0x07f]);
|
||||
bits >>= 1;
|
||||
if (i == 20) {
|
||||
bits |= add << (hiresMode ? 9 : 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int s = rowStart; s < 20; s++) {
|
||||
int bits = scanline[s];
|
||||
for (int i = 0; i < 28; i++) {
|
||||
b.setElem(p++, ((bits & 1) == 0) ? BLACK : WHITE);
|
||||
bits >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
// Flag this scanline to be written again, something screwed up!
|
||||
// This only happens during a race condition when the video
|
||||
// mode changes at just the wrong time.
|
||||
getCurrentWriter().markDirty(y);
|
||||
}
|
||||
}
|
||||
// y Range [0,1]
|
||||
public static final double MIN_Y = 0;
|
||||
public static final double MAX_Y = 1;
|
||||
// i Range [-0.5957, 0.5957]
|
||||
public static final double MAX_I = 0.5957;
|
||||
// q Range [-0.5226, 0.5226]
|
||||
public static final double MAX_Q = 0.5226;
|
||||
static final int solidPalette[][] = new int[4][128];
|
||||
static final int textPalette[][] = new int[4][128];
|
||||
static final double[][] yiq = {
|
||||
{0.0, 0.0, 0.0}, //0000 0
|
||||
{0.25, 0.5, 0.5}, //0001 1
|
||||
{0.25, -0.5, 0.5}, //0010 2
|
||||
{0.5, 0.0, 1.0}, //0011 3 +Q
|
||||
{0.25, -0.5, -0.5}, //0100 4
|
||||
{0.5, 0.0, 0.0}, //0101 5
|
||||
{0.5, -1.0, 0.0}, //0110 6 +I
|
||||
{0.75, -0.5, 0.5}, //0111 7
|
||||
{0.25, 0.5, -0.5}, //1000 8
|
||||
{0.5, 1.0, 0.0}, //1001 9 -I
|
||||
{0.5, 0.0, 0.0}, //1010 a
|
||||
{0.75, 0.5, 0.5}, //1011 b
|
||||
{0.5, 0.0, -1.0}, //1100 c -Q
|
||||
{0.75, 0.5, -0.5}, //1101 d
|
||||
{0.75, -0.5, -0.5}, //1110 e
|
||||
{1.0, 0.0, 0.0}, //1111 f
|
||||
};
|
||||
|
||||
static {
|
||||
int maxLevel = 10;
|
||||
for (int offset = 0; offset < 4; offset++) {
|
||||
for (int pattern = 0; pattern < 128; pattern++) {
|
||||
int level = (pattern & 1)
|
||||
+ ((pattern >> 1) & 1) * 1
|
||||
+ ((pattern >> 2) & 1) * 2
|
||||
+ ((pattern >> 3) & 1) * 4
|
||||
+ ((pattern >> 4) & 1) * 2
|
||||
+ ((pattern >> 5) & 1) * 1;
|
||||
|
||||
int col = (pattern >> 2) & 0x0f;
|
||||
for (int rot = 0; rot < offset; rot++) {
|
||||
col = ((col & 8) >> 3) | ((col << 1) & 0x0f);
|
||||
}
|
||||
double y1 = yiq[col][0];
|
||||
double y2 = ((double) level / (double) maxLevel);
|
||||
solidPalette[offset][pattern] = (255 << 24) | yiqToRgb(y1, yiq[col][1] * MAX_I, yiq[col][2] * MAX_Q);
|
||||
textPalette[offset][pattern] = (255 << 24) | yiqToRgb(y2, yiq[col][1] * MAX_I, yiq[col][2] * MAX_Q);
|
||||
}
|
||||
}
|
||||
// Avoid NPE just in case.
|
||||
activePalette = solidPalette;
|
||||
}
|
||||
|
||||
static public int yiqToRgb(double y, double i, double q) {
|
||||
int r = (int) (normalize((y + 0.956 * i + 0.621 * q), 0, 1) * 255);
|
||||
int g = (int) (normalize((y - 0.272 * i - 0.647 * q), 0, 1) * 255);
|
||||
int b = (int) (normalize((y - 1.105 * i + 1.702 * q), 0, 1) * 255);
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
public static double normalize(double x, double minX, double maxX) {
|
||||
if (x < minX) {
|
||||
return minX;
|
||||
}
|
||||
if (x > maxX) {
|
||||
return maxX;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
detach();
|
||||
activePalette = useTextPalette ? textPalette : solidPalette;
|
||||
super.reconfigure();
|
||||
attach();
|
||||
}
|
||||
// The following section captures changes to the RGB mode
|
||||
// The details of this are in Brodener's patent application #4631692
|
||||
// http://www.freepatentsonline.com/4631692.pdf
|
||||
// as well as the AppleColor adapter card manual
|
||||
// http://apple2.info/download/Ext80ColumnAppleColorCardHR.pdf
|
||||
rgbMode graphicsMode = rgbMode.color;
|
||||
|
||||
public static enum rgbMode {
|
||||
|
||||
color(true), mix(true), bw(false), _160col(false);
|
||||
boolean colorMode = false;
|
||||
|
||||
rgbMode(boolean c) {
|
||||
this.colorMode = c;
|
||||
}
|
||||
|
||||
public boolean isColor() {
|
||||
return colorMode;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum ModeStateChanges {
|
||||
|
||||
SET_AN3, CLEAR_AN3, SET_80, CLEAR_80;
|
||||
}
|
||||
boolean f1 = true;
|
||||
boolean f2 = true;
|
||||
boolean an3 = true;
|
||||
|
||||
public void rgbStateChange(ModeStateChanges state) {
|
||||
switch (state) {
|
||||
case CLEAR_80:
|
||||
break;
|
||||
case CLEAR_AN3:
|
||||
an3 = false;
|
||||
break;
|
||||
case SET_80:
|
||||
break;
|
||||
case SET_AN3:
|
||||
if (!an3) {
|
||||
f2 = f1;
|
||||
f1 = SoftSwitches._80COL.getState();
|
||||
}
|
||||
an3 = true;
|
||||
break;
|
||||
}
|
||||
// This is the more technically correct implementation except for two issues:
|
||||
// 1) 160-column mode isn't implemented so it's not worth bothering to capture that state
|
||||
// 2) A lot of programs are clueless about RGB modes so it's good to default to normal color mode
|
||||
// graphicsMode = f1 ? (f2 ? rgbMode.color : rgbMode.mix) : (f2 ? rgbMode._160col : rgbMode.bw);
|
||||
graphicsMode = f1 ? (f2 ? rgbMode.color : rgbMode.mix) : (f2 ? rgbMode.color : rgbMode.bw);
|
||||
// System.out.println(state + ": "+ graphicsMode);
|
||||
}
|
||||
// These catch changes to the RGB mode to toggle between color, BW and mixed
|
||||
static Set<RAMListener> rgbStateListeners = new HashSet<>();
|
||||
|
||||
static {
|
||||
rgbStateListeners.add(new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0c05e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
Video v = Computer.getComputer().getVideo();
|
||||
if (v instanceof VideoNTSC) {
|
||||
((VideoNTSC) v).rgbStateChange(ModeStateChanges.CLEAR_AN3);
|
||||
}
|
||||
}
|
||||
});
|
||||
rgbStateListeners.add(new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0c05f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
Video v = Computer.getComputer().getVideo();
|
||||
if (v instanceof VideoNTSC) {
|
||||
((VideoNTSC) v).rgbStateChange(ModeStateChanges.SET_AN3);
|
||||
}
|
||||
}
|
||||
});
|
||||
rgbStateListeners.add(new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0fa62);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
Video v = Computer.getComputer().getVideo();
|
||||
if (v instanceof VideoNTSC) {
|
||||
// When reset hook is called, reset the graphics mode
|
||||
// This is useful in case a program is running that
|
||||
// is totally clueless how to set the RGB state correctly.
|
||||
((VideoNTSC) v).f1 = true;
|
||||
((VideoNTSC) v).f2 = true;
|
||||
((VideoNTSC) v).an3 = false;
|
||||
((VideoNTSC) v).graphicsMode = rgbMode.color;
|
||||
}
|
||||
}
|
||||
});
|
||||
rgbStateListeners.add(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0c00d);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
Video v = Computer.getComputer().getVideo();
|
||||
if (v instanceof VideoNTSC) {
|
||||
((VideoNTSC) v).rgbStateChange(ModeStateChanges.SET_80);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
super.detach();
|
||||
rgbStateListeners.stream().forEach((l) -> {
|
||||
Computer.getComputer().getMemory().removeListener(l);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
super.attach();
|
||||
rgbStateListeners.stream().forEach((l) -> {
|
||||
Computer.getComputer().getMemory().addListener(l);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e.softswitch;
|
||||
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.core.SoftSwitch;
|
||||
|
||||
/**
|
||||
* Very funky softswitch which controls Slot 3 / C8 ROM behavior (and is the
|
||||
* reason why some cards don't like Slot 3)
|
||||
* Created on February 1, 2007, 9:40 PM
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class IntC8SoftSwitch extends SoftSwitch {
|
||||
|
||||
/**
|
||||
* Creates a new instance of IntC8SoftSwitch
|
||||
*/
|
||||
public IntC8SoftSwitch() {
|
||||
super("InternalC8Rom", false);
|
||||
// INTC8Rom should activate whenever C3xx memory is accessed and SLOTC3ROM is off
|
||||
addListener(
|
||||
new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0C300);
|
||||
setScopeEnd(0x0C3FF);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (SoftSwitches.SLOTC3ROM.isOff()) {
|
||||
setState(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// INTCXRom shoud deactivate whenever CFFF is accessed
|
||||
addListener(
|
||||
new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0CFFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
setState(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte readSwitch() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged() {
|
||||
if (Computer.getComputer().getMemory() != null) {
|
||||
Computer.getComputer().getMemory().configureActiveMemory();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e.softswitch;
|
||||
|
||||
import jace.core.Keyboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.SoftSwitch;
|
||||
|
||||
/**
|
||||
* Keyboard keypress strobe -- on = key pressed
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class KeyboardSoftSwitch extends SoftSwitch {
|
||||
public KeyboardSoftSwitch(String name, int offAddress, int onAddress, int queryAddress, RAMEvent.TYPE changeType, Boolean initalState) {
|
||||
super(name,offAddress,onAddress,queryAddress,changeType,initalState);
|
||||
}
|
||||
|
||||
public KeyboardSoftSwitch(String name, int[] offAddrs, int[] onAddrs, int[] queryAddrs, RAMEvent.TYPE changeType, Boolean initalState) {
|
||||
super(name,offAddrs,onAddrs,queryAddrs,changeType,initalState);
|
||||
}
|
||||
|
||||
public void stateChanged() {
|
||||
Keyboard.clearStrobe();
|
||||
}
|
||||
|
||||
/**
|
||||
* return current keypress (if high bit set, strobe is not cleared)
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public byte readSwitch() {
|
||||
return Keyboard.readState();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e.softswitch;
|
||||
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
|
||||
/**
|
||||
* A softswitch that requires two consecutive accesses to flip
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Memory2SoftSwitch extends MemorySoftSwitch {
|
||||
public Memory2SoftSwitch(String name, int offAddress, int onAddress, int queryAddress, TYPE changeType, Boolean initalState) {
|
||||
super(name, offAddress, onAddress, queryAddress, changeType, initalState);
|
||||
}
|
||||
|
||||
public Memory2SoftSwitch(String name, int[] offAddrs, int[] onAddrs, int[] queryAddrs, TYPE changeType, Boolean initalState) {
|
||||
super(name, offAddrs, onAddrs, queryAddrs, changeType, initalState);
|
||||
}
|
||||
|
||||
// The switch must be set true two times in a row before it will actually be set.
|
||||
int count = 0;
|
||||
@Override
|
||||
public void setState(boolean newState) {
|
||||
if (!newState) {
|
||||
count = 0;
|
||||
super.setState(newState);
|
||||
} else {
|
||||
count++;
|
||||
if (count >= 2) {
|
||||
super.setState(newState);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName()+(getState()?":1":":0")+"~~"+count;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e.softswitch;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.SoftSwitch;
|
||||
|
||||
/**
|
||||
* A memory softswitch is a softswitch which triggers a memory reconfiguration
|
||||
* after its value is changed.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class MemorySoftSwitch extends SoftSwitch {
|
||||
|
||||
public MemorySoftSwitch(String name, int offAddress, int onAddress, int queryAddress, RAMEvent.TYPE changeType, Boolean initalState) {
|
||||
super(name, offAddress, onAddress, queryAddress, changeType, initalState);
|
||||
}
|
||||
|
||||
public MemorySoftSwitch(String name, int[] offAddrs, int[] onAddrs, int[] queryAddrs, RAMEvent.TYPE changeType, Boolean initalState) {
|
||||
super(name, offAddrs, onAddrs, queryAddrs, changeType, initalState);
|
||||
}
|
||||
|
||||
public void stateChanged() {
|
||||
// System.out.println(getName()+ " was switched to "+getState());
|
||||
if (Computer.getComputer().getMemory() != null) {
|
||||
Computer.getComputer().getMemory().configureActiveMemory();
|
||||
}
|
||||
}
|
||||
|
||||
// Todo: Implement floating bus, maybe?
|
||||
protected byte readSwitch() {
|
||||
return (byte) (getState() ? 0x0A0 : 0x020);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.apple2e.softswitch;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.SoftSwitch;
|
||||
|
||||
/**
|
||||
* A video softswitch is a softswitch which triggers a change in video mode when
|
||||
* it is altered.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class VideoSoftSwitch extends SoftSwitch {
|
||||
|
||||
public VideoSoftSwitch(String name, int offAddress, int onAddress, int queryAddress, RAMEvent.TYPE changeType, Boolean initalState) {
|
||||
super(name, offAddress, onAddress, queryAddress, changeType, initalState);
|
||||
}
|
||||
|
||||
public VideoSoftSwitch(String name, int[] offAddrs, int[] onAddrs, int[] queryAddrs, RAMEvent.TYPE changeType, Boolean initalState) {
|
||||
super(name, offAddrs, onAddrs, queryAddrs, changeType, initalState);
|
||||
}
|
||||
|
||||
public void stateChanged() {
|
||||
// System.out.println("Set "+getName()+" -> "+getState());
|
||||
if (Computer.getComputer().getVideo() != null) {
|
||||
Computer.getComputer().getVideo().configureVideoMode();
|
||||
}
|
||||
}
|
||||
|
||||
protected byte readSwitch() {
|
||||
// System.out.println("Read "+getName()+" = "+getState());
|
||||
return (byte) (getState() ? 0x080 : 0x000);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.applesoft;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A command is a list of parts, either raw bytes (ascii text) or tokens. When
|
||||
* put together they represent a single basic statement.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Command {
|
||||
|
||||
public static enum TOKEN {
|
||||
|
||||
END((byte) 0x080, "END"),
|
||||
FOR((byte) 0x081, "FOR"),
|
||||
NEXT((byte) 0x082, "NEXT"),
|
||||
DATA((byte) 0x083, "DATA"),
|
||||
INPUT((byte) 0x084, "INPUT"),
|
||||
DEL((byte) 0x085, "DEL"),
|
||||
DIM((byte) 0x086, "DIM"),
|
||||
READ((byte) 0x087, "READ"),
|
||||
GR((byte) 0x088, "GR"),
|
||||
TEXT((byte) 0x089, "TEXT"),
|
||||
PR((byte) 0x08A, "PR#"),
|
||||
IN((byte) 0x08B, "IN#"),
|
||||
CALL((byte) 0x08C, "CALL"),
|
||||
PLOT((byte) 0x08D, "PLOT"),
|
||||
HLIN((byte) 0x08E, "HLIN"),
|
||||
VLIN((byte) 0x08F, "VLIN"),
|
||||
HGR2((byte) 0x090, "HGR2"),
|
||||
HGR((byte) 0x091, "HGR"),
|
||||
HCOLOR((byte) 0x092, "HCOLOR="),
|
||||
HPLOT((byte) 0x093, "HPLOT"),
|
||||
DRAW((byte) 0x094, "DRAW"),
|
||||
XDRAW((byte) 0x095, "XDRAW"),
|
||||
HTAB((byte) 0x096, "HTAB"),
|
||||
HOME((byte) 0x097, "HOME"),
|
||||
ROT((byte) 0x098, "ROT="),
|
||||
SCALE((byte) 0x099, "SCALE="),
|
||||
SHLOAD((byte) 0x09A, "SHLOAD"),
|
||||
TRACE((byte) 0x09B, "TRACE"),
|
||||
NOTRACE((byte) 0x09C, "NOTRACE"),
|
||||
NORMAL((byte) 0x09D, "NORMAL"),
|
||||
INVERSE((byte) 0x09E, "INVERSE"),
|
||||
FLASH((byte) 0x09F, "FLASH"),
|
||||
COLOR((byte) 0x0A0, "COLOR="),
|
||||
POP((byte) 0x0A1, "POP"),
|
||||
VTAB((byte) 0x0A2, "VTAB"),
|
||||
HIMEM((byte) 0x0A3, "HIMEM:"),
|
||||
LOMEM((byte) 0x0A4, "LOMEM:"),
|
||||
ONERR((byte) 0x0A5, "ONERR"),
|
||||
RESUME((byte) 0x0A6, "RESUME"),
|
||||
RECALL((byte) 0x0A7, "RECALL"),
|
||||
STORE((byte) 0x0A8, "STORE"),
|
||||
SPEED((byte) 0x0A9, "SPEED="),
|
||||
LET((byte) 0x0AA, "LET"),
|
||||
GOTO((byte) 0x0AB, "GOTO"),
|
||||
RUN((byte) 0x0AC, "RUN"),
|
||||
IF((byte) 0x0AD, "IF"),
|
||||
RESTORE((byte) 0x0AE, "RESTORE"),
|
||||
AMPERSAND((byte) 0x0AF, "&"),
|
||||
GOSUB((byte) 0x0B0, "GOSUB"),
|
||||
RETURN((byte) 0x0B1, "RETURN"),
|
||||
REM((byte) 0x0B2, "REM"),
|
||||
STOP((byte) 0x0B3, "STOP"),
|
||||
ONGOTO((byte) 0x0B4, "ON"),
|
||||
WAIT((byte) 0x0B5, "WAIT"),
|
||||
LOAD((byte) 0x0B6, "LOAD"),
|
||||
SAVE((byte) 0x0B7, "SAVE"),
|
||||
DEF((byte) 0x0B8, "DEF"),
|
||||
POKE((byte) 0x0B9, "POKE"),
|
||||
PRINT((byte) 0x0BA, "PRINT"),
|
||||
CONT((byte) 0x0BB, "CONT"),
|
||||
LIST((byte) 0x0BC, "LIST"),
|
||||
CLEAR((byte) 0x0BD, "CLEAR"),
|
||||
GET((byte) 0x0BE, "GET"),
|
||||
NEW((byte) 0x0BF, "NEW"),
|
||||
TAB((byte) 0x0C0, "TAB("),
|
||||
TO((byte) 0x0C1, "TO"),
|
||||
FN((byte) 0x0C2, "FN"),
|
||||
SPC((byte) 0x0c3, "SPC"),
|
||||
THEN((byte) 0x0c4, "THEN"),
|
||||
AT((byte) 0x0c5, "AT"),
|
||||
NOT((byte) 0x0c6, "NOT"),
|
||||
STEP((byte) 0x0c7, "STEP"),
|
||||
PLUS((byte) 0x0c8, "+"),
|
||||
MINUS((byte) 0x0c9, "-"),
|
||||
MULTIPLY((byte) 0x0Ca, "*"),
|
||||
DIVIDE((byte) 0x0Cb, "/"),
|
||||
POWER((byte) 0x0Cc, "^"),
|
||||
AND((byte) 0x0Cd, "AND"),
|
||||
OR((byte) 0x0Ce, "OR"),
|
||||
GREATER((byte) 0x0CF, ">"),
|
||||
EQUAL((byte) 0x0d0, "="),
|
||||
LESS((byte) 0x0d1, "<"),
|
||||
SGN((byte) 0x0D2, "SGN"),
|
||||
INT((byte) 0x0D3, "INT"),
|
||||
ABS((byte) 0x0D4, "ABS"),
|
||||
USR((byte) 0x0D5, "USR"),
|
||||
FRE((byte) 0x0D6, "FRE"),
|
||||
SCREEN((byte) 0x0D7, "SCRN("),
|
||||
PDL((byte) 0x0D8, "PDL"),
|
||||
POS((byte) 0x0D9, "POS"),
|
||||
SQR((byte) 0x0DA, "SQR"),
|
||||
RND((byte) 0x0DB, "RND"),
|
||||
LOG((byte) 0x0DC, "LOG"),
|
||||
EXP((byte) 0x0DD, "EXP"),
|
||||
COS((byte) 0x0DE, "COS"),
|
||||
SIN((byte) 0x0DF, "SIN"),
|
||||
TAN((byte) 0x0E0, "TAN"),
|
||||
ATN((byte) 0x0E1, "ATN"),
|
||||
PEEK((byte) 0x0E2, "PEEK"),
|
||||
LEN((byte) 0x0E3, "LEN"),
|
||||
STR((byte) 0x0E4, "STR$"),
|
||||
VAL((byte) 0x0E5, "VAL"),
|
||||
ASC((byte) 0x0E6, "ASC"),
|
||||
CHR((byte) 0x0E7, "CHR$"),
|
||||
LEFT((byte) 0x0E8, "LEFT$"),
|
||||
RIGHT((byte) 0x0E9, "RIGHT$"),
|
||||
MID((byte) 0x0EA, "MID$");
|
||||
private String str;
|
||||
private byte b;
|
||||
|
||||
TOKEN(byte b, String str) {
|
||||
this.b = b;
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return str;
|
||||
}
|
||||
|
||||
public static TOKEN fromByte(byte b) {
|
||||
for (TOKEN t : values()) {
|
||||
if (t.b == b) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ByteOrToken {
|
||||
|
||||
byte b;
|
||||
TOKEN t;
|
||||
boolean isToken = false;
|
||||
|
||||
public ByteOrToken(byte b) {
|
||||
TOKEN t = TOKEN.fromByte(b);
|
||||
if (t != null) {
|
||||
isToken = true;
|
||||
this.t = t;
|
||||
} else {
|
||||
isToken = false;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return isToken
|
||||
? " " + t.toString() + " "
|
||||
: String.valueOf((char) b);
|
||||
}
|
||||
}
|
||||
List<ByteOrToken> parts = new ArrayList<ByteOrToken>();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String out = "";
|
||||
for (ByteOrToken p : parts) {
|
||||
out += p.toString();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.applesoft;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Representation of a line of applesoft basic, having a line number and a list of program commands.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Line {
|
||||
|
||||
private static char STATEMENT_BREAK = ':'; // delimits multiple commands, the colon character
|
||||
private int number;
|
||||
private Line next;
|
||||
private Line previous;
|
||||
private List<Command> commands = new ArrayList<Command>();
|
||||
private int length;
|
||||
|
||||
/**
|
||||
* @return the number
|
||||
*/
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param number the number to set
|
||||
*/
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the next
|
||||
*/
|
||||
public Line getNext() {
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param next the next to set
|
||||
*/
|
||||
public void setNext(Line next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the previous
|
||||
*/
|
||||
public Line getPrevious() {
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param previous the previous to set
|
||||
*/
|
||||
public void setPrevious(Line previous) {
|
||||
this.previous = previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the commands
|
||||
*/
|
||||
public List<Command> getCommands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param commands the commands to set
|
||||
*/
|
||||
public void setCommands(List<Command> commands) {
|
||||
this.commands = commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length
|
||||
*/
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param length the length to set
|
||||
*/
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String out = String.valueOf(getNumber());
|
||||
boolean isFirst = true;
|
||||
for (Command c : commands) {
|
||||
if (!isFirst) out += STATEMENT_BREAK;
|
||||
out += c.toString();
|
||||
isFirst = false;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static Line fromBinary(byte[] binary, int pos) {
|
||||
Line l = new Line();
|
||||
int lineNumber = (binary[pos+2] & 0x0ff) + ((binary[pos+3] & 0x0ff) << 8);
|
||||
l.setNumber(lineNumber);
|
||||
pos += 4;
|
||||
Command c = new Command();
|
||||
int size = 5;
|
||||
while (binary[pos] != 0) {
|
||||
size++;
|
||||
if (binary[pos] == STATEMENT_BREAK) {
|
||||
l.commands.add(c);
|
||||
c = new Command();
|
||||
} else {
|
||||
Command.ByteOrToken bt = new Command.ByteOrToken(binary[pos]);
|
||||
c.parts.add(bt);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
l.commands.add(c);
|
||||
l.length = size;
|
||||
return l;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.applesoft;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Decode an applesoft program into a list of program lines
|
||||
* Right now this is an example/test program but it successfully tokenized the
|
||||
* souce of Lemonade Stand.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Program {
|
||||
List<Line> lines = new ArrayList<Line>();
|
||||
int startingAddress = 0x0801;
|
||||
|
||||
public static void main(String... args) {
|
||||
byte[] source = null;
|
||||
try {
|
||||
File f = new File("/home/brobert/Documents/Personal/a2gameserver/lib/data/games/LEMONADE#fc0801");
|
||||
FileInputStream in = new FileInputStream(f);
|
||||
source = new byte[(int) f.length()];
|
||||
in.read(source);
|
||||
} catch (FileNotFoundException ex) {
|
||||
Logger.getLogger(Program.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Program.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
Program test = Program.fromBinary(source);
|
||||
System.out.println(test.toString());
|
||||
}
|
||||
|
||||
static Program fromBinary(byte[] binary) {
|
||||
return fromBinary(binary, 0x0801);
|
||||
}
|
||||
|
||||
static Program fromBinary(byte[] binary, int startAddress) {
|
||||
Program program = new Program();
|
||||
int currentAddress = startAddress;
|
||||
int pos = 0;
|
||||
while (pos < binary.length) {
|
||||
int nextAddress = (binary[pos] & 0x0ff) + ((binary[pos+1] & 0x0ff) << 8);
|
||||
if (nextAddress == 0) break;
|
||||
int length = nextAddress - currentAddress;
|
||||
Line l = Line.fromBinary(binary, pos);
|
||||
if (l == null) break;
|
||||
program.lines.add(l);
|
||||
if (l.getLength() != length) {
|
||||
System.out.println("Line "+l.getNumber()+" parsed as "+l.getLength()+" bytes long, but that leaves "+
|
||||
(length - l.getLength())+" bytes hidden behind next line");
|
||||
}
|
||||
pos += length;
|
||||
currentAddress = nextAddress;
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String out = "";
|
||||
for (Line l : lines)
|
||||
out += l.toString() + "\n";
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.cheat;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.core.Device;
|
||||
import jace.core.RAMListener;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents some combination of hacks that can be enabled or disabled
|
||||
* through the configuration interface.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class Cheats extends Device {
|
||||
Set<RAMListener> listeners = new HashSet<RAMListener>();
|
||||
|
||||
public void addCheat(RAMListener l) {
|
||||
listeners.add(l);
|
||||
Computer.getComputer().getMemory().addListener(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
for (RAMListener l : listeners) {
|
||||
Computer.getComputer().getMemory().removeListener(l);
|
||||
}
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
detach();
|
||||
attach();
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return "cheat";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
|
||||
<NonVisualComponents>
|
||||
<Component class="javax.swing.ButtonGroup" name="mapType">
|
||||
</Component>
|
||||
</NonVisualComponents>
|
||||
<Properties>
|
||||
<Property name="defaultCloseOperation" type="int" value="3"/>
|
||||
</Properties>
|
||||
<SyntheticProperties>
|
||||
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
|
||||
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
|
||||
</SyntheticProperties>
|
||||
<Events>
|
||||
<EventHandler event="componentResized" listener="java.awt.event.ComponentListener" parameters="java.awt.event.ComponentEvent" handler="formComponentResized"/>
|
||||
<EventHandler event="componentShown" listener="java.awt.event.ComponentListener" parameters="java.awt.event.ComponentEvent" handler="formComponentShown"/>
|
||||
<EventHandler event="componentHidden" listener="java.awt.event.ComponentListener" parameters="java.awt.event.ComponentEvent" handler="formComponentHidden"/>
|
||||
<EventHandler event="ancestorResized" listener="java.awt.event.HierarchyBoundsListener" parameters="java.awt.event.HierarchyEvent" handler="formAncestorResized"/>
|
||||
</Events>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="viewScroll" pref="633" max="32767" attributes="0"/>
|
||||
<Component id="controlPanel" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="viewScroll" pref="492" max="32767" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="controlPanel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JScrollPane" name="viewScroll">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[640, 480]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="jace.cheat.MemorySpyGrid" name="memorySpyGrid">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[580, 480]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="646" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="490" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="controlPanel">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.LineBorderInfo">
|
||||
<LineBorder roundedCorners="true"/>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="19" max="-2" attributes="0"/>
|
||||
<Component id="showLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="mapActivityMode" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fastFadeCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="mapValueMode" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="zoomLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="useColorCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="144" max="-2" attributes="0"/>
|
||||
<Component id="endLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="zoomAmount" min="-2" pref="107" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="startLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="endAddress" pref="60" max="32767" attributes="0"/>
|
||||
<Component id="startAddress" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="updateAddressButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="28" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="statusLabel" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="zoomAmount" alignment="0" pref="0" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="startLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="startAddress" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="endAddress" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="endLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="updateAddressButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="useColorCheckbox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fastFadeCheckbox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Group type="103" alignment="1" groupAlignment="3" attributes="0">
|
||||
<Component id="mapActivityMode" alignment="3" min="-2" pref="22" max="-2" attributes="0"/>
|
||||
<Component id="showLabel" alignment="3" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="103" alignment="0" groupAlignment="3" attributes="0">
|
||||
<Component id="zoomLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="mapValueMode" alignment="3" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="statusLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="showLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Show"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSlider" name="zoomAmount">
|
||||
<Properties>
|
||||
<Property name="maximum" type="int" value="15"/>
|
||||
<Property name="minimum" type="int" value="1"/>
|
||||
<Property name="paintTicks" type="boolean" value="true"/>
|
||||
<Property name="snapToTicks" type="boolean" value="true"/>
|
||||
<Property name="value" type="int" value="5"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="zoomAmountStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JRadioButton" name="mapActivityMode">
|
||||
<Properties>
|
||||
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||
<ComponentRef name="mapType"/>
|
||||
</Property>
|
||||
<Property name="selected" type="boolean" value="true"/>
|
||||
<Property name="text" type="java.lang.String" value="Activity"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="mapActivityModeActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JRadioButton" name="mapValueMode">
|
||||
<Properties>
|
||||
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||
<ComponentRef name="mapType"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" value="Value"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="mapValueModeActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="zoomLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Zoom"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="startAddress">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="$0000"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="startLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Start"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="endAddress">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="$FFFF"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="statusLabel">
|
||||
<Properties>
|
||||
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
|
||||
<Font name="Courier New" size="14" style="0"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" value=" "/>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
|
||||
<EtchetBorder bevelType="0"/>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="1"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="endLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="End"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="updateAddressButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Update"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="updateAddressButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="useColorCheckbox">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Color Gradient"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="useColorCheckboxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="fastFadeCheckbox">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Fast Fade"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fastFadeCheckboxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
|
@ -0,0 +1,395 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.cheat;
|
||||
|
||||
import jace.core.Utility;
|
||||
import static jace.core.Utility.gripe;
|
||||
|
||||
/**
|
||||
* This represents the window of the memory spy module. The memory view itself
|
||||
* is implemented in MemorySpyGrid.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class MemorySpy extends javax.swing.JFrame {
|
||||
|
||||
static MemorySpy singleton = null;
|
||||
|
||||
/**
|
||||
* Creates new form MemorySpy
|
||||
*/
|
||||
public MemorySpy() {
|
||||
initComponents();
|
||||
setDefaultCloseOperation(HIDE_ON_CLOSE);
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
mapType = new javax.swing.ButtonGroup();
|
||||
viewScroll = new javax.swing.JScrollPane();
|
||||
memorySpyGrid = new jace.cheat.MemorySpyGrid();
|
||||
controlPanel = new javax.swing.JPanel();
|
||||
showLabel = new javax.swing.JLabel();
|
||||
zoomAmount = new javax.swing.JSlider();
|
||||
mapActivityMode = new javax.swing.JRadioButton();
|
||||
mapValueMode = new javax.swing.JRadioButton();
|
||||
zoomLabel = new javax.swing.JLabel();
|
||||
startAddress = new javax.swing.JTextField();
|
||||
startLabel = new javax.swing.JLabel();
|
||||
endAddress = new javax.swing.JTextField();
|
||||
statusLabel = new javax.swing.JLabel();
|
||||
endLabel = new javax.swing.JLabel();
|
||||
updateAddressButton = new javax.swing.JButton();
|
||||
useColorCheckbox = new javax.swing.JCheckBox();
|
||||
fastFadeCheckbox = new javax.swing.JCheckBox();
|
||||
|
||||
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
|
||||
addComponentListener(new java.awt.event.ComponentAdapter() {
|
||||
public void componentResized(java.awt.event.ComponentEvent evt) {
|
||||
formComponentResized(evt);
|
||||
}
|
||||
public void componentShown(java.awt.event.ComponentEvent evt) {
|
||||
formComponentShown(evt);
|
||||
}
|
||||
public void componentHidden(java.awt.event.ComponentEvent evt) {
|
||||
formComponentHidden(evt);
|
||||
}
|
||||
});
|
||||
addHierarchyBoundsListener(new java.awt.event.HierarchyBoundsListener() {
|
||||
public void ancestorMoved(java.awt.event.HierarchyEvent evt) {
|
||||
}
|
||||
public void ancestorResized(java.awt.event.HierarchyEvent evt) {
|
||||
formAncestorResized(evt);
|
||||
}
|
||||
});
|
||||
|
||||
viewScroll.setPreferredSize(new java.awt.Dimension(640, 480));
|
||||
|
||||
memorySpyGrid.setPreferredSize(new java.awt.Dimension(580, 480));
|
||||
|
||||
javax.swing.GroupLayout memorySpyGridLayout = new javax.swing.GroupLayout(memorySpyGrid);
|
||||
memorySpyGrid.setLayout(memorySpyGridLayout);
|
||||
memorySpyGridLayout.setHorizontalGroup(
|
||||
memorySpyGridLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 631, Short.MAX_VALUE)
|
||||
);
|
||||
memorySpyGridLayout.setVerticalGroup(
|
||||
memorySpyGridLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 490, Short.MAX_VALUE)
|
||||
);
|
||||
|
||||
viewScroll.setViewportView(memorySpyGrid);
|
||||
|
||||
controlPanel.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(0, 0, 0), 1, true));
|
||||
|
||||
showLabel.setText("Show");
|
||||
|
||||
zoomAmount.setMaximum(15);
|
||||
zoomAmount.setMinimum(1);
|
||||
zoomAmount.setPaintTicks(true);
|
||||
zoomAmount.setSnapToTicks(true);
|
||||
zoomAmount.setValue(5);
|
||||
zoomAmount.addChangeListener(new javax.swing.event.ChangeListener() {
|
||||
public void stateChanged(javax.swing.event.ChangeEvent evt) {
|
||||
zoomAmountStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
mapType.add(mapActivityMode);
|
||||
mapActivityMode.setSelected(true);
|
||||
mapActivityMode.setText("Activity");
|
||||
mapActivityMode.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
mapActivityModeActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
mapType.add(mapValueMode);
|
||||
mapValueMode.setText("Value");
|
||||
mapValueMode.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
mapValueModeActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
zoomLabel.setText("Zoom");
|
||||
|
||||
startAddress.setText("$0000");
|
||||
|
||||
startLabel.setText("Start");
|
||||
|
||||
endAddress.setText("$FFFF");
|
||||
|
||||
statusLabel.setFont(new java.awt.Font("Courier New", 0, 14)); // NOI18N
|
||||
statusLabel.setText(" ");
|
||||
statusLabel.setBorder(javax.swing.BorderFactory.createEtchedBorder(javax.swing.border.EtchedBorder.RAISED));
|
||||
|
||||
endLabel.setText("End");
|
||||
|
||||
updateAddressButton.setText("Update");
|
||||
updateAddressButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
updateAddressButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
useColorCheckbox.setText("Color Gradient");
|
||||
useColorCheckbox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
useColorCheckboxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
fastFadeCheckbox.setText("Fast Fade");
|
||||
fastFadeCheckbox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
fastFadeCheckboxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel);
|
||||
controlPanel.setLayout(controlPanelLayout);
|
||||
controlPanelLayout.setHorizontalGroup(
|
||||
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addGap(19, 19, 19)
|
||||
.addComponent(showLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(mapActivityMode)
|
||||
.addComponent(fastFadeCheckbox))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addComponent(mapValueMode)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(zoomLabel))
|
||||
.addComponent(useColorCheckbox))
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addGap(144, 144, 144)
|
||||
.addComponent(endLabel))
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(zoomAmount, javax.swing.GroupLayout.PREFERRED_SIZE, 107, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(startLabel)))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(endAddress, javax.swing.GroupLayout.DEFAULT_SIZE, 60, Short.MAX_VALUE)
|
||||
.addComponent(startAddress))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(updateAddressButton)
|
||||
.addContainerGap(28, Short.MAX_VALUE))
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(statusLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addContainerGap())
|
||||
);
|
||||
controlPanelLayout.setVerticalGroup(
|
||||
controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(controlPanelLayout.createSequentialGroup()
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(zoomAmount, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(startLabel)
|
||||
.addComponent(startAddress, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(endAddress, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(endLabel)
|
||||
.addComponent(updateAddressButton)
|
||||
.addComponent(useColorCheckbox)
|
||||
.addComponent(fastFadeCheckbox)))
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(mapActivityMode, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(showLabel))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(zoomLabel)
|
||||
.addComponent(mapValueMode))))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(statusLabel)
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
|
||||
getContentPane().setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(viewScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 633, Short.MAX_VALUE)
|
||||
.addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addContainerGap())
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(viewScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 492, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(controlPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
pack();
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void mapActivityModeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mapActivityModeActionPerformed
|
||||
memorySpyGrid.setDisplayMode(MemorySpyGrid.Modes.ACTIVITY);
|
||||
}//GEN-LAST:event_mapActivityModeActionPerformed
|
||||
|
||||
private void mapValueModeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mapValueModeActionPerformed
|
||||
memorySpyGrid.setDisplayMode(MemorySpyGrid.Modes.VALUE);
|
||||
}//GEN-LAST:event_mapValueModeActionPerformed
|
||||
|
||||
private void zoomAmountStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_zoomAmountStateChanged
|
||||
if (!zoomAmount.getValueIsAdjusting()) {
|
||||
memorySpyGrid.setZoomAmount(zoomAmount.getValue());
|
||||
}
|
||||
}//GEN-LAST:event_zoomAmountStateChanged
|
||||
|
||||
private void updateAddressButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateAddressButtonActionPerformed
|
||||
int startAddr = Utility.parseHexInt(startAddress.getText());
|
||||
int endAddr = Utility.parseHexInt(endAddress.getText());
|
||||
if (startAddr < 0 || startAddr > 0x0ffff) {
|
||||
gripe("Start address is out of range!");
|
||||
return;
|
||||
}
|
||||
if (endAddr < 0 || endAddr > 0x0ffff) {
|
||||
gripe("End address is out of range!");
|
||||
return;
|
||||
}
|
||||
if (startAddr > endAddr) {
|
||||
gripe("Start address must be smaller than end address");
|
||||
return;
|
||||
}
|
||||
memorySpyGrid.updateRange(startAddr, endAddr);
|
||||
}//GEN-LAST:event_updateAddressButtonActionPerformed
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
super.validate();
|
||||
memorySpyGrid.adjustSize();
|
||||
}
|
||||
|
||||
private void formComponentHidden(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentHidden
|
||||
memorySpyGrid.stopDisplay();
|
||||
}//GEN-LAST:event_formComponentHidden
|
||||
|
||||
private void formComponentShown(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentShown
|
||||
memorySpyGrid.startDisplay();
|
||||
}//GEN-LAST:event_formComponentShown
|
||||
long lastRefresh = -1L;
|
||||
long refreshDelay = 1000L;
|
||||
private void formComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentResized
|
||||
}//GEN-LAST:event_formComponentResized
|
||||
|
||||
private void formAncestorResized(java.awt.event.HierarchyEvent evt) {//GEN-FIRST:event_formAncestorResized
|
||||
// java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// lastRefresh = System.currentTimeMillis() + refreshDelay + 10;
|
||||
// try {
|
||||
// Thread.sleep(refreshDelay);
|
||||
// } catch (InterruptedException ex) {
|
||||
// Logger.getLogger(MemorySpy.class.getName()).log(Level.SEVERE, null, ex);
|
||||
// }
|
||||
// if (System.currentTimeMillis() >= lastRefresh) {
|
||||
// int newWidth = getParent().getWidth();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// memorySpyGrid.adjustSize();
|
||||
}//GEN-LAST:event_formAncestorResized
|
||||
|
||||
private void useColorCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useColorCheckboxActionPerformed
|
||||
memorySpyGrid.setColorGradient(useColorCheckbox.isSelected());
|
||||
}//GEN-LAST:event_useColorCheckboxActionPerformed
|
||||
|
||||
private void fastFadeCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastFadeCheckboxActionPerformed
|
||||
memorySpyGrid.setFastFade(fastFadeCheckbox.isSelected());
|
||||
}//GEN-LAST:event_fastFadeCheckboxActionPerformed
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
/* Set the Nimbus look and feel */
|
||||
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
|
||||
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
|
||||
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
|
||||
*/
|
||||
try {
|
||||
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
|
||||
if ("Nimbus".equals(info.getName())) {
|
||||
javax.swing.UIManager.setLookAndFeel(info.getClassName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
java.util.logging.Logger.getLogger(MemorySpy.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||
} catch (InstantiationException ex) {
|
||||
java.util.logging.Logger.getLogger(MemorySpy.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||
} catch (IllegalAccessException ex) {
|
||||
java.util.logging.Logger.getLogger(MemorySpy.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
|
||||
java.util.logging.Logger.getLogger(MemorySpy.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
/* Create and display the form */
|
||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
new MemorySpy().setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JPanel controlPanel;
|
||||
private javax.swing.JTextField endAddress;
|
||||
private javax.swing.JLabel endLabel;
|
||||
private javax.swing.JCheckBox fastFadeCheckbox;
|
||||
private javax.swing.JRadioButton mapActivityMode;
|
||||
private javax.swing.ButtonGroup mapType;
|
||||
private javax.swing.JRadioButton mapValueMode;
|
||||
private jace.cheat.MemorySpyGrid memorySpyGrid;
|
||||
private javax.swing.JLabel showLabel;
|
||||
private javax.swing.JTextField startAddress;
|
||||
private javax.swing.JLabel startLabel;
|
||||
public javax.swing.JLabel statusLabel;
|
||||
private javax.swing.JButton updateAddressButton;
|
||||
private javax.swing.JCheckBox useColorCheckbox;
|
||||
private javax.swing.JScrollPane viewScroll;
|
||||
private javax.swing.JSlider zoomAmount;
|
||||
private javax.swing.JLabel zoomLabel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Properties>
|
||||
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="1" green="1" red="1" type="rgb"/>
|
||||
</Property>
|
||||
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
|
||||
<Color id="Crosshair Cursor"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="formMouseClicked"/>
|
||||
<EventHandler event="mouseExited" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="formMouseExited"/>
|
||||
<EventHandler event="mouseEntered" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="formMouseEntered"/>
|
||||
<EventHandler event="mouseMoved" listener="java.awt.event.MouseMotionListener" parameters="java.awt.event.MouseEvent" handler="formMouseMoved"/>
|
||||
</Events>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="400" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="300" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
</Form>
|
|
@ -0,0 +1,489 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.cheat;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.RAMListener;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
/**
|
||||
* This is the memory view of the memory spy module. The window is defined by
|
||||
* the MemorySpy class. This class registers memory listeners needed to provide
|
||||
* the relevant memory views.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class MemorySpyGrid extends javax.swing.JPanel {
|
||||
|
||||
/**
|
||||
* Creates new form MemorySpyGrid
|
||||
*/
|
||||
public MemorySpyGrid() {
|
||||
initComponents();
|
||||
gradient = bwGradient;
|
||||
}
|
||||
// This is used primarily during scroll events to repaint the whole area
|
||||
ChangeListener viewportChangeListener = new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
repaint();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
super.validate();
|
||||
if (Computer.getComputer() != null) {
|
||||
adjustSize();
|
||||
}
|
||||
if (getParent() != null) {
|
||||
JViewport viewport = (JViewport) getParent();
|
||||
viewport.removeChangeListener(viewportChangeListener);
|
||||
viewport.addChangeListener(viewportChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
super.paint(g);
|
||||
if (mode == Modes.ACTIVITY) {
|
||||
for (int a = startAddress; a <= endAddress; a++) {
|
||||
if (!activeRam.contains(a)) {
|
||||
drawActivity(a, 0, 0, 0, (Graphics2D) g);
|
||||
} else {
|
||||
drawActivity(a,
|
||||
getInt(writeActivity.get(a)),
|
||||
getInt(readActivity.get(a)),
|
||||
getInt(pcActivity.get(a)),
|
||||
(Graphics2D) g);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
RAM ram = Computer.getComputer().getMemory();
|
||||
for (int a = startAddress; a <= endAddress; a++) {
|
||||
drawValue(a, ram.readRaw(a) & 0x0ff, (Graphics2D) g);
|
||||
}
|
||||
}
|
||||
}
|
||||
Color[] gradient;
|
||||
static Color[] colorGradient, bwGradient;
|
||||
|
||||
static {
|
||||
colorGradient = new Color[256];
|
||||
bwGradient = new Color[256];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
float hue = ((float) i) / 360.0f;
|
||||
colorGradient[i] = Color.getHSBColor(0.67f - hue, 1, 1);
|
||||
bwGradient[i] = new Color(i, i, i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
setBackground(new java.awt.Color(1, 1, 1));
|
||||
setCursor(new java.awt.Cursor(java.awt.Cursor.CROSSHAIR_CURSOR));
|
||||
addMouseListener(new java.awt.event.MouseAdapter() {
|
||||
public void mouseClicked(java.awt.event.MouseEvent evt) {
|
||||
formMouseClicked(evt);
|
||||
}
|
||||
public void mouseExited(java.awt.event.MouseEvent evt) {
|
||||
formMouseExited(evt);
|
||||
}
|
||||
public void mouseEntered(java.awt.event.MouseEvent evt) {
|
||||
formMouseEntered(evt);
|
||||
}
|
||||
});
|
||||
addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
|
||||
public void mouseMoved(java.awt.event.MouseEvent evt) {
|
||||
formMouseMoved(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 400, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 300, Short.MAX_VALUE)
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
boolean mousePresent = false;
|
||||
int mouseX;
|
||||
int mouseY;
|
||||
private void formMouseMoved(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseMoved
|
||||
mouseX = evt.getX();
|
||||
mouseY = evt.getY();
|
||||
}//GEN-LAST:event_formMouseMoved
|
||||
|
||||
private void formMouseEntered(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseEntered
|
||||
mousePresent = true;
|
||||
}//GEN-LAST:event_formMouseEntered
|
||||
|
||||
private void formMouseExited(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseExited
|
||||
mousePresent = false;
|
||||
}//GEN-LAST:event_formMouseExited
|
||||
|
||||
private void formMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseClicked
|
||||
int addr = convertXYtoAddr(mouseX, mouseY);
|
||||
if (addr >= 0) {
|
||||
MetaCheats.singleton.addWatches(addr, addr);
|
||||
}
|
||||
}//GEN-LAST:event_formMouseClicked
|
||||
|
||||
private int convertXYtoAddr(int x, int y) {
|
||||
int offset = x / zoomAmount;
|
||||
if (offset < 0 || offset > columns) {
|
||||
return -1;
|
||||
}
|
||||
int row = y / zoomAmount;
|
||||
int addr = startAddress + (row * columns) + offset;
|
||||
if (addr > endAddress) {
|
||||
return -1;
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
private String spaceFill(String s, int size) {
|
||||
while (s.length() < size) {
|
||||
s = s + " ";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private void updateDetails() {
|
||||
final JLabel status = MemorySpy.singleton.statusLabel;
|
||||
if (mousePresent) {
|
||||
final int addr = convertXYtoAddr(mouseX, mouseY);
|
||||
if (addr >= 0) {
|
||||
final int value = Computer.getComputer().getMemory().readRaw(addr) & 0x0ff;
|
||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
Integer lastChange = setBy.get(addr);
|
||||
Integer lastRead = readBy.get(addr);
|
||||
char c1 = (char) Math.max(32, value & 0x07f);
|
||||
char c2 = (char) (64 + (value % 32));
|
||||
char c3 = (char) (32 + (value % 96));
|
||||
|
||||
status.setText("$"
|
||||
+ spaceFill(Integer.toHexString(addr), 4) + ": "
|
||||
+ spaceFill(String.valueOf(value), 3) + " ($"
|
||||
+ spaceFill(Integer.toHexString(value), 2) + ") " + c1 + " " + c2 + " " + c3
|
||||
+ (lastChange != null
|
||||
? " Written by $"
|
||||
+ spaceFill(Integer.toHexString(lastChange), 4)
|
||||
: "")
|
||||
+ (lastRead != null
|
||||
? " Read by $"
|
||||
+ spaceFill(Integer.toHexString(lastRead), 4)
|
||||
: ""));
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
status.setText("Nothing selected");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
// End of variables declaration//GEN-END:variables
|
||||
Modes mode = Modes.ACTIVITY;
|
||||
|
||||
void setDisplayMode(Modes newMode) {
|
||||
stopDisplay();
|
||||
mode = newMode;
|
||||
repaint();
|
||||
startDisplay();
|
||||
}
|
||||
int zoomAmount = 5;
|
||||
|
||||
void setZoomAmount(int value) {
|
||||
zoomAmount = value;
|
||||
recalibrate();
|
||||
}
|
||||
int startAddress = 0;
|
||||
int endAddress = 0x0ffff;
|
||||
|
||||
void updateRange(int startAddr, int endAddr) {
|
||||
startAddress = startAddr;
|
||||
endAddress = endAddr;
|
||||
activeRam.clear();
|
||||
valueActivity.clear();
|
||||
readActivity.clear();
|
||||
writeActivity.clear();
|
||||
pcActivity.clear();
|
||||
recalibrate();
|
||||
}
|
||||
int columns = 1;
|
||||
int lastKnownWidth = 0;
|
||||
int lastKnownHeight = 0;
|
||||
|
||||
public void adjustSize() {
|
||||
int newWidth = getParent().getWidth();
|
||||
if (newWidth == lastKnownWidth && getParent().getHeight() == lastKnownHeight) {
|
||||
return;
|
||||
}
|
||||
lastKnownWidth = newWidth;
|
||||
lastKnownHeight = getParent().getHeight();
|
||||
recalibrate();
|
||||
}
|
||||
|
||||
public void recalibrate() {
|
||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stopDisplay();
|
||||
int size = endAddress - startAddress + 1;
|
||||
int width = getParent().getWidth();
|
||||
columns = ((width / zoomAmount) / 16) * 16;
|
||||
int height = ((size / columns) + 1) * zoomAmount;
|
||||
setSize(width, height);
|
||||
setPreferredSize(new Dimension(width, height));
|
||||
getParent().validate();
|
||||
repaint();
|
||||
startDisplay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void drawValue(int addr, int value, Graphics2D g) {
|
||||
int offset = addr - startAddress;
|
||||
int x = zoomAmount * (offset % columns);
|
||||
int y = zoomAmount * (offset / columns);
|
||||
value = value & 0x0ff;
|
||||
g.setColor(gradient[value]);
|
||||
g.fillRect(x, y, zoomAmount, zoomAmount);
|
||||
}
|
||||
|
||||
protected void drawActivity(int addr, int read, int write, int pc, Graphics2D g) {
|
||||
int offset = addr - startAddress;
|
||||
int x = zoomAmount * (offset % columns);
|
||||
int y = zoomAmount * (offset / columns);
|
||||
g.setColor(new Color(read & 0x0ff, write & 0x0ff, pc & 0x0ff));
|
||||
g.fillRect(x, y, zoomAmount, zoomAmount);
|
||||
}
|
||||
|
||||
private int getInt(Integer i) {
|
||||
if (i != null) {
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
boolean isDisplayActive = false;
|
||||
Thread redrawThread = null;
|
||||
// 30 fps or so
|
||||
static int UPDATE_INTERVAL = 10;
|
||||
static int ACTIVITY_INCREMENT = 150;
|
||||
static int ACTIVITY_DECREMENT = 5;
|
||||
static int ACTIVITY_MAX = 255;
|
||||
RAMListener memoryListener = null;
|
||||
Map<Integer, Integer> readActivity = new ConcurrentHashMap<Integer, Integer>();
|
||||
Map<Integer, Integer> writeActivity = new ConcurrentHashMap<Integer, Integer>();
|
||||
Map<Integer, Integer> pcActivity = new ConcurrentHashMap<Integer, Integer>();
|
||||
Set<Integer> activeRam = new ConcurrentSkipListSet<Integer>();
|
||||
Map<Integer, Integer> valueActivity = new ConcurrentHashMap<Integer, Integer>();
|
||||
Map<Integer, Integer> setBy = new ConcurrentHashMap<Integer, Integer>();
|
||||
Map<Integer, Integer> readBy = new ConcurrentHashMap<Integer, Integer>();
|
||||
|
||||
void startDisplay() {
|
||||
if (memoryListener != null) {
|
||||
Computer.getComputer().getMemory().removeListener(memoryListener);
|
||||
memoryListener = null;
|
||||
}
|
||||
isDisplayActive = true;
|
||||
memoryListener = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(startAddress);
|
||||
setScopeEnd(endAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
int addr = e.getAddress();
|
||||
if (addr < startAddress || addr > endAddress) {
|
||||
return;
|
||||
}
|
||||
int pc = Computer.getComputer().getCpu().programCounter;
|
||||
if (e.getType() == TYPE.EXECUTE || e.getType() == TYPE.READ_OPERAND) {
|
||||
if (pcActivity.containsKey(addr)) {
|
||||
pcActivity.put(addr, Math.min(ACTIVITY_MAX, getInt(pcActivity.get(addr)) + ACTIVITY_INCREMENT));
|
||||
} else {
|
||||
pcActivity.put(addr, ACTIVITY_INCREMENT);
|
||||
}
|
||||
} else if (e.getType().isRead()) {
|
||||
if (readActivity.containsKey(addr)) {
|
||||
readActivity.put(addr, Math.min(ACTIVITY_MAX, getInt(readActivity.get(addr)) + ACTIVITY_INCREMENT));
|
||||
} else {
|
||||
readActivity.put(addr, ACTIVITY_INCREMENT);
|
||||
}
|
||||
readBy.put(addr, pc);
|
||||
} else {
|
||||
if (writeActivity.containsKey(addr)) {
|
||||
writeActivity.put(addr, Math.min(ACTIVITY_MAX, getInt(writeActivity.get(addr)) + ACTIVITY_INCREMENT));
|
||||
} else {
|
||||
writeActivity.put(addr, ACTIVITY_INCREMENT);
|
||||
}
|
||||
valueActivity.put(addr, e.getNewValue());
|
||||
setBy.put(addr, pc);
|
||||
}
|
||||
activeRam.add(addr);
|
||||
}
|
||||
};
|
||||
Computer.getComputer().getMemory().addListener(memoryListener);
|
||||
redrawThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mode == Modes.ACTIVITY) {
|
||||
// Activity heatmap mode
|
||||
while (isDisplayActive) {
|
||||
updateDetails();
|
||||
Graphics2D g = (Graphics2D) getGraphics();
|
||||
for (Iterator<Integer> i = activeRam.iterator(); i.hasNext();) {
|
||||
boolean remove = true;
|
||||
int addr = i.next();
|
||||
int read = getInt(readActivity.get(addr));
|
||||
if (read <= 0) {
|
||||
read = 0;
|
||||
} else {
|
||||
remove = false;
|
||||
readActivity.put(addr, read - ACTIVITY_DECREMENT);
|
||||
}
|
||||
int write = getInt(writeActivity.get(addr));
|
||||
if (write <= 0) {
|
||||
write = 0;
|
||||
} else {
|
||||
remove = false;
|
||||
writeActivity.put(addr, write - ACTIVITY_DECREMENT);
|
||||
}
|
||||
int pc = getInt(pcActivity.get(addr));
|
||||
if (pc <= 0) {
|
||||
pc = 0;
|
||||
} else {
|
||||
remove = false;
|
||||
pcActivity.put(addr, pc - ACTIVITY_DECREMENT);
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
i.remove();
|
||||
pcActivity.remove(addr);
|
||||
writeActivity.remove(addr);
|
||||
readActivity.remove(addr);
|
||||
}
|
||||
|
||||
drawActivity(addr, write, read, pc, g);
|
||||
}
|
||||
g.dispose();
|
||||
try {
|
||||
Thread.sleep(UPDATE_INTERVAL);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(MemorySpyGrid.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Redraw value, no activity counts needed
|
||||
while (isDisplayActive) {
|
||||
updateDetails();
|
||||
Graphics2D g = (Graphics2D) getGraphics();
|
||||
for (Iterator<Integer> i = valueActivity.keySet().iterator(); i.hasNext();) {
|
||||
int addr = i.next();
|
||||
int value = getInt(valueActivity.get(addr));
|
||||
i.remove();
|
||||
drawValue(addr, value, g);
|
||||
}
|
||||
g.dispose();
|
||||
try {
|
||||
Thread.sleep(UPDATE_INTERVAL);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(MemorySpyGrid.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
redrawThread.setName("Memory spy redraw");
|
||||
redrawThread.start();
|
||||
}
|
||||
|
||||
void stopDisplay() {
|
||||
isDisplayActive = false;
|
||||
if (memoryListener != null) {
|
||||
Computer.getComputer().getMemory().removeListener(memoryListener);
|
||||
memoryListener = null;
|
||||
}
|
||||
if (redrawThread != null && redrawThread.isAlive()) {
|
||||
try {
|
||||
redrawThread.join();
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(MemorySpyGrid.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setColorGradient(boolean useColor) {
|
||||
if (useColor) {
|
||||
gradient = colorGradient;
|
||||
} else {
|
||||
gradient = bwGradient;
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
void setFastFade(boolean fast) {
|
||||
if (fast) {
|
||||
ACTIVITY_DECREMENT = 20;
|
||||
} else {
|
||||
ACTIVITY_DECREMENT = 5;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Modes {
|
||||
|
||||
ACTIVITY, VALUE
|
||||
}
|
||||
}
|
|
@ -0,0 +1,516 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
|
||||
<NonVisualComponents>
|
||||
<Component class="javax.swing.ButtonGroup" name="searchTypes">
|
||||
</Component>
|
||||
<Component class="javax.swing.ButtonGroup" name="valueTypes">
|
||||
</Component>
|
||||
</NonVisualComponents>
|
||||
<Properties>
|
||||
<Property name="defaultCloseOperation" type="int" value="3"/>
|
||||
</Properties>
|
||||
<SyntheticProperties>
|
||||
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
|
||||
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
|
||||
</SyntheticProperties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="1"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="tabs" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="tabs" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JTabbedPane" name="tabs">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="cheatPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="Cheats">
|
||||
<Property name="tabTitle" type="java.lang.String" value="Cheats"/>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="activeCheatsScroll" pref="586" max="32767" attributes="0"/>
|
||||
<Component id="jSeparator1" alignment="0" max="32767" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="activeCheatsLabel" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="addNewCheatLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="addByteValueButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="addWordValueButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="addValueLabel" min="-2" pref="56" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="addValueField" min="-2" pref="79" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="addAddressLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="addAddressField" min="-2" pref="79" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="disableCheatButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="enableCheatButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="removeSelectedButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="activeCheatsLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="activeCheatsScroll" pref="118" max="32767" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="removeSelectedButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="disableCheatButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="enableCheatButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jSeparator1" min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="addNewCheatLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="addAddressLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="addAddressField" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="addValueLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="addValueField" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="addByteValueButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="addWordValueButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="activeCheatsLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Active Cheats"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="activeCheatsScroll">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTable" name="activeCheatsTable">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
|
||||
<Table columnCount="2" rowCount="0">
|
||||
<Column editable="false" title="Address" type="java.lang.String"/>
|
||||
<Column editable="false" title="Value" type="java.lang.String"/>
|
||||
</Table>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="removeSelectedButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Remove Selected"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="removeSelectedButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="addNewCheatLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Add a new cheat:"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSeparator" name="jSeparator1">
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="addValueLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Value"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="addAddressLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Address"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="addByteValueButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Add Byte Value"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="addByteValueButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="addWordValueButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Add Word Value"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="addWordValueButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="addAddressField">
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="addValueField">
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="disableCheatButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Disable Selected"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="disableCheatButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="enableCheatButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Enable Selected"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="enableCheatButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="searchPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="Search">
|
||||
<Property name="tabTitle" type="java.lang.String" value="Search"/>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="resultsScroll" pref="569" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="addSelected" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="resultsStatusLabel" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="searchForLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="searchForChange" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="searchForValue" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
|
||||
<Component id="searchForByte" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
|
||||
<Component id="searchForWord" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="valueLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="searchNumber" min="-2" pref="63" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="resetButton" min="-2" pref="76" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="searchButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace pref="158" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="addWatchLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="addWatchesButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="addStartLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="addEndLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="addEndNumber" max="32767" attributes="0"/>
|
||||
<Component id="addStartNumber" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="searchForLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="searchForValue" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="searchForByte" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="searchForChange" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="searchForWord" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="addStartLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="addStartNumber" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="searchNumber" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="valueLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="resetButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="searchButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="addEndLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="addEndNumber" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="addWatchesButton" alignment="3" min="-2" pref="24" max="-2" attributes="0"/>
|
||||
<Component id="addWatchLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="resultsScroll" pref="195" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="addSelected" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="resultsStatusLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JRadioButton" name="searchForValue">
|
||||
<Properties>
|
||||
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||
<ComponentRef name="searchTypes"/>
|
||||
</Property>
|
||||
<Property name="selected" type="boolean" value="true"/>
|
||||
<Property name="text" type="java.lang.String" value="Value"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="searchForValueActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="searchForLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Search for:"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JRadioButton" name="searchForChange">
|
||||
<Properties>
|
||||
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||
<ComponentRef name="searchTypes"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" value="Change"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="searchForChangeActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="searchNumber">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="0"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="searchNumberActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="valueLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Value:"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="resetButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Reset"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="resetButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="searchButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Search"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="searchButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="resultsStatusLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="No results"/>
|
||||
<Property name="verticalAlignment" type="int" value="1"/>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.SoftBevelBorderInfo">
|
||||
<BevelBorder/>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="resultsScroll">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTable" name="resultsTable">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
|
||||
<Table columnCount="3" rowCount="0">
|
||||
<Column editable="false" title="Address" type="java.lang.String"/>
|
||||
<Column editable="false" title="Last Search" type="java.lang.String"/>
|
||||
<Column editable="false" title="Current Value" type="java.lang.String"/>
|
||||
</Table>
|
||||
</Property>
|
||||
<Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor">
|
||||
<TableColumnModel selectionModel="0">
|
||||
<Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="false">
|
||||
<Title/>
|
||||
<Editor/>
|
||||
<Renderer/>
|
||||
</Column>
|
||||
<Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="false">
|
||||
<Title/>
|
||||
<Editor/>
|
||||
<Renderer/>
|
||||
</Column>
|
||||
<Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="false">
|
||||
<Title/>
|
||||
<Editor/>
|
||||
<Renderer/>
|
||||
</Column>
|
||||
</TableColumnModel>
|
||||
</Property>
|
||||
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
|
||||
<TableHeader reorderingAllowed="true" resizingAllowed="false"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="addSelected">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Add Selected"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="addSelectedActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JRadioButton" name="searchForByte">
|
||||
<Properties>
|
||||
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||
<ComponentRef name="valueTypes"/>
|
||||
</Property>
|
||||
<Property name="selected" type="boolean" value="true"/>
|
||||
<Property name="text" type="java.lang.String" value="Byte"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JRadioButton" name="searchForWord">
|
||||
<Properties>
|
||||
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
|
||||
<ComponentRef name="valueTypes"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" value="Word"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="addWatchLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Add Watches:"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="addStartLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Start"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="addStartNumber">
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="addEndNumber">
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="addEndLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="End"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="addWatchesButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Add"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="addWatchesButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
|
@ -0,0 +1,623 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.cheat;
|
||||
|
||||
import jace.core.Utility;
|
||||
import static jace.core.Utility.gripe;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This is the metacheat user interface. The actual logic of metacheat is in the
|
||||
* Metacheats class.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class MetaCheatForm extends javax.swing.JFrame {
|
||||
|
||||
/**
|
||||
* Creates new form MetaCheatForm
|
||||
*/
|
||||
public MetaCheatForm() {
|
||||
initComponents();
|
||||
this.setDefaultCloseOperation(HIDE_ON_CLOSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
searchTypes = new javax.swing.ButtonGroup();
|
||||
valueTypes = new javax.swing.ButtonGroup();
|
||||
tabs = new javax.swing.JTabbedPane();
|
||||
cheatPanel = new javax.swing.JPanel();
|
||||
activeCheatsLabel = new javax.swing.JLabel();
|
||||
activeCheatsScroll = new javax.swing.JScrollPane();
|
||||
activeCheatsTable = new javax.swing.JTable();
|
||||
removeSelectedButton = new javax.swing.JButton();
|
||||
addNewCheatLabel = new javax.swing.JLabel();
|
||||
jSeparator1 = new javax.swing.JSeparator();
|
||||
addValueLabel = new javax.swing.JLabel();
|
||||
addAddressLabel = new javax.swing.JLabel();
|
||||
addByteValueButton = new javax.swing.JButton();
|
||||
addWordValueButton = new javax.swing.JButton();
|
||||
addAddressField = new javax.swing.JTextField();
|
||||
addValueField = new javax.swing.JTextField();
|
||||
disableCheatButton = new javax.swing.JButton();
|
||||
enableCheatButton = new javax.swing.JButton();
|
||||
searchPanel = new javax.swing.JPanel();
|
||||
searchForValue = new javax.swing.JRadioButton();
|
||||
searchForLabel = new javax.swing.JLabel();
|
||||
searchForChange = new javax.swing.JRadioButton();
|
||||
searchNumber = new javax.swing.JTextField();
|
||||
valueLabel = new javax.swing.JLabel();
|
||||
resetButton = new javax.swing.JButton();
|
||||
searchButton = new javax.swing.JButton();
|
||||
resultsStatusLabel = new javax.swing.JLabel();
|
||||
resultsScroll = new javax.swing.JScrollPane();
|
||||
resultsTable = new javax.swing.JTable();
|
||||
addSelected = new javax.swing.JButton();
|
||||
searchForByte = new javax.swing.JRadioButton();
|
||||
searchForWord = new javax.swing.JRadioButton();
|
||||
addWatchLabel = new javax.swing.JLabel();
|
||||
addStartLabel = new javax.swing.JLabel();
|
||||
addStartNumber = new javax.swing.JTextField();
|
||||
addEndNumber = new javax.swing.JTextField();
|
||||
addEndLabel = new javax.swing.JLabel();
|
||||
addWatchesButton = new javax.swing.JButton();
|
||||
|
||||
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
|
||||
|
||||
activeCheatsLabel.setText("Active Cheats");
|
||||
|
||||
activeCheatsTable.setModel(new javax.swing.table.DefaultTableModel(
|
||||
new Object [][] {
|
||||
|
||||
},
|
||||
new String [] {
|
||||
"Address", "Value"
|
||||
}
|
||||
) {
|
||||
Class[] types = new Class [] {
|
||||
java.lang.String.class, java.lang.String.class
|
||||
};
|
||||
boolean[] canEdit = new boolean [] {
|
||||
false, false
|
||||
};
|
||||
|
||||
public Class getColumnClass(int columnIndex) {
|
||||
return types [columnIndex];
|
||||
}
|
||||
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
return canEdit [columnIndex];
|
||||
}
|
||||
});
|
||||
activeCheatsScroll.setViewportView(activeCheatsTable);
|
||||
|
||||
removeSelectedButton.setText("Remove Selected");
|
||||
removeSelectedButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
removeSelectedButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
addNewCheatLabel.setText("Add a new cheat:");
|
||||
|
||||
addValueLabel.setText("Value");
|
||||
|
||||
addAddressLabel.setText("Address");
|
||||
|
||||
addByteValueButton.setText("Add Byte Value");
|
||||
addByteValueButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
addByteValueButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
addWordValueButton.setText("Add Word Value");
|
||||
addWordValueButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
addWordValueButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
disableCheatButton.setText("Disable Selected");
|
||||
disableCheatButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
disableCheatButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
enableCheatButton.setText("Enable Selected");
|
||||
enableCheatButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
enableCheatButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout cheatPanelLayout = new javax.swing.GroupLayout(cheatPanel);
|
||||
cheatPanel.setLayout(cheatPanelLayout);
|
||||
cheatPanelLayout.setHorizontalGroup(
|
||||
cheatPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(cheatPanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(cheatPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(activeCheatsScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 569, Short.MAX_VALUE)
|
||||
.addComponent(jSeparator1)
|
||||
.addGroup(cheatPanelLayout.createSequentialGroup()
|
||||
.addGroup(cheatPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(activeCheatsLabel)
|
||||
.addComponent(addNewCheatLabel)
|
||||
.addGroup(cheatPanelLayout.createSequentialGroup()
|
||||
.addComponent(addByteValueButton)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(addWordValueButton))
|
||||
.addGroup(cheatPanelLayout.createSequentialGroup()
|
||||
.addComponent(addValueLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 56, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(addValueField, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGroup(cheatPanelLayout.createSequentialGroup()
|
||||
.addComponent(addAddressLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(addAddressField, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addGap(0, 0, Short.MAX_VALUE))
|
||||
.addGroup(cheatPanelLayout.createSequentialGroup()
|
||||
.addComponent(disableCheatButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(enableCheatButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(removeSelectedButton)))
|
||||
.addContainerGap())
|
||||
);
|
||||
cheatPanelLayout.setVerticalGroup(
|
||||
cheatPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(cheatPanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(activeCheatsLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(activeCheatsScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 118, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(cheatPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(removeSelectedButton)
|
||||
.addComponent(disableCheatButton)
|
||||
.addComponent(enableCheatButton))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(addNewCheatLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(cheatPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(addAddressLabel)
|
||||
.addComponent(addAddressField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(cheatPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(addValueLabel)
|
||||
.addComponent(addValueField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(cheatPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(addByteValueButton)
|
||||
.addComponent(addWordValueButton))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
tabs.addTab("Cheats", cheatPanel);
|
||||
|
||||
searchTypes.add(searchForValue);
|
||||
searchForValue.setSelected(true);
|
||||
searchForValue.setText("Value");
|
||||
searchForValue.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
searchForValueActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
searchForLabel.setText("Search for:");
|
||||
|
||||
searchTypes.add(searchForChange);
|
||||
searchForChange.setText("Change");
|
||||
searchForChange.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
searchForChangeActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
searchNumber.setText("0");
|
||||
searchNumber.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
searchNumberActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
valueLabel.setText("Value:");
|
||||
|
||||
resetButton.setText("Reset");
|
||||
resetButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
resetButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
searchButton.setText("Search");
|
||||
searchButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
searchButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
resultsStatusLabel.setText("No results");
|
||||
resultsStatusLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP);
|
||||
resultsStatusLabel.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED));
|
||||
|
||||
resultsTable.setModel(new javax.swing.table.DefaultTableModel(
|
||||
new Object [][] {
|
||||
|
||||
},
|
||||
new String [] {
|
||||
"Address", "Last Search", "Current Value"
|
||||
}
|
||||
) {
|
||||
Class[] types = new Class [] {
|
||||
java.lang.String.class, java.lang.String.class, java.lang.String.class
|
||||
};
|
||||
boolean[] canEdit = new boolean [] {
|
||||
false, false, false
|
||||
};
|
||||
|
||||
public Class getColumnClass(int columnIndex) {
|
||||
return types [columnIndex];
|
||||
}
|
||||
|
||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||
return canEdit [columnIndex];
|
||||
}
|
||||
});
|
||||
resultsTable.getTableHeader().setResizingAllowed(false);
|
||||
resultsScroll.setViewportView(resultsTable);
|
||||
resultsTable.getColumnModel().getColumn(0).setResizable(false);
|
||||
resultsTable.getColumnModel().getColumn(1).setResizable(false);
|
||||
resultsTable.getColumnModel().getColumn(2).setResizable(false);
|
||||
|
||||
addSelected.setText("Add Selected");
|
||||
addSelected.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
addSelectedActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
valueTypes.add(searchForByte);
|
||||
searchForByte.setSelected(true);
|
||||
searchForByte.setText("Byte");
|
||||
|
||||
valueTypes.add(searchForWord);
|
||||
searchForWord.setText("Word");
|
||||
|
||||
addWatchLabel.setText("Add Watches:");
|
||||
|
||||
addStartLabel.setText("Start");
|
||||
|
||||
addEndLabel.setText("End");
|
||||
|
||||
addWatchesButton.setText("Add");
|
||||
addWatchesButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
addWatchesButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout searchPanelLayout = new javax.swing.GroupLayout(searchPanel);
|
||||
searchPanel.setLayout(searchPanelLayout);
|
||||
searchPanelLayout.setHorizontalGroup(
|
||||
searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(resultsScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 569, Short.MAX_VALUE)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addComponent(addSelected)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(resultsStatusLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, searchPanelLayout.createSequentialGroup()
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addComponent(searchForLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(searchForChange)
|
||||
.addComponent(searchForValue))
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addGap(3, 3, 3)
|
||||
.addComponent(searchForByte))
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addGap(6, 6, 6)
|
||||
.addComponent(searchForWord))))
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addComponent(valueLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(searchNumber, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(resetButton, javax.swing.GroupLayout.PREFERRED_SIZE, 76, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(searchButton)))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 158, Short.MAX_VALUE)
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, searchPanelLayout.createSequentialGroup()
|
||||
.addComponent(addWatchLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(addWatchesButton))
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(addStartLabel)
|
||||
.addComponent(addEndLabel))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(addEndNumber)
|
||||
.addComponent(addStartNumber))))))
|
||||
.addContainerGap())
|
||||
);
|
||||
searchPanelLayout.setVerticalGroup(
|
||||
searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(searchForLabel)
|
||||
.addComponent(searchForValue)
|
||||
.addComponent(searchForByte))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(searchForChange)
|
||||
.addComponent(searchForWord)
|
||||
.addComponent(addStartLabel)
|
||||
.addComponent(addStartNumber, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(searchNumber, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(valueLabel)
|
||||
.addComponent(resetButton)
|
||||
.addComponent(searchButton)
|
||||
.addComponent(addEndLabel)
|
||||
.addComponent(addEndNumber, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(addWatchesButton, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(addWatchLabel))))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(resultsScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 194, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(addSelected)
|
||||
.addComponent(resultsStatusLabel))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
tabs.addTab("Search", searchPanel);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
|
||||
getContentPane().setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(tabs)
|
||||
.addContainerGap())
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(tabs)
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
pack();
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void searchForValueActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchForValueActionPerformed
|
||||
// TODO add your handling code here:
|
||||
}//GEN-LAST:event_searchForValueActionPerformed
|
||||
|
||||
private void searchForChangeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchForChangeActionPerformed
|
||||
// TODO add your handling code here:
|
||||
}//GEN-LAST:event_searchForChangeActionPerformed
|
||||
|
||||
private void searchNumberActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchNumberActionPerformed
|
||||
// TODO add your handling code here:
|
||||
}//GEN-LAST:event_searchNumberActionPerformed
|
||||
|
||||
private void resetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resetButtonActionPerformed
|
||||
MetaCheats.singleton.resetSearch();
|
||||
resultsStatusLabel.setText("Results cleared.");
|
||||
}//GEN-LAST:event_resetButtonActionPerformed
|
||||
|
||||
private void addSelectedActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addSelectedActionPerformed
|
||||
List<Integer> addr = new ArrayList<Integer>(MetaCheats.singleton.results.keySet());
|
||||
int val = Utility.parseHexInt(searchNumber.getText());
|
||||
for (int i : resultsTable.getSelectedRows()) {
|
||||
if (searchForByte.isSelected()) {
|
||||
MetaCheats.singleton.addByteCheat(addr.get(i), val);
|
||||
} else {
|
||||
MetaCheats.singleton.addWordCheat(addr.get(i), val);
|
||||
}
|
||||
}
|
||||
}//GEN-LAST:event_addSelectedActionPerformed
|
||||
|
||||
private void removeSelectedButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeSelectedButtonActionPerformed
|
||||
Set<Integer> remove = new HashSet<Integer>();
|
||||
for (int i : activeCheatsTable.getSelectedRows()) {
|
||||
String s = String.valueOf(activeCheatsTable.getModel().getValueAt(i, 0));
|
||||
remove.add(Utility.parseHexInt(s));
|
||||
}
|
||||
for (int i : remove) {
|
||||
MetaCheats.singleton.removeCheat(i);
|
||||
}
|
||||
}//GEN-LAST:event_removeSelectedButtonActionPerformed
|
||||
|
||||
private void addByteValueButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addByteValueButtonActionPerformed
|
||||
try {
|
||||
int addr = Utility.parseHexInt(addAddressField.getText());
|
||||
int val = Utility.parseHexInt(addValueField.getText());
|
||||
MetaCheats.singleton.addByteCheat(addr, val);
|
||||
} catch (NullPointerException e) {
|
||||
gripe("Please enure that the address and value fields are correctly filled in.");
|
||||
}
|
||||
}//GEN-LAST:event_addByteValueButtonActionPerformed
|
||||
|
||||
private void addWordValueButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addWordValueButtonActionPerformed
|
||||
try {
|
||||
int addr = Utility.parseHexInt(addAddressField.getText());
|
||||
int val = Utility.parseHexInt(addValueField.getText());
|
||||
MetaCheats.singleton.addWordCheat(addr, val);
|
||||
} catch (NullPointerException e) {
|
||||
gripe("Please enure that the address and value fields are correctly filled in.");
|
||||
}
|
||||
}//GEN-LAST:event_addWordValueButtonActionPerformed
|
||||
|
||||
private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed
|
||||
if (searchForChange.isSelected() == searchForValue.isSelected()) {
|
||||
gripe("Please select if you want to search for a fixed value or a delta change");
|
||||
return;
|
||||
}
|
||||
if (searchForByte.isSelected() == searchForWord.isSelected()) {
|
||||
gripe("Please select if you want to search for a byte or a word value");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int val = Utility.parseHexInt(searchNumber.getText());
|
||||
MetaCheats.singleton.performSearch(searchForChange.isSelected(), searchForByte.isSelected(), val);
|
||||
} catch (NullPointerException e) {
|
||||
gripe("Please enter a value");
|
||||
}
|
||||
}//GEN-LAST:event_searchButtonActionPerformed
|
||||
|
||||
private void disableCheatButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_disableCheatButtonActionPerformed
|
||||
for (int i : activeCheatsTable.getSelectedRows()) {
|
||||
String s = String.valueOf(activeCheatsTable.getModel().getValueAt(i, 0));
|
||||
MetaCheats.singleton.disableCheat(Utility.parseHexInt(s));
|
||||
}
|
||||
|
||||
}//GEN-LAST:event_disableCheatButtonActionPerformed
|
||||
|
||||
private void enableCheatButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_enableCheatButtonActionPerformed
|
||||
for (int i : activeCheatsTable.getSelectedRows()) {
|
||||
String s = String.valueOf(activeCheatsTable.getModel().getValueAt(i, 0));
|
||||
MetaCheats.singleton.enableCheat(Utility.parseHexInt(s));
|
||||
}
|
||||
}//GEN-LAST:event_enableCheatButtonActionPerformed
|
||||
|
||||
private void addWatchesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addWatchesButtonActionPerformed
|
||||
try {
|
||||
int addrStart = Utility.parseHexInt(addStartNumber.getText());
|
||||
int addrEnd = Utility.parseHexInt(addEndNumber.getText());
|
||||
if (addrStart > addrEnd) {
|
||||
gripe("Start address must be smaller than end address!");
|
||||
return;
|
||||
}
|
||||
MetaCheats.singleton.addWatches(addrStart, addrEnd);
|
||||
resultsStatusLabel.setText("Added range to watch list.");
|
||||
} catch (NullPointerException e) {
|
||||
gripe("Please enure that the start and end fields are correctly filled in.");
|
||||
}
|
||||
}//GEN-LAST:event_addWatchesButtonActionPerformed
|
||||
|
||||
/**
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
/* Set the Nimbus look and feel */
|
||||
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
|
||||
/* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
|
||||
* For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
|
||||
*/
|
||||
try {
|
||||
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
|
||||
if ("Nimbus".equals(info.getName())) {
|
||||
javax.swing.UIManager.setLookAndFeel(info.getClassName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
java.util.logging.Logger.getLogger(MetaCheatForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||
} catch (InstantiationException ex) {
|
||||
java.util.logging.Logger.getLogger(MetaCheatForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||
} catch (IllegalAccessException ex) {
|
||||
java.util.logging.Logger.getLogger(MetaCheatForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
|
||||
java.util.logging.Logger.getLogger(MetaCheatForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
/* Create and display the form */
|
||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
new MetaCheatForm().setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
public javax.swing.JLabel activeCheatsLabel;
|
||||
public javax.swing.JScrollPane activeCheatsScroll;
|
||||
public javax.swing.JTable activeCheatsTable;
|
||||
public javax.swing.JTextField addAddressField;
|
||||
public javax.swing.JLabel addAddressLabel;
|
||||
public javax.swing.JButton addByteValueButton;
|
||||
public javax.swing.JLabel addEndLabel;
|
||||
public javax.swing.JTextField addEndNumber;
|
||||
public javax.swing.JLabel addNewCheatLabel;
|
||||
public javax.swing.JButton addSelected;
|
||||
public javax.swing.JLabel addStartLabel;
|
||||
public javax.swing.JTextField addStartNumber;
|
||||
public javax.swing.JTextField addValueField;
|
||||
public javax.swing.JLabel addValueLabel;
|
||||
public javax.swing.JLabel addWatchLabel;
|
||||
public javax.swing.JButton addWatchesButton;
|
||||
public javax.swing.JButton addWordValueButton;
|
||||
public javax.swing.JPanel cheatPanel;
|
||||
public javax.swing.JButton disableCheatButton;
|
||||
public javax.swing.JButton enableCheatButton;
|
||||
public javax.swing.JSeparator jSeparator1;
|
||||
public javax.swing.JButton removeSelectedButton;
|
||||
public javax.swing.JButton resetButton;
|
||||
public javax.swing.JScrollPane resultsScroll;
|
||||
public javax.swing.JLabel resultsStatusLabel;
|
||||
public javax.swing.JTable resultsTable;
|
||||
public javax.swing.JButton searchButton;
|
||||
public javax.swing.JRadioButton searchForByte;
|
||||
public javax.swing.JRadioButton searchForChange;
|
||||
public javax.swing.JLabel searchForLabel;
|
||||
public javax.swing.JRadioButton searchForValue;
|
||||
public javax.swing.JRadioButton searchForWord;
|
||||
public javax.swing.JTextField searchNumber;
|
||||
public javax.swing.JPanel searchPanel;
|
||||
public javax.swing.ButtonGroup searchTypes;
|
||||
public javax.swing.JTabbedPane tabs;
|
||||
public javax.swing.JLabel valueLabel;
|
||||
public javax.swing.ButtonGroup valueTypes;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.cheat;
|
||||
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.core.Computer;
|
||||
import jace.core.KeyHandler;
|
||||
import jace.core.Keyboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
|
||||
/**
|
||||
* Basic mame-style cheats. The user interface is in MetaCheatForm.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class MetaCheats extends Cheats {
|
||||
static MetaCheats singleton = null;
|
||||
// This is used to help the handler exit faster when there is nothing to do
|
||||
boolean noCheats = true;
|
||||
|
||||
public MetaCheats() {
|
||||
super();
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
|
||||
Map<Integer, Integer> holdBytes = new TreeMap<Integer,Integer>();
|
||||
Map<Integer, Integer> holdWords = new TreeMap<Integer,Integer>();
|
||||
Set<Integer> disabled = new HashSet<Integer>();
|
||||
Map<Integer, Integer> results = new TreeMap<Integer,Integer>();
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "Meta-cheat engine";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public static int MAX_RESULTS_SHOWN = 256;
|
||||
|
||||
public MetaCheatForm form = null;
|
||||
public boolean isDrawing = false;
|
||||
public void redrawResults() {
|
||||
if (isDrawing) {
|
||||
return;
|
||||
}
|
||||
isDrawing = true;
|
||||
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(MetaCheats.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
RAM128k ram = (RAM128k) Computer.getComputer().getMemory();
|
||||
DefaultTableModel model = (DefaultTableModel) form.resultsTable.getModel();
|
||||
if (results.size() > MAX_RESULTS_SHOWN) {
|
||||
model.setRowCount(0);
|
||||
isDrawing = false;
|
||||
return;
|
||||
}
|
||||
boolean useWord = form.searchForWord.isSelected();
|
||||
if (model.getRowCount() != results.size()) {
|
||||
model.setRowCount(0);
|
||||
List<Integer> iter = new ArrayList<Integer>(results.keySet());
|
||||
for (Integer i : iter) {
|
||||
int val = results.get(i);
|
||||
if (useWord) {
|
||||
int current = ram.readWordRaw(i) & 0x0ffff;
|
||||
model.addRow(new Object[]{hex(i, 4), val + " ("+hex(val,4)+")", current + " ("+hex(current,4)+")"});
|
||||
} else {
|
||||
int current = ram.readRaw(i) & 0x0ff;
|
||||
model.addRow(new Object[]{hex(i, 4), val + " ("+hex(val,2)+")", current + " ("+hex(current,2)+")"});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<Integer> iter = new ArrayList<Integer>(results.keySet());
|
||||
for (int i=0; i < iter.size(); i++) {
|
||||
int val = results.get(iter.get(i));
|
||||
if (useWord) {
|
||||
int current = ram.readWordRaw(iter.get(i)) & 0x0ffff;
|
||||
model.setValueAt(val + " ("+hex(val,4)+")", i, 1);
|
||||
model.setValueAt(current + " ("+hex(current,4)+")", i, 2);
|
||||
} else {
|
||||
int current = ram.readRaw(iter.get(i)) & 0x0ff;
|
||||
model.setValueAt(val + " ("+hex(val,2)+")", i, 1);
|
||||
model.setValueAt(current + " ("+hex(current,2)+")", i, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
isDrawing = false;
|
||||
}
|
||||
|
||||
public static String hex(int val, int size) {
|
||||
String out = Integer.toHexString(val);
|
||||
while (out.length() < size) {
|
||||
out = "0"+out;
|
||||
}
|
||||
return "$"+out;
|
||||
}
|
||||
|
||||
public void redrawCheats() {
|
||||
noCheats = holdBytes.isEmpty() && holdWords.isEmpty() && disabled.isEmpty();
|
||||
DefaultTableModel model = (DefaultTableModel) form.activeCheatsTable.getModel();
|
||||
model.setRowCount(0);
|
||||
for (Integer i : holdBytes.keySet()) {
|
||||
String loc = hex(i, 4);
|
||||
if (disabled.contains(i)) loc += " (off)";
|
||||
int val = holdBytes.get(i);
|
||||
model.addRow(new Object[]{loc, val + " ("+hex(val,2)+")"});
|
||||
}
|
||||
for (Integer i : holdWords.keySet()) {
|
||||
String loc = hex(i, 4);
|
||||
if (disabled.contains(i)) loc += " (off)";
|
||||
int val = holdWords.get(i);
|
||||
model.addRow(new Object[]{loc, val + " ("+hex(val,4)+")"});
|
||||
}
|
||||
}
|
||||
public void showCheatForm() {
|
||||
if (form == null) {
|
||||
form = new MetaCheatForm();
|
||||
}
|
||||
form.setVisible(true);
|
||||
}
|
||||
|
||||
MemorySpy spy = null;
|
||||
public void showMemorySpy() {
|
||||
if (spy == null) {
|
||||
spy = new MemorySpy();
|
||||
}
|
||||
spy.setVisible(true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
this.addCheat(new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.ANY, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (noCheats) return;
|
||||
if (disabled.contains(e.getAddress())) return;
|
||||
if (holdBytes.containsKey(e.getAddress())) {
|
||||
e.setNewValue(holdBytes.get(e.getAddress()));
|
||||
} else if (holdWords.containsKey(e.getAddress())) {
|
||||
e.setNewValue(holdWords.get(e.getAddress()) & 0x0ff);
|
||||
} else if (holdWords.containsKey(e.getAddress()-1)) {
|
||||
if (disabled.contains(e.getAddress()-1)) return;
|
||||
e.setNewValue((holdWords.get(e.getAddress()-1)>>8) & 0x0ff);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.addCheat(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ANY, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (results.isEmpty()) return;
|
||||
if (results.size() > MAX_RESULTS_SHOWN || isDrawing) return;
|
||||
if (results.containsKey(e.getAddress()) || results.containsKey(e.getAddress()-1)) {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
redrawResults();
|
||||
}
|
||||
});
|
||||
t.setName("Metacheat results updater");
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Keyboard.registerKeyHandler(new KeyHandler(KeyEvent.VK_END) {
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent e) {
|
||||
showCheatForm();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyDown(KeyEvent e) {
|
||||
return false;
|
||||
}
|
||||
}, this);
|
||||
Keyboard.registerKeyHandler(new KeyHandler(KeyEvent.VK_HOME) {
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent e) {
|
||||
showMemorySpy();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyDown(KeyEvent e) {
|
||||
return false;
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
super.detach();
|
||||
Keyboard.unregisterAllHandlers(this);
|
||||
}
|
||||
|
||||
public void addByteCheat(int addr, int val) {
|
||||
holdBytes.put(addr, val);
|
||||
redrawCheats();
|
||||
}
|
||||
|
||||
public void addWordCheat(int addr, int val) {
|
||||
holdWords.put(addr, val);
|
||||
redrawCheats();
|
||||
}
|
||||
|
||||
void removeCheat(int i) {
|
||||
holdBytes.remove(i);
|
||||
holdWords.remove(i);
|
||||
disabled.remove(i);
|
||||
redrawCheats();
|
||||
}
|
||||
|
||||
void resetSearch() {
|
||||
results.clear();
|
||||
redrawResults();
|
||||
}
|
||||
|
||||
void performSearch(boolean useDeltaSearch, boolean searchForByteValues, int val) {
|
||||
RAM128k ram = (RAM128k) Computer.getComputer().getMemory();
|
||||
if (results.isEmpty()) {
|
||||
int max = 0x010000;
|
||||
if (!searchForByteValues) max--;
|
||||
for (int i=0; i < max; i++) {
|
||||
if (i >= 0x0c000 && i <= 0x0cfff) continue;
|
||||
int v = searchForByteValues ? ram.readRaw(i) & 0x0ff : ram.readWordRaw(i) & 0x0ffff;
|
||||
if (useDeltaSearch) {
|
||||
results.put(i, v);
|
||||
} else if (v == val) {
|
||||
results.put(i, v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Set<Integer> remove = new HashSet<Integer>();
|
||||
for (Integer i : results.keySet()) {
|
||||
int v = searchForByteValues ? ram.readRaw(i) & 0x0ff : ram.readWordRaw(i) & 0x0ffff;
|
||||
if (useDeltaSearch) {
|
||||
if (v - results.get(i) != val) {
|
||||
remove.add(i);
|
||||
} else {
|
||||
results.put(i,v);
|
||||
}
|
||||
} else {
|
||||
if (v != val) {
|
||||
remove.add(i);
|
||||
} else {
|
||||
results.put(i,v);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Integer i : remove) {
|
||||
results.remove(i);
|
||||
}
|
||||
}
|
||||
form.resultsStatusLabel.setText("Search found "+results.size()+" result(s).");
|
||||
redrawResults();
|
||||
}
|
||||
|
||||
void enableCheat(int addr) {
|
||||
disabled.remove(addr);
|
||||
redrawCheats();
|
||||
}
|
||||
|
||||
void disableCheat(int addr) {
|
||||
disabled.add(addr);
|
||||
redrawCheats();
|
||||
}
|
||||
|
||||
void addWatches(int addrStart, int addrEnd) {
|
||||
RAM128k ram = (RAM128k) Computer.getComputer().getMemory();
|
||||
if (form == null) return;
|
||||
boolean searchForByteValues = form.searchForByte.isSelected();
|
||||
for (int i = addrStart; i <= addrEnd; i = i + (searchForByteValues ? 1 : 2)) {
|
||||
int v = searchForByteValues ? ram.readRaw(i) & 0x0ff : ram.readWordRaw(i) & 0x0ffff;
|
||||
results.put(i,v);
|
||||
}
|
||||
redrawResults();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,455 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.cheat;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.core.Computer;
|
||||
import jace.core.PagedMemory;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import java.awt.Component;
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
/**
|
||||
* Prince of Persia game cheats. This would not have been possible without the
|
||||
* source. I am eternally grateful to Jordan Mechner both for creating this
|
||||
* game, and for being so kind to release the source code to it so that we can
|
||||
* learn how it works. Where possible, I've indicated where I found the various
|
||||
* game variables in the original source so that it might help anyone else
|
||||
* trying to learn how this game works.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class PrinceOfPersiaCheats extends Cheats implements MouseListener {
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Feather fall", defaultValue = "false", description = "Fall like a feather!")
|
||||
public static boolean velocityHack;
|
||||
// Game memory locations
|
||||
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/01%20POP%20Source/Source/GAMEEQ.S
|
||||
@ConfigurableField(category = "Hack", name = "Invincibility", defaultValue = "false", description = "Warning: will crash game if you are impaled")
|
||||
public static boolean invincibilityHack;
|
||||
@ConfigurableField(category = "Hack", name = "Infinite Time", defaultValue = "false", description = "Freeze the clock")
|
||||
public static boolean timeHack;
|
||||
@ConfigurableField(category = "Hack", name = "Sleepy Time", defaultValue = "false", description = "Enemies won't react")
|
||||
public static boolean sleepHack;
|
||||
@ConfigurableField(category = "Hack", name = "Can haz sword?", defaultValue = "false", description = "Start with sword in level 1")
|
||||
public static boolean swordHack;
|
||||
@ConfigurableField(category = "Hack", name = "Mouse", defaultValue = "false", description = "Left click kills/opens, Right click teleports")
|
||||
public static boolean mouseHack;
|
||||
boolean mouseRegistered = false;
|
||||
public static int PREV = 0x02b;
|
||||
public static int SPREV = 0x02e;
|
||||
public static int CharPosn = 0x040;
|
||||
public static int CharX = 0x041;
|
||||
public static int CharY = 0x042;
|
||||
public static int CharFace = 0x043;
|
||||
public static int CharBlockX = 0x44;
|
||||
public static int CharBlockY = 0x45;
|
||||
public static int CharAction = 0x46;
|
||||
public static int CharXVel = 0x47;
|
||||
public static int CharYVel = 0x48;
|
||||
public static int CharSeq = 0x49; // Word
|
||||
public static int CharScrn = 0x4b;
|
||||
public static int CharRepeat = 0x4c;
|
||||
public static int CharID = 0x4d;
|
||||
public static int CharSword = 0x4e;
|
||||
public static int CharLife = 0x4f;
|
||||
public static int KidX = 0x051;
|
||||
public static int KidY = 0x052;
|
||||
public static int KidFace = 0x53;
|
||||
public static int KidBlockX = 0x54;
|
||||
public static int KidBlockY = 0x55;
|
||||
public static int KidAction = 0x56;
|
||||
public static int KidScrn = 0x5b;
|
||||
public static int ShadBlockX = 0x64;
|
||||
public static int ShadBlockY = 0x65;
|
||||
public static int ShadLife = 0x06f;
|
||||
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/02%20POP%20Disk%20Routines/CP.525/RYELLOW1.S
|
||||
public static int deprotectCheckYellow = 0x07c;
|
||||
public static int NumTrans = 0x096;
|
||||
public static int OppStrength = 0x0cc;
|
||||
public static int KidStrength = 0x0ce;
|
||||
public static int EnemyAlert = 0x0d1;
|
||||
public static int ChgOppStr = 0x0d2;
|
||||
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/02%20POP%20Disk%20Routines/CP.525/PURPLE.MAIN.S
|
||||
public static int deprotectCheckPurple = 0x0da;
|
||||
public static int Heoric = 0x0d3;
|
||||
public static int InEditor = 0x0202;
|
||||
public static int MinLeft = 0x0300;
|
||||
public static int hasSword = 0x030a;
|
||||
public static int mobtables = 0x0b600;
|
||||
public static int trloc = mobtables;
|
||||
public static int trscrn = trloc + 0x020;
|
||||
public static int trdirec = trscrn + 0x020;
|
||||
// Blueprint (map level data)0
|
||||
public static int BlueSpec = 0x0b9d0;
|
||||
public static int LinkLoc = 0x0bca0;
|
||||
public static int LinkMap = 0x0bda0;
|
||||
public static int Map = 0x0bea0;
|
||||
public static int MapInfo = 0x0bf00;
|
||||
public static int RedBufs = 0x05e00;
|
||||
public static int RedBuf = RedBufs + 90;
|
||||
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/01%20POP%20Source/Source/EQ.S
|
||||
public static int WipeBuf = RedBuf + 90;
|
||||
public static int MoveBuf = WipeBuf + 30;
|
||||
// Object types
|
||||
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/01%20POP%20Source/Source/MOVEDATA.S
|
||||
public static int space = 0;
|
||||
public static int floor = 1;
|
||||
public static int spikes = 2;
|
||||
public static int posts = 3;
|
||||
public static int gate = 4;
|
||||
public static int dpressplate = 5;
|
||||
public static int pressplate = 6;
|
||||
public static int panelwif = 7;
|
||||
public static int pillarbottom = 8;
|
||||
public static int pillartop = 9;
|
||||
public static int flask = 10;
|
||||
public static int loose = 11;
|
||||
public static int panelwof = 12;
|
||||
public static int mirror = 13;
|
||||
public static int rubble = 14;
|
||||
public static int upressplate = 15;
|
||||
public static int exit = 16;
|
||||
public static int exit2 = 17;
|
||||
public static int slicer = 18;
|
||||
public static int torch = 19;
|
||||
public static int block = 20;
|
||||
public static int bones = 21;
|
||||
public static int sword = 22;
|
||||
public static int window = 23;
|
||||
public static int window2 = 24;
|
||||
public static int archbot = 25;
|
||||
public static int archtop1 = 26;
|
||||
public static int archtop2 = 27;
|
||||
public static int archtop3 = 28;
|
||||
public static int archtop4 = 29;
|
||||
// This is the correct value for an open exit door.
|
||||
public static int ExitOpen = 172;
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return ("Prince of Persia");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
if (velocityHack) {
|
||||
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(CharYVel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
registerMouse();
|
||||
if (!SoftSwitches.AUXZP.getState()) {
|
||||
return;
|
||||
}
|
||||
int newVel = e.getNewValue();
|
||||
if (newVel > 5) {
|
||||
newVel = 1;
|
||||
}
|
||||
e.setNewValue(newVel & 0x0ff);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (invincibilityHack) {
|
||||
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(KidStrength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
registerMouse();
|
||||
if (!SoftSwitches.AUXZP.getState()) {
|
||||
return;
|
||||
}
|
||||
e.setNewValue(3);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (sleepHack) {
|
||||
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(EnemyAlert);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
registerMouse();
|
||||
if (!SoftSwitches.AUXZP.getState()) {
|
||||
return;
|
||||
}
|
||||
e.setNewValue(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (swordHack) {
|
||||
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(hasSword);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
registerMouse();
|
||||
if (!SoftSwitches.AUXZP.getState()) {
|
||||
return;
|
||||
}
|
||||
e.setNewValue(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (timeHack) {
|
||||
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(MinLeft);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
registerMouse();
|
||||
if (!SoftSwitches.AUXZP.getState()) {
|
||||
return;
|
||||
}
|
||||
e.setNewValue(0x069);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (mouseHack) {
|
||||
if (Emulator.getScreen() != null) {
|
||||
Emulator.getScreen().addMouseListener(this);
|
||||
} else {
|
||||
mouseRegistered = true;
|
||||
}
|
||||
} else {
|
||||
if (Emulator.getScreen() != null) {
|
||||
Emulator.getScreen().removeMouseListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
super.detach();
|
||||
if (Emulator.getScreen() != null) {
|
||||
Emulator.getScreen().removeMouseListener(this);
|
||||
}
|
||||
mouseRegistered = false;
|
||||
}
|
||||
public static int BlueType = 0x0b700;
|
||||
|
||||
public void registerMouse() {
|
||||
if (mouseRegistered) {
|
||||
Component drawingArea = Emulator.getScreen();
|
||||
if (drawingArea == null) {
|
||||
return;
|
||||
}
|
||||
Emulator.getScreen().addMouseListener(this);
|
||||
mouseRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent me) {
|
||||
Component drawingArea = Emulator.getScreen();
|
||||
if (drawingArea == null) {
|
||||
return;
|
||||
}
|
||||
Point currentMouseLocation = MouseInfo.getPointerInfo().getLocation();
|
||||
Point topLeft = drawingArea.getLocationOnScreen();
|
||||
Double x = (currentMouseLocation.x - topLeft.x) / drawingArea.getSize().getWidth();
|
||||
// Offset y by three pixels to account for tiles above
|
||||
Double y = (currentMouseLocation.y - topLeft.y) / drawingArea.getSize().getHeight() - 0.015625;
|
||||
// Now we have the x and y coordinates ranging from 0 to 1.0, scale to POP values
|
||||
int row = y < 0 ? -1 : (int) (y * 3);
|
||||
int col = (int) (x * 10);
|
||||
|
||||
// Do a check if we are at the bottom of the tile, the user might have been clicking on the tile to the right.
|
||||
// This accounts for the isometric view and allows a little more flexibility, not to mention warping behind gates
|
||||
// that are on the left edge of the screen!
|
||||
int yCoor = ((int) (y * 192) % 63);
|
||||
if (yCoor >= 47) {
|
||||
double yOffset = 1.0 - (((double) yCoor - 47.0) / 16.0);
|
||||
int xCoor = ((int) (x * 280) % 28);
|
||||
double xOffset = ((double) xCoor) / 28.0;
|
||||
if (xOffset <= yOffset) {
|
||||
col--;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: POP uses a 255-pixel horizontal axis, Pixels 0-57 are offscreen to the left
|
||||
// and 198-255 offscreen to the right.
|
||||
|
||||
// System.out.println("Clicked on " + col + "," + row + " -- screen " + (x * 280) + "," + (y * 192));
|
||||
RAM128k mem = (RAM128k) Computer.getComputer().getMemory();
|
||||
PagedMemory auxMem = mem.getAuxMemory();
|
||||
|
||||
if (me.getButton() == MouseEvent.BUTTON1) {
|
||||
// Left click hacks
|
||||
// See if there is an opponent we can kill off.
|
||||
int opponentX = auxMem.readByte(ShadBlockX);
|
||||
int opponentY = auxMem.readByte(ShadBlockY);
|
||||
int opponentLife = auxMem.readByte(ShadLife);
|
||||
// If there is a guy near where the user clicked and he's alive, then kill 'em.
|
||||
if (opponentLife != 0 && opponentY == row && Math.abs(col - opponentX) <= 1) {
|
||||
// System.out.println("Enemy at " + opponentX + "," + opponentY + "; life=" + opponentLife);
|
||||
// Occasionally, if the code is at the right spot this will cause the special effect of a hit to appear
|
||||
auxMem.writeByte(ChgOppStr, (byte) -opponentLife);
|
||||
// And this will kill the dude pretty much right away.
|
||||
auxMem.writeByte(ShadLife, (byte) 0);
|
||||
} else if (row >= 0 && col >= 0) {
|
||||
// Try to perform actions on the block clicked as well as to the left and right of it.
|
||||
// This opens gates and exits.
|
||||
performAction(row, col, 1);
|
||||
performAction(row, col - 1, 1);
|
||||
performAction(row, col + 1, 1);
|
||||
}
|
||||
} else {
|
||||
// Right/middle click == warp
|
||||
byte warpX = (byte) (x * 140 + 58);
|
||||
// This aliases the Y coordinate so the prince is on the floor at the correct spot.
|
||||
byte warpY = (byte) ((row * 63) + 54);
|
||||
// System.out.println("Warping to " + warpX + "," + warpY);
|
||||
auxMem.writeByte(KidX, warpX);
|
||||
auxMem.writeByte(KidY, warpY);
|
||||
auxMem.writeByte(KidBlockX, (byte) col);
|
||||
auxMem.writeByte(KidBlockY, (byte) row);
|
||||
// Set action to bump into a wall so it can reset the kid's feet on the ground correctly.
|
||||
// Not sure if this has any real effect but things seem to be working (so I'll just leave this here...)
|
||||
auxMem.writeByte(KidAction, (byte) 5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param row
|
||||
* @param col
|
||||
* @param direction
|
||||
*/
|
||||
public void performAction(int row, int col, int direction) {
|
||||
RAM128k mem = (RAM128k) Computer.getComputer().getMemory();
|
||||
PagedMemory auxMem = mem.getAuxMemory();
|
||||
byte currentScrn = auxMem.readByte(KidScrn);
|
||||
if (col < 0) {
|
||||
col += 10;
|
||||
int scrnLeft = auxMem.readByte(Map + ((currentScrn - 1) * 4));
|
||||
if (scrnLeft == 0) {
|
||||
return;
|
||||
}
|
||||
currentScrn = (byte) scrnLeft;
|
||||
byte prev = auxMem.readByte(PREV + row);
|
||||
byte sprev = auxMem.readByte(SPREV + row);
|
||||
// If the block to the left is gate, let's lie about it being open... for science
|
||||
// This causes odd-looking screen behavior but it gets the job done.
|
||||
if (prev == 4) {
|
||||
// Update the temp variable that represents that object
|
||||
auxMem.writeByte(SPREV + row, (byte) 255);
|
||||
// And also update the blueprint
|
||||
auxMem.writeByte(BlueSpec + ((scrnLeft - 1) * 30) + row * 10 + 9, (byte) 255);
|
||||
}
|
||||
// System.out.println("Looking at room to left, row "+row+": "+Integer.toHexString(prev)+","+Integer.toHexString(sprev));
|
||||
} else if (col >= 10) {
|
||||
// This code will probably never be called but here just in case.
|
||||
col -= 10;
|
||||
int scrnRight = auxMem.readByte(Map + ((currentScrn - 1) * 4) + 1);
|
||||
if (scrnRight == 0) {
|
||||
return;
|
||||
}
|
||||
currentScrn = (byte) scrnRight;
|
||||
}
|
||||
int numTransition = auxMem.readByte(NumTrans);
|
||||
byte clickedLoc = (byte) (row * 10 + col);
|
||||
// Figure out what kind of block is there
|
||||
int blockType = auxMem.readByte(BlueType + (currentScrn - 1) * 30 + row * 10 + col) & 0x01f;
|
||||
if (blockType == exit2 || blockType == exit) {
|
||||
// Open the exit by changing the map data and adding the tiles to the move buffer
|
||||
auxMem.writeByte(BlueSpec + (currentScrn - 1) * 30 + row * 10 + col, (byte) ExitOpen);
|
||||
direction = 1;
|
||||
// Tell the graphics engine that this piece has moved.
|
||||
auxMem.writeByte(MoveBuf + row * 10 + col, (byte) 2);
|
||||
}
|
||||
if (blockType == gate || blockType == exit2 || blockType == exit) {
|
||||
// If the object in question can be opened (exit or gate) add it to the transitional animation buffer
|
||||
//System.out.print("Triggering screen " + currentScrn + " at pos " + clickedLoc);
|
||||
boolean addTransition = false;
|
||||
if (numTransition == 0) {
|
||||
addTransition = true;
|
||||
} else {
|
||||
addTransition = true;
|
||||
for (int i = 1; i <= numTransition; i++) {
|
||||
byte scrn = auxMem.readByte(trscrn + i);
|
||||
byte loc = auxMem.readByte(trloc + i);
|
||||
if (scrn == currentScrn && loc == clickedLoc) {
|
||||
// Entry already exists, just change its direction
|
||||
auxMem.writeByte(trdirec + i, (byte) direction);
|
||||
addTransition = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (addTransition && numTransition >= 0x20) {
|
||||
addTransition = false;
|
||||
}
|
||||
}
|
||||
// If the object was not in the animation buffer, add it.
|
||||
if (addTransition) {
|
||||
numTransition++;
|
||||
auxMem.writeByte(trdirec + numTransition, (byte) direction);
|
||||
auxMem.writeByte(trscrn + numTransition, currentScrn);
|
||||
auxMem.writeByte(trloc + numTransition, clickedLoc);
|
||||
auxMem.writeByte(NumTrans, (byte) numTransition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent me) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent me) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent me) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent me) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import jace.config.Configuration.ConfigNode;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JCheckBox;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
class BooleanComponent extends JCheckBox implements ActionListener {
|
||||
ConfigNode node;
|
||||
String fieldName;
|
||||
|
||||
public BooleanComponent(ConfigNode node, String fieldName) {
|
||||
this.node = node;
|
||||
this.fieldName = fieldName;
|
||||
synchronizeValue();
|
||||
addActionListener(this);
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Boolean value = Boolean.valueOf(isSelected());
|
||||
node.setFieldValue(fieldName, value);
|
||||
}
|
||||
|
||||
public void synchronizeValue() {
|
||||
try {
|
||||
|
||||
Object value = node.getFieldValue(fieldName);
|
||||
if (value == null) {
|
||||
setSelected(false);
|
||||
} else {
|
||||
setSelected(Boolean.valueOf(String.valueOf(value)).booleanValue());
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.getLogger(BooleanComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import jace.core.Utility;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
|
||||
public class ClassSelection extends DynamicSelection<Class> {
|
||||
|
||||
Class template = null;
|
||||
|
||||
public ClassSelection(Class supertype, Class defaultValue) {
|
||||
super(defaultValue);
|
||||
template = supertype;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<Class, String> getSelections() {
|
||||
LinkedHashMap<Class, String> selections = new LinkedHashMap<Class, String>();
|
||||
List<? extends Class> allClasses = (List<? extends Class>) Utility.findAllSubclasses(template);
|
||||
if (!allClasses.contains(null)) {
|
||||
allClasses.add(null);
|
||||
}
|
||||
List<Entry<Class, String>> values = new ArrayList<Map.Entry<Class, String>>();
|
||||
if (allowNull()) {
|
||||
values.add(new Entry<Class, String>() {
|
||||
|
||||
@Override
|
||||
public Class getKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return "***Empty***";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setValue(String v) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
});
|
||||
}
|
||||
for (final Class c : allClasses) {
|
||||
Entry<Class, String> entry = new Map.Entry<Class, String>() {
|
||||
|
||||
public Class getKey() {
|
||||
return c;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
if (c == null) {
|
||||
return "**Empty**";
|
||||
}
|
||||
if (c.isAnnotationPresent(Name.class)) {
|
||||
return ((Name) c.getAnnotation(Name.class)).value();
|
||||
}
|
||||
return c.getSimpleName();
|
||||
}
|
||||
|
||||
public String setValue(String value) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return super.equals(obj) || obj == getKey() || getKey() != null && getKey().equals(obj);
|
||||
}
|
||||
};
|
||||
values.add(entry);
|
||||
}
|
||||
Collections.sort(values, new Comparator<Map.Entry<? extends Class, String>>() {
|
||||
public int compare(Entry<? extends Class, String> o1, Entry<? extends Class, String> o2) {
|
||||
if (o1.getKey() == null) {
|
||||
return -1;
|
||||
}
|
||||
if (o2.getKey() == null) {
|
||||
return 1;
|
||||
} else {
|
||||
return (o1.getValue().compareTo(o2.getValue()));
|
||||
}
|
||||
}
|
||||
});
|
||||
for (Map.Entry<Class, String> entry : values) {
|
||||
Class key = entry.getKey();
|
||||
selections.put(key, entry.getValue());
|
||||
}
|
||||
return selections;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowNull() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Class value) {
|
||||
Object v = value;
|
||||
if (v != null && v instanceof String) {
|
||||
super.setValueByMatch((String) v);
|
||||
return;
|
||||
}
|
||||
super.setValue(value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A configurable field annotation means that an object property can be changed
|
||||
* by the end-user.
|
||||
* NOTE: Any field that implements this must be public and serializable!
|
||||
* If a field is not serializable, it will result in a serialization error
|
||||
* when the configuration is being saved. There is no way to offer a compiler
|
||||
* warning to avoid this, unfortunately.
|
||||
* One way you can work with this constraint when allowing large reconfiguration
|
||||
* of functionality, such as Cards or other class implementations of hardware,
|
||||
* is to store the class itself of the component as a configuration value
|
||||
* and let the Reconfigure method generate a new instance.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ConfigurableField {
|
||||
public String name();
|
||||
public String shortName() default "";
|
||||
public String defaultValue() default "";
|
||||
public String description() default "";
|
||||
public String category() default "General";
|
||||
}
|
|
@ -0,0 +1,512 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.core.Utility;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.event.TreeModelListener;
|
||||
import javax.swing.tree.TreeModel;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
/**
|
||||
* Manages the configuration state of the emulator components.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Configuration implements Reconfigurable {
|
||||
|
||||
public String getName() {
|
||||
return "Configuration";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "cfg";
|
||||
}
|
||||
|
||||
public void reconfigure() {
|
||||
}
|
||||
|
||||
public static class ConfigTreeModel implements TreeModel {
|
||||
|
||||
public Object getRoot() {
|
||||
return BASE;
|
||||
}
|
||||
|
||||
public Object getChild(Object parent, int index) {
|
||||
if (parent instanceof ConfigNode) {
|
||||
ConfigNode n = (ConfigNode) parent;
|
||||
return n.children.values().toArray()[index];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getChildCount(Object parent) {
|
||||
if (parent instanceof ConfigNode) {
|
||||
ConfigNode n = (ConfigNode) parent;
|
||||
return n.children.size();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLeaf(Object node) {
|
||||
return getChildCount(node) == 0;
|
||||
}
|
||||
|
||||
public void valueForPathChanged(TreePath path, Object newValue) {
|
||||
// Do nothing...
|
||||
}
|
||||
|
||||
public int getIndexOfChild(Object parent, Object child) {
|
||||
if (parent instanceof ConfigNode) {
|
||||
ConfigNode n = (ConfigNode) parent;
|
||||
ConfigNode[] c = (ConfigNode[]) n.children.values().toArray(new ConfigNode[0]);
|
||||
for (int i = 0; i < c.length; i++) {
|
||||
if (c[i].equals(child)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void addTreeModelListener(TreeModelListener l) {
|
||||
// Do nothing...
|
||||
}
|
||||
|
||||
public void removeTreeModelListener(TreeModelListener l) {
|
||||
// Do nothing...
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a serializable configuration node as part of a tree.
|
||||
* The root node should be a single instance (e.g. Computer)
|
||||
* The child nodes should be all object instances that stem from each object
|
||||
* The overall goal of this class is two-fold:
|
||||
* 1) Provide a navigable manner to inspect configuration
|
||||
* 2) Provide a simple persistence mechanism to load/store configuration
|
||||
*/
|
||||
public static class ConfigNode implements Serializable {
|
||||
|
||||
public transient ConfigNode root;
|
||||
public transient ConfigNode parent;
|
||||
public transient Reconfigurable subject;
|
||||
private Map<String, Serializable> settings;
|
||||
protected Map<String, ConfigNode> children;
|
||||
private boolean changed = false;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (subject == null) {
|
||||
return "???";
|
||||
}
|
||||
return (changed ? "<html><i>" : "") + subject.getName();
|
||||
}
|
||||
|
||||
public ConfigNode(Reconfigurable subject) {
|
||||
this(null, subject);
|
||||
this.root = null;
|
||||
}
|
||||
|
||||
public ConfigNode(ConfigNode parent, Reconfigurable subject) {
|
||||
this.subject = subject;
|
||||
this.settings = new TreeMap<String, Serializable>();
|
||||
this.children = new TreeMap<String, ConfigNode>();
|
||||
this.parent = parent;
|
||||
if (this.parent != null) {
|
||||
this.root = this.parent.root != null ? this.parent.root : this.parent;
|
||||
}
|
||||
}
|
||||
|
||||
public void setFieldValue(String field, Serializable value) {
|
||||
if (value != null) {
|
||||
if (value.equals(getFieldValue(field))) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (getFieldValue(field) == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
setRawFieldValue(field, value);
|
||||
}
|
||||
|
||||
public void setRawFieldValue(String field, Serializable value) {
|
||||
settings.put(field, value);
|
||||
}
|
||||
|
||||
public Serializable getFieldValue(String field) {
|
||||
return settings.get(field);
|
||||
}
|
||||
|
||||
public Set<String> getAllSettingNames() {
|
||||
return settings.keySet();
|
||||
}
|
||||
}
|
||||
public final static ConfigNode BASE;
|
||||
public static Computer emulator = Computer.getComputer();
|
||||
@ConfigurableField(name = "Autosave Changes", description = "If unchecked, changes are only saved when the Save button is pressed.")
|
||||
public static boolean saveAutomatically = false;
|
||||
|
||||
static {
|
||||
BASE = new ConfigNode(new Configuration());
|
||||
buildTree();
|
||||
}
|
||||
|
||||
public static void buildTree() {
|
||||
buildTree(BASE, new HashSet());
|
||||
}
|
||||
|
||||
private static void buildTree(ConfigNode node, Set visited) {
|
||||
if (node.subject == null) {
|
||||
return;
|
||||
}
|
||||
for (Field f : node.subject.getClass().getFields()) {
|
||||
// System.out.println("Evaluating field " + f.getName());
|
||||
try {
|
||||
Object o = f.get(node.subject);
|
||||
if (/*o == null ||*/visited.contains(o)) {
|
||||
continue;
|
||||
}
|
||||
// System.out.println(o.getClass().getName());
|
||||
// If the object in question is not reconfigurable,
|
||||
// skip over it and investigate its fields instead
|
||||
// if (o.getClass().isAssignableFrom(Reconfigurable.class)) {
|
||||
// if (Reconfigurable.class.isAssignableFrom(o.getClass())) {
|
||||
if (f.isAnnotationPresent(ConfigurableField.class)) {
|
||||
if (o != null && ISelection.class.isAssignableFrom(o.getClass())) {
|
||||
ISelection selection = (ISelection) o;
|
||||
node.setRawFieldValue(f.getName(), (Serializable) selection.getSelections().get(selection.getValue()));
|
||||
} else {
|
||||
node.setRawFieldValue(f.getName(), (Serializable) o);
|
||||
}
|
||||
continue;
|
||||
} else if (o == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (o instanceof Reconfigurable) {
|
||||
Reconfigurable r = (Reconfigurable) o;
|
||||
visited.add(r);
|
||||
ConfigNode child = node.children.get(f.getName());
|
||||
if (child == null || !child.subject.equals(o)) {
|
||||
child = new ConfigNode(node, r);
|
||||
node.children.put(f.getName(), child);
|
||||
}
|
||||
buildTree(child, visited);
|
||||
} else if (o.getClass().isArray()) {
|
||||
String fieldName = f.getName();
|
||||
Class type = o.getClass().getComponentType();
|
||||
if (!Reconfigurable.class.isAssignableFrom(type)) {
|
||||
continue;
|
||||
}
|
||||
Reconfigurable[] r = (Reconfigurable[]) o;
|
||||
visited.add(r);
|
||||
for (int i = 0; i < r.length; i++) {
|
||||
String childName = fieldName + i;
|
||||
if (r[i] == null) {
|
||||
node.children.remove(childName);
|
||||
continue;
|
||||
}
|
||||
ConfigNode grandchild = node.children.get(childName);
|
||||
if (grandchild == null || !grandchild.subject.equals(r[i])) {
|
||||
grandchild = new ConfigNode(node, r[i]);
|
||||
node.children.put(childName, grandchild);
|
||||
}
|
||||
buildTree(grandchild, visited);
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IllegalAccessException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
name="Save settings",
|
||||
description="Save all configuration settings as defaults",
|
||||
category="general",
|
||||
alternatives="save preferences;save defaults"
|
||||
)
|
||||
public static void saveSettings() {
|
||||
FileOutputStream fos = null;
|
||||
{
|
||||
ObjectOutputStream oos = null;
|
||||
try {
|
||||
applySettings(BASE);
|
||||
oos = new ObjectOutputStream(new FileOutputStream(getSettingsFile()));
|
||||
oos.writeObject(BASE);
|
||||
} catch (FileNotFoundException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
if (oos != null) {
|
||||
oos.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
name="Load settings",
|
||||
description="Load all configuration settings previously saved",
|
||||
category="general",
|
||||
alternatives="load preferences;revert settings;revert preferences"
|
||||
)
|
||||
public static void loadSettings() {
|
||||
{
|
||||
ObjectInputStream ois = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
ois = new ObjectInputStream(new FileInputStream(getSettingsFile()));
|
||||
ConfigNode newRoot = (ConfigNode) ois.readObject();
|
||||
applyConfigTree(newRoot, BASE);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (FileNotFoundException ex) {
|
||||
// Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
// This just means there are no settings to be saved -- just ignore it.
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
if (ois != null) {
|
||||
ois.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetToDefaults() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
|
||||
public static File getSettingsFile() {
|
||||
return new File(System.getProperty("user.dir"), ".jace.conf");
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply settings from node tree to the object model
|
||||
* This also calls "reconfigure" on objects in sequence
|
||||
* @param node
|
||||
* @return True if any settings have changed in the node or any of its descendants
|
||||
*/
|
||||
public static boolean applySettings(ConfigNode node) {
|
||||
boolean resume = false;
|
||||
if (node == BASE) {
|
||||
resume = Computer.pause();
|
||||
}
|
||||
boolean hasChanged = false;
|
||||
if (node.changed) {
|
||||
doApply(node);
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
// Now that the object structure reflects the current configuration,
|
||||
// process reconfiguration from the children, etc.
|
||||
for (ConfigNode child : node.children.values()) {
|
||||
hasChanged |= applySettings(child);
|
||||
}
|
||||
|
||||
if (node.equals(BASE) && hasChanged) {
|
||||
buildTree();
|
||||
}
|
||||
|
||||
if (resume) {
|
||||
Computer.resume();
|
||||
}
|
||||
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
private static void applyConfigTree(ConfigNode newRoot, ConfigNode oldRoot) {
|
||||
if (oldRoot == null || newRoot == null) {
|
||||
return;
|
||||
}
|
||||
oldRoot.settings = newRoot.settings;
|
||||
if (oldRoot.subject != null) {
|
||||
doApply(oldRoot);
|
||||
buildTree(oldRoot, new HashSet());
|
||||
}
|
||||
for (String childName : newRoot.children.keySet()) {
|
||||
// System.out.println("Applying settings for " + childName);
|
||||
applyConfigTree(newRoot.children.get(childName), oldRoot.children.get(childName));
|
||||
}
|
||||
}
|
||||
|
||||
private static void doApply(ConfigNode node) {
|
||||
List<String> removeList = new ArrayList<String>();
|
||||
for (String f : node.settings.keySet()) {
|
||||
try {
|
||||
Field ff = node.subject.getClass().getField(f);
|
||||
// System.out.println("Setting " + f + " to " + node.settings.get(f));
|
||||
Object val = node.settings.get(f);
|
||||
Class valType = (val != null ? val.getClass() : null);
|
||||
Class fieldType = ff.getType();
|
||||
if (ISelection.class.isAssignableFrom(fieldType)) {
|
||||
ISelection selection = (ISelection) ff.get(node.subject);
|
||||
try {
|
||||
selection.setValue(val);
|
||||
} catch (ClassCastException c) {
|
||||
selection.setValueByMatch(String.valueOf(val));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (val == null || valType.equals(fieldType)) {
|
||||
ff.set(node.subject, val);
|
||||
continue;
|
||||
}
|
||||
// System.out.println(fieldType);
|
||||
val = Utility.deserializeString(String.valueOf(val), fieldType, false);
|
||||
// System.out.println("Setting "+node.subject.getName()+" property "+ff.getName()+" with value "+String.valueOf(val));
|
||||
ff.set(node.subject, val);
|
||||
} catch (NoSuchFieldException ex) {
|
||||
System.out.println("Setting "+f+" no longer exists, skipping.");
|
||||
removeList.add(f);
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IllegalAccessException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
for (String f : removeList) {
|
||||
node.settings.remove(f);
|
||||
}
|
||||
|
||||
try {
|
||||
// When settings are applied, this could very well change the object structure
|
||||
// For example, if cards or other pieces of emulation are changed around
|
||||
// System.out.println("Reconfiguring "+node.subject.getName());
|
||||
node.subject.reconfigure();
|
||||
} catch (Exception ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
node.changed = false;
|
||||
}
|
||||
|
||||
public static void applySettings(Map<String, String> settings) {
|
||||
for (Map.Entry<String, String> setting : settings.entrySet()) {
|
||||
Map<String, ConfigNode> shortNames = new HashMap<String, ConfigNode>();
|
||||
buildNodeMap(BASE, shortNames);
|
||||
|
||||
String settingName = setting.getKey();
|
||||
String value = setting.getValue();
|
||||
String[] parts = settingName.split("\\.");
|
||||
if (parts.length != 2) {
|
||||
System.err.println("Unable to parse settting, should be in the form of DEVICE.PROPERTYNAME "+settingName);
|
||||
continue;
|
||||
}
|
||||
String deviceName = parts[0];
|
||||
String fieldName = parts[1];
|
||||
ConfigNode n = shortNames.get(deviceName.toLowerCase());
|
||||
if (n == null) {
|
||||
System.err.println("Unable to find device named "+deviceName+", try one of these: "+Utility.join(shortNames.keySet(), ", "));
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean found = false;
|
||||
List<String> shortFieldNames = new ArrayList<String>();
|
||||
for (String longName : n.getAllSettingNames()) {
|
||||
ConfigurableField f = null;
|
||||
try {
|
||||
f = n.subject.getClass().getField(longName).getAnnotation(ConfigurableField.class);
|
||||
} catch (NoSuchFieldException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
String shortName = (f != null && !f.shortName().equals("")) ? f.shortName() : longName;
|
||||
shortFieldNames.add(shortName);
|
||||
|
||||
if (fieldName.equalsIgnoreCase(longName) || fieldName.equalsIgnoreCase(shortName)) {
|
||||
found = true;
|
||||
n.setFieldValue(longName, value);
|
||||
applySettings(n);
|
||||
n.subject.reconfigure();
|
||||
buildTree();
|
||||
System.out.println("Set property "+n.subject.getName()+"."+longName+" to "+value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
System.err.println("Unable to find property "+fieldName+" for device "+deviceName+". Try one of these :"+Utility.join(shortFieldNames, ", "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildNodeMap(ConfigNode n, Map<String, ConfigNode> shortNames) {
|
||||
shortNames.put(n.subject.getShortName().toLowerCase(), n);
|
||||
for (Map.Entry<String, ConfigNode> c : n.children.entrySet()) {
|
||||
buildNodeMap(c.getValue(), shortNames);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printTree(ConfigNode n, String prefix, int i) {
|
||||
for (String setting : n.getAllSettingNames()) {
|
||||
for (int j=0; j < i; j++) System.out.print(" ");
|
||||
ConfigurableField f = null;
|
||||
try {
|
||||
f = n.subject.getClass().getField(setting).getAnnotation(ConfigurableField.class);
|
||||
} catch (NoSuchFieldException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
String sn = (f != null && !f.shortName().equals("")) ? f.shortName() : setting;
|
||||
System.out.println(prefix+">>"+setting+" ("+n.subject.getShortName()+"."+sn+")");
|
||||
}
|
||||
for (Map.Entry<String, ConfigNode> c : n.children.entrySet()) {
|
||||
printTree(c.getValue(), prefix+"."+c.getKey(), i+1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.3" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[650, 480]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="applyButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="saveButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="revertButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="configTreeScrollPane" min="-2" pref="271" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="settingsPanel" pref="438" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="configTreeScrollPane" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="applyButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="saveButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="revertButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="settingsPanel" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JScrollPane" name="configTreeScrollPane">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTree" name="configTree">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.tree.TreeModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="new Configuration.ConfigTreeModel()" type="code"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="valueChanged" listener="javax.swing.event.TreeSelectionListener" parameters="javax.swing.event.TreeSelectionEvent" handler="configTreeValueChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="settingsPanel">
|
||||
<Properties>
|
||||
<Property name="alignmentX" type="float" value="0.0"/>
|
||||
<Property name="alignmentY" type="float" value="0.0"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[375, 480]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="applyButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Apply"/>
|
||||
<Property name="toolTipText" type="java.lang.String" value="Apply current changes without saving"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="applyButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="saveButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Save"/>
|
||||
<Property name="toolTipText" type="java.lang.String" value="Apply settings and save"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="saveButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="revertButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Revert"/>
|
||||
<Property name="toolTipText" type="java.lang.String" value="Revert all settings to last saved values (hold SHIFT while clicking to revert to defaults)"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="revertButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import jace.apple2e.Apple2e;
|
||||
import jace.config.Configuration.ConfigNode;
|
||||
import java.awt.Component;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.JTree;
|
||||
|
||||
/**
|
||||
* Configuration user interface. Not pretty but it works.
|
||||
* Created on Dec 16, 2010, 3:23:13 PM
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class ConfigurationPanel extends javax.swing.JPanel {
|
||||
|
||||
public static void main(String... args) {
|
||||
new Apple2e();
|
||||
Apple2e.getComputer().reconfigure();
|
||||
Configuration.loadSettings();
|
||||
JFrame f = new JFrame();
|
||||
f.setContentPane(new ConfigurationPanel());
|
||||
f.setSize(f.getContentPane().getPreferredSize());
|
||||
f.validate();
|
||||
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
f.setVisible(true);
|
||||
}
|
||||
|
||||
/** Creates new form ConfigurationPanel */
|
||||
public ConfigurationPanel() {
|
||||
initComponents();
|
||||
expandAll(configTree);
|
||||
}
|
||||
|
||||
/** This method is called from within the constructor to
|
||||
* initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is
|
||||
* always regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
configTreeScrollPane = new javax.swing.JScrollPane();
|
||||
configTree = new javax.swing.JTree();
|
||||
settingsPanel = new javax.swing.JPanel();
|
||||
applyButton = new javax.swing.JButton();
|
||||
saveButton = new javax.swing.JButton();
|
||||
revertButton = new javax.swing.JButton();
|
||||
|
||||
setPreferredSize(new java.awt.Dimension(650, 480));
|
||||
|
||||
configTree.setModel(new Configuration.ConfigTreeModel());
|
||||
configTree.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() {
|
||||
public void valueChanged(javax.swing.event.TreeSelectionEvent evt) {
|
||||
configTreeValueChanged(evt);
|
||||
}
|
||||
});
|
||||
configTreeScrollPane.setViewportView(configTree);
|
||||
|
||||
settingsPanel.setAlignmentX(0.0F);
|
||||
settingsPanel.setAlignmentY(0.0F);
|
||||
settingsPanel.setPreferredSize(new java.awt.Dimension(375, 480));
|
||||
settingsPanel.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
applyButton.setText("Apply");
|
||||
applyButton.setToolTipText("Apply current changes without saving");
|
||||
applyButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
applyButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
saveButton.setText("Save");
|
||||
saveButton.setToolTipText("Apply settings and save");
|
||||
saveButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
saveButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
revertButton.setText("Revert");
|
||||
revertButton.setToolTipText("Revert all settings to last saved values (hold SHIFT while clicking to revert to defaults)");
|
||||
revertButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
revertButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(applyButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(saveButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(revertButton))
|
||||
.addComponent(configTreeScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 271, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(settingsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 438, Short.MAX_VALUE))
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(configTreeScrollPane)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(applyButton)
|
||||
.addComponent(saveButton)
|
||||
.addComponent(revertButton))
|
||||
.addContainerGap())
|
||||
.addComponent(settingsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 0, Short.MAX_VALUE)
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
ConfigNode currentNode = null;
|
||||
private void configTreeValueChanged(javax.swing.event.TreeSelectionEvent evt) {//GEN-FIRST:event_configTreeValueChanged
|
||||
settingsPanel.removeAll();
|
||||
ConfigNode node = (ConfigNode) evt.getPath().getLastPathComponent();
|
||||
if (currentNode == node) return;
|
||||
currentNode = node;
|
||||
if (node != null && node.subject != null && !node.getAllSettingNames().isEmpty()) {
|
||||
GridBagLayout l = (GridBagLayout) settingsPanel.getLayout();
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
int y = 0;
|
||||
for (String s : node.getAllSettingNames()) {
|
||||
try {
|
||||
Field f = node.subject.getClass().getField(s);
|
||||
ConfigurableField annotation = f.getAnnotation(ConfigurableField.class);
|
||||
if (annotation == null) continue;
|
||||
JLabel label = new JLabel(annotation.name());
|
||||
c.anchor = GridBagConstraints.FIRST_LINE_START;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.gridwidth = 1;
|
||||
c.gridy = y++;
|
||||
c.gridx = 0;
|
||||
c.weightx = 0.0;
|
||||
c.insets = new Insets(2, 2, 2, 2);
|
||||
c.ipady = 2;
|
||||
settingsPanel.add(label,c);
|
||||
Component edit = generateEditComponent(node, s);
|
||||
edit.setSize(edit.getPreferredSize());
|
||||
c.gridx = 1;
|
||||
c.weightx = 0.5;
|
||||
c.ipady = 0;
|
||||
c.insets = new Insets(0, 2, 2, 2);
|
||||
c.gridwidth = GridBagConstraints.REMAINDER;
|
||||
settingsPanel.add(edit,c);
|
||||
if (!annotation.description().equals("")) {
|
||||
c.gridy = y++;
|
||||
c.gridx = 0;
|
||||
c.gridwidth = 2;
|
||||
c.weightx = 0.0;
|
||||
JLabel desc = new JLabel("<html><i>" + annotation.description());
|
||||
c.insets = new Insets(1, 15, 7, 5);
|
||||
settingsPanel.add(desc, c);
|
||||
}
|
||||
} catch (NoSuchFieldException ex) {
|
||||
Logger.getLogger(ConfigurationPanel.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(ConfigurationPanel.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settingsPanel.validate();
|
||||
settingsPanel.setVisible(true);
|
||||
settingsPanel.repaint();
|
||||
|
||||
}//GEN-LAST:event_configTreeValueChanged
|
||||
|
||||
private void applyButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_applyButtonActionPerformed
|
||||
Configuration.applySettings(Configuration.BASE);
|
||||
configTree.updateUI();
|
||||
expandAll(configTree);
|
||||
}//GEN-LAST:event_applyButtonActionPerformed
|
||||
|
||||
private void saveButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveButtonActionPerformed
|
||||
Configuration.saveSettings();
|
||||
configTree.updateUI();
|
||||
expandAll(configTree);
|
||||
}//GEN-LAST:event_saveButtonActionPerformed
|
||||
|
||||
private void revertButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_revertButtonActionPerformed
|
||||
if ((evt.getModifiers() & evt.SHIFT_MASK) != 0) {
|
||||
revertDefaultsActionPerformed(evt);
|
||||
return;
|
||||
}
|
||||
Configuration.loadSettings();
|
||||
configTree.updateUI();
|
||||
expandAll(configTree);
|
||||
}//GEN-LAST:event_revertButtonActionPerformed
|
||||
|
||||
private void revertDefaultsActionPerformed(java.awt.event.ActionEvent evt) {
|
||||
if (JOptionPane.showConfirmDialog(null, "Revert all settings to defaults?", "Revert to defaults?", JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION)
|
||||
return;
|
||||
Configuration.resetToDefaults();
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton applyButton;
|
||||
private javax.swing.JTree configTree;
|
||||
private javax.swing.JScrollPane configTreeScrollPane;
|
||||
private javax.swing.JButton revertButton;
|
||||
private javax.swing.JButton saveButton;
|
||||
private javax.swing.JPanel settingsPanel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
public void expandAll(JTree tree) {
|
||||
for (int row = 0; row < tree.getRowCount(); tree.expandRow(row++));
|
||||
}
|
||||
|
||||
private Component generateEditComponent(ConfigNode node, String s) {
|
||||
try {
|
||||
Field f = node.subject.getClass().getField(s);
|
||||
|
||||
if (f.getType().isPrimitive()) {
|
||||
if (f.getType().equals(Boolean.TYPE)) {
|
||||
return new BooleanComponent(node, s);
|
||||
} else if (f.getType().equals(Integer.TYPE)) {
|
||||
return new IntegerComponent(node, s);
|
||||
} else if (f.getType().equals(Short.TYPE)) {
|
||||
return new IntegerComponent(node, s);
|
||||
} else if (f.getType().equals(Byte.TYPE)) {
|
||||
return new IntegerComponent(node, s);
|
||||
} else if (f.getType().equals(Long.TYPE)) {
|
||||
return new IntegerComponent(node, s);
|
||||
} else {
|
||||
return new StringComponent(node, s);
|
||||
}
|
||||
} else if (f.getType().equals(String.class)) {
|
||||
return new StringComponent(node, s);
|
||||
} else if (f.getType().equals(File.class)) {
|
||||
return new FileComponent(node, s);
|
||||
} else if (Class.class.isEnum()) {
|
||||
// TODO: Add enumeration support!
|
||||
} else if (ISelection.class.isAssignableFrom(f.getType())) {
|
||||
return new DynamicSelectComponent(node, s);
|
||||
}
|
||||
return new JTextField();
|
||||
} catch (NoSuchFieldException ex) {
|
||||
Logger.getLogger(ConfigurationPanel.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(ConfigurationPanel.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import jace.config.Configuration.ConfigNode;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.ComboBoxModel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.event.ListDataListener;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
class DynamicSelectComponent extends JComboBox implements ActionListener {
|
||||
|
||||
ConfigNode node;
|
||||
String fieldName;
|
||||
Serializable currentValue;
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
node.setFieldValue(fieldName, currentValue);
|
||||
}
|
||||
|
||||
public void synchronizeValue() {
|
||||
try {
|
||||
Object value = node.getFieldValue(fieldName);
|
||||
if (value == null) {
|
||||
getModel().setSelectedItem(null);
|
||||
setSelectedItem(getModel().getSelectedItem());
|
||||
} else {
|
||||
getModel().setSelectedItem(value);
|
||||
setSelectedItem(getModel().getSelectedItem());
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.getLogger(StringComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public DynamicSelectComponent(ConfigNode node, String fieldName) {
|
||||
try {
|
||||
this.node = node;
|
||||
this.fieldName = fieldName;
|
||||
DynamicSelection sel;
|
||||
try {
|
||||
sel = (DynamicSelection) node.subject.getClass().getField(fieldName).get(node.subject);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.getLogger(DynamicSelectComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
System.err.print("Couldn't get selections for field " + fieldName);
|
||||
return;
|
||||
} catch (IllegalAccessException ex) {
|
||||
Logger.getLogger(DynamicSelectComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
System.err.print("Couldn't get selections for field " + fieldName);
|
||||
return;
|
||||
} catch (NoSuchFieldException ex) {
|
||||
Logger.getLogger(DynamicSelectComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
System.err.print("Couldn't get selections for field " + fieldName);
|
||||
return;
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(DynamicSelectComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
System.err.print("Couldn't get selections for field " + fieldName);
|
||||
return;
|
||||
}
|
||||
currentValue = node.getFieldValue(fieldName);
|
||||
final LinkedHashMap selections = sel.getSelections();
|
||||
|
||||
addActionListener(this);
|
||||
ComboBoxModel m;
|
||||
|
||||
setModel(new ComboBoxModel() {
|
||||
|
||||
Entry value;
|
||||
|
||||
public void setSelectedItem(Object anItem) {
|
||||
if (anItem != null && anItem instanceof Map.Entry) {
|
||||
value = (Entry) anItem;
|
||||
currentValue = (Serializable) ((Entry) anItem).getKey();
|
||||
} else {
|
||||
for (Map.Entry entry : (Set<Map.Entry>) selections.entrySet()) {
|
||||
if (entry.getValue().equals(anItem)) {
|
||||
value = entry;
|
||||
currentValue = (Serializable) entry.getKey();
|
||||
}
|
||||
if (entry.getKey() == null && anItem == null) {
|
||||
value = entry;
|
||||
currentValue = (Serializable) entry.getKey();
|
||||
}
|
||||
if (entry.getKey() != null && entry.equals(anItem)) {
|
||||
value = entry;
|
||||
currentValue = (Serializable) entry.getKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Object getSelectedItem() {
|
||||
return selections.get(currentValue);
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return selections.size();
|
||||
}
|
||||
|
||||
public Object getElementAt(int index) {
|
||||
for (Map.Entry entry : (Set<Map.Entry>) selections.entrySet()) {
|
||||
if (index == 0) {
|
||||
return entry.getValue();
|
||||
}
|
||||
index--;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addListDataListener(ListDataListener l) {
|
||||
}
|
||||
|
||||
public void removeListDataListener(ListDataListener l) {
|
||||
}
|
||||
});
|
||||
synchronizeValue();
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(DynamicSelectComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import jace.core.Utility;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class DynamicSelection<T> implements ISelection<T> {
|
||||
public DynamicSelection(T defaultValue) {
|
||||
setValue(defaultValue);
|
||||
}
|
||||
abstract public boolean allowNull();
|
||||
T currentValue;
|
||||
@Override
|
||||
public T getValue() {
|
||||
if (currentValue != null || !allowNull()) {
|
||||
return currentValue;
|
||||
} else {
|
||||
Iterator<? extends T> i = getSelections().keySet().iterator();
|
||||
return i.next();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(T value) {currentValue = value;}
|
||||
|
||||
public void setValueByMatch(String search) {
|
||||
Map<? extends T, String> selections = getSelections();
|
||||
String match = Utility.findBestMatch(search, selections.values());
|
||||
if (match != null) {
|
||||
for (T key : selections.keySet()) {
|
||||
if (selections.get(key).equals(match)) {
|
||||
setValue(key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setValue(null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import jace.config.Configuration.ConfigNode;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
|
||||
/**
|
||||
* This component provides a text field for the manual input of a file, as well
|
||||
* as an associated 'Browse' button, allowing the user to browse for a file and
|
||||
* have its location show up automatically in the text field.
|
||||
*
|
||||
* --borrowed and modified by Brendan Robert
|
||||
*
|
||||
* @author Eelke Spaak
|
||||
* @see javax.swing.JFileChooser
|
||||
*/
|
||||
class FileComponent extends javax.swing.JPanel implements ActionListener, KeyListener {
|
||||
ConfigNode node;
|
||||
String fieldName;
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
textField.setBackground(Color.WHITE);
|
||||
String value = textField.getText();
|
||||
if (value == null || value.equals("")) {
|
||||
node.setFieldValue(fieldName, null);
|
||||
} else {
|
||||
File f = new File(value);
|
||||
if (f.exists()) {
|
||||
node.setFieldValue(fieldName, f);
|
||||
} else {
|
||||
textField.setBackground(Color.RED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void synchronizeValue() {
|
||||
try {
|
||||
Object value = node.getFieldValue(fieldName);
|
||||
if (value == null) {
|
||||
setText("");
|
||||
} else {
|
||||
setText(String.valueOf(value));
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.getLogger(StringComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private int TEXT_FIELD_WIDTH = 150;
|
||||
|
||||
/** Creates new form JFileField */
|
||||
public FileComponent(ConfigNode node, String fieldName) {
|
||||
this.node = node;
|
||||
this.fieldName = fieldName;
|
||||
// if (".".equals(type.value())) {
|
||||
// fileSelectionMode = JFileChooser.DIRECTORIES_ONLY;
|
||||
// } else {
|
||||
// setFileTypeName(type.value());
|
||||
// setExtensionFilter(type.value());
|
||||
// }
|
||||
initComponents();
|
||||
textField.addActionListener(this);
|
||||
synchronizeValue();
|
||||
}
|
||||
private String extensionFilter;
|
||||
private String fileTypeName;
|
||||
private int fileSelectionMode = JFileChooser.FILES_ONLY;
|
||||
|
||||
/** This method is called from within the constructor to
|
||||
* initialize the form.
|
||||
*/
|
||||
private void initComponents() {
|
||||
textField = new javax.swing.JTextField();
|
||||
browseButton = new javax.swing.JButton();
|
||||
textField.setPreferredSize(new Dimension(150,20));
|
||||
textField.addKeyListener(this);
|
||||
browseButton.setText("...");
|
||||
browseButton.setPreferredSize(new Dimension(25,20));
|
||||
browseButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
browseButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
this.add(textField);
|
||||
this.add(browseButton);
|
||||
|
||||
FlowLayout layout = new FlowLayout();
|
||||
this.setLayout(layout);
|
||||
this.validate();
|
||||
}
|
||||
|
||||
private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {
|
||||
File currentDirectory = new File(".");
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
|
||||
chooser.setFileSelectionMode(fileSelectionMode);
|
||||
|
||||
// Quick-n-dirty implementation of file extension filter since it's new in JDK 1.6
|
||||
if (extensionFilter != null && fileTypeName != null) {
|
||||
FileFilter filter = new FileFilter() {
|
||||
String[] extensions = extensionFilter.toLowerCase().split(",");
|
||||
@Override
|
||||
public boolean accept(File f) {
|
||||
for (int i=0; i < extensions.length; i++) {
|
||||
if (f.getPath().toLowerCase().endsWith(extensions[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return fileTypeName;
|
||||
}
|
||||
};
|
||||
chooser.setFileFilter(filter);
|
||||
}
|
||||
|
||||
try {
|
||||
File f = new File(textField.getText());
|
||||
if (f.exists()) {
|
||||
if (f.isDirectory()) {
|
||||
chooser.setCurrentDirectory(f);
|
||||
} else {
|
||||
chooser.setCurrentDirectory(f.getParentFile());
|
||||
chooser.setSelectedFile(f);
|
||||
}
|
||||
} else {
|
||||
chooser.setCurrentDirectory(currentDirectory);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
int returnVal = chooser.showOpenDialog(this);
|
||||
if (returnVal == JFileChooser.APPROVE_OPTION) {
|
||||
try {
|
||||
File selectedFile = chooser.getSelectedFile();
|
||||
if (selectedFile.getCanonicalPath().startsWith(currentDirectory.getCanonicalPath())) {
|
||||
String use = selectedFile.getCanonicalPath().substring(currentDirectory.getCanonicalPath().length() + 1);
|
||||
textField.setText(use);
|
||||
} else {
|
||||
textField.setText(selectedFile.getPath());
|
||||
}
|
||||
node.setFieldValue(fieldName, selectedFile);
|
||||
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(FileComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the text field.
|
||||
*
|
||||
* @return the value of the text field
|
||||
*/
|
||||
public String getText() {
|
||||
return textField.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the text field.
|
||||
*
|
||||
* @param the value to put in the text field
|
||||
*/
|
||||
public void setText(String text) {
|
||||
textField.setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension filter (a comma-separated string of extensions)
|
||||
* that the JFileChooser should use when browsing for a file.
|
||||
*
|
||||
* @return the extension filter
|
||||
*/
|
||||
public String getExtensionFilter() {
|
||||
return extensionFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the extension filter (a comma-separated string of extensions)
|
||||
* that the JFileChooser should use when browsing for a file.
|
||||
*
|
||||
* @param extensionFilter the extension filter
|
||||
*/
|
||||
public void setExtensionFilter(String extensionFilter) {
|
||||
this.extensionFilter = extensionFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the file types the JFileChooser should be
|
||||
* browsing for.
|
||||
*
|
||||
* @return the file type description
|
||||
*/
|
||||
public String getFileTypeName() {
|
||||
return fileTypeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of the file types the JFileChooser should be
|
||||
* browsing for.
|
||||
*
|
||||
* @param fileTypeName the file type description
|
||||
*/
|
||||
public void setFileTypeName(String fileTypeName) {
|
||||
this.fileTypeName = fileTypeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file selection mode to be used by the JFileChooser.
|
||||
*
|
||||
* @return the type of files to be displayed
|
||||
* @see javax.swing.JFileChooser#getFileSelectionMode()
|
||||
*/
|
||||
public int getFileSelectionMode() {
|
||||
return fileSelectionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file selection mode to be used by the JFileChooser.
|
||||
*
|
||||
* @param fileSelectionMode the type of files to be displayed
|
||||
* @see javax.swing.JFileChooser#setFileSelectionMode(int)
|
||||
*/
|
||||
public void setFileSelectionMode(int fileSelectionMode) {
|
||||
this.fileSelectionMode = fileSelectionMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented to make layout managers align the JFileField on the baseline
|
||||
* of the included text field, rather than on the absolute bottom of the
|
||||
* JPanel.
|
||||
*
|
||||
* @param w
|
||||
* @param h
|
||||
* @return
|
||||
*/
|
||||
// @Override
|
||||
// public int getBaseline(int w, int h) {
|
||||
// return textField.getBaseline(w, h);
|
||||
// }
|
||||
// Variables declaration - do not modify
|
||||
private javax.swing.JButton browseButton;
|
||||
private javax.swing.JTextField textField;
|
||||
|
||||
public void keyTyped(KeyEvent e) {
|
||||
}
|
||||
|
||||
public void keyPressed(KeyEvent e) {
|
||||
}
|
||||
|
||||
public void keyReleased(KeyEvent e) {
|
||||
actionPerformed(null);
|
||||
}
|
||||
// End of variables declaration
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public interface ISelection<T> extends Serializable {
|
||||
|
||||
public LinkedHashMap<? extends T, String> getSelections();
|
||||
|
||||
public T getValue();
|
||||
|
||||
public void setValue(T value);
|
||||
|
||||
public void setValueByMatch(String value);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import jace.config.Configuration.ConfigNode;
|
||||
import java.awt.Color;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class IntegerComponent extends StringComponent {
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
String t = getText();
|
||||
if (t == null || t.equals("")) {
|
||||
try {
|
||||
ConfigurableField f = node.subject.getClass().getField(fieldName).getAnnotation(ConfigurableField.class);
|
||||
t = f.defaultValue();
|
||||
if (t == null || t.equals("")) {
|
||||
t = "0";
|
||||
}
|
||||
// setText(t);
|
||||
} catch (NoSuchFieldException ex) {
|
||||
Logger.getLogger(IntegerComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(IntegerComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
int i = Integer.parseInt(t);
|
||||
node.setFieldValue(fieldName, i);
|
||||
setBackground(Color.white);
|
||||
} catch (NumberFormatException ex) {
|
||||
setBackground(Color.red);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IntegerComponent(ConfigNode node, String fieldName) {
|
||||
super(node, fieldName);
|
||||
setColumns(10);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A invokable action annotation means that an object method can be called by the end-user.
|
||||
* This serves as a hook for keybindings as well as semantic navigation potential.
|
||||
* <br/>
|
||||
* Name should be short, meaningful, and succinct. e.g. "Insert disk"
|
||||
* <br/>
|
||||
* Category can be used to group actions by overall topic, for example an automated table of contents
|
||||
* <br/>
|
||||
* Description is descriptive text which provides additional clarity, e.g.
|
||||
* "This will present you with a file selection dialog to pick a floppy disk image.
|
||||
* Currently, dos-ordered (DSK, DO), Prodos-ordered (PO), and Nibble (NIB) formats are supported.
|
||||
* <br/>
|
||||
* Alternatives should be delimited by semicolons) can provide more powerful search
|
||||
* For "insert disk", alternatives might be "change disk;switch disk" and
|
||||
* reboot might have alternatives as "warm start;cold start;boot;restart".
|
||||
* <hr/>
|
||||
* NOTE: Any method that implements this must be public and take no parameters!
|
||||
* If a method signature is not correct, it will result in a runtime exception
|
||||
* when the action is triggered. There is no way to offer a compiler
|
||||
* warning to avoid this, unfortunately.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface InvokableAction {
|
||||
/*
|
||||
* Should be short and meaningful name for action being invoked, e.g. "Insert disk"
|
||||
*/
|
||||
public String name();
|
||||
/*
|
||||
* Can be used to group actions by overall topic, for example an automated table of contents
|
||||
* To be determined...
|
||||
*/
|
||||
public String category() default "General";
|
||||
/*
|
||||
* More descriptive text which provides additional clarity, e.g.
|
||||
* "This will present you with a file selection dialog to pick a floppy disk image.
|
||||
* Currently, dos-ordered (DSK, DO), Prodos-ordered (PO), and Nibble (NIB) formats are supported."
|
||||
*/
|
||||
public String description() default "";
|
||||
/*
|
||||
* Alternatives should be delimited by semicolons) can provide more powerful search
|
||||
* For "insert disk", alternatives might be "change disk;switch disk" and
|
||||
* reboot might have alternatives as "warm start;cold start;boot;restart".
|
||||
*/
|
||||
public String alternatives() default "";
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Name {
|
||||
public String value();
|
||||
public String description() default "";
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public interface Reconfigurable {
|
||||
public String getName();
|
||||
public String getShortName();
|
||||
public void reconfigure();
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.config;
|
||||
|
||||
import jace.config.Configuration.ConfigNode;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
class StringComponent extends JTextField implements KeyListener {
|
||||
ConfigNode node;
|
||||
String fieldName;
|
||||
|
||||
public void keyTyped(KeyEvent e) {
|
||||
}
|
||||
|
||||
public void keyPressed(KeyEvent e) {
|
||||
}
|
||||
|
||||
public void keyReleased(KeyEvent e) {
|
||||
node.setFieldValue(fieldName, getText());
|
||||
}
|
||||
|
||||
public StringComponent(ConfigNode node, String fieldName) {
|
||||
this.node = node;
|
||||
this.fieldName = fieldName;
|
||||
synchronizeValue();
|
||||
addKeyListener(this);
|
||||
}
|
||||
|
||||
public void synchronizeValue() {
|
||||
try {
|
||||
Object value = node.getFieldValue(fieldName);
|
||||
if (value == null) {
|
||||
setText("");
|
||||
} else {
|
||||
setText(String.valueOf(value));
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.getLogger(StringComponent.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.config.ConfigurableField;
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* CPU is a vague abstraction of a CPU. It is defined as something which can be
|
||||
* debugged or traced. It has a program counter which can be incremented or
|
||||
* change. Most importantly, it is a device which does something on every clock tick.
|
||||
* Subclasses should implement "executeOpcode" rather than override the tick method.
|
||||
* Created on January 4, 2007, 7:27 PM
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class CPU extends Device {
|
||||
|
||||
/**
|
||||
* Creates a new instance of CPU
|
||||
*/
|
||||
public CPU() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "cpu";
|
||||
}
|
||||
private Debugger debugger = null;
|
||||
@ConfigurableField(name = "Enable trace to STDOUT", shortName = "trace")
|
||||
public boolean trace = false;
|
||||
|
||||
public boolean isTraceEnabled() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
public void setTraceEnabled(boolean t) {
|
||||
trace = t;
|
||||
}
|
||||
@ConfigurableField(name = "Trace length", shortName = "traceSize", description = "Number of most recent trace lines to keep for debugging errors. Zero == disabled")
|
||||
public int traceLength = 0;
|
||||
private ArrayList<String> traceLog = new ArrayList<>();
|
||||
|
||||
public boolean isLogEnabled() {
|
||||
return (traceLength > 0);
|
||||
}
|
||||
|
||||
public void log(String line) {
|
||||
if (!isLogEnabled()) {
|
||||
return;
|
||||
}
|
||||
while (traceLog.size() >= traceLength) {
|
||||
traceLog.remove(0);
|
||||
}
|
||||
traceLog.add(line);
|
||||
}
|
||||
|
||||
public void dumpTrace() {
|
||||
Computer.pause();
|
||||
ArrayList<String> newLog = new ArrayList<>();
|
||||
ArrayList<String> log = traceLog;
|
||||
traceLog = newLog;
|
||||
Computer.resume();
|
||||
System.out.println("Most recent " + traceLength + " instructions:");
|
||||
log.stream().forEach((s) -> {
|
||||
System.out.println(s);
|
||||
});
|
||||
traceLog.clear();
|
||||
}
|
||||
|
||||
public void setDebug(Debugger d) {
|
||||
debugger = d;
|
||||
suspend();
|
||||
}
|
||||
|
||||
public void clearDebug() {
|
||||
debugger = null;
|
||||
resume();
|
||||
}
|
||||
//@ConfigurableField(name="Program Counter")
|
||||
public int programCounter = 0;
|
||||
|
||||
public int getProgramCounter() {
|
||||
return programCounter;
|
||||
}
|
||||
|
||||
public void setProgramCounter(int programCounter) {
|
||||
this.programCounter = 0x00FFFF & programCounter;
|
||||
}
|
||||
|
||||
public void incrementProgramCounter(int amount) {
|
||||
this.programCounter += amount;
|
||||
this.programCounter = 0x00FFFF & this.programCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single tick of the main processor clock. Either we're waiting
|
||||
* to execute the next instruction, or the next instruction is ready to go
|
||||
*/
|
||||
@Override
|
||||
public void tick() {
|
||||
try {
|
||||
if (debugger != null) {
|
||||
if (!debugger.isActive() && debugger.hasBreakpoints()) {
|
||||
debugger.getBreakpoints().stream().filter((i) -> (i == getProgramCounter())).forEach((_item) -> {
|
||||
debugger.setActive(true);
|
||||
});
|
||||
}
|
||||
if (debugger.isActive()) {
|
||||
debugger.updateStatus();
|
||||
if (!debugger.takeStep()) {
|
||||
// If the debugger is active and we aren't ready for the next step, sleep and exit
|
||||
// Without the sleep, this would constitute a very rapid-fire loop and would eat
|
||||
// an unnecessary amount of CPU.
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(CPU.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// ignore
|
||||
}
|
||||
executeOpcode();
|
||||
}
|
||||
/*
|
||||
* Execute the current opcode at the current program counter
|
||||
*@return number of cycles to wait until next command can be executed
|
||||
*/
|
||||
|
||||
protected abstract void executeOpcode();
|
||||
|
||||
public abstract void reset();
|
||||
|
||||
public abstract void generateInterrupt();
|
||||
|
||||
abstract public void pushPC();
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.apple2e.SoftSwitches;
|
||||
|
||||
/**
|
||||
* Card is an abstraction of an Apple ][ hardware module which can carry its own
|
||||
* ROM (both CX, a 256-byte ROM which loads into memory depending on what slot
|
||||
* the card is in) and the C8 ROM is a 2K ROM loaded at $C800 when the card is
|
||||
* active.
|
||||
*
|
||||
* This class mostly just stubs out common functionality used by many different
|
||||
* cards and provides a consistent interface for more advanced features like VBL
|
||||
* synchronization.
|
||||
* Created on February 1, 2007, 5:35 PM
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class Card extends Device {
|
||||
|
||||
private PagedMemory cxRom;
|
||||
private PagedMemory c8Rom;
|
||||
private int slot;
|
||||
private RAMListener ioListener;
|
||||
private RAMListener firmwareListener;
|
||||
private RAMListener c8firmwareListener;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Card
|
||||
*/
|
||||
public Card() {
|
||||
cxRom = new PagedMemory(0x0100, PagedMemory.Type.cardFirmware);
|
||||
c8Rom = new PagedMemory(0x0800, PagedMemory.Type.cardFirmware);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "s" + getSlot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getDeviceName() + " (slot " + slot + ")";
|
||||
}
|
||||
|
||||
abstract public void reset();
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
ioListener = new RAMListener(
|
||||
RAMEvent.TYPE.ANY,
|
||||
RAMEvent.SCOPE.RANGE,
|
||||
RAMEvent.VALUE.ANY) {
|
||||
protected void doConfig() {
|
||||
setScopeStart(slot * 16 + 0x00c080);
|
||||
setScopeEnd(slot * 16 + 0x00c08F);
|
||||
}
|
||||
|
||||
protected void doEvent(RAMEvent e) {
|
||||
int address = e.getAddress() & 0x0f;
|
||||
handleIOAccess(address, e.getType(), e.getNewValue(), e);
|
||||
}
|
||||
};
|
||||
|
||||
firmwareListener = new RAMListener(
|
||||
RAMEvent.TYPE.ANY,
|
||||
RAMEvent.SCOPE.RANGE,
|
||||
RAMEvent.VALUE.ANY) {
|
||||
protected void doConfig() {
|
||||
setScopeStart(slot * 256 + 0x00c000);
|
||||
setScopeEnd(slot * 256 + 0x00c0ff);
|
||||
}
|
||||
|
||||
protected void doEvent(RAMEvent e) {
|
||||
Computer.getComputer().getMemory().setActiveCard(slot);
|
||||
if (SoftSwitches.CXROM.getState()) {
|
||||
return;
|
||||
}
|
||||
handleFirmwareAccess(e.getAddress() & 0x0ff, e.getType(), e.getNewValue(), e);
|
||||
}
|
||||
};
|
||||
|
||||
c8firmwareListener = new RAMListener(
|
||||
RAMEvent.TYPE.ANY,
|
||||
RAMEvent.SCOPE.RANGE,
|
||||
RAMEvent.VALUE.ANY) {
|
||||
protected void doConfig() {
|
||||
setScopeStart(slot * 256 + 0x00c800);
|
||||
setScopeEnd(slot * 256 + 0x00cfff);
|
||||
}
|
||||
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (SoftSwitches.CXROM.getState()
|
||||
|| Computer.getComputer().getMemory().getActiveSlot() != getSlot()
|
||||
|| SoftSwitches.INTC8ROM.getState()) {
|
||||
return;
|
||||
}
|
||||
handleC8FirmwareAccess(e.getAddress() - 0x0c800, e.getType(), e.getNewValue(), e);
|
||||
}
|
||||
};
|
||||
|
||||
Computer.getComputer().getMemory().addListener(ioListener);
|
||||
Computer.getComputer().getMemory().addListener(firmwareListener);
|
||||
Computer.getComputer().getMemory().addListener(c8firmwareListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
suspend();
|
||||
Computer.getComputer().getMemory().removeListener(ioListener);
|
||||
Computer.getComputer().getMemory().removeListener(firmwareListener);
|
||||
Computer.getComputer().getMemory().removeListener(c8firmwareListener);
|
||||
}
|
||||
|
||||
abstract protected void handleIOAccess(int register, RAMEvent.TYPE type, int value, RAMEvent e);
|
||||
|
||||
abstract protected void handleFirmwareAccess(int register, RAMEvent.TYPE type, int value, RAMEvent e);
|
||||
|
||||
abstract protected void handleC8FirmwareAccess(int register, RAMEvent.TYPE type, int value, RAMEvent e);
|
||||
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
|
||||
public void setSlot(int slot) {
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
public PagedMemory getCxRom() {
|
||||
return cxRom;
|
||||
}
|
||||
|
||||
public PagedMemory getC8Rom() {
|
||||
return c8Rom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
boolean restart = suspend();
|
||||
detach();
|
||||
if (restart) {
|
||||
resume();
|
||||
}
|
||||
attach();
|
||||
}
|
||||
|
||||
public void notifyVBLStateChanged(boolean state) {
|
||||
// Do nothing unless overridden
|
||||
}
|
||||
|
||||
public boolean suspendWithCPU() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.InvokableAction;
|
||||
import jace.config.Reconfigurable;
|
||||
import jace.library.MediaLibrary;
|
||||
import jace.state.StateManager;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This is a very generic stub of a Computer and provides a generic set of
|
||||
* overall functionality, namely boot, pause and resume features. What sort of
|
||||
* memory, video and cpu get used are totally determined by fully-baked
|
||||
* subclasses.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class Computer implements Reconfigurable {
|
||||
|
||||
static private Computer theComputer;
|
||||
public RAM memory;
|
||||
public CPU cpu;
|
||||
public Video video;
|
||||
public Keyboard keyboard;
|
||||
public StateManager stateManager;
|
||||
public MediaLibrary mediaLibrary = MediaLibrary.getInstance();
|
||||
@ConfigurableField(category = "advanced", name = "State management", shortName = "rewind", description = "This enables rewind support, but consumes a lot of memory when active.")
|
||||
public boolean enableStateManager;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Computer
|
||||
*/
|
||||
public Computer() {
|
||||
theComputer = this;
|
||||
keyboard = new Keyboard();
|
||||
}
|
||||
|
||||
public RAM getMemory() {
|
||||
return memory;
|
||||
}
|
||||
|
||||
public void notifyVBLStateChanged(boolean state) {
|
||||
for (Card c : getMemory().cards) {
|
||||
if (c == null) {
|
||||
continue;
|
||||
}
|
||||
c.notifyVBLStateChanged(state);
|
||||
}
|
||||
if (state && stateManager != null) {
|
||||
stateManager.notifyVBLActive();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMemory(RAM memory) {
|
||||
if (this.memory != memory) {
|
||||
if (this.memory != null) {
|
||||
this.memory.detach();
|
||||
}
|
||||
memory.attach();
|
||||
}
|
||||
this.memory = memory;
|
||||
}
|
||||
|
||||
public void waitForNextCycle() {
|
||||
//@TODO IMPLEMENT TIMER SLEEP CODE!
|
||||
}
|
||||
|
||||
public Video getVideo() {
|
||||
return video;
|
||||
}
|
||||
|
||||
public void setVideo(Video video) {
|
||||
this.video = video;
|
||||
}
|
||||
|
||||
public CPU getCpu() {
|
||||
return cpu;
|
||||
}
|
||||
|
||||
public void setCpu(CPU cpu) {
|
||||
this.cpu = cpu;
|
||||
}
|
||||
|
||||
public void loadRom(String path) throws IOException {
|
||||
memory.loadRom(path);
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
name = "Cold boot",
|
||||
description = "Process startup sequence from power-up",
|
||||
category = "general",
|
||||
alternatives = "Full reset;reset emulator")
|
||||
public abstract void coldStart();
|
||||
|
||||
@InvokableAction(
|
||||
name = "Warm boot",
|
||||
description = "Process user-initatiated reboot (ctrl+apple+reset)",
|
||||
category = "general",
|
||||
alternatives = "reboot;reset;three-finger-salute")
|
||||
public abstract void warmStart();
|
||||
|
||||
static public Computer getComputer() {
|
||||
return theComputer;
|
||||
}
|
||||
|
||||
public Keyboard getKeyboard() {
|
||||
return this.keyboard;
|
||||
}
|
||||
|
||||
protected abstract boolean isRunning();
|
||||
|
||||
protected abstract void doPause();
|
||||
|
||||
protected abstract void doResume();
|
||||
|
||||
@InvokableAction(name = "Pause", description = "Stops the computer, allowing reconfiguration of core elements", alternatives = "freeze;halt")
|
||||
public static boolean pause() {
|
||||
boolean result = false;
|
||||
if (theComputer != null) {
|
||||
result = theComputer.isRunning();
|
||||
theComputer.doPause();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@InvokableAction(name = "Resume", description = "Resumes the computer if it was previously paused", alternatives = "unpause;unfreeze;resume")
|
||||
public static void resume() {
|
||||
if (theComputer != null) {
|
||||
theComputer.doResume();
|
||||
}
|
||||
}
|
||||
|
||||
public void reconfigure() {
|
||||
if (enableStateManager) {
|
||||
stateManager = StateManager.getInstance();
|
||||
} else {
|
||||
stateManager = null;
|
||||
StateManager.getInstance().invalidate();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A debugger has the ability to track a list of breakpoints and step a CPU one
|
||||
* instruction at a time. This is a very generic abstraction used to describe
|
||||
* the basic contract of what a debugger should do. EmulatorUILogic creates an
|
||||
* anonymous subclass that hooks into the actual emulator.
|
||||
* Created on April 16, 2007, 10:37 PM
|
||||
*
|
||||
* @author Administrator
|
||||
*/
|
||||
public abstract class Debugger {
|
||||
|
||||
public abstract void updateStatus();
|
||||
private boolean active = false;
|
||||
public boolean step = false;
|
||||
|
||||
public void setActive(boolean state) {
|
||||
active = state;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
private List<Integer> breakpoints = new ArrayList<Integer>();
|
||||
|
||||
public List<Integer> getBreakpoints() {
|
||||
return breakpoints;
|
||||
}
|
||||
private boolean hasBreakpoints = false;
|
||||
|
||||
boolean hasBreakpoints() {
|
||||
return hasBreakpoints;
|
||||
}
|
||||
|
||||
public void updateBreakpoints() {
|
||||
hasBreakpoints = false;
|
||||
for (Integer i : breakpoints) {
|
||||
if (i != null) {
|
||||
hasBreakpoints = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean takeStep() {
|
||||
if (step) {
|
||||
step = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.state.Stateful;
|
||||
import jace.config.Reconfigurable;
|
||||
|
||||
/**
|
||||
* Device is a very simple abstraction of any emulation component. A device
|
||||
* performs some sort of action on every clock tick, unless it is waiting for a
|
||||
* number of cycles to elapse (waitCycles > 0). A device might also be paused or
|
||||
* suspended.
|
||||
*
|
||||
* Depending on the type of device, some special work might be required to
|
||||
* attach or detach it to the active emulation (such as what should happen when
|
||||
* a card is inserted or removed from a slot?)
|
||||
* Created on May 10, 2007, 5:46 PM
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
public abstract class Device implements Reconfigurable {
|
||||
// Number of cycles to do nothing (for cpu/video cycle accuracy)
|
||||
|
||||
@Stateful
|
||||
private int waitCycles = 0;
|
||||
@Stateful
|
||||
private boolean run = true;
|
||||
@Stateful
|
||||
public boolean isPaused = false;
|
||||
|
||||
public void addWaitCycles(int wait) {
|
||||
waitCycles += wait;
|
||||
}
|
||||
|
||||
public void setWaitCycles(int wait) {
|
||||
waitCycles = wait;
|
||||
}
|
||||
|
||||
public void doTick() {
|
||||
/*
|
||||
if (waitCycles <= 0)
|
||||
tick();
|
||||
else
|
||||
waitCycles--;
|
||||
*/
|
||||
|
||||
if (!run) {
|
||||
// System.out.println("Device stopped: " + getName());
|
||||
isPaused = true;
|
||||
return;
|
||||
}
|
||||
// The following might be as much as 7% faster than the above
|
||||
// My guess is that the above results in a GOTO
|
||||
// whereas the following pre-emptive return avoids that
|
||||
if (waitCycles > 0) {
|
||||
waitCycles--;
|
||||
return;
|
||||
}
|
||||
// Implicit else...
|
||||
tick();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return run;
|
||||
}
|
||||
|
||||
public synchronized void setRun(boolean run) {
|
||||
// System.out.println(Thread.currentThread().getName() + (run ? " resuming " : " suspending ")+ getDeviceName());
|
||||
isPaused = false;
|
||||
this.run = run;
|
||||
}
|
||||
|
||||
protected abstract String getDeviceName();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getDeviceName();
|
||||
}
|
||||
|
||||
public abstract void tick();
|
||||
|
||||
public boolean suspend() {
|
||||
if (isRunning()) {
|
||||
setRun(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
setRun(true);
|
||||
}
|
||||
|
||||
public abstract void attach();
|
||||
|
||||
public abstract void detach();
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
/**
|
||||
* Represents the Apple ][ character font used in text modes.
|
||||
* Created on January 16, 2007, 8:16 PM
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Font {
|
||||
static public int[][] font;
|
||||
static public int getByte(int c, int yOffset) {
|
||||
return font[c][yOffset];
|
||||
}
|
||||
static {
|
||||
font = new int[256][8];
|
||||
try {
|
||||
InputStream in = ClassLoader.getSystemResourceAsStream("jace/data/font.gif");
|
||||
BufferedImage fontImage = ImageIO.read(in);
|
||||
for (int i=0; i < 256; i++) {
|
||||
int x = (i >> 4)*13 + 2;
|
||||
int y = (i & 15)*13 + 4;
|
||||
for (int j=0; j < 8; j++) {
|
||||
int row = 0;
|
||||
for (int k=0; k < 7; k++) {
|
||||
int color = fontImage.getRGB((7-k)+x, j+y);
|
||||
row = (row<<1) | (1-(color&1));
|
||||
// row = (row<<1) | (color&1);
|
||||
}
|
||||
font[i][j]=row;
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Creates a new instance of Font */
|
||||
private Font() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
/**
|
||||
* Listen for a specific key or set of keys
|
||||
* If there is a match, the handleKeyUp or handleKeyDown methods will be called.
|
||||
* This is meant to save a lot of extra conditional logic elsewhere.
|
||||
*
|
||||
* The handler methods should return true if they have consumed the key event and do
|
||||
* not want any other processing to continue for that keypress.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class KeyHandler {
|
||||
public int key = 0;
|
||||
public int modifiers = 0;
|
||||
public KeyHandler(int key, int... modifiers) {
|
||||
this.key = key;
|
||||
this.modifiers = 0;
|
||||
for (int m : modifiers) {
|
||||
this.modifiers |= m;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract boolean handleKeyUp(KeyEvent e);
|
||||
public abstract boolean handleKeyDown(KeyEvent e);
|
||||
}
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.EmulatorUILogic;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.apple2e.Speaker;
|
||||
import jace.apple2e.softswitch.KeyboardSoftSwitch;
|
||||
import jace.config.Reconfigurable;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Keyboard manages all keyboard-related activities. For now, all hotkeys are
|
||||
* hard-coded. The eventual direction for this class is to only manage key
|
||||
* handlers for all keys and provide remapping -- but it's not there yet.
|
||||
* Created on March 29, 2007, 11:32 PM
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Keyboard implements Reconfigurable {
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "kbd";
|
||||
}
|
||||
static byte currentKey = 0;
|
||||
|
||||
public static void clearStrobe() {
|
||||
currentKey = (byte) (currentKey & 0x07f);
|
||||
}
|
||||
|
||||
public static void pressKey(byte key) {
|
||||
currentKey = (byte) (0x0ff & (0x080 | key));
|
||||
}
|
||||
|
||||
public static byte readState() {
|
||||
// If strobe was cleared...
|
||||
if ((currentKey & 0x080) == 0) {
|
||||
// Call clipboard buffer paste routine
|
||||
int newKey = Keyboard.getClipboardKeystroke();
|
||||
if (newKey >= 0) {
|
||||
pressKey((byte) newKey);
|
||||
}
|
||||
}
|
||||
return currentKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of Keyboard
|
||||
*/
|
||||
public Keyboard() {
|
||||
}
|
||||
private static Map<Integer, Set<KeyHandler>> keyHandlersByKey = new HashMap<Integer, Set<KeyHandler>>();
|
||||
private static Map<Object, Set<KeyHandler>> keyHandlersByOwner = new HashMap<Object, Set<KeyHandler>>();
|
||||
|
||||
public static void registerKeyHandler(KeyHandler l, Object owner) {
|
||||
if (!keyHandlersByKey.containsKey(l.key)) {
|
||||
keyHandlersByKey.put(l.key, new HashSet<KeyHandler>());
|
||||
}
|
||||
keyHandlersByKey.get(l.key).add(l);
|
||||
if (!keyHandlersByOwner.containsKey(owner)) {
|
||||
keyHandlersByOwner.put(owner, new HashSet<KeyHandler>());
|
||||
}
|
||||
keyHandlersByOwner.get(owner).add(l);
|
||||
}
|
||||
|
||||
public static void unregisterAllHandlers(Object owner) {
|
||||
if (!keyHandlersByOwner.containsKey(owner)) {
|
||||
return;
|
||||
}
|
||||
for (KeyHandler handler : keyHandlersByOwner.get(owner)) {
|
||||
if (!keyHandlersByKey.containsKey(handler.key)) {
|
||||
continue;
|
||||
}
|
||||
keyHandlersByKey.get(handler.key).remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public static void processKeyDownEvents(KeyEvent e) {
|
||||
if (keyHandlersByKey.containsKey(e.getKeyCode())) {
|
||||
for (KeyHandler h : keyHandlersByKey.get(e.getKeyCode())) {
|
||||
if (h.modifiers != e.getModifiers() && h.modifiers != -1) {
|
||||
continue;
|
||||
}
|
||||
boolean isHandled = h.handleKeyDown(e);
|
||||
if (isHandled) {
|
||||
e.consume();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void processKeyUpEvents(KeyEvent e) {
|
||||
if (keyHandlersByKey.containsKey(e.getKeyCode())) {
|
||||
for (KeyHandler h : keyHandlersByKey.get(e.getKeyCode())) {
|
||||
if (h.modifiers != e.getModifiers() && h.modifiers != -1) {
|
||||
continue;
|
||||
}
|
||||
boolean isHandled = h.handleKeyUp(e);
|
||||
if (isHandled) {
|
||||
e.consume();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public KeyListener getListener() {
|
||||
return new KeyListener() {
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
processKeyDownEvents(e);
|
||||
if (e.getKeyCode() == 0 || e.isConsumed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeyboardSoftSwitch key =
|
||||
(KeyboardSoftSwitch) SoftSwitches.KEYBOARD.getSwitch();
|
||||
char c = e.getKeyChar();
|
||||
if ((e.getModifiers() & (KeyEvent.ALT_MASK|KeyEvent.META_MASK|KeyEvent.META_DOWN_MASK)) > 0) {
|
||||
// explicit left and right here because other locations
|
||||
// can be sent as well, e.g. KEY_LOCATION_STANDARD
|
||||
if (e.getKeyLocation() == KeyEvent.KEY_LOCATION_LEFT) {
|
||||
pressOpenApple();
|
||||
} else if (e.getKeyLocation() == KeyEvent.KEY_LOCATION_RIGHT) {
|
||||
pressSolidApple();
|
||||
}
|
||||
}
|
||||
|
||||
int code = e.getKeyCode();
|
||||
|
||||
switch (code) {
|
||||
case KeyEvent.VK_LEFT:
|
||||
case KeyEvent.VK_KP_LEFT:
|
||||
c = 8;
|
||||
break;
|
||||
case KeyEvent.VK_RIGHT:
|
||||
case KeyEvent.VK_KP_RIGHT:
|
||||
c = 21;
|
||||
break;
|
||||
case KeyEvent.VK_UP:
|
||||
case KeyEvent.VK_KP_UP:
|
||||
c = 11;
|
||||
break;
|
||||
case KeyEvent.VK_DOWN:
|
||||
case KeyEvent.VK_KP_DOWN:
|
||||
c = 10;
|
||||
break;
|
||||
case KeyEvent.VK_TAB:
|
||||
c = 9;
|
||||
break;
|
||||
case KeyEvent.VK_ENTER:
|
||||
c = 13;
|
||||
break;
|
||||
case KeyEvent.VK_BACK_SPACE:
|
||||
c = 127;
|
||||
break;
|
||||
default:
|
||||
if ((e.getModifiers() & KeyEvent.CTRL_DOWN_MASK) > 0) {
|
||||
c = (char) (code - 'A' + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (c < 128) {
|
||||
pressKey((byte) c);
|
||||
}
|
||||
|
||||
// e.consume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
int code = e.getKeyCode();
|
||||
processKeyUpEvents(e);
|
||||
if (code == 0 || e.isConsumed()) {
|
||||
return;
|
||||
}
|
||||
if (code == KeyEvent.VK_INSERT && e.isShiftDown()) {
|
||||
doPaste();
|
||||
}
|
||||
if (code == KeyEvent.VK_F10) {
|
||||
EmulatorUILogic.toggleDebugPanel();
|
||||
}
|
||||
if ((code == KeyEvent.VK_F12 || code == KeyEvent.VK_PAGE_UP || code == KeyEvent.VK_BACK_SPACE || code == KeyEvent.VK_PAUSE) && ((e.getModifiers() & KeyEvent.CTRL_MASK) > 0)) {
|
||||
Computer.getComputer().warmStart();
|
||||
}
|
||||
if (code == KeyEvent.VK_F1) {
|
||||
EmulatorUILogic.showMediaManager();
|
||||
}
|
||||
if (code == KeyEvent.VK_F4) {
|
||||
EmulatorUILogic.showConfig();
|
||||
}
|
||||
if (code == KeyEvent.VK_F7) {
|
||||
Speaker.toggleFileOutput();
|
||||
}
|
||||
if (code == KeyEvent.VK_F8) {
|
||||
EmulatorUILogic.scaleIntegerRatio();
|
||||
}
|
||||
if (code == KeyEvent.VK_F9) {
|
||||
EmulatorUILogic.toggleFullscreen();
|
||||
}
|
||||
if (code == KeyEvent.VK_PRINTSCREEN || code == KeyEvent.VK_SCROLL_LOCK) {
|
||||
try {
|
||||
if (e.isShiftDown()) {
|
||||
EmulatorUILogic.saveScreenshotRaw();
|
||||
} else {
|
||||
EmulatorUILogic.saveScreenshot();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
Computer.resume();
|
||||
}
|
||||
if ((e.getModifiers() & (KeyEvent.ALT_MASK|KeyEvent.META_MASK|KeyEvent.META_DOWN_MASK)) > 0) {
|
||||
// explicit left and right here because other locations
|
||||
// can be sent as well, e.g. KEY_LOCATION_STANDARD
|
||||
if (e.getKeyLocation() == KeyEvent.KEY_LOCATION_LEFT) {
|
||||
releaseOpenApple();
|
||||
} else if (e.getKeyLocation() == KeyEvent.KEY_LOCATION_RIGHT) {
|
||||
releaseSolidApple();
|
||||
}
|
||||
}
|
||||
|
||||
e.consume();
|
||||
// e.setKeyChar((char) 0);
|
||||
// e.setKeyCode(0);
|
||||
}
|
||||
|
||||
private void pressOpenApple() {
|
||||
Computer.pause();
|
||||
SoftSwitches.PB0.getSwitch().setState(true);
|
||||
Computer.resume();
|
||||
}
|
||||
|
||||
private void pressSolidApple() {
|
||||
Computer.pause();
|
||||
SoftSwitches.PB1.getSwitch().setState(true);
|
||||
Computer.resume();
|
||||
}
|
||||
|
||||
private void releaseOpenApple() {
|
||||
Computer.pause();
|
||||
SoftSwitches.PB0.getSwitch().setState(false);
|
||||
Computer.resume();
|
||||
}
|
||||
|
||||
private void releaseSolidApple() {
|
||||
Computer.pause();
|
||||
SoftSwitches.PB1.getSwitch().setState(false);
|
||||
Computer.resume();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void doPaste(String text) {
|
||||
pasteBuffer = new StringReader(text);
|
||||
}
|
||||
|
||||
private static void doPaste() {
|
||||
try {
|
||||
Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
String contents = (String) clip.getData(DataFlavor.stringFlavor);
|
||||
if (contents != null && !"".equals(contents)) {
|
||||
contents = contents.replaceAll("\\n(\\r)?", (char) 0x0d + "");
|
||||
pasteBuffer = new StringReader(contents);
|
||||
}
|
||||
} catch (UnsupportedFlavorException ex) {
|
||||
Logger.getLogger(Keyboard.class
|
||||
.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Keyboard.class
|
||||
.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
}
|
||||
static StringReader pasteBuffer = null;
|
||||
|
||||
public static int getClipboardKeystroke() {
|
||||
if (pasteBuffer == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
int keypress = pasteBuffer.read();
|
||||
// Handle end of paste buffer
|
||||
if (keypress == -1) {
|
||||
pasteBuffer.close();
|
||||
pasteBuffer = null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
KeyboardSoftSwitch key =
|
||||
(KeyboardSoftSwitch) SoftSwitches.KEYBOARD.getSwitch();
|
||||
return (keypress & 0x0ff);
|
||||
|
||||
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Keyboard.class
|
||||
.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Keyboard";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.apple2e.Speaker;
|
||||
import jace.config.ConfigurableField;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Motherboard is the heart of the computer. It can have a list of cards
|
||||
* inserted (the behavior and number of cards is determined by the Memory class)
|
||||
* as well as a speaker and any other miscellaneous devices (e.g. joysticks).
|
||||
* This class provides the real main loop of the emulator, and is responsible
|
||||
* for all timing as well as the pause/resume features used to prevent resource
|
||||
* collisions between threads. Created on May 1, 2007, 11:22 PM
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Motherboard extends TimedDevice {
|
||||
|
||||
static final Computer computer = Computer.getComputer();
|
||||
static final CPU cpu = computer.getCpu();
|
||||
static Motherboard instance;
|
||||
static final public Set<Device> miscDevices = new HashSet<Device>();
|
||||
@ConfigurableField(name = "Enable Speaker", shortName = "speaker", defaultValue = "true")
|
||||
public static boolean enableSpeaker = true;
|
||||
public static Speaker speaker;
|
||||
public static SoundMixer mixer = new SoundMixer();
|
||||
|
||||
static void vblankEnd() {
|
||||
SoftSwitches.VBL.getSwitch().setState(true);
|
||||
computer.notifyVBLStateChanged(true);
|
||||
}
|
||||
|
||||
static void vblankStart() {
|
||||
SoftSwitches.VBL.getSwitch().setState(false);
|
||||
computer.notifyVBLStateChanged(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of Motherboard
|
||||
*/
|
||||
public Motherboard() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
protected String getDeviceName() {
|
||||
return "Motherboard";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "mb";
|
||||
}
|
||||
@ConfigurableField(category = "advanced", name = "CPU per clock", defaultValue = "1", description = "Number of CPU cycles per clock cycle (normal = 1)")
|
||||
public static int cpuPerClock = 1;
|
||||
public int clockCounter = 1;
|
||||
public Card[] cards;
|
||||
|
||||
public void tick() {
|
||||
try {
|
||||
clockCounter--;
|
||||
cpu.doTick();
|
||||
if (clockCounter > 0) {
|
||||
return;
|
||||
}
|
||||
clockCounter = cpuPerClock;
|
||||
Computer.getComputer().getVideo().doTick();
|
||||
// Unrolled loop since this happens so often
|
||||
if (cards[0] != null) {
|
||||
cards[0].doTick();
|
||||
}
|
||||
if (cards[1] != null) {
|
||||
cards[1].doTick();
|
||||
}
|
||||
if (cards[2] != null) {
|
||||
cards[2].doTick();
|
||||
}
|
||||
if (cards[3] != null) {
|
||||
cards[3].doTick();
|
||||
}
|
||||
if (cards[4] != null) {
|
||||
cards[4].doTick();
|
||||
}
|
||||
if (cards[5] != null) {
|
||||
cards[5].doTick();
|
||||
}
|
||||
if (cards[6] != null) {
|
||||
cards[6].doTick();
|
||||
}
|
||||
for (Device m : miscDevices) {
|
||||
m.doTick();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
// From the holy word of Sather 3:5 (Table 3.1) :-)
|
||||
// This average speed averages in the "long" cycles
|
||||
public static long SPEED = 1020484L; // (NTSC)
|
||||
//public static long SPEED = 1015625L; // (PAL)
|
||||
|
||||
public long defaultCyclesPerSecond() {
|
||||
return SPEED;
|
||||
}
|
||||
|
||||
public synchronized void reconfigure() {
|
||||
boolean startAgain = pause();
|
||||
accelorationRequestors.clear();
|
||||
super.reconfigure();
|
||||
Card[] cards = computer.getMemory().getAllCards();
|
||||
// Now create devices as needed, e.g. sound
|
||||
Motherboard.miscDevices.add(mixer);
|
||||
mixer.reconfigure();
|
||||
|
||||
if (enableSpeaker) {
|
||||
try {
|
||||
if (speaker == null) {
|
||||
speaker = new Speaker();
|
||||
} else {
|
||||
speaker.attach();
|
||||
}
|
||||
if (mixer.lineAvailable) {
|
||||
Motherboard.miscDevices.add(speaker);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.out.println("Unable to initalize sound -- deactivating speaker out");
|
||||
speaker.detach();
|
||||
Motherboard.miscDevices.remove(speaker);
|
||||
}
|
||||
} else {
|
||||
if (speaker != null) {
|
||||
speaker.detach();
|
||||
Motherboard.miscDevices.remove(speaker);
|
||||
}
|
||||
}
|
||||
if (startAgain && Computer.getComputer().getMemory() != null) {
|
||||
resume();
|
||||
}
|
||||
}
|
||||
static HashSet<Object> accelorationRequestors = new HashSet<Object>();
|
||||
|
||||
static public void requestSpeed(Object requester) {
|
||||
accelorationRequestors.add(requester);
|
||||
if (instance != null) {
|
||||
instance.enableTempMaxSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
static public void cancelSpeedRequest(Object requester) {
|
||||
accelorationRequestors.remove(requester);
|
||||
if (instance != null && accelorationRequestors.isEmpty()) {
|
||||
instance.disableTempMaxSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
}
|
||||
Map<Card, Boolean> resume = new HashMap<Card, Boolean>();
|
||||
|
||||
@Override
|
||||
public boolean suspend() {
|
||||
synchronized (resume) {
|
||||
resume.clear();
|
||||
for (Card c : cards) {
|
||||
if (c == null || !c.suspendWithCPU() || !c.isRunning()) {
|
||||
continue;
|
||||
}
|
||||
resume.put(c, c.suspend());
|
||||
}
|
||||
}
|
||||
return super.suspend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
cards = computer.getMemory().getAllCards();
|
||||
super.resume();
|
||||
synchronized (resume) {
|
||||
for (Card c : cards) {
|
||||
if (Boolean.TRUE.equals(resume.get(c))) {
|
||||
c.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
for (Device d : miscDevices) {
|
||||
d.suspend();
|
||||
}
|
||||
miscDevices.clear();
|
||||
// halt();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.state.StateManager;
|
||||
import jace.state.Stateful;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This represents bank-switchable ram which can reside at fixed portions of the
|
||||
* computer's memory. This makes it possible to switch out memory pages in a
|
||||
* very efficient manner so that the MMU abstraction doesn't bury the rest of
|
||||
* the emulator in messy conditionals.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
public class PagedMemory {
|
||||
|
||||
public enum Type {
|
||||
|
||||
cardFirmware(0x0c800),
|
||||
languageCard(0x0d000),
|
||||
firmwareMain(0x0d000),
|
||||
firmware80column(0x0c300),
|
||||
slotRom(0x0c100),
|
||||
ram(0x0000);
|
||||
protected int baseAddress;
|
||||
|
||||
private Type(int newBase) {
|
||||
baseAddress = newBase;
|
||||
}
|
||||
|
||||
public int getBaseAddress() {
|
||||
return baseAddress;
|
||||
}
|
||||
}
|
||||
// This is a fixed array, used for internal-only!!
|
||||
@Stateful
|
||||
public byte[][] internalMemory = new byte[0][];
|
||||
@Stateful
|
||||
public Type type;
|
||||
|
||||
/**
|
||||
* Creates a new instance of PagedMemory
|
||||
*/
|
||||
public PagedMemory(int size, Type memType) {
|
||||
type = memType;
|
||||
internalMemory = new byte[size >> 8][256];
|
||||
for (int i = 0; i < size; i += 256) {
|
||||
byte[] b = new byte[256];
|
||||
Arrays.fill(b, (byte) 0x00);
|
||||
internalMemory[i >> 8] = b;
|
||||
}
|
||||
}
|
||||
|
||||
public PagedMemory(byte[] romData, Type memType) {
|
||||
type = memType;
|
||||
loadData(romData);
|
||||
}
|
||||
|
||||
public void loadData(byte[] romData) {
|
||||
for (int i = 0; i < romData.length; i += 256) {
|
||||
byte[] b = new byte[256];
|
||||
for (int j = 0; j < 256; j++) {
|
||||
b[j] = romData[i + j];
|
||||
}
|
||||
internalMemory[i >> 8] = b;
|
||||
}
|
||||
}
|
||||
|
||||
public void loadData(byte[] romData, int offset, int length) {
|
||||
for (int i = 0; i < length; i += 256) {
|
||||
byte[] b = new byte[256];
|
||||
for (int j = 0; j < 256; j++) {
|
||||
b[j] = romData[offset + i + j];
|
||||
}
|
||||
internalMemory[i >> 8] = b;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[][] getMemory() {
|
||||
return internalMemory;
|
||||
}
|
||||
|
||||
public byte[] get(int pageNumber) {
|
||||
return internalMemory[pageNumber];
|
||||
}
|
||||
|
||||
public void set(int pageNumber, byte[] bank) {
|
||||
internalMemory[pageNumber] = bank;
|
||||
}
|
||||
|
||||
public byte[] getMemoryPage(int memoryBase) {
|
||||
int offset = memoryBase - type.baseAddress;
|
||||
// int page = offset >> 8;
|
||||
int page = (offset >> 8) & 0x0ff;
|
||||
// return get(page);
|
||||
return internalMemory[page];
|
||||
}
|
||||
|
||||
public void setBanks(int sourceStart, int sourceLength, int targetStart, PagedMemory source) {
|
||||
for (int i = 0; i < sourceLength; i++) {
|
||||
set(targetStart + i, source.get(sourceStart + i));
|
||||
}
|
||||
}
|
||||
|
||||
public byte readByte(int address) {
|
||||
return getMemoryPage(address)[address & 0x0ff];
|
||||
}
|
||||
|
||||
public void writeByte(int address, byte value) {
|
||||
byte[] page = getMemoryPage(address);
|
||||
StateManager.markDirtyValue(page);
|
||||
getMemoryPage(address)[address & 0x0ff] = value;
|
||||
}
|
||||
|
||||
public void fillBanks(PagedMemory source) {
|
||||
byte[][] sourceMemory = source.getMemory();
|
||||
int sourceBase = source.type.getBaseAddress() >> 8;
|
||||
int thisBase = type.getBaseAddress() >> 8;
|
||||
int start = sourceBase > thisBase ? sourceBase : thisBase;
|
||||
int sourceEnd = sourceBase + source.getMemory().length;
|
||||
int thisEnd = thisBase + getMemory().length;
|
||||
int end = sourceEnd < thisEnd ? sourceEnd : thisEnd;
|
||||
for (int i = start; i < end; i++) {
|
||||
set(i - thisBase, sourceMemory[i - sourceBase]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Fixed color palette -- only used for the older DHGR renderer (the new NTSC renderer uses its own YUV conversion and builds its own palettes)
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Palette {
|
||||
private Palette() {}
|
||||
|
||||
static public final int BLACK = 0;
|
||||
static public final int VIOLET = 3;
|
||||
static public final int BLUE = 6;
|
||||
static public final int ORANGE = 9;
|
||||
static public final int GREEN = 12;
|
||||
static public final int WHITE = 15;
|
||||
|
||||
static public Color[] color;
|
||||
static {
|
||||
color = new Color[16];
|
||||
color[ 0] = new Color( 0, 0, 0);
|
||||
color[ 1] = new Color(208, 0, 48);
|
||||
color[ 2] = new Color( 0, 0,128);
|
||||
color[ 3] = new Color(255, 0,255);
|
||||
color[ 4] = new Color( 0,128, 0);
|
||||
color[ 5] = new Color(128,128,128);
|
||||
color[ 6] = new Color( 0, 0,255);
|
||||
color[ 7] = new Color( 96,160,255);
|
||||
color[ 8] = new Color(128, 80, 0);
|
||||
color[ 9] = new Color(255,128, 0);
|
||||
color[10] = new Color(192,192,192);
|
||||
color[11] = new Color(255,144,128);
|
||||
color[12] = new Color( 0,255, 0);
|
||||
color[13] = new Color(255,255, 0);
|
||||
color[14] = new Color( 64,255,144);
|
||||
color[15] = new Color(255,255,255);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.config.Reconfigurable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* RAM is a 64K address space of paged memory. It also manages sets of memory
|
||||
* listeners, used by I/O as well as emulator add-ons (and cheats). RAM also
|
||||
* manages cards in the emulator because they are tied into the MMU memory
|
||||
* bankswitch logic.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class RAM implements Reconfigurable {
|
||||
public PagedMemory activeRead;
|
||||
public PagedMemory activeWrite;
|
||||
public List<RAMListener> listeners;
|
||||
public List<RAMListener>[] listenerMap;
|
||||
public List<RAMListener>[] ioListenerMap;
|
||||
protected Card[] cards;
|
||||
// card 0 = 80 column card firmware / system rom
|
||||
public int activeSlot = 0;
|
||||
|
||||
/**
|
||||
* Creates a new instance of RAM
|
||||
*/
|
||||
public RAM() {
|
||||
listeners = new Vector<RAMListener>();
|
||||
cards = new Card[8];
|
||||
refreshListenerMap();
|
||||
}
|
||||
|
||||
public void setActiveCard(int slot) {
|
||||
if (activeSlot != slot) {
|
||||
activeSlot = slot;
|
||||
configureActiveMemory();
|
||||
} else if (!SoftSwitches.CXROM.getState()) {
|
||||
configureActiveMemory();
|
||||
}
|
||||
}
|
||||
|
||||
public int getActiveSlot() {
|
||||
return activeSlot;
|
||||
}
|
||||
|
||||
public Card[] getAllCards() {
|
||||
return cards;
|
||||
}
|
||||
|
||||
public Card getCard(int slot) {
|
||||
if (slot >= 1 && slot <= 7) {
|
||||
return cards[slot];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addCard(Card c, int slot) {
|
||||
cards[slot] = c;
|
||||
c.setSlot(slot);
|
||||
c.attach();
|
||||
}
|
||||
|
||||
public void removeCard(Card c) {
|
||||
c.suspend();
|
||||
c.detach();
|
||||
removeCard(c.getSlot());
|
||||
}
|
||||
|
||||
public void removeCard(int slot) {
|
||||
if (cards[slot] != null) {
|
||||
cards[slot].suspend();
|
||||
cards[slot].detach();
|
||||
}
|
||||
cards[slot] = null;
|
||||
}
|
||||
|
||||
abstract public void configureActiveMemory();
|
||||
|
||||
public byte write(int address, byte b, boolean generateEvent, boolean requireSynchronization) {
|
||||
byte[] page = activeWrite.getMemoryPage(address);
|
||||
byte old = 0;
|
||||
if (page == null) {
|
||||
if (generateEvent) {
|
||||
callListener(RAMEvent.TYPE.WRITE, address, old, b, requireSynchronization);
|
||||
}
|
||||
} else {
|
||||
int offset = address & 0x0FF;
|
||||
old = page[offset];
|
||||
if (generateEvent) {
|
||||
b = callListener(RAMEvent.TYPE.WRITE, address, old, b, requireSynchronization);
|
||||
}
|
||||
page[offset] = b;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
public void writeWord(int address, int w, boolean generateEvent, boolean requireSynchronization) {
|
||||
int lsb = write(address, (byte) (w & 0x0ff), generateEvent, requireSynchronization);
|
||||
int msb = write(address + 1, (byte) (w >> 8), generateEvent, requireSynchronization);
|
||||
// int oldValue = msb << 8 + lsb;
|
||||
}
|
||||
|
||||
public byte readRaw(int address) {
|
||||
// if (address >= 65536) return 0;
|
||||
return activeRead.getMemoryPage(address)[address & 0x0FF];
|
||||
}
|
||||
|
||||
public byte read(int address, RAMEvent.TYPE eventType, boolean triggerEvent, boolean requireSyncronization) {
|
||||
// if (address >= 65536) return 0;
|
||||
byte value = activeRead.getMemoryPage(address)[address & 0x0FF];
|
||||
// if (triggerEvent || ((address & 0x0FF00) == 0x0C000)) {
|
||||
if (triggerEvent || (address & 0x0FFF0) == 0x0c030) {
|
||||
value = callListener(eventType, address, value, value, requireSyncronization);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public int readWordRaw(int address) {
|
||||
int lsb = 0x00ff & readRaw(address);
|
||||
int msb = (0x00ff & readRaw(address + 1)) << 8;
|
||||
return msb + lsb;
|
||||
}
|
||||
|
||||
public int readWord(int address, RAMEvent.TYPE eventType, boolean triggerEvent, boolean requireSynchronization) {
|
||||
int lsb = 0x00ff & read(address, eventType, triggerEvent, requireSynchronization);
|
||||
int msb = (0x00ff & read(address + 1, eventType, triggerEvent, requireSynchronization)) << 8;
|
||||
int value = msb + lsb;
|
||||
// if (generateEvent) {
|
||||
// callListener(RAMEvent.TYPE.READ, address, value, value);
|
||||
// }
|
||||
return value;
|
||||
}
|
||||
|
||||
private void mapListener(RAMListener l, int address) {
|
||||
if ((address & 0x0FF00) == 0x0C000) {
|
||||
int index = address & 0x0FF;
|
||||
List<RAMListener> ioListeners = ioListenerMap[index];
|
||||
if (ioListeners == null) {
|
||||
ioListeners = new ArrayList<>();
|
||||
ioListenerMap[index] = ioListeners;
|
||||
}
|
||||
if (!ioListeners.contains(l)) {
|
||||
ioListeners.add(l);
|
||||
}
|
||||
} else {
|
||||
int index = address >> 8;
|
||||
List<RAMListener> otherListeners = listenerMap[index];
|
||||
if (otherListeners == null) {
|
||||
otherListeners = new ArrayList<>();
|
||||
listenerMap[index] = otherListeners;
|
||||
}
|
||||
if (!otherListeners.contains(l)) {
|
||||
otherListeners.add(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addListenerRange(RAMListener l) {
|
||||
if (l.getScope() == RAMEvent.SCOPE.ADDRESS) {
|
||||
mapListener(l, l.getScopeStart());
|
||||
} else {
|
||||
int start = 0;
|
||||
int end = 0x0ffff;
|
||||
if (l.getScope() == RAMEvent.SCOPE.RANGE) {
|
||||
start = l.getScopeStart();
|
||||
end = l.getScopeEnd();
|
||||
}
|
||||
for (int i = start; i <= end; i++) {
|
||||
mapListener(l, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshListenerMap() {
|
||||
listenerMap = new ArrayList[256];
|
||||
ioListenerMap = new ArrayList[256];
|
||||
for (RAMListener l : listeners) {
|
||||
addListenerRange(l);
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(final RAMListener l) {
|
||||
boolean restart = Computer.pause();
|
||||
if (listeners.contains(l)) {
|
||||
return;
|
||||
}
|
||||
listeners.add(l);
|
||||
addListenerRange(l);
|
||||
if (restart) {
|
||||
Computer.resume();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(final RAMListener l) {
|
||||
boolean restart = Computer.pause();
|
||||
listeners.remove(l);
|
||||
refreshListenerMap();
|
||||
if (restart) {
|
||||
Computer.resume();
|
||||
}
|
||||
}
|
||||
|
||||
public byte callListener(RAMEvent.TYPE t, int address, int oldValue, int newValue, boolean requireSyncronization) {
|
||||
List<RAMListener> activeListeners = null;
|
||||
if (requireSyncronization) {
|
||||
Computer.getComputer().getCpu().suspend();
|
||||
}
|
||||
if ((address & 0x0FF00) == 0x0C000) {
|
||||
activeListeners = ioListenerMap[address & 0x0FF];
|
||||
if (activeListeners == null && t.isRead()) {
|
||||
if (requireSyncronization) {
|
||||
Computer.getComputer().getCpu().resume();
|
||||
}
|
||||
return Computer.getComputer().getVideo().getFloatingBus();
|
||||
}
|
||||
} else {
|
||||
activeListeners = listenerMap[(address >> 8) & 0x0ff];
|
||||
}
|
||||
if (activeListeners != null) {
|
||||
RAMEvent e = new RAMEvent(t, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY, address, oldValue, newValue);
|
||||
for (RAMListener l : activeListeners) {
|
||||
l.handleEvent(e);
|
||||
}
|
||||
if (requireSyncronization) {
|
||||
Computer.getComputer().getCpu().resume();
|
||||
}
|
||||
return (byte) e.getNewValue();
|
||||
}
|
||||
if (requireSyncronization) {
|
||||
Computer.getComputer().getCpu().resume();
|
||||
}
|
||||
return (byte) newValue;
|
||||
}
|
||||
|
||||
abstract protected void loadRom(String path) throws IOException;
|
||||
|
||||
public void dump() {
|
||||
for (int i = 0; i < 0x0FFFF; i += 16) {
|
||||
System.out.print(Integer.toString(i, 16));
|
||||
System.out.print(":");
|
||||
String part1 = "";
|
||||
String part2 = "";
|
||||
for (int j = 0; j < 16; j++) {
|
||||
int a = i + j;
|
||||
int br = 0x0FF & activeRead.getMemory()[i >> 8][i & 0x0ff];
|
||||
String s1 = Integer.toString(br, 16);
|
||||
System.out.print(' ');
|
||||
if (s1.length() == 1) {
|
||||
System.out.print('0');
|
||||
}
|
||||
System.out.print(s1);
|
||||
|
||||
/*
|
||||
try {
|
||||
int bw = 0;
|
||||
bw = 0x0FF & activeWrite.getMemory().get(a/256)[a%256];
|
||||
String s2 = (br == bw) ? "**" : Integer.toString(bw,16);
|
||||
System.out.print(' ');
|
||||
if (s2.length()==1) System.out.print('0');
|
||||
System.out.print(s2);
|
||||
} catch (NullPointerException ex) {
|
||||
System.out.print(" --");
|
||||
}
|
||||
*/
|
||||
}
|
||||
System.out.println();
|
||||
// System.out.println(Integer.toString(i, 16)+":"+part1+" -> "+part2);
|
||||
}
|
||||
}
|
||||
|
||||
abstract public void attach();
|
||||
abstract public void detach();
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
/**
|
||||
* A RAM event is defined as anything that causes a read or write to the
|
||||
* mainboard RAM of the computer. This could be the result of an indirect
|
||||
* address fetch (indirect addressing) as well as direct or indexed operator
|
||||
* addressing modes.
|
||||
*
|
||||
* It is also possible to track if the read is an opcode read, indicating that
|
||||
* the CPU is executing the given memory location at that moment.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class RAMEvent {
|
||||
|
||||
public enum TYPE {
|
||||
|
||||
READ(true),
|
||||
READ_DATA(true),
|
||||
EXECUTE(true),
|
||||
READ_OPERAND(true),
|
||||
WRITE(false),
|
||||
ANY(false);
|
||||
boolean read = false;
|
||||
|
||||
TYPE(boolean r) {
|
||||
this.read = r;
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
return read;
|
||||
}
|
||||
};
|
||||
|
||||
public enum SCOPE {
|
||||
|
||||
ADDRESS,
|
||||
RANGE,
|
||||
ANY
|
||||
};
|
||||
|
||||
public enum VALUE {
|
||||
|
||||
ANY,
|
||||
RANGE,
|
||||
EQUALS,
|
||||
NOT_EQUALS,
|
||||
CHANGE_BY
|
||||
};
|
||||
private TYPE type;
|
||||
private SCOPE scope;
|
||||
private VALUE value;
|
||||
private int address, oldValue, newValue;
|
||||
|
||||
/**
|
||||
* Creates a new instance of RAMEvent
|
||||
*/
|
||||
public RAMEvent(TYPE t, SCOPE s, VALUE v, int address, int oldValue, int newValue) {
|
||||
setType(t);
|
||||
setScope(s);
|
||||
setValue(v);
|
||||
this.setAddress(address);
|
||||
this.setOldValue(oldValue);
|
||||
this.setNewValue(newValue);
|
||||
}
|
||||
|
||||
public TYPE getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public final void setType(TYPE type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public SCOPE getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public final void setScope(SCOPE scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public VALUE getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public final void setValue(VALUE value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public final void setAddress(int address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public int getOldValue() {
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
public final void setOldValue(int oldValue) {
|
||||
this.oldValue = oldValue;
|
||||
}
|
||||
|
||||
public int getNewValue() {
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public final void setNewValue(int newValue) {
|
||||
this.newValue = newValue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
|
||||
/**
|
||||
* A Ram Listener waits for a specific ram event, as specified by access type
|
||||
* (read/write/execute, etc) or a memory address, or range of addresses. The
|
||||
* subclass must define the address range (scope start/end) via the doConfig
|
||||
* method. Ram listeners are used all over the emulator, but especially in cheat
|
||||
* modules and the softswitch and I/O cards.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class RAMListener {
|
||||
|
||||
private RAMEvent.TYPE type;
|
||||
private RAMEvent.SCOPE scope;
|
||||
private RAMEvent.VALUE value;
|
||||
private int scopeStart;
|
||||
private int scopeEnd;
|
||||
private int valueStart;
|
||||
private int valueEnd;
|
||||
private int valueAmount;
|
||||
|
||||
/**
|
||||
* Creates a new instance of RAMListener
|
||||
*/
|
||||
public RAMListener(RAMEvent.TYPE t, RAMEvent.SCOPE s, RAMEvent.VALUE v) {
|
||||
setType(t);
|
||||
setScope(s);
|
||||
setValue(v);
|
||||
doConfig();
|
||||
}
|
||||
|
||||
public RAMEvent.TYPE getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public final void setType(RAMEvent.TYPE type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public RAMEvent.SCOPE getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public final void setScope(RAMEvent.SCOPE scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public RAMEvent.VALUE getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public final void setValue(RAMEvent.VALUE value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getScopeStart() {
|
||||
return scopeStart;
|
||||
}
|
||||
|
||||
public void setScopeStart(int scopeStart) {
|
||||
this.scopeStart = scopeStart;
|
||||
}
|
||||
|
||||
public int getScopeEnd() {
|
||||
return scopeEnd;
|
||||
}
|
||||
|
||||
public void setScopeEnd(int scopeEnd) {
|
||||
this.scopeEnd = scopeEnd;
|
||||
}
|
||||
|
||||
public int getValueStart() {
|
||||
return valueStart;
|
||||
}
|
||||
|
||||
public void setValueStart(int valueStart) {
|
||||
this.valueStart = valueStart;
|
||||
}
|
||||
|
||||
public int getValueEnd() {
|
||||
return valueEnd;
|
||||
}
|
||||
|
||||
public void setValueEnd(int valueEnd) {
|
||||
this.valueEnd = valueEnd;
|
||||
}
|
||||
|
||||
public int getValueAmount() {
|
||||
return valueAmount;
|
||||
}
|
||||
|
||||
public void setValueAmount(int valueAmount) {
|
||||
this.valueAmount = valueAmount;
|
||||
}
|
||||
|
||||
public boolean isRelevant(RAMEvent e) {
|
||||
// Skip event if it's not the right type
|
||||
if (type != TYPE.ANY && e.getType() != TYPE.ANY) {
|
||||
if ((type != e.getType())) {
|
||||
if (type == TYPE.READ) {
|
||||
if (!e.getType().isRead()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Skip event if it's not in the scope we care about
|
||||
if (scope != RAMEvent.SCOPE.ANY) {
|
||||
if (scope == RAMEvent.SCOPE.ADDRESS && e.getAddress() != scopeStart) {
|
||||
return false;
|
||||
} else if (scope == RAMEvent.SCOPE.RANGE && (e.getAddress() < scopeStart || e.getAddress() > scopeEnd)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip event if the value modification is uninteresting
|
||||
if (value != RAMEvent.VALUE.ANY) {
|
||||
if (value == RAMEvent.VALUE.CHANGE_BY && e.getNewValue() - e.getOldValue() != valueAmount) {
|
||||
return false;
|
||||
} else if (value == RAMEvent.VALUE.EQUALS && e.getNewValue() != valueAmount) {
|
||||
return false;
|
||||
} else if (value == RAMEvent.VALUE.NOT_EQUALS && e.getNewValue() == valueAmount) {
|
||||
return false;
|
||||
} else if (value == RAMEvent.VALUE.RANGE && (e.getNewValue() < valueStart || e.getNewValue() > valueEnd)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ok, so we've filtered out the uninteresting stuff
|
||||
// If we've made it this far then the event is valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
public void handleEvent(RAMEvent e) {
|
||||
if (isRelevant(e)) {
|
||||
doEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected void doConfig();
|
||||
|
||||
abstract protected void doEvent(RAMEvent e);
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.state.Stateful;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A softswitch is a hidden bit that lives in the MMU, it can be activated or
|
||||
* deactivated to change operating characteristics of the computer such as video
|
||||
* display mode or memory paging model. Other special softswitches access
|
||||
* keyboard and speaker ports. The underlying mechanic of softswitches is
|
||||
* managed by the RamListener/Ram model and, in the case of video modes, the
|
||||
* Video classes.
|
||||
*
|
||||
* The implementation of softswitches is in jace.apple2e.SoftSwitches
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
* @see jace.apple2e.SoftSwitches
|
||||
*/
|
||||
public abstract class SoftSwitch {
|
||||
|
||||
@Stateful
|
||||
public Boolean state;
|
||||
private Boolean initalState;
|
||||
private List<RAMListener> listeners;
|
||||
private final List<Integer> exclusionActivate = new ArrayList<>();
|
||||
private final List<Integer> exclusionDeactivate = new ArrayList<>();
|
||||
private final List<Integer> exclusionQuery = new ArrayList<>();
|
||||
private String name;
|
||||
private boolean toggleType = false;
|
||||
|
||||
/**
|
||||
* Creates a new instance of SoftSwitch
|
||||
*
|
||||
* @param name
|
||||
* @param initalState
|
||||
*/
|
||||
public SoftSwitch(String name, Boolean initalState) {
|
||||
this.initalState = initalState;
|
||||
this.state = initalState;
|
||||
this.listeners = new ArrayList<>();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public SoftSwitch(String name, int offAddress, int onAddress, int queryAddress, RAMEvent.TYPE changeType, Boolean initalState) {
|
||||
if (onAddress == offAddress && onAddress != -1) {
|
||||
toggleType = true;
|
||||
// System.out.println("Switch " + name + " is a toggle type switch!");
|
||||
}
|
||||
this.initalState = initalState;
|
||||
this.state = initalState;
|
||||
this.listeners = new ArrayList<>();
|
||||
this.name = name;
|
||||
int[] onAddresses = null;
|
||||
int[] offAddresses = null;
|
||||
int[] queryAddressList = null;
|
||||
if (onAddress >= 0) {
|
||||
onAddresses = new int[]{onAddress};
|
||||
}
|
||||
if (offAddress >= 0) {
|
||||
offAddresses = new int[]{offAddress};
|
||||
}
|
||||
if (queryAddress >= 0) {
|
||||
queryAddressList = new int[]{queryAddress};
|
||||
}
|
||||
init(offAddresses, onAddresses, queryAddressList, changeType);
|
||||
}
|
||||
|
||||
public SoftSwitch(String name, int[] offAddrs, int[] onAddrs, int[] queryAddrs, RAMEvent.TYPE changeType, Boolean initalState) {
|
||||
this(name, initalState);
|
||||
init(offAddrs, onAddrs, queryAddrs, changeType);
|
||||
}
|
||||
|
||||
private void init(int[] offAddrs, int[] onAddrs, int[] queryAddrs, RAMEvent.TYPE changeType) {
|
||||
if (toggleType) {
|
||||
List<Integer> addrs = new ArrayList<>();
|
||||
for (int i : onAddrs) {
|
||||
addrs.add(i);
|
||||
}
|
||||
Collections.sort(addrs);
|
||||
final int beginAddr = addrs.get(0);
|
||||
final int endAddr = addrs.get(addrs.size() - 1);
|
||||
for (int i = beginAddr; i < endAddr; i++) {
|
||||
if (!addrs.contains(i)) {
|
||||
exclusionActivate.add(i);
|
||||
}
|
||||
}
|
||||
RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(beginAddr);
|
||||
setScopeEnd(endAddr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (!exclusionActivate.contains(e.getAddress())) {
|
||||
// System.out.println("Access to "+Integer.toHexString(e.getAddress())+" ENABLES switch "+getName());
|
||||
setState(!getState());
|
||||
}
|
||||
}
|
||||
};
|
||||
addListener(l);
|
||||
} else {
|
||||
if (onAddrs != null) {
|
||||
List<Integer> addrs = new ArrayList<>();
|
||||
for (int i : onAddrs) {
|
||||
addrs.add(i);
|
||||
}
|
||||
Collections.sort(addrs);
|
||||
final int beginAddr = addrs.get(0);
|
||||
final int endAddr = addrs.get(addrs.size() - 1);
|
||||
for (int i = beginAddr; i < endAddr; i++) {
|
||||
if (!addrs.contains(i)) {
|
||||
exclusionActivate.add(i);
|
||||
}
|
||||
}
|
||||
RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(beginAddr);
|
||||
setScopeEnd(endAddr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (!exclusionActivate.contains(e.getAddress())) {
|
||||
// System.out.println("Access to "+Integer.toHexString(e.getAddress())+" ENABLES switch "+getName());
|
||||
setState(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
addListener(l);
|
||||
}
|
||||
|
||||
if (offAddrs != null) {
|
||||
List<Integer> addrs = new ArrayList<>();
|
||||
for (int i : offAddrs) {
|
||||
addrs.add(i);
|
||||
}
|
||||
final int beginAddr = addrs.get(0);
|
||||
final int endAddr = addrs.get(addrs.size() - 1);
|
||||
for (int i = beginAddr; i < endAddr; i++) {
|
||||
if (!addrs.contains(i)) {
|
||||
exclusionDeactivate.add(i);
|
||||
}
|
||||
}
|
||||
RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(beginAddr);
|
||||
setScopeEnd(endAddr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (!exclusionDeactivate.contains(e.getAddress())) {
|
||||
setState(false);
|
||||
// System.out.println("Access to "+Integer.toHexString(e.getAddress())+" disables switch "+getName());
|
||||
}
|
||||
}
|
||||
};
|
||||
addListener(l);
|
||||
}
|
||||
}
|
||||
|
||||
if (queryAddrs != null) {
|
||||
List<Integer> addrs = new ArrayList<>();
|
||||
for (int i : queryAddrs) {
|
||||
addrs.add(i);
|
||||
}
|
||||
final int beginAddr = addrs.get(0);
|
||||
final int endAddr = addrs.get(addrs.size() - 1);
|
||||
for (int i = beginAddr; i < endAddr; i++) {
|
||||
if (!addrs.contains(i)) {
|
||||
exclusionQuery.add(i);
|
||||
}
|
||||
}
|
||||
// RAMListener l = new RAMListener(changeType, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
RAMListener l = new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(beginAddr);
|
||||
setScopeEnd(endAddr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (!exclusionQuery.contains(e.getAddress())) {
|
||||
e.setNewValue(0x0ff & readSwitch());
|
||||
// System.out.println("Read from "+Integer.toHexString(e.getAddress())+" returns "+Integer.toHexString(e.getNewValue()));
|
||||
}
|
||||
}
|
||||
};
|
||||
addListener(l);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean inhibit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
abstract protected byte readSwitch();
|
||||
|
||||
protected void addListener(RAMListener l) {
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
if (initalState != null) {
|
||||
setState(initalState);
|
||||
}
|
||||
}
|
||||
|
||||
public void register() {
|
||||
RAM m = Computer.getComputer().getMemory();
|
||||
listeners.stream().forEach((l) -> {
|
||||
m.addListener(l);
|
||||
});
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
RAM m = Computer.getComputer().getMemory();
|
||||
listeners.stream().forEach((l) -> {
|
||||
m.removeListener(l);
|
||||
});
|
||||
}
|
||||
|
||||
public void setState(boolean newState) {
|
||||
if (inhibit()) {
|
||||
return;
|
||||
}
|
||||
// if (this != SoftSwitches.VBL.getSwitch() &&
|
||||
// this != SoftSwitches.KEYBOARD.getSwitch())
|
||||
// System.out.println("Switch "+name+" set to "+newState);
|
||||
state = newState;
|
||||
/*
|
||||
if (queryAddresses != null) {
|
||||
RAM m = Computer.getComputer().getMemory();
|
||||
for (int i:queryAddresses) {
|
||||
byte old = m.read(i, false);
|
||||
m.write(i, (byte) (old & 0x7f | (state ? 0x080:0x000)), false);
|
||||
}
|
||||
}
|
||||
*/
|
||||
stateChanged();
|
||||
}
|
||||
|
||||
public final boolean getState() {
|
||||
if (state == null) {
|
||||
return false;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
abstract public void stateChanged();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + (getState() ? ":1" : ":0");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.DynamicSelection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.Mixer.Info;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
/**
|
||||
* Manages sound resources used by various audio devices (such as speaker and
|
||||
* mockingboard cards.) The plumbing is managed in this class so that the
|
||||
* consumers do not have to do a lot of work to manage mixer lines or deal with
|
||||
* how to reuse active lines if needed. It is possible that this class might be
|
||||
* used to manage volume in the future, but that remains to be seen.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class SoundMixer extends Device {
|
||||
|
||||
private final Set<SourceDataLine> availableLines = Collections.synchronizedSet(new HashSet<SourceDataLine>());
|
||||
private final Map<Object, SourceDataLine> activeLines = Collections.synchronizedMap(new HashMap<Object, SourceDataLine>());
|
||||
/**
|
||||
* Bits per sample
|
||||
*/
|
||||
@ConfigurableField(name = "Bits per sample", shortName = "bits")
|
||||
public static int BITS = 16;
|
||||
/**
|
||||
* Sample playback rate
|
||||
*/
|
||||
@ConfigurableField(name = "Playback Rate", shortName = "freq")
|
||||
public static int RATE = 48000;
|
||||
/**
|
||||
* Sound format used for playback
|
||||
*/
|
||||
private AudioFormat af;
|
||||
/**
|
||||
* Is sound line available for playback at all?
|
||||
*/
|
||||
public boolean lineAvailable;
|
||||
@ConfigurableField(name = "Audio device", description = "Audio output device")
|
||||
public static DynamicSelection<String> preferredMixer = new DynamicSelection<String>(null) {
|
||||
@Override
|
||||
public boolean allowNull() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<? extends String, String> getSelections() {
|
||||
Info[] mixerInfo = AudioSystem.getMixerInfo();
|
||||
LinkedHashMap<String, String> out = new LinkedHashMap<>();
|
||||
for (Info i : mixerInfo) {
|
||||
out.put(i.getName(), i.getName());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
private Mixer theMixer;
|
||||
|
||||
@Override
|
||||
public String getDeviceName() {
|
||||
return "Sound Output";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "mixer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reconfigure() {
|
||||
detach();
|
||||
try {
|
||||
initMixer();
|
||||
if (lineAvailable) {
|
||||
initAudio();
|
||||
System.out.println("Started sound");
|
||||
} else {
|
||||
System.out.println("Sound not stared: Line not available");
|
||||
}
|
||||
} catch (LineUnavailableException ex) {
|
||||
System.out.println("Unable to start sound");
|
||||
Logger.getLogger(SoundMixer.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
attach();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain sound playback line if available
|
||||
*
|
||||
* @throws javax.sound.sampled.LineUnavailableException If there is no line
|
||||
* available
|
||||
*/
|
||||
private void initAudio() throws LineUnavailableException {
|
||||
af = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, RATE, BITS, 2, BITS / 4, RATE, true);
|
||||
// af = new AudioFormat(RATE, BITS, 2, true, true);
|
||||
DataLine.Info dli = new DataLine.Info(SourceDataLine.class, af);
|
||||
lineAvailable = AudioSystem.isLineSupported(dli);
|
||||
}
|
||||
|
||||
public synchronized SourceDataLine getLine(Object requester) throws LineUnavailableException {
|
||||
if (activeLines.containsKey(requester)) {
|
||||
return activeLines.get(requester);
|
||||
}
|
||||
SourceDataLine sdl = null;
|
||||
if (availableLines.isEmpty()) {
|
||||
sdl = getNewLine();
|
||||
} else {
|
||||
sdl = availableLines.iterator().next();
|
||||
availableLines.remove(sdl);
|
||||
}
|
||||
activeLines.put(requester, sdl);
|
||||
sdl.start();
|
||||
return sdl;
|
||||
}
|
||||
|
||||
public void returnLine(Object requester) {
|
||||
if (activeLines.containsKey(requester)) {
|
||||
SourceDataLine sdl = activeLines.remove(requester);
|
||||
// Calling drain on pulse driver can cause it to freeze up (?)
|
||||
// sdl.drain();
|
||||
sdl.stop();
|
||||
sdl.flush();
|
||||
availableLines.add(sdl);
|
||||
}
|
||||
}
|
||||
|
||||
private SourceDataLine getNewLine() throws LineUnavailableException {
|
||||
SourceDataLine l = null;
|
||||
// Line.Info[] info = theMixer.getSourceLineInfo();
|
||||
DataLine.Info dli = new DataLine.Info(SourceDataLine.class, af);
|
||||
System.out.println("Maximum output lines: " + theMixer.getMaxLines(dli));
|
||||
System.out.println("Allocated output lines: " + theMixer.getSourceLines().length);
|
||||
System.out.println("Getting source line from " + theMixer.getMixerInfo().toString() + ": " + af.toString());
|
||||
try {
|
||||
l = (SourceDataLine) theMixer.getLine(dli);
|
||||
} catch (IllegalArgumentException e) {
|
||||
lineAvailable = false;
|
||||
throw new LineUnavailableException(e.getMessage());
|
||||
} catch (LineUnavailableException e) {
|
||||
lineAvailable = false;
|
||||
throw e;
|
||||
}
|
||||
if (!(l instanceof SourceDataLine)) {
|
||||
lineAvailable = false;
|
||||
throw new LineUnavailableException("Line is not an output line!");
|
||||
}
|
||||
final SourceDataLine sdl = (SourceDataLine) l;
|
||||
// sdl.open(af);
|
||||
// if (false) {
|
||||
// return sdl;
|
||||
// }
|
||||
sdl.open();
|
||||
sdl.start();
|
||||
// new Thread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// System.out.println("Going into an infinite loop!!!");
|
||||
// try {
|
||||
// while (true) {
|
||||
// sdl.write(new byte[]{randomByte(),randomByte(),randomByte(),randomByte()}, 0, 4);
|
||||
// }
|
||||
// } catch (Throwable t) {
|
||||
// t.printStackTrace();
|
||||
// }
|
||||
// System.out.println("Thread dying...");
|
||||
// }
|
||||
// }).start();
|
||||
return sdl;
|
||||
}
|
||||
|
||||
public byte randomByte() {
|
||||
return (byte) (Math.random() * 256);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
// if (Motherboard.enableSpeaker)
|
||||
// Motherboard.speaker.attach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
availableLines.stream().forEach((line) -> {
|
||||
line.close();
|
||||
});
|
||||
Set requesters = new HashSet(activeLines.keySet());
|
||||
requesters.stream().map((o) -> {
|
||||
if (o instanceof Device) {
|
||||
((Device) o).detach();
|
||||
}
|
||||
return o;
|
||||
}).filter((o) -> (o instanceof Card)).forEach((o) -> {
|
||||
((Card) o).reconfigure();
|
||||
});
|
||||
if (theMixer != null) {
|
||||
for (Line l : theMixer.getSourceLines()) {
|
||||
l.close();
|
||||
}
|
||||
}
|
||||
availableLines.clear();
|
||||
activeLines.clear();
|
||||
}
|
||||
|
||||
private void initMixer() {
|
||||
Info selected = null;
|
||||
Info[] mixerInfo = AudioSystem.getMixerInfo();
|
||||
|
||||
if (mixerInfo == null || mixerInfo.length == 0) {
|
||||
theMixer = null;
|
||||
lineAvailable = false;
|
||||
System.out.println("No sound mixer is available!");
|
||||
return;
|
||||
}
|
||||
|
||||
String mixer = preferredMixer.getValue();
|
||||
selected = mixerInfo[0];
|
||||
for (Info i : mixerInfo) {
|
||||
if (i.getName().equalsIgnoreCase(mixer)) {
|
||||
selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
theMixer = AudioSystem.getMixer(selected);
|
||||
for (Line l : theMixer.getSourceLines()) {
|
||||
l.close();
|
||||
}
|
||||
lineAvailable = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.config.ConfigurableField;
|
||||
|
||||
/**
|
||||
* A timed device is a device which executes so many ticks in a given time
|
||||
* interval. This is the core of the emulator timing mechanics.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class TimedDevice extends Device {
|
||||
|
||||
/**
|
||||
* Creates a new instance of TimedDevice
|
||||
*/
|
||||
public TimedDevice() {
|
||||
setSpeed(cyclesPerSecond);
|
||||
}
|
||||
@ConfigurableField(name = "Speed", description = "(in hertz)")
|
||||
public long cyclesPerSecond = defaultCyclesPerSecond();
|
||||
@ConfigurableField(name = "Max speed")
|
||||
public boolean maxspeed = false;
|
||||
|
||||
@Override
|
||||
public abstract void tick();
|
||||
private static final double NANOS_PER_SECOND = 1000000000.0;
|
||||
// current cycle within the period
|
||||
private int cycleTimer = 0;
|
||||
// The actual worker that the device runs as
|
||||
public Thread worker;
|
||||
public static int TEMP_SPEED_MAX_DURATION = 1000000;
|
||||
private int tempSpeedDuration = 0;
|
||||
public boolean hasStopped = true;
|
||||
|
||||
@Override
|
||||
public boolean suspend() {
|
||||
disableTempMaxSpeed();
|
||||
boolean result = super.suspend();
|
||||
if (worker != null && worker.isAlive()) {
|
||||
try {
|
||||
worker.interrupt();
|
||||
worker.join(1000);
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
worker = null;
|
||||
return result;
|
||||
}
|
||||
Thread timerThread;
|
||||
|
||||
public boolean pause() {
|
||||
if (!isRunning()) {
|
||||
return false;
|
||||
}
|
||||
isPaused = true;
|
||||
try {
|
||||
// KLUDGE: Sleeping to wait for worker thread to hit paused state. We might be inside the worker (?)
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
super.resume();
|
||||
isPaused = false;
|
||||
if (worker != null && worker.isAlive()) {
|
||||
return;
|
||||
}
|
||||
worker = new Thread(() -> {
|
||||
while (isRunning()) {
|
||||
hasStopped = false;
|
||||
doTick();
|
||||
while (isPaused) {
|
||||
hasStopped = true;
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException ex) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
resync();
|
||||
}
|
||||
hasStopped = true;
|
||||
});
|
||||
worker.setDaemon(false);
|
||||
worker.setPriority(Thread.MAX_PRIORITY);
|
||||
worker.start();
|
||||
worker.setName("Timed device " + getDeviceName() + " worker");
|
||||
}
|
||||
long nanosPerInterval; // How long to wait between pauses
|
||||
long cyclesPerInterval; // How many cycles to wait until a pause interval
|
||||
long nextSync; // When was the last pause?
|
||||
|
||||
public final void setSpeed(long cyclesPerSecond) {
|
||||
cyclesPerInterval = cyclesPerSecond / 100L;
|
||||
nanosPerInterval = (long) ((double) cyclesPerInterval * NANOS_PER_SECOND / (double) cyclesPerSecond);
|
||||
// System.out.println("Will pause " + nanosPerInterval + " nanos every " + cyclesPerInterval + " cycles");
|
||||
cycleTimer = 0;
|
||||
resetSyncTimer();
|
||||
}
|
||||
long skip = 0;
|
||||
long wait = 0;
|
||||
|
||||
public final void resetSyncTimer() {
|
||||
nextSync = System.nanoTime() + nanosPerInterval;
|
||||
cycleTimer = 0;
|
||||
}
|
||||
|
||||
public void enableTempMaxSpeed() {
|
||||
tempSpeedDuration = TEMP_SPEED_MAX_DURATION;
|
||||
}
|
||||
|
||||
public void disableTempMaxSpeed() {
|
||||
tempSpeedDuration = 0;
|
||||
resetSyncTimer();
|
||||
}
|
||||
|
||||
protected void resync() {
|
||||
if (++cycleTimer >= cyclesPerInterval) {
|
||||
if (maxspeed || tempSpeedDuration > 0) {
|
||||
if (tempSpeedDuration > 0) {
|
||||
tempSpeedDuration -= cyclesPerInterval;
|
||||
}
|
||||
resetSyncTimer();
|
||||
return;
|
||||
}
|
||||
long now = System.nanoTime();
|
||||
if (now < nextSync) {
|
||||
cycleTimer = 0;
|
||||
long currentSyncDiff = nextSync - now;
|
||||
// Don't bother resynchronizing unless we're off by 10ms
|
||||
if (currentSyncDiff > 10000000L) {
|
||||
try {
|
||||
// System.out.println("Sleeping for " + currentSyncDiff / 1000000 + " milliseconds");
|
||||
Thread.sleep(currentSyncDiff / 1000000L, (int) (currentSyncDiff % 1000000L));
|
||||
} catch (InterruptedException ex) {
|
||||
System.err.println(getDeviceName() + " was trying to sleep for " + (currentSyncDiff / 1000000) + " millis but was woken up");
|
||||
// Logger.getLogger(TimedDevice.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
} else {
|
||||
// System.out.println("Sleeping for " + currentSyncDiff + " nanoseconds");
|
||||
// LockSupport.parkNanos(currentSyncDiff);
|
||||
}
|
||||
}
|
||||
nextSync += nanosPerInterval;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
if (cyclesPerSecond == 0) {
|
||||
cyclesPerSecond = defaultCyclesPerSecond();
|
||||
}
|
||||
setSpeed(cyclesPerSecond);
|
||||
}
|
||||
|
||||
public abstract long defaultCyclesPerSecond();
|
||||
}
|
|
@ -0,0 +1,552 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.Emulator;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.EventQueue;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JProgressBar;
|
||||
|
||||
/**
|
||||
* This is a set of helper functions which do not belong anywhere else. Functions vary from introspection, discovery, and string/pattern matching.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Utility {
|
||||
|
||||
//--------------- Introspection utilities
|
||||
private static Set<Class> findClasses(String pckgname, Class clazz) {
|
||||
Set<Class> output = new HashSet<>();
|
||||
// Code from JWhich
|
||||
// ======
|
||||
// Translate the package name into an absolute path
|
||||
String name = pckgname;
|
||||
if (!name.startsWith("/")) {
|
||||
name = "/" + name;
|
||||
}
|
||||
name = name.replace('.', '/');
|
||||
|
||||
// Get a File object for the package
|
||||
URL url = Utility.class.getResource(name);
|
||||
if (url == null || url.getFile().contains("jre/lib")) {
|
||||
return output;
|
||||
}
|
||||
if (url.getProtocol().equalsIgnoreCase("jar")) {
|
||||
return findClassesInJar(url, clazz);
|
||||
}
|
||||
|
||||
File directory = new File(url.getFile());
|
||||
// New code
|
||||
// ======
|
||||
if (directory.exists()) {
|
||||
// Get the list of the files contained in the package
|
||||
for (String filename : directory.list()) {
|
||||
char firstLetter = filename.charAt(0);
|
||||
if (firstLetter < 'A' || (firstLetter > 'Z' && firstLetter < 'a') || firstLetter > 'z') {
|
||||
continue;
|
||||
}
|
||||
// we are only interested in .class files
|
||||
if (filename.endsWith(".class")) {
|
||||
// removes the .class extension
|
||||
String classname = filename.substring(0, filename.length() - 6);
|
||||
try {
|
||||
// Try to create an instance of the object
|
||||
String className = pckgname + "." + classname;
|
||||
// System.out.println("Class: " + className);
|
||||
Class c = Class.forName(className);
|
||||
if (clazz.isAssignableFrom(c)) {
|
||||
output.add(c);
|
||||
}
|
||||
} catch (ClassNotFoundException cnfex) {
|
||||
System.err.println(cnfex);
|
||||
}
|
||||
} else {
|
||||
// System.out.println("Skipping non class: " + filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private static Set<Class> findClassesInJar(URL jarLocation, Class clazz) {
|
||||
Set<Class> output = new HashSet<>();
|
||||
JarFile jarFile = null;
|
||||
try {
|
||||
JarURLConnection conn = (JarURLConnection) jarLocation.openConnection();
|
||||
jarFile = conn.getJarFile();
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
String last = "";
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry jarEntry = entries.nextElement();
|
||||
if (jarEntry.getName().equals(last)) {
|
||||
return output;
|
||||
}
|
||||
last = jarEntry.getName();
|
||||
if (jarEntry.getName().endsWith(".class")) {
|
||||
String className = jarEntry.getName();
|
||||
className = className.substring(0, className.length() - 6);
|
||||
className = className.replaceAll("/", "\\.");
|
||||
if (className.startsWith("com.sun")) {
|
||||
continue;
|
||||
}
|
||||
if (className.startsWith("java")) {
|
||||
continue;
|
||||
}
|
||||
if (className.startsWith("javax")) {
|
||||
continue;
|
||||
}
|
||||
if (className.startsWith("com.oracle")) {
|
||||
continue;
|
||||
}
|
||||
// removes the .class extension
|
||||
try {
|
||||
// Try to create an instance of the object
|
||||
// System.out.println("Class: " + className);
|
||||
Class c = Class.forName(className);
|
||||
if (clazz.isAssignableFrom(c)) {
|
||||
output.add(c);
|
||||
}
|
||||
} catch (ClassNotFoundException cnfex) {
|
||||
System.err.println(cnfex);
|
||||
} catch (Throwable cnfex) {
|
||||
// System.err.println(cnfex);
|
||||
}
|
||||
} else {
|
||||
// System.out.println("Skipping non class: " + jarEntry.getName());
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Utility.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
if (jarFile != null) {
|
||||
jarFile.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Utility.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
private static final Map<Class, Collection<Class>> classCache = new HashMap<>();
|
||||
|
||||
public static List<Class> findAllSubclasses(Class clazz) {
|
||||
if (classCache.containsKey(clazz)) {
|
||||
return (List<Class>) classCache.get(clazz);
|
||||
}
|
||||
TreeMap<String, Class> allClasses = new TreeMap<>();
|
||||
for (Package p : Package.getPackages()) {
|
||||
if (p.getName().startsWith("java")
|
||||
|| p.getName().startsWith("com.sun")
|
||||
|| p.getName().startsWith("com.oracle")) {
|
||||
continue;
|
||||
}
|
||||
findClasses(p.getName(), clazz).stream().filter((c) -> !(Modifier.isAbstract(c.getModifiers()))).forEach((c) -> {
|
||||
allClasses.put(c.getSimpleName(), c);
|
||||
});
|
||||
}
|
||||
List<Class> values = new ArrayList(allClasses.values());
|
||||
classCache.put(clazz, values);
|
||||
return values;
|
||||
}
|
||||
|
||||
//------------------------------ String comparators
|
||||
/**
|
||||
* Rank two strings similarity in terms of distance The lower the number,
|
||||
* the more similar these strings are to each other See:
|
||||
* http://en.wikipedia.org/wiki/Levenshtein_distance#Computing_Levenshtein_distance
|
||||
*
|
||||
* @param s
|
||||
* @param t
|
||||
* @return Distance (higher is better)
|
||||
*/
|
||||
public static int levenshteinDistance(String s, String t) {
|
||||
if (s == null || t == null || s.length() == 0 || t.length() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
s = s.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "");
|
||||
t = t.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "");
|
||||
int m = s.length();
|
||||
int n = t.length();
|
||||
int[][] dist = new int[m + 1][n + 1];
|
||||
for (int i = 1; i <= m; i++) {
|
||||
dist[i][0] = i;
|
||||
}
|
||||
for (int i = 1; i <= n; i++) {
|
||||
dist[0][i] = i;
|
||||
}
|
||||
for (int j = 1; j <= n; j++) {
|
||||
for (int i = 1; i <= m; i++) {
|
||||
if (s.charAt(i - 1) == t.charAt(j - 1)) {
|
||||
dist[i][j] = dist[i - 1][j - 1];
|
||||
} else {
|
||||
int del = dist[i - 1][j] + 1;
|
||||
int insert = dist[i][j - 1] + 1;
|
||||
int sub = dist[i - 1][j - 1] + 1;
|
||||
dist[i][j] = Math.min(Math.min(del, insert), sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.max(m, n) - dist[m][n];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare strings based on a tally of similar patterns found, using a fixed
|
||||
* search window The resulting score is heavily penalized if the strings
|
||||
* differ greatly in length This is not as efficient as levenshtein, so it's
|
||||
* only used as a tie-breaker.
|
||||
*
|
||||
* @param c1
|
||||
* @param c2
|
||||
* @param width Search window size
|
||||
* @return Overall similarity score (higher is beter)
|
||||
*/
|
||||
public static double rankMatch(String c1, String c2, int width) {
|
||||
double score = 0;
|
||||
String s1 = c1.toLowerCase();
|
||||
String s2 = c2.toLowerCase();
|
||||
for (int i = 0; i < s1.length() + 1 - width; i++) {
|
||||
String m = s1.substring(i, i + width);
|
||||
int j = 0;
|
||||
while ((j = s2.indexOf(m, j)) > -1) {
|
||||
score += width;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
double l1 = s1.length();
|
||||
double l2 = s2.length();
|
||||
// If the two strings are equivilent in length, the score is higher
|
||||
// If the two strings are different in length, the score is adjusted lower depending on how large the difference is
|
||||
// This is offset just a hair for tuning purposes
|
||||
double adjustment = (Math.min(l1, l2) / Math.max(l1, l2)) + 0.1;
|
||||
return score * adjustment * adjustment;
|
||||
}
|
||||
|
||||
public static String join(Collection c, String d) {
|
||||
String result = "";
|
||||
boolean isFirst = true;
|
||||
for (Object o : c) {
|
||||
result += (isFirst ? "" : d) + o.toString();
|
||||
isFirst = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ImageIcon loadIcon(String filename) {
|
||||
URL imageUrl = Utility.class.getClassLoader().getResource("jace/data/" + filename);
|
||||
ImageIcon i = new ImageIcon(imageUrl);
|
||||
return i;
|
||||
}
|
||||
|
||||
public static void runModalProcess(String title, final Runnable runnable) {
|
||||
final JDialog frame = new JDialog(Emulator.getFrame());
|
||||
final JProgressBar progressBar = new JProgressBar();
|
||||
progressBar.setIndeterminate(true);
|
||||
final JPanel contentPane = new JPanel();
|
||||
contentPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
contentPane.setLayout(new BorderLayout());
|
||||
contentPane.add(new JLabel(title), BorderLayout.NORTH);
|
||||
contentPane.add(progressBar, BorderLayout.CENTER);
|
||||
frame.setContentPane(contentPane);
|
||||
frame.pack();
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setVisible(true);
|
||||
|
||||
new Thread(() -> {
|
||||
runnable.run();
|
||||
frame.setVisible(false);
|
||||
frame.dispose();
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static class RankingComparator implements Comparator<String> {
|
||||
|
||||
String match;
|
||||
|
||||
public RankingComparator(String match) {
|
||||
// Adding a space helps respect word boundaries as part of the match
|
||||
// In the case of very close matches this is another tie-breaker
|
||||
// Especially for very small search terms
|
||||
this.match = match + " ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
double s1 = levenshteinDistance(match, o1);
|
||||
double s2 = levenshteinDistance(match, o2);
|
||||
if (s2 == s1) {
|
||||
s1 = rankMatch(o1, match, 3) + rankMatch(o1, match, 2);
|
||||
s2 = rankMatch(o2, match, 3) + rankMatch(o2, match, 2);
|
||||
if (s2 == s1) {
|
||||
return (o1.compareTo(o2));
|
||||
} else {
|
||||
// Normalize result to -1, 0 or 1 so there is no rounding issues!
|
||||
return (int) Math.signum(s2 - s1);
|
||||
}
|
||||
} else {
|
||||
return (int) (s2 - s1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a desired search string and a search space of recognized
|
||||
* selections, identify the best match in the list
|
||||
*
|
||||
* @param match String to search for
|
||||
* @param search Space of all valid results
|
||||
* @return Best match found, or null if there was nothing close to a match
|
||||
* found.
|
||||
*/
|
||||
public static String findBestMatch(String match, Collection<String> search) {
|
||||
if (search == null || search.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
RankingComparator r = new RankingComparator(match);
|
||||
List<String> candidates = new ArrayList<>(search);
|
||||
Collections.sort(candidates, r);
|
||||
// for (String c : candidates) {
|
||||
// double m2 = rankMatch(c, match, 2);
|
||||
// double m3 = rankMatch(c, match, 3);
|
||||
// double m4 = rankMatch(c, match, 4);
|
||||
// double l = levenshteinDistance(match, c);
|
||||
// System.out.println(match + "->" + c + ":" + l + " -- "+ m2 + "," + m3 + "," + "(" + (m2 + m3) + ")");
|
||||
// }
|
||||
// double score = rankMatch(match, candidates.get(0), 2);
|
||||
double score = levenshteinDistance(match, candidates.get(0));
|
||||
if (score > 1) {
|
||||
return candidates.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void printStackTrace() {
|
||||
System.out.println("CURRENT STACK TRACE:");
|
||||
for (StackTraceElement s : Thread.currentThread().getStackTrace()) {
|
||||
System.out.println(s.getClassName() + "." + s.getMethodName() + " (line " + s.getLineNumber() + ") " + (s.isNativeMethod() ? "NATIVE" : ""));
|
||||
}
|
||||
System.out.println("END OF STACK TRACE");
|
||||
}
|
||||
|
||||
public static int parseHexInt(Object s) {
|
||||
if (s == null) {
|
||||
return -1;
|
||||
}
|
||||
if (s instanceof Integer) {
|
||||
return (Integer) s;
|
||||
}
|
||||
String val = String.valueOf(s).trim();
|
||||
int base = 10;
|
||||
if (val.startsWith("$")) {
|
||||
base = 16;
|
||||
val = val.contains(" ") ? val.substring(1, val.indexOf(' ')) : val.substring(1);
|
||||
} else if (val.startsWith("0x")) {
|
||||
base = 16;
|
||||
val = val.contains(" ") ? val.substring(2, val.indexOf(' ')) : val.substring(2);
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(val, base);
|
||||
} catch (NumberFormatException ex) {
|
||||
gripe("This isn't a valid number: " + val + ". If you put a $ in front of that then I'll know you meant it to be a hex number.");
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static void gripe(final String message) {
|
||||
EventQueue.invokeLater(() -> {
|
||||
JOptionPane.showMessageDialog(Emulator.getFrame(), message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
});
|
||||
}
|
||||
|
||||
public static Object findChild(Object object, String fieldName) {
|
||||
if (object instanceof Map) {
|
||||
Map map = (Map) object;
|
||||
for (Object key : map.keySet()) {
|
||||
if (key.toString().equalsIgnoreCase(fieldName)) {
|
||||
return map.get(key);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Field f = object.getClass().getField(fieldName);
|
||||
return f.get(object);
|
||||
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
|
||||
for (Method m : object.getClass().getMethods()) {
|
||||
if (m.getName().equalsIgnoreCase("get" + fieldName) && m.getParameterTypes().length == 0) {
|
||||
try {
|
||||
return m.invoke(object, new Object[0]);
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex1) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object setChild(Object object, String fieldName, String value, boolean hex) {
|
||||
if (object instanceof Map) {
|
||||
Map map = (Map) object;
|
||||
for (Object key : map.entrySet()) {
|
||||
if (key.toString().equalsIgnoreCase(fieldName)) {
|
||||
map.put(key, value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Field f;
|
||||
try {
|
||||
f = object.getClass().getField(fieldName);
|
||||
} catch (NoSuchFieldException ex) {
|
||||
System.out.println("Object type " + object.getClass().getName() + " has no field named " + fieldName);
|
||||
Logger.getLogger(Utility.class.getName()).log(Level.SEVERE, null, ex);
|
||||
return null;
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(Utility.class.getName()).log(Level.SEVERE, null, ex);
|
||||
return null;
|
||||
}
|
||||
Object useValue = deserializeString(value, f.getType(), hex);
|
||||
try {
|
||||
f.set(object, useValue);
|
||||
return useValue;
|
||||
} catch (IllegalArgumentException | IllegalAccessException ex) {
|
||||
for (Method m : object.getClass().getMethods()) {
|
||||
if (m.getName().equalsIgnoreCase("set" + fieldName) && m.getParameterTypes().length == 0) {
|
||||
try {
|
||||
m.invoke(object, useValue);
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex1) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return useValue;
|
||||
}
|
||||
static Map<Class, Map<String, Object>> enumCache = new HashMap<>();
|
||||
|
||||
public static Object findClosestEnumConstant(String value, Class type) {
|
||||
Map<String, Object> enumConstants = enumCache.get(type);
|
||||
if (enumConstants == null) {
|
||||
Object[] constants = type.getEnumConstants();
|
||||
enumConstants = new HashMap<>();
|
||||
for (Object o : constants) {
|
||||
enumConstants.put(o.toString(), o);
|
||||
}
|
||||
enumCache.put(type, enumConstants);
|
||||
}
|
||||
|
||||
String key = findBestMatch(value, enumConstants.keySet());
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
return enumConstants.get(key);
|
||||
}
|
||||
|
||||
public static Object deserializeString(String value, Class type, boolean hex) {
|
||||
int radix = hex ? 16 : 10;
|
||||
if (type.equals(Integer.TYPE) || type == Integer.class) {
|
||||
value = value.replaceAll(hex ? "[^0-9\\-A-Fa-f]" : "[^0-9\\-]", "");
|
||||
try {
|
||||
return Integer.parseInt(value, radix);
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
} else if (type.equals(Short.TYPE) || type == Short.class) {
|
||||
value = value.replaceAll(hex ? "[^0-9\\-\\.A-Fa-f]" : "[^0-9\\-\\.]", "");
|
||||
try {
|
||||
return Short.parseShort(value, radix);
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
} else if (type.equals(Long.TYPE) || type == Long.class) {
|
||||
value = value.replaceAll(hex ? "[^0-9\\-\\.A-Fa-f]" : "[^0-9\\-\\.]", "");
|
||||
try {
|
||||
return Long.parseLong(value, radix);
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
} else if (type.equals(Byte.TYPE) || type == Byte.class) {
|
||||
try {
|
||||
value = value.replaceAll(hex ? "[^0-9\\-A-Fa-f]" : "[^0-9\\-]", "");
|
||||
return Byte.parseByte(value, radix);
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
} else if (type.equals(Boolean.TYPE) || type == Boolean.class) {
|
||||
return Boolean.valueOf(value);
|
||||
} else if (type == File.class) {
|
||||
return new File(String.valueOf(value));
|
||||
} else if (type.isEnum()) {
|
||||
value = value.replaceAll("[\\.\\s\\-]", "");
|
||||
return findClosestEnumConstant(value, type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object getProperty(Object object, String path) {
|
||||
String[] paths = path.split("\\.");
|
||||
for (String path1 : paths) {
|
||||
object = findChild(object, path1);
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
public static Object setProperty(Object object, String path, String value, boolean hex) {
|
||||
String[] paths = path.split("\\.");
|
||||
for (int i = 0; i < paths.length - 1; i++) {
|
||||
object = findChild(object, paths[i]);
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return setChild(object, paths[paths.length - 1], value, hex);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.state.Stateful;
|
||||
import jace.Emulator;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.InvokableAction;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Generic abstraction of a 560x192 video output device which renders 40 columns
|
||||
* per scanline. This also triggers VBL and updates the physical screen.
|
||||
* Subclasses are used to manage actual rendering via ScreenWriter
|
||||
* implementations.
|
||||
* Created on November 10, 2006, 4:29 PM
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
public abstract class Video extends Device {
|
||||
|
||||
@Stateful
|
||||
BufferedImage video;
|
||||
VideoWriter currentWriter;
|
||||
Graphics screen;
|
||||
private byte floatingBus = 0;
|
||||
private int width = 560;
|
||||
private int height = 192;
|
||||
@Stateful
|
||||
public int x = 0;
|
||||
@Stateful
|
||||
public int y = 0;
|
||||
@Stateful
|
||||
public int scannerAddress;
|
||||
@Stateful
|
||||
public int vPeriod = 0;
|
||||
@Stateful
|
||||
public int hPeriod = 0;
|
||||
static final public int CYCLES_PER_LINE = 65;
|
||||
static final public int TOTAL_LINES = 262;
|
||||
static final public int APPLE_CYCLES_PER_LINE = 40;
|
||||
static final public int APPLE_SCREEN_LINES = 192;
|
||||
static final public int HBLANK = CYCLES_PER_LINE - APPLE_CYCLES_PER_LINE;
|
||||
static final public int VBLANK = (TOTAL_LINES - APPLE_SCREEN_LINES) * CYCLES_PER_LINE;
|
||||
static public int[] textOffset;
|
||||
static public int[] hiresOffset;
|
||||
static public int[] textRowLookup;
|
||||
static public int[] hiresRowLookup;
|
||||
private boolean screenDirty;
|
||||
private boolean lineDirty;
|
||||
private boolean isVblank = false;
|
||||
static VideoWriter[][] writerCheck = new VideoWriter[40][192];
|
||||
|
||||
static {
|
||||
textOffset = new int[192];
|
||||
hiresOffset = new int[192];
|
||||
textRowLookup = new int[0x0400];
|
||||
hiresRowLookup = new int[0x02000];
|
||||
for (int i = 0; i < 192; i++) {
|
||||
textOffset[i] = calculateTextOffset(i >> 3);
|
||||
hiresOffset[i] = calculateHiresOffset(i);
|
||||
}
|
||||
for (int i = 0; i < 0x0400; i++) {
|
||||
textRowLookup[i] = identifyTextRow(i);
|
||||
}
|
||||
for (int i = 0; i < 0x2000; i++) {
|
||||
hiresRowLookup[i] = identifyHiresRow(i);
|
||||
}
|
||||
}
|
||||
private int forceRedrawRowCount = 0;
|
||||
Thread updateThread;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Video
|
||||
*/
|
||||
public Video() {
|
||||
suspend();
|
||||
video = new BufferedImage(560, 192, BufferedImage.TYPE_INT_RGB);
|
||||
vPeriod = 0;
|
||||
hPeriod = 0;
|
||||
forceRefresh();
|
||||
}
|
||||
|
||||
public void setWidth(int w) {
|
||||
width = w;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setHeight(int h) {
|
||||
height = h;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setScreen(Graphics g) {
|
||||
screen = g;
|
||||
|
||||
}
|
||||
|
||||
public Graphics getScreen() {
|
||||
return screen;
|
||||
}
|
||||
|
||||
public VideoWriter getCurrentWriter() {
|
||||
return currentWriter;
|
||||
}
|
||||
|
||||
public void setCurrentWriter(VideoWriter currentWriter) {
|
||||
if (this.currentWriter != currentWriter || currentWriter.isMixed()) {
|
||||
this.currentWriter = currentWriter;
|
||||
forceRedrawRowCount = APPLE_SCREEN_LINES + 1;
|
||||
}
|
||||
}
|
||||
@ConfigurableField(category = "video", name = "Min. Screen Refesh", defaultValue = "15", description = "Minimum number of miliseconds to wait before trying to redraw.")
|
||||
public static int MIN_SCREEN_REFRESH = 15;
|
||||
|
||||
public void redraw() {
|
||||
if (screen == null || video == null) {
|
||||
return;
|
||||
}
|
||||
screenDirty = false;
|
||||
screen.drawImage(video, 0, 0, width, height, null);
|
||||
if (Emulator.getFrame() != null) {
|
||||
Emulator.getFrame().repaintIndicators();
|
||||
}
|
||||
}
|
||||
|
||||
public void vblankStart() {
|
||||
if (screenDirty && isRunning()) {
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
|
||||
abstract public void vblankEnd();
|
||||
|
||||
abstract public void hblankStart(BufferedImage screen, int y, boolean isDirty);
|
||||
|
||||
public void setScannerLocation(int loc) {
|
||||
scannerAddress = loc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
setFloatingBus(Computer.getComputer().getMemory().readRaw(scannerAddress + x));
|
||||
if (hPeriod > 0) {
|
||||
hPeriod--;
|
||||
if (hPeriod == 0) {
|
||||
x = -1;
|
||||
setScannerLocation(currentWriter.getYOffset(y));
|
||||
}
|
||||
} else {
|
||||
if (!isVblank) {
|
||||
draw();
|
||||
}
|
||||
if (x >= APPLE_CYCLES_PER_LINE - 1) {
|
||||
int yy = y + hblankOffsetY;
|
||||
if (yy < 0) {
|
||||
yy += APPLE_SCREEN_LINES;
|
||||
}
|
||||
if (yy >= APPLE_SCREEN_LINES) {
|
||||
yy -= (TOTAL_LINES - APPLE_SCREEN_LINES);
|
||||
}
|
||||
setScannerLocation(currentWriter.getYOffset(yy) + hblankOffsetX + (yy < 64 ? 128 : 0));
|
||||
x = -1;
|
||||
if (!isVblank) {
|
||||
if (lineDirty) {
|
||||
screenDirty = true;
|
||||
currentWriter.clearDirty(y);
|
||||
}
|
||||
hblankStart(video, y, lineDirty);
|
||||
lineDirty = false;
|
||||
forceRedrawRowCount--;
|
||||
}
|
||||
hPeriod = HBLANK;
|
||||
y++;
|
||||
if (y >= APPLE_SCREEN_LINES) {
|
||||
if (!isVblank) {
|
||||
y = APPLE_SCREEN_LINES - (TOTAL_LINES - APPLE_SCREEN_LINES);
|
||||
isVblank = true;
|
||||
vblankStart();
|
||||
Motherboard.vblankStart();
|
||||
} else {
|
||||
y = 0;
|
||||
isVblank = false;
|
||||
vblankEnd();
|
||||
Motherboard.vblankEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x++;
|
||||
}
|
||||
|
||||
abstract public void configureVideoMode();
|
||||
|
||||
protected static int byteDoubler(byte b) {
|
||||
int num =
|
||||
// Skip hi-bit because it's not used in display
|
||||
// ((b&0x080)<<7) |
|
||||
((b & 0x040) << 6)
|
||||
| ((b & 0x020) << 5)
|
||||
| ((b & 0x010) << 4)
|
||||
| ((b & 0x08) << 3)
|
||||
| ((b & 0x04) << 2)
|
||||
| ((b & 0x02) << 1)
|
||||
| (b & 0x01);
|
||||
return num | (num << 1);
|
||||
}
|
||||
@ConfigurableField(name = "Waits per cycle", category = "Advanced", description = "Adjust the delay for the scanner")
|
||||
public static int waitsPerCycle = 0;
|
||||
@ConfigurableField(name = "Hblank X offset", category = "Advanced", description = "Adjust where the hblank period starts relative to the start of the line")
|
||||
public static int hblankOffsetX = -29;
|
||||
@ConfigurableField(name = "Hblank Y offset", category = "Advanced", description = "Adjust which line the HBLANK starts on (0=current, 1=next, etc)")
|
||||
public static int hblankOffsetY = 1;
|
||||
|
||||
private void draw() {
|
||||
if (lineDirty || forceRedrawRowCount > 0 || currentWriter.isRowDirty(y)) {
|
||||
lineDirty = true;
|
||||
currentWriter.displayByte(video, x, y, textOffset[y], hiresOffset[y]);
|
||||
}
|
||||
setWaitCycles(waitsPerCycle);
|
||||
doPostDraw();
|
||||
}
|
||||
|
||||
static public int calculateHiresOffset(int y) {
|
||||
return calculateTextOffset(y >> 3) + ((y & 7) << 10);
|
||||
}
|
||||
|
||||
static public int calculateTextOffset(int y) {
|
||||
return ((y & 7) << 7) + 40 * (y >> 3);
|
||||
}
|
||||
|
||||
static public int identifyTextRow(int y) {
|
||||
//floor((x-1024)/128) + floor(((x-1024)%128)/40)*8
|
||||
// Caller must check result is <= 23, if so then they are in a screenhole!
|
||||
return (y >> 7) + (((y & 0x7f) / 40) << 3);
|
||||
}
|
||||
|
||||
static public int identifyHiresRow(int y) {
|
||||
int blockOffset = identifyTextRow(y & 0x03ff);
|
||||
// Caller must check results is > 0, if not then they are in a screenhole!
|
||||
if (blockOffset > 23) {
|
||||
return -1;
|
||||
}
|
||||
return ((y >> 10) & 7) + (blockOffset << 3);
|
||||
}
|
||||
|
||||
public abstract void doPostDraw();
|
||||
|
||||
public byte getFloatingBus() {
|
||||
return floatingBus;
|
||||
}
|
||||
|
||||
private void setFloatingBus(byte floatingBus) {
|
||||
this.floatingBus = floatingBus;
|
||||
}
|
||||
|
||||
@InvokableAction(name = "Refresh screen",
|
||||
category = "display",
|
||||
description = "Marks screen contents as changed, forcing full screen redraw",
|
||||
alternatives = "redraw")
|
||||
public final void forceRefresh() {
|
||||
lineDirty = true;
|
||||
screenDirty = true;
|
||||
forceRedrawRowCount = APPLE_SCREEN_LINES + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "vid";
|
||||
}
|
||||
|
||||
public BufferedImage getFrameBuffer() {
|
||||
return video;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.core;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* VideoWriter is an abstraction of a graphics display mode that knows how to
|
||||
* render a scanline a certain way (lo-res, hi-res, text, etc) over a specific
|
||||
* range of memory (as determined by getYOffset.) Dirty flags are used to mark
|
||||
* scanlines that were altered and require redraw. This is the key to only
|
||||
* updating the screen as needed instead of drawing all the time at the expense
|
||||
* of CPU.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class VideoWriter {
|
||||
|
||||
public abstract void displayByte(BufferedImage screen, int xOffset, int y, int yTextOffset, int yGraphicsOffset);
|
||||
|
||||
// This is used to support composite mixed-mode writers so that we can talk to the writer being used for a scanline
|
||||
public VideoWriter actualWriter() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract int getYOffset(int y);
|
||||
// Dirty flags allow us to know if a scanline has or has not changed
|
||||
// Very useful for knowing if we should bother drawing changes
|
||||
private final boolean[] dirtyFlags = new boolean[192];
|
||||
|
||||
public void markDirty(int y) {
|
||||
actualWriter().dirtyFlags[y] = true;
|
||||
}
|
||||
|
||||
public void clearDirty(int y) {
|
||||
actualWriter().dirtyFlags[y] = false;
|
||||
}
|
||||
|
||||
public boolean isRowDirty(int y) {
|
||||
return actualWriter().dirtyFlags[y];
|
||||
}
|
||||
|
||||
public boolean isMixed() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,637 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.Name;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.PagedMemory;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.state.Stateful;
|
||||
import jace.core.Utility;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
* Apple Mouse interface implementation. This is fully compatible with several
|
||||
* applications.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
@Name("Apple Mouse")
|
||||
public class CardAppleMouse extends Card implements MouseListener {
|
||||
|
||||
@Stateful
|
||||
public int mode;
|
||||
@Stateful
|
||||
public boolean active;
|
||||
@Stateful
|
||||
public boolean interruptOnMove;
|
||||
@Stateful
|
||||
public boolean interruptOnPress;
|
||||
@Stateful
|
||||
public boolean interruptOnVBL;
|
||||
@Stateful
|
||||
public boolean button0press;
|
||||
@Stateful
|
||||
public boolean button1press;
|
||||
@Stateful
|
||||
public boolean button0pressLast;
|
||||
@Stateful
|
||||
public boolean button1pressLast;
|
||||
@Stateful
|
||||
public boolean isInterrupt;
|
||||
@Stateful
|
||||
public boolean isVBL;
|
||||
@Stateful
|
||||
public int statusByte;
|
||||
@Stateful
|
||||
public Point lastMouseLocation;
|
||||
@Stateful
|
||||
public Point clampMin = new Point(0, 0x03ff);
|
||||
@Stateful
|
||||
public Point clampMax = new Point(0, 0x03ff);
|
||||
// By default, update 60 times a second -- roughly every VBL period (in theory)
|
||||
@ConfigurableField(name = "Update frequency", shortName = "updateFreq", category = "Mouse", description = "# of CPU cycles between updates; affects polling and interrupt-based routines")
|
||||
public static int CYCLES_PER_UPDATE = (int) (1020484L / 60L);
|
||||
@ConfigurableField(name = "Fullscreen fix", shortName = "fsfix", category = "Mouse", description = "If the mouse pointer is a little off when in fullscreen, this should fix it.")
|
||||
public boolean fullscreenFix = true;
|
||||
ImageIcon mouseActive = Utility.loadIcon("input-mouse.png");
|
||||
|
||||
@Override
|
||||
public String getDeviceName() {
|
||||
return "Apple Mouse";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
mode = 0;
|
||||
deactivateMouse();
|
||||
}
|
||||
|
||||
/*
|
||||
* Coded against this information
|
||||
* http://stason.org/TULARC/pc/apple2/programmer/012-How-do-I-write-programs-which-use-the-mouse.html
|
||||
*/
|
||||
@Override
|
||||
protected void handleFirmwareAccess(int offset, TYPE type, int value, RAMEvent e) {
|
||||
/*
|
||||
* Screen holes
|
||||
* $0478 + slot Low byte of absolute X position
|
||||
* $04F8 + slot Low byte of absolute Y position
|
||||
* $0578 + slot High byte of absolute X position
|
||||
* $05F8 + slot High byte of absolute Y position
|
||||
* $0678 + slot Reserved and used by the firmware
|
||||
* $06F8 + slot Reserved and used by the firmware
|
||||
* $0778 + slot Button 0/1 interrupt status byte
|
||||
* $07F8 + slot Mode byte
|
||||
*
|
||||
* Interrupt status byte:
|
||||
* Set by READMOUSE
|
||||
* Bit 7 6 5 4 3 2 1 0
|
||||
* | | | | | | | |
|
||||
* | | | | | | | `--- Previously, button 1 was up (0) or down (1)
|
||||
* | | | | | | `----- Movement interrupt
|
||||
* | | | | | `------- Button 0/1 interrupt
|
||||
* | | | | `--------- VBL interrupt
|
||||
* | | | `----------- Currently, button 1 is up (0) or down (1)
|
||||
* | | `------------- X/Y moved since last READMOUSE
|
||||
* | `--------------- Previously, button 0 was up (0) or down (1)
|
||||
* `----------------- Currently, button 0 is up (0) or down (1)
|
||||
*
|
||||
* Mode byte
|
||||
* Valid after calling SERVEMOUSE, cleared with READMOUSE
|
||||
* Bit 7 6 5 4 3 2 1 0
|
||||
* | | | | | | | |
|
||||
* | | | | | | | `--- Mouse off (0) or on (1)
|
||||
* | | | | | | `----- Interrupt if mouse is moved
|
||||
* | | | | | `------- Interrupt if button is pressed
|
||||
* | | | | `--------- Interrupt on VBL
|
||||
* | | | `----------- Reserved
|
||||
* | | `------------- Reserved
|
||||
* | `--------------- Reserved
|
||||
* `----------------- Reserved
|
||||
*/
|
||||
if (type == RAMEvent.TYPE.EXECUTE) {
|
||||
// This means the CPU is calling firmware at this location
|
||||
switch (offset - 0x080) {
|
||||
case 0:
|
||||
setMouse();
|
||||
break;
|
||||
case 1:
|
||||
serveMouse();
|
||||
break;
|
||||
case 2:
|
||||
readMouse();
|
||||
break;
|
||||
case 3:
|
||||
clearMouse();
|
||||
break;
|
||||
case 4:
|
||||
posMouse();
|
||||
break;
|
||||
case 5:
|
||||
clampMouse();
|
||||
break;
|
||||
case 6:
|
||||
homeMouse();
|
||||
break;
|
||||
case 7:
|
||||
initMouse();
|
||||
break;
|
||||
}
|
||||
// Always pass back RTS
|
||||
e.setNewValue(0x060);
|
||||
} else if (type.isRead()) {
|
||||
/* Identification bytes
|
||||
* $Cn05 = $38 $Cn07 = $18 $Cn0B = $01 $Cn0C = $20 $CnFB = $D6
|
||||
*/
|
||||
switch (offset) {
|
||||
case 0x05:
|
||||
e.setNewValue(0x038);
|
||||
break;
|
||||
case 0x07:
|
||||
e.setNewValue(0x018);
|
||||
break;
|
||||
case 0x0B:
|
||||
e.setNewValue(0x01);
|
||||
break;
|
||||
case 0x0C:
|
||||
e.setNewValue(0x020);
|
||||
break;
|
||||
case 0x0FB:
|
||||
e.setNewValue(0x0D6);
|
||||
break;
|
||||
// As per the //gs firmware reference manual
|
||||
case 0x08:
|
||||
// Pascal signature byte
|
||||
e.setNewValue(0x001);
|
||||
case 0x011:
|
||||
e.setNewValue(0x000);
|
||||
break;
|
||||
// Function call offsets
|
||||
case 0x12:
|
||||
e.setNewValue(0x080);
|
||||
break;
|
||||
case 0x13:
|
||||
e.setNewValue(0x081);
|
||||
break;
|
||||
case 0x14:
|
||||
e.setNewValue(0x082);
|
||||
break;
|
||||
case 0x15:
|
||||
e.setNewValue(0x083);
|
||||
break;
|
||||
case 0x16:
|
||||
e.setNewValue(0x084);
|
||||
break;
|
||||
case 0x17:
|
||||
e.setNewValue(0x085);
|
||||
break;
|
||||
case 0x18:
|
||||
e.setNewValue(0x086);
|
||||
break;
|
||||
case 0x19:
|
||||
e.setNewValue(0x087);
|
||||
break;
|
||||
default:
|
||||
e.setNewValue(0x069);
|
||||
}
|
||||
// System.out.println("Read mouse firmware at "+Integer.toHexString(e.getAddress())+" == "+Integer.toHexString(e.getNewValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private MOS65C02 getCPU() {
|
||||
return (MOS65C02) Computer.getComputer().getCpu();
|
||||
}
|
||||
|
||||
/*
|
||||
* $Cn12 SETMOUSE Sets mouse mode
|
||||
* A = mouse operation mode (0-f)
|
||||
* C = 1 if illegal mode requested
|
||||
* mode byte updated
|
||||
*/
|
||||
private void setMouse() {
|
||||
mode = getCPU().A & 0x0ff;
|
||||
if (mode > 0x0f) {
|
||||
getCPU().C = 1;
|
||||
return;
|
||||
} else {
|
||||
getCPU().C = 0;
|
||||
}
|
||||
//Mouse off (0) or on (1)
|
||||
if ((mode & 1) == 0) {
|
||||
deactivateMouse();
|
||||
return;
|
||||
}
|
||||
//Interrupt if mouse is moved
|
||||
interruptOnMove = ((mode & 2) != 0);
|
||||
//Interrupt if button is pressed
|
||||
interruptOnPress = ((mode & 4) != 0);
|
||||
//Interrupt on VBL
|
||||
interruptOnVBL = ((mode & 8) != 0);
|
||||
activateMouse();
|
||||
}
|
||||
|
||||
/*
|
||||
* $Cn13 SERVEMOUSE Services mouse interrupt
|
||||
* Test for interupt and clear mouse interrupt line
|
||||
* Return C=0 if mouse interrupt occurred
|
||||
* Updates screen hole interrupt status bits
|
||||
*/
|
||||
private void serveMouse() {
|
||||
// If any interrupts are registered then
|
||||
updateMouseState();
|
||||
if (isInterrupt) {
|
||||
getCPU().C = 0;
|
||||
} else {
|
||||
getCPU().C = 1;
|
||||
// System.out.println("MOUSE TRIGGERED INTERRUPT!");
|
||||
}
|
||||
// isInterrupt = false;
|
||||
// isVBL=false;
|
||||
}
|
||||
|
||||
/*
|
||||
* $Cn14 READMOUSE Reads mouse position
|
||||
* Reads delta (X/Y) positions, updates abolute X/Y pos
|
||||
* and reads button statuses
|
||||
* Always returns C=0
|
||||
* Interrupt status bits cleared
|
||||
* Screen hole positions for button/movement status bits updated
|
||||
*/
|
||||
private void readMouse() {
|
||||
updateMouseState();
|
||||
isInterrupt = false;
|
||||
isVBL = false;
|
||||
// set screen holes
|
||||
getCPU().C = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* $Cn16 POSMOUSE Sets mouse position to a user-defined pos
|
||||
* Caller puts new position in screenhole
|
||||
* Always returns C=0
|
||||
*/
|
||||
private void posMouse() {
|
||||
// Ignore?
|
||||
getCPU().C = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* $Cn17 CLAMPMOUSE Sets mouse bounds in a window
|
||||
* Sets up clamping window for mouse user
|
||||
* Power up defaults are 0 - 1023 (0 - 3ff)
|
||||
* Caller sets:
|
||||
* A = 0 if setting X, 1 if setting Y
|
||||
* $0478 = low byte of low clamp.
|
||||
* $04F8 = low byte of high clamp.
|
||||
* $0578 = high byte of low clamp.
|
||||
* $05F8 = high byte of high clamp.
|
||||
* //gs homes mouse to low address, but //c and //e do not
|
||||
*/
|
||||
private void clampMouse() {
|
||||
RAM128k memory = (RAM128k) Computer.getComputer().memory;
|
||||
byte clampMinLo = memory.getMainMemory().readByte(0x0478);
|
||||
byte clampMaxLo = memory.getMainMemory().readByte(0x04F8);
|
||||
byte clampMinHi = memory.getMainMemory().readByte(0x0578);
|
||||
byte clampMaxHi = memory.getMainMemory().readByte(0x05F8);
|
||||
int min = (clampMinLo & 0x0ff) | ((clampMinHi << 8) & 0x0FF00);
|
||||
int max = (clampMaxLo & 0x0ff) | ((clampMaxHi << 8) & 0x0FF00);
|
||||
if (getCPU().A == 0) {
|
||||
setClampWindowX(min, max);
|
||||
} else if (getCPU().A == 1) {
|
||||
setClampWindowY(min, max);
|
||||
}
|
||||
// System.out.println("Set mouse clamping to:" + clampMin.toString() + ";" + clampMax.toString());
|
||||
}
|
||||
|
||||
/*
|
||||
* $Cn19 INITMOUSE Resets mouse clamps to default values; sets mouse position to 0,0
|
||||
* Sets screen holes to default values and sets clamping
|
||||
* window to default value (000 - 3ff) for both X and Y
|
||||
* Exit:C=0
|
||||
* Screen holes are updated
|
||||
*/
|
||||
private void initMouse() {
|
||||
mouseActive.setDescription("Active");
|
||||
Emulator.getFrame().addIndicator(this, mouseActive, 2000);
|
||||
setClampWindowX(0, 0x3ff);
|
||||
setClampWindowY(0, 0x3ff);
|
||||
clearMouse();
|
||||
}
|
||||
|
||||
/*
|
||||
* $Cn15 CLEARMOUSE Clears mouse position to 0 (for delta mode)
|
||||
* Resets buttons, movement and interrupt status bits to 0
|
||||
* Intended to be used for delta mouse positioning instead of absolute positioning
|
||||
* Always returns C=0
|
||||
* Interrupt status bits cleared
|
||||
* Screen hole positions for button/movement status bits updated
|
||||
*/
|
||||
private void clearMouse() {
|
||||
isVBL = false;
|
||||
isInterrupt = false;
|
||||
button0press = false;
|
||||
button1press = false;
|
||||
button0pressLast = false;
|
||||
button1pressLast = false;
|
||||
homeMouse();
|
||||
}
|
||||
|
||||
/*
|
||||
* $Cn18 HOMEMOUSE Sets absolute position to upper-left corner of clamping window
|
||||
* Exit: c=0
|
||||
* Screen hole positions are updated
|
||||
*/
|
||||
private void homeMouse() {
|
||||
lastMouseLocation = new Point(0, 0);
|
||||
updateMouseState();
|
||||
getCPU().C = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called whenever the mouse firmware has been activated in software
|
||||
*/
|
||||
private void activateMouse() {
|
||||
active = true;
|
||||
Component drawingArea = Emulator.getScreen();
|
||||
if (drawingArea != null) {
|
||||
drawingArea.addMouseListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called whenever there is a hard reset or when the mouse is turned off
|
||||
*/
|
||||
private void deactivateMouse() {
|
||||
active = false;
|
||||
mode = 0;
|
||||
interruptOnMove = false;
|
||||
interruptOnPress = false;
|
||||
interruptOnVBL = false;
|
||||
Component drawingArea = Emulator.getScreen();
|
||||
if (drawingArea != null) {
|
||||
drawingArea.removeMouseListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// No IO access necessary (is there?)
|
||||
}
|
||||
private int delay = CYCLES_PER_UPDATE;
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
delay--;
|
||||
if (delay > 0) {
|
||||
return;
|
||||
}
|
||||
delay = CYCLES_PER_UPDATE;
|
||||
|
||||
// If interrupts not used, just move on
|
||||
if (!interruptOnMove && !interruptOnPress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (interruptOnPress) {
|
||||
if (button0press != button0pressLast || button1press != button1pressLast) {
|
||||
isInterrupt = true;
|
||||
getCPU().generateInterrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (interruptOnMove) {
|
||||
Point currentMouseLocation = MouseInfo.getPointerInfo().getLocation();
|
||||
if (!currentMouseLocation.equals(lastMouseLocation)) {
|
||||
isInterrupt = true;
|
||||
getCPU().generateInterrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyVBLStateChanged(boolean state) {
|
||||
// VBL is false when it is the vertical blanking period
|
||||
if (!state && interruptOnVBL && active) {
|
||||
isVBL = true;
|
||||
isInterrupt = true;
|
||||
getCPU().generateInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMouseState() {
|
||||
Component drawingArea = Emulator.getScreen();
|
||||
if (drawingArea == null) {
|
||||
return;
|
||||
}
|
||||
Graphics2D screen = (Graphics2D) Computer.getComputer().getVideo().getScreen();
|
||||
// Point currentMouseLocation = MouseInfo.getPointerInfo().getLocation();
|
||||
// Point topLeft = drawingArea.getLocationOnScreen();
|
||||
Point currentMouseLocation = Emulator.getFrame().getContentPane().getMousePosition();
|
||||
if (currentMouseLocation == null) return;
|
||||
// Point topLeft = drawingArea.getLocationOnScreen();
|
||||
Point topLeft = new Point(0,0);
|
||||
|
||||
Dimension d = drawingArea.getBounds().getSize();
|
||||
if (screen.getTransform() != null) {
|
||||
d = new Dimension((int) (screen.getTransform().getScaleX() * d.width),
|
||||
(int) (screen.getTransform().getScaleY() * d.height));
|
||||
topLeft.x += screen.getTransform().getTranslateX();
|
||||
topLeft.y += screen.getTransform().getTranslateY();
|
||||
}
|
||||
if (fullscreenFix) {
|
||||
if (Emulator.getFrame().isFullscreenActive()) {
|
||||
Toolkit t = Toolkit.getDefaultToolkit();
|
||||
topLeft.y -= t.getScreenInsets(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration()).top;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale X and Y to the clamping range of the mouse (will this work for most software?)
|
||||
double width = clampMax.x - clampMin.x;
|
||||
double x = currentMouseLocation.getX() - topLeft.x;
|
||||
x *= width;
|
||||
x /= d.width;
|
||||
x += clampMin.x;
|
||||
if (x < clampMin.x) {
|
||||
x = clampMin.x;
|
||||
}
|
||||
if (x > clampMax.x) {
|
||||
x = clampMax.x;
|
||||
}
|
||||
|
||||
double height = clampMax.y - clampMin.y;
|
||||
double y = currentMouseLocation.getY() - topLeft.y;
|
||||
y *= height;
|
||||
y /= d.height;
|
||||
y += clampMin.y;
|
||||
if (y < clampMin.y) {
|
||||
y = clampMin.y;
|
||||
}
|
||||
if (y > clampMax.y) {
|
||||
y = clampMax.y;
|
||||
}
|
||||
|
||||
PagedMemory m = ((RAM128k) Computer.getComputer().getMemory()).getMainMemory();
|
||||
int s = getSlot();
|
||||
/*
|
||||
* $0478 + slot Low byte of absolute X position
|
||||
* $04F8 + slot Low byte of absolute Y position
|
||||
*/
|
||||
m.writeByte(0x0478 + s, (byte) ((int) x & 0x0ff));
|
||||
m.writeByte(0x04F8 + s, (byte) ((int) y & 0x0ff));
|
||||
/*
|
||||
* $0578 + slot High byte of absolute X position
|
||||
* $05F8 + slot High byte of absolute Y position
|
||||
*/
|
||||
m.writeByte(0x0578 + s, (byte) (((int) x & 0x0ff00) >> 8));
|
||||
m.writeByte(0x05F8 + s, (byte) (((int) y & 0x0ff00) >> 8));
|
||||
/*
|
||||
* $0678 + slot Reserved and used by the firmware
|
||||
* $06F8 + slot Reserved and used by the firmware
|
||||
*
|
||||
* Interrupt status byte:
|
||||
* Set by READMOUSE
|
||||
* Bit 7 6 5 4 3 2 1 0
|
||||
* | | | | | | | |
|
||||
* | | | | | | | `--- Previously, button 1 was up (0) or down (1)
|
||||
* | | | | | | `----- Movement interrupt
|
||||
* | | | | | `------- Button 0/1 interrupt
|
||||
* | | | | `--------- VBL interrupt
|
||||
* | | | `----------- Currently, button 1 is up (0) or down (1)
|
||||
* | | `------------- X/Y moved since last READMOUSE
|
||||
* | `--------------- Previously, button 0 was up (0) or down (1)
|
||||
* `----------------- Currently, button 0 is up (0) or down (1)
|
||||
*/
|
||||
int status = 0;
|
||||
boolean mouseMoved = !currentMouseLocation.equals(lastMouseLocation);
|
||||
if (button1pressLast) {
|
||||
status |= 1;
|
||||
}
|
||||
if (interruptOnMove && mouseMoved) {
|
||||
status |= 2;
|
||||
}
|
||||
if (interruptOnPress && (button0press != button0pressLast || button1press != button1pressLast)) {
|
||||
status |= 4;
|
||||
}
|
||||
if (isVBL) {
|
||||
status |= 8;
|
||||
}
|
||||
if (button1press) {
|
||||
status |= 16;
|
||||
}
|
||||
if (mouseMoved) {
|
||||
status |= 32;
|
||||
}
|
||||
if (button0pressLast) {
|
||||
status |= 64;
|
||||
}
|
||||
if (button0press) {
|
||||
status |= 128;
|
||||
}
|
||||
/*
|
||||
* $0778 + slot Button 0/1 interrupt status byte
|
||||
*/
|
||||
m.writeByte(0x0778 + s, (byte) (status));
|
||||
|
||||
/*
|
||||
* $07F8 + slot Mode byte
|
||||
*/
|
||||
m.writeByte(0x07F8 + s, (byte) (mode));
|
||||
|
||||
lastMouseLocation = currentMouseLocation;
|
||||
button0pressLast = button0press;
|
||||
button1pressLast = button1press;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent me) {
|
||||
int button = me.getButton();
|
||||
if (button == 1 || button == 2) {
|
||||
button0press = true;
|
||||
}
|
||||
if (button == 2 || button == 3) {
|
||||
button1press = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent me) {
|
||||
int button = me.getButton();
|
||||
if (button == 1 || button == 2) {
|
||||
button0press = false;
|
||||
}
|
||||
if (button == 2 || button == 3) {
|
||||
button1press = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent me) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent me) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent me) {
|
||||
}
|
||||
|
||||
private void setClampWindowX(int min, int max) {
|
||||
// Fix for GEOS clamping funkiness
|
||||
if (max == 32767) {
|
||||
max = 560;
|
||||
}
|
||||
clampMin.x = min;
|
||||
clampMax.x = max;
|
||||
}
|
||||
|
||||
private void setClampWindowY(int min, int max) {
|
||||
// Fix for GEOS clamping funkiness
|
||||
if (max == 32767) {
|
||||
max = 192;
|
||||
}
|
||||
clampMin.y = min;
|
||||
clampMax.y = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleC8FirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// Do nothing, there is no need to emulate c8 rom
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.Name;
|
||||
import jace.config.Reconfigurable;
|
||||
import jace.core.Card;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.Utility;
|
||||
import jace.library.MediaConsumer;
|
||||
import jace.library.MediaConsumerParent;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Apple Disk ][ interface implementation. This card represents the interface
|
||||
* side of the Disk ][ controller interface as well as the on-board "boot0" ROM.
|
||||
* The behavior of the actual drive stepping, reading disk images, and so on is
|
||||
* performed by DiskIIDrive and FloppyDisk, respectively. This class only serves
|
||||
* as the I/O interface portion.
|
||||
* Created on April 21, 2007
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Name("Disk ][ Controller")
|
||||
public class CardDiskII extends Card implements Reconfigurable, MediaConsumerParent {
|
||||
|
||||
DiskIIDrive currentDrive;
|
||||
DiskIIDrive drive1 = new DiskIIDrive();
|
||||
DiskIIDrive drive2 = new DiskIIDrive();
|
||||
@ConfigurableField(category = "Disk", defaultValue = "254", name = "Default volume", description = "Value to use for disk volume number")
|
||||
static public int DEFAULT_VOLUME_NUMBER = 0x0FE;
|
||||
@ConfigurableField(category = "Disk", defaultValue = "true", name = "Speed boost", description = "If enabled, emulator will run at max speed during disk access")
|
||||
static public boolean USE_MAX_SPEED = true;
|
||||
|
||||
public CardDiskII() {
|
||||
try {
|
||||
loadRom("jace/data/DiskII.rom");
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardDiskII.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
drive1.setIcon(Utility.loadIcon("disk_ii.png"));
|
||||
drive2.setIcon(Utility.loadIcon("disk_ii.png"));
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeviceName() {
|
||||
return "Disk ][ Controller";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
currentDrive = drive1;
|
||||
drive1.reset();
|
||||
drive2.reset();
|
||||
if (Emulator.getFrame() != null) {
|
||||
Emulator.getFrame().removeIndicators(this);
|
||||
}
|
||||
// Motherboard.cancelSpeedRequest(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
@Override
|
||||
protected void handleIOAccess(int register, RAMEvent.TYPE type, int value, RAMEvent e) {
|
||||
// handle Disk ][ registers
|
||||
switch (register) {
|
||||
case 0x0:
|
||||
case 0x1:
|
||||
case 0x2:
|
||||
case 0x3:
|
||||
case 0x4:
|
||||
case 0x5:
|
||||
case 0x6:
|
||||
case 0x7:
|
||||
currentDrive.step(register);
|
||||
break;
|
||||
|
||||
case 0x8:
|
||||
// drive off
|
||||
currentDrive.setOn(false);
|
||||
// Emulator.getFrame().removeIndicator(this, currentDrive == drive1 ? diskDrive1Icon : diskDrive2Icon, false);
|
||||
break;
|
||||
|
||||
case 0x9:
|
||||
// drive on
|
||||
currentDrive.setOn(true);
|
||||
Emulator.getFrame().addIndicator(this, currentDrive.getIcon());
|
||||
break;
|
||||
|
||||
case 0xA:
|
||||
// drive 1
|
||||
currentDrive = drive1;
|
||||
break;
|
||||
|
||||
case 0xB:
|
||||
// drive 2
|
||||
currentDrive = drive2;
|
||||
break;
|
||||
|
||||
case 0xC:
|
||||
// read/write latch
|
||||
currentDrive.write();
|
||||
e.setNewValue(currentDrive.readLatch());
|
||||
break;
|
||||
|
||||
case 0xF:
|
||||
// write mode
|
||||
currentDrive.setWriteMode();
|
||||
case 0xD:
|
||||
// set latch
|
||||
if (e.getType() == RAMEvent.TYPE.WRITE) {
|
||||
currentDrive.setLatchValue((byte) e.getNewValue());
|
||||
}
|
||||
e.setNewValue(currentDrive.readLatch());
|
||||
break;
|
||||
|
||||
case 0xE:
|
||||
// read mode
|
||||
currentDrive.setReadMode();
|
||||
if (currentDrive.disk != null && currentDrive.disk.writeProtected) {
|
||||
e.setNewValue(0x080);
|
||||
} else
|
||||
{
|
||||
// e.setNewValue((byte) (Math.random() * 256.0));
|
||||
e.setNewValue(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// even addresses return the latch value
|
||||
// if (e.getType() == RAMEvent.TYPE.READ) {
|
||||
// if ((register & 0x1) == 0) {
|
||||
// e.setNewValue(currentDrive.latch);
|
||||
// } else {
|
||||
// // return floating bus value (IIRC)
|
||||
// }
|
||||
// }
|
||||
tweakTiming();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// Do nothing: The ROM does everything
|
||||
return;
|
||||
}
|
||||
|
||||
public void loadRom(String path) throws IOException {
|
||||
InputStream romFile = CardDiskII.class.getClassLoader().getResourceAsStream(path);
|
||||
final int cxRomLength = 0x100;
|
||||
byte[] romData = new byte[cxRomLength];
|
||||
try {
|
||||
if (romFile.read(romData) != cxRomLength) {
|
||||
throw new IOException("Bad Disk ][ ROM size");
|
||||
}
|
||||
getCxRom().loadData(romData);
|
||||
} catch (IOException ex) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// Do nothing (if you want 1mhz timing control, you can do that here...)
|
||||
// drive1.tick();
|
||||
// drive2.tick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
super.reconfigure();
|
||||
}
|
||||
|
||||
private void tweakTiming() {
|
||||
if (drive1.isOn() || drive2.isOn()) {
|
||||
if (USE_MAX_SPEED) {
|
||||
Motherboard.requestSpeed(this);
|
||||
}
|
||||
} else {
|
||||
Motherboard.cancelSpeedRequest(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleC8FirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// There is no special c8 rom for this card
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSlot(int slot) {
|
||||
super.setSlot(slot);
|
||||
drive1.getIcon().setDescription("S" + slot + "D1");
|
||||
drive2.getIcon().setDescription("S" + slot + "D2");
|
||||
}
|
||||
|
||||
public MediaConsumer[] getConsumers() {
|
||||
return new MediaConsumer[] {drive1, drive2};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.core.PagedMemory;
|
||||
import jace.state.Stateful;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
public class CardExt80Col extends RAM128k {
|
||||
@Stateful
|
||||
public PagedMemory auxMemory;
|
||||
@Stateful
|
||||
public PagedMemory auxLanguageCard;
|
||||
@Stateful
|
||||
public PagedMemory auxLanguageCard2;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Extended 80-col card (128kb)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "128kb";
|
||||
}
|
||||
|
||||
public CardExt80Col() {
|
||||
super();
|
||||
auxMemory = new PagedMemory(0xc000, PagedMemory.Type.ram);
|
||||
auxLanguageCard = new PagedMemory(0x3000, PagedMemory.Type.languageCard);
|
||||
auxLanguageCard2 = new PagedMemory(0x1000, PagedMemory.Type.languageCard);
|
||||
initMemoryPattern(auxMemory);
|
||||
}
|
||||
|
||||
// This is redundant here, but necessary for Ramworks
|
||||
@Override
|
||||
public PagedMemory getAuxVideoMemory() {
|
||||
return auxMemory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the auxMemory
|
||||
*/
|
||||
@Override
|
||||
public PagedMemory getAuxMemory() {
|
||||
return auxMemory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the auxLanguageCard
|
||||
*/
|
||||
@Override
|
||||
public PagedMemory getAuxLanguageCard() {
|
||||
return auxLanguageCard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the auxLanguageCard2
|
||||
*/
|
||||
@Override
|
||||
public PagedMemory getAuxLanguageCard2() {
|
||||
return auxLanguageCard2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
// Nothing to do...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
// Nothing to do...
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.config.Name;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Partial Hayes Micromodem II implementation, acting more as a bridge to
|
||||
* provide something similar to the Super Serial support for applications which
|
||||
* do not support the SSC card but do support Hayes, such as DiversiDial.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Name("Hayes Micromodem II")
|
||||
public class CardHayesMicromodem extends CardSSC {
|
||||
|
||||
@Override
|
||||
public String getDeviceName() {
|
||||
return "Hayes Micromodem";
|
||||
}
|
||||
public int RING_INDICATOR_REG = 5;
|
||||
private boolean ringIndicator = false;
|
||||
|
||||
public CardHayesMicromodem() {
|
||||
ACIA_Data = 7;
|
||||
ACIA_Status = 6;
|
||||
ACIA_Control = 5;
|
||||
ACIA_Command = 6;
|
||||
// set these to high values will essentially NO-OP them.
|
||||
SW1 = 255;
|
||||
SW1_SETTING = 255;
|
||||
SW2_CTS = 255;
|
||||
RECV_IRQ_ENABLED = false;
|
||||
TRANS_IRQ_ENABLED = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientConnected() {
|
||||
setRingIndicator(true);
|
||||
super.clientConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientDisconnected() {
|
||||
setRingIndicator(false);
|
||||
super.clientDisconnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
if (register == ACIA_Data) {
|
||||
super.handleIOAccess(register, type, value, e);
|
||||
return;
|
||||
}
|
||||
if (type.isRead() && register == RING_INDICATOR_REG) {
|
||||
e.setNewValue(isRingIndicator() ? 0 : 255);
|
||||
} else if (type.isRead() && register == ACIA_Status) {
|
||||
e.setNewValue(getStatusValue());
|
||||
} else if (type == TYPE.WRITE && register == ACIA_Control) {
|
||||
if ((value & 0x080) == 0) {
|
||||
System.out.println("Software triggered disconnect");
|
||||
try {
|
||||
if (clientSocket != null) {
|
||||
clientSocket.getOutputStream().write("Disconnected by host\n".getBytes());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Client disconnected before host");
|
||||
// If there's an error, ignore it. That means the client disconnected first.
|
||||
}
|
||||
// Hang up
|
||||
hangUp();
|
||||
setRingIndicator(false);
|
||||
} else {
|
||||
System.out.println("Software answered connect request");
|
||||
try {
|
||||
if (clientSocket != null) {
|
||||
clientSocket.getOutputStream().write("Connected to emulated Apple\n".getBytes());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardHayesMicromodem.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
setRingIndicator(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRom(String path) throws IOException {
|
||||
// Do nothing -- there is no rom for this card right now.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ringIndicator
|
||||
*/
|
||||
public boolean isRingIndicator() {
|
||||
return ringIndicator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ringIndicator the ringIndicator to set
|
||||
*/
|
||||
public void setRingIndicator(boolean ringIndicator) {
|
||||
this.ringIndicator = ringIndicator;
|
||||
}
|
||||
|
||||
private int getStatusValue() {
|
||||
int status = 0;
|
||||
try {
|
||||
// 0 = receive register full
|
||||
if (inputAvailable()) {
|
||||
status |= 0x01;
|
||||
}
|
||||
// 1 = transmit register empty -- always :-)
|
||||
status |= 0x02;
|
||||
// 2 = No Carrier
|
||||
if (isRingIndicator() || !isConnected()) {
|
||||
status |= 0x04;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Logger.getLogger(CardHayesMicromodem.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.Name;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.RAMListener;
|
||||
import jace.core.SoundMixer;
|
||||
import static jace.core.Utility.*;
|
||||
import jace.hardware.mockingboard.PSG;
|
||||
import jace.hardware.mockingboard.R6522;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
/**
|
||||
* Mockingboard-C implementation (with partial Phasor support). This uses two
|
||||
* 6522 chips to communicate to two respective AY PSG sound chips. This class
|
||||
* manages the I/O access as well as the sound playback thread.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Name("Mockingboard")
|
||||
public class CardMockingboard extends Card implements Runnable {
|
||||
// If true, emulation will cover 4 AY chips. Otherwise, only 2 AY chips
|
||||
|
||||
static final int[] AY_ADDRESSES = new int[]{0, 0x080, 0x010, 0x090};
|
||||
@ConfigurableField(name = "Volume", shortName = "vol",
|
||||
category = "Sound",
|
||||
description = "Mockingboard volume, 100=max, 0=silent")
|
||||
public int volume = 100;
|
||||
static public int MAX_AMPLITUDE = 0x007fff;
|
||||
@ConfigurableField(name = "Phasor mode",
|
||||
category = "Sound",
|
||||
description = "If enabled, card will have 4 sound chips instead of 2")
|
||||
public boolean phasorMode = false;
|
||||
@ConfigurableField(name = "Clock Rate (hz)",
|
||||
category = "Sound",
|
||||
defaultValue = "1020484",
|
||||
description = "Clock rate of AY oscillators")
|
||||
public int CLOCK_SPEED = 1020484;
|
||||
public int SAMPLE_RATE = 48000;
|
||||
@ConfigurableField(name = "Buffer size",
|
||||
category = "Sound",
|
||||
description = "Number of samples to generate on each pass")
|
||||
public int BUFFER_LENGTH = 64;
|
||||
// The array of configured AY chips
|
||||
public PSG[] chips;
|
||||
// The 6522 controllr chips (always 2)
|
||||
public R6522[] controllers;
|
||||
private int ticksBeteenPlayback = 5000;
|
||||
Lock timerSync = new ReentrantLock();
|
||||
Condition cpuCountReached = timerSync.newCondition();
|
||||
Condition playbackFinished = timerSync.newCondition();
|
||||
@ConfigurableField(name = "Idle sample threshold", description = "Number of samples to wait before suspending sound")
|
||||
private int MAX_IDLE_SAMPLES = SAMPLE_RATE;
|
||||
|
||||
@Override
|
||||
public String getDeviceName() {
|
||||
return "Mockingboard";
|
||||
}
|
||||
|
||||
public CardMockingboard() {
|
||||
controllers = new R6522[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
//don't ask...
|
||||
final int j = i;
|
||||
controllers[i] = new R6522() {
|
||||
@Override
|
||||
public void sendOutputA(int value) {
|
||||
if (activeChip != null) {
|
||||
activeChip.setBus(value);
|
||||
} else {
|
||||
System.out.println("No active AY chip!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendOutputB(int value) {
|
||||
if (activeChip != null) {
|
||||
activeChip.setControl(value & 0x07);
|
||||
} else {
|
||||
System.out.println("No active AY chip!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int receiveOutputA() {
|
||||
return activeChip == null ? 0 : activeChip.bus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int receiveOutputB() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "timer" + j;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
// Reset PSG registers
|
||||
suspend();
|
||||
if (chips != null) {
|
||||
for (PSG psg : chips) {
|
||||
psg.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
RAMListener mainListener = null;
|
||||
PSG activeChip = null;
|
||||
|
||||
@Override
|
||||
protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// System.out.println(e.getType().toString() + " event to mockingboard register "+Integer.toHexString(register)+", value "+e.getNewValue());
|
||||
activeChip = null;
|
||||
resume();
|
||||
int chip = 0;
|
||||
for (PSG psg : chips) {
|
||||
if (psg.getBaseReg() == (register & 0x0f0)) {
|
||||
activeChip = psg;
|
||||
break;
|
||||
}
|
||||
chip++;
|
||||
}
|
||||
if (activeChip == null) {
|
||||
System.err.println("Could not determine which PSG to communicate to");
|
||||
e.setNewValue(Computer.getComputer().getVideo().getFloatingBus());
|
||||
return;
|
||||
}
|
||||
R6522 controller = controllers[chip & 1];
|
||||
if (e.getType().isRead()) {
|
||||
int val = controller.readRegister(register & 0x0f);
|
||||
// System.out.println("Register returns "+Integer.toHexString(val));
|
||||
e.setNewValue(val);
|
||||
} else {
|
||||
controller.writeRegister(register & 0x0f, e.getNewValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// Oddly, all IO is done at the firmware address bank. It's a strange card.
|
||||
e.setNewValue(Computer.getComputer().getVideo().getFloatingBus());
|
||||
}
|
||||
long ticksSinceLastPlayback = 0;
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
for (R6522 c : controllers) {
|
||||
if (c == null || !c.isRunning()) {
|
||||
continue;
|
||||
}
|
||||
c.tick();
|
||||
}
|
||||
|
||||
if (isRunning() && !pause) {
|
||||
timerSync.lock();
|
||||
try {
|
||||
ticksSinceLastPlayback++;
|
||||
if (ticksSinceLastPlayback >= ticksBeteenPlayback) {
|
||||
cpuCountReached.signalAll();
|
||||
while (isRunning() && ticksSinceLastPlayback >= ticksBeteenPlayback) {
|
||||
if (!playbackFinished.await(1, TimeUnit.SECONDS)) {
|
||||
gripe("The mockingboard playback thread has stalled. Disabling mockingboard.");
|
||||
suspend();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
suspend();
|
||||
// Do nothing, probably suspending CPU
|
||||
} finally {
|
||||
timerSync.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
boolean restart = suspend();
|
||||
initPSG();
|
||||
for (PSG chip : chips) {
|
||||
chip.setRate(CLOCK_SPEED, SAMPLE_RATE);
|
||||
chip.reset();
|
||||
}
|
||||
super.reconfigure();
|
||||
if (restart) {
|
||||
resume();
|
||||
}
|
||||
}
|
||||
///////////////////////////////////////////////////////////
|
||||
public static int[] VolTable;
|
||||
int[][] buffers;
|
||||
int bufferLength = -1;
|
||||
|
||||
public void playSound(int[] left, int[] right) {
|
||||
chips[0].update(left, true, left, false, left, false, BUFFER_LENGTH);
|
||||
chips[1].update(right, true, right, false, right, false, BUFFER_LENGTH);
|
||||
if (phasorMode) {
|
||||
chips[2].update(left, false, left, false, left, false, BUFFER_LENGTH);
|
||||
chips[3].update(right, false, right, false, right, false, BUFFER_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
public void buildMixerTable() {
|
||||
VolTable = new int[16];
|
||||
int numChips = phasorMode ? 4 : 2;
|
||||
|
||||
/* calculate the volume->voltage conversion table */
|
||||
/* The AY-3-8910 has 16 levels, in a logarithmic scale (3dB per step) */
|
||||
/* The YM2149 still has 16 levels for the tone generators, but 32 for */
|
||||
/* the envelope generator (1.5dB per step). */
|
||||
double out = ((double) MAX_AMPLITUDE * (double) volume) / 100.0;
|
||||
// Reduce max amplitude to reflect post-mixer values so we don't have to scale volume when mixing channels
|
||||
out = out * 2.0 / 3.0 / numChips;
|
||||
double delta = 1.15;
|
||||
for (int i = 15; i > 0; i--) {
|
||||
VolTable[i] = (int) Math.round(out); /* round to nearest */ // [TC: unsigned int cast]
|
||||
// out /= 1.188502227; /* = 10 ^ (1.5/20) = 1.5dB */
|
||||
// out /= 1.15; /* = 10 ^ (3/20) = 3dB */
|
||||
delta += 0.0225;
|
||||
out /= delta; // As per applewin's source, the levels don't scale as documented.
|
||||
}
|
||||
VolTable[0] = 0;
|
||||
}
|
||||
Thread playbackThread = null;
|
||||
boolean pause = false;
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
pause = false;
|
||||
if (!isRunning()) {
|
||||
if (chips == null) {
|
||||
initPSG();
|
||||
}
|
||||
for (R6522 controller : controllers) {
|
||||
controller.attach();
|
||||
controller.resume();
|
||||
}
|
||||
}
|
||||
super.resume();
|
||||
if (playbackThread == null || !playbackThread.isAlive()) {
|
||||
playbackThread = new Thread(this, "Mockingboard sound playback");
|
||||
playbackThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean suspend() {
|
||||
super.suspend();
|
||||
for (R6522 controller : controllers) {
|
||||
controller.suspend();
|
||||
controller.detach();
|
||||
}
|
||||
if (playbackThread == null || !playbackThread.isAlive()) {
|
||||
return false;
|
||||
}
|
||||
if (playbackThread != null) {
|
||||
playbackThread.interrupt();
|
||||
try {
|
||||
// Wait for thread to die
|
||||
playbackThread.join();
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
playbackThread = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* This is the audio playback thread
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
SourceDataLine out = Motherboard.mixer.getLine(this);
|
||||
int[] leftBuffer = new int[BUFFER_LENGTH];
|
||||
int[] rightBuffer = new int[BUFFER_LENGTH];
|
||||
int frameSize = out.getFormat().getFrameSize();
|
||||
byte[] buffer = new byte[BUFFER_LENGTH * frameSize];
|
||||
System.out.println("Mockingboard playback started");
|
||||
int bytesPerSample = frameSize / 2;
|
||||
buildMixerTable();
|
||||
ticksBeteenPlayback = (int) ((Motherboard.SPEED * BUFFER_LENGTH) / SAMPLE_RATE);
|
||||
ticksSinceLastPlayback = 0;
|
||||
int zeroSamples = 0;
|
||||
while (isRunning()) {
|
||||
Motherboard.requestSpeed(this);
|
||||
playSound(leftBuffer, rightBuffer);
|
||||
int p = 0;
|
||||
for (int idx = 0; idx < BUFFER_LENGTH; idx++) {
|
||||
int sampleL = leftBuffer[idx];
|
||||
int sampleR = rightBuffer[idx];
|
||||
// Convert left + right samples into buffer format
|
||||
if (sampleL == 0 && sampleR == 0) {
|
||||
zeroSamples++;
|
||||
} else {
|
||||
zeroSamples = 0;
|
||||
}
|
||||
for (int shift = SoundMixer.BITS - 8, index = 0; shift >= 0; shift -= 8, index++) {
|
||||
buffer[p + index] = (byte) (sampleR >> shift);
|
||||
buffer[p + index + bytesPerSample] = (byte) (sampleL >> shift);
|
||||
}
|
||||
p += frameSize;
|
||||
}
|
||||
try {
|
||||
timerSync.lock();
|
||||
ticksSinceLastPlayback -= ticksBeteenPlayback;
|
||||
} finally {
|
||||
timerSync.unlock();
|
||||
}
|
||||
out.write(buffer, 0, buffer.length);
|
||||
if (zeroSamples >= MAX_IDLE_SAMPLES) {
|
||||
zeroSamples = 0;
|
||||
pause = true;
|
||||
Motherboard.cancelSpeedRequest(this);
|
||||
while (pause && isRunning()) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
timerSync.lock();
|
||||
playbackFinished.signalAll();
|
||||
} catch (InterruptedException ex) {
|
||||
return;
|
||||
} catch (IllegalMonitorStateException ex) {
|
||||
// Do nothing
|
||||
} finally {
|
||||
try {
|
||||
timerSync.unlock();
|
||||
} catch (IllegalMonitorStateException ex) {
|
||||
// Do nothing -- this is probably caused by a suspension event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
timerSync.lock();
|
||||
playbackFinished.signalAll();
|
||||
while (isRunning() && ticksSinceLastPlayback < ticksBeteenPlayback) {
|
||||
cpuCountReached.await();
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
// Do nothing, probably killing playback thread on purpose
|
||||
} finally {
|
||||
timerSync.unlock();
|
||||
}
|
||||
}
|
||||
} catch (LineUnavailableException ex) {
|
||||
Logger.getLogger(CardMockingboard.class
|
||||
.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
Motherboard.cancelSpeedRequest(this);
|
||||
System.out.println("Mockingboard playback stopped");
|
||||
Motherboard.mixer.returnLine(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void initPSG() {
|
||||
int max = phasorMode ? 4 : 2;
|
||||
chips = new PSG[max];
|
||||
for (int i = 0; i < max; i++) {
|
||||
chips[i] = new PSG(AY_ADDRESSES[i], CLOCK_SPEED, SAMPLE_RATE, "AY" + i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleC8FirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// There is no c8 rom access to emulate
|
||||
}
|
||||
|
||||
// This fixes freezes when resizing the window, etc.
|
||||
@Override
|
||||
public boolean suspendWithCPU() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.Name;
|
||||
import jace.core.Card;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.state.Stateful;
|
||||
import jace.core.Utility;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
* This card strives to be a clone of the Applied Engineering RamFactor card
|
||||
* http://www.downloads.reactivemicro.com/Public/Apple%20II%20Items/Hardware/RAMFactor/RAMFactor%20v1.5.pdf
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
@Name("RamFactor")
|
||||
public class CardRamFactor extends Card {
|
||||
int ADDR1 = 0;
|
||||
int ADDR2 = 1;
|
||||
int ADDR3 = 2;
|
||||
int DATA = 3;
|
||||
int BANK_SELECT = 0x0f;
|
||||
@ConfigurableField(category = "memory", name = "Ram size", description = "Size of card ram in KB", shortName = "size", defaultValue = "8192")
|
||||
public int RAM_SIZE = 8192;
|
||||
int actualSize = RAM_SIZE * 1024;
|
||||
// Important: pointer of current ram read/write for slinky access
|
||||
@Stateful
|
||||
int addressPointer = 0x0ffffff;
|
||||
@Stateful
|
||||
int firmwareBank = 0;
|
||||
@ConfigurableField(category = "performance", name = "Speed Boost", description = "Boost emulator speed when RAM in use", shortName = "boostSpeed", defaultValue = "false")
|
||||
public boolean speedBoost = false;
|
||||
|
||||
@Override
|
||||
public String getDeviceName() {
|
||||
return "RamFactor";
|
||||
}
|
||||
ImageIcon indicator;
|
||||
public CardRamFactor() {
|
||||
indicator=Utility.loadIcon("ram.png");
|
||||
try {
|
||||
loadRom("jace/data/RAMFactor14.rom");
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardRamFactor.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
allocateMemory(actualSize);
|
||||
updateFirmwareMemory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
firmwareBank = 0;
|
||||
updateFirmwareMemory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
actualSize = RAM_SIZE * 1024;
|
||||
allocateMemory(actualSize);
|
||||
updateFirmwareMemory();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
Emulator.getFrame().addIndicator(this, indicator);
|
||||
value &= 0x0ff;
|
||||
switch (register) {
|
||||
case 0:
|
||||
case 4:
|
||||
// Lo-byte of pointer
|
||||
if (type.isRead()) {
|
||||
e.setNewValue(addressPointer & 0x0ff);
|
||||
} else {
|
||||
addressPointer = (addressPointer & 0x0ffff00) | value;
|
||||
if (RAM_SIZE <= 1024) {
|
||||
addressPointer |= 0x0f00000;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
case 5:
|
||||
// Mid-byte of pointer
|
||||
if (type.isRead()) {
|
||||
e.setNewValue((addressPointer >> 8) & 0x0ff);
|
||||
} else {
|
||||
addressPointer = (addressPointer & 0x0ff00ff) | (value << 8);
|
||||
if (RAM_SIZE <= 1024) {
|
||||
addressPointer |= 0x0f00000;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
case 6:
|
||||
// Hi-byte of pointer
|
||||
if (type.isRead()) {
|
||||
if (RAM_SIZE <= 1024) {
|
||||
e.setNewValue(0x0f0 | ((addressPointer >> 16) & 0x0ff));
|
||||
} else {
|
||||
e.setNewValue((addressPointer >> 16) & 0x0ff);
|
||||
}
|
||||
} else {
|
||||
addressPointer = (addressPointer & 0x00ffff) | (value << 16);
|
||||
if (RAM_SIZE <= 1024) {
|
||||
addressPointer |= 0x0f00000;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
case 7:
|
||||
if (type.isRead()) {
|
||||
e.setNewValue(readMemory(addressPointer));
|
||||
} else {
|
||||
writeMemory(addressPointer, (byte) value);
|
||||
}
|
||||
addressPointer++;
|
||||
// Keep the pointer in range
|
||||
addressPointer &= 0x0ffffff;
|
||||
break;
|
||||
case 15: {
|
||||
// Firmware bank select
|
||||
if (type == TYPE.WRITE) {
|
||||
firmwareBank = value;
|
||||
updateFirmwareMemory();
|
||||
}
|
||||
}
|
||||
default:
|
||||
if (type.isRead()) {
|
||||
e.setNewValue(0x0ff);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
if (speedBoost) {
|
||||
Motherboard.requestSpeed(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleC8FirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
if (speedBoost) {
|
||||
Motherboard.requestSpeed(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Stateful
|
||||
public byte[] cardRam;
|
||||
int ADDRESS_MASK = 0x07FFFFF;
|
||||
private byte readMemory(int i) {
|
||||
while (i >= cardRam.length) {
|
||||
i -= cardRam.length;
|
||||
}
|
||||
return cardRam[i];
|
||||
}
|
||||
|
||||
private void writeMemory(int i, byte newValue) {
|
||||
while (i >= cardRam.length) {
|
||||
i -= cardRam.length;
|
||||
}
|
||||
cardRam[i] = newValue;
|
||||
}
|
||||
|
||||
private void allocateMemory(int size) {
|
||||
if (cardRam != null && cardRam.length == size) return;
|
||||
cardRam = new byte[size];
|
||||
Arrays.fill(cardRam, (byte) 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSlot(int slot) {
|
||||
super.setSlot(slot);
|
||||
indicator.setDescription("Slot "+getSlot());
|
||||
// Rom has different images for each slot
|
||||
updateFirmwareMemory();
|
||||
}
|
||||
|
||||
final int cxRomLength = 0x02000;
|
||||
byte[] romData = new byte[cxRomLength];
|
||||
public void loadRom(String path) throws IOException {
|
||||
InputStream romFile = CardRamFactor.class.getClassLoader().getResourceAsStream(path);
|
||||
try {
|
||||
if (romFile.read(romData) != cxRomLength) {
|
||||
throw new IOException("Bad RamFactor rom size");
|
||||
}
|
||||
updateFirmwareMemory();
|
||||
} catch (IOException ex) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFirmwareMemory() {
|
||||
int romOffset = 0;
|
||||
if ((firmwareBank&1) == 1) {
|
||||
romOffset = 0x01000;
|
||||
}
|
||||
getCxRom().loadData(romData, romOffset + getSlot()*0x0100, 256);
|
||||
getC8Rom().loadData(romData, romOffset + 0x0800, 0x0800);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.Name;
|
||||
import jace.core.Computer;
|
||||
import jace.core.PagedMemory;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.state.Stateful;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Emulates the Ramworks Basic and Ramworks III cards
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
@Name("Ramworks III Memory Expansion")
|
||||
public class CardRamworks extends RAM128k {
|
||||
public static int BANK_SELECT = 0x0c073;
|
||||
@Stateful
|
||||
public int currentBank = 0;
|
||||
@Stateful
|
||||
public List<Map<BankType, PagedMemory>> memory;
|
||||
public Map<BankType, PagedMemory> nullBank = generateBank();
|
||||
@ConfigurableField(
|
||||
category = "memory",
|
||||
defaultValue = "3072",
|
||||
name = "Memory Size",
|
||||
description = "Size in KB. Should be a multiple of 64 and not exceed 8192. The real card cannot support more than 3072k")
|
||||
public int memorySize = 3072;
|
||||
public int maxBank = memorySize / 64;
|
||||
private Map<BankType, PagedMemory> generateBank() {
|
||||
Map<BankType, PagedMemory> memoryBank = new EnumMap<BankType, PagedMemory>(BankType.class);
|
||||
memoryBank.put(BankType.MAIN_MEMORY, new PagedMemory(0xc000, PagedMemory.Type.ram));
|
||||
memoryBank.put(BankType.LANGUAGE_CARD_1, new PagedMemory(0x3000, PagedMemory.Type.languageCard));
|
||||
memoryBank.put(BankType.LANGUAGE_CARD_2, new PagedMemory(0x1000, PagedMemory.Type.languageCard));
|
||||
return memoryBank;
|
||||
}
|
||||
|
||||
public static enum BankType {
|
||||
MAIN_MEMORY, LANGUAGE_CARD_1, LANGUAGE_CARD_2
|
||||
};
|
||||
|
||||
public CardRamworks() {
|
||||
super();
|
||||
memory = new ArrayList<Map<BankType, PagedMemory>>(maxBank);
|
||||
reconfigure();
|
||||
}
|
||||
|
||||
private PagedMemory getAuxBank(BankType type, int bank) {
|
||||
if (bank >= maxBank) {
|
||||
return nullBank.get(type);
|
||||
}
|
||||
Map<BankType, PagedMemory> memoryBank = memory.get(bank);
|
||||
if (memoryBank == null) {
|
||||
memoryBank = generateBank();
|
||||
memory.set(bank, memoryBank);
|
||||
}
|
||||
return memoryBank.get(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedMemory getAuxVideoMemory() {
|
||||
return getAuxBank(BankType.MAIN_MEMORY, 0);
|
||||
}
|
||||
|
||||
PagedMemory lastAux = null;
|
||||
@Override
|
||||
public PagedMemory getAuxMemory() {
|
||||
return getAuxBank(BankType.MAIN_MEMORY, currentBank);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedMemory getAuxLanguageCard() {
|
||||
return getAuxBank(BankType.LANGUAGE_CARD_1, currentBank);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedMemory getAuxLanguageCard2() {
|
||||
return getAuxBank(BankType.LANGUAGE_CARD_2, currentBank);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Ramworks III";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "Ramworks3";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
boolean resume = Computer.pause();
|
||||
maxBank = memorySize / 64;
|
||||
if (maxBank < 1) maxBank = 1;
|
||||
if (maxBank > 128) maxBank = 128;
|
||||
for (int i = memory.size(); i < maxBank; i++) {
|
||||
memory.add(null);
|
||||
}
|
||||
configureActiveMemory();
|
||||
if (resume) {
|
||||
Computer.resume();
|
||||
}
|
||||
}
|
||||
RAMListener bankSelectListener = new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(BANK_SELECT);
|
||||
setScopeEnd(BANK_SELECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
currentBank = e.getNewValue();
|
||||
configureActiveMemory();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
addListener(bankSelectListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
removeListener(bankSelectListener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,490 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.Name;
|
||||
import jace.config.Reconfigurable;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.Utility;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
* Super Serial Card with serial-over-tcp/ip support. This is fully compatible
|
||||
* with the SSC ROM and supported applications.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Name("Super Serial Card")
|
||||
public class CardSSC extends Card implements Reconfigurable, Runnable {
|
||||
|
||||
@ConfigurableField(name = "TCP/IP Port", shortName = "port")
|
||||
public static short IP_PORT = 1977;
|
||||
protected ServerSocket socket;
|
||||
protected Socket clientSocket;
|
||||
protected Thread listenThread;
|
||||
private int lastInputByte = 0;
|
||||
private boolean FULL_ECHO = true;
|
||||
private boolean RECV_ACTIVE = true;
|
||||
private boolean TRANS_ACTIVE = true;
|
||||
// private boolean RECV_STRIP_LF = true;
|
||||
// private boolean TRANS_ADD_LF = true;
|
||||
@ConfigurableField(name = "Strip LF (recv)", shortName = "stripLF", defaultValue = "false", description = "Strip incoming linefeeds")
|
||||
public boolean RECV_STRIP_LF = false;
|
||||
@ConfigurableField(name = "Add LF (send)", shortName = "addLF", defaultValue = "false", description = "Append linefeeds after outgoing carriage returns")
|
||||
public boolean TRANS_ADD_LF = false;
|
||||
private boolean DTR = true;
|
||||
public int SW1 = 0x01; // Read = Jumper block SW1
|
||||
//Bit 0 = !SW1-6
|
||||
//Bit 1 = !SW1-5
|
||||
//Bit 4 = !SW1-4
|
||||
//Bit 5 = !SW1-3
|
||||
//Bit 6 = !SW1-2
|
||||
//Bit 7 = !SW1-1
|
||||
// 19200 baud (SW1-1,2,3,4 off)
|
||||
// Communications mode (SW1-5,6 on)
|
||||
public int SW1_SETTING = 0x0F0;
|
||||
public int SW2_CTS = 0x02; // Read = Jumper block SW2 and CTS
|
||||
//Bit 0 = !CTS
|
||||
//SW2-6 = Allow interrupts (disable in ][, ][+)
|
||||
//Bit 1 = !SW2-5 -- Generate LF after CR
|
||||
//Bit 2 = !SW2-4
|
||||
//Bit 3 = !SW2-3
|
||||
//Bit 5 = !SW2-2
|
||||
//Bit 7 = !SW2-1
|
||||
// 1 stop bit (SW2-1 on)
|
||||
// 8 data bits (SW2-2 on)
|
||||
// No parity (SW2-3 don't care, SW2-4 off)
|
||||
private static int SW2_SETTING = 0x04;
|
||||
public int ACIA_Data = 0x08; // Read=Receive / Write=transmit
|
||||
public int ACIA_Status = 0x09; // Read=Status / Write=Reset
|
||||
public int ACIA_Command = 0x0A;
|
||||
public int ACIA_Control = 0x0B;
|
||||
public boolean PORT_CONNECTED = false;
|
||||
public boolean RECV_IRQ_ENABLED = false;
|
||||
public boolean TRANS_IRQ_ENABLED = false;
|
||||
public boolean IRQ_TRIGGERED = false;
|
||||
// Bitmask for stop bits (FF = 8, 7F = 7, etc)
|
||||
private int DATA_BITS = 0x07F;
|
||||
|
||||
public String getDeviceName() {
|
||||
return "Super Serial Card";
|
||||
}
|
||||
ImageIcon activityIndicator;
|
||||
|
||||
@Override
|
||||
public void setSlot(int slot) {
|
||||
try {
|
||||
loadRom("jace/data/SSC.rom");
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardSSC.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
super.setSlot(slot);
|
||||
activityIndicator = Utility.loadIcon("network-wired.png");
|
||||
activityIndicator.setDescription("Slot " + slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (socket != null && !socket.isClosed()) {
|
||||
try {
|
||||
Logger.getLogger(CardSSC.class.getName()).log(Level.INFO, "Slot " + getSlot() + " listening on port " + IP_PORT, (Throwable) null);
|
||||
// System.out.println("Waiting for connect");
|
||||
while ((clientSocket = socket.accept()) != null) {
|
||||
clientConnected();
|
||||
clientSocket.setTcpNoDelay(true);
|
||||
while (isConnected()) {
|
||||
try {
|
||||
Thread.sleep(livenessCheck / 2);
|
||||
} catch (InterruptedException ex) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
clientDisconnected();
|
||||
hangUp();
|
||||
}
|
||||
} catch (SocketTimeoutException ex) {
|
||||
// Do nothing
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardSSC.class.getName()).log(Level.FINE, null, ex);
|
||||
}
|
||||
}
|
||||
socket = null;
|
||||
}
|
||||
|
||||
// Called when a client first connects via telnet
|
||||
public void clientConnected() {
|
||||
System.err.println("Client connected");
|
||||
}
|
||||
|
||||
// Called when a client disconnects
|
||||
public void clientDisconnected() {
|
||||
System.out.println("Client disconnected");
|
||||
}
|
||||
|
||||
public void loadRom(String path) throws IOException {
|
||||
// Load rom file, first 0x0700 bytes are C8 rom, last 0x0100 bytes are CX rom
|
||||
// CF00-CFFF are unused by the SSC
|
||||
InputStream romFile = CardSSC.class.getClassLoader().getResourceAsStream(path);
|
||||
final int cxRomLength = 0x0100;
|
||||
final int c8RomLength = 0x0700;
|
||||
byte[] romxData = new byte[cxRomLength];
|
||||
byte[] rom8Data = new byte[c8RomLength];
|
||||
try {
|
||||
if (romFile.read(rom8Data) != c8RomLength) {
|
||||
throw new IOException("Bad SSC rom size");
|
||||
}
|
||||
getC8Rom().loadData(rom8Data);
|
||||
if (romFile.read(romxData) != cxRomLength) {
|
||||
throw new IOException("Bad SSC rom size");
|
||||
}
|
||||
getCxRom().loadData(romxData);
|
||||
} catch (IOException ex) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
suspend();
|
||||
resume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
try {
|
||||
int newValue = -1;
|
||||
switch (type) {
|
||||
case EXECUTE:
|
||||
case READ_OPERAND:
|
||||
case READ_DATA:
|
||||
case READ:
|
||||
if (register == SW1) {
|
||||
newValue = SW1_SETTING;
|
||||
}
|
||||
if (register == SW2_CTS) {
|
||||
newValue = SW2_SETTING & 0x0FE;
|
||||
// if port is connected and ready to send another byte, set CTS bit on
|
||||
newValue |= (PORT_CONNECTED && inputAvailable()) ? 0x00 : 0x01;
|
||||
}
|
||||
if (register == ACIA_Data) {
|
||||
Emulator.getFrame().addIndicator(this, activityIndicator);
|
||||
newValue = getInputByte();
|
||||
if (RECV_IRQ_ENABLED) {
|
||||
triggerIRQ();
|
||||
}
|
||||
}
|
||||
if (register == ACIA_Status) {
|
||||
newValue = 0;
|
||||
// 0 = Parity error (1)
|
||||
// 1 = Framing error (1)
|
||||
// 2 = Overrun error (1)
|
||||
// 3 = ACIA Receive Register full (1)
|
||||
if (inputAvailable()) {
|
||||
newValue |= 0x08;
|
||||
}
|
||||
// 4 = ACIA Transmit Register empty (1)
|
||||
if (true) {
|
||||
newValue |= 0x010;
|
||||
}
|
||||
// 5 = Data Carrier Detect (DCD) true (0)
|
||||
// 6 = Data Set Ready (DSR) true (0)
|
||||
// 7 = Interrupt (IRQ) has occurred
|
||||
if (IRQ_TRIGGERED) {
|
||||
newValue |= 0x080;
|
||||
}
|
||||
IRQ_TRIGGERED = false;
|
||||
}
|
||||
if (register == ACIA_Command) {
|
||||
newValue = 0;
|
||||
// 0 = DTR Enable (1) / Disable (0) receiver and IRQ
|
||||
// 1 = Allow IRQ (1) when status bit 3 is true
|
||||
if (RECV_IRQ_ENABLED) {
|
||||
newValue |= 2;
|
||||
}
|
||||
// 2,3 = Control transmit IRQ, RTS level and transmitter
|
||||
newValue |= 12;
|
||||
// 4 = Normal mode 0, or Echo mode 1 (bits 2 and 3 must be 0)
|
||||
if (FULL_ECHO) {
|
||||
newValue |= 16;
|
||||
}
|
||||
// 5 = Control parity
|
||||
}
|
||||
if (register == ACIA_Control) {
|
||||
// 0-3 = Baud Rate
|
||||
// 4 = Use baud rate generator (1) / Use external clock (0)
|
||||
// 5-6 = Number of data bits (00 = 8, 10 = 7, 01 = 6, 11 = 5)
|
||||
// 7 = Number of stop bits (0 = 1 stop bit, 1 = 1-1/2 (with 5 data bits no parity), 1 (8 data plus parity) or 2)
|
||||
newValue = 0;
|
||||
}
|
||||
break;
|
||||
case WRITE:
|
||||
if (register == ACIA_Data) {
|
||||
Emulator.getFrame().addIndicator(this, activityIndicator);
|
||||
sendOutputByte(value & 0x0FF);
|
||||
if (TRANS_IRQ_ENABLED) {
|
||||
triggerIRQ();
|
||||
}
|
||||
}
|
||||
if (register == ACIA_Command) {
|
||||
// 0 = DTR Enable (1) / Disable (0) receiver and IRQ
|
||||
DTR = ((value & 1) == 0);
|
||||
// 0 = Allow IRQ (0) when status bit 3 is true
|
||||
if ((value & 2) == 0) {
|
||||
RECV_IRQ_ENABLED = !DTR;
|
||||
} else {
|
||||
RECV_IRQ_ENABLED = false;
|
||||
}
|
||||
// 2,3 = Control transmit IRQ, RTS level and transmitter
|
||||
// 0 0 = Transmit interrupt off, RTS high, Transmitter off
|
||||
// 1 0 = Transmit interrupt ON, RTS low, Transmitter on
|
||||
// 0 1 = Transmit interrupt off, RTS low, Transmitter on
|
||||
// 1 1 = Transmit interrupt off, RTS low, Transmit BRK
|
||||
switch ((value >> 2) & 3) {
|
||||
case 0:
|
||||
TRANS_IRQ_ENABLED = false;
|
||||
TRANS_ACTIVE = false;
|
||||
break;
|
||||
case 1:
|
||||
TRANS_IRQ_ENABLED = true;
|
||||
TRANS_ACTIVE = true;
|
||||
break;
|
||||
case 2:
|
||||
TRANS_IRQ_ENABLED = false;
|
||||
TRANS_ACTIVE = true;
|
||||
break;
|
||||
case 3:
|
||||
TRANS_IRQ_ENABLED = false;
|
||||
TRANS_ACTIVE = true;
|
||||
break;
|
||||
}
|
||||
// 4 = Normal mode 0, or Echo mode 1 (bits 2 and 3 must be 0)
|
||||
FULL_ECHO = ((value & 16) > 0);
|
||||
System.out.println("Echo set to " + FULL_ECHO);
|
||||
// 5 = Control parity
|
||||
}
|
||||
if (register == ACIA_Control) {
|
||||
// 0-3 = Baud Rate
|
||||
// 4 = Use baud rate generator (1) / Use external clock (0)
|
||||
// 5-6 = Number of data bits (00 = 8, 01 = 7, 10 = 6, 11 = 5)
|
||||
// 7 = Number of stop bits (0 = 1 stop bit, 1 = 1-1/2 (with 5 data bits no parity), 1 (8 data plus parity) or 2)
|
||||
int bits = (value & 127) >> 5;
|
||||
System.out.println("Data bits set to " + (8 - bits));
|
||||
switch (bits) {
|
||||
case 0:
|
||||
DATA_BITS = 0x0FF;
|
||||
break;
|
||||
case 1:
|
||||
DATA_BITS = 0x07F;
|
||||
break;
|
||||
case 2:
|
||||
DATA_BITS = 0x03F;
|
||||
break;
|
||||
case 3:
|
||||
DATA_BITS = 0x01F;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (newValue > -1) {
|
||||
e.setNewValue(newValue);
|
||||
value = newValue;
|
||||
}
|
||||
// System.out.println("SSC I/O "+type+", register "+register+", value "+value);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardSSC.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public boolean inputAvailable() throws IOException {
|
||||
if (isConnected() && clientSocket != null && clientSocket.getInputStream() != null) {
|
||||
return clientSocket.getInputStream().available() > 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int getInputByte() throws IOException {
|
||||
if (inputAvailable()) {
|
||||
int in = clientSocket.getInputStream().read() & DATA_BITS;
|
||||
// System.out.write(in & 0x07f);
|
||||
if (RECV_STRIP_LF && in == 10 && lastInputByte == 13) {
|
||||
in = clientSocket.getInputStream().read() & DATA_BITS;
|
||||
// System.out.write(in & 0x07f);
|
||||
}
|
||||
// System.out.flush();
|
||||
lastInputByte = in;
|
||||
}
|
||||
return lastInputByte;
|
||||
}
|
||||
long lastSuccessfulWrite = -1L;
|
||||
|
||||
private void sendOutputByte(int i) throws IOException {
|
||||
if (clientSocket != null && clientSocket.isConnected()) {
|
||||
try {
|
||||
// System.out.write(i & 0x07f);
|
||||
clientSocket.getOutputStream().write(i & DATA_BITS);
|
||||
if (TRANS_ADD_LF && (i & DATA_BITS) == 13) {
|
||||
// System.out.write(10);
|
||||
clientSocket.getOutputStream().write(10);
|
||||
}
|
||||
clientSocket.getOutputStream().flush();
|
||||
lastSuccessfulWrite = System.currentTimeMillis();
|
||||
} catch (IOException e) {
|
||||
lastSuccessfulWrite = -1L;
|
||||
hangUp();
|
||||
}
|
||||
} else {
|
||||
lastSuccessfulWrite = -1L;
|
||||
}
|
||||
}
|
||||
|
||||
private void setCTS(boolean b) throws InterruptedException {
|
||||
PORT_CONNECTED = b;
|
||||
if (b == false) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean getCTS() throws InterruptedException {
|
||||
return PORT_CONNECTED;
|
||||
}
|
||||
|
||||
private void triggerIRQ() {
|
||||
IRQ_TRIGGERED = true;
|
||||
Computer.getComputer().getCpu().generateInterrupt();
|
||||
}
|
||||
|
||||
public void hangUp() {
|
||||
lastInputByte = 0;
|
||||
lastSuccessfulWrite = -1L;
|
||||
if (clientSocket != null && clientSocket.isConnected()) {
|
||||
try {
|
||||
clientSocket.shutdownInput();
|
||||
clientSocket.shutdownOutput();
|
||||
clientSocket.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardSSC.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
clientSocket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach from server socket port and ensure that the card's resources are
|
||||
* no longer in use
|
||||
*/
|
||||
@Override
|
||||
public boolean suspend() {
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardSSC.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
hangUp();
|
||||
if (listenThread != null && listenThread.isAlive()) {
|
||||
try {
|
||||
listenThread.join();
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(CardSSC.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
listenThread = null;
|
||||
socket = null;
|
||||
return super.suspend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
if (!isRunning()) {
|
||||
super.resume();
|
||||
RECV_IRQ_ENABLED = false;
|
||||
TRANS_IRQ_ENABLED = false;
|
||||
IRQ_TRIGGERED = false;
|
||||
|
||||
try {
|
||||
socket = new ServerSocket(IP_PORT);
|
||||
socket.setReuseAddress(true);
|
||||
socket.setSoTimeout(0);
|
||||
//socket.setReuseAddress(true);
|
||||
listenThread = new Thread(this);
|
||||
listenThread.setDaemon(false);
|
||||
listenThread.setName("SSC port listener");
|
||||
listenThread.start();
|
||||
} catch (IOException ex) {
|
||||
suspend();
|
||||
Logger.getLogger(CardSSC.class.getName()).log(Level.SEVERE, null, ex);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ConfigurableField(category = "Advanced", name = "Liveness check interval", description = "How often the connection is polled for signs of life (in milliseconds)")
|
||||
public int livenessCheck = 10000;
|
||||
|
||||
public boolean isConnected() {
|
||||
if (clientSocket == null || !clientSocket.isConnected()) {
|
||||
return false;
|
||||
}
|
||||
if (lastSuccessfulWrite == -1 || System.currentTimeMillis() > (lastSuccessfulWrite + livenessCheck)) {
|
||||
try {
|
||||
sendOutputByte(0);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// Do nothing -- the card rom does everything
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleC8FirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// There is no special c8 rom behavior for this card
|
||||
}
|
||||
}
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.Name;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.PagedMemory;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.Utility;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Calendar;
|
||||
import java.util.Stack;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
* Implementation of the Thunderclock Plus with some limitations:
|
||||
*
|
||||
* The apple cannot set time. The firmware will act like it is working but
|
||||
* nothing will actually happen when a time set command is sent.
|
||||
*
|
||||
* Though the interrupt features are implemented, they have not been tested.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Name("ThunderClock Plus")
|
||||
public class CardThunderclock extends Card {
|
||||
|
||||
ImageIcon clockIcon;
|
||||
ImageIcon clockFixIcon;
|
||||
long lastShownIcon = -1;
|
||||
// Only mention that the clock is read if it hasn't been checked for over 30 seconds
|
||||
// This is to avoid showing it all the time in programs that poll it constantly
|
||||
long MIN_WAIT = 30000;
|
||||
@ConfigurableField(category = "OS", name = "Patch Prodos Year", description = "If enabled, the Prodos clock driver will be patched to use the current year.")
|
||||
public boolean attemptYearPatch = true;
|
||||
|
||||
public CardThunderclock() {
|
||||
try {
|
||||
loadRom("jace/data/thunderclock_plus.rom");
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardDiskII.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
clockIcon = Utility.loadIcon("clock.png");
|
||||
clockFixIcon = Utility.loadIcon("clock_fix.png");
|
||||
clockFixIcon.setDescription("Fixed");
|
||||
}
|
||||
|
||||
// Raw format: 40 bits, in BCD form (it actually streams out in the reverse order of this, bit 0 first)
|
||||
// The data format is fully elaborated in the datasheet of the calendar/clock chip: NEC uPD1990AC
|
||||
// month (1-12) -- hex
|
||||
// day of week (0-6)
|
||||
// day of month, tens digit (0-3)
|
||||
// day of month, ones digit (0-9)
|
||||
// hour, tens digit (0-2)
|
||||
// hour, ones digit (0-9)
|
||||
// minute, tens digit (0-5)
|
||||
// minute, ones digit (0-9)
|
||||
// second, tens digit (0-5)
|
||||
// second, ones digit (0-9)
|
||||
@Override
|
||||
public void reset() {
|
||||
irqAsserted = false;
|
||||
irqEnabled = false;
|
||||
ticks = 0;
|
||||
timerRate = 0;
|
||||
}
|
||||
public boolean strobe = false;
|
||||
public boolean clock = false;
|
||||
public boolean shiftMode = false;
|
||||
public boolean irqEnabled = false;
|
||||
public boolean irqAsserted = false;
|
||||
public boolean timerEnabled = false;
|
||||
public int timerRate = 0;
|
||||
|
||||
@Override
|
||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// Data is read via bit-banging the status register
|
||||
// Nibbles are sent lowest significant bit first.
|
||||
// Commands are sent to the register followed by a strobe pulse on bit 2 on and off
|
||||
// So senting the time read command would be a string of bytes: 0x018, 0x01c and then 0x018 again
|
||||
//
|
||||
// Time read is signaled by 0x018 followed by a register shift command 0x08
|
||||
// When register shift is active, a clock signal is used to move to the next bit.
|
||||
//
|
||||
// A bit is placed in data-in (bit 0)
|
||||
// Then the clock is raised (bit 1 set) and then lowered (bit 1 unset)
|
||||
// After this, the next time the register is read it will have the next bit
|
||||
// of the register in the hibit (bit 7)
|
||||
//
|
||||
// Reg 0: Command register
|
||||
// data in = 0x01
|
||||
// clock = 0x02
|
||||
// strobe = 0x04
|
||||
// register hold = 0x0
|
||||
// register shift = 0x08
|
||||
// time set = 0x010
|
||||
// time read = 0x018
|
||||
// Timer modes = 0x020 (64hz), 0x028 (256hz), 0x030 (2048hz)
|
||||
// Interrupt enable = 0x040 (IRQ assert is read as 0x020 in the status register)
|
||||
// data out = 0x080
|
||||
if (type.isRead() && register == 0) {
|
||||
e.setNewValue((peekBit()) | (irqAsserted ? 0x020 : 0));
|
||||
return;
|
||||
}
|
||||
|
||||
if (register == 8) {
|
||||
irqAsserted = false;
|
||||
return;
|
||||
} else if (register != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isClock = (value & 0x02) != 0;
|
||||
boolean isStrobe = (value & 0x04) != 0;
|
||||
boolean isShift = (value & 0x08) != 0;
|
||||
boolean isRead = (value & 0x18) != 0;
|
||||
|
||||
if (!isClock && clock) {
|
||||
if (buffer != null) {
|
||||
buffer.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if (!isStrobe && strobe) {
|
||||
shiftMode = isShift;
|
||||
if (isRead) {
|
||||
if (attemptYearPatch) {
|
||||
performProdosPatch();
|
||||
}
|
||||
getTime();
|
||||
clockIcon.setDescription("Slot " + getSlot());
|
||||
long now = System.currentTimeMillis();
|
||||
if ((now - lastShownIcon) > MIN_WAIT) {
|
||||
Emulator.getFrame().addIndicator(this, clockIcon, 3000);
|
||||
}
|
||||
lastShownIcon = now;
|
||||
}
|
||||
shiftMode = isShift;
|
||||
}
|
||||
|
||||
timerEnabled = (value & 0x020) != 0;
|
||||
ticks = 0;
|
||||
if (timerEnabled) {
|
||||
switch (value & 0x038) {
|
||||
case 0x020:
|
||||
timerRate = (int) (Motherboard.SPEED / 64);
|
||||
break;
|
||||
case 0x028:
|
||||
timerRate = (int) (Motherboard.SPEED / 256);
|
||||
break;
|
||||
case 0x030:
|
||||
timerRate = (int) (Motherboard.SPEED / 2048);
|
||||
break;
|
||||
default:
|
||||
timerEnabled = false;
|
||||
timerRate = 0;
|
||||
}
|
||||
} else {
|
||||
timerRate = 0;
|
||||
}
|
||||
|
||||
irqEnabled = (value & 0x040) != 0;
|
||||
clock = isClock;
|
||||
strobe = isStrobe;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// Firmware ROM is used -- only I/O port was needed for proper emulation
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleC8FirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// C8 access is used to read the clock directly
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "Thunderclock Plus";
|
||||
}
|
||||
|
||||
int ticks = 0;
|
||||
@Override
|
||||
public void tick() {
|
||||
if (timerEnabled) {
|
||||
ticks++;
|
||||
if (ticks >= timerRate) {
|
||||
ticks = 0;
|
||||
irqAsserted = true;
|
||||
if (irqEnabled) {
|
||||
Computer.getComputer().getCpu().generateInterrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getTime() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTimeInMillis(System.currentTimeMillis());
|
||||
clearBuffer();
|
||||
pushNibble(cal.get(Calendar.MONTH) + 1);
|
||||
pushNibble(cal.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY);
|
||||
pushNibble(cal.get(Calendar.DAY_OF_MONTH) / 10);
|
||||
pushNibble(cal.get(Calendar.DAY_OF_MONTH) % 10);
|
||||
pushNibble(cal.get(Calendar.HOUR_OF_DAY) / 10);
|
||||
pushNibble(cal.get(Calendar.HOUR_OF_DAY) % 10);
|
||||
pushNibble(cal.get(Calendar.MINUTE) / 10);
|
||||
pushNibble(cal.get(Calendar.MINUTE) % 10);
|
||||
pushNibble(cal.get(Calendar.SECOND) / 10);
|
||||
pushNibble(cal.get(Calendar.SECOND) % 10);
|
||||
}
|
||||
Stack<Boolean> buffer;
|
||||
|
||||
private void clearBuffer() {
|
||||
if (buffer == null) {
|
||||
buffer = new Stack<Boolean>();
|
||||
} else {
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void pushNibble(int value) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
boolean val = (value & 8) != 0;
|
||||
buffer.push(val);
|
||||
value <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
private int peekBit() {
|
||||
if (buffer == null || buffer.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return buffer.peek() ? 0x080 : 0;
|
||||
}
|
||||
|
||||
public void loadRom(String path) throws IOException {
|
||||
InputStream romFile = CardThunderclock.class.getClassLoader().getResourceAsStream(path);
|
||||
final int cxRomLength = 0x0100;
|
||||
final int c8RomLength = 0x0700;
|
||||
byte[] romxData = new byte[cxRomLength];
|
||||
byte[] rom8Data = new byte[c8RomLength];
|
||||
try {
|
||||
if (romFile.read(romxData) != cxRomLength) {
|
||||
throw new IOException("Bad Thunderclock rom size");
|
||||
}
|
||||
getCxRom().loadData(romxData);
|
||||
romFile.close();
|
||||
romFile = CardThunderclock.class.getClassLoader().getResourceAsStream(path);
|
||||
if (romFile.read(rom8Data) != c8RomLength) {
|
||||
throw new IOException("Bad Thunderclock rom size");
|
||||
}
|
||||
getC8Rom().loadData(rom8Data);
|
||||
romFile.close();
|
||||
} catch (IOException ex) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
static byte[] DRIVER_PATTERN = {
|
||||
(byte) 0x00, (byte) 0x01f, (byte) 0x03b, (byte) 0x05a,
|
||||
(byte) 0x078, (byte) 0x097, (byte) 0x0b5, (byte) 0x0d3,
|
||||
(byte) 0x0f2
|
||||
};
|
||||
static int DRIVER_OFFSET = -26;
|
||||
int patchLoc = -1;
|
||||
|
||||
/**
|
||||
* Scan active memory for the Prodos clock driver and patch the internal
|
||||
* code to use a fixed value for the present year. This means Prodos will
|
||||
* always tell time correctly.
|
||||
*/
|
||||
private void performProdosPatch() {
|
||||
PagedMemory ram = Computer.getComputer().getMemory().activeRead;
|
||||
if (patchLoc > 0) {
|
||||
// We've already patched, just validate
|
||||
if (ram.readByte(patchLoc) == (byte) MOS65C02.OPCODE.LDA_IMM.getCode()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
int match = 0;
|
||||
int matchStart = 0;
|
||||
for (int addr = 0x08000; addr < 0x010000; addr++) {
|
||||
if (ram.readByte(addr) == DRIVER_PATTERN[match]) {
|
||||
match++;
|
||||
if (match == DRIVER_PATTERN.length) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
match = 0;
|
||||
matchStart = addr;
|
||||
}
|
||||
}
|
||||
if (match != DRIVER_PATTERN.length) {
|
||||
return;
|
||||
}
|
||||
patchLoc = matchStart + DRIVER_OFFSET;
|
||||
ram.writeByte(patchLoc, (byte) MOS65C02.OPCODE.LDA_IMM.getCode());
|
||||
int year = Calendar.getInstance().get(Calendar.YEAR) % 100;
|
||||
ram.writeByte(patchLoc + 1, (byte) year);
|
||||
ram.writeByte(patchLoc + 2, (byte) MOS65C02.OPCODE.NOP.getCode());
|
||||
ram.writeByte(patchLoc + 3, (byte) MOS65C02.OPCODE.NOP.getCode());
|
||||
Emulator.getFrame().addIndicator(this, clockFixIcon, 4000);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Keyboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import java.awt.Rectangle;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Attempt at adding accessibility by redirecting screen/keyboard traffic to
|
||||
* stdout/stdin of the console. Doesn't work well, unfortunately.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class ConsoleProbe {
|
||||
|
||||
public static boolean enabled = true;
|
||||
public String[] lastScreen = new String[24];
|
||||
public List<Rectangle> regions = new ArrayList<Rectangle>();
|
||||
private RAMListener textListener;
|
||||
public static long lastChange;
|
||||
public static long updateDelay = 100L;
|
||||
public static boolean readerActive = false;
|
||||
public Computer computer;
|
||||
private Thread keyReaderThread;
|
||||
|
||||
public void init(final Computer c) {
|
||||
computer = c;
|
||||
enabled = true;
|
||||
keyReaderThread = new Thread(new KeyReader());
|
||||
keyReaderThread.setName("Console probe key reader");
|
||||
keyReaderThread.start();
|
||||
textListener = new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0400);
|
||||
setScopeEnd(0x0BFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
if (e.getAddress() < 0x0800 && SoftSwitches.PAGE2.isOn()) {
|
||||
return;
|
||||
}
|
||||
if (SoftSwitches.TEXT.isOff()) {
|
||||
if (SoftSwitches.MIXED.isOn()) {
|
||||
handleMixedMode();
|
||||
}
|
||||
} else {
|
||||
handleTextMode();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMixedMode() {
|
||||
handleTextMode();
|
||||
}
|
||||
|
||||
private void handleTextMode() {
|
||||
lastChange = System.currentTimeMillis();
|
||||
if (readerActive) {
|
||||
return;
|
||||
}
|
||||
Thread t = new Thread(new ScreenReader());
|
||||
t.start();
|
||||
}
|
||||
};
|
||||
c.getMemory().addListener(textListener);
|
||||
}
|
||||
|
||||
public static synchronized void performRead() {
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
enabled = false;
|
||||
if (textListener != null) {
|
||||
computer.getMemory().removeListener(textListener);
|
||||
}
|
||||
|
||||
if (keyReaderThread != null && keyReaderThread.isAlive()) {
|
||||
try {
|
||||
keyReaderThread.join();
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(ConsoleProbe.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ScreenReader implements Runnable {
|
||||
|
||||
public void run() {
|
||||
readerActive = true;
|
||||
try {
|
||||
// Keep sleeping until there have been no more screen changes during the specified delay period
|
||||
// It is possible that the lastChange will keep being updated while in this loop
|
||||
// That is both expected and the reason this is a loop!
|
||||
long delay = 0;
|
||||
while (System.currentTimeMillis() - lastChange <= updateDelay) {
|
||||
delay = updateDelay - System.currentTimeMillis() - lastChange;
|
||||
if (delay > 0) {
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(ConsoleProbe.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
// Signal that we're off to go read the screen (and any additional update will need to spawn the thread again)
|
||||
readerActive = false;
|
||||
performRead();
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyReader implements Runnable {
|
||||
|
||||
public Computer c;
|
||||
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
while (enabled && (System.in.available() == 0 || Keyboard.readState() < 0)) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException ex) {
|
||||
System.out.println(ex.getMessage());
|
||||
Logger.getLogger(ConsoleProbe.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
int ch = System.in.read();
|
||||
if (ch == 10) {
|
||||
ch = 13;
|
||||
}
|
||||
Keyboard.pressKey((byte) ch);
|
||||
} catch (IOException ex) {
|
||||
System.out.println(ex.getMessage());
|
||||
Logger.getLogger(ConsoleProbe.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Keyboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import java.awt.Toolkit;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Attempt to simplify what ConsoleProbe was attempting. Still not ready for any
|
||||
* real use.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class ConsoleProbeSimple {
|
||||
|
||||
RAMListener cout;
|
||||
public static int COUT = 0xFDED;
|
||||
|
||||
public void init(final Computer c) {
|
||||
Thread t = new Thread(new KeyReader());
|
||||
t.start();
|
||||
|
||||
cout = new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(COUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
MOS65C02 cpu = (MOS65C02) c.getCpu();
|
||||
int ch = cpu.A & 0x07f;
|
||||
if (ch == 13) {
|
||||
System.out.println();
|
||||
} else if (ch < ' ') {
|
||||
if (ch == 7) {
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
} else {
|
||||
System.out.println("CHR" + ch);
|
||||
}
|
||||
} else {
|
||||
System.out.print((char) ch);
|
||||
}
|
||||
}
|
||||
};
|
||||
c.getMemory().addListener(cout);
|
||||
}
|
||||
|
||||
public static class KeyReader implements Runnable {
|
||||
|
||||
public Computer c;
|
||||
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
while (System.in.available() == 0 || Keyboard.readState() < 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException ex) {
|
||||
System.out.println(ex.getMessage());
|
||||
Logger.getLogger(ConsoleProbeSimple.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
int ch = System.in.read();
|
||||
if (ch == 10) {
|
||||
ch = 13;
|
||||
}
|
||||
Keyboard.pressKey((byte) ch);
|
||||
} catch (IOException ex) {
|
||||
System.out.println(ex.getMessage());
|
||||
Logger.getLogger(ConsoleProbeSimple.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.library.MediaConsumer;
|
||||
import jace.library.MediaEntry;
|
||||
import jace.library.MediaEntry.MediaFile;
|
||||
import jace.state.StateManager;
|
||||
import jace.state.Stateful;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
* This implements the mechanical part of the disk drive and tracks changes to
|
||||
* disk images. The actual handling of disk images is performed in the
|
||||
* FloppyDisk class. The apple interface card portion is managed in the
|
||||
* CardDiskII class. Useful reading:
|
||||
* http://www.doc.ic.ac.uk/~ih/doc/stepper/others/example3/diskii_specs.html
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
public class DiskIIDrive implements MediaConsumer {
|
||||
|
||||
FloppyDisk disk;
|
||||
// Number of milliseconds to wait between last write and update to disk image
|
||||
public static long WRITE_UPDATE_DELAY = 1000;
|
||||
// Flag to halt if any writes to floopy occur when updating physical disk image
|
||||
boolean diskUpdatePending = false;
|
||||
// Last time of write operation
|
||||
long lastWriteTime;
|
||||
// Managed thread to update disk image in background
|
||||
Thread writerThread;
|
||||
private final byte[][] driveHeadStepDelta = {
|
||||
{0, 0, 1, 1, 0, 0, 1, 1, -1, -1, 0, 0, -1, -1, 0, 0}, // phase 0
|
||||
{0, -1, 0, -1, 1, 0, 1, 0, 0, -1, 0, -1, 1, 0, 1, 0}, // phase 1
|
||||
{0, 0, -1, -1, 0, 0, -1, -1, 1, 1, 0, 0, 1, 1, 0, 0}, // phase 2
|
||||
{0, 1, 0, 1, -1, 0, -1, 0, 0, 1, 0, 1, -1, 0, -1, 0}}; // phase 3
|
||||
@Stateful
|
||||
public int halfTrack;
|
||||
@Stateful
|
||||
public int trackStartOffset;
|
||||
@Stateful
|
||||
public int nibbleOffset;
|
||||
@Stateful
|
||||
public boolean writeMode;
|
||||
@Stateful
|
||||
public boolean driveOn;
|
||||
@Stateful
|
||||
public int magnets;
|
||||
@Stateful
|
||||
public byte latch;
|
||||
@Stateful
|
||||
public int spinCount;
|
||||
Set<Integer> dirtyTracks;
|
||||
|
||||
public void reset() {
|
||||
driveOn = false;
|
||||
magnets = 0;
|
||||
dirtyTracks = new HashSet<Integer>();
|
||||
diskUpdatePending = false;
|
||||
}
|
||||
|
||||
void step(int register) {
|
||||
// switch drive head stepper motor magnets on/off
|
||||
int magnet = (register >> 1) & 0x3;
|
||||
magnets &= ~(1 << magnet);
|
||||
magnets |= ((register & 0x1) << magnet);
|
||||
|
||||
// step the drive head according to stepper magnet changes
|
||||
if (driveOn) {
|
||||
int delta = driveHeadStepDelta[halfTrack & 0x3][magnets];
|
||||
if (delta != 0) {
|
||||
int newHalfTrack = halfTrack + delta;
|
||||
if (newHalfTrack < 0) {
|
||||
newHalfTrack = 0;
|
||||
} else if (newHalfTrack > FloppyDisk.HALF_TRACK_COUNT) {
|
||||
newHalfTrack = FloppyDisk.HALF_TRACK_COUNT;
|
||||
}
|
||||
if (newHalfTrack != halfTrack) {
|
||||
halfTrack = newHalfTrack;
|
||||
trackStartOffset = (halfTrack >> 1) * FloppyDisk.TRACK_NIBBLE_LENGTH;
|
||||
if (trackStartOffset >= FloppyDisk.DISK_NIBBLE_LENGTH) {
|
||||
trackStartOffset = FloppyDisk.DISK_NIBBLE_LENGTH - FloppyDisk.TRACK_NIBBLE_LENGTH;
|
||||
}
|
||||
nibbleOffset = 0;
|
||||
|
||||
//System.out.printf("new half track %d\n", currentHalfTrack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setOn(boolean b) {
|
||||
driveOn = b;
|
||||
}
|
||||
|
||||
boolean isOn() {
|
||||
return driveOn;
|
||||
}
|
||||
|
||||
byte readLatch() {
|
||||
byte result = 0x07f;
|
||||
if (!writeMode) {
|
||||
spinCount = (spinCount + 1) & 0x0F;
|
||||
if (spinCount > 0) {
|
||||
if (disk != null) {
|
||||
result = disk.nibbles[trackStartOffset + nibbleOffset++];
|
||||
} else {
|
||||
result = (byte) 0x0ff;
|
||||
}
|
||||
}
|
||||
if (nibbleOffset >= FloppyDisk.TRACK_NIBBLE_LENGTH) {
|
||||
nibbleOffset = 0;
|
||||
}
|
||||
} else {
|
||||
spinCount = (spinCount + 1) & 0x0F;
|
||||
if (spinCount > 0) {
|
||||
result = (byte) 0x080;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void write() {
|
||||
if (writeMode) {
|
||||
while (diskUpdatePending) {
|
||||
// If another thread requested writes to block (e.g. because of disk activity), wait for it to finish!
|
||||
LockSupport.parkNanos(1000);
|
||||
}
|
||||
if (disk != null) {
|
||||
// Do nothing if write-protection is enabled!
|
||||
if (getMediaEntry() == null || !getMediaEntry().writeProtected) {
|
||||
dirtyTracks.add(trackStartOffset / FloppyDisk.TRACK_NIBBLE_LENGTH);
|
||||
disk.nibbles[trackStartOffset + nibbleOffset++] = latch;
|
||||
triggerDiskUpdate();
|
||||
StateManager.markDirtyValue(disk.nibbles);
|
||||
}
|
||||
}
|
||||
|
||||
if (nibbleOffset >= FloppyDisk.TRACK_NIBBLE_LENGTH) {
|
||||
nibbleOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setLatchValue(byte value) {
|
||||
if (writeMode) {
|
||||
latch = value;
|
||||
} else {
|
||||
latch = (byte) 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void setReadMode() {
|
||||
writeMode = false;
|
||||
}
|
||||
|
||||
void setWriteMode() {
|
||||
writeMode = true;
|
||||
}
|
||||
|
||||
private void updateDisk() {
|
||||
|
||||
// Signal disk update is underway
|
||||
diskUpdatePending = true;
|
||||
// Update all tracks as necessary
|
||||
if (disk != null) {
|
||||
for (Integer track : dirtyTracks) {
|
||||
disk.updateTrack(track);
|
||||
}
|
||||
}
|
||||
// Empty out dirty list
|
||||
dirtyTracks.clear();
|
||||
// Signal disk update is completed
|
||||
diskUpdatePending = false;
|
||||
}
|
||||
|
||||
private void triggerDiskUpdate() {
|
||||
lastWriteTime = System.currentTimeMillis();
|
||||
if (writerThread == null || !writerThread.isAlive()) {
|
||||
writerThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long diff = 0;
|
||||
// Wait until there have been no virtual writes for specified delay time
|
||||
while ((diff = System.currentTimeMillis() - lastWriteTime) < WRITE_UPDATE_DELAY) {
|
||||
// Sleep for difference of time
|
||||
LockSupport.parkNanos(diff * 1000);
|
||||
// Note: In the meantime, there could have been another disk write,
|
||||
// in which case this loop will repeat again as needed.
|
||||
}
|
||||
updateDisk();
|
||||
}
|
||||
});
|
||||
writerThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
void insertDisk(File diskPath) throws IOException {
|
||||
disk = new FloppyDisk(diskPath);
|
||||
dirtyTracks = new HashSet<Integer>();
|
||||
// Emulator state has changed significantly, reset state manager
|
||||
StateManager.getInstance().invalidate();
|
||||
}
|
||||
private ImageIcon icon;
|
||||
|
||||
public ImageIcon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(ImageIcon i) {
|
||||
icon = i;
|
||||
}
|
||||
private MediaEntry currentMediaEntry;
|
||||
private MediaFile currentMediaFile;
|
||||
|
||||
public void eject() {
|
||||
if (disk == null) {
|
||||
return;
|
||||
}
|
||||
waitForPendingWrites();
|
||||
disk = null;
|
||||
dirtyTracks = new HashSet<Integer>();
|
||||
// Emulator state has changed significantly, reset state manager
|
||||
StateManager.getInstance().invalidate();
|
||||
}
|
||||
|
||||
public void insertMedia(MediaEntry e, MediaFile f) throws IOException {
|
||||
if (!isAccepted(e, f)) {
|
||||
return;
|
||||
}
|
||||
eject();
|
||||
insertDisk(f.path);
|
||||
currentMediaEntry = e;
|
||||
currentMediaFile = f;
|
||||
}
|
||||
|
||||
public MediaEntry getMediaEntry() {
|
||||
return currentMediaEntry;
|
||||
}
|
||||
|
||||
public MediaFile getMediaFile() {
|
||||
return currentMediaFile;
|
||||
}
|
||||
|
||||
public boolean isAccepted(MediaEntry e, MediaFile f) {
|
||||
if (f == null) return false;
|
||||
System.out.println("Type is accepted: "+f.path+"; "+e.type.toString()+": "+e.type.is140kb);
|
||||
return e.type.is140kb;
|
||||
}
|
||||
|
||||
private void waitForPendingWrites() {
|
||||
while (diskUpdatePending || !dirtyTracks.isEmpty()) {
|
||||
// If the current disk has unsaved changes, wait!!!
|
||||
LockSupport.parkNanos(1000);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.state.StateManager;
|
||||
import jace.state.Stateful;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Representation of a 140kb floppy disk image. This also performs conversions as
|
||||
* needed. Internally, the emulator will always use a "nibblized" disk
|
||||
* representation during active use. So if any sort of dsk/do/po image is loaded
|
||||
* it will be converted first. If changes are made to the disk then the tracks
|
||||
* will be converted back into de-nibblized form prior to saving. The
|
||||
* DiskIIDrive class managed disk changes, this class is more an interface to
|
||||
* load/save various disk formats and hold the active disk image while it is in
|
||||
* use.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
public class FloppyDisk {
|
||||
|
||||
@Stateful
|
||||
boolean writeProtected;
|
||||
@Stateful
|
||||
public int headerLength = 0;
|
||||
@Stateful
|
||||
public boolean isNibblizedImage;
|
||||
@Stateful
|
||||
public int volumeNumber;
|
||||
static final public int TRACK_NIBBLE_LENGTH = 0x1A00;
|
||||
static final public int TRACK_COUNT = 35;
|
||||
static final public int SECTOR_COUNT = 16;
|
||||
static final public int HALF_TRACK_COUNT = TRACK_COUNT * 2;
|
||||
static final public int DISK_NIBBLE_LENGTH = TRACK_NIBBLE_LENGTH * TRACK_COUNT;
|
||||
static final public int DISK_PLAIN_LENGTH = 143360;
|
||||
static final public int DISK_2MG_NON_NIB_LENGTH = DISK_PLAIN_LENGTH + 0x040;
|
||||
static final public int DISK_2MG_NIB_LENGTH = DISK_NIBBLE_LENGTH + 0x040;
|
||||
@Stateful
|
||||
public byte[] nibbles = new byte[DISK_NIBBLE_LENGTH];
|
||||
// Denotes the mapping of physical order (array index) to the dos 3.3 logical order (value)
|
||||
public static int[] DOS_33_SECTOR_ORDER = {
|
||||
0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04,
|
||||
0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F
|
||||
};
|
||||
// Denotes the mapping of physical order (array index) to the Prodos logical order (value)
|
||||
// Borrowed from KEGS -- thanks KEGS team!
|
||||
public static int[] PRODOS_SECTOR_ORDER = {
|
||||
0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b,
|
||||
0x04, 0x0c, 0x05, 0x0d, 0x06, 0x0e, 0x07, 0x0f
|
||||
};
|
||||
// Sector ordering used for current disk
|
||||
@Stateful
|
||||
public int[] currentSectorOrder;
|
||||
// Location of image
|
||||
@Stateful
|
||||
public File diskPath;
|
||||
static int[] NIBBLE_62 = {
|
||||
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
|
||||
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
||||
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
||||
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
|
||||
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
|
||||
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
|
||||
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
|
||||
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff};
|
||||
static int[] NIBBLE_62_REVERSE;
|
||||
|
||||
static {
|
||||
NIBBLE_62_REVERSE = new int[256];
|
||||
for (int i = 0; i < NIBBLE_62.length; i++) {
|
||||
NIBBLE_62_REVERSE[NIBBLE_62[i] & 0x0ff] = 0x0ff & i;
|
||||
}
|
||||
}
|
||||
private static boolean DEBUG = false;
|
||||
|
||||
public FloppyDisk() throws IOException {
|
||||
// This constructor is only used for disk conversion...
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param diskFile
|
||||
* @throws IOException
|
||||
*/
|
||||
public FloppyDisk(File diskFile) throws IOException {
|
||||
FileInputStream input = new FileInputStream(diskFile);
|
||||
String name = diskFile.getName().toUpperCase();
|
||||
readDisk(input, name.endsWith(".PO"));
|
||||
writeProtected = !diskFile.canWrite();
|
||||
diskPath = diskFile;
|
||||
}
|
||||
|
||||
// brendanr: refactored to use input stream
|
||||
public void readDisk(InputStream diskFile, boolean prodosOrder) throws IOException {
|
||||
isNibblizedImage = true;
|
||||
volumeNumber = CardDiskII.DEFAULT_VOLUME_NUMBER;
|
||||
headerLength = 0;
|
||||
try {
|
||||
int bytesRead = diskFile.read(nibbles);
|
||||
if (bytesRead == DISK_2MG_NIB_LENGTH) {
|
||||
bytesRead -= 0x040;
|
||||
// Try to pick up volume number from 2MG header.
|
||||
volumeNumber = ((nibbles[17] & 1) == 1) ? nibbles[16] : 254;
|
||||
nibbles = Arrays.copyOfRange(nibbles, 0x040, nibbles.length);
|
||||
|
||||
headerLength = 0x040;
|
||||
}
|
||||
if (bytesRead == DISK_2MG_NON_NIB_LENGTH) {
|
||||
bytesRead -= 0x040;
|
||||
// Try to pick up correct sector ordering and volume from 2MG header.
|
||||
prodosOrder = (nibbles[12] == 01);
|
||||
volumeNumber = ((nibbles[17] & 1) == 1) ? nibbles[16] : 254;
|
||||
nibbles = Arrays.copyOfRange(nibbles, 0x040, nibbles.length);
|
||||
|
||||
headerLength = 0x040;
|
||||
}
|
||||
currentSectorOrder = prodosOrder ? PRODOS_SECTOR_ORDER : DOS_33_SECTOR_ORDER;
|
||||
if (bytesRead == DISK_PLAIN_LENGTH) {
|
||||
isNibblizedImage = false;
|
||||
nibbles = nibblize(nibbles);
|
||||
if (nibbles.length != DISK_NIBBLE_LENGTH) {
|
||||
throw new IOException("Nibblized version is wrong size (expected-actual = " + (DISK_NIBBLE_LENGTH - nibbles.length) + ")");
|
||||
}
|
||||
} else if (bytesRead != DISK_NIBBLE_LENGTH) {
|
||||
throw new IOException("Bad NIB size " + bytesRead + "; JACE only recognizes plain images " + DISK_PLAIN_LENGTH + " or nibble images " + DISK_NIBBLE_LENGTH + " sizes");
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw ex;
|
||||
}
|
||||
StateManager.markDirtyValue(nibbles);
|
||||
StateManager.markDirtyValue(currentSectorOrder);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a block-format disk to a 6-by-2 nibblized encoding scheme (raw NIB disk format)
|
||||
*/
|
||||
public byte[] nibblize(byte[] nibbles) throws IOException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
for (int track = 0; track < TRACK_COUNT; track++) {
|
||||
for (int sector = 0; sector < SECTOR_COUNT; sector++) {
|
||||
// 15 junk bytes
|
||||
writeJunkBytes(output, 15);
|
||||
// Address block
|
||||
writeAddressBlock(output, track, sector);
|
||||
// 4 junk bytes
|
||||
writeJunkBytes(output, 4);
|
||||
// Data block
|
||||
nibblizeBlock(output, track, currentSectorOrder[sector], nibbles);
|
||||
// 34 junk bytes
|
||||
writeJunkBytes(output, 34);
|
||||
}
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
private void writeJunkBytes(ByteArrayOutputStream output, int i) {
|
||||
for (int b = 0; b < i; b++) {
|
||||
output.write(0x0FF);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAddressBlock(ByteArrayOutputStream output, int track, int sector) throws IOException {
|
||||
output.write(0x0d5);
|
||||
output.write(0x0aa);
|
||||
output.write(0x096);
|
||||
|
||||
int checksum = 00;
|
||||
// volume
|
||||
checksum ^= volumeNumber;
|
||||
output.write(getOddEven(volumeNumber));
|
||||
// track
|
||||
checksum ^= track;
|
||||
output.write(getOddEven(track));
|
||||
// sector
|
||||
checksum ^= sector;
|
||||
output.write(getOddEven(sector));
|
||||
// checksum
|
||||
output.write(getOddEven(checksum & 0x0ff));
|
||||
output.write(0x0de);
|
||||
output.write(0x0aa);
|
||||
output.write(0x0eb);
|
||||
}
|
||||
|
||||
private byte[] getOddEven(int i) {
|
||||
byte[] out = new byte[2];
|
||||
out[0] = (byte) (0xAA | (i >> 1));
|
||||
out[1] = (byte) (0xAA | i);
|
||||
return out;
|
||||
}
|
||||
|
||||
private int decodeOddEven(byte b1, byte b2) {
|
||||
// return (((b1 ^ 0x0AA) << 1) & 0x0ff) | ((b2 ^ 0x0AA) & 0x0ff);
|
||||
int result = ((((b1 << 1) | 1) & b2) & 0x0ff);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void nibblizeBlock(ByteArrayOutputStream output, int track, int sector, byte[] nibbles) {
|
||||
int offset = ((track * SECTOR_COUNT) + sector) * 256;
|
||||
int[] temp = new int[342];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
temp[i] = (nibbles[offset + i] & 0x0ff) >> 2;
|
||||
}
|
||||
int hi = 0x001;
|
||||
int med = 0x0AB;
|
||||
int low = 0x055;
|
||||
|
||||
for (int i = 0; i < 0x56; i++) {
|
||||
int value = ((nibbles[offset + hi] & 1) << 5)
|
||||
| ((nibbles[offset + hi] & 2) << 3)
|
||||
| ((nibbles[offset + med] & 1) << 3)
|
||||
| ((nibbles[offset + med] & 2) << 1)
|
||||
| ((nibbles[offset + low] & 1) << 1)
|
||||
| ((nibbles[offset + low] & 2) >> 1);
|
||||
temp[i + 256] = value;
|
||||
hi = (hi - 1) & 0x0ff;
|
||||
med = (med - 1) & 0x0ff;
|
||||
low = (low - 1) & 0x0ff;
|
||||
}
|
||||
output.write(0x0d5);
|
||||
output.write(0x0aa);
|
||||
output.write(0x0ad);
|
||||
|
||||
int last = 0;
|
||||
for (int i = temp.length - 1; i > 255; i--) {
|
||||
int value = temp[i] ^ last;
|
||||
output.write(NIBBLE_62[value]);
|
||||
last = temp[i];
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
int value = temp[i] ^ last;
|
||||
output.write(NIBBLE_62[value]);
|
||||
last = temp[i];
|
||||
}
|
||||
// Last data byte used as checksum
|
||||
output.write(NIBBLE_62[last]);
|
||||
output.write(0x0de);
|
||||
output.write(0x0aa);
|
||||
output.write(0x0eb);
|
||||
}
|
||||
|
||||
public void updateTrack(Integer track) {
|
||||
// If disk is nibble image, write nibbles directly
|
||||
if (isNibblizedImage) {
|
||||
updateNibblizedTrack(track);
|
||||
}
|
||||
// Otherwise denibblize and write out
|
||||
if (!isNibblizedImage) {
|
||||
updateDenibblizedTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
void updateNibblizedTrack(Integer track) {
|
||||
try {
|
||||
RandomAccessFile disk = new RandomAccessFile(diskPath, "rws");
|
||||
// Locate start of track
|
||||
disk.seek(headerLength + track * TRACK_NIBBLE_LENGTH);
|
||||
// Update that section of the disk image
|
||||
disk.write(nibbles, track * TRACK_NIBBLE_LENGTH, TRACK_NIBBLE_LENGTH);
|
||||
disk.close();
|
||||
} catch (FileNotFoundException ex) {
|
||||
Logger.getLogger(FloppyDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(FloppyDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
static public boolean CHECK_NIB_SECTOR_PATTERN_ON_WRITE = true;
|
||||
|
||||
void updateDenibblizedTrack(Integer track) {
|
||||
try {
|
||||
byte[] trackNibbles = new byte[TRACK_NIBBLE_LENGTH];
|
||||
byte[] trackData = new byte[SECTOR_COUNT * 256];
|
||||
// Copy track into temporary buffer
|
||||
// System.out.println("Nibblized track "+track);
|
||||
// System.out.printf("%04d:",0);
|
||||
for (int i = 0, pos = track * TRACK_NIBBLE_LENGTH; i < TRACK_NIBBLE_LENGTH; i++, pos++) {
|
||||
trackNibbles[i] = nibbles[pos];
|
||||
// System.out.print(Integer.toString(nibbles[pos] & 0x0ff, 16)+" ");
|
||||
// if (i % 16 == 15) {
|
||||
// System.out.println();
|
||||
// System.out.printf("%04d:",i+1);
|
||||
// }
|
||||
}
|
||||
// System.out.println();
|
||||
|
||||
int pos = 0;
|
||||
for (int i = 0; i < SECTOR_COUNT; i++) {
|
||||
// Loop through number of sectors
|
||||
pos = locatePattern(pos, trackNibbles, 0x0d5, 0x0aa, 0x096);
|
||||
// Locate track number
|
||||
int trackVerify = decodeOddEven(trackNibbles[pos + 5], trackNibbles[pos + 6]);
|
||||
// Locate sector number
|
||||
int sector = decodeOddEven(trackNibbles[pos + 7], trackNibbles[pos + 8]);
|
||||
// System.out.println("Writing track " + track + ", getting address block for T" + trackVerify + ".S" + sector + " found at NIB offset "+pos);
|
||||
// Skip to end of address block
|
||||
pos = locatePattern(pos, trackNibbles, 0x0de, 0x0aa /*, 0x0eb this is sometimes being written as FF??*/);
|
||||
// Locate start of sector data
|
||||
pos = locatePattern(pos, trackNibbles, 0x0d5, 0x0aa, 0x0ad);
|
||||
// Determine offset in output data for sector
|
||||
//int offset = reverseLoopkup(currentSectorOrder, sector) * 256;
|
||||
int offset = currentSectorOrder[sector] * 256;
|
||||
// System.out.println("Sector "+sector+" maps to physical sector "+reverseLoopkup(currentSectorOrder, sector));
|
||||
// Decode sector data
|
||||
denibblizeSector(trackNibbles, pos + 3, trackData, offset);
|
||||
// Skip to end of sector
|
||||
pos = locatePattern(pos, trackNibbles, 0x0de, 0x0aa, 0x0eb);
|
||||
}
|
||||
// Write track to disk
|
||||
RandomAccessFile disk;
|
||||
try {
|
||||
disk = new RandomAccessFile(diskPath, "rws");
|
||||
disk.seek(headerLength + track * 256 * SECTOR_COUNT);
|
||||
disk.write(trackData);
|
||||
disk.close();
|
||||
} catch (FileNotFoundException ex) {
|
||||
Logger.getLogger(FloppyDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(FloppyDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Logger.getLogger(FloppyDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private int locatePattern(int pos, byte[] data, int... pattern) throws Throwable {
|
||||
int max = data.length;
|
||||
while (!matchPattern(pos, data, pattern)) {
|
||||
pos = (pos + 1) % data.length;
|
||||
max--;
|
||||
if (max < 0) {
|
||||
throw new Throwable("Could not match pattern!");
|
||||
}
|
||||
}
|
||||
// System.out.print("Found pattern at "+pos+": ");
|
||||
// for (int i : pattern) {System.out.print(Integer.toString( i & 0x0ff, 16)+" ");}
|
||||
// System.out.println();
|
||||
return pos;
|
||||
}
|
||||
|
||||
private boolean matchPattern(int pos, byte[] data, int... pattern) {
|
||||
int matched = 0;
|
||||
for (int i : pattern) {
|
||||
int d = data[pos] & 0x0ff;
|
||||
if (d != i) {
|
||||
if (matched > 1) {
|
||||
System.out.println("Warning: Issue when interpreting nibbilized disk data: at position " + pos + " pattern byte " + Integer.toString(i, 16) + " doesn't match " + Integer.toString(d, 16));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pos = (pos + 1) % data.length;
|
||||
matched++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void denibblizeSector(byte[] source, int pos, byte[] trackData, int offset) {
|
||||
int[] temp = new int[342];
|
||||
int current = pos;
|
||||
int last = 0;
|
||||
// Un-encode raw data, leaving with pre-nibblized bytes
|
||||
for (int i = temp.length - 1; i > 255; i--) {
|
||||
int t = NIBBLE_62_REVERSE[0x0ff & source[current++]];
|
||||
temp[i] = t ^ last;
|
||||
last ^= t;
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
int t = NIBBLE_62_REVERSE[0x0ff & source[current++]];
|
||||
temp[i] = t ^ last;
|
||||
last ^= t;
|
||||
}
|
||||
|
||||
// Now decode the pre-nibblized bytes
|
||||
int p = temp.length - 1;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
int a = (temp[i] << 2);
|
||||
a = a + ((temp[p] & 1) << 1) + ((temp[p] & 2) >> 1);
|
||||
trackData[i + offset] = (byte) a;
|
||||
temp[p] = temp[p] >> 2;
|
||||
p--;
|
||||
if (p < 256) {
|
||||
p = temp.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int reverseLoopkup(int[] table, int value) {
|
||||
for (int i = 0; i < table.length; i++) {
|
||||
if (table[i] == value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.apple2e.softswitch.MemorySoftSwitch;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Device;
|
||||
import jace.core.KeyHandler;
|
||||
import jace.core.Keyboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.state.Stateful;
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
import java.awt.Robot;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Simple implementation of joystick support that supports mouse or keyboard.
|
||||
* Actual joystick support isn't offered by Java at this moment in time
|
||||
* unfortunately.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Stateful
|
||||
public class Joystick extends Device {
|
||||
|
||||
@ConfigurableField(name = "Enabled", shortName = "enabled", description = "If unchecked, then there is no joystick support.")
|
||||
public boolean enabled;
|
||||
@ConfigurableField(name = "Center Mouse", description = "Moves mouse back to the center of the screen, can get annoying.")
|
||||
public boolean centerMouse;
|
||||
@ConfigurableField(name = "Use keyboard", shortName = "useKeys", description = "Arrow keys will control joystick instead of the mouse.")
|
||||
public boolean useKeyboard;
|
||||
@ConfigurableField(name = "Hog keypresses", shortName = "hog", description = "Key presses will not be sent to emulator.")
|
||||
public boolean hogKeyboard;
|
||||
public int port;
|
||||
@Stateful
|
||||
public int x = 0;
|
||||
@Stateful
|
||||
public int y = 0;
|
||||
private int joyX = 0;
|
||||
private int joyY = 0;
|
||||
MemorySoftSwitch xSwitch;
|
||||
MemorySoftSwitch ySwitch;
|
||||
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
Point lastMouseLocation;
|
||||
Robot robot;
|
||||
Point centerPoint;
|
||||
|
||||
public Joystick(int port) {
|
||||
centerPoint = new Point(screenSize.width / 2, screenSize.height / 2);
|
||||
this.port = port;
|
||||
if (port == 0) {
|
||||
xSwitch = (MemorySoftSwitch) SoftSwitches.PDL0.getSwitch();
|
||||
ySwitch = (MemorySoftSwitch) SoftSwitches.PDL1.getSwitch();
|
||||
} else {
|
||||
xSwitch = (MemorySoftSwitch) SoftSwitches.PDL2.getSwitch();
|
||||
ySwitch = (MemorySoftSwitch) SoftSwitches.PDL3.getSwitch();
|
||||
}
|
||||
lastMouseLocation = MouseInfo.getPointerInfo().getLocation();
|
||||
try {
|
||||
robot = new Robot();
|
||||
} catch (AWTException ex) {
|
||||
Logger.getLogger(Joystick.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
public boolean leftPressed = false;
|
||||
public boolean rightPressed = false;
|
||||
public boolean upPressed = false;
|
||||
public boolean downPressed = false;
|
||||
|
||||
private void readJoystick() {
|
||||
if (useKeyboard) {
|
||||
joyX = leftPressed ? (rightPressed ? 128 : 0) : (rightPressed ? 255 : 128);
|
||||
joyY = upPressed ? (downPressed ? 128 : 0) : (downPressed ? 255 : 128);
|
||||
} else {
|
||||
Point l = MouseInfo.getPointerInfo().getLocation();
|
||||
if (l.x < lastMouseLocation.x) {
|
||||
joyX = 0;
|
||||
} else if (l.x > lastMouseLocation.x) {
|
||||
joyX = 255;
|
||||
} else {
|
||||
joyX = 128;
|
||||
}
|
||||
|
||||
if (l.y < lastMouseLocation.y) {
|
||||
joyY = 0;
|
||||
} else if (l.y > lastMouseLocation.y) {
|
||||
joyY = 255;
|
||||
} else {
|
||||
joyY = 128;
|
||||
}
|
||||
|
||||
if (centerMouse) {
|
||||
lastMouseLocation = centerPoint;
|
||||
robot.mouseMove(centerPoint.x, centerPoint.y);
|
||||
} else {
|
||||
if (l.x <= 20) {
|
||||
robot.mouseMove(20, l.y);
|
||||
l = MouseInfo.getPointerInfo().getLocation();
|
||||
}
|
||||
if ((l.x + 21) == screenSize.getWidth()) {
|
||||
robot.mouseMove((int) (screenSize.getWidth() - 20), l.y);
|
||||
l = MouseInfo.getPointerInfo().getLocation();
|
||||
}
|
||||
if (l.y <= 20) {
|
||||
robot.mouseMove(l.x, 20);
|
||||
l = MouseInfo.getPointerInfo().getLocation();
|
||||
}
|
||||
if ((l.y + 21) == screenSize.getHeight()) {
|
||||
robot.mouseMove(l.x, (int) (screenSize.getHeight() - 20));
|
||||
l = MouseInfo.getPointerInfo().getLocation();
|
||||
}
|
||||
|
||||
lastMouseLocation = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "Joystick (port " + port + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "joy" + port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
boolean finished = true;
|
||||
if (x > 0) {
|
||||
if (--x == 0) {
|
||||
xSwitch.setState(false);
|
||||
} else {
|
||||
finished = false;
|
||||
}
|
||||
}
|
||||
if (y > 0) {
|
||||
if (--y == 0) {
|
||||
ySwitch.setState(false);
|
||||
} else {
|
||||
finished = false;
|
||||
}
|
||||
}
|
||||
if (finished) {
|
||||
setRun(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
registerListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
removeListeners();
|
||||
}
|
||||
|
||||
public void reconfigure() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
if (enabled) {
|
||||
registerListeners();
|
||||
} else {
|
||||
removeListeners();
|
||||
}
|
||||
}
|
||||
RAMListener listener = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
|
||||
@Override
|
||||
protected void doConfig() {
|
||||
setScopeStart(0x0C070);
|
||||
setScopeEnd(0x0C07f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEvent(RAMEvent e) {
|
||||
setRun(true);
|
||||
readJoystick();
|
||||
// if (x <= 0) {
|
||||
xSwitch.setState(true);
|
||||
x = 10 + joyX * 11;
|
||||
// }
|
||||
// if (y <= 0) {
|
||||
ySwitch.setState(true);
|
||||
y = 10 + joyY * 11;
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
private void registerListeners() {
|
||||
Computer.getComputer().getMemory().addListener(listener);
|
||||
if (useKeyboard) {
|
||||
System.out.println("Registering key handlers");
|
||||
Keyboard.registerKeyHandler(new KeyHandler(KeyEvent.VK_LEFT, -1) {
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent e) {
|
||||
leftPressed = false;
|
||||
return hogKeyboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyDown(KeyEvent e) {
|
||||
leftPressed = true;
|
||||
return hogKeyboard;
|
||||
}
|
||||
}, this);
|
||||
Keyboard.registerKeyHandler(new KeyHandler(KeyEvent.VK_RIGHT, -1) {
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent e) {
|
||||
rightPressed = false;
|
||||
return hogKeyboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyDown(KeyEvent e) {
|
||||
rightPressed = true;
|
||||
return hogKeyboard;
|
||||
}
|
||||
}, this);
|
||||
Keyboard.registerKeyHandler(new KeyHandler(KeyEvent.VK_UP, -1) {
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent e) {
|
||||
upPressed = false;
|
||||
return hogKeyboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyDown(KeyEvent e) {
|
||||
upPressed = true;
|
||||
return hogKeyboard;
|
||||
}
|
||||
}, this);
|
||||
Keyboard.registerKeyHandler(new KeyHandler(KeyEvent.VK_DOWN, -1) {
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent e) {
|
||||
downPressed = false;
|
||||
return hogKeyboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleKeyDown(KeyEvent e) {
|
||||
downPressed = true;
|
||||
return hogKeyboard;
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeListeners() {
|
||||
Computer.getComputer().getMemory().removeListener(listener);
|
||||
Keyboard.unregisterAllHandlers(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,551 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.config.Name;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.sound.midi.InvalidMidiDataException;
|
||||
import javax.sound.midi.MidiDevice;
|
||||
import javax.sound.midi.MidiSystem;
|
||||
import javax.sound.midi.MidiUnavailableException;
|
||||
import javax.sound.midi.ShortMessage;
|
||||
import javax.sound.midi.Synthesizer;
|
||||
|
||||
/**
|
||||
* Partial implementation of Passport midi card, supporting midi output routed
|
||||
* to the java midi synth for playback. Compatible with Ultima V. Card
|
||||
* operational notes taken from the Passport MIDI interface manual
|
||||
* ftp://ftp.apple.asimov.net/pub/apple_II/documentation/hardware/misc/passport_midi.pdf
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Name(value = "Passport Midi Interface", description = "MIDI sound card")
|
||||
public class PassportMidiInterface extends Card {
|
||||
|
||||
@Override
|
||||
protected void handleC8FirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// There is no rom on this card, so nothing to do here
|
||||
}
|
||||
|
||||
// MIDI timing: 31250 BPS, 8-N-1 (roughly 3472k per second)
|
||||
public static enum TIMER_MODE {
|
||||
|
||||
continuous, singleShot, freqComparison, pulseComparison
|
||||
};
|
||||
|
||||
public static class PTMTimer {
|
||||
// Configuration values
|
||||
|
||||
public boolean prescaledTimer = false; // Only available on Timer 3
|
||||
public boolean enableClock = false; // False == use CX clock input
|
||||
public boolean dual8BitMode = false;
|
||||
public TIMER_MODE mode = TIMER_MODE.continuous;
|
||||
public boolean irqEnabled = false;
|
||||
public boolean counterOutputEnable = false;
|
||||
// Set by data latches
|
||||
public Long duration = 0L;
|
||||
// Run values
|
||||
public boolean irqRequested = true;
|
||||
public Long value = 0L;
|
||||
}
|
||||
// I/O registers
|
||||
// --- 6840 PTM
|
||||
public static final int TIMER_CONTROL_1 = 0;
|
||||
public static final int TIMER_CONTROL_2 = 1;
|
||||
public static final int TIMER1_MSB = 2;
|
||||
public static final int TIMER1_LSB = 3;
|
||||
public static final int TIMER2_MSB = 4;
|
||||
public static final int TIMER2_LSB = 5;
|
||||
// (Most likely not used)
|
||||
public static final int TIMER3_MSB = 6;
|
||||
public static final int TIMER3_LSB = 7;
|
||||
// --- 6850 ACIA registers (write)
|
||||
public static final int ACIA_CONTROL = 8;
|
||||
public static final int ACIA_SEND = 9;
|
||||
// --- 6850 ACIA registers (read)
|
||||
public static final int ACIA_STATUS = 8;
|
||||
public static final int ACIA_RECV = 9;
|
||||
// --- Drums
|
||||
public static final int DRUM_SYNC_SET = 0x0e;
|
||||
public static final int DRUM_SYNC_CLEAR = 0x0f;
|
||||
//---------------------------------------------------------
|
||||
// PTM control values (register 1,2 and 3)
|
||||
public static final int PTM_START_TIMERS = 0;
|
||||
public static final int PTM_STOP_TIMERS = 1;
|
||||
public static final int PTM_RESET = 67;
|
||||
// PTM select values (register 2 only) -- modifies what Reg 1 points to
|
||||
public static final int PTM_SELECT_REG_1 = 1;
|
||||
public static final int PTM_SELECT_REG_3 = 0;
|
||||
// PTM select values (register 3 only)
|
||||
public static final int TIMER_3_PRESCALED = 1;
|
||||
public static final int TIMER_3_NOT_PRESCALED = 0;
|
||||
// PTM bit values
|
||||
public static final int PTM_CLOCK_SOURCE = 2; // Bit 1
|
||||
// 0 = external, 2 = internal clock
|
||||
public static final int PTM_LATCH_IS_16_BIT = 4; // Bit 2
|
||||
// 0 = 16-bit, 4 = dual 8-bit
|
||||
// Bits 3-5
|
||||
// 5 4 3
|
||||
public static final int PTM_CONTINUOUS = 0; // 0 x 0
|
||||
public static final int PTM_SINGLE_SHOT = 32; // 1 x 0
|
||||
public static final int PTM_FREQ_COMP = 8; // x 0 1
|
||||
public static final int PTM_PULSE_COMP = 24; // x 1 1
|
||||
public static final int PTM_IRQ_ENABLED = 64; // Bit 6
|
||||
// 64 = IRQ Enabled, 0 = IRQ Masked
|
||||
public static final int PTM_OUTPUT_ENABLED = 128; // Bit 7
|
||||
// 128 = Timer output enabled, 0 = disabled
|
||||
// ACIA control values
|
||||
// Reset == Master reset + even parity + 2 stop bits + 8 bit + No interrupts (??)
|
||||
public static final int ACIA_RESET = 19;
|
||||
public static final int ACIA_MASK_INTERRUPTS = 17;
|
||||
public static final int ACIA_OFF = 21;
|
||||
// Counter * 1 + RTS = low, transmit interrupt enabled
|
||||
public static final int ACIA_INT_ON_SEND = 49;
|
||||
// Counter * 1 + RTS = high, transmit interrupt disabled + Interrupt on receive
|
||||
public static final int ACIA_INT_ON_RECV = 145;
|
||||
// Counter * 1 + RTS = low, transmit interrupt enabled + Interrupt on receive
|
||||
public static final int ACIA_INT_ON_SEND_AND_RECV = 177;
|
||||
// ACIA control register values
|
||||
// --- Bits 1 and 0 control counter divide select
|
||||
public static final int ACIA_COUNTER_1 = 0;
|
||||
public static final int ACIA_COUNTER_16 = 1;
|
||||
public static final int ACIA_COUNTER_64 = 2;
|
||||
public static final int ACIA_MASTER_RESET = 3;
|
||||
// Midi is always transmitted 8-N-1
|
||||
public static final int ACIA_ODD_PARITY = 4; // 4 = odd, 0 = even
|
||||
public static final int ACIA_STOP_BITS_1 = 8; // 8 = 1 stop bit, 0 = 2 stop bits
|
||||
public static final int ACIA_WORD_LENGTH_8 = 16; // 16 = 8-bit, 0 = 7-bit
|
||||
// --- Bits 5 and 6 control interrupts
|
||||
// 6 5
|
||||
// 0 0 RTS = low, transmit interrupt disabled
|
||||
// 0 1 RTS = low, transmit interrupt enabled
|
||||
// 1 0 RTS = high, transmit interrupt disabled
|
||||
// 1 1 RTS = low, Transmit break, trasmit interrupt disabled
|
||||
public static final int ACIA_RECV_INTERRUPT = 128; // 128 = interrupt on receive, 0 = no interrupt
|
||||
// PTM configuration
|
||||
private boolean ptmTimer3Selected = false; // When true, reg 1 points at timer 3
|
||||
private boolean ptmTimersActive = false; // When true, timers run constantly
|
||||
private PTMTimer[] ptmTimer = {
|
||||
new PTMTimer(),
|
||||
new PTMTimer(),
|
||||
new PTMTimer()
|
||||
};
|
||||
private boolean ptmStatusReadSinceIRQ = false;
|
||||
// ---------------------- ACIA CONFIGURATION
|
||||
private boolean aciaInterruptOnSend = false;
|
||||
private boolean aciaInterruptOnReceive = false;
|
||||
// ---------------------- ACIA STATUS BITS
|
||||
// True when MIDI IN receives a byte
|
||||
private boolean receivedACIAByte = false;
|
||||
// True when data is not transmitting (always true because we aren't really doing wire transmission);
|
||||
private boolean transmitACIAEmpty = true;
|
||||
// True if another byte is received before the previous byte was processed
|
||||
private boolean receiverACIAOverrun = false;
|
||||
// True if ACIA generated interrupt request
|
||||
private boolean irqRequestedACIA = false;
|
||||
//--- the synth
|
||||
private Synthesizer synth;
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
// TODO: Deactivate card
|
||||
suspend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean suspend() {
|
||||
// TODO: Deactivate card
|
||||
suspendACIA();
|
||||
return super.suspend();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// No firmware, so do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
switch (type) {
|
||||
case READ_DATA:
|
||||
int returnValue = 0;
|
||||
switch (register) {
|
||||
case ACIA_STATUS:
|
||||
returnValue = getACIAStatus();
|
||||
break;
|
||||
case ACIA_RECV:
|
||||
returnValue = getACIARecieve();
|
||||
break;
|
||||
//TODO: Implement PTM registers
|
||||
case TIMER_CONTROL_1:
|
||||
// Technically it's not supposed to return anything...
|
||||
returnValue = getPTMStatus();
|
||||
break;
|
||||
case TIMER_CONTROL_2:
|
||||
returnValue = getPTMStatus();
|
||||
break;
|
||||
case TIMER1_LSB:
|
||||
returnValue = (int) (ptmTimer[0].value & 0x0ff);
|
||||
if (ptmStatusReadSinceIRQ) {
|
||||
ptmTimer[0].irqRequested = false;
|
||||
}
|
||||
break;
|
||||
case TIMER1_MSB:
|
||||
returnValue = (int) (ptmTimer[0].value >> 8) & 0x0ff;
|
||||
if (ptmStatusReadSinceIRQ) {
|
||||
ptmTimer[0].irqRequested = false;
|
||||
}
|
||||
break;
|
||||
case TIMER2_LSB:
|
||||
returnValue = (int) (ptmTimer[1].value & 0x0ff);
|
||||
if (ptmStatusReadSinceIRQ) {
|
||||
ptmTimer[1].irqRequested = false;
|
||||
}
|
||||
break;
|
||||
case TIMER2_MSB:
|
||||
returnValue = (int) (ptmTimer[1].value >> 8) & 0x0ff;
|
||||
if (ptmStatusReadSinceIRQ) {
|
||||
ptmTimer[1].irqRequested = false;
|
||||
}
|
||||
break;
|
||||
case TIMER3_LSB:
|
||||
returnValue = (int) (ptmTimer[2].value & 0x0ff);
|
||||
if (ptmStatusReadSinceIRQ) {
|
||||
ptmTimer[2].irqRequested = false;
|
||||
}
|
||||
break;
|
||||
case TIMER3_MSB:
|
||||
returnValue = (int) (ptmTimer[2].value >> 8) & 0x0ff;
|
||||
if (ptmStatusReadSinceIRQ) {
|
||||
ptmTimer[2].irqRequested = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Passport midi read unrecognized, port " + register);
|
||||
}
|
||||
|
||||
e.setNewValue(returnValue);
|
||||
// System.out.println("Passport I/O read register " + register + " == " + returnValue);
|
||||
break;
|
||||
case WRITE:
|
||||
int v = e.getNewValue() & 0x0ff;
|
||||
// System.out.println("Passport I/O write register " + register + " == " + v);
|
||||
switch (register) {
|
||||
case ACIA_CONTROL:
|
||||
processACIAControl(v);
|
||||
break;
|
||||
case ACIA_SEND:
|
||||
processACIASend(v);
|
||||
break;
|
||||
case TIMER_CONTROL_1:
|
||||
if (ptmTimer3Selected) {
|
||||
// System.out.println("Configuring timer 3");
|
||||
ptmTimer[2].prescaledTimer = ((v & TIMER_3_PRESCALED) != 0);
|
||||
processPTMConfiguration(ptmTimer[2], v);
|
||||
} else {
|
||||
// System.out.println("Configuring timer 1");
|
||||
if ((v & PTM_STOP_TIMERS) == 0) {
|
||||
startPTM();
|
||||
} else {
|
||||
stopPTM();
|
||||
}
|
||||
processPTMConfiguration(ptmTimer[0], v);
|
||||
}
|
||||
break;
|
||||
case TIMER_CONTROL_2:
|
||||
// System.out.println("Configuring timer 2");
|
||||
ptmTimer3Selected = ((v & PTM_SELECT_REG_1) == 0);
|
||||
processPTMConfiguration(ptmTimer[1], v);
|
||||
break;
|
||||
case TIMER1_LSB:
|
||||
ptmTimer[0].duration = (ptmTimer[0].duration & 0x0ff00) | v;
|
||||
break;
|
||||
case TIMER1_MSB:
|
||||
ptmTimer[0].duration = (ptmTimer[0].duration & 0x0ff) | (v << 8);
|
||||
break;
|
||||
case TIMER2_LSB:
|
||||
ptmTimer[1].duration = (ptmTimer[1].duration & 0x0ff00) | v;
|
||||
break;
|
||||
case TIMER2_MSB:
|
||||
ptmTimer[1].duration = (ptmTimer[1].duration & 0x0ff) | (v << 8);
|
||||
break;
|
||||
case TIMER3_LSB:
|
||||
ptmTimer[2].duration = (ptmTimer[2].duration & 0x0ff00) | v;
|
||||
break;
|
||||
case TIMER3_MSB:
|
||||
ptmTimer[2].duration = (ptmTimer[2].duration & 0x0ff00) | (v << 8);
|
||||
break;
|
||||
default:
|
||||
System.out.println("Passport midi write unrecognized, port " + register);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (ptmTimersActive) {
|
||||
for (PTMTimer t : ptmTimer) {
|
||||
// if (t.duration == 0) {
|
||||
// continue;
|
||||
// }
|
||||
t.value--;
|
||||
if (t.value < 0) {
|
||||
// TODO: interrupt dual 8-bit mode, whatver that is!
|
||||
if (t.irqEnabled) {
|
||||
// System.out.println("Timer generating interrupt!");
|
||||
t.irqRequested = true;
|
||||
Computer.getComputer().getCpu().generateInterrupt();
|
||||
ptmStatusReadSinceIRQ = false;
|
||||
}
|
||||
if (t.mode == TIMER_MODE.continuous || t.mode == TIMER_MODE.freqComparison) {
|
||||
t.value = t.duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeviceName() {
|
||||
return "Passport MIDI Controller";
|
||||
}
|
||||
|
||||
//------------------------------------------------------ PTM
|
||||
private void processPTMConfiguration(PTMTimer timer, int val) {
|
||||
timer.enableClock = (val & PTM_CLOCK_SOURCE) != 0;
|
||||
timer.dual8BitMode = (val & PTM_LATCH_IS_16_BIT) != 0;
|
||||
switch (val & 56) {
|
||||
// Evaluate bits 3, 4 and 5 to determine mode
|
||||
case PTM_CONTINUOUS:
|
||||
timer.mode = TIMER_MODE.continuous;
|
||||
break;
|
||||
case PTM_PULSE_COMP:
|
||||
timer.mode = TIMER_MODE.pulseComparison;
|
||||
break;
|
||||
case PTM_FREQ_COMP:
|
||||
timer.mode = TIMER_MODE.freqComparison;
|
||||
break;
|
||||
case PTM_SINGLE_SHOT:
|
||||
timer.mode = TIMER_MODE.singleShot;
|
||||
break;
|
||||
default:
|
||||
timer.mode = TIMER_MODE.continuous;
|
||||
break;
|
||||
}
|
||||
timer.irqEnabled = (val & PTM_IRQ_ENABLED) != 0;
|
||||
timer.counterOutputEnable = (val & PTM_OUTPUT_ENABLED) != 0;
|
||||
}
|
||||
|
||||
private void stopPTM() {
|
||||
// System.out.println("Passport timers halted");
|
||||
ptmTimersActive = false;
|
||||
}
|
||||
|
||||
private void startPTM() {
|
||||
// System.out.println("Passport timers started");
|
||||
ptmTimersActive = true;
|
||||
ptmTimer[0].irqRequested = false;
|
||||
ptmTimer[1].irqRequested = false;
|
||||
ptmTimer[2].irqRequested = false;
|
||||
ptmTimer[0].value = ptmTimer[0].duration;
|
||||
ptmTimer[1].value = ptmTimer[1].duration;
|
||||
ptmTimer[2].value = ptmTimer[2].duration;
|
||||
|
||||
}
|
||||
|
||||
// Bits 0, 1 and 2 == IRQ requested from timer 1, 2 or 3
|
||||
// Bit 7 = Any IRQ
|
||||
private int getPTMStatus() {
|
||||
int status = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
PTMTimer t = ptmTimer[i];
|
||||
if (t.irqRequested && t.irqEnabled) {
|
||||
ptmStatusReadSinceIRQ = true;
|
||||
status |= (1 << i);
|
||||
status |= 128;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
//------------------------------------------------------ ACIA
|
||||
/*
|
||||
ACIA status register
|
||||
Bit 0 = Receive data register full
|
||||
Bit 1 = Transmit data register empty
|
||||
Bits 2 and 3 pertain to modem (DCD and CTS, so ignore)
|
||||
Bit 4 = Framing error
|
||||
Bit 5 = Receiver overrun
|
||||
Bit 6 = Partity error (not used by MIDI)
|
||||
Bit 7 = Interrupt request
|
||||
*/
|
||||
|
||||
private int getACIAStatus() {
|
||||
int status = 0;
|
||||
if (receivedACIAByte) {
|
||||
status |= 1;
|
||||
}
|
||||
if (transmitACIAEmpty) {
|
||||
status |= 2;
|
||||
}
|
||||
if (receiverACIAOverrun) {
|
||||
status |= 32;
|
||||
}
|
||||
if (irqRequestedACIA) {
|
||||
status |= 128;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// TODO: Implement MIDI IN... some day
|
||||
private int getACIARecieve() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void processACIAControl(int value) {
|
||||
if ((value & 0x03) == ACIA_MASTER_RESET) {
|
||||
resume();
|
||||
}
|
||||
}
|
||||
ShortMessage currentMessage;
|
||||
int currentMessageStatus;
|
||||
int currentMessageData1;
|
||||
int currentMessageData2;
|
||||
int messageSize = 255;
|
||||
int currentMessageReceived = 0;
|
||||
|
||||
private void processACIASend(int value) {
|
||||
if (!isRunning()) {
|
||||
// System.err.println("ACIA not active!");
|
||||
return;
|
||||
} else {
|
||||
// System.out.println("ACIA send "+value);
|
||||
}
|
||||
// First off try to finish off previous command already in play
|
||||
boolean sendMessage = false;
|
||||
if (currentMessage != null) {
|
||||
if ((value & 0x080) > 0) {
|
||||
// Any command byte received means we finished receiving another command
|
||||
// and valid or not, process it as-is
|
||||
if (currentMessage != null) {
|
||||
sendMessage = true;
|
||||
}
|
||||
// If there is no current message, then we'll pick this up afterwards...
|
||||
} else {
|
||||
// If we receive a data byte ( < 128 ) then check if we have the right size
|
||||
// if so, then the command was completely received, and it's time to send it.
|
||||
currentMessageReceived++;
|
||||
if (currentMessageReceived >= messageSize) {
|
||||
sendMessage = true;
|
||||
}
|
||||
if (currentMessageReceived == 1) {
|
||||
currentMessageData1 = value;
|
||||
} else {
|
||||
// Possibly redundant, but there's no reason a message should be longer than this...
|
||||
currentMessageData2 = value;
|
||||
sendMessage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a command to send, then do it
|
||||
if (sendMessage == true) {
|
||||
if (synth != null && synth.isOpen()) {
|
||||
// Send message
|
||||
try {
|
||||
// System.out.println("Sending MIDI message "+currentMessageStatus+","+currentMessageData1+","+currentMessageData2);
|
||||
currentMessage.setMessage(currentMessageStatus, currentMessageData1, currentMessageData2);
|
||||
synth.getReceiver().send(currentMessage, -1L);
|
||||
} catch (InvalidMidiDataException ex) {
|
||||
Logger.getLogger(PassportMidiInterface.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (MidiUnavailableException ex) {
|
||||
Logger.getLogger(PassportMidiInterface.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
currentMessage = null;
|
||||
}
|
||||
|
||||
// Do we have a new command byte?
|
||||
if ((value & 0x080) > 0) {
|
||||
// Start a new message
|
||||
currentMessage = new ShortMessage();
|
||||
currentMessageStatus = value;
|
||||
currentMessageData1 = 0;
|
||||
currentMessageData2 = 0;
|
||||
try {
|
||||
currentMessage.setMessage(currentMessageStatus, 0, 0);
|
||||
messageSize = currentMessage.getLength();
|
||||
} catch (InvalidMidiDataException ex) {
|
||||
messageSize = 0;
|
||||
}
|
||||
currentMessageReceived = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
if (isRunning() && synth != null && synth.isOpen()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
MidiDevice.Info[] devices = MidiSystem.getMidiDeviceInfo();
|
||||
if (devices.length == 0) {
|
||||
System.out.println("No MIDI devices found");
|
||||
} else {
|
||||
for (MidiDevice.Info dev : devices) {
|
||||
System.out.println("MIDI Device found: " + dev);
|
||||
if (dev.getName().contains("Java Sound")) {
|
||||
if (dev instanceof Synthesizer) {
|
||||
synth = (Synthesizer) dev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (synth == null) {
|
||||
synth = MidiSystem.getSynthesizer();
|
||||
}
|
||||
if (synth != null) {
|
||||
System.out.println("Selected MIDI device: " + synth.getDeviceInfo().getName());
|
||||
synth.open();
|
||||
super.resume();
|
||||
}
|
||||
} catch (MidiUnavailableException ex) {
|
||||
System.out.println("Could not open MIDI synthesizer");
|
||||
ex.printStackTrace();
|
||||
Logger.getLogger(PassportMidiInterface.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void suspendACIA() {
|
||||
// TODO: Stop ACIA thread...
|
||||
if (synth != null && synth.isOpen()) {
|
||||
synth.close();
|
||||
synth = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Helper functions for prodos drivers
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class ProdosDriver {
|
||||
public static int MLI_COMMAND = 0x042;
|
||||
public static int MLI_UNITNUMBER = 0x043;
|
||||
public static int MLI_BUFFER_ADDRESS = 0x044;
|
||||
public static int MLI_BLOCK_NUMBER = 0x046;
|
||||
|
||||
public static enum MLI_RETURN {
|
||||
NO_ERROR(0), IO_ERROR(0x027), NO_DEVICE(0x028), WRITE_PROTECTED(0x02B);
|
||||
public int intValue;
|
||||
|
||||
MLI_RETURN(int val) {
|
||||
intValue = val;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum MLI_COMMAND_TYPE {
|
||||
STATUS(0x0), READ(0x01), WRITE(0x02), FORMAT(0x03);
|
||||
public int intValue;
|
||||
|
||||
MLI_COMMAND_TYPE(int val) {
|
||||
intValue = val;
|
||||
}
|
||||
|
||||
public static MLI_COMMAND_TYPE fromInt(int value) {
|
||||
for (MLI_COMMAND_TYPE c : values()) {
|
||||
if (c.intValue == value) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
abstract public boolean changeUnit(int unitNumber);
|
||||
abstract public int getSize();
|
||||
abstract public boolean isWriteProtected();
|
||||
abstract public void mliFormat() throws IOException;
|
||||
abstract public void mliRead(int block, int bufferAddress) throws IOException;
|
||||
abstract public void mliWrite(int block, int bufferAddress) throws IOException;
|
||||
abstract public Card getOwner();
|
||||
|
||||
public void handleMLI() {
|
||||
int returnCode = prodosMLI().intValue;
|
||||
MOS65C02 cpu = (MOS65C02) Computer.getComputer().getCpu();
|
||||
cpu.A = returnCode;
|
||||
// Clear carry flag if no error, otherwise set carry flag
|
||||
cpu.C = (returnCode == 0x00) ? 00 : 01;
|
||||
}
|
||||
|
||||
private MLI_RETURN prodosMLI() {
|
||||
try {
|
||||
RAM memory = Computer.getComputer().getMemory();
|
||||
int cmd = memory.readRaw(MLI_COMMAND);
|
||||
MLI_COMMAND_TYPE command = MLI_COMMAND_TYPE.fromInt(cmd);
|
||||
int unit = (memory.readWordRaw(MLI_UNITNUMBER) & 0x080) > 0 ? 1 : 0;
|
||||
if (changeUnit(unit) == false) {
|
||||
return MLI_RETURN.NO_DEVICE;
|
||||
}
|
||||
int block = memory.readWordRaw(MLI_BLOCK_NUMBER);
|
||||
int bufferAddress = memory.readWordRaw(MLI_BUFFER_ADDRESS);
|
||||
// System.out.println(getOwner().getName()+" MLI Call "+command+", unit "+unit+" Block "+block+" --> "+Integer.toHexString(bufferAddress));
|
||||
if (command == null) {
|
||||
System.out.println(getOwner().getName()+" Mass storage given bogus command (" + Integer.toHexString(cmd) + "), returning I/O error");
|
||||
return MLI_RETURN.IO_ERROR;
|
||||
}
|
||||
switch (command) {
|
||||
case STATUS:
|
||||
int blocks = getSize();
|
||||
MOS65C02 cpu = (MOS65C02) Computer.getComputer().getCpu();
|
||||
cpu.X = blocks & 0x0ff;
|
||||
cpu.Y = (blocks >> 8) & 0x0ff;
|
||||
if (isWriteProtected()) {
|
||||
return MLI_RETURN.WRITE_PROTECTED;
|
||||
}
|
||||
break;
|
||||
case FORMAT:
|
||||
mliFormat();
|
||||
case READ:
|
||||
mliRead(block, bufferAddress);
|
||||
break;
|
||||
case WRITE:
|
||||
mliWrite(block, bufferAddress);
|
||||
break;
|
||||
default:
|
||||
System.out.println(getOwner().getName()+" MLI given bogus command (" + Integer.toHexString(cmd) + " = " + command.name() + "), returning I/O error");
|
||||
return MLI_RETURN.IO_ERROR;
|
||||
}
|
||||
return MLI_RETURN.NO_ERROR;
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
return MLI_RETURN.WRITE_PROTECTED;
|
||||
} catch (IOException ex) {
|
||||
System.out.println(getOwner().getName()+" Encountered IO Error, returning error: " + ex.getMessage());
|
||||
return MLI_RETURN.IO_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import jace.hardware.massStorage.CardMassStorage;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Generic abstraction of a smartport device.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class SmartportDriver {
|
||||
|
||||
public static enum ERROR_CODE {
|
||||
NO_ERROR(0), INVALID_COMMAND(0x01), BAD_PARAM_COUNT(0x04), INVALID_UNIT(0x011), INVALID_CODE(0x021), BAD_BLOCK_NUMBER(0x02d);
|
||||
int intValue;
|
||||
ERROR_CODE(int c) {
|
||||
intValue = c;
|
||||
}
|
||||
}
|
||||
|
||||
public void handleSmartport() {
|
||||
int returnCode = callSmartport().intValue;
|
||||
MOS65C02 cpu = (MOS65C02) Computer.getComputer().getCpu();
|
||||
cpu.A = returnCode;
|
||||
// Clear carry flag if no error, otherwise set carry flag
|
||||
cpu.C = (returnCode == 0x00) ? 00 : 01;
|
||||
}
|
||||
|
||||
private ERROR_CODE callSmartport() {
|
||||
MOS65C02 cpu = (MOS65C02) Computer.getComputer().getCpu();
|
||||
RAM ram = Computer.getComputer().getMemory();
|
||||
int callAddress = cpu.popWord() + 1;
|
||||
int command = ram.readRaw(callAddress);
|
||||
boolean extendedCall = command >= 0x040;
|
||||
// command &= 0x0f;
|
||||
// Modify stack so that RTS goes to the right place after the smartport device call
|
||||
//cpu.pushWord(callAddress + (extendedCall ? 5 : 3));
|
||||
// Kludge due to the CPU not getting the faked RTS opcode
|
||||
cpu.setProgramCounter(callAddress + (extendedCall ? 5 : 3));
|
||||
|
||||
// Calculate parameter address block
|
||||
int parmAddr = 0;
|
||||
if (!extendedCall) {
|
||||
parmAddr = ram.readWordRaw(callAddress + 1);
|
||||
} else {
|
||||
// Extended calls -- not gonna happen on this platform anyway
|
||||
int parmAddrLo = ram.readWordRaw(callAddress + 1);
|
||||
int parmAddrHi = ram.readWordRaw(callAddress + 3);
|
||||
parmAddr = parmAddrHi << 16 | parmAddrLo;
|
||||
}
|
||||
// Now process command
|
||||
System.out.println("Received command " + command + " with address block " + Integer.toHexString(parmAddr));
|
||||
byte numParms = ram.readRaw(parmAddr);
|
||||
int[] params = new int[16];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int value = 0x0ff & ram.readRaw(parmAddr + i);
|
||||
params[i] = value;
|
||||
System.out.print(Integer.toHexString(value) + " ");
|
||||
}
|
||||
System.out.println();
|
||||
int unitNumber = params[1];
|
||||
if (!changeUnit(unitNumber)) {
|
||||
System.out.println("Invalid unit: "+unitNumber);
|
||||
return ERROR_CODE.INVALID_UNIT;
|
||||
}
|
||||
int dataBuffer = params[2] | (params[3] << 8);
|
||||
|
||||
try {
|
||||
switch (command) {
|
||||
case 0: //Status
|
||||
return returnStatus(dataBuffer, params);
|
||||
case 1: //Read Block
|
||||
int blockNum = params[4] | (params[5] << 8) | (params[6] << 16);
|
||||
read(blockNum, dataBuffer);
|
||||
return ERROR_CODE.NO_ERROR;
|
||||
// System.out.println("reading "+blockNum+" to $"+Integer.toHexString(dataBuffer));
|
||||
case 2: //Write Block
|
||||
blockNum = params[4] | (params[5] << 8) | (params[6] << 16);
|
||||
write(blockNum, dataBuffer);
|
||||
return ERROR_CODE.NO_ERROR;
|
||||
case 3: //Format
|
||||
case 4: //Control
|
||||
case 5: //Init
|
||||
case 6: //Open
|
||||
case 7: //Close
|
||||
case 8: //Read
|
||||
case 9: //Write
|
||||
default:
|
||||
System.out.println("Unimplemented command "+command);
|
||||
return ERROR_CODE.INVALID_COMMAND;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardMassStorage.class.getName()).log(Level.SEVERE, null, ex);
|
||||
return ERROR_CODE.INVALID_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
abstract public boolean changeUnit(int unitNumber);
|
||||
abstract public void read(int blockNum, int buffer) throws IOException;
|
||||
abstract public void write(int blockNum, int buffer) throws IOException;
|
||||
abstract public ERROR_CODE returnStatus(int dataBuffer, int[] params);
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.config.Name;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMEvent.TYPE;
|
||||
import jace.core.Utility;
|
||||
import jace.hardware.ProdosDriver;
|
||||
import jace.hardware.SmartportDriver;
|
||||
import jace.library.MediaConsumer;
|
||||
import jace.library.MediaConsumerParent;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Hard disk and 800k floppy (smartport) controller card. HDV and 2MG images are
|
||||
* both supported.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
@Name("Mass Storage Device")
|
||||
public class CardMassStorage extends Card implements MediaConsumerParent {
|
||||
|
||||
MassStorageDrive drive1;
|
||||
MassStorageDrive drive2;
|
||||
|
||||
public CardMassStorage() {
|
||||
drive1 = new MassStorageDrive();
|
||||
drive2 = new MassStorageDrive();
|
||||
drive1.setIcon(Utility.loadIcon("drive-harddisk.png"));
|
||||
drive2.setIcon(Utility.loadIcon("drive-harddisk.png"));
|
||||
currentDrive = drive1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSlot(int slot) {
|
||||
super.setSlot(slot);
|
||||
drive1.getIcon().setDescription("S" + getSlot() + "D1");
|
||||
drive2.getIcon().setDescription("S" + getSlot() + "D2");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeviceName() {
|
||||
return "Mass Storage Device";
|
||||
}
|
||||
// boot0 stores cards*16 of boot device here
|
||||
static int SLT16 = 0x02B;
|
||||
// "rom" offset where device driver is called by MLI
|
||||
// static int DEVICE_DRIVER_OFFSET = 0x042;
|
||||
static int DEVICE_DRIVER_OFFSET = 0x0A;
|
||||
byte[] cardSignature = new byte[]{
|
||||
(byte) 0x0a9 /*NOP*/, 0x020, (byte) 0x0a9, 0x00,
|
||||
(byte) 0x0a9, 0x03 /*currentDisk cards*/, (byte) 0x0a9, 0x03c /*currentDisk cards*/,
|
||||
(byte) 0xd0, 0x07, 0x60, (byte) 0x0b0,
|
||||
0x01 /*firmware cards*/, 0x18, (byte) 0x0b0, 0x5a
|
||||
};
|
||||
Card theCard = this;
|
||||
public MassStorageDrive currentDrive;
|
||||
|
||||
public IDisk getCurrentDisk() {
|
||||
if (currentDrive != null) {
|
||||
return currentDrive.getCurrentDisk();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
ProdosDriver driver = new ProdosDriver() {
|
||||
@Override
|
||||
public boolean changeUnit(int unit) {
|
||||
currentDrive = unit == 0 ? drive1 : drive2;
|
||||
return getCurrentDisk() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return getCurrentDisk() != null ? getCurrentDisk().getSize() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteProtected() {
|
||||
return getCurrentDisk() != null ? getCurrentDisk().isWriteProtected() : true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mliFormat() throws IOException {
|
||||
getCurrentDisk().mliFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mliRead(int block, int bufferAddress) throws IOException {
|
||||
getCurrentDisk().mliRead(block, bufferAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mliWrite(int block, int bufferAddress) throws IOException {
|
||||
getCurrentDisk().mliWrite(block, bufferAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card getOwner() {
|
||||
return theCard;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
try {
|
||||
detach();
|
||||
|
||||
int pc = Computer.getComputer().getCpu().getProgramCounter();
|
||||
if (drive1.getCurrentDisk() != null && getSlot() == 7 && (pc == 0x0c65e || pc == 0x0c661)) {
|
||||
// If the computer is in a loop trying to boot from cards 6, fast-boot from here instead
|
||||
// This is a convenience to boot a hard-drive if the emulator has started waiting for a currentDisk
|
||||
currentDrive = drive1;
|
||||
getCurrentDisk().boot0(getSlot());
|
||||
Card[] cards = Computer.getComputer().getMemory().getAllCards();
|
||||
Motherboard.cancelSpeedRequest(cards[6]);
|
||||
}
|
||||
attach();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardMassStorage.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleC8FirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// There is no c8 rom for this card
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFirmwareAccess(int offset, TYPE type, int value, RAMEvent e) {
|
||||
MOS65C02 cpu = (MOS65C02) Computer.getComputer().getCpu();
|
||||
// System.out.println(e.getType()+" "+Integer.toHexString(e.getAddress())+" from instruction at "+Integer.toHexString(cpu.getProgramCounter()));
|
||||
if (type.isRead()) {
|
||||
Emulator.getFrame().addIndicator(this, currentDrive.getIcon());
|
||||
if (drive1.getCurrentDisk() == null && drive2.getCurrentDisk() == null) {
|
||||
e.setNewValue(0);
|
||||
return;
|
||||
}
|
||||
if (type == TYPE.EXECUTE) {
|
||||
// Virtual functions, handle accordingly
|
||||
String error;
|
||||
if (offset == 0x00) {
|
||||
// NOP unless otherwise specified
|
||||
e.setNewValue(0x0ea);
|
||||
try {
|
||||
if (drive1.getCurrentDisk() != null) {
|
||||
currentDrive = drive1;
|
||||
getCurrentDisk().boot0(getSlot());
|
||||
} else {
|
||||
// Patch for crash on start when no image is mounted
|
||||
e.setNewValue(0x060);
|
||||
}
|
||||
return;
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CardMassStorage.class.getName()).log(Level.SEVERE, null, ex);
|
||||
error = ex.getMessage();
|
||||
// Jump to the basic interpreter for now
|
||||
cpu.setProgramCounter(0x0dfff);
|
||||
int address = 0x0480;
|
||||
for (char c : error.toCharArray()) {
|
||||
Computer.getComputer().getMemory().write(address++, (byte) (c + 0x080), false, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (offset == DEVICE_DRIVER_OFFSET) {
|
||||
driver.handleMLI();
|
||||
} else if (offset == DEVICE_DRIVER_OFFSET + 3) {
|
||||
smartport.handleSmartport();
|
||||
} else {
|
||||
System.out.println("Call to unknown handler " + Integer.toString(e.getAddress(), 16) + "-- returning");
|
||||
}
|
||||
/* act like RTS was called */
|
||||
e.setNewValue(0x060);
|
||||
}
|
||||
}
|
||||
if (offset < 16) {
|
||||
e.setNewValue(cardSignature[offset]);
|
||||
} else {
|
||||
switch (offset) {
|
||||
// Disk capacity = 65536 blocks
|
||||
case 0x0FC:
|
||||
e.setNewValue(0x0ff);
|
||||
break;
|
||||
case 0x0FD:
|
||||
e.setNewValue(0x07f);
|
||||
break;
|
||||
// Status bits
|
||||
case 0x0FE:
|
||||
e.setNewValue(0x0D7);
|
||||
break;
|
||||
case 0x0FF:
|
||||
e.setNewValue(DEVICE_DRIVER_OFFSET);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIOAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
// Ignore IO registers
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// Nothing is done per CPU cycle
|
||||
}
|
||||
SmartportDriver smartport = new SmartportDriver() {
|
||||
@Override
|
||||
public boolean changeUnit(int unitNumber) {
|
||||
currentDrive = unitNumber == 1 ? drive1 : drive2;
|
||||
return getCurrentDisk() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(int blockNum, int buffer) throws IOException {
|
||||
getCurrentDisk().mliRead(blockNum, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int blockNum, int buffer) throws IOException {
|
||||
getCurrentDisk().mliWrite(blockNum, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ERROR_CODE returnStatus(int dataBuffer, int[] params) {
|
||||
return ERROR_CODE.NO_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
public MediaConsumer[] getConsumers() {
|
||||
return new MediaConsumer[]{drive1, drive2};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Prodos directory node
|
||||
* @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 DirectoryNode(ProdosVirtualDisk ownerFilesystem, File physicalDir) throws IOException {
|
||||
init(ownerFilesystem, physicalDir);
|
||||
}
|
||||
|
||||
|
||||
private void init(ProdosVirtualDisk ownerFilesystem, File physicalFile) throws IOException {
|
||||
setPhysicalFile(physicalFile);
|
||||
setType(EntryType.SUBDIRECTORY);
|
||||
setName(physicalFile.getName());
|
||||
setOwnerFilesystem(ownerFilesystem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDeallocate() {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
for (File f : files) {
|
||||
addFile(f);
|
||||
}
|
||||
Collections.sort(children, new Comparator<DiskNode>() {
|
||||
public int compare(DiskNode o1, DiskNode o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doRefresh() {
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* 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<String>();
|
||||
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 (!realFiles.isEmpty()) {
|
||||
success = false;
|
||||
// New files showed up -- deal with them!
|
||||
for (String fileName : realFiles) {
|
||||
addFile(new File(physicalFile, fileName));
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readBlock(int block, byte[] buffer) throws IOException {
|
||||
checkFile();
|
||||
if (block == 0) {
|
||||
generateHeader(buffer);
|
||||
for (int i=0; i < 12 && i < children.size(); i++)
|
||||
generateFileEntry(buffer, 4 + (i+1) * FILE_ENTRY_SIZE, i);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean accept(File file) {
|
||||
if (file.getName().endsWith("~")) return false;
|
||||
char c = file.getName().charAt(0);
|
||||
if (c == '.' || c == '~') {
|
||||
return false;
|
||||
}
|
||||
if (file.isHidden()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
generateName(buffer, 5, this);
|
||||
for (int i=0x014 ; i <= 0x01b; i++)
|
||||
buffer[i] = 0;
|
||||
generateTimestamp(buffer, 0x01c, getPhysicalFile().lastModified());
|
||||
// Prodos 1.9
|
||||
buffer[0x020] = 0x019;
|
||||
// Minimum version = 0 (no min)
|
||||
buffer[0x021] = 0x000;
|
||||
// Directory may be read/written to, may not be destroyed or renamed
|
||||
buffer[0x022] = 0x03;
|
||||
// Entry size
|
||||
buffer[0x023] = (byte) FILE_ENTRY_SIZE;
|
||||
// Entries per block
|
||||
buffer[0x024] = (byte) 0x0d;
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// Entry Type and length
|
||||
buffer[offset] = (byte) ((child.getType().code << 4) + child.getName().length());
|
||||
// Name
|
||||
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 + 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;
|
||||
// Minimum version = 0
|
||||
buffer[offset + 0x01d] = 0;
|
||||
// Access = all granted
|
||||
buffer[offset + 0x01e] = (byte) 0x0ff;
|
||||
// AUX type
|
||||
if (child instanceof FileNode)
|
||||
generateWord(buffer, offset + 0x01f, ((FileNode) child).loadAddress);
|
||||
// Modification date
|
||||
generateTimestamp(buffer, offset + 0x021, child.physicalFile.lastModified());
|
||||
// Key pointer for directory
|
||||
generateWord(buffer, offset + 0x025, getBaseBlock());
|
||||
}
|
||||
|
||||
private void generateTimestamp(byte[] buffer, int offset, long date) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(date);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
private void generateWord(byte[] buffer, int i, int value) {
|
||||
// Little endian format
|
||||
buffer[i] = (byte) (value & 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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Prodos file/directory node abstraction. This provides a lot of the glue for
|
||||
* maintaining some sort of state across the virtual prodos volume and the
|
||||
* physical disk folder or file represented by this node.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class DiskNode {
|
||||
|
||||
public enum EntryType {
|
||||
|
||||
DELETED(0),
|
||||
SEEDLING(1),
|
||||
SAPLING(2),
|
||||
TREE(3),
|
||||
SUBDIRECTORY(0x0D),
|
||||
SUBDIRECTORY_HEADER(0x0E),
|
||||
VOLUME_HEADER(0x0F);
|
||||
public int code;
|
||||
|
||||
EntryType(int c) {
|
||||
code = c;
|
||||
}
|
||||
}
|
||||
boolean allocated = false;
|
||||
long allocationTime = -1L;
|
||||
long lastCheckTime = -1L;
|
||||
int baseBlock = -1;
|
||||
List<DiskNode> additionalNodes;
|
||||
ProdosVirtualDisk ownerFilesystem;
|
||||
File physicalFile;
|
||||
DiskNode parent;
|
||||
List<DiskNode> children;
|
||||
private EntryType type;
|
||||
private String name;
|
||||
|
||||
public DiskNode() {
|
||||
additionalNodes = new ArrayList<DiskNode>();
|
||||
children = new ArrayList<DiskNode>();
|
||||
}
|
||||
|
||||
public boolean checkFile() throws IOException {
|
||||
allocate();
|
||||
if (physicalFile == null) {
|
||||
return false;
|
||||
}
|
||||
if (physicalFile.lastModified() != lastCheckTime) {
|
||||
lastCheckTime = physicalFile.lastModified();
|
||||
refresh();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void allocate() throws IOException {
|
||||
if (!allocated) {
|
||||
doAllocate();
|
||||
allocationTime = System.currentTimeMillis();
|
||||
allocated = true;
|
||||
ownerFilesystem.allocateEntry(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void deallocate() {
|
||||
if (allocated) {
|
||||
ownerFilesystem.deallocateEntry(this);
|
||||
doDeallocate();
|
||||
allocationTime = -1L;
|
||||
allocated = false;
|
||||
additionalNodes.clear();
|
||||
// NOTE: This is recursive!
|
||||
for (DiskNode node : getChildren()) {
|
||||
node.deallocate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
ownerFilesystem.deallocateEntry(this);
|
||||
doRefresh();
|
||||
allocationTime = System.currentTimeMillis();
|
||||
allocated = true;
|
||||
ownerFilesystem.allocateEntry(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the allocated
|
||||
*/
|
||||
public boolean isAllocated() {
|
||||
return allocated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the allocationTime
|
||||
*/
|
||||
public long getAllocationTime() {
|
||||
return allocationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastCheckTime
|
||||
*/
|
||||
public long getLastCheckTime() {
|
||||
return lastCheckTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the baseBlock
|
||||
*/
|
||||
public int getBaseBlock() {
|
||||
return baseBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseBlock the baseBlock to set
|
||||
*/
|
||||
public void setBaseBlock(int baseBlock) {
|
||||
this.baseBlock = baseBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ownerFilesystem
|
||||
*/
|
||||
public ProdosVirtualDisk getOwnerFilesystem() {
|
||||
return ownerFilesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ownerFilesystem the ownerFilesystem to set
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setOwnerFilesystem(ProdosVirtualDisk ownerFilesystem) throws IOException {
|
||||
this.ownerFilesystem = ownerFilesystem;
|
||||
if (baseBlock == -1) {
|
||||
setBaseBlock(ownerFilesystem.getNextFreeBlock());
|
||||
}
|
||||
ownerFilesystem.allocateEntry(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the physicalFile
|
||||
*/
|
||||
public File getPhysicalFile() {
|
||||
return physicalFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param physicalFile the physicalFile to set
|
||||
*/
|
||||
public void setPhysicalFile(File physicalFile) {
|
||||
this.physicalFile = physicalFile;
|
||||
setName(physicalFile.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the parent
|
||||
*/
|
||||
public DiskNode getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param parent the parent to set
|
||||
*/
|
||||
public void setParent(DiskNode parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the children
|
||||
*/
|
||||
public List<DiskNode> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param children the children to set
|
||||
*/
|
||||
public void setChildren(List<DiskNode> children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public void addChild(DiskNode child) {
|
||||
children.add(child);
|
||||
}
|
||||
|
||||
public void removeChild(DiskNode child) {
|
||||
children.remove(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type
|
||||
*/
|
||||
public EntryType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type the type to set
|
||||
*/
|
||||
public void setType(EntryType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setName(String name) {
|
||||
if (name.length() > 15) {
|
||||
name = name.substring(0, 15);
|
||||
}
|
||||
this.name = name.toUpperCase();
|
||||
}
|
||||
|
||||
public abstract void doDeallocate();
|
||||
|
||||
public abstract void doAllocate() throws IOException;
|
||||
|
||||
public abstract void doRefresh();
|
||||
|
||||
public abstract void readBlock(int sequence, byte[] buffer) throws IOException;
|
||||
|
||||
public void readBlock(byte[] buffer) throws IOException {
|
||||
checkFile();
|
||||
readBlock(0, buffer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
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
|
||||
*/
|
||||
public class FileNode extends DiskNode {
|
||||
|
||||
public enum FileType {
|
||||
|
||||
UNKNOWN(0x00, 0x0000),
|
||||
ADB(0x019, 0x0000),
|
||||
AWP(0x01a, 0x0000),
|
||||
ASP(0x01b, 0x0000),
|
||||
BAD(0x01, 0x0000),
|
||||
BIN(0x06, 0x0300),
|
||||
CLASS(0xED, 0x0000),
|
||||
BAS(0xfc, 0x0801),
|
||||
CMD(0x0f0, 0x0000),
|
||||
INT(0xfa, 0x0801),
|
||||
IVR(0xfb, 0x0000),
|
||||
PAS(0xef, 0x0000),
|
||||
REL(0x0Fe, 0x0000),
|
||||
SHK(0x0e0, 0x08002),
|
||||
SDK(0x0e0, 0x08002),
|
||||
SYS(0x0ff, 0x02000),
|
||||
SYSTEM(0x0ff, 0x02000),
|
||||
TXT(0x04, 0x0000),
|
||||
U01(0x0f1, 0x0000),
|
||||
U02(0x0f2, 0x0000),
|
||||
U03(0x0f3, 0x0000),
|
||||
U04(0x0f4, 0x0000),
|
||||
U05(0x0f5, 0x0000),
|
||||
U06(0x0f6, 0x0000),
|
||||
U07(0x0f7, 0x0000),
|
||||
U08(0x0f8, 0x0000),
|
||||
VAR(0x0FD, 0x0000);
|
||||
public int code = 0;
|
||||
public int defaultLoadAddress = 0;
|
||||
|
||||
FileType(int code, int addr) {
|
||||
this.code = code;
|
||||
this.defaultLoadAddress = addr;
|
||||
}
|
||||
}
|
||||
public int fileType = 0x00;
|
||||
public int loadAddress = 0x00;
|
||||
public static int SEEDLING_MAX_SIZE = ProdosVirtualDisk.BLOCK_SIZE;
|
||||
public static int SAPLING_MAX_SIZE = ProdosVirtualDisk.BLOCK_SIZE * 128;
|
||||
|
||||
@Override
|
||||
public EntryType getType() {
|
||||
long fileSize = getPhysicalFile().length();
|
||||
if (fileSize <= SEEDLING_MAX_SIZE) {
|
||||
setType(EntryType.SEEDLING);
|
||||
return EntryType.SEEDLING;
|
||||
} else if (fileSize <= SAPLING_MAX_SIZE) {
|
||||
setType(EntryType.SAPLING);
|
||||
return EntryType.SAPLING;
|
||||
}
|
||||
setType(EntryType.TREE);
|
||||
return EntryType.TREE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
String[] parts = name.split("\\.");
|
||||
FileType t = null;
|
||||
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];
|
||||
}
|
||||
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;
|
||||
}
|
||||
fileType = t.code;
|
||||
loadAddress = offset;
|
||||
|
||||
// Pass usable name (stripped of file extension and other type info) as name
|
||||
super.setName(name);
|
||||
}
|
||||
|
||||
public FileNode(ProdosVirtualDisk ownerFilesystem, File file) throws IOException {
|
||||
setOwnerFilesystem(ownerFilesystem);
|
||||
setPhysicalFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDeallocate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAllocate() throws IOException {
|
||||
int dataBlocks = (int) ((getPhysicalFile().length() / ProdosVirtualDisk.BLOCK_SIZE) + 1);
|
||||
int treeBlocks = 0;
|
||||
if (dataBlocks > 1 && dataBlocks < 257) {
|
||||
treeBlocks = 1;
|
||||
} else {
|
||||
treeBlocks = 1 + (dataBlocks / 256);
|
||||
}
|
||||
for (int i = 1; i < dataBlocks + treeBlocks; i++) {
|
||||
new SubNode(i, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doRefresh() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readBlock(int block, byte[] buffer) throws IOException {
|
||||
// System.out.println("Read block "+block+" of file "+getName());
|
||||
switch (this.getType()) {
|
||||
case SEEDLING:
|
||||
readFile(buffer, 0);
|
||||
break;
|
||||
case SAPLING:
|
||||
if (block > 0) {
|
||||
readFile(buffer, (block - 1));
|
||||
} else {
|
||||
// Generate seedling index block
|
||||
generateIndex(buffer, 0, 256);
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
readFile(buffer, (block - treeBlocks));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void readFile(byte[] buffer, int start) throws IOException {
|
||||
FileInputStream f = new FileInputStream(physicalFile);
|
||||
f.skip(start * ProdosVirtualDisk.BLOCK_SIZE);
|
||||
f.read(buffer, 0, ProdosVirtualDisk.BLOCK_SIZE);
|
||||
f.close();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Maintain freespace and node allocation
|
||||
* @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++) {
|
||||
new SubNode(i, this, start+i);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void doDeallocate() {
|
||||
//
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAllocate() {
|
||||
///
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doRefresh() {
|
||||
//
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Generic representation of a mass storage disk, either an image or a virtual volume.
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public interface IDisk {
|
||||
public static int BLOCK_SIZE = 512;
|
||||
public static int MAX_BLOCK = 65535;
|
||||
|
||||
public void mliFormat() throws IOException;
|
||||
public void mliRead(int block, int bufferAddress) throws IOException;
|
||||
public void mliWrite(int block, int bufferAddress) throws IOException;
|
||||
public void boot0(int slot) throws IOException;
|
||||
|
||||
// Return size in 512k blocks
|
||||
public int getSize();
|
||||
|
||||
public void eject();
|
||||
public boolean isWriteProtected();
|
||||
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import static jace.hardware.ProdosDriver.*;
|
||||
import jace.hardware.ProdosDriver.MLI_COMMAND_TYPE;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Representation of a hard drive or 800k disk image used by CardMassStorage
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class LargeDisk implements IDisk {
|
||||
RandomAccessFile diskImage;
|
||||
File diskPath;
|
||||
// Offset in input file where data can be found
|
||||
private int dataOffset = 0;
|
||||
private int physicalBlocks = 0;
|
||||
private int logicalBlocks = 0;
|
||||
|
||||
public LargeDisk(File f) {
|
||||
try {
|
||||
readDiskImage(f);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(LargeDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void mliFormat() throws IOException {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public void mliRead(int block, int bufferAddress) throws IOException {
|
||||
RAM memory = Computer.getComputer().getMemory();
|
||||
if (block < physicalBlocks) {
|
||||
diskImage.seek((block * BLOCK_SIZE) + dataOffset);
|
||||
for (int i = 0; i < BLOCK_SIZE; i++) {
|
||||
memory.write(bufferAddress + i, diskImage.readByte(), true, false);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < BLOCK_SIZE; i++) {
|
||||
memory.write(bufferAddress + i, (byte) 0, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void mliWrite(int block, int bufferAddress) throws IOException {
|
||||
if (block < physicalBlocks) {
|
||||
RAM memory = Computer.getComputer().getMemory();
|
||||
diskImage.seek((block * BLOCK_SIZE) + dataOffset);
|
||||
byte[] buf = new byte[BLOCK_SIZE];
|
||||
for (int i = 0; i < BLOCK_SIZE; i++) {
|
||||
buf[i]=memory.readRaw(bufferAddress + i);
|
||||
}
|
||||
diskImage.write(buf);
|
||||
}
|
||||
}
|
||||
|
||||
public void boot0(int slot) throws IOException {
|
||||
Computer.getComputer().getCpu().suspend();
|
||||
mliRead(0, 0x0800);
|
||||
byte slot16 = (byte) (slot << 4);
|
||||
((MOS65C02) Computer.getComputer().getCpu()).X = slot16;
|
||||
RAM memory = Computer.getComputer().getMemory();
|
||||
memory.write(CardMassStorage.SLT16, slot16, false, false);
|
||||
memory.write(MLI_COMMAND, (byte) MLI_COMMAND_TYPE.READ.intValue, false, false);
|
||||
memory.write(MLI_UNITNUMBER, slot16, false, false);
|
||||
// Write location to block read routine to zero page
|
||||
memory.writeWord(0x048, 0x0c000 + CardMassStorage.DEVICE_DRIVER_OFFSET + (slot * 0x0100), false, false);
|
||||
((MOS65C02) Computer.getComputer().getCpu()).setProgramCounter(0x0800);
|
||||
Computer.getComputer().getCpu().resume();
|
||||
}
|
||||
|
||||
public File getPhysicalPath() {
|
||||
return diskPath;
|
||||
}
|
||||
|
||||
public void setPhysicalPath(File f) throws IOException {
|
||||
diskPath = f;
|
||||
}
|
||||
|
||||
private boolean read2mg(File f) {
|
||||
boolean result = false;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(getPhysicalPath());
|
||||
if (fis.read() == 0x32 && fis.read() == 0x49 && fis.read() == 0x4D && fis.read() == 0x47) {
|
||||
System.out.println("Disk is 2MG");
|
||||
// todo: read header
|
||||
dataOffset = 64;
|
||||
physicalBlocks = (int) (f.length() / BLOCK_SIZE);
|
||||
logicalBlocks = physicalBlocks;
|
||||
result = true;
|
||||
}
|
||||
} catch (FileNotFoundException ex) {
|
||||
Logger.getLogger(LargeDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(LargeDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(LargeDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void readHdv(File f) {
|
||||
System.out.println("Disk is HDV");
|
||||
dataOffset = 0;
|
||||
physicalBlocks = (int) (f.length() / BLOCK_SIZE);
|
||||
logicalBlocks = physicalBlocks;
|
||||
}
|
||||
|
||||
private void readDiskImage(File f) throws FileNotFoundException, IOException {
|
||||
eject();
|
||||
setPhysicalPath(f);
|
||||
if (!read2mg(f)) {
|
||||
readHdv(f);
|
||||
}
|
||||
diskImage = new RandomAccessFile(f, "rwd");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eject() {
|
||||
if (diskImage != null) {
|
||||
try {
|
||||
diskImage.close();
|
||||
diskImage = null;
|
||||
setPhysicalPath(null);
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(LargeDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteProtected() {
|
||||
return diskPath == null || !diskPath.canWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return physicalBlocks;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (C) 2013 brobert.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import jace.library.MediaConsumer;
|
||||
import jace.library.MediaEntry;
|
||||
import jace.library.MediaEntry.MediaFile;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author brobert
|
||||
*/
|
||||
public class MassStorageDrive implements MediaConsumer {
|
||||
IDisk disk = null;
|
||||
ImageIcon icon = null;
|
||||
|
||||
public ImageIcon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(ImageIcon i) {
|
||||
icon = i;
|
||||
}
|
||||
|
||||
MediaEntry currentEntry;
|
||||
MediaFile currentFile;
|
||||
|
||||
public void insertMedia(MediaEntry e, MediaFile f) throws IOException {
|
||||
eject();
|
||||
currentEntry = e;
|
||||
currentFile = f;
|
||||
disk= readDisk(currentFile.path);
|
||||
}
|
||||
|
||||
public MediaEntry getMediaEntry() {
|
||||
return currentEntry;
|
||||
}
|
||||
|
||||
public MediaFile getMediaFile() {
|
||||
return currentFile;
|
||||
}
|
||||
|
||||
public boolean isAccepted(MediaEntry e, MediaFile f) {
|
||||
return e.type.isProdosOrdered;
|
||||
}
|
||||
|
||||
public void eject() {
|
||||
if (disk != null) {
|
||||
disk.eject();
|
||||
disk = null;
|
||||
}
|
||||
}
|
||||
|
||||
private IDisk readDisk(File f) {
|
||||
if (f.isFile()) {
|
||||
return new LargeDisk(f);
|
||||
} else if (f.isDirectory()) {
|
||||
try {
|
||||
return new ProdosVirtualDisk(f);
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Unable to open virtual disk: " + ex.getMessage());
|
||||
Logger.getLogger(CardMassStorage.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public IDisk getCurrentDisk() {
|
||||
return disk;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import jace.EmulatorUILogic;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import static jace.hardware.ProdosDriver.*;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Representation of a Prodos Volume which maps to a physical folder on the
|
||||
* actual hard drive. This is used by CardMassStorage in the event the disk path
|
||||
* 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
|
||||
*/
|
||||
public class ProdosVirtualDisk implements IDisk {
|
||||
|
||||
public static int VOLUME_START = 2;
|
||||
public static int FREESPACE_BITMAP_START = 6;
|
||||
byte[] ioBuffer;
|
||||
File physicalRoot;
|
||||
Map<Integer, DiskNode> physicalMap;
|
||||
DirectoryNode rootDirectory;
|
||||
FreespaceBitmap freespaceBitmap;
|
||||
|
||||
public ProdosVirtualDisk(File rootPath) throws IOException {
|
||||
ioBuffer = new byte[BLOCK_SIZE];
|
||||
setPhysicalPath(rootPath);
|
||||
}
|
||||
|
||||
public void mliRead(int block, int bufferAddress) throws IOException {
|
||||
// System.out.println("Read block " + block + " to " + Integer.toHexString(bufferAddress));
|
||||
DiskNode node = physicalMap.get(block);
|
||||
RAM memory = Computer.getComputer().getMemory();
|
||||
Arrays.fill(ioBuffer, (byte) (block & 0x0ff));
|
||||
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);
|
||||
}
|
||||
} 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) + " ");
|
||||
// }
|
||||
// System.out.println();
|
||||
}
|
||||
|
||||
public void mliWrite(int block, int bufferAddress) throws IOException {
|
||||
System.out.println("Write block " + block + " to " + Integer.toHexString(bufferAddress));
|
||||
throw new IOException("Write not implemented yet!");
|
||||
// DiskNode node = physicalMap.get(block);
|
||||
// RAM memory = Computer.getComputer().getMemory();
|
||||
// if (node == null) {
|
||||
// // CAPTURE WRITES TO UNUSED BLOCKS
|
||||
// } else {
|
||||
// node.readBlock(block, ioBuffer);
|
||||
// for (int i=0; i < BLOCK_SIZE; i++) {
|
||||
// memory.write(bufferAddress+i, ioBuffer[i], false);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public void mliFormat() {
|
||||
throw new UnsupportedOperationException("Formatting for this type of media is not supported!");
|
||||
}
|
||||
|
||||
public File locateFile(File rootPath, String string) {
|
||||
File mostLikelyMatch = null;
|
||||
for (File f : rootPath.listFiles()) {
|
||||
if (f.getName().equalsIgnoreCase(string)) {
|
||||
return f;
|
||||
}
|
||||
// This is not sufficient, a more deterministic approach should be taken
|
||||
if (string.toUpperCase().startsWith(f.getName().toUpperCase())) {
|
||||
if (mostLikelyMatch == null || f.getName().length() > mostLikelyMatch.getName().length()) {
|
||||
mostLikelyMatch = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mostLikelyMatch;
|
||||
}
|
||||
|
||||
public int getNextFreeBlock() 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)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IOException("Virtual Disk Full!");
|
||||
}
|
||||
|
||||
// Mark space occupied by node
|
||||
public void allocateEntry(DiskNode node) {
|
||||
physicalMap.put(node.baseBlock, node);
|
||||
for (DiskNode sub : node.additionalNodes) {
|
||||
physicalMap.put(sub.getBaseBlock(), sub);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
for (DiskNode sub : node.additionalNodes) {
|
||||
if (physicalMap.get(sub.getBaseBlock()) != null && physicalMap.get(sub.baseBlock).equals(sub)) {
|
||||
physicalMap.remove(sub.getBaseBlock());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is the specified block in use?
|
||||
public boolean isAllocated(int i) {
|
||||
return (physicalMap.containsKey(i));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void boot0(int slot) throws IOException {
|
||||
File prodos = locateFile(physicalRoot, "PRODOS.SYS");
|
||||
if (prodos == null || !prodos.exists()) {
|
||||
throw new IOException("Unable to locate PRODOS.SYS");
|
||||
}
|
||||
Computer.getComputer().getCpu().suspend();
|
||||
byte slot16 = (byte) (slot << 4);
|
||||
((MOS65C02) Computer.getComputer().getCpu()).X = slot16;
|
||||
RAM memory = Computer.getComputer().getMemory();
|
||||
memory.write(CardMassStorage.SLT16, slot16, false, false);
|
||||
memory.write(MLI_COMMAND, (byte) MLI_COMMAND_TYPE.READ.intValue, false, false);
|
||||
memory.write(MLI_UNITNUMBER, slot16, false, false);
|
||||
// Write location to block read routine to zero page
|
||||
memory.writeWord(0x048, 0x0c000 + CardMassStorage.DEVICE_DRIVER_OFFSET + (slot * 0x0100), false, false);
|
||||
EmulatorUILogic.brun(prodos, 0x02000);
|
||||
Computer.getComputer().getCpu().resume();
|
||||
}
|
||||
|
||||
public File getPhysicalPath() {
|
||||
return physicalRoot;
|
||||
}
|
||||
|
||||
public void setPhysicalPath(File f) throws IOException {
|
||||
if (physicalRoot != null && physicalRoot.equals(f)) {
|
||||
return;
|
||||
}
|
||||
physicalRoot = f;
|
||||
physicalMap = new HashMap<Integer, DiskNode>();
|
||||
if (!physicalRoot.exists() || !physicalRoot.isDirectory()) {
|
||||
try {
|
||||
throw new IOException("Root path must be a directory that exists!");
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(ProdosVirtualDisk.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
// Root directory ALWAYS starts on block 2!
|
||||
rootDirectory = new DirectoryNode(this, physicalRoot, VOLUME_START);
|
||||
rootDirectory.setName("VIRTUAL");
|
||||
allocateEntry(rootDirectory);
|
||||
freespaceBitmap = new FreespaceBitmap(this, FREESPACE_BITMAP_START);
|
||||
allocateEntry(freespaceBitmap);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void eject() {
|
||||
// Nothing to do here...
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteProtected() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 0x0ffff;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
package jace.hardware.massStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Subnode is a generic node (block) used up by a file. Thes nodes do nothing
|
||||
* more than just occupy space in the freespace bitmap. The file node itself
|
||||
* keeps track of its subnodes and figures out what each subnode should
|
||||
* "contain". The subnodes themselves don't track anything though.
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class SubNode extends DiskNode {
|
||||
|
||||
int sequenceNumber;
|
||||
private int seq;
|
||||
|
||||
public SubNode(int seq, DiskNode parent) throws IOException {
|
||||
init(seq, parent);
|
||||
}
|
||||
|
||||
public SubNode(int seq, DiskNode parent, int baseBlock) throws IOException {
|
||||
setBaseBlock(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 void doDeallocate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAllocate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doRefresh() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readBlock(int sequence, byte[] buffer) throws IOException {
|
||||
parent.readBlock(sequenceNumber, buffer);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue