Compare commits
28 Commits
2.0-Stable
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
d87210849f | ||
|
46b6c20458 | ||
|
9674e59f1e | ||
|
774f706f68 | ||
|
cc0cead894 | ||
|
553d439ff8 | ||
|
15be6e3436 | ||
|
8bd9ec1781 | ||
|
f7ca7c198c | ||
|
1f2eff2e42 | ||
|
d4073b9096 | ||
|
9cee0cece9 | ||
|
142ee2df2a | ||
|
9118b83a43 | ||
|
8525330d53 | ||
|
ddc41ec84e | ||
|
eb776d44af | ||
|
ad9da99cb8 | ||
|
79c1ee825c | ||
|
0d07d65b82 | ||
|
769d7f4302 | ||
|
d8ab357d84 | ||
|
ce9027cb6b | ||
|
ede44af6d1 | ||
|
4425e8884d | ||
|
0c0b2c107c | ||
|
dba57e6e89 | ||
|
09c1d78832 |
1
.gitignore
vendored
|
@ -20,3 +20,4 @@ hs_err_pid*
|
|||
*.DS_Store
|
||||
!/lib/nestedvm.jar
|
||||
_acme_tmp*
|
||||
/.idea
|
||||
|
|
11
README.md
|
@ -11,12 +11,21 @@ To Run:
|
|||
|
||||
or `java -jar Jace.jar`
|
||||
|
||||
Support JACE:
|
||||
|
||||
JACE will always be free, but it does take considerable time to refine and add new features. If you would like to show your support and encourage the author to keep maintaining this emulator, why not throw him some change to buy him a drink? (The emulator was named for the Jack and Cokes consumed during its inception.)
|
||||
|
||||
<a href="bitcoin:1TmP94jrEtJNqz7wrCpViA6musGsiTXEq?amount=0.000721&label=Jace%20Donations"><img src="https://sites.google.com/site/brendanrobert/projects/jace/donate.png" height="150px" width="150px"></a>
|
||||
* <a href="bitcoin:1TmP94jrEtJNqz7wrCpViA6musGsiTXEq?amount=0.000721&label=Jace%20Donations">Donate to support JACE! (BTC address: 1TmP94jrEtJNqz7wrCpViA6musGsiTXEq)</a>
|
||||
* <a href="https://www.paypal.me/BrendanRobert">Donate to support JACE! (Paypal)</a>
|
||||
|
||||
To Build:
|
||||
|
||||
* See [build.sh](build.sh)
|
||||
|
||||
Jace is a java-based Apple //e emulator with many compelling features:
|
||||
* NEW: Built-in IDE for writing basic and assembly programs, using ACME to compile and execute directly without leaving the emulator.
|
||||
* NEW: UI Control Overlay makes common actions more conveniently available at the click of a button!
|
||||
* Built-in IDE for writing basic and assembly programs, using ACME to compile and execute directly without leaving the emulator.
|
||||
* Disk and Mass-storage (hard drive, 3.5 floppy) images
|
||||
* Joystick and Mouse are fully supported (Joystick can be emulated with either keyboard or mouse, Java doesn't have native joystick support
|
||||
* All graphics modes are supported, including Apple RGB "Mixed" and B&W modes.
|
||||
|
|
18
build.sh
|
@ -21,7 +21,7 @@
|
|||
# 0. Here is some information to clear up the confusion about Java:
|
||||
#
|
||||
# The JRE (runtime) is needed to RUN Java programs.
|
||||
# The JDK (compiler) is needed to COMPILTE Java programs.
|
||||
# The JDK (compiler) is needed to COMPILE Java programs.
|
||||
#
|
||||
# Solution:
|
||||
#
|
||||
|
@ -29,6 +29,15 @@
|
|||
#
|
||||
# mvn -version
|
||||
#
|
||||
# NOTE: If this command returns:
|
||||
#
|
||||
# -bash: mvn: command not found
|
||||
#
|
||||
# You either didn't install maven or it isn't in your path
|
||||
# i.e.
|
||||
# which mvn
|
||||
# /usr/local/bin/mvn
|
||||
#
|
||||
# 2. Check which version of the Java JRE is installed:
|
||||
#
|
||||
# java -version
|
||||
|
@ -88,6 +97,13 @@
|
|||
#
|
||||
# As the source code is using Java 1.8 langauge features.
|
||||
|
||||
if [[ -z "$(which mvn)" ]]; then
|
||||
echo "ERROR: Maven isn't installed"
|
||||
echo "Install via:"
|
||||
echo " brew install maven"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [[ -z "$JAVA_HOME" ]]; then
|
||||
echo "WARNING: JAVA_HOME was not set"
|
||||
echo "... Defaulting to Java 1.8..."
|
||||
|
|
53
pom.xml
|
@ -77,6 +77,7 @@
|
|||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
<version>3.5</version>
|
||||
</plugin>
|
||||
|
@ -92,7 +93,7 @@
|
|||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.10</version>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -101,4 +102,54 @@
|
|||
<version>1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>public</id>
|
||||
<name>aliyun nexus</name>
|
||||
<!--<url>http://maven.aliyun.com/nexus/content/groups/public/</url>-->
|
||||
<url>https://maven.aliyun.com/repository/public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>ias-snapshots</id>
|
||||
<name>Infinite Automation Snapshot Repository</name>
|
||||
<url>https://maven.mangoautomation.net/repository/ias-snapshot/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>ias-releases</id>
|
||||
<name>Infinite Automation Release Repository</name>
|
||||
<url>https://maven.mangoautomation.net/repository/ias-release/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>public</id>
|
||||
<name>aliyun nexus</name>
|
||||
<!--<url>http://maven.aliyun.com/nexus/content/groups/public/</url>-->
|
||||
<url>https://maven.aliyun.com/repository/public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</project>
|
||||
|
|
|
@ -26,7 +26,7 @@ import java.util.Map;
|
|||
|
||||
/**
|
||||
* Created on January 15, 2007, 10:10 PM
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Emulator {
|
||||
|
||||
|
@ -51,6 +51,10 @@ public class Emulator {
|
|||
Configuration.buildTree();
|
||||
Configuration.loadSettings();
|
||||
mainThread = Thread.currentThread();
|
||||
applyConfiguration(args);
|
||||
}
|
||||
|
||||
public void applyConfiguration(List<String> args) {
|
||||
Map<String, String> settings = new LinkedHashMap<>();
|
||||
if (args != null) {
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
*/
|
||||
package jace;
|
||||
|
||||
import com.sun.javafx.tk.quantum.OverlayWarning;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.RAM128k;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.ConfigurationUIController;
|
||||
import jace.config.InvokableAction;
|
||||
import jace.config.Reconfigurable;
|
||||
|
@ -30,7 +30,6 @@ import jace.core.Computer;
|
|||
import jace.core.Debugger;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMListener;
|
||||
import static jace.core.Utility.*;
|
||||
import jace.ide.IdeController;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -59,6 +58,8 @@ import javafx.scene.layout.AnchorPane;
|
|||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import static jace.core.Utility.*;
|
||||
|
||||
/**
|
||||
* This class contains miscellaneous user-invoked actions such as debugger
|
||||
* operations and running arbitrary files in the emulator. It is possible for
|
||||
|
@ -82,6 +83,18 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
};
|
||||
}
|
||||
|
||||
@ConfigurableField(
|
||||
category = "General",
|
||||
name = "Speed Setting"
|
||||
)
|
||||
public int speedSetting = 3;
|
||||
|
||||
@ConfigurableField(
|
||||
category = "General",
|
||||
name = "Show Drives"
|
||||
)
|
||||
public boolean showDrives = true;
|
||||
|
||||
public static void updateCPURegisters(MOS65C02 cpu) {
|
||||
// DebuggerPanel debuggerPanel = Emulator.getFrame().getDebuggerPanel();
|
||||
// debuggerPanel.valueA.setText(Integer.toHexString(cpu.A));
|
||||
|
@ -246,7 +259,7 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
name = "Toggle Debug",
|
||||
category = "debug",
|
||||
description = "Show/hide the debug panel",
|
||||
alternatives = "Show Debug;Hide Debug",
|
||||
alternatives = "Show Debug;Hide Debug;Inspect",
|
||||
defaultKeyMapping = "ctrl+shift+d")
|
||||
public static void toggleDebugPanel() {
|
||||
// AbstractEmulatorFrame frame = Emulator.getFrame();
|
||||
|
@ -262,13 +275,14 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
name = "Toggle fullscreen",
|
||||
category = "general",
|
||||
description = "Activate/deactivate fullscreen mode",
|
||||
alternatives = "fullscreen,maximize",
|
||||
alternatives = "fullscreen;maximize",
|
||||
defaultKeyMapping = "ctrl+shift+f")
|
||||
public static void toggleFullscreen() {
|
||||
Platform.runLater(() -> {
|
||||
Stage stage = JaceApplication.getApplication().primaryStage;
|
||||
stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
|
||||
stage.setFullScreen(!stage.isFullScreen());
|
||||
JaceApplication.getApplication().controller.setAspectRatioEnabled(stage.isFullScreen());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -276,7 +290,7 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
name = "Save Raw Screenshot",
|
||||
category = "general",
|
||||
description = "Save raw (RAM) format of visible screen",
|
||||
alternatives = "screendump, raw screenshot",
|
||||
alternatives = "screendump;raw screenshot",
|
||||
defaultKeyMapping = "ctrl+shift+z")
|
||||
public static void saveScreenshotRaw() throws FileNotFoundException, IOException {
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss");
|
||||
|
@ -317,7 +331,7 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
name = "Save Screenshot",
|
||||
category = "general",
|
||||
description = "Save image of visible screen",
|
||||
alternatives = "Save image,save framebuffer,screenshot",
|
||||
alternatives = "Save image;save framebuffer;screenshot",
|
||||
defaultKeyMapping = "ctrl+shift+s")
|
||||
public static void saveScreenshot() throws IOException {
|
||||
FileChooser select = new FileChooser();
|
||||
|
@ -346,7 +360,7 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
name = "Configuration",
|
||||
category = "general",
|
||||
description = "Edit emulator configuraion",
|
||||
alternatives = "Reconfigure,Preferences,Settings",
|
||||
alternatives = "Reconfigure;Preferences;Settings;Config",
|
||||
defaultKeyMapping = {"f4", "ctrl+shift+c"})
|
||||
public static void showConfig() {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(EmulatorUILogic.class.getResource("/fxml/Configuration.fxml"));
|
||||
|
@ -368,7 +382,7 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
name = "Open IDE",
|
||||
category = "development",
|
||||
description = "Open new IDE window for Basic/Assembly/Plasma coding",
|
||||
alternatives = "dev,development,acme,assembler,editor",
|
||||
alternatives = "IDE;dev;development;acme;assembler;editor",
|
||||
defaultKeyMapping = {"ctrl+shift+i"})
|
||||
public static void showIDE() {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(EmulatorUILogic.class.getResource("/fxml/editor.fxml"));
|
||||
|
@ -392,45 +406,62 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
name = "Resize window",
|
||||
category = "general",
|
||||
description = "Resize the screen to 1x/1.5x/2x/3x video size",
|
||||
alternatives = "Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;",
|
||||
alternatives = "Aspect;Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;",
|
||||
defaultKeyMapping = {"ctrl+shift+a"})
|
||||
public static void scaleIntegerRatio() {
|
||||
Platform.runLater(() -> {
|
||||
JaceApplication.getApplication().primaryStage.setFullScreen(false);
|
||||
if (JaceApplication.getApplication() == null
|
||||
|| JaceApplication.getApplication().primaryStage == null) {
|
||||
return;
|
||||
}
|
||||
Stage stage = JaceApplication.getApplication().primaryStage;
|
||||
size++;
|
||||
if (size > 3) {
|
||||
size = 0;
|
||||
}
|
||||
int width = 0, height = 0;
|
||||
switch (size) {
|
||||
case 0: // 1x
|
||||
width = 560;
|
||||
height = 384;
|
||||
break;
|
||||
case 1: // 1.5x
|
||||
width = 840;
|
||||
height = 576;
|
||||
break;
|
||||
case 2: // 2x
|
||||
width = 560*2;
|
||||
height = 384*2;
|
||||
break;
|
||||
case 3: // 3x (retina) 2880x1800
|
||||
width = 560*3;
|
||||
height = 384*3;
|
||||
break;
|
||||
default: // 2x
|
||||
width = 560*2;
|
||||
height = 384*2;
|
||||
if (stage.isFullScreen()) {
|
||||
JaceApplication.getApplication().controller.toggleAspectRatio();
|
||||
} else {
|
||||
int width = 0, height = 0;
|
||||
switch (size) {
|
||||
case 0: // 1x
|
||||
width = 560;
|
||||
height = 384;
|
||||
break;
|
||||
case 1: // 1.5x
|
||||
width = 840;
|
||||
height = 576;
|
||||
break;
|
||||
case 2: // 2x
|
||||
width = 560 * 2;
|
||||
height = 384 * 2;
|
||||
break;
|
||||
case 3: // 3x (retina) 2880x1800
|
||||
width = 560 * 3;
|
||||
height = 384 * 3;
|
||||
break;
|
||||
default: // 2x
|
||||
width = 560 * 2;
|
||||
height = 384 * 2;
|
||||
}
|
||||
double vgap = stage.getScene().getY();
|
||||
double hgap = stage.getScene().getX();
|
||||
stage.setWidth(hgap * 2 + width);
|
||||
stage.setHeight(vgap + height);
|
||||
}
|
||||
Stage stage = JaceApplication.getApplication().primaryStage;
|
||||
double vgap = stage.getScene().getY();
|
||||
double hgap = stage.getScene().getX();
|
||||
stage.setWidth(hgap*2 + width);
|
||||
stage.setHeight(vgap + height);
|
||||
});
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
name = "About",
|
||||
category = "general",
|
||||
description = "Display about window",
|
||||
alternatives = "info;credits",
|
||||
defaultKeyMapping = {"ctrl+shift+."})
|
||||
public static void showAboutWindow() {
|
||||
//TODO: Implement
|
||||
}
|
||||
|
||||
public static boolean confirm(String message) {
|
||||
// return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(Emulator.getFrame(), message);
|
||||
return false;
|
||||
|
@ -530,5 +561,8 @@ public class EmulatorUILogic implements Reconfigurable {
|
|||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
if (JaceApplication.getApplication() != null) {
|
||||
JaceApplication.getApplication().controller.setSpeed(speedSetting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,14 +54,15 @@ public class JaceApplication extends Application {
|
|||
}
|
||||
|
||||
primaryStage.show();
|
||||
Emulator emulator = new Emulator(getParameters().getRaw());
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
new Thread(() -> {
|
||||
new Emulator(getParameters().getRaw());
|
||||
reconnectUIHooks();
|
||||
EmulatorUILogic.scaleIntegerRatio();
|
||||
while (Emulator.computer.getVideo() == null || Emulator.computer.getVideo().getFrameBuffer() == null) {
|
||||
Thread.yield();
|
||||
}
|
||||
controller.connectComputer(Emulator.computer, primaryStage);
|
||||
bootWatchdog();
|
||||
});
|
||||
}).start();
|
||||
primaryStage.setOnCloseRequest(event -> {
|
||||
Emulator.computer.deactivate();
|
||||
Platform.exit();
|
||||
|
@ -69,6 +70,10 @@ public class JaceApplication extends Application {
|
|||
});
|
||||
}
|
||||
|
||||
public void reconnectUIHooks() {
|
||||
controller.connectComputer(Emulator.computer, primaryStage);
|
||||
}
|
||||
|
||||
public static JaceApplication getApplication() {
|
||||
return singleton;
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
package jace;
|
||||
|
||||
import com.sun.glass.ui.Application;
|
||||
import jace.cheat.MetaCheat;
|
||||
import jace.core.Card;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Keyboard;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.Utility;
|
||||
import jace.library.MediaCache;
|
||||
import jace.library.MediaConsumer;
|
||||
import jace.library.MediaConsumerParent;
|
||||
|
@ -30,10 +30,22 @@ import java.util.concurrent.ScheduledFuture;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.NumberBinding;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.DragEvent;
|
||||
|
@ -43,11 +55,14 @@ import javafx.scene.input.TransferMode;
|
|||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -70,29 +85,213 @@ public class JaceUIController {
|
|||
@FXML
|
||||
private ImageView appleScreen;
|
||||
|
||||
@FXML
|
||||
private BorderPane controlOverlay;
|
||||
|
||||
@FXML
|
||||
private Slider speedSlider;
|
||||
|
||||
@FXML
|
||||
private AnchorPane menuButtonPane;
|
||||
|
||||
@FXML
|
||||
private Button menuButton;
|
||||
|
||||
Computer computer;
|
||||
|
||||
private final BooleanProperty aspectRatioCorrectionEnabled = new SimpleBooleanProperty(false);
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
assert rootPane != null : "fx:id=\"rootPane\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||
assert stackPane != null : "fx:id=\"stackPane\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||
assert notificationBox != null : "fx:id=\"notificationBox\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||
assert appleScreen != null : "fx:id=\"appleScreen\" was not injected: check your FXML file 'JaceUI.fxml'.";
|
||||
appleScreen.fitWidthProperty().bind(rootPane.widthProperty());
|
||||
speedSlider.setValue(1.0);
|
||||
controlOverlay.setVisible(false);
|
||||
menuButtonPane.setVisible(false);
|
||||
controlOverlay.setFocusTraversable(false);
|
||||
menuButtonPane.setFocusTraversable(false);
|
||||
NumberBinding aspectCorrectedWidth = rootPane.heightProperty().multiply(3.0).divide(2.0);
|
||||
NumberBinding width = new When(
|
||||
aspectRatioCorrectionEnabled.and(aspectCorrectedWidth.lessThan(rootPane.widthProperty()))
|
||||
).then(aspectCorrectedWidth).otherwise(rootPane.widthProperty());
|
||||
appleScreen.fitWidthProperty().bind(width);
|
||||
appleScreen.fitHeightProperty().bind(rootPane.heightProperty());
|
||||
appleScreen.setVisible(false);
|
||||
menuButtonPane.setPickOnBounds(false);
|
||||
rootPane.setOnDragEntered(this::processDragEnteredEvent);
|
||||
rootPane.setOnDragExited(this::processDragExitedEvent);
|
||||
rootPane.setBackground(new Background(new BackgroundFill(Color.BLACK, null, null)));
|
||||
rootPane.setOnMouseMoved(this::showMenuButton);
|
||||
rootPane.setOnMouseExited(this::hideControlOverlay);
|
||||
menuButton.setOnMouseClicked(this::showControlOverlay);
|
||||
controlOverlay.setOnMouseClicked(this::hideControlOverlay);
|
||||
delayTimer.getKeyFrames().add(new KeyFrame(Duration.millis(3000), evt -> {
|
||||
hideControlOverlay(null);
|
||||
rootPane.requestFocus();
|
||||
}));
|
||||
}
|
||||
|
||||
private void showMenuButton(MouseEvent evt) {
|
||||
if (!evt.isPrimaryButtonDown() && !evt.isSecondaryButtonDown() && !controlOverlay.isVisible()) {
|
||||
resetMenuButtonTimer();
|
||||
if (!menuButtonPane.isVisible()) {
|
||||
menuButtonPane.setVisible(true);
|
||||
FadeTransition ft = new FadeTransition(Duration.millis(500), menuButtonPane);
|
||||
ft.setFromValue(0.0);
|
||||
ft.setToValue(1.0);
|
||||
ft.play();
|
||||
}
|
||||
}
|
||||
rootPane.requestFocus();
|
||||
}
|
||||
|
||||
Timeline delayTimer = new Timeline();
|
||||
|
||||
private void resetMenuButtonTimer() {
|
||||
delayTimer.playFromStart();
|
||||
}
|
||||
|
||||
private void showControlOverlay(MouseEvent evt) {
|
||||
if (!evt.isPrimaryButtonDown() && !evt.isSecondaryButtonDown()) {
|
||||
delayTimer.stop();
|
||||
menuButtonPane.setVisible(false);
|
||||
controlOverlay.setVisible(true);
|
||||
FadeTransition ft = new FadeTransition(Duration.millis(500), controlOverlay);
|
||||
ft.setFromValue(0.0);
|
||||
ft.setToValue(1.0);
|
||||
ft.play();
|
||||
rootPane.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideControlOverlay(MouseEvent evt) {
|
||||
if (menuButtonPane.isVisible()) {
|
||||
FadeTransition ft1 = new FadeTransition(Duration.millis(500), menuButtonPane);
|
||||
ft1.setFromValue(1.0);
|
||||
ft1.setToValue(0.0);
|
||||
ft1.setOnFinished(evt1 -> menuButtonPane.setVisible(false));
|
||||
ft1.play();
|
||||
}
|
||||
if (controlOverlay.isVisible()) {
|
||||
FadeTransition ft2 = new FadeTransition(Duration.millis(500), controlOverlay);
|
||||
ft2.setFromValue(1.0);
|
||||
ft2.setToValue(0.0);
|
||||
ft2.setOnFinished(evt1 -> controlOverlay.setVisible(false));
|
||||
ft2.play();
|
||||
}
|
||||
}
|
||||
|
||||
protected double convertSpeedToRatio(Double setting) {
|
||||
if (setting < 1.0) {
|
||||
return 0.5;
|
||||
} else if (setting == 1.0) {
|
||||
return 1.0;
|
||||
} else if (setting >= 10) {
|
||||
return Double.MAX_VALUE;
|
||||
} else {
|
||||
double val = Math.pow(2.0, (setting - 1.0) / 1.5);
|
||||
val = Math.floor(val * 2.0) / 2.0;
|
||||
if (val > 2.0) {
|
||||
val = Math.floor(val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
private void connectControls(Stage primaryStage) {
|
||||
connectButtons(controlOverlay);
|
||||
if (computer.getKeyboard() != null) {
|
||||
EventHandler<KeyEvent> keyboardHandler = computer.getKeyboard().getListener();
|
||||
primaryStage.setOnShowing(evt -> computer.getKeyboard().resetState());
|
||||
rootPane.setOnKeyPressed(keyboardHandler);
|
||||
rootPane.setOnKeyReleased(keyboardHandler);
|
||||
rootPane.setFocusTraversable(true);
|
||||
}
|
||||
speedSlider.setMinorTickCount(0);
|
||||
speedSlider.setMajorTickUnit(1);
|
||||
speedSlider.setLabelFormatter(new StringConverter<Double>() {
|
||||
@Override
|
||||
public String toString(Double val) {
|
||||
if (val < 1.0) {
|
||||
return "Half";
|
||||
} else if (val >= 10.0) {
|
||||
return "∞";
|
||||
}
|
||||
double v = convertSpeedToRatio(val);
|
||||
if (v != Math.floor(v)) {
|
||||
return String.valueOf(v) + "x";
|
||||
} else {
|
||||
return String.valueOf((int) v) + "x";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double fromString(String string) {
|
||||
return 1.0;
|
||||
}
|
||||
});
|
||||
speedSlider.valueProperty().addListener((val, oldValue, newValue) -> setSpeed(newValue.doubleValue()));
|
||||
Platform.runLater(() -> {
|
||||
speedSlider.setValue(Emulator.logic.speedSetting);
|
||||
// Kind of redundant but make sure speed is properly set as if the user did it
|
||||
setSpeed(Emulator.logic.speedSetting);
|
||||
});
|
||||
}
|
||||
|
||||
private void connectButtons(Node n) {
|
||||
if (n instanceof Button) {
|
||||
Button button = (Button) n;
|
||||
Runnable action = Utility.getNamedInvokableAction(button.getText());
|
||||
button.setOnMouseClicked(evt -> action.run());
|
||||
} else if (n instanceof Parent) {
|
||||
for (Node child : ((Parent) n).getChildrenUnmodifiable()) {
|
||||
connectButtons(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setSpeed(double speed) {
|
||||
Emulator.logic.speedSetting = (int) speed;
|
||||
double speedRatio = convertSpeedToRatio(speed);
|
||||
if (speedSlider.getValue() != speed) {
|
||||
Platform.runLater(() -> speedSlider.setValue(speed));
|
||||
}
|
||||
if (speedRatio > 100.0) {
|
||||
Emulator.computer.getMotherboard().setMaxSpeed(true);
|
||||
Motherboard.cpuPerClock = 3;
|
||||
} else {
|
||||
if (speedRatio > 25) {
|
||||
Motherboard.cpuPerClock = 2;
|
||||
} else {
|
||||
Motherboard.cpuPerClock = 1;
|
||||
}
|
||||
Emulator.computer.getMotherboard().setMaxSpeed(false);
|
||||
Emulator.computer.getMotherboard().setSpeedInPercentage((int) (speedRatio * 100));
|
||||
}
|
||||
Emulator.computer.getMotherboard().reconfigure();
|
||||
}
|
||||
|
||||
public void toggleAspectRatio() {
|
||||
setAspectRatioEnabled(aspectRatioCorrectionEnabled.not().get());
|
||||
}
|
||||
|
||||
public void setAspectRatioEnabled(boolean enabled) {
|
||||
aspectRatioCorrectionEnabled.set(enabled);
|
||||
}
|
||||
|
||||
public void connectComputer(Computer computer, Stage primaryStage) {
|
||||
if (computer == null) {
|
||||
return;
|
||||
}
|
||||
this.computer = computer;
|
||||
appleScreen.setImage(computer.getVideo().getFrameBuffer());
|
||||
EventHandler<KeyEvent> keyboardHandler = computer.getKeyboard().getListener();
|
||||
primaryStage.setOnShowing(evt -> computer.getKeyboard().resetState());
|
||||
rootPane.setFocusTraversable(true);
|
||||
rootPane.setOnKeyPressed(keyboardHandler);
|
||||
rootPane.setOnKeyReleased(keyboardHandler);
|
||||
rootPane.requestFocus();
|
||||
Platform.runLater(() -> {
|
||||
connectControls(primaryStage);
|
||||
appleScreen.setImage(computer.getVideo().getFrameBuffer());
|
||||
appleScreen.setVisible(true);
|
||||
rootPane.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
private void processDragEnteredEvent(DragEvent evt) {
|
||||
|
@ -182,10 +381,12 @@ public class JaceUIController {
|
|||
|
||||
private List<MediaConsumer> getMediaConsumers() {
|
||||
List<MediaConsumer> consumers = new ArrayList<>();
|
||||
for (Optional<Card> card : computer.memory.getAllCards()) {
|
||||
card.filter(c -> c instanceof MediaConsumerParent).ifPresent(parent -> {
|
||||
consumers.addAll(Arrays.asList(((MediaConsumerParent) parent).getConsumers()));
|
||||
});
|
||||
if (Emulator.logic.showDrives) {
|
||||
for (Optional<Card> card : computer.memory.getAllCards()) {
|
||||
card.filter(c -> c instanceof MediaConsumerParent).ifPresent(parent -> {
|
||||
consumers.addAll(Arrays.asList(((MediaConsumerParent) parent).getConsumers()));
|
||||
});
|
||||
}
|
||||
}
|
||||
return consumers;
|
||||
}
|
||||
|
@ -245,24 +446,25 @@ public class JaceUIController {
|
|||
public void removeMouseListener(EventHandler<MouseEvent> handler) {
|
||||
appleScreen.removeEventHandler(MouseEvent.ANY, handler);
|
||||
}
|
||||
|
||||
|
||||
Label currentNotification = null;
|
||||
|
||||
public void displayNotification(String message) {
|
||||
Label oldNotification = currentNotification;
|
||||
Label notification = new Label(message);
|
||||
currentNotification = notification;
|
||||
notification.setEffect(new DropShadow(2.0, Color.BLACK));
|
||||
notification.setTextFill(Color.WHITE);
|
||||
notification.setBackground(new Background(new BackgroundFill(Color.rgb(0,0,80, 0.7), new CornerRadii(5.0), new Insets(-5.0))));
|
||||
Application.invokeLater(() -> {
|
||||
notification.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 80, 0.7), new CornerRadii(5.0), new Insets(-5.0))));
|
||||
Application.invokeLater(() -> {
|
||||
stackPane.getChildren().remove(oldNotification);
|
||||
stackPane.getChildren().add(notification);
|
||||
});
|
||||
|
||||
notificationExecutor.schedule(()->{
|
||||
Application.invokeLater(() -> {
|
||||
|
||||
notificationExecutor.schedule(() -> {
|
||||
Application.invokeLater(() -> {
|
||||
stackPane.getChildren().remove(notification);
|
||||
});
|
||||
});
|
||||
}, 4, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
package jace.apple2e;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.JaceApplication;
|
||||
import jace.apple2e.softswitch.VideoSoftSwitch;
|
||||
import jace.cheat.Cheats;
|
||||
import jace.config.ClassSelection;
|
||||
import jace.config.ConfigurableField;
|
||||
|
@ -36,25 +38,27 @@ import jace.hardware.CardExt80Col;
|
|||
import jace.hardware.ConsoleProbe;
|
||||
import jace.hardware.Joystick;
|
||||
import jace.hardware.NoSlotClock;
|
||||
import jace.hardware.ZipWarpAccelerator;
|
||||
import jace.hardware.massStorage.CardMassStorage;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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.
|
||||
* 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
|
||||
*/
|
||||
|
@ -93,6 +97,8 @@ public class Apple2e extends Computer {
|
|||
public boolean joy2enabled = false;
|
||||
@ConfigurableField(name = "No-Slot Clock Enabled", shortName = "clock", description = "If checked, no-slot clock will be enabled", enablesDevice = true)
|
||||
public boolean clockEnabled = true;
|
||||
@ConfigurableField(name = "Accelerator Enabled", shortName = "zip", description = "If checked, add support for Zip/Transwarp", enablesDevice = true)
|
||||
public boolean acceleratorEnabled = true;
|
||||
|
||||
public Joystick joystick1;
|
||||
public Joystick joystick2;
|
||||
|
@ -100,6 +106,7 @@ public class Apple2e extends Computer {
|
|||
public ClassSelection cheatEngine = new ClassSelection(Cheats.class, null);
|
||||
public Cheats activeCheatEngine = null;
|
||||
public NoSlotClock clock;
|
||||
public ZipWarpAccelerator accelerator;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Apple2e
|
||||
|
@ -121,7 +128,7 @@ public class Apple2e extends Computer {
|
|||
return "Computer (Apple //e)";
|
||||
}
|
||||
|
||||
private void reinitMotherboard() {
|
||||
protected void reinitMotherboard() {
|
||||
if (motherboard != null && motherboard.isRunning()) {
|
||||
motherboard.suspend();
|
||||
}
|
||||
|
@ -134,16 +141,26 @@ public class Apple2e extends Computer {
|
|||
public void coldStart() {
|
||||
pause();
|
||||
reinitMotherboard();
|
||||
RAM128k ram = (RAM128k) getMemory();
|
||||
ram.initMemoryPattern(ram.mainMemory);
|
||||
ram.initMemoryPattern(ram.getAuxMemory());
|
||||
for (SoftSwitches s : SoftSwitches.values()) {
|
||||
s.getSwitch().reset();
|
||||
}
|
||||
getMemory().configureActiveMemory();
|
||||
getVideo().configureVideoMode();
|
||||
for (Optional<Card> c : getMemory().getAllCards()) {
|
||||
c.ifPresent(Card::reset);
|
||||
try {
|
||||
for (Optional<Card> c : getMemory().getAllCards()) {
|
||||
c.ifPresent(Card::reset);
|
||||
waitForVBL();
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
getCpu().resume();
|
||||
reboot();
|
||||
resume();
|
||||
}
|
||||
reboot();
|
||||
resume();
|
||||
}
|
||||
|
||||
public void reboot() {
|
||||
|
@ -158,7 +175,9 @@ public class Apple2e extends Computer {
|
|||
public void warmStart() {
|
||||
boolean restart = pause();
|
||||
for (SoftSwitches s : SoftSwitches.values()) {
|
||||
s.getSwitch().reset();
|
||||
if (!(s.getSwitch() instanceof VideoSoftSwitch)) {
|
||||
s.getSwitch().reset();
|
||||
}
|
||||
}
|
||||
getMemory().configureActiveMemory();
|
||||
getVideo().configureVideoMode();
|
||||
|
@ -194,11 +213,11 @@ public class Apple2e extends Computer {
|
|||
@Override
|
||||
public final void reconfigure() {
|
||||
boolean restart = pause();
|
||||
|
||||
|
||||
if (Utility.isHeadlessMode()) {
|
||||
joy1enabled = false;
|
||||
joy2enabled = false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
super.reconfigure();
|
||||
|
@ -216,9 +235,7 @@ public class Apple2e extends Computer {
|
|||
if (getMemory() == null) {
|
||||
try {
|
||||
currentMemory = (RAM128k) ramCard.getValue().getConstructor(Computer.class).newInstance(this);
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (IllegalArgumentException | InvocationTargetException ex) {
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
try {
|
||||
|
@ -232,39 +249,42 @@ public class Apple2e extends Computer {
|
|||
currentMemory.reconfigure();
|
||||
|
||||
if (motherboard != null) {
|
||||
if (accelerator == null) {
|
||||
accelerator = new ZipWarpAccelerator(this);
|
||||
}
|
||||
if (acceleratorEnabled) {
|
||||
motherboard.addChildDevice(accelerator);
|
||||
} else {
|
||||
motherboard.removeChildDevice(accelerator);
|
||||
}
|
||||
|
||||
if (joy1enabled) {
|
||||
if (joystick1 == null) {
|
||||
joystick1 = new Joystick(0, this);
|
||||
motherboard.miscDevices.add(joystick1);
|
||||
joystick1.attach();
|
||||
motherboard.addChildDevice(joystick1);
|
||||
}
|
||||
} else if (joystick1 != null) {
|
||||
joystick1.detach();
|
||||
motherboard.miscDevices.remove(joystick1);
|
||||
motherboard.removeChildDevice(joystick1);
|
||||
joystick1 = null;
|
||||
}
|
||||
|
||||
if (joy2enabled) {
|
||||
if (joystick2 == null) {
|
||||
joystick2 = new Joystick(1, this);
|
||||
motherboard.miscDevices.add(joystick2);
|
||||
joystick2.attach();
|
||||
motherboard.addChildDevice(joystick2);
|
||||
}
|
||||
} else if (joystick2 != null) {
|
||||
joystick2.detach();
|
||||
motherboard.miscDevices.remove(joystick2);
|
||||
motherboard.removeChildDevice(joystick2);
|
||||
joystick2 = null;
|
||||
}
|
||||
|
||||
if (clockEnabled) {
|
||||
if (clock == null) {
|
||||
clock = new NoSlotClock(this);
|
||||
motherboard.miscDevices.add(clock);
|
||||
clock.attach();
|
||||
motherboard.addChildDevice(clock);
|
||||
}
|
||||
} else if (clock != null) {
|
||||
motherboard.miscDevices.remove(clock);
|
||||
clock.detach();
|
||||
motherboard.removeChildDevice(clock);
|
||||
clock = null;
|
||||
}
|
||||
}
|
||||
|
@ -291,6 +311,9 @@ public class Apple2e extends Computer {
|
|||
getVideo().configureVideoMode();
|
||||
getVideo().reconfigure();
|
||||
Emulator.resizeVideo();
|
||||
if (JaceApplication.getApplication() != null) {
|
||||
JaceApplication.getApplication().reconnectUIHooks();
|
||||
}
|
||||
getVideo().resume();
|
||||
} catch (InstantiationException | IllegalAccessException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
|
@ -321,7 +344,7 @@ public class Apple2e extends Computer {
|
|||
if (cheatEngine.getValue() == null) {
|
||||
if (activeCheatEngine != null) {
|
||||
activeCheatEngine.detach();
|
||||
motherboard.miscDevices.remove(activeCheatEngine);
|
||||
motherboard.addChildDevice(activeCheatEngine);
|
||||
}
|
||||
activeCheatEngine = null;
|
||||
} else {
|
||||
|
@ -330,9 +353,8 @@ public class Apple2e extends Computer {
|
|||
if (activeCheatEngine.getClass().equals(cheatEngine.getValue())) {
|
||||
startCheats = false;
|
||||
} else {
|
||||
activeCheatEngine.detach();
|
||||
motherboard.removeChildDevice(activeCheatEngine);
|
||||
activeCheatEngine = null;
|
||||
motherboard.miscDevices.remove(activeCheatEngine);
|
||||
}
|
||||
}
|
||||
if (startCheats) {
|
||||
|
@ -341,8 +363,7 @@ public class Apple2e extends Computer {
|
|||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
activeCheatEngine.attach();
|
||||
motherboard.miscDevices.add(activeCheatEngine);
|
||||
motherboard.addChildDevice(activeCheatEngine);
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
|
@ -377,6 +398,35 @@ public class Apple2e extends Computer {
|
|||
// }
|
||||
private List<RAMListener> hints = new ArrayList<>();
|
||||
|
||||
List<Runnable> vblCallbacks = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
public void waitForVBL() throws InterruptedException {
|
||||
waitForVBL(0);
|
||||
}
|
||||
|
||||
public void waitForVBL(int count) throws InterruptedException {
|
||||
Semaphore s = new Semaphore(0);
|
||||
onNextVBL(s::release);
|
||||
s.acquire();
|
||||
if (count > 1) {
|
||||
waitForVBL(count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void onNextVBL(Runnable r) {
|
||||
vblCallbacks.add(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyVBLStateChanged(boolean state) {
|
||||
super.notifyVBLStateChanged(state);
|
||||
if (state) {
|
||||
while (vblCallbacks != null && !vblCallbacks.isEmpty()) {
|
||||
vblCallbacks.remove(0).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScheduledExecutorService animationTimer = new ScheduledThreadPoolExecutor(1);
|
||||
Runnable drawHints = () -> {
|
||||
if (getCpu().getProgramCounter() >> 8 != 0x0c6) {
|
||||
|
@ -438,10 +488,10 @@ public class Apple2e extends Computer {
|
|||
|
||||
private void enableHints() {
|
||||
if (hints.isEmpty()) {
|
||||
hints.add(getMemory().observe(RAMEvent.TYPE.EXECUTE, 0x0FB63, (e)->{
|
||||
animationTimer.schedule(drawHints, 1, TimeUnit.SECONDS);
|
||||
animationSchedule =
|
||||
animationTimer.scheduleAtFixedRate(doAnimation, 1250, 100, TimeUnit.MILLISECONDS);
|
||||
hints.add(getMemory().observe(RAMEvent.TYPE.EXECUTE, 0x0FB63, (e) -> {
|
||||
animationTimer.schedule(drawHints, 1, TimeUnit.SECONDS);
|
||||
animationSchedule
|
||||
= animationTimer.scheduleAtFixedRate(doAnimation, 1250, 100, TimeUnit.MILLISECONDS);
|
||||
}));
|
||||
// Latch to the PRODOS SYNTAX CHECK parser
|
||||
/*
|
||||
|
@ -475,4 +525,4 @@ public class Apple2e extends Computer {
|
|||
public String getShortName() {
|
||||
return "computer";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1326,6 +1326,9 @@ public class MOS65C02 extends CPU {
|
|||
*/
|
||||
StringBuilder out = new StringBuilder(o.getCommand().toString());
|
||||
out.append(" ").append(format);
|
||||
if (o.getMode().isIndirect()) {
|
||||
out.append(" >> $").append(Integer.toHexString(o.getMode().getCalculator().calculateAddress(this)));
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
private boolean pageBoundaryPenalty = false;
|
||||
|
|
|
@ -20,7 +20,6 @@ package jace.apple2e;
|
|||
|
||||
import jace.Emulator;
|
||||
import jace.EmulatorUILogic;
|
||||
import static jace.apple2e.VideoDHGR.BLACK;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.config.InvokableAction;
|
||||
import jace.core.Computer;
|
||||
|
@ -34,6 +33,8 @@ import javafx.scene.image.PixelWriter;
|
|||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import static jace.apple2e.VideoDHGR.BLACK;
|
||||
|
||||
/**
|
||||
* Provides a clean color monitor simulation, complete with text-friendly
|
||||
* palette and mixed color/bw (mode 7) rendering. This class extends the
|
||||
|
@ -57,14 +58,17 @@ public class VideoNTSC extends VideoDHGR {
|
|||
public boolean enableVideo7 = true;
|
||||
// Scanline represents 560 bits, divided up into 28-bit words
|
||||
int[] scanline = new int[20];
|
||||
static int[] divBy28 = new int[560];
|
||||
static public int[] divBy28 = new int[560];
|
||||
|
||||
@ConfigurableField(name = "Video Mode", category = "video", shortName = "mode", defaultValue = "TextFriendly", description = "Set Video Mode (Color|TextFriendly|Mode7|Mode7TextFriendly|Monochrome|Greenscreen|Amber)")
|
||||
public static VideoMode videoMode = VideoMode.TextFriendly;
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 560; i++) {
|
||||
divBy28[i] = i / 28;
|
||||
}
|
||||
}
|
||||
boolean[] colorActive = new boolean[80];
|
||||
protected boolean[] colorActive = new boolean[80];
|
||||
int rowStart = 0;
|
||||
|
||||
public VideoNTSC(Computer computer) {
|
||||
|
@ -81,58 +85,68 @@ public class VideoNTSC extends VideoDHGR {
|
|||
Greenscreen("Green"),
|
||||
Amber("Amber");
|
||||
String name;
|
||||
|
||||
VideoMode(String n) {
|
||||
name = n;
|
||||
}
|
||||
}
|
||||
|
||||
static int currentMode = -1;
|
||||
|
||||
@InvokableAction(name = "Toggle video mode",
|
||||
category = "video",
|
||||
alternatives = "mode,color,b&w,monochrome",
|
||||
alternatives = "Gfx mode;color;b&w;monochrome",
|
||||
defaultKeyMapping = {"ctrl+shift+g"})
|
||||
public static void changeVideoMode() {
|
||||
VideoNTSC thiss = (VideoNTSC) Emulator.computer.video;
|
||||
int currentMode = Arrays.asList(VideoMode.values()).indexOf(thiss.getVideoMode());
|
||||
currentMode++;
|
||||
if (currentMode >= VideoMode.values().length) {
|
||||
currentMode = 0;
|
||||
}
|
||||
thiss.monochomeMode = false;
|
||||
thiss.setVideoMode(VideoMode.values()[currentMode]);
|
||||
}
|
||||
|
||||
public void setVideoMode(VideoMode mode) {
|
||||
videoMode = mode;
|
||||
monochomeMode = false;
|
||||
WHITE = Color.WHITE;
|
||||
switch (VideoMode.values()[currentMode]) {
|
||||
switch (mode) {
|
||||
case Amber:
|
||||
thiss.monochomeMode = true;
|
||||
monochomeMode = true;
|
||||
WHITE = Color.web("ff8000");
|
||||
break;
|
||||
case Greenscreen:
|
||||
thiss.monochomeMode = true;
|
||||
monochomeMode = true;
|
||||
WHITE = Color.web("0ccc68");
|
||||
break;
|
||||
case Monochrome:
|
||||
thiss.monochomeMode = true;
|
||||
monochomeMode = true;
|
||||
break;
|
||||
case Color:
|
||||
thiss.useTextPalette = false;
|
||||
thiss.enableVideo7 = false;
|
||||
useTextPalette = false;
|
||||
enableVideo7 = false;
|
||||
break;
|
||||
case Mode7:
|
||||
thiss.useTextPalette = false;
|
||||
thiss.enableVideo7 = true;
|
||||
useTextPalette = false;
|
||||
enableVideo7 = true;
|
||||
break;
|
||||
case Mode7TextFriendly:
|
||||
thiss.useTextPalette = true;
|
||||
thiss.enableVideo7 = true;
|
||||
useTextPalette = true;
|
||||
enableVideo7 = true;
|
||||
break;
|
||||
case TextFriendly:
|
||||
thiss.useTextPalette = true;
|
||||
thiss.enableVideo7 = false;
|
||||
useTextPalette = true;
|
||||
enableVideo7 = false;
|
||||
break;
|
||||
}
|
||||
thiss.activePalette = thiss.useTextPalette ? TEXT_PALETTE : SOLID_PALETTE;
|
||||
EmulatorUILogic.notify("Video mode: "+VideoMode.values()[currentMode].name);
|
||||
activePalette = useTextPalette ? TEXT_PALETTE : SOLID_PALETTE;
|
||||
EmulatorUILogic.notify("Video mode: " + mode.name);
|
||||
forceRefresh();
|
||||
}
|
||||
|
||||
|
||||
public VideoMode getVideoMode() {
|
||||
return videoMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showBW(WritableImage screen, int x, int y, int dhgrWord) {
|
||||
int pos = divBy28[x];
|
||||
|
@ -235,6 +249,7 @@ public class VideoNTSC extends VideoDHGR {
|
|||
}
|
||||
|
||||
boolean monochomeMode = false;
|
||||
|
||||
private void renderScanline(WritableImage screen, int y) {
|
||||
int p = 0;
|
||||
if (rowStart != 0) {
|
||||
|
@ -364,12 +379,13 @@ public class VideoNTSC extends VideoDHGR {
|
|||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
setVideoMode(videoMode);
|
||||
activePalette = useTextPalette ? TEXT_PALETTE : SOLID_PALETTE;
|
||||
super.reconfigure();
|
||||
}
|
||||
// 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
|
||||
// http://www.freepatentsonline.com/4631692.pdf
|
||||
// as well as the AppleColor adapter card manual
|
||||
// http://apple2.info/download/Ext80ColumnAppleColorCardHR.pdf
|
||||
rgbMode graphicsMode = rgbMode.MIX;
|
||||
|
@ -426,7 +442,7 @@ public class VideoNTSC extends VideoDHGR {
|
|||
}));
|
||||
rgbStateListeners.add(memory.observe(RAMEvent.TYPE.EXECUTE, 0x0fa62, (e) -> {
|
||||
// When reset hook is called, reset the graphics mode
|
||||
// This is useful in case a program is running that
|
||||
// This is useful in case a program is running that
|
||||
// is totally clueless how to set the RGB state correctly.
|
||||
f1 = true;
|
||||
f2 = true;
|
||||
|
|
|
@ -25,7 +25,7 @@ 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
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class MemorySoftSwitch extends SoftSwitch {
|
||||
|
||||
|
@ -48,11 +48,15 @@ public class MemorySoftSwitch extends SoftSwitch {
|
|||
// Todo: Implement floating bus, maybe?
|
||||
@Override
|
||||
protected byte readSwitch() {
|
||||
byte value = computer.getVideo().getFloatingBus();
|
||||
if (getState()) {
|
||||
return (byte) (value | 0x080);
|
||||
if (computer.getVideo() == null) {
|
||||
return 0x00;
|
||||
} else {
|
||||
return (byte) (value & 0x07f);
|
||||
byte value = computer.getVideo().getFloatingBus();
|
||||
if (getState()) {
|
||||
return (byte) (value | 0x080);
|
||||
} else {
|
||||
return (byte) (value & 0x07f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public abstract class Cheats extends Device {
|
|||
super(computer);
|
||||
}
|
||||
|
||||
@InvokableAction(name = "Toggle Cheats", alternatives = "cheat", defaultKeyMapping = "ctrl+shift+m")
|
||||
@InvokableAction(name = "Toggle Cheats", alternatives = "cheat;Plug-in", defaultKeyMapping = "ctrl+shift+m")
|
||||
public void toggleCheats() {
|
||||
cheatsActive = !cheatsActive;
|
||||
if (cheatsActive) {
|
||||
|
@ -97,7 +97,7 @@ public abstract class Cheats extends Device {
|
|||
super.detach();
|
||||
}
|
||||
|
||||
abstract void registerListeners();
|
||||
public abstract void registerListeners();
|
||||
|
||||
protected void unregisterListeners() {
|
||||
listeners.stream().forEach((l) -> {
|
||||
|
|
|
@ -13,6 +13,7 @@ import javafx.beans.value.ChangeListener;
|
|||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.shape.Shape;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -31,10 +32,6 @@ public class MemoryCell implements Comparable<MemoryCell> {
|
|||
public ObservableList<Integer> writeInstructions = FXCollections.observableList(new ArrayList<>());
|
||||
public ObservableList<String> writeInstructionsDisassembly = FXCollections.observableArrayList(new ArrayList<>());
|
||||
public ObservableList<String> execInstructionsDisassembly = FXCollections.observableArrayList(new ArrayList<>());
|
||||
private int x;
|
||||
private int y;
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public static void setListener(ChangeListener<MemoryCell> l) {
|
||||
listener = l;
|
||||
|
@ -49,29 +46,6 @@ public class MemoryCell implements Comparable<MemoryCell> {
|
|||
value.addListener(changeListener);
|
||||
}
|
||||
|
||||
public void setRect(int x, int y, int w, int h) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = w;
|
||||
this.height = h;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MemoryCell o) {
|
||||
return address - o.address;
|
||||
|
@ -80,4 +54,14 @@ public class MemoryCell implements Comparable<MemoryCell> {
|
|||
public boolean hasCounts() {
|
||||
return hasCount.get();
|
||||
}
|
||||
|
||||
private Shape shape;
|
||||
|
||||
public void setShape(Shape s) {
|
||||
shape = s;
|
||||
}
|
||||
|
||||
public Shape getShape() {
|
||||
return shape;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,445 +1,445 @@
|
|||
package jace.cheat;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.JaceApplication;
|
||||
import jace.core.CPU;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.state.State;
|
||||
import jace.ui.MetacheatUI;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
public class MetaCheat extends Cheats {
|
||||
|
||||
static final ScriptEngine NASHORN_ENGINE = new ScriptEngineManager().getEngineByName("nashorn");
|
||||
static Invocable NASHORN_INVOCABLE = (Invocable) NASHORN_ENGINE;
|
||||
|
||||
public static enum SearchType {
|
||||
VALUE, TEXT, CHANGE
|
||||
}
|
||||
|
||||
public static enum SearchChangeType {
|
||||
NO_CHANGE, ANY_CHANGE, LESS, GREATER, AMOUNT
|
||||
}
|
||||
|
||||
public static class SearchResult {
|
||||
|
||||
int address;
|
||||
int lastObservedValue = 0;
|
||||
|
||||
private SearchResult(int address, int val) {
|
||||
this.address = address;
|
||||
lastObservedValue = val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toHexString(address) + ": " + lastObservedValue + " (" + Integer.toHexString(lastObservedValue) + ")";
|
||||
}
|
||||
|
||||
public int getAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
MetacheatUI ui;
|
||||
|
||||
public int fadeRate = 1;
|
||||
public int lightRate = 30;
|
||||
public int historyLength = 10;
|
||||
|
||||
private int startAddress = 0;
|
||||
private int endAddress = 0x0ffff;
|
||||
private final StringProperty startAddressProperty = new SimpleStringProperty(Integer.toHexString(startAddress));
|
||||
private final StringProperty endAddressProperty = new SimpleStringProperty(Integer.toHexString(endAddress));
|
||||
private boolean byteSized = true;
|
||||
private SearchType searchType = SearchType.VALUE;
|
||||
private SearchChangeType searchChangeType = SearchChangeType.NO_CHANGE;
|
||||
private final BooleanProperty signedProperty = new SimpleBooleanProperty(false);
|
||||
private final StringProperty searchValueProperty = new SimpleStringProperty("0");
|
||||
private final StringProperty changeByProperty = new SimpleStringProperty("0");
|
||||
private final ObservableList<DynamicCheat> cheatList = FXCollections.observableArrayList();
|
||||
private final ObservableList<SearchResult> resultList = FXCollections.observableArrayList();
|
||||
private final ObservableList<State> snapshotList = FXCollections.observableArrayList();
|
||||
|
||||
public MetaCheat(Computer computer) {
|
||||
super(computer);
|
||||
addNumericValidator(startAddressProperty);
|
||||
addNumericValidator(endAddressProperty);
|
||||
addNumericValidator(searchValueProperty);
|
||||
addNumericValidator(changeByProperty);
|
||||
startAddressProperty.addListener((prop, oldVal, newVal) -> {
|
||||
startAddress = Math.max(0, Math.min(65535, parseInt(newVal)));
|
||||
});
|
||||
endAddressProperty.addListener((prop, oldVal, newVal) -> {
|
||||
endAddress = Math.max(0, Math.min(65535, parseInt(newVal)));
|
||||
});
|
||||
}
|
||||
|
||||
private void addNumericValidator(StringProperty stringProperty) {
|
||||
stringProperty.addListener((ObservableValue<? extends String> prop, String oldVal, String newVal) -> {
|
||||
if (newVal == null || newVal.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!newVal.matches("(\\+|-)?(x|$)?[0-9a-fA-F]*")) {
|
||||
stringProperty.set("");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int parseInt(String s) throws NumberFormatException {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
if (s.matches("(\\+|-)?[0-9]+")) {
|
||||
return Integer.parseInt(s);
|
||||
} else {
|
||||
String upper = s.toUpperCase();
|
||||
boolean positive = !upper.startsWith("-");
|
||||
for (int i = 0; i < upper.length(); i++) {
|
||||
char c = upper.charAt(i);
|
||||
if ((c >= '0' && c <= '9') || (c >= 'A' & c <= 'F')) {
|
||||
int value = Integer.parseInt(s.substring(i), 16);
|
||||
if (!positive) {
|
||||
value *= -1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new NumberFormatException("Could not interpret int value " + s);
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerListeners() {
|
||||
}
|
||||
|
||||
public void addCheat(DynamicCheat cheat) {
|
||||
cheatList.add(cheat);
|
||||
computer.getMemory().addListener(cheat);
|
||||
cheat.addressProperty().addListener((prop, oldVal, newVal) -> {
|
||||
computer.getMemory().removeListener(cheat);
|
||||
cheat.doConfig();
|
||||
computer.getMemory().addListener(cheat);
|
||||
});
|
||||
}
|
||||
|
||||
public void removeCheat(DynamicCheat cheat) {
|
||||
cheat.active.set(false);
|
||||
computer.getMemory().removeListener(cheat);
|
||||
cheatList.remove(cheat);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
super.unregisterListeners();
|
||||
cheatList.stream().forEach(computer.getMemory()::removeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "MetaCheat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
super.detach();
|
||||
ui.detach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
ui = JaceApplication.getApplication().showMetacheat();
|
||||
ui.registerMetacheatEngine(this);
|
||||
super.attach();
|
||||
}
|
||||
|
||||
public int getStartAddress() {
|
||||
return startAddress;
|
||||
}
|
||||
|
||||
public int getEndAddress() {
|
||||
return endAddress;
|
||||
}
|
||||
|
||||
public void setByteSized(boolean b) {
|
||||
byteSized = b;
|
||||
}
|
||||
|
||||
public void setSearchType(SearchType searchType) {
|
||||
this.searchType = searchType;
|
||||
}
|
||||
|
||||
public void setSearchChangeType(SearchChangeType searchChangeType) {
|
||||
this.searchChangeType = searchChangeType;
|
||||
}
|
||||
|
||||
public Property<Boolean> signedProperty() {
|
||||
return signedProperty;
|
||||
}
|
||||
|
||||
public Property<String> searchValueProperty() {
|
||||
return searchValueProperty;
|
||||
}
|
||||
|
||||
public Property<String> searchChangeByProperty() {
|
||||
return changeByProperty;
|
||||
}
|
||||
|
||||
public ObservableList<DynamicCheat> getCheats() {
|
||||
return cheatList;
|
||||
}
|
||||
|
||||
public ObservableList<SearchResult> getSearchResults() {
|
||||
return resultList;
|
||||
}
|
||||
|
||||
public ObservableList<State> getSnapshots() {
|
||||
return snapshotList;
|
||||
}
|
||||
|
||||
public Property<String> startAddressProperty() {
|
||||
return startAddressProperty;
|
||||
}
|
||||
|
||||
public Property<String> endAddressProperty() {
|
||||
return endAddressProperty;
|
||||
}
|
||||
|
||||
public void newSearch() {
|
||||
RAM memory = Emulator.computer.getMemory();
|
||||
resultList.clear();
|
||||
int compare = parseInt(searchValueProperty.get());
|
||||
for (int i = 0; i < 0x10000; i++) {
|
||||
boolean signed = signedProperty.get();
|
||||
int val
|
||||
= byteSized
|
||||
? signed ? memory.readRaw(i) : memory.readRaw(i) & 0x0ff
|
||||
: signed ? memory.readWordRaw(i) : memory.readWordRaw(i) & 0x0ffff;
|
||||
if (!searchType.equals(SearchType.VALUE) || val == compare) {
|
||||
SearchResult result = new SearchResult(i, val);
|
||||
resultList.add(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void performSearch() {
|
||||
RAM memory = Emulator.computer.getMemory();
|
||||
boolean signed = signedProperty.get();
|
||||
resultList.removeIf((SearchResult result) -> {
|
||||
int val = byteSized
|
||||
? signed ? memory.readRaw(result.address) : memory.readRaw(result.address) & 0x0ff
|
||||
: signed ? memory.readWordRaw(result.address) : memory.readWordRaw(result.address) & 0x0ffff;
|
||||
int last = result.lastObservedValue;
|
||||
result.lastObservedValue = val;
|
||||
switch (searchType) {
|
||||
case VALUE:
|
||||
int compare = parseInt(searchValueProperty.get());
|
||||
return compare != val;
|
||||
case CHANGE:
|
||||
switch (searchChangeType) {
|
||||
case AMOUNT:
|
||||
int amount = parseInt(searchChangeByProperty().getValue());
|
||||
return (val - last) != amount;
|
||||
case GREATER:
|
||||
return val <= last;
|
||||
case ANY_CHANGE:
|
||||
return val == last;
|
||||
case LESS:
|
||||
return val >= last;
|
||||
case NO_CHANGE:
|
||||
return val != last;
|
||||
}
|
||||
break;
|
||||
case TEXT:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
RAMListener memoryViewListener = null;
|
||||
private final Map<Integer, MemoryCell> memoryCells = new ConcurrentHashMap<>();
|
||||
|
||||
public MemoryCell getMemoryCell(int address) {
|
||||
return memoryCells.get(address);
|
||||
}
|
||||
|
||||
public void initMemoryView() {
|
||||
RAM memory = Emulator.computer.getMemory();
|
||||
for (int addr = getStartAddress(); addr <= getEndAddress(); addr++) {
|
||||
if (getMemoryCell(addr) == null) {
|
||||
MemoryCell cell = new MemoryCell();
|
||||
cell.address = addr;
|
||||
cell.value.set(memory.readRaw(addr));
|
||||
memoryCells.put(addr, cell);
|
||||
}
|
||||
}
|
||||
if (memoryViewListener == null) {
|
||||
memoryViewListener = memory.observe(RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent);
|
||||
listeners.add(memoryViewListener);
|
||||
}
|
||||
}
|
||||
|
||||
int fadeCounter = 0;
|
||||
int FADE_TIMER_VALUE = (int) (Emulator.computer.getMotherboard().cyclesPerSecond / 60);
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
computer.cpu.performSingleTrace();
|
||||
if (fadeCounter-- <= 0) {
|
||||
fadeCounter = FADE_TIMER_VALUE;
|
||||
memoryCells.values().stream()
|
||||
.filter((cell) -> cell.hasCounts())
|
||||
.forEach((cell) -> {
|
||||
if (cell.execCount.get() > 0) {
|
||||
cell.execCount.set(Math.max(0, cell.execCount.get() - fadeRate));
|
||||
}
|
||||
if (cell.readCount.get() > 0) {
|
||||
cell.readCount.set(Math.max(0, cell.readCount.get() - fadeRate));
|
||||
}
|
||||
if (cell.writeCount.get() > 0) {
|
||||
cell.writeCount.set(Math.max(0, cell.writeCount.get() - fadeRate));
|
||||
}
|
||||
if (MemoryCell.listener != null) {
|
||||
MemoryCell.listener.changed(null, cell, cell);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AtomicInteger pendingInspectorUpdates = new AtomicInteger(0);
|
||||
public void onInspectorChanged() {
|
||||
pendingInspectorUpdates.set(0);
|
||||
}
|
||||
|
||||
private void processMemoryEvent(RAMEvent e) {
|
||||
MemoryCell cell = getMemoryCell(e.getAddress());
|
||||
if (cell != null) {
|
||||
CPU cpu = Emulator.computer.getCpu();
|
||||
int pc = cpu.getProgramCounter();
|
||||
String trace = cpu.getLastTrace();
|
||||
switch (e.getType()) {
|
||||
case EXECUTE:
|
||||
cell.execInstructionsDisassembly.add(trace);
|
||||
if (cell.execInstructionsDisassembly.size() > historyLength) {
|
||||
cell.execInstructionsDisassembly.remove(0);
|
||||
}
|
||||
case READ_OPERAND:
|
||||
cell.execCount.set(Math.min(255, cell.execCount.get() + lightRate));
|
||||
break;
|
||||
case WRITE:
|
||||
cell.writeCount.set(Math.min(255, cell.writeCount.get() + lightRate));
|
||||
if (ui.isInspecting(cell.address)) {
|
||||
if (pendingInspectorUpdates.incrementAndGet() < 5) {
|
||||
Platform.runLater(() -> {
|
||||
pendingInspectorUpdates.decrementAndGet();
|
||||
cell.writeInstructions.add(pc);
|
||||
cell.writeInstructionsDisassembly.add(trace);
|
||||
if (cell.writeInstructions.size() > historyLength) {
|
||||
cell.writeInstructions.remove(0);
|
||||
cell.writeInstructionsDisassembly.remove(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cell.writeInstructions.add(cpu.getProgramCounter());
|
||||
cell.writeInstructionsDisassembly.add(cpu.getLastTrace());
|
||||
if (cell.writeInstructions.size() > historyLength) {
|
||||
cell.writeInstructions.remove(0);
|
||||
cell.writeInstructionsDisassembly.remove(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
cell.readCount.set(Math.min(255, cell.readCount.get() + lightRate));
|
||||
if (ui.isInspecting(cell.address)) {
|
||||
if (pendingInspectorUpdates.incrementAndGet() < 5) {
|
||||
Platform.runLater(() -> {
|
||||
pendingInspectorUpdates.decrementAndGet();
|
||||
cell.readInstructions.add(pc);
|
||||
cell.readInstructionsDisassembly.add(trace);
|
||||
if (cell.readInstructions.size() > historyLength) {
|
||||
cell.readInstructions.remove(0);
|
||||
cell.readInstructionsDisassembly.remove(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cell.readInstructions.add(cpu.getProgramCounter());
|
||||
cell.readInstructionsDisassembly.add(cpu.getLastTrace());
|
||||
if (cell.readInstructions.size() > historyLength) {
|
||||
cell.readInstructions.remove(0);
|
||||
cell.readInstructionsDisassembly.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
cell.value.set(e.getNewValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void saveCheats(File saveFile) {
|
||||
FileWriter writer = null;
|
||||
try {
|
||||
writer = new FileWriter(saveFile);
|
||||
for (DynamicCheat cheat : cheatList) {
|
||||
writer.write(cheat.serialize());
|
||||
writer.write("\n");
|
||||
}
|
||||
writer.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadCheats(File saveFile) {
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
in = new BufferedReader(new FileReader(saveFile));
|
||||
StringBuilder guts = new StringBuilder();
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
DynamicCheat cheat = DynamicCheat.deserialize(line);
|
||||
addCheat(cheat);
|
||||
}
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
package jace.cheat;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.JaceApplication;
|
||||
import jace.core.CPU;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
import jace.state.State;
|
||||
import jace.ui.MetacheatUI;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
public class MetaCheat extends Cheats {
|
||||
|
||||
static final ScriptEngine NASHORN_ENGINE = new ScriptEngineManager().getEngineByName("nashorn");
|
||||
static Invocable NASHORN_INVOCABLE = (Invocable) NASHORN_ENGINE;
|
||||
|
||||
public static enum SearchType {
|
||||
VALUE, TEXT, CHANGE
|
||||
}
|
||||
|
||||
public static enum SearchChangeType {
|
||||
NO_CHANGE, ANY_CHANGE, LESS, GREATER, AMOUNT
|
||||
}
|
||||
|
||||
public static class SearchResult {
|
||||
|
||||
int address;
|
||||
int lastObservedValue = 0;
|
||||
|
||||
private SearchResult(int address, int val) {
|
||||
this.address = address;
|
||||
lastObservedValue = val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toHexString(address) + ": " + lastObservedValue + " (" + Integer.toHexString(lastObservedValue) + ")";
|
||||
}
|
||||
|
||||
public int getAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
MetacheatUI ui;
|
||||
|
||||
public int fadeRate = 1;
|
||||
public int lightRate = 30;
|
||||
public int historyLength = 10;
|
||||
|
||||
private int startAddress = 0;
|
||||
private int endAddress = 0x0AFFF;
|
||||
private final StringProperty startAddressProperty = new SimpleStringProperty(Integer.toHexString(startAddress));
|
||||
private final StringProperty endAddressProperty = new SimpleStringProperty(Integer.toHexString(endAddress));
|
||||
private boolean byteSized = true;
|
||||
private SearchType searchType = SearchType.VALUE;
|
||||
private SearchChangeType searchChangeType = SearchChangeType.NO_CHANGE;
|
||||
private final BooleanProperty signedProperty = new SimpleBooleanProperty(false);
|
||||
private final StringProperty searchValueProperty = new SimpleStringProperty("0");
|
||||
private final StringProperty changeByProperty = new SimpleStringProperty("0");
|
||||
private final ObservableList<DynamicCheat> cheatList = FXCollections.observableArrayList();
|
||||
private final ObservableList<SearchResult> resultList = FXCollections.observableArrayList();
|
||||
private final ObservableList<State> snapshotList = FXCollections.observableArrayList();
|
||||
|
||||
public MetaCheat(Computer computer) {
|
||||
super(computer);
|
||||
addNumericValidator(startAddressProperty);
|
||||
addNumericValidator(endAddressProperty);
|
||||
addNumericValidator(searchValueProperty);
|
||||
addNumericValidator(changeByProperty);
|
||||
startAddressProperty.addListener((prop, oldVal, newVal) -> {
|
||||
startAddress = Math.max(0, Math.min(65535, Integer.parseInt(newVal, 16)));
|
||||
});
|
||||
endAddressProperty.addListener((prop, oldVal, newVal) -> {
|
||||
endAddress = Math.max(0, Math.min(65535, Integer.parseInt(newVal, 16)));
|
||||
});
|
||||
}
|
||||
|
||||
private void addNumericValidator(StringProperty stringProperty) {
|
||||
stringProperty.addListener((ObservableValue<? extends String> prop, String oldVal, String newVal) -> {
|
||||
if (newVal == null || newVal.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!newVal.matches("(\\+|-)?(x|$)?[0-9a-fA-F]*")) {
|
||||
stringProperty.set("");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int parseInt(String s) throws NumberFormatException {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
if (s.matches("(\\+|-)?[0-9]+")) {
|
||||
return Integer.parseInt(s);
|
||||
} else {
|
||||
String upper = s.toUpperCase();
|
||||
boolean positive = !upper.startsWith("-");
|
||||
for (int i = 0; i < upper.length(); i++) {
|
||||
char c = upper.charAt(i);
|
||||
if ((c >= '0' && c <= '9') || (c >= 'A' & c <= 'F')) {
|
||||
int value = Integer.parseInt(s.substring(i), 16);
|
||||
if (!positive) {
|
||||
value *= -1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new NumberFormatException("Could not interpret int value " + s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
}
|
||||
|
||||
public void addCheat(DynamicCheat cheat) {
|
||||
cheatList.add(cheat);
|
||||
computer.getMemory().addListener(cheat);
|
||||
cheat.addressProperty().addListener((prop, oldVal, newVal) -> {
|
||||
computer.getMemory().removeListener(cheat);
|
||||
cheat.doConfig();
|
||||
computer.getMemory().addListener(cheat);
|
||||
});
|
||||
}
|
||||
|
||||
public void removeCheat(DynamicCheat cheat) {
|
||||
cheat.active.set(false);
|
||||
computer.getMemory().removeListener(cheat);
|
||||
cheatList.remove(cheat);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
super.unregisterListeners();
|
||||
cheatList.stream().forEach(computer.getMemory()::removeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "MetaCheat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
super.detach();
|
||||
ui.detach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
ui = JaceApplication.getApplication().showMetacheat();
|
||||
ui.registerMetacheatEngine(this);
|
||||
super.attach();
|
||||
}
|
||||
|
||||
public int getStartAddress() {
|
||||
return startAddress;
|
||||
}
|
||||
|
||||
public int getEndAddress() {
|
||||
return endAddress;
|
||||
}
|
||||
|
||||
public void setByteSized(boolean b) {
|
||||
byteSized = b;
|
||||
}
|
||||
|
||||
public void setSearchType(SearchType searchType) {
|
||||
this.searchType = searchType;
|
||||
}
|
||||
|
||||
public void setSearchChangeType(SearchChangeType searchChangeType) {
|
||||
this.searchChangeType = searchChangeType;
|
||||
}
|
||||
|
||||
public Property<Boolean> signedProperty() {
|
||||
return signedProperty;
|
||||
}
|
||||
|
||||
public Property<String> searchValueProperty() {
|
||||
return searchValueProperty;
|
||||
}
|
||||
|
||||
public Property<String> searchChangeByProperty() {
|
||||
return changeByProperty;
|
||||
}
|
||||
|
||||
public ObservableList<DynamicCheat> getCheats() {
|
||||
return cheatList;
|
||||
}
|
||||
|
||||
public ObservableList<SearchResult> getSearchResults() {
|
||||
return resultList;
|
||||
}
|
||||
|
||||
public ObservableList<State> getSnapshots() {
|
||||
return snapshotList;
|
||||
}
|
||||
|
||||
public Property<String> startAddressProperty() {
|
||||
return startAddressProperty;
|
||||
}
|
||||
|
||||
public Property<String> endAddressProperty() {
|
||||
return endAddressProperty;
|
||||
}
|
||||
|
||||
public void newSearch() {
|
||||
RAM memory = Emulator.computer.getMemory();
|
||||
resultList.clear();
|
||||
int compare = parseInt(searchValueProperty.get());
|
||||
for (int i = 0; i < 0x10000; i++) {
|
||||
boolean signed = signedProperty.get();
|
||||
int val
|
||||
= byteSized
|
||||
? signed ? memory.readRaw(i) : memory.readRaw(i) & 0x0ff
|
||||
: signed ? memory.readWordRaw(i) : memory.readWordRaw(i) & 0x0ffff;
|
||||
if (!searchType.equals(SearchType.VALUE) || val == compare) {
|
||||
SearchResult result = new SearchResult(i, val);
|
||||
resultList.add(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void performSearch() {
|
||||
RAM memory = Emulator.computer.getMemory();
|
||||
boolean signed = signedProperty.get();
|
||||
resultList.removeIf((SearchResult result) -> {
|
||||
int val = byteSized
|
||||
? signed ? memory.readRaw(result.address) : memory.readRaw(result.address) & 0x0ff
|
||||
: signed ? memory.readWordRaw(result.address) : memory.readWordRaw(result.address) & 0x0ffff;
|
||||
int last = result.lastObservedValue;
|
||||
result.lastObservedValue = val;
|
||||
switch (searchType) {
|
||||
case VALUE:
|
||||
int compare = parseInt(searchValueProperty.get());
|
||||
return compare != val;
|
||||
case CHANGE:
|
||||
switch (searchChangeType) {
|
||||
case AMOUNT:
|
||||
int amount = parseInt(searchChangeByProperty().getValue());
|
||||
return (val - last) != amount;
|
||||
case GREATER:
|
||||
return val <= last;
|
||||
case ANY_CHANGE:
|
||||
return val == last;
|
||||
case LESS:
|
||||
return val >= last;
|
||||
case NO_CHANGE:
|
||||
return val != last;
|
||||
}
|
||||
break;
|
||||
case TEXT:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
RAMListener memoryViewListener = null;
|
||||
private final Map<Integer, MemoryCell> memoryCells = new ConcurrentHashMap<>();
|
||||
|
||||
public MemoryCell getMemoryCell(int address) {
|
||||
return memoryCells.get(address);
|
||||
}
|
||||
|
||||
public void initMemoryView() {
|
||||
RAM memory = Emulator.computer.getMemory();
|
||||
for (int addr = getStartAddress(); addr <= getEndAddress(); addr++) {
|
||||
if (getMemoryCell(addr) == null) {
|
||||
MemoryCell cell = new MemoryCell();
|
||||
cell.address = addr;
|
||||
cell.value.set(memory.readRaw(addr));
|
||||
memoryCells.put(addr, cell);
|
||||
}
|
||||
}
|
||||
if (memoryViewListener == null) {
|
||||
memoryViewListener = memory.observe(RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent);
|
||||
listeners.add(memoryViewListener);
|
||||
}
|
||||
}
|
||||
|
||||
int fadeCounter = 0;
|
||||
int FADE_TIMER_VALUE = (int) (Emulator.computer.getMotherboard().getSpeedInHz() / 60);
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
computer.cpu.performSingleTrace();
|
||||
if (fadeCounter-- <= 0) {
|
||||
fadeCounter = FADE_TIMER_VALUE;
|
||||
memoryCells.values().stream()
|
||||
.filter((cell) -> cell.hasCounts())
|
||||
.forEach((cell) -> {
|
||||
if (cell.execCount.get() > 0) {
|
||||
cell.execCount.set(Math.max(0, cell.execCount.get() - fadeRate));
|
||||
}
|
||||
if (cell.readCount.get() > 0) {
|
||||
cell.readCount.set(Math.max(0, cell.readCount.get() - fadeRate));
|
||||
}
|
||||
if (cell.writeCount.get() > 0) {
|
||||
cell.writeCount.set(Math.max(0, cell.writeCount.get() - fadeRate));
|
||||
}
|
||||
if (MemoryCell.listener != null) {
|
||||
MemoryCell.listener.changed(null, cell, cell);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AtomicInteger pendingInspectorUpdates = new AtomicInteger(0);
|
||||
public void onInspectorChanged() {
|
||||
pendingInspectorUpdates.set(0);
|
||||
}
|
||||
|
||||
private void processMemoryEvent(RAMEvent e) {
|
||||
MemoryCell cell = getMemoryCell(e.getAddress());
|
||||
if (cell != null) {
|
||||
CPU cpu = Emulator.computer.getCpu();
|
||||
int pc = cpu.getProgramCounter();
|
||||
String trace = cpu.getLastTrace();
|
||||
switch (e.getType()) {
|
||||
case EXECUTE:
|
||||
cell.execInstructionsDisassembly.add(trace);
|
||||
if (cell.execInstructionsDisassembly.size() > historyLength) {
|
||||
cell.execInstructionsDisassembly.remove(0);
|
||||
}
|
||||
case READ_OPERAND:
|
||||
cell.execCount.set(Math.min(255, cell.execCount.get() + lightRate));
|
||||
break;
|
||||
case WRITE:
|
||||
cell.writeCount.set(Math.min(255, cell.writeCount.get() + lightRate));
|
||||
if (ui.isInspecting(cell.address)) {
|
||||
if (pendingInspectorUpdates.incrementAndGet() < 5) {
|
||||
Platform.runLater(() -> {
|
||||
pendingInspectorUpdates.decrementAndGet();
|
||||
cell.writeInstructions.add(pc);
|
||||
cell.writeInstructionsDisassembly.add(trace);
|
||||
if (cell.writeInstructions.size() > historyLength) {
|
||||
cell.writeInstructions.remove(0);
|
||||
cell.writeInstructionsDisassembly.remove(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cell.writeInstructions.add(cpu.getProgramCounter());
|
||||
cell.writeInstructionsDisassembly.add(cpu.getLastTrace());
|
||||
if (cell.writeInstructions.size() > historyLength) {
|
||||
cell.writeInstructions.remove(0);
|
||||
cell.writeInstructionsDisassembly.remove(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
cell.readCount.set(Math.min(255, cell.readCount.get() + lightRate));
|
||||
if (ui.isInspecting(cell.address)) {
|
||||
if (pendingInspectorUpdates.incrementAndGet() < 5) {
|
||||
Platform.runLater(() -> {
|
||||
pendingInspectorUpdates.decrementAndGet();
|
||||
cell.readInstructions.add(pc);
|
||||
cell.readInstructionsDisassembly.add(trace);
|
||||
if (cell.readInstructions.size() > historyLength) {
|
||||
cell.readInstructions.remove(0);
|
||||
cell.readInstructionsDisassembly.remove(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cell.readInstructions.add(cpu.getProgramCounter());
|
||||
cell.readInstructionsDisassembly.add(cpu.getLastTrace());
|
||||
if (cell.readInstructions.size() > historyLength) {
|
||||
cell.readInstructions.remove(0);
|
||||
cell.readInstructionsDisassembly.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
cell.value.set(e.getNewValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void saveCheats(File saveFile) {
|
||||
FileWriter writer = null;
|
||||
try {
|
||||
writer = new FileWriter(saveFile);
|
||||
for (DynamicCheat cheat : cheatList) {
|
||||
writer.write(cheat.serialize());
|
||||
writer.write("\n");
|
||||
}
|
||||
writer.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadCheats(File saveFile) {
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
in = new BufferedReader(new FileReader(saveFile));
|
||||
StringBuilder guts = new StringBuilder();
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
DynamicCheat cheat = DynamicCheat.deserialize(line);
|
||||
addCheat(cheat);
|
||||
}
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ public class MontezumasRevengeCheats extends Cheats {
|
|||
};
|
||||
|
||||
@Override
|
||||
void registerListeners() {
|
||||
public void registerListeners() {
|
||||
RAM memory = Emulator.computer.memory;
|
||||
if (repulsiveHack) {
|
||||
addCheat(RAMEvent.TYPE.WRITE, this::repulsiveBehavior, 0x1508, 0x1518);
|
||||
|
|
357
src/main/java/jace/cheat/WolfensteinCheats.java
Normal file
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* Copyright 2018 org.badvision.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package jace.cheat;
|
||||
|
||||
import jace.EmulatorUILogic;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAMEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
|
||||
/**
|
||||
* Cheats for the Wolfenstein series games
|
||||
*/
|
||||
public class WolfensteinCheats extends Cheats {
|
||||
|
||||
// Specific to Wolfenstein
|
||||
static final int KEYS = 0x04359;
|
||||
static final int GRENADES = 0x04348;
|
||||
// Object types
|
||||
static final int CHEST = 48;
|
||||
static final int SS = 32;
|
||||
|
||||
// Specific to Beyond Wolfenstein
|
||||
static final int MARKS = 0x0434b;
|
||||
static final int PASSES = 0x04360;
|
||||
static final int CLOSET_CONTENTS_CMP = 0x05FB9; // Only locks by type, so mess up the check
|
||||
// Object types
|
||||
static final int CLOSET = 32;
|
||||
static final int ALARM = 48;
|
||||
static final int SEATED_GUARD = 80;
|
||||
static final int BW_DOOR = 96;
|
||||
|
||||
// Same in both Wolfenstein and Beyond Wolfenstein
|
||||
static final int PLAYER_LOCATION = 0x04343;
|
||||
static final int BULLETS = 0x04347;
|
||||
// Object types
|
||||
static final int CORPSE = 64;
|
||||
static final int GUARD = 16;
|
||||
static final int DOOR = 80;
|
||||
static final int NOTHING = 0;
|
||||
|
||||
public WolfensteinCheats(Computer computer) {
|
||||
super(computer);
|
||||
}
|
||||
|
||||
private EventHandler<MouseEvent> mouseListener = this::processMouseEvent;
|
||||
@ConfigurableField(category = "Hack", name = "Beyond Wolfenstein", defaultValue = "false", description = "Make sure cheats work with Beyond Wolfenstein")
|
||||
public static boolean _isBeyondWolfenstein = false;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Mouse (1+2)", defaultValue = "false", description = "Left click kills/opens, Right click teleports")
|
||||
public static boolean mouseMode = true;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Ammo (1+2)", defaultValue = "false", description = "All the bullets and grenades you'll need")
|
||||
public static boolean ammo = true;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Rich (2)", defaultValue = "false", description = "All the money")
|
||||
public static boolean rich = true;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Uniform (1)", defaultValue = "false", description = "PUT SOME CLOTHES ON!")
|
||||
public static boolean uniform = true;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Vest (1)", defaultValue = "false", description = "Bulletproof vest")
|
||||
public static boolean vest = true;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Skeleton Key (1+2)", defaultValue = "false", description = "Open all things")
|
||||
public static boolean skeletonKey = true;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Fast Open (1)", defaultValue = "false", description = "Open all things quickly")
|
||||
public static boolean fastOpen = true;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "All dead (1+2)", defaultValue = "false", description = "Everything is dead")
|
||||
public static boolean allDead = true;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Sleepy Time (1+2)", defaultValue = "false", description = "Nobody move, nobody get hurt")
|
||||
public static boolean sleepyTime = false;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Legendary (1)", defaultValue = "false", description = "All of them are SS guards!")
|
||||
public static boolean legendary = false;
|
||||
|
||||
@ConfigurableField(category = "Hack", name = "Day at the office (2)", defaultValue = "false", description = "All of them are at desks")
|
||||
public static boolean dayAtTheOffice = false;
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
if (_isBeyondWolfenstein) {
|
||||
// Only work in Beyond Wolfenstein
|
||||
if (rich) {
|
||||
forceValue(MARKS, 255);
|
||||
}
|
||||
if (dayAtTheOffice) {
|
||||
for (int i = 0x04080; i < 0x04100; i += 0x010) {
|
||||
addCheat(RAMEvent.TYPE.READ, this::allDesks, i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only work in the first Wolfenstein game
|
||||
if (uniform) {
|
||||
forceValue(255, 0x04349);
|
||||
}
|
||||
if (vest) {
|
||||
forceValue(255, 0x0434A);
|
||||
}
|
||||
if (fastOpen) {
|
||||
addCheat(RAMEvent.TYPE.WRITE, this::fastOpenHandler, 0x04351);
|
||||
addCheat(RAMEvent.TYPE.WRITE, this::fastOpenHandler, 0x0587B);
|
||||
}
|
||||
}
|
||||
if (ammo) {
|
||||
forceValue(10, BULLETS);
|
||||
if (!_isBeyondWolfenstein) {
|
||||
forceValue(9, GRENADES);
|
||||
}
|
||||
}
|
||||
if (skeletonKey) {
|
||||
if (_isBeyondWolfenstein) {
|
||||
forceValue(255, PASSES);
|
||||
forceValue(64, CLOSET_CONTENTS_CMP); // Fake it out so it thinks all doors are unlocked
|
||||
} else {
|
||||
forceValue(255, KEYS);
|
||||
}
|
||||
}
|
||||
if (allDead) {
|
||||
for (int i = 0x04080; i < 0x04100; i += 0x010) {
|
||||
addCheat(RAMEvent.TYPE.READ, this::allDead, i);
|
||||
}
|
||||
}
|
||||
if (sleepyTime) {
|
||||
for (int i = 0x04080; i < 0x04100; i += 0x010) {
|
||||
forceValue(0, i + 2);
|
||||
forceValue(0, i + 3);
|
||||
// This makes them shout ACHTUNG over and over again... so don't do that.
|
||||
// forceValue(144, i+12);
|
||||
forceValue(0, i + 12);
|
||||
}
|
||||
}
|
||||
if (legendary) {
|
||||
for (int i = 0x04080; i < 0x04100; i += 0x010) {
|
||||
addCheat(RAMEvent.TYPE.READ, this::legendaryMode, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseMode) {
|
||||
EmulatorUILogic.addMouseListener(mouseListener);
|
||||
} else {
|
||||
EmulatorUILogic.removeMouseListener(mouseListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void fastOpenHandler(RAMEvent evt) {
|
||||
int newVal = evt.getNewValue() & 0x0ff;
|
||||
if (newVal > 1) {
|
||||
evt.setNewValue(1);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFinalRoom() {
|
||||
for (int i = 0x04080; i < 0x04100; i += 0x010) {
|
||||
int objectType = computer.getMemory().readRaw(i) & 0x0ff;
|
||||
if (objectType == BW_DOOR) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void allDesks(RAMEvent evt) {
|
||||
int location = computer.getMemory().readRaw(evt.getAddress() + 1);
|
||||
if (!isFinalRoom() || location < 32) {
|
||||
int type = evt.getNewValue();
|
||||
if (type == GUARD) {
|
||||
evt.setNewValue(SEATED_GUARD);
|
||||
// Reset the status flag to 0 to prevent the boss desk from rendering, but don't revive dead guards!
|
||||
if (computer.getMemory().readRaw(evt.getAddress() + 4) != 4) {
|
||||
computer.getMemory().write(evt.getAddress() + 4, (byte) 0, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void allDead(RAMEvent evt) {
|
||||
int type = evt.getNewValue();
|
||||
if (_isBeyondWolfenstein) {
|
||||
int location = computer.getMemory().readRaw(evt.getAddress() + 1);
|
||||
if (!isFinalRoom() || location < 32) {
|
||||
if (type == GUARD) {
|
||||
evt.setNewValue(CORPSE);
|
||||
} else if (type == SEATED_GUARD) {
|
||||
computer.getMemory().write(evt.getAddress() + 4, (byte) 4, false, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (type == GUARD || type == SS) {
|
||||
evt.setNewValue(CORPSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int debugTicks = 0;
|
||||
|
||||
private void legendaryMode(RAMEvent evt) {
|
||||
int type = evt.getNewValue();
|
||||
if (type == 16) {
|
||||
evt.setNewValue(32);
|
||||
}
|
||||
}
|
||||
|
||||
private void processMouseEvent(MouseEvent evt) {
|
||||
if (evt.isPrimaryButtonDown() || evt.isSecondaryButtonDown()) {
|
||||
Node source = (Node) evt.getSource();
|
||||
double mouseX = evt.getSceneX() / source.getBoundsInLocal().getWidth();
|
||||
double mouseY = evt.getSceneY() / source.getBoundsInLocal().getHeight();
|
||||
int x = Math.max(0, Math.min(7, (int) ((mouseX - 0.148) * 11)));
|
||||
int y = Math.max(0, Math.min(7, (int) ((mouseY - 0.101) * 11)));
|
||||
int location = x + (y << 3);
|
||||
if (evt.getButton() == MouseButton.PRIMARY) {
|
||||
killEnemyAt(location);
|
||||
} else {
|
||||
teleportTo(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void killEnemyAt(int location) {
|
||||
System.out.println("Looking for bad guy at " + location);
|
||||
for (int i = 0x04080; i < 0x04100; i += 0x010) {
|
||||
int enemyLocation = computer.getMemory().readRaw(i + 1) & 0x0ff;
|
||||
System.out.print("Location " + enemyLocation);
|
||||
String type = "";
|
||||
boolean isAlive = false;
|
||||
boolean isSeatedGuard = false;
|
||||
if (_isBeyondWolfenstein) {
|
||||
switch (computer.getMemory().readRaw(i) & 0x0ff) {
|
||||
case GUARD:
|
||||
type = "guard";
|
||||
isAlive = true;
|
||||
break;
|
||||
case SEATED_GUARD:
|
||||
type = "seated guard";
|
||||
isAlive = true;
|
||||
isSeatedGuard = true;
|
||||
break;
|
||||
case CLOSET:
|
||||
type = "closet";
|
||||
break;
|
||||
case CORPSE:
|
||||
type = "corpse";
|
||||
break;
|
||||
case NOTHING:
|
||||
type = "nothing";
|
||||
break;
|
||||
default:
|
||||
type = "unknown type " + (computer.getMemory().readRaw(i) & 0x0ff);
|
||||
}
|
||||
} else {
|
||||
switch (computer.getMemory().readRaw(i) & 0x0ff) {
|
||||
case GUARD:
|
||||
type = "guard";
|
||||
isAlive = true;
|
||||
break;
|
||||
case SS:
|
||||
type = "SS";
|
||||
isAlive = true;
|
||||
break;
|
||||
case CHEST:
|
||||
type = "chest";
|
||||
break;
|
||||
case CORPSE:
|
||||
type = "corpse";
|
||||
break;
|
||||
case DOOR:
|
||||
type = "door";
|
||||
break;
|
||||
case NOTHING:
|
||||
type = "nothing";
|
||||
break;
|
||||
default:
|
||||
type = "unknown type " + (computer.getMemory().readRaw(i) & 0x0ff);
|
||||
}
|
||||
}
|
||||
System.out.println(" is a " + type);
|
||||
for (int j = 0x00; j < 0x0f; j++) {
|
||||
int val = computer.getMemory().readRaw(i + j) & 0x0ff;
|
||||
System.out.print(Integer.toHexString(val) + " ");
|
||||
}
|
||||
System.out.println();
|
||||
|
||||
if (isAlive && location == enemyLocation) {
|
||||
if (isSeatedGuard) {
|
||||
computer.getMemory().write(i + 4, (byte) 4, false, false);
|
||||
} else {
|
||||
computer.getMemory().write(i, (byte) CORPSE, false, true);
|
||||
}
|
||||
|
||||
System.out.println("*BLAM*");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void teleportTo(int location) {
|
||||
computer.getMemory().write(0x04343, (byte) location, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners() {
|
||||
super.unregisterListeners();
|
||||
EmulatorUILogic.removeMouseListener(mouseListener);
|
||||
}
|
||||
public static int BlueType = 0x0b700;
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "Wolfenstein Cheats";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (debugTicks > 0) {
|
||||
debugTicks--;
|
||||
if (debugTicks == 0) {
|
||||
computer.getCpu().setTraceEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 4147-4247: Room map?
|
||||
*
|
||||
* 4080-40ff : Enemies and chests 4090-409f : Enemy 2 40a0-40af : Enemy 1 0: State/Type (0-15 = Nothing?, 16 =
|
||||
* soldier, 32 = SS, 48 = Chest, 64 = dead) 1: Location 2: Direction (0 = still) 3: Aim (0 = no gun) C: Caution?
|
||||
* (144 = stickup)
|
||||
*
|
||||
* 4341 : Player walking direction (0 = still, 1=D, 2=U, 4=L, 8=R) 4342 : Player gun direction 4343 : Real Player
|
||||
* location (4 high bits = vertical, 4 low bits = horizontal) .. use this for teleport 4344 : Player Drawing X
|
||||
* location 4345 : Player Drawing Y location 4347 : Bullets 4348 : Grenades 4349 : Uniform (0 = none, 1+ = yes) 434A
|
||||
* : Vest (0 = none, 1+ = yes) 434C : Wall collision animation timer 434D/E : Game timer (lo/high) -- no immediate
|
||||
* effect 4351 : Search / Use timer 4352 : 0 normally, 144/176 opening chest, 160 when searching body, 176 opening
|
||||
* door 4359 : Keys (8-bit flags, 255=skeleton key) 587B : Search timer
|
||||
*/
|
||||
}
|
|
@ -49,9 +49,9 @@ import java.util.Set;
|
|||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
|
||||
/**
|
||||
|
@ -189,16 +189,17 @@ public class Configuration implements Reconfigurable {
|
|||
}
|
||||
|
||||
public void setFieldValue(String field, Serializable value) {
|
||||
setChanged(true);
|
||||
if (value != null) {
|
||||
if (value.equals(getFieldValue(field))) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (getFieldValue(field) == null) {
|
||||
setChanged(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setChanged(true);
|
||||
setRawFieldValue(field, value);
|
||||
}
|
||||
|
||||
|
@ -255,6 +256,12 @@ public class Configuration implements Reconfigurable {
|
|||
getChangedIcon().ifPresent(this::setGraphic);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<ConfigNode> getTreeAsStream() {
|
||||
return Stream.concat(
|
||||
Stream.of(this),
|
||||
children.stream().flatMap(ConfigNode::getTreeAsStream));
|
||||
}
|
||||
}
|
||||
public static ConfigNode BASE;
|
||||
public static EmulatorUILogic ui = Emulator.logic;
|
||||
|
@ -301,7 +308,7 @@ public class Configuration implements Reconfigurable {
|
|||
node.setRawFieldValue(f.getName(), (Serializable) o);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (o == null) {
|
||||
continue;
|
||||
}
|
||||
|
@ -494,7 +501,9 @@ public class Configuration implements Reconfigurable {
|
|||
newRoot.getChildren().stream().forEach((child) -> {
|
||||
String childName = child.toString();
|
||||
ConfigNode oldChild = oldRoot.findChild(childName);
|
||||
if (oldChild == null) {oldChild = oldRoot.findChild(child.id);}
|
||||
if (oldChild == null) {
|
||||
oldChild = oldRoot.findChild(child.id);
|
||||
}
|
||||
// System.out.println("Applying settings for " + childName);
|
||||
applyConfigTree(child, oldChild);
|
||||
});
|
||||
|
|
|
@ -128,10 +128,18 @@ public abstract class Computer implements Reconfigurable {
|
|||
}
|
||||
|
||||
public void deactivate() {
|
||||
cpu.suspend();
|
||||
motherboard.suspend();
|
||||
video.suspend();
|
||||
mixer.detach();
|
||||
if (cpu != null) {
|
||||
cpu.suspend();
|
||||
}
|
||||
if (motherboard != null) {
|
||||
motherboard.suspend();
|
||||
}
|
||||
if (video != null) {
|
||||
video.suspend();
|
||||
}
|
||||
if (mixer != null) {
|
||||
mixer.detach();
|
||||
}
|
||||
}
|
||||
|
||||
@InvokableAction(
|
||||
|
@ -161,7 +169,7 @@ public abstract class Computer implements Reconfigurable {
|
|||
name = "Warm boot",
|
||||
description = "Process user-initatiated reboot (ctrl+apple+reset)",
|
||||
category = "general",
|
||||
alternatives = "reboot;reset;three-finger-salute",
|
||||
alternatives = "reboot;reset;three-finger-salute;restart",
|
||||
defaultKeyMapping = {"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"})
|
||||
public void invokeWarmStart() {
|
||||
warmStart();
|
||||
|
@ -185,7 +193,7 @@ public abstract class Computer implements Reconfigurable {
|
|||
return result;
|
||||
}
|
||||
|
||||
@InvokableAction(name = "Resume", description = "Resumes the computer if it was previously paused", alternatives = "unpause;unfreeze;resume", defaultKeyMapping = {"meta+shift+pause", "alt+shift+pause"})
|
||||
@InvokableAction(name = "Resume", description = "Resumes the computer if it was previously paused", alternatives = "unpause;unfreeze;resume;play", defaultKeyMapping = {"meta+shift+pause", "alt+shift+pause"})
|
||||
public void resume() {
|
||||
doResume();
|
||||
getRunningProperty().set(true);
|
||||
|
|
|
@ -20,6 +20,10 @@ package jace.core;
|
|||
|
||||
import jace.state.Stateful;
|
||||
import jace.config.Reconfigurable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
|
@ -39,9 +43,14 @@ import javafx.beans.property.SimpleBooleanProperty;
|
|||
@Stateful
|
||||
public abstract class Device implements Reconfigurable {
|
||||
protected Computer computer;
|
||||
private Device() {
|
||||
private List<Device> children;
|
||||
|
||||
private Device() {
|
||||
children = Collections.synchronizedList(new ArrayList<>());
|
||||
}
|
||||
|
||||
public Device(Computer computer) {
|
||||
this();
|
||||
this.computer = computer;
|
||||
}
|
||||
|
||||
|
@ -52,6 +61,31 @@ public abstract class Device implements Reconfigurable {
|
|||
private final BooleanProperty run = new SimpleBooleanProperty(true);
|
||||
@Stateful
|
||||
public boolean isPaused = false;
|
||||
@Stateful
|
||||
public boolean isAttached = false;
|
||||
|
||||
public void addChildDevice(Device d) {
|
||||
children.add(d);
|
||||
if (isAttached) {
|
||||
d.attach();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeChildDevice(Device d) {
|
||||
children.remove(d);
|
||||
d.suspend();
|
||||
if (isAttached) {
|
||||
d.detach();
|
||||
}
|
||||
}
|
||||
|
||||
public void addAllDevices(Collection<Device> devices) {
|
||||
devices.forEach(this::addChildDevice);
|
||||
}
|
||||
|
||||
public List<Device> getChildren() {
|
||||
return Collections.unmodifiableList(children);
|
||||
}
|
||||
|
||||
public BooleanProperty getRunningProperty() {
|
||||
return run;
|
||||
|
@ -86,6 +120,7 @@ public abstract class Device implements Reconfigurable {
|
|||
return;
|
||||
}
|
||||
// Implicit else...
|
||||
children.forEach(Device::doTick);
|
||||
tick();
|
||||
}
|
||||
|
||||
|
@ -113,17 +148,24 @@ public abstract class Device implements Reconfigurable {
|
|||
setRun(false);
|
||||
return true;
|
||||
}
|
||||
children.forEach(Device::suspend);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
setRun(true);
|
||||
waitCycles = 0;
|
||||
children.forEach(Device::resume);
|
||||
}
|
||||
|
||||
public abstract void attach();
|
||||
public void attach() {
|
||||
isAttached = true;
|
||||
children.forEach(Device::attach);
|
||||
}
|
||||
|
||||
public void detach() {
|
||||
Keyboard.unregisterAllHandlers(this);
|
||||
children.forEach(Device::suspend);
|
||||
children.forEach(Device::detach);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ public class Font {
|
|||
initialized = true;
|
||||
font = new int[256][8];
|
||||
Thread fontLoader = new Thread(() -> {
|
||||
InputStream in = ClassLoader.getSystemResourceAsStream("jace/data/font.png");
|
||||
InputStream in = Font.class.getClassLoader().getResourceAsStream("jace/data/font.png");
|
||||
Image image = new Image(in);
|
||||
PixelReader reader = image.getPixelReader();
|
||||
for (int i = 0; i < 256; i++) {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.SoftSwitches;
|
||||
import jace.config.InvokableAction;
|
||||
import jace.config.Reconfigurable;
|
||||
|
@ -68,6 +69,7 @@ public class Keyboard implements Reconfigurable {
|
|||
return "kbd";
|
||||
}
|
||||
static byte currentKey = 0;
|
||||
public boolean shiftPressed = false;
|
||||
|
||||
public static void clearStrobe() {
|
||||
currentKey = (byte) (currentKey & 0x07f);
|
||||
|
@ -102,6 +104,7 @@ public class Keyboard implements Reconfigurable {
|
|||
registerKeyHandler(new KeyHandler(code) {
|
||||
@Override
|
||||
public boolean handleKeyUp(KeyEvent e) {
|
||||
Emulator.computer.getKeyboard().shiftPressed = e.isShiftDown();
|
||||
if (action == null || !action.notifyOnRelease()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -125,6 +128,7 @@ public class Keyboard implements Reconfigurable {
|
|||
@Override
|
||||
public boolean handleKeyDown(KeyEvent e) {
|
||||
// System.out.println("Key down: "+method.toString());
|
||||
Emulator.computer.getKeyboard().shiftPressed = e.isShiftDown();
|
||||
Object returnValue = null;
|
||||
try {
|
||||
if (method.getParameterCount() > 0) {
|
||||
|
@ -248,6 +252,7 @@ public class Keyboard implements Reconfigurable {
|
|||
default:
|
||||
}
|
||||
|
||||
Emulator.computer.getKeyboard().shiftPressed = e.isShiftDown();
|
||||
if (e.isShiftDown()) {
|
||||
c = fixShiftedChar(c);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import jace.apple2e.SoftSwitches;
|
|||
import jace.apple2e.Speaker;
|
||||
import jace.config.ConfigurableField;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
@ -39,8 +38,7 @@ import java.util.logging.Logger;
|
|||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Motherboard extends TimedDevice {
|
||||
|
||||
final public Set<Device> miscDevices = new LinkedHashSet<>();
|
||||
|
||||
@ConfigurableField(name = "Enable Speaker", shortName = "speaker", defaultValue = "true")
|
||||
public static boolean enableSpeaker = true;
|
||||
public Speaker speaker;
|
||||
|
@ -63,8 +61,11 @@ public class Motherboard extends TimedDevice {
|
|||
public Motherboard(Computer computer, Motherboard oldMotherboard) {
|
||||
super(computer);
|
||||
if (oldMotherboard != null) {
|
||||
miscDevices.addAll(oldMotherboard.miscDevices);
|
||||
addAllDevices(oldMotherboard.getChildren());
|
||||
speaker = oldMotherboard.speaker;
|
||||
accelorationRequestors.addAll(oldMotherboard.accelorationRequestors);
|
||||
setSpeedInHz(oldMotherboard.getSpeedInHz());
|
||||
setMaxSpeed(oldMotherboard.isMaxSpeed());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,11 +94,8 @@ public class Motherboard extends TimedDevice {
|
|||
clockCounter = cpuPerClock;
|
||||
computer.getVideo().doTick();
|
||||
for (Optional<Card> card : cards) {
|
||||
card.ifPresent(c -> c.doTick());
|
||||
card.ifPresent(Card::doTick);
|
||||
}
|
||||
miscDevices.stream().forEach((m) -> {
|
||||
m.doTick();
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, t);
|
||||
}
|
||||
|
@ -125,7 +123,7 @@ public class Motherboard extends TimedDevice {
|
|||
speaker = new Speaker(computer);
|
||||
if (computer.mixer.lineAvailable) {
|
||||
speaker.attach();
|
||||
miscDevices.add(speaker);
|
||||
addChildDevice(speaker);
|
||||
} else {
|
||||
System.out.print("No lines available! Speaker not running.");
|
||||
}
|
||||
|
@ -133,21 +131,19 @@ public class Motherboard extends TimedDevice {
|
|||
speaker.reconfigure();
|
||||
} catch (Throwable t) {
|
||||
System.out.println("Unable to initalize sound -- deactivating speaker out");
|
||||
speaker.detach();
|
||||
miscDevices.remove(speaker);
|
||||
removeChildDevice(speaker);
|
||||
}
|
||||
} else {
|
||||
System.out.println("Speaker not enabled, leaving it off.");
|
||||
if (speaker != null) {
|
||||
speaker.detach();
|
||||
miscDevices.remove(speaker);
|
||||
removeChildDevice(speaker);
|
||||
}
|
||||
}
|
||||
if (startAgain && computer.getMemory() != null) {
|
||||
resume();
|
||||
}
|
||||
}
|
||||
static HashSet<Object> accelorationRequestors = new HashSet<>();
|
||||
HashSet<Object> accelorationRequestors = new HashSet<>();
|
||||
|
||||
public void requestSpeed(Object requester) {
|
||||
accelorationRequestors.add(requester);
|
||||
|
@ -198,11 +194,6 @@ public class Motherboard extends TimedDevice {
|
|||
@Override
|
||||
public void detach() {
|
||||
System.out.println("Detaching motherboard");
|
||||
miscDevices.stream().forEach((d) -> {
|
||||
d.suspend();
|
||||
d.detach();
|
||||
});
|
||||
miscDevices.clear();
|
||||
// halt();
|
||||
super.detach();
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ package jace.core;
|
|||
import jace.config.ConfigurableField;
|
||||
import jace.config.DynamicSelection;
|
||||
import jace.config.Reconfigurable;
|
||||
import jace.library.StringUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
|
|
@ -21,23 +21,25 @@ 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.
|
||||
* 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
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class TimedDevice extends Device {
|
||||
|
||||
/**
|
||||
* Creates a new instance of TimedDevice
|
||||
*
|
||||
* @param computer
|
||||
*/
|
||||
public TimedDevice(Computer computer) {
|
||||
super(computer);
|
||||
setSpeed(cyclesPerSecond);
|
||||
setSpeedInHz(cyclesPerSecond);
|
||||
}
|
||||
@ConfigurableField(name = "Speed", description = "(in hertz)")
|
||||
public long cyclesPerSecond = defaultCyclesPerSecond();
|
||||
@ConfigurableField(name = "Speed", description = "(Percentage)")
|
||||
public int speedRatio = 100;
|
||||
private long cyclesPerSecond = defaultCyclesPerSecond();
|
||||
@ConfigurableField(name = "Max speed")
|
||||
public boolean maxspeed = false;
|
||||
|
||||
|
@ -56,10 +58,11 @@ public abstract class TimedDevice extends Device {
|
|||
public boolean suspend() {
|
||||
disableTempMaxSpeed();
|
||||
boolean result = super.suspend();
|
||||
if (worker != null && worker.isAlive()) {
|
||||
Thread w = worker;
|
||||
if (w != null && w.isAlive()) {
|
||||
try {
|
||||
worker.interrupt();
|
||||
worker.join(1000);
|
||||
w.interrupt();
|
||||
w.join(1000);
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
|
@ -113,13 +116,44 @@ public abstract class TimedDevice extends Device {
|
|||
long cyclesPerInterval; // How many cycles to wait until a pause interval
|
||||
long nextSync; // When was the last pause?
|
||||
|
||||
public final void setSpeed(long cyclesPerSecond) {
|
||||
public final int getSpeedRatio() {
|
||||
return speedRatio;
|
||||
}
|
||||
|
||||
public final void setMaxSpeed(boolean enabled) {
|
||||
maxspeed = enabled;
|
||||
if (!enabled) {
|
||||
disableTempMaxSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean isMaxSpeed() {
|
||||
return maxspeed;
|
||||
}
|
||||
|
||||
public final long getSpeedInHz() {
|
||||
return cyclesPerInterval * 100L;
|
||||
}
|
||||
|
||||
public final void setSpeedInHz(long cyclesPerSecond) {
|
||||
// System.out.println("Raw set speed for " + getName() + " to " + cyclesPerSecond + "hz");
|
||||
speedRatio = (int) Math.round(cyclesPerSecond * 100.0 / defaultCyclesPerSecond());
|
||||
cyclesPerInterval = cyclesPerSecond / 100L;
|
||||
nanosPerInterval = (long) (cyclesPerInterval * NANOS_PER_SECOND / cyclesPerSecond);
|
||||
// System.out.println("Will pause " + nanosPerInterval + " nanos every " + cyclesPerInterval + " cycles");
|
||||
cycleTimer = 0;
|
||||
resetSyncTimer();
|
||||
}
|
||||
|
||||
public final void setSpeedInPercentage(int ratio) {
|
||||
// System.out.println("Setting " + getName() + " speed ratio to " + speedRatio);
|
||||
cyclesPerSecond = defaultCyclesPerSecond() * ratio / 100;
|
||||
if (cyclesPerSecond == 0) {
|
||||
cyclesPerSecond = defaultCyclesPerSecond();
|
||||
}
|
||||
setSpeedInHz(cyclesPerSecond);
|
||||
}
|
||||
|
||||
long skip = 0;
|
||||
long wait = 0;
|
||||
|
||||
|
@ -170,10 +204,6 @@ public abstract class TimedDevice extends Device {
|
|||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
if (cyclesPerSecond == 0) {
|
||||
cyclesPerSecond = defaultCyclesPerSecond();
|
||||
}
|
||||
setSpeed(cyclesPerSecond);
|
||||
}
|
||||
|
||||
public abstract long defaultCyclesPerSecond();
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
*/
|
||||
package jace.core;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.config.Configuration;
|
||||
import jace.config.InvokableAction;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import org.reflections.Reflections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -39,12 +41,15 @@ import java.util.stream.Collectors;
|
|||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
/**
|
||||
* This is a set of helper functions which do not belong anywhere else.
|
||||
* Functions vary from introspection, discovery, and string/pattern matching.
|
||||
|
@ -52,7 +57,9 @@ import javafx.scene.paint.Color;
|
|||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public class Utility {
|
||||
|
||||
static Reflections reflections = new Reflections("jace");
|
||||
|
||||
public static Set<Class> findAllSubclasses(Class clazz) {
|
||||
return reflections.getSubTypesOf(clazz);
|
||||
}
|
||||
|
@ -65,11 +72,11 @@ public class Utility {
|
|||
*
|
||||
* @param s
|
||||
* @param t
|
||||
* @return Distance (higher is better)
|
||||
* @return Distance (lower means a closer match, zero is identical)
|
||||
*/
|
||||
public static int levenshteinDistance(String s, String t) {
|
||||
if (s == null || t == null || s.length() == 0 || t.length() == 0) {
|
||||
return -1;
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
s = s.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "");
|
||||
|
@ -95,9 +102,20 @@ public class Utility {
|
|||
}
|
||||
}
|
||||
}
|
||||
return Math.max(m, n) - dist[m][n];
|
||||
return dist[m][n];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize distance based on longest string
|
||||
* @param s
|
||||
* @param t
|
||||
* @return Similarity ranking, higher is better
|
||||
*/
|
||||
public static int adjustedLevenshteinDistance(String s, String t) {
|
||||
return Math.max(s.length(), t.length()) - levenshteinDistance(s, t);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare strings based on a tally of similar patterns found, using a fixed
|
||||
* search window The resulting score is heavily penalized if the strings
|
||||
|
@ -107,7 +125,7 @@ public class Utility {
|
|||
* @param c1
|
||||
* @param c2
|
||||
* @param width Search window size
|
||||
* @return Overall similarity score (higher is beter)
|
||||
* @return Overall similarity score (higher is better)
|
||||
*/
|
||||
public static double rankMatch(String c1, String c2, int width) {
|
||||
double score = 0;
|
||||
|
@ -135,14 +153,18 @@ public class Utility {
|
|||
}
|
||||
|
||||
private static boolean isHeadless = false;
|
||||
|
||||
public static void setHeadlessMode(boolean headless) {
|
||||
isHeadless = headless;
|
||||
if (Emulator.instance == null && headless) {
|
||||
Emulator.instance = new Emulator(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isHeadlessMode() {
|
||||
return isHeadless;
|
||||
}
|
||||
|
||||
|
||||
public static Optional<Image> loadIcon(String filename) {
|
||||
if (isHeadless) {
|
||||
return Optional.empty();
|
||||
|
@ -181,6 +203,20 @@ public class Utility {
|
|||
return Optional.of(label);
|
||||
}
|
||||
|
||||
public static void confirm(String title, String message, Runnable accept) {
|
||||
Platform.runLater(() -> {
|
||||
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
confirm.setContentText(message);
|
||||
confirm.setTitle(title);
|
||||
Optional<ButtonType> response = confirm.showAndWait();
|
||||
response.ifPresent(b -> {
|
||||
if (b.getButtonData().isDefaultButton()) {
|
||||
(new Thread(accept)).start();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// public static void runModalProcess(String title, final Runnable runnable) {
|
||||
//// final JDialog frame = new JDialog(Emulator.getFrame());
|
||||
// final JProgressBar progressBar = new JProgressBar();
|
||||
|
@ -201,7 +237,6 @@ public class Utility {
|
|||
// frame.dispose();
|
||||
// }).start();
|
||||
// }
|
||||
|
||||
public static class RankingComparator implements Comparator<String> {
|
||||
|
||||
String match;
|
||||
|
@ -215,8 +250,8 @@ public class Utility {
|
|||
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
double s1 = levenshteinDistance(match, o1);
|
||||
double s2 = levenshteinDistance(match, o2);
|
||||
double s1 = adjustedLevenshteinDistance(match, o1);
|
||||
double s2 = adjustedLevenshteinDistance(match, o2);
|
||||
if (s2 == s1) {
|
||||
s1 = rankMatch(o1, match, 3) + rankMatch(o1, match, 2);
|
||||
s2 = rankMatch(o2, match, 3) + rankMatch(o2, match, 2);
|
||||
|
@ -256,7 +291,7 @@ public class Utility {
|
|||
// 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));
|
||||
double score = adjustedLevenshteinDistance(match, candidates.get(0));
|
||||
if (score > 1) {
|
||||
return candidates.get(0);
|
||||
}
|
||||
|
@ -450,4 +485,47 @@ public class Utility {
|
|||
}
|
||||
return setChild(object, paths[paths.length - 1], value, hex);
|
||||
}
|
||||
|
||||
static Map<InvokableAction, Runnable> allActions = null;
|
||||
|
||||
public static Map<InvokableAction, Runnable> getAllInvokableActions() {
|
||||
if (allActions == null) {
|
||||
allActions = new HashMap<>();
|
||||
Configuration.BASE.getTreeAsStream().forEach((Configuration.ConfigNode node) -> {
|
||||
for (Method m : node.subject.getClass().getMethods()) {
|
||||
if (m.isAnnotationPresent(InvokableAction.class)) {
|
||||
allActions.put(m.getAnnotation(InvokableAction.class), () -> {
|
||||
try {
|
||||
m.invoke(node.subject);
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
Logger.getLogger(Utility.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return allActions;
|
||||
}
|
||||
|
||||
public static Runnable getNamedInvokableAction(String action) {
|
||||
Map<InvokableAction, Runnable> actions = getAllInvokableActions();
|
||||
List<InvokableAction> actionsList = new ArrayList(actions.keySet());
|
||||
actionsList.sort((a,b) -> Integer.compare(getActionNameMatch(action, a), getActionNameMatch(action, b)));
|
||||
// for (InvokableAction a : actionsList) {
|
||||
// String actionName = a.alternatives() == null ? a.name() : (a.name() + ";" + a.alternatives());
|
||||
// System.out.println("Score for " + action + " evaluating " + a.name() + ": " + getActionNameMatch(action, a));
|
||||
// }
|
||||
return actions.get(actionsList.get(0));
|
||||
}
|
||||
|
||||
private static int getActionNameMatch(String str, InvokableAction action) {
|
||||
int nameMatch = levenshteinDistance(str, action.name());
|
||||
if (action.alternatives() != null) {
|
||||
for (String alt : action.alternatives().split(";")) {
|
||||
nameMatch = Math.min(nameMatch, levenshteinDistance(str, alt));
|
||||
}
|
||||
}
|
||||
return nameMatch;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,13 +132,15 @@ public abstract class Video extends Device {
|
|||
public static int MIN_SCREEN_REFRESH = 15;
|
||||
|
||||
Runnable redrawScreen = () -> {
|
||||
if (computer.getRunningProperty().get()) {
|
||||
visible.getPixelWriter().setPixels(0, 0, 560, 192, video.getPixelReader(), 0, 0);
|
||||
if (visible != null && video != null) {
|
||||
// if (computer.getRunningProperty().get()) {
|
||||
screenDirty = false;
|
||||
visible.getPixelWriter().setPixels(0, 0, 560, 192, video.getPixelReader(), 0, 0);
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
public void redraw() {
|
||||
screenDirty = false;
|
||||
javafx.application.Platform.runLater(redrawScreen);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ 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;
|
||||
|
@ -80,7 +79,7 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
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";
|
||||
|
@ -90,7 +89,7 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
super(computer);
|
||||
controllers = new R6522[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
//don't ask...
|
||||
// has to be final to be used inside of anonymous class below
|
||||
final int j = i;
|
||||
controllers[i] = new R6522(computer) {
|
||||
int controller = j;
|
||||
|
@ -131,6 +130,13 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
public String getShortName() {
|
||||
return "timer" + j;
|
||||
}
|
||||
|
||||
public void doTick() {
|
||||
super.doTick();
|
||||
if (controller == 0) {
|
||||
doSoundTick();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +146,18 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
suspend();
|
||||
}
|
||||
RAMListener mainListener = null;
|
||||
|
||||
boolean heatbeatUnclocked = false;
|
||||
long heartbeatReclockTime = 0L;
|
||||
long unclockTime = 5000L;
|
||||
|
||||
private void setUnclocked(boolean unclocked) {
|
||||
heatbeatUnclocked = unclocked;
|
||||
for (R6522 controller : controllers) {
|
||||
controller.setUnclocked(unclocked);
|
||||
}
|
||||
heartbeatReclockTime = System.currentTimeMillis() + unclockTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) {
|
||||
|
@ -152,7 +170,7 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
chip++;
|
||||
}
|
||||
if (chip >= 2) {
|
||||
System.err.println("Could not determine which PSG to communicate to");
|
||||
System.err.println("Could not determine which PSG to communicate to for access to regsiter + " + Integer.toHexString(register));
|
||||
e.setNewValue(computer.getVideo().getFloatingBus());
|
||||
return;
|
||||
}
|
||||
|
@ -177,13 +195,25 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
|
||||
@Override
|
||||
public void tick() {
|
||||
for (R6522 c : controllers) {
|
||||
if (c == null || !c.isRunning()) {
|
||||
continue;
|
||||
if (heatbeatUnclocked) {
|
||||
if (System.currentTimeMillis() - heartbeatReclockTime >= unclockTime) {
|
||||
setUnclocked(false);
|
||||
} else {
|
||||
for (R6522 c : controllers) {
|
||||
if (c == null || !c.isRunning()) {
|
||||
continue;
|
||||
}
|
||||
c.doTick();
|
||||
}
|
||||
}
|
||||
c.tick();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return super.isRunning() && playbackThread != null && playbackThread.isAlive();
|
||||
}
|
||||
|
||||
private void doSoundTick() {
|
||||
if (isRunning() && !pause) {
|
||||
// buildMixerTable();
|
||||
timerSync.lock();
|
||||
|
@ -194,7 +224,7 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
while (isRunning() && ticksSinceLastPlayback >= ticksBetweenPlayback) {
|
||||
if (!playbackFinished.await(1, TimeUnit.SECONDS)) {
|
||||
// gripe("The mockingboard playback thread has stalled. Disabling mockingboard.");
|
||||
suspend();
|
||||
suspendSound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -263,14 +293,15 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
@Override
|
||||
public void resume() {
|
||||
pause = false;
|
||||
if (!isRunning()) {
|
||||
if (chips == null) {
|
||||
initPSG();
|
||||
for (PSG psg : chips) {
|
||||
psg.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SAMPLE_RATE);
|
||||
psg.reset();
|
||||
}
|
||||
if (chips == null) {
|
||||
initPSG();
|
||||
for (PSG psg : chips) {
|
||||
psg.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SAMPLE_RATE);
|
||||
psg.reset();
|
||||
}
|
||||
}
|
||||
if (!isRunning()) {
|
||||
setUnclocked(true);
|
||||
for (R6522 controller : controllers) {
|
||||
controller.attach();
|
||||
controller.resume();
|
||||
|
@ -290,6 +321,10 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
controller.suspend();
|
||||
controller.detach();
|
||||
}
|
||||
return suspendSound();
|
||||
}
|
||||
|
||||
public boolean suspendSound() {
|
||||
if (playbackThread == null || !playbackThread.isAlive()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -325,11 +360,11 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
int zeroSamples = 0;
|
||||
setRun(true);
|
||||
LockSupport.parkNanos(5000);
|
||||
while (isRunning()) {
|
||||
while (isRunning() && !Thread.interrupted()) {
|
||||
while (isRunning() && !computer.isRunning()) {
|
||||
Thread.currentThread().yield();
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
if (isRunning()) {
|
||||
if (isRunning() && !Thread.interrupted()) {
|
||||
playSound(leftBuffer, rightBuffer);
|
||||
int p = 0;
|
||||
for (int idx = 0; idx < BUFFER_LENGTH; idx++) {
|
||||
|
@ -394,6 +429,8 @@ public class CardMockingboard extends Card implements Runnable {
|
|||
} catch (LineUnavailableException ex) {
|
||||
Logger.getLogger(CardMockingboard.class
|
||||
.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(CardMockingboard.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} finally {
|
||||
computer.getMotherboard().cancelSpeedRequest(this);
|
||||
System.out.println("Mockingboard playback stopped");
|
||||
|
|
227
src/main/java/jace/hardware/ZipWarpAccelerator.java
Normal file
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Copyright 2018 org.badvision.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package jace.hardware;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.config.ConfigurableField;
|
||||
import jace.core.Computer;
|
||||
import jace.core.Device;
|
||||
import jace.core.Motherboard;
|
||||
import jace.core.RAMEvent;
|
||||
import jace.core.RAMListener;
|
||||
|
||||
/**
|
||||
* Implements a basic hardware accelerator that is able to adjust the speed of the emulator
|
||||
*/
|
||||
public class ZipWarpAccelerator extends Device {
|
||||
@ConfigurableField(category = "debug", name = "Debug messages")
|
||||
public boolean debugMessagesEnabled = false;
|
||||
|
||||
public static final int ENABLE_ADDR = 0x0c05a;
|
||||
public static final int MAX_SPEED = 0x0c05b;
|
||||
public static final int REGISTERS = 0x0c05c;
|
||||
public static final int SET_SPEED = 0x0c05d;
|
||||
public static final int UNLOCK_VAL = 0x05A;
|
||||
public static final int LOCK_VAL = 0x0A5;
|
||||
public static final double UNLOCK_PENALTY_PER_TICK = 0.19;
|
||||
public static final double UNLOCK_MIN = 4.0;
|
||||
|
||||
public static final int TRANSWARP = 0x0c074;
|
||||
public static final int TRANSWARP_ON = 1; // Any other value written disables acceleration
|
||||
|
||||
boolean zipLocked = true;
|
||||
double zipUnlockCount = 0;
|
||||
int zipRegisters = 0;
|
||||
int speedValue = 0;
|
||||
|
||||
RAMListener zipListener;
|
||||
RAMListener transwarpListener;
|
||||
|
||||
public ZipWarpAccelerator(Computer computer) {
|
||||
super(computer);
|
||||
zipListener = computer.memory.observe(RAMEvent.TYPE.ANY, ENABLE_ADDR, SET_SPEED, this::handleZipChipEvent);
|
||||
transwarpListener = computer.memory.observe(RAMEvent.TYPE.ANY, TRANSWARP, this::handleTranswarpEvent);
|
||||
}
|
||||
|
||||
private void handleZipChipEvent(RAMEvent e) {
|
||||
boolean isWrite = e.getType() == RAMEvent.TYPE.WRITE;
|
||||
if (ENABLE_ADDR == e.getAddress() && isWrite) {
|
||||
if (e.getNewValue() == UNLOCK_VAL) {
|
||||
zipUnlockCount = Math.ceil(zipUnlockCount) + 1.0;
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Unlock sequence detected, new lock value at " + zipUnlockCount + " of " + UNLOCK_MIN);
|
||||
}
|
||||
if (zipUnlockCount >= UNLOCK_MIN) {
|
||||
zipLocked = false;
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Zip unlocked!");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
zipLocked = true;
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Zip locked!");
|
||||
}
|
||||
zipUnlockCount = 0;
|
||||
if ((e.getNewValue() & 0x0ff) != LOCK_VAL) {
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Warp disabled.");
|
||||
}
|
||||
turnOffAcceleration();
|
||||
}
|
||||
}
|
||||
} else if (!zipLocked && isWrite) {
|
||||
switch (e.getAddress()) {
|
||||
case MAX_SPEED:
|
||||
setSpeed(SPEED.MAX);
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("MAXIMUM WARP!");
|
||||
}
|
||||
break;
|
||||
case SET_SPEED:
|
||||
SPEED s = lookupSpeedSetting(e.getNewValue());
|
||||
setSpeed(s);
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Set speed to " + s.ratio);
|
||||
}
|
||||
break;
|
||||
case REGISTERS:
|
||||
zipRegisters = e.getNewValue();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (!zipLocked && e.getAddress() == REGISTERS) {
|
||||
e.setNewValue(zipRegisters);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTranswarpEvent(RAMEvent e) {
|
||||
if (e.getType().isRead()) {
|
||||
e.setNewValue(speedValue);
|
||||
} else {
|
||||
if (e.getNewValue() == TRANSWARP_ON) {
|
||||
setSpeed(SPEED.MAX);
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("MAXIMUM WARP!");
|
||||
}
|
||||
} else {
|
||||
turnOffAcceleration();
|
||||
if (debugMessagesEnabled) {
|
||||
System.out.println("Warp disabled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "ZipChip Accelerator";
|
||||
}
|
||||
|
||||
public static enum SPEED {
|
||||
MAX(4.0, 0b000000000, 0b011111100),
|
||||
_2_667(2.6667, 0b000000100, 0b011111100),
|
||||
_3(3.0, 0b000001000, 0b011111000),
|
||||
_3_2(3.2, 0b000010000, 0b011110000),
|
||||
_3_333(3.333, 0b000100000, 0b011100000),
|
||||
_2(2.0, 0b001000000, 0b011111100),
|
||||
_1_333(1.333, 0b001000100, 0b011111100),
|
||||
_1_5(1.5, 0b001001000, 0b011111000),
|
||||
_1_6(1.6, 0b001010000, 0b011110000),
|
||||
_1_667(1.6667, 0b001100000, 0b011100000),
|
||||
_1b(1.0, 0b010000000, 0b011111100),
|
||||
_0_667(0.6667, 0b010000100, 0b011111100),
|
||||
_0_75(0.75, 0b010001000, 0b011111000),
|
||||
_0_8(0.8, 0b010010000, 0b011110000),
|
||||
_0_833(0.833, 0b010100000, 0b011100000),
|
||||
_1_33b(1.333, 0b011000000, 0b011111100),
|
||||
_0_889(0.8889, 0b011000100, 0b011111100),
|
||||
_1(1.0, 0b011001000, 0b011111000),
|
||||
_1_067(1.0667, 0b011010000, 0b011110000),
|
||||
_1_111(1.111, 0b011100000, 0b011100000);
|
||||
double ratio;
|
||||
int val;
|
||||
int mask;
|
||||
boolean max;
|
||||
|
||||
SPEED(double speed, int val, int mask) {
|
||||
this.ratio = speed;
|
||||
this.val = val;
|
||||
this.mask = mask;
|
||||
this.max = speed >= 4.0;
|
||||
}
|
||||
}
|
||||
|
||||
private SPEED lookupSpeedSetting(int v) {
|
||||
for (SPEED s : SPEED.values()) {
|
||||
if ((v & s.mask) == s.val) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return SPEED._1;
|
||||
}
|
||||
|
||||
private void setSpeed(SPEED speed) {
|
||||
speedValue = speed.val;
|
||||
if (speed.max) {
|
||||
Emulator.computer.getMotherboard().setMaxSpeed(true);
|
||||
Motherboard.cpuPerClock = 3;
|
||||
} else {
|
||||
Emulator.computer.getMotherboard().setMaxSpeed(false);
|
||||
Emulator.computer.getMotherboard().setSpeedInPercentage((int) (speed.ratio * 100));
|
||||
Motherboard.cpuPerClock = 1;
|
||||
}
|
||||
Emulator.computer.getMotherboard().reconfigure();
|
||||
}
|
||||
|
||||
private void turnOffAcceleration() {
|
||||
// The UI Logic retains the user's desired normal speed, reset to that
|
||||
Emulator.logic.reconfigure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (zipUnlockCount > 0) {
|
||||
zipUnlockCount -= UNLOCK_PENALTY_PER_TICK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
computer.memory.addListener(zipListener);
|
||||
computer.memory.addListener(transwarpListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
super.detach();
|
||||
computer.memory.removeListener(zipListener);
|
||||
computer.memory.removeListener(transwarpListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortName() {
|
||||
return "zip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
zipUnlockCount = 0;
|
||||
zipLocked = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,638 +0,0 @@
|
|||
/*
|
||||
* 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.mockingboard;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jace.hardware.CardMockingboard;
|
||||
|
||||
// Port of AY code from AppleWin -- not used buy kept for reference.
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
* ay8910.c
|
||||
*
|
||||
*
|
||||
* Emulation of the AY-3-8910 / YM2149 sound chip.
|
||||
*
|
||||
* Based on various code snippets by Ville Hallik, Michael Cuddy,
|
||||
* Tatsuyuki Satoh, Fabrice Frances, Nicola Salmoria.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
//
|
||||
// From mame.txt (http://www.mame.net/readme.html)
|
||||
//
|
||||
// VI. Reuse of Source Code
|
||||
// --------------------------
|
||||
// This chapter might not apply to specific portions of MAME (e.g. CPU
|
||||
// emulators) which bear different copyright notices.
|
||||
// The source code cannot be used in a commercial product without the written
|
||||
// authorization of the authors. Use in non-commercial products is allowed, and
|
||||
// indeed encouraged. If you use portions of the MAME source code in your
|
||||
// program, however, you must make the full source code freely available as
|
||||
// well.
|
||||
// Usage of the _information_ contained in the source code is free for any use.
|
||||
// However, given the amount of time and energy it took to collect this
|
||||
// information, if you find new information we would appreciate if you made it
|
||||
// freely available as well.
|
||||
//
|
||||
|
||||
public class AY8910_old {
|
||||
static final int MAX_OUTPUT = 0x007fff;
|
||||
static final int MAX_AY8910 = 2;
|
||||
static final int CLOCK = 1789770;
|
||||
static final int SAMPLE_RATE = 44100;
|
||||
|
||||
// See AY8910_set_clock() for definition of STEP
|
||||
static final int STEP = 0x008000;
|
||||
static int num = 0, ym_num = 0;
|
||||
int SampleRate = 0;
|
||||
|
||||
/* register id's */
|
||||
public enum Reg {
|
||||
AFine(0, 255),
|
||||
ACoarse(1, 15),
|
||||
BFine(2, 255),
|
||||
BCoarse(3, 15),
|
||||
CFine(4, 255),
|
||||
CCoarse(5, 15),
|
||||
NoisePeriod(6, 31),
|
||||
Enable(7, 255),
|
||||
AVol(8, 31),
|
||||
BVol(9, 31),
|
||||
CVol(10, 31),
|
||||
EnvFine(11, 255),
|
||||
EnvCoarse(12, 255),
|
||||
EnvShape(13, 15),
|
||||
PortA(14, 255),
|
||||
PortB(15, 255);
|
||||
|
||||
public final int registerNumber;
|
||||
public final int max;
|
||||
Reg(int number, int maxValue) {
|
||||
registerNumber = number;
|
||||
max=maxValue;
|
||||
}
|
||||
static Reg get(int number) {
|
||||
for (Reg r:Reg.values())
|
||||
if (r.registerNumber == number) return r;
|
||||
return null;
|
||||
}
|
||||
static public Reg[] preferredOrder = new Reg[]{
|
||||
Enable,EnvShape,EnvCoarse,EnvFine,NoisePeriod,AVol,BVol,CVol,
|
||||
AFine,ACoarse,BFine,BCoarse,CFine,CCoarse};
|
||||
}
|
||||
|
||||
public AY8910_old() {
|
||||
chips = new ArrayList<PSG>();
|
||||
for (int i=0; i < MAX_AY8910; i++) {
|
||||
PSG chip = new PSG();
|
||||
chips.add(chip);
|
||||
}
|
||||
initAll(CLOCK, SAMPLE_RATE);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
private List<PSG> chips;
|
||||
|
||||
static int[] VolTable;
|
||||
static {
|
||||
buildMixerTable();
|
||||
}
|
||||
|
||||
private class PSG {
|
||||
int Channel;
|
||||
int register_latch;
|
||||
Map<Reg, Integer> registers;
|
||||
|
||||
int lastEnable;
|
||||
int UpdateStep;
|
||||
int PeriodA,PeriodB,PeriodC,PeriodN,PeriodE;
|
||||
int CountA,CountB,CountC,CountN,CountE;
|
||||
int VolA,VolB,VolC,VolE;
|
||||
int EnvelopeA,EnvelopeB,EnvelopeC;
|
||||
int OutputA,OutputB,OutputC,OutputN;
|
||||
int CountEnv;
|
||||
int Hold,Alternate,Attack,Holding;
|
||||
int RNG;
|
||||
|
||||
public PSG() {
|
||||
registers = new HashMap<Reg, Integer>();
|
||||
for (Reg r: Reg.values())
|
||||
setReg(r, 0);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
register_latch = 0;
|
||||
RNG = 1;
|
||||
OutputA = 0;
|
||||
OutputB = 0;
|
||||
OutputC = 0;
|
||||
OutputN = 0x00ff;
|
||||
lastEnable = -1; /* force a write */
|
||||
for (Reg r: Reg.values())
|
||||
writeReg(r, 0);
|
||||
}
|
||||
|
||||
public void setClock(int clock) {
|
||||
/* the step clock for the tone and noise generators is the chip clock */
|
||||
/* divided by 8; for the envelope generator of the AY-3-8910, it is half */
|
||||
/* that much (clock/16), but the envelope of the YM2149 goes twice as */
|
||||
/* fast, therefore again clock/8. */
|
||||
/* Here we calculate the number of steps which happen during one sample */
|
||||
/* at the given sample rate. No. of events = sample rate / (clock/8). */
|
||||
/* STEP is a multiplier used to turn the fraction into a fixed point */
|
||||
/* number. */
|
||||
double clk = clock;
|
||||
double smprate = SampleRate;
|
||||
UpdateStep = (int) ((STEP * smprate * 8.0 + clk/2.0) / clk);
|
||||
}
|
||||
|
||||
public void setReg(Reg r, int value) {
|
||||
registers.put(r,value);
|
||||
}
|
||||
public int getReg(Reg r) {
|
||||
return registers.get(r);
|
||||
}
|
||||
|
||||
public void writeReg(Reg r, int value) {
|
||||
value &= r.max;
|
||||
setReg(r, value);
|
||||
int old;
|
||||
/* A note about the period of tones, noise and envelope: for speed reasons,*/
|
||||
/* we count down from the period to 0, but careful studies of the chip */
|
||||
/* output prove that it instead counts up from 0 until the counter becomes */
|
||||
/* greater or equal to the period. This is an important difference when the*/
|
||||
/* program is rapidly changing the period to modulate the sound. */
|
||||
/* To compensate for the difference, when the period is changed we adjust */
|
||||
/* our internal counter. */
|
||||
/* Also, note that period = 0 is the same as period = 1. This is mentioned */
|
||||
/* in the YM2203 data sheets. However, this does NOT apply to the Envelope */
|
||||
/* period. In that case, period = 0 is half as period = 1. */
|
||||
switch(r) {
|
||||
case ACoarse:
|
||||
case AFine:
|
||||
old = PeriodA;
|
||||
PeriodA = (getReg(Reg.AFine) + 256 * getReg(Reg.ACoarse)) * UpdateStep;
|
||||
if (PeriodA == 0) PeriodA = UpdateStep;
|
||||
CountA += PeriodA - old;
|
||||
if (CountA <= 0) CountA = 1;
|
||||
break;
|
||||
case BCoarse:
|
||||
case BFine:
|
||||
old = PeriodB;
|
||||
PeriodB = (getReg(Reg.BFine) + 256 * getReg(Reg.BCoarse)) * UpdateStep;
|
||||
if (PeriodB == 0) PeriodB = UpdateStep;
|
||||
CountB += PeriodB - old;
|
||||
if (CountB <= 0) CountB = 1;
|
||||
break;
|
||||
case CCoarse:
|
||||
case CFine:
|
||||
setReg(Reg.CCoarse, getReg(Reg.CCoarse) & 0x0f);
|
||||
old = PeriodC;
|
||||
PeriodA = (getReg(Reg.CFine) + 256 * getReg(Reg.CCoarse)) * UpdateStep;
|
||||
if (PeriodC == 0) PeriodC = UpdateStep;
|
||||
CountC += PeriodC - old;
|
||||
if (CountC <= 0) CountC = 1;
|
||||
break;
|
||||
case NoisePeriod:
|
||||
old = PeriodN;
|
||||
PeriodN = getReg(Reg.NoisePeriod) * UpdateStep;
|
||||
if (PeriodN == 0) PeriodN = UpdateStep;
|
||||
CountN += PeriodN - old;
|
||||
if (CountN <= 0) CountN = 1;
|
||||
break;
|
||||
case Enable:
|
||||
lastEnable = value;
|
||||
break;
|
||||
case AVol:
|
||||
EnvelopeA = value & 0x10;
|
||||
if (EnvelopeA > 0)
|
||||
VolA = VolE;
|
||||
else {
|
||||
if (value > 0)
|
||||
VolA = CardMockingboard.VolTable[value];
|
||||
else
|
||||
VolA = CardMockingboard.VolTable[0];
|
||||
}
|
||||
break;
|
||||
case BVol:
|
||||
EnvelopeB = value & 0x10;
|
||||
if (EnvelopeB > 0)
|
||||
VolB = VolE;
|
||||
else {
|
||||
if (value > 0)
|
||||
VolB = CardMockingboard.VolTable[value];
|
||||
else
|
||||
VolB = CardMockingboard.VolTable[0];
|
||||
}
|
||||
break;
|
||||
case CVol:
|
||||
EnvelopeC = value & 0x10;
|
||||
if (EnvelopeC > 0)
|
||||
VolC = VolE;
|
||||
else {
|
||||
if (value > 0)
|
||||
VolC = CardMockingboard.VolTable[value];
|
||||
else
|
||||
VolC = CardMockingboard.VolTable[0];
|
||||
}
|
||||
break;
|
||||
case EnvFine:
|
||||
case EnvCoarse:
|
||||
old = PeriodE;
|
||||
PeriodE = ((getReg(Reg.EnvFine) + 256 * getReg(Reg.EnvCoarse))) * UpdateStep;
|
||||
if (PeriodE == 0) PeriodE = UpdateStep / 2;
|
||||
CountE += PeriodE - old;
|
||||
if (CountE <= 0) CountE = 1;
|
||||
if (PeriodE <= 0) PeriodE = 1;
|
||||
break;
|
||||
case EnvShape:
|
||||
/* envelope shapes:
|
||||
C AtAlH
|
||||
0 0 x x \___
|
||||
0 1 x x /|__
|
||||
1 0 0 0 \\\\
|
||||
1 0 0 1 \___
|
||||
1 0 1 0 \/\/
|
||||
__
|
||||
1 0 1 1 \|
|
||||
1 1 0 0 ////
|
||||
___
|
||||
1 1 0 1 /
|
||||
1 1 1 0 /\/\
|
||||
1 1 1 1 /|__
|
||||
|
||||
The envelope counter on the AY-3-8910 has 16 steps. On the YM2149 it
|
||||
has twice the steps, happening twice as fast. Since the end result is
|
||||
just a smoother curve, we always use the YM2149 behaviour.
|
||||
*/
|
||||
Attack = (value & 0x04) != 0 ? 0x1f : 0x00;
|
||||
if ( (value & 0x08) == 0) {
|
||||
/* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */
|
||||
Hold = 1;
|
||||
Alternate = Attack;
|
||||
} else {
|
||||
Hold = value & 0x01;
|
||||
Alternate = value & 0x02;
|
||||
}
|
||||
CountE = PeriodE;
|
||||
CountEnv = 0x1f;
|
||||
Holding = 0;
|
||||
VolE = CardMockingboard.VolTable[CountEnv ^ Attack];
|
||||
if (EnvelopeA != 0) VolA = VolE;
|
||||
if (EnvelopeB != 0) VolB = VolE;
|
||||
if (EnvelopeC != 0) VolC = VolE;
|
||||
break;
|
||||
case PortA:
|
||||
case PortB:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void update(int[][] buffer, int length) {
|
||||
int[] buf1, buf2, buf3;
|
||||
int outn;
|
||||
|
||||
buf1 = buffer[0];
|
||||
buf2 = buffer[1];
|
||||
buf3 = buffer[2];
|
||||
|
||||
|
||||
/* The 8910 has three outputs, each output is the mix of one of the three */
|
||||
/* tone generators and of the (single) noise generator. The two are mixed */
|
||||
/* BEFORE going into the DAC. The formula to mix each channel is: */
|
||||
/* (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). */
|
||||
/* Note that this means that if both tone and noise are disabled, the output */
|
||||
/* is 1, not 0, and can be modulated changing the volume. */
|
||||
|
||||
|
||||
/* If the channels are disabled, set their output to 1, and increase the */
|
||||
/* counter, if necessary, so they will not be inverted during this update. */
|
||||
/* Setting the output to 1 is necessary because a disabled channel is locked */
|
||||
/* into the ON state (see above); and it has no effect if the volume is 0. */
|
||||
/* If the volume is 0, increase the counter, but don't touch the output. */
|
||||
if ( (getReg(Reg.Enable) & 0x01) != 0) {
|
||||
if (CountA <= length*STEP) CountA += length*STEP;
|
||||
OutputA = 1;
|
||||
} else if (getReg(Reg.AVol) == 0) {
|
||||
/* note that I do count += length, NOT count = length + 1. You might think */
|
||||
/* it's the same since the volume is 0, but doing the latter could cause */
|
||||
/* interferencies when the program is rapidly modulating the volume. */
|
||||
if (CountA <= length*STEP) CountA += length*STEP;
|
||||
}
|
||||
|
||||
if ( (getReg(Reg.Enable) & 0x02) != 0) {
|
||||
if (CountB <= length*STEP) CountB += length*STEP;
|
||||
OutputB = 1;
|
||||
} else if (getReg(Reg.BVol) == 0) {
|
||||
if (CountB <= length*STEP) CountB += length*STEP;
|
||||
}
|
||||
|
||||
if ( (getReg(Reg.Enable) & 0x04) != 0) {
|
||||
if (CountC <= length*STEP) CountC += length*STEP;
|
||||
OutputC = 1;
|
||||
} else if (getReg(Reg.CVol) == 0) {
|
||||
if (CountC <= length*STEP) CountC += length*STEP;
|
||||
}
|
||||
|
||||
/* for the noise channel we must not touch OutputN - it's also not necessary */
|
||||
/* since we use outn. */
|
||||
if ((getReg(Reg.Enable) & 0x38) == 0x38) /* all off */
|
||||
if (CountN <= length*STEP) CountN += length*STEP;
|
||||
|
||||
outn = (OutputN | getReg(Reg.Enable));
|
||||
int index = 0;
|
||||
//System.out.println("Length:"+length);
|
||||
/* buffering loop */
|
||||
while (length != 0) {
|
||||
int vola,volb,volc;
|
||||
int left;
|
||||
|
||||
/* vola, volb and volc keep track of how long each square wave stays */
|
||||
/* in the 1 position during the sample period. */
|
||||
vola = volb = volc = 0;
|
||||
//System.out.println("STEP:"+STEP);
|
||||
|
||||
left = STEP;
|
||||
do {
|
||||
int nextevent;
|
||||
|
||||
if (CountN < left) nextevent = CountN;
|
||||
else nextevent = left;
|
||||
|
||||
if ( (outn & 0x08) != 0) {
|
||||
if (OutputA != 0) vola += CountA;
|
||||
CountA -= nextevent;
|
||||
/* PeriodA is the half period of the square wave. Here, in each */
|
||||
/* loop I add PeriodA twice, so that at the end of the loop the */
|
||||
/* square wave is in the same status (0 or 1) it was at the start. */
|
||||
/* vola is also incremented by PeriodA, since the wave has been 1 */
|
||||
/* exactly half of the time, regardless of the initial position. */
|
||||
/* If we exit the loop in the middle, OutputA has to be inverted */
|
||||
/* and vola incremented only if the exit status of the square */
|
||||
/* wave is 1. */
|
||||
while (CountA <= 0 && PeriodA > 0) {
|
||||
CountA += PeriodA;
|
||||
if (CountA > 0) {
|
||||
OutputA ^= 1;
|
||||
if (OutputA != 0) vola += PeriodA;
|
||||
break;
|
||||
}
|
||||
CountA += PeriodA;
|
||||
vola += PeriodA;
|
||||
}
|
||||
if (OutputA != 0) vola -= CountA;
|
||||
} else {
|
||||
CountA -= nextevent;
|
||||
while (CountA <= 0 && PeriodA > 0) {
|
||||
CountA += PeriodA;
|
||||
if (CountA > 0) {
|
||||
OutputA ^= 1;
|
||||
break;
|
||||
}
|
||||
CountA += PeriodA;
|
||||
}
|
||||
}
|
||||
|
||||
if ((outn & 0x10) != 0) {
|
||||
if (OutputB != 0) volb += CountB;
|
||||
CountB -= nextevent;
|
||||
while (CountB <= 0 && PeriodB > 0) {
|
||||
CountB += PeriodB;
|
||||
if (CountB > 0) {
|
||||
OutputB ^= 1;
|
||||
if (OutputB != 0) volb += PeriodB;
|
||||
break;
|
||||
}
|
||||
CountB += PeriodB;
|
||||
volb += PeriodB;
|
||||
}
|
||||
if (OutputB != 0) volb -= CountB;
|
||||
} else {
|
||||
CountB -= nextevent;
|
||||
while (CountB <= 0 && PeriodB > 0) {
|
||||
CountB += PeriodB;
|
||||
if (CountB > 0) {
|
||||
OutputB ^= 1;
|
||||
break;
|
||||
}
|
||||
CountB += PeriodB;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (outn & 0x20) != 0) {
|
||||
if (OutputC != 0) volc += CountC;
|
||||
CountC -= nextevent;
|
||||
while (CountC <= 0 && PeriodC > 0) {
|
||||
CountC += PeriodC;
|
||||
if (CountC > 0) {
|
||||
OutputC ^= 1;
|
||||
if (OutputC != 0) volc += PeriodC;
|
||||
break;
|
||||
}
|
||||
CountC += PeriodC;
|
||||
volc += PeriodC;
|
||||
}
|
||||
if (OutputC != 0) volc -= CountC;
|
||||
} else {
|
||||
CountC -= nextevent;
|
||||
while (CountC <= 0 && PeriodC > 0) {
|
||||
CountC += PeriodC;
|
||||
if (CountC > 0) {
|
||||
OutputC ^= 1;
|
||||
break;
|
||||
}
|
||||
CountC += PeriodC;
|
||||
}
|
||||
}
|
||||
|
||||
CountN -= nextevent;
|
||||
if (CountN <= 0 && PeriodN > 0) {
|
||||
/* Is noise output going to change? */
|
||||
/* (bit0^bit1)? */
|
||||
if (((RNG + 1) & 2) != 0) {
|
||||
OutputN ^= 0x0FF;
|
||||
outn = (OutputN | getReg(Reg.Enable));
|
||||
}
|
||||
|
||||
/* The Random Number Generator of the 8910 is a 17-bit shift */
|
||||
/* register. The input to the shift register is bit0 XOR bit3 */
|
||||
/* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */
|
||||
|
||||
/* The following is a fast way to compute bit17 = bit0^bit3. */
|
||||
/* Instead of doing all the logic operations, we only check */
|
||||
/* bit0, relying on the fact that after three shifts of the */
|
||||
/* register, what now is bit3 will become bit0, and will */
|
||||
/* invert, if necessary, bit14, which previously was bit17. */
|
||||
if ((RNG & 1) != 0) RNG ^= 0x0024000; /* This version is called the "Galois configuration". */
|
||||
RNG >>= 1;
|
||||
CountN += PeriodN;
|
||||
}
|
||||
|
||||
left -= nextevent;
|
||||
} while (left > 0);
|
||||
// System.out.println("End left loop");
|
||||
|
||||
/* update envelope */
|
||||
if (Holding == 0) {
|
||||
CountE -= STEP;
|
||||
if (CountE <= 0) {
|
||||
do {
|
||||
CountEnv--;
|
||||
CountE += PeriodE;
|
||||
} while (CountE <= 0);
|
||||
|
||||
/* check envelope current position */
|
||||
if (CountEnv < 0) {
|
||||
if (Hold != 0) {
|
||||
if (Alternate != 0)
|
||||
Attack ^= 0x1f;
|
||||
Holding = 1;
|
||||
CountEnv = 0;
|
||||
} else {
|
||||
/* if CountEnv has looped an odd number of times (usually 1), */
|
||||
/* invert the output. */
|
||||
if ( (Alternate != 0) && ((CountEnv & 0x20) != 0))
|
||||
Attack ^= 0x1f;
|
||||
CountEnv &= 0x1f;
|
||||
}
|
||||
}
|
||||
|
||||
VolE = VolTable[CountEnv ^ Attack];
|
||||
/* reload volume */
|
||||
if (EnvelopeA != 0) VolA = VolE;
|
||||
if (EnvelopeB != 0) VolB = VolE;
|
||||
if (EnvelopeC != 0) VolC = VolE;
|
||||
}
|
||||
}
|
||||
|
||||
// Output PCM wave [-32768...32767] instead of MAME's voltage level [0...32767]
|
||||
// - This allows for better s/w mixing
|
||||
buf1[index] = (vola * VolA) / STEP;
|
||||
buf2[index] = (volb * VolB) / STEP;
|
||||
buf3[index] = (volc * VolC) / STEP;
|
||||
/*
|
||||
if(VolA != 0) {
|
||||
if (vola != 0) buf1[index] = (vola * VolA) / STEP;
|
||||
else buf1[index] = -VolA;
|
||||
} else {
|
||||
buf1[index] = 0;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
if(VolB != 0) {
|
||||
if (volb != 0) buf2[index] = (volb * VolB) / STEP;
|
||||
else buf2[index] = -VolB;
|
||||
} else
|
||||
buf2[index] = 0;
|
||||
|
||||
//
|
||||
|
||||
if(VolC != 0) {
|
||||
if (volc != 0) buf3[index] = (volc * VolC) / STEP;
|
||||
else buf3[index] = -VolC;
|
||||
} else
|
||||
buf3[index] = 0;
|
||||
*/
|
||||
index++;
|
||||
length--;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public void writeReg(int chipNumber, int register, int value) {
|
||||
Reg r = Reg.get(register);
|
||||
writeReg(chipNumber, r, value);
|
||||
}
|
||||
|
||||
public void writeReg(int chipNumber, Reg register, int value) {
|
||||
chips.get(chipNumber).writeReg(register, value);
|
||||
}
|
||||
|
||||
// /length/ is the number of samples we require
|
||||
// NB. This should be called at twice the 6522 IRQ rate or (eg) 60Hz if no IRQ.
|
||||
public void update(int chipNumber,int[][] buffer,int length) {
|
||||
chips.get(chipNumber).update(buffer, length);
|
||||
}
|
||||
|
||||
int[][] buffers;
|
||||
int bufferLength = -1;
|
||||
public int[][] getBuffers(int length) {
|
||||
if (buffers == null || bufferLength != length) {
|
||||
buffers = new int[3][length];
|
||||
bufferLength = length;
|
||||
}
|
||||
return buffers;
|
||||
}
|
||||
|
||||
public void playSound(int size, int[] left, int[] right) {
|
||||
int[][] buffers = getBuffers(left.length);
|
||||
update(0, buffers, size);
|
||||
mixDown(left, buffers, size);
|
||||
update(1, buffers, size);
|
||||
mixDown(right, buffers, size);
|
||||
}
|
||||
|
||||
public void mixDown(int[] out, int[][] in, int size) {
|
||||
for (int i=0; i < size; i++) {
|
||||
int sample = (in[0][i] + in[1][i] + in[2][i]) / 3;
|
||||
out[i] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
public void setClock(int chipNumber,int clock) {
|
||||
chips.get(chipNumber).setClock(clock);
|
||||
}
|
||||
|
||||
public void reset(int chipNumber) {
|
||||
chips.get(chipNumber).reset();
|
||||
}
|
||||
|
||||
public void initAll(int nClock, int nSampleRate) {
|
||||
SampleRate = nSampleRate;
|
||||
for (PSG p:chips) {
|
||||
p.setClock(nClock);
|
||||
p.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void initClock(int nClock) {
|
||||
for (PSG p:chips) p.setClock(nClock);
|
||||
}
|
||||
|
||||
static void buildMixerTable() {
|
||||
VolTable = new int[32];
|
||||
int SampleRate;
|
||||
|
||||
/* 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 = MAX_OUTPUT;
|
||||
for (int i = 31;i > 0;i--) {
|
||||
VolTable[i] = (int) (out + 0.5); /* round to nearest */ // [TC: unsigned int cast]
|
||||
out /= 1.188502227; /* = 10 ^ (1.5/20) = 1.5dB */
|
||||
}
|
||||
VolTable[0] = 0;
|
||||
}
|
||||
}
|
|
@ -1,346 +1,365 @@
|
|||
/*
|
||||
* 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.mockingboard;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.core.Device;
|
||||
|
||||
/**
|
||||
* Implementation of 6522 VIA chip
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class R6522 extends Device {
|
||||
|
||||
public R6522(Computer computer) {
|
||||
super(computer);
|
||||
timer1freerun = true;
|
||||
timer1running = true;
|
||||
timer1latch = 0x1fff;
|
||||
timer1interruptEnabled = false;
|
||||
setRun(true);
|
||||
}
|
||||
|
||||
// 6522 VIA
|
||||
// http://www.applevault.com/twiki/Main/Mockingboard/6522.pdf
|
||||
// I/O registers
|
||||
public static enum Register {
|
||||
ORB(0), // Output Register B
|
||||
ORA(1), // Output Register A
|
||||
DDRB(2),// Data direction reg B
|
||||
DDRA(3),// Data direction reg A
|
||||
T1CL(4),// T1 low-order latches (low-order counter for read operations)
|
||||
T1CH(5),// T1 high-order counter
|
||||
T1LL(6),// T1 low-order latches
|
||||
T1LH(7),// T1 high-order latches
|
||||
T2CL(8),// T2 low-order latches (low-order counter for read operations)
|
||||
T2CH(9),// T2 high-order counter
|
||||
SR(10),// Shift register
|
||||
ACR(11),// Aux control register
|
||||
PCR(12),// Perripheral control register
|
||||
IFR(13),// Interrupt flag register
|
||||
IER(14),// Interrupt enable register
|
||||
ORAH(15);// Output Register A (no handshake)
|
||||
|
||||
int val;
|
||||
|
||||
Register(int v) {
|
||||
val = v;
|
||||
}
|
||||
|
||||
static public Register fromInt(int i) {
|
||||
for (Register r : Register.values()) {
|
||||
if (r.val == i) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// state variables
|
||||
public int oraReg = 0;
|
||||
public int iraReg = 0;
|
||||
public int orbReg = 0;
|
||||
public int irbReg = 0;
|
||||
// DDRA and DDRB must be set to output for mockingboard to do anything
|
||||
// Common values for this are FF for DDRA and 7 for DDRB
|
||||
// DDRB bits 0-2 are used to control AY chips but bits 3-7 are not connected.
|
||||
// that's why it is common to see mockingboard drivers init the port with a 7
|
||||
public int dataDirectionA = 0;
|
||||
public int dataDirectionB = 0;
|
||||
|
||||
// Though this is necessary for a complete emulation of the 6522, it isn't needed by the mockingboard
|
||||
// set by bit 0 of ACR
|
||||
// public boolean latchEnabledA = false;
|
||||
// set by bit 1 of ACR
|
||||
// public boolean latchEnabledB = false;
|
||||
//Bits 2,3,4 of ACR
|
||||
// static public enum ShiftRegisterControl {
|
||||
// interruptDisabled(0),
|
||||
// shiftInT2(4),
|
||||
// shiftIn02(8),
|
||||
// shiftInExt(12),
|
||||
// shiftOutFree(16),
|
||||
// shiftOutT2(20),
|
||||
// shiftOut02(24),
|
||||
// shiftOutExt(28);
|
||||
//
|
||||
// int val;
|
||||
// private ShiftRegisterControl(int v) {
|
||||
// val = v;
|
||||
// }
|
||||
//
|
||||
// public static ShiftRegisterControl fromBits(int b) {
|
||||
// b=b&28;
|
||||
// for (ShiftRegisterControl s : values()) {
|
||||
// if (s.val == b) return s;
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// public ShiftRegisterControl shiftMode = ShiftRegisterControl.interruptDisabled;
|
||||
// //Bit 5 of ACR (false = timed interrupt, true = count down pulses on PB6)
|
||||
// public boolean t2countPulses = false;
|
||||
// //Bit 6 of ACR (true = continuous, false = one-shot)
|
||||
// public boolean t1continuous = false;
|
||||
// //Bit 7 of ACR (true = enable PB7, false = interruptDisabled)
|
||||
// public boolean t1enablePB7 = false;
|
||||
// // NOTE: Mockingboard did not use PB6 or PB7, they are not connected to anything
|
||||
public boolean timer1interruptEnabled = true;
|
||||
public boolean timer1IRQ = false; // True if timer interrupt flag is set
|
||||
public int timer1latch = 0;
|
||||
public int timer1counter = 0;
|
||||
public boolean timer1freerun = false;
|
||||
public boolean timer1running = false;
|
||||
public boolean timer2interruptEnabled = true;
|
||||
public boolean timer2IRQ = false; // True if timer interrupt flag is set
|
||||
public int timer2latch = 0;
|
||||
public int timer2counter = 0;
|
||||
public boolean timer2running = false;
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "6522 VIA Chip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (timer1running) {
|
||||
timer1counter--;
|
||||
if (timer1counter < 0) {
|
||||
timer1counter = timer1latch;
|
||||
if (!timer1freerun) {
|
||||
timer1running = false;
|
||||
}
|
||||
if (timer1interruptEnabled) {
|
||||
// System.out.println("Timer 1 generated interrupt");
|
||||
timer1IRQ = true;
|
||||
computer.getCpu().generateInterrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (timer2running) {
|
||||
timer2counter--;
|
||||
if (timer2counter < 0) {
|
||||
timer2running = false;
|
||||
timer2counter = timer2latch;
|
||||
if (timer2interruptEnabled) {
|
||||
timer2IRQ = true;
|
||||
computer.getCpu().generateInterrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!timer1running && !timer2running) {
|
||||
setRun(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
// Start chip
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
// Reset
|
||||
}
|
||||
|
||||
public void writeRegister(int reg, int val) {
|
||||
int value = val & 0x0ff;
|
||||
Register r = Register.fromInt(reg);
|
||||
// System.out.println("Writing "+(value&0x0ff)+" to register "+r.toString());
|
||||
switch (r) {
|
||||
case ORB:
|
||||
if (dataDirectionB == 0) {
|
||||
break;
|
||||
}
|
||||
sendOutputB(value & dataDirectionB);
|
||||
break;
|
||||
case ORA:
|
||||
// case ORAH:
|
||||
if (dataDirectionA == 0) {
|
||||
break;
|
||||
}
|
||||
sendOutputA(value & dataDirectionA);
|
||||
break;
|
||||
case DDRB:
|
||||
dataDirectionB = value;
|
||||
break;
|
||||
case DDRA:
|
||||
dataDirectionA = value;
|
||||
break;
|
||||
case T1CL:
|
||||
case T1LL:
|
||||
timer1latch = (timer1latch & 0x0ff00) | value;
|
||||
break;
|
||||
case T1CH:
|
||||
timer1latch = (timer1latch & 0x0ff) | (value << 8);
|
||||
timer1IRQ = false;
|
||||
timer1counter = timer1latch;
|
||||
timer1running = true;
|
||||
setRun(true);
|
||||
break;
|
||||
case T1LH:
|
||||
timer1latch = (timer1latch & 0x0ff) | (value << 8);
|
||||
timer1IRQ = false;
|
||||
break;
|
||||
case T2CL:
|
||||
timer2latch = (timer2latch & 0x0ff00) | value;
|
||||
break;
|
||||
case T2CH:
|
||||
timer2latch = (timer2latch & 0x0ff) | (value << 8);
|
||||
timer2IRQ = false;
|
||||
timer2counter = timer2latch;
|
||||
timer2running = true;
|
||||
setRun(true);
|
||||
break;
|
||||
case SR:
|
||||
// SHIFT REGISTER NOT IMPLEMENTED
|
||||
break;
|
||||
case ACR:
|
||||
// SHIFT REGISTER NOT IMPLEMENTED
|
||||
timer1freerun = (value & 64) != 0;
|
||||
if (timer1freerun) {
|
||||
timer1running = true;
|
||||
setRun(true);
|
||||
}
|
||||
break;
|
||||
case PCR:
|
||||
// TODO: Implement if Votrax (SSI) is to be supported
|
||||
break;
|
||||
case IFR:
|
||||
if ((value & 64) != 0) {
|
||||
timer1IRQ = false;
|
||||
}
|
||||
if ((value & 32) != 0) {
|
||||
timer2IRQ = false;
|
||||
}
|
||||
break;
|
||||
case IER:
|
||||
boolean enable = (value & 128) != 0;
|
||||
if ((value & 64) != 0) {
|
||||
timer1interruptEnabled = enable;
|
||||
}
|
||||
if ((value & 32) != 0) {
|
||||
timer2interruptEnabled = enable;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Whatever uses 6522 will want to know when it is outputting values
|
||||
// So to hook that in, these abstract methods will be defined as appropriate
|
||||
public abstract void sendOutputA(int value);
|
||||
|
||||
public abstract void sendOutputB(int value);
|
||||
|
||||
public int readRegister(int reg) {
|
||||
Register r = Register.fromInt(reg);
|
||||
// System.out.println("Reading register "+r.toString());
|
||||
switch (r) {
|
||||
case ORB:
|
||||
if (dataDirectionB == 0x0ff) {
|
||||
break;
|
||||
}
|
||||
return receiveOutputB() & (dataDirectionB ^ 0x0ff);
|
||||
case ORA:
|
||||
case ORAH:
|
||||
if (dataDirectionA == 0x0ff) {
|
||||
break;
|
||||
}
|
||||
return receiveOutputA() & (dataDirectionA ^ 0x0ff);
|
||||
case DDRB:
|
||||
return dataDirectionB;
|
||||
case DDRA:
|
||||
return dataDirectionA;
|
||||
case T1CL:
|
||||
timer1IRQ = false;
|
||||
return timer1counter & 0x0ff;
|
||||
case T1CH:
|
||||
return (timer1counter & 0x0ff00) >> 8;
|
||||
case T1LL:
|
||||
return timer1latch & 0x0ff;
|
||||
case T1LH:
|
||||
return (timer1latch & 0x0ff00) >> 8;
|
||||
case T2CL:
|
||||
timer2IRQ = false;
|
||||
return timer2counter & 0x0ff;
|
||||
case T2CH:
|
||||
return (timer2counter & 0x0ff00) >> 8;
|
||||
case SR:
|
||||
// SHIFT REGISTER NOT IMPLEMENTED
|
||||
return 0;
|
||||
case ACR:
|
||||
// SHIFT REGISTER NOT IMPLEMENTED
|
||||
if (timer1freerun) {
|
||||
return 64;
|
||||
}
|
||||
return 0;
|
||||
case PCR:
|
||||
break;
|
||||
case IFR:
|
||||
int val = 0;
|
||||
if (timer1IRQ) {
|
||||
val |= 64;
|
||||
}
|
||||
if (timer2IRQ) {
|
||||
val |= 32;
|
||||
}
|
||||
if (val != 0) {
|
||||
val |= 128;
|
||||
}
|
||||
return val;
|
||||
case IER:
|
||||
val = 128;
|
||||
if (timer1interruptEnabled) {
|
||||
val |= 64;
|
||||
}
|
||||
if (timer2interruptEnabled) {
|
||||
val |= 32;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public abstract int receiveOutputA();
|
||||
|
||||
public abstract int receiveOutputB();
|
||||
}
|
||||
/*
|
||||
* 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.mockingboard;
|
||||
|
||||
import jace.core.Computer;
|
||||
import jace.core.Device;
|
||||
import jace.core.TimedDevice;
|
||||
|
||||
/**
|
||||
* Implementation of 6522 VIA chip
|
||||
*
|
||||
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||||
*/
|
||||
public abstract class R6522 extends TimedDevice {
|
||||
public static long SPEED = 1020484L; // (NTSC)
|
||||
|
||||
public R6522(Computer computer) {
|
||||
super(computer);
|
||||
timer1freerun = true;
|
||||
timer1running = true;
|
||||
timer1latch = 0x1fff;
|
||||
timer1interruptEnabled = false;
|
||||
setSpeedInHz(SPEED);
|
||||
setRun(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long defaultCyclesPerSecond() {
|
||||
return SPEED;
|
||||
}
|
||||
|
||||
// 6522 VIA
|
||||
// http://www.applevault.com/twiki/Main/Mockingboard/6522.pdf
|
||||
// I/O registers
|
||||
public static enum Register {
|
||||
ORB(0), // Output Register B
|
||||
ORA(1), // Output Register A
|
||||
DDRB(2),// Data direction reg B
|
||||
DDRA(3),// Data direction reg A
|
||||
T1CL(4),// T1 low-order latches (low-order counter for read operations)
|
||||
T1CH(5),// T1 high-order counter
|
||||
T1LL(6),// T1 low-order latches
|
||||
T1LH(7),// T1 high-order latches
|
||||
T2CL(8),// T2 low-order latches (low-order counter for read operations)
|
||||
T2CH(9),// T2 high-order counter
|
||||
SR(10),// Shift register
|
||||
ACR(11),// Aux control register
|
||||
PCR(12),// Perripheral control register
|
||||
IFR(13),// Interrupt flag register
|
||||
IER(14),// Interrupt enable register
|
||||
ORAH(15);// Output Register A (no handshake)
|
||||
|
||||
int val;
|
||||
|
||||
Register(int v) {
|
||||
val = v;
|
||||
}
|
||||
|
||||
static public Register fromInt(int i) {
|
||||
for (Register r : Register.values()) {
|
||||
if (r.val == i) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// state variables
|
||||
public int oraReg = 0;
|
||||
public int iraReg = 0;
|
||||
public int orbReg = 0;
|
||||
public int irbReg = 0;
|
||||
// DDRA and DDRB must be set to output for mockingboard to do anything
|
||||
// Common values for this are FF for DDRA and 7 for DDRB
|
||||
// DDRB bits 0-2 are used to control AY chips but bits 3-7 are not connected.
|
||||
// that's why it is common to see mockingboard drivers init the port with a 7
|
||||
public int dataDirectionA = 0;
|
||||
public int dataDirectionB = 0;
|
||||
|
||||
// Though this is necessary for a complete emulation of the 6522, it isn't needed by the mockingboard
|
||||
// set by bit 0 of ACR
|
||||
// public boolean latchEnabledA = false;
|
||||
// set by bit 1 of ACR
|
||||
// public boolean latchEnabledB = false;
|
||||
//Bits 2,3,4 of ACR
|
||||
// static public enum ShiftRegisterControl {
|
||||
// interruptDisabled(0),
|
||||
// shiftInT2(4),
|
||||
// shiftIn02(8),
|
||||
// shiftInExt(12),
|
||||
// shiftOutFree(16),
|
||||
// shiftOutT2(20),
|
||||
// shiftOut02(24),
|
||||
// shiftOutExt(28);
|
||||
//
|
||||
// int val;
|
||||
// private ShiftRegisterControl(int v) {
|
||||
// val = v;
|
||||
// }
|
||||
//
|
||||
// public static ShiftRegisterControl fromBits(int b) {
|
||||
// b=b&28;
|
||||
// for (ShiftRegisterControl s : values()) {
|
||||
// if (s.val == b) return s;
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// public ShiftRegisterControl shiftMode = ShiftRegisterControl.interruptDisabled;
|
||||
// //Bit 5 of ACR (false = timed interrupt, true = count down pulses on PB6)
|
||||
// public boolean t2countPulses = false;
|
||||
// //Bit 6 of ACR (true = continuous, false = one-shot)
|
||||
// public boolean t1continuous = false;
|
||||
// //Bit 7 of ACR (true = enable PB7, false = interruptDisabled)
|
||||
// public boolean t1enablePB7 = false;
|
||||
// // NOTE: Mockingboard did not use PB6 or PB7, they are not connected to anything
|
||||
public boolean timer1interruptEnabled = true;
|
||||
public boolean timer1IRQ = false; // True if timer interrupt flag is set
|
||||
public int timer1latch = 0;
|
||||
public int timer1counter = 0;
|
||||
public boolean timer1freerun = false;
|
||||
public boolean timer1running = false;
|
||||
public boolean timer2interruptEnabled = true;
|
||||
public boolean timer2IRQ = false; // True if timer interrupt flag is set
|
||||
public int timer2latch = 0;
|
||||
public int timer2counter = 0;
|
||||
public boolean timer2running = false;
|
||||
public boolean unclocked = false;
|
||||
|
||||
@Override
|
||||
protected String getDeviceName() {
|
||||
return "6522 VIA Chip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
if (!unclocked) {
|
||||
doTick();
|
||||
}
|
||||
}
|
||||
|
||||
public void setUnclocked(boolean unclocked) {
|
||||
this.unclocked = unclocked;
|
||||
}
|
||||
|
||||
public void doTick() {
|
||||
if (timer1running) {
|
||||
timer1counter--;
|
||||
if (timer1counter < 0) {
|
||||
timer1counter = timer1latch;
|
||||
if (!timer1freerun) {
|
||||
timer1running = false;
|
||||
}
|
||||
if (timer1interruptEnabled) {
|
||||
// System.out.println("Timer 1 generated interrupt");
|
||||
timer1IRQ = true;
|
||||
computer.getCpu().generateInterrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (timer2running) {
|
||||
timer2counter--;
|
||||
if (timer2counter < 0) {
|
||||
timer2running = false;
|
||||
timer2counter = timer2latch;
|
||||
if (timer2interruptEnabled) {
|
||||
timer2IRQ = true;
|
||||
computer.getCpu().generateInterrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!timer1running && !timer2running) {
|
||||
setRun(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach() {
|
||||
// Start chip
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure() {
|
||||
// Reset
|
||||
}
|
||||
|
||||
public void writeRegister(int reg, int val) {
|
||||
int value = val & 0x0ff;
|
||||
Register r = Register.fromInt(reg);
|
||||
// System.out.println("Writing "+(value&0x0ff)+" to register "+r.toString());
|
||||
switch (r) {
|
||||
case ORB:
|
||||
if (dataDirectionB == 0) {
|
||||
break;
|
||||
}
|
||||
sendOutputB(value & dataDirectionB);
|
||||
break;
|
||||
case ORA:
|
||||
// case ORAH:
|
||||
if (dataDirectionA == 0) {
|
||||
break;
|
||||
}
|
||||
sendOutputA(value & dataDirectionA);
|
||||
break;
|
||||
case DDRB:
|
||||
dataDirectionB = value;
|
||||
break;
|
||||
case DDRA:
|
||||
dataDirectionA = value;
|
||||
break;
|
||||
case T1CL:
|
||||
case T1LL:
|
||||
timer1latch = (timer1latch & 0x0ff00) | value;
|
||||
break;
|
||||
case T1CH:
|
||||
timer1latch = (timer1latch & 0x0ff) | (value << 8);
|
||||
timer1IRQ = false;
|
||||
timer1counter = timer1latch;
|
||||
timer1running = true;
|
||||
setRun(true);
|
||||
break;
|
||||
case T1LH:
|
||||
timer1latch = (timer1latch & 0x0ff) | (value << 8);
|
||||
timer1IRQ = false;
|
||||
break;
|
||||
case T2CL:
|
||||
timer2latch = (timer2latch & 0x0ff00) | value;
|
||||
break;
|
||||
case T2CH:
|
||||
timer2latch = (timer2latch & 0x0ff) | (value << 8);
|
||||
timer2IRQ = false;
|
||||
timer2counter = timer2latch;
|
||||
timer2running = true;
|
||||
setRun(true);
|
||||
break;
|
||||
case SR:
|
||||
// SHIFT REGISTER NOT IMPLEMENTED
|
||||
break;
|
||||
case ACR:
|
||||
// SHIFT REGISTER NOT IMPLEMENTED
|
||||
timer1freerun = (value & 64) != 0;
|
||||
if (timer1freerun) {
|
||||
timer1running = true;
|
||||
setRun(true);
|
||||
}
|
||||
break;
|
||||
case PCR:
|
||||
// TODO: Implement if Votrax (SSI) is to be supported
|
||||
break;
|
||||
case IFR:
|
||||
if ((value & 64) != 0) {
|
||||
timer1IRQ = false;
|
||||
}
|
||||
if ((value & 32) != 0) {
|
||||
timer2IRQ = false;
|
||||
}
|
||||
break;
|
||||
case IER:
|
||||
boolean enable = (value & 128) != 0;
|
||||
if ((value & 64) != 0) {
|
||||
timer1interruptEnabled = enable;
|
||||
}
|
||||
if ((value & 32) != 0) {
|
||||
timer2interruptEnabled = enable;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Whatever uses 6522 will want to know when it is outputting values
|
||||
// So to hook that in, these abstract methods will be defined as appropriate
|
||||
public abstract void sendOutputA(int value);
|
||||
|
||||
public abstract void sendOutputB(int value);
|
||||
|
||||
public int readRegister(int reg) {
|
||||
Register r = Register.fromInt(reg);
|
||||
// System.out.println("Reading register "+r.toString());
|
||||
switch (r) {
|
||||
case ORB:
|
||||
if (dataDirectionB == 0x0ff) {
|
||||
break;
|
||||
}
|
||||
return receiveOutputB() & (dataDirectionB ^ 0x0ff);
|
||||
case ORA:
|
||||
case ORAH:
|
||||
if (dataDirectionA == 0x0ff) {
|
||||
break;
|
||||
}
|
||||
return receiveOutputA() & (dataDirectionA ^ 0x0ff);
|
||||
case DDRB:
|
||||
return dataDirectionB;
|
||||
case DDRA:
|
||||
return dataDirectionA;
|
||||
case T1CL:
|
||||
timer1IRQ = false;
|
||||
return timer1counter & 0x0ff;
|
||||
case T1CH:
|
||||
return (timer1counter & 0x0ff00) >> 8;
|
||||
case T1LL:
|
||||
return timer1latch & 0x0ff;
|
||||
case T1LH:
|
||||
return (timer1latch & 0x0ff00) >> 8;
|
||||
case T2CL:
|
||||
timer2IRQ = false;
|
||||
return timer2counter & 0x0ff;
|
||||
case T2CH:
|
||||
return (timer2counter & 0x0ff00) >> 8;
|
||||
case SR:
|
||||
// SHIFT REGISTER NOT IMPLEMENTED
|
||||
return 0;
|
||||
case ACR:
|
||||
// SHIFT REGISTER NOT IMPLEMENTED
|
||||
if (timer1freerun) {
|
||||
return 64;
|
||||
}
|
||||
return 0;
|
||||
case PCR:
|
||||
break;
|
||||
case IFR:
|
||||
int val = 0;
|
||||
if (timer1IRQ) {
|
||||
val |= 64;
|
||||
}
|
||||
if (timer2IRQ) {
|
||||
val |= 32;
|
||||
}
|
||||
if (val != 0) {
|
||||
val |= 128;
|
||||
}
|
||||
return val;
|
||||
case IER:
|
||||
val = 128;
|
||||
if (timer1interruptEnabled) {
|
||||
val |= 64;
|
||||
}
|
||||
if (timer2interruptEnabled) {
|
||||
val |= 32;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public abstract int receiveOutputA();
|
||||
|
||||
public abstract int receiveOutputB();
|
||||
}
|
||||
|
|
30
src/main/java/jace/library/StringUtils.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
package jace.library;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
public class StringUtils {
|
||||
|
||||
/**
|
||||
* Get XML String of utf-8
|
||||
*
|
||||
* @return XML-Formed string
|
||||
*/
|
||||
public static String getUTF8XMLString(String xml) {
|
||||
// A StringBuffer Object
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(xml);
|
||||
String xmString = "";
|
||||
String xmlUTF8 = "";
|
||||
try {
|
||||
xmString = new String(sb.toString().getBytes("UTF-8"));
|
||||
xmlUTF8 = URLEncoder.encode(xmString, "UTF-8");
|
||||
System.out.println("utf-8 编码:" + xmlUTF8);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
// return to String Formed
|
||||
return xmlUTF8;
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import jace.cheat.MetaCheat.SearchType;
|
|||
import jace.state.State;
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
@ -25,8 +26,8 @@ import javafx.collections.FXCollections;
|
|||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.SubScene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
|
@ -44,18 +45,22 @@ import javafx.scene.control.cell.CheckBoxTableCell;
|
|||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.control.cell.TextFieldTableCell;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.TilePane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.util.converter.DefaultStringConverter;
|
||||
import javafx.util.converter.IntegerStringConverter;
|
||||
|
||||
public class MetacheatUI {
|
||||
|
||||
boolean isRetina;
|
||||
double drawScale;
|
||||
|
||||
|
||||
@FXML
|
||||
private Button pauseButton;
|
||||
|
||||
|
@ -70,10 +75,7 @@ public class MetacheatUI {
|
|||
|
||||
@FXML
|
||||
private StackPane memoryViewContents;
|
||||
|
||||
@FXML
|
||||
private Canvas memoryViewCanvas;
|
||||
|
||||
|
||||
@FXML
|
||||
private TabPane searchTypesTabPane;
|
||||
|
||||
|
@ -238,6 +240,7 @@ public class MetacheatUI {
|
|||
assert searchStartAddressField != null : "fx:id=\"searchStartAddressField\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
||||
assert searchEndAddressField != null : "fx:id=\"searchEndAddressField\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
||||
assert memoryViewPane != null : "fx:id=\"memoryViewPane\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
||||
assert memoryViewContents != null : "fx:id=\"memoryViewContents\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
||||
assert searchTypesTabPane != null : "fx:id=\"searchTypesTabPane\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
||||
assert searchValueField != null : "fx:id=\"searchValueField\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
||||
assert searchTypeByte != null : "fx:id=\"searchTypeByte\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
||||
|
@ -261,7 +264,7 @@ public class MetacheatUI {
|
|||
assert cheatsTableView != null : "fx:id=\"cheatsTableView\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
||||
|
||||
isRetina = Screen.getPrimary().getDpi() >= 110;
|
||||
|
||||
|
||||
Emulator.computer.getRunningProperty().addListener((val, oldVal, newVal) -> {
|
||||
Platform.runLater(() -> pauseButton.setText(newVal ? "Pause" : "Resume"));
|
||||
});
|
||||
|
@ -301,8 +304,6 @@ public class MetacheatUI {
|
|||
addWatch(result.getAddress());
|
||||
});
|
||||
|
||||
memoryViewCanvas.setMouseTransparent(false);
|
||||
memoryViewCanvas.addEventFilter(MouseEvent.MOUSE_CLICKED, this::memoryViewClicked);
|
||||
showValuesCheckbox.selectedProperty().addListener((prop, oldVal, newVal) -> {
|
||||
if (newVal) {
|
||||
redrawMemoryView();
|
||||
|
@ -310,7 +311,6 @@ public class MetacheatUI {
|
|||
});
|
||||
memoryViewPane.boundsInParentProperty().addListener((prop, oldVal, newVal) -> redrawMemoryView());
|
||||
drawScale = isRetina ? 0.5 : 1.0;
|
||||
memoryViewCanvas.widthProperty().bind(memoryViewPane.widthProperty().multiply(drawScale).subtract(8));
|
||||
|
||||
watchesPane.setHgap(5);
|
||||
watchesPane.setVgap(5);
|
||||
|
@ -377,9 +377,10 @@ public class MetacheatUI {
|
|||
|
||||
ChangeListener<String> addressRangeListener = (prop, oldVal, newVal) -> Application.invokeLater(this::redrawMemoryView);
|
||||
|
||||
public static final int MEMORY_BOX_SIZE = 4;
|
||||
public static final int MEMORY_BOX_GAP = 2;
|
||||
public static final int MEMORY_BOX_SIZE = 5;
|
||||
public static final int MEMORY_BOX_GAP = 1;
|
||||
public static final int MEMORY_BOX_TOTAL_SIZE = (MEMORY_BOX_SIZE + MEMORY_BOX_GAP);
|
||||
public static final int UPDATE_NODE_LIMIT = 10000;
|
||||
public int memoryViewColumns;
|
||||
public int memoryViewRows;
|
||||
|
||||
|
@ -388,18 +389,14 @@ public class MetacheatUI {
|
|||
ScheduledFuture animationFuture = null;
|
||||
Tooltip memoryWatchTooltip = new Tooltip();
|
||||
|
||||
private void memoryViewClicked(MouseEvent e) {
|
||||
private void memoryViewClicked(MouseEvent e, MemoryCell cell) {
|
||||
if (cheatEngine != null) {
|
||||
Watch currentWatch = (Watch) memoryWatchTooltip.getGraphic();
|
||||
if (currentWatch != null) {
|
||||
currentWatch.disconnect();
|
||||
}
|
||||
|
||||
double x = e.getX() / drawScale;
|
||||
double y = e.getY() / drawScale;
|
||||
int col = (int) (x / MEMORY_BOX_TOTAL_SIZE);
|
||||
int row = (int) (y / MEMORY_BOX_TOTAL_SIZE);
|
||||
int addr = cheatEngine.getStartAddress() + row * memoryViewColumns + col;
|
||||
|
||||
int addr = cell.address;
|
||||
Watch watch = new Watch(addr, this);
|
||||
|
||||
Label addWatch = new Label("Watch >>");
|
||||
|
@ -424,7 +421,7 @@ public class MetacheatUI {
|
|||
memoryWatchTooltip.setGraphic(null);
|
||||
});
|
||||
memoryWatchTooltip.setGraphic(watch);
|
||||
memoryWatchTooltip.show(memoryViewContents, e.getScreenX() + 5, e.getScreenY() - 15);
|
||||
memoryWatchTooltip.show(memoryViewPane.getContent(), e.getScreenX() + 5, e.getScreenY() - 15);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -432,22 +429,21 @@ public class MetacheatUI {
|
|||
if (!Emulator.computer.getRunningProperty().get()) {
|
||||
return;
|
||||
}
|
||||
GraphicsContext context = memoryViewCanvas.getGraphicsContext2D();
|
||||
Set<MemoryCell> draw = new HashSet<>(redrawNodes);
|
||||
redrawNodes.clear();
|
||||
Application.invokeLater(() -> {
|
||||
draw.stream().forEach((jace.cheat.MemoryCell cell) -> {
|
||||
Iterator<MemoryCell> i = redrawNodes.iterator();
|
||||
for (int limit = 0; i.hasNext() && limit < UPDATE_NODE_LIMIT; limit++) {
|
||||
MemoryCell cell = i.next();
|
||||
i.remove();
|
||||
if (showValuesCheckbox.isSelected()) {
|
||||
int val = cell.value.get() & 0x0ff;
|
||||
context.setFill(Color.rgb(val, val, val));
|
||||
cell.getShape().setFill(Color.rgb(val, val, val));
|
||||
} else {
|
||||
context.setFill(Color.rgb(
|
||||
cell.getShape().setFill(Color.rgb(
|
||||
cell.writeCount.get(),
|
||||
cell.readCount.get(),
|
||||
cell.execCount.get()));
|
||||
}
|
||||
context.fillRect(cell.getX(), cell.getY(), cell.getWidth(), cell.getHeight());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -473,29 +469,29 @@ public class MetacheatUI {
|
|||
int pixelsPerBlock = 16 * MEMORY_BOX_TOTAL_SIZE;
|
||||
memoryViewColumns = (int) (memoryViewPane.getWidth() / pixelsPerBlock) * 16;
|
||||
memoryViewRows = ((cheatEngine.getEndAddress() - cheatEngine.getStartAddress()) / memoryViewColumns) + 1;
|
||||
double canvasHeight = memoryViewRows * MEMORY_BOX_TOTAL_SIZE * drawScale;
|
||||
|
||||
memoryViewContents.setPrefHeight(canvasHeight);
|
||||
memoryViewCanvas.setHeight(canvasHeight);
|
||||
GraphicsContext context = memoryViewCanvas.getGraphicsContext2D();
|
||||
context.setFill(Color.rgb(40, 40, 40));
|
||||
context.fillRect(0, 0, memoryViewCanvas.getWidth(), memoryViewCanvas.getHeight());
|
||||
|
||||
memoryViewContents.prefWidthProperty().bind(memoryViewPane.widthProperty().multiply(drawScale).subtract(8));
|
||||
|
||||
Group memoryView = new Group();
|
||||
memoryViewContents.setBackground(new Background(new BackgroundFill(Color.rgb(40, 40, 40), null, null)));
|
||||
for (int addr = cheatEngine.getStartAddress(); addr <= cheatEngine.getEndAddress(); addr++) {
|
||||
int col = (addr - cheatEngine.getStartAddress()) % memoryViewColumns;
|
||||
int row = (addr - cheatEngine.getStartAddress()) / memoryViewColumns;
|
||||
MemoryCell cell = cheatEngine.getMemoryCell(addr);
|
||||
cell.setRect(
|
||||
(int) (col * MEMORY_BOX_TOTAL_SIZE * drawScale),
|
||||
(int) (row * MEMORY_BOX_TOTAL_SIZE * drawScale),
|
||||
(int) (MEMORY_BOX_SIZE * drawScale),
|
||||
(int) (MEMORY_BOX_SIZE * drawScale));
|
||||
Rectangle rect = new Rectangle(col * MEMORY_BOX_TOTAL_SIZE * drawScale, row * MEMORY_BOX_TOTAL_SIZE * drawScale, MEMORY_BOX_SIZE * drawScale, MEMORY_BOX_SIZE * drawScale);
|
||||
rect.setOnMouseClicked(e -> memoryViewClicked(e, cell));
|
||||
rect.setFill(Color.GRAY);
|
||||
cell.setShape(rect);
|
||||
memoryView.getChildren().add(rect);
|
||||
redrawNodes.add(cell);
|
||||
}
|
||||
memoryViewContents.getChildren().clear();
|
||||
memoryViewContents.getChildren().add(memoryView);
|
||||
MemoryCell.setListener((javafx.beans.value.ObservableValue<? extends jace.cheat.MemoryCell> prop, jace.cheat.MemoryCell oldCell, jace.cheat.MemoryCell newCell) -> {
|
||||
redrawNodes.add(newCell);
|
||||
});
|
||||
|
||||
setZoom(1/drawScale);
|
||||
|
||||
setZoom(1 / drawScale);
|
||||
|
||||
if (resume) {
|
||||
Emulator.computer.resume();
|
||||
|
@ -503,19 +499,19 @@ public class MetacheatUI {
|
|||
}
|
||||
|
||||
private void changeZoom(double amount) {
|
||||
if (memoryViewCanvas != null) {
|
||||
double zoom = memoryViewCanvas.getScaleX();
|
||||
if (memoryViewContents != null) {
|
||||
double zoom = memoryViewContents.getScaleX();
|
||||
zoom += amount;
|
||||
setZoom(zoom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setZoom(double zoom) {
|
||||
if (memoryViewCanvas != null) {
|
||||
memoryViewCanvas.setScaleX(zoom);
|
||||
memoryViewCanvas.setScaleY(zoom);
|
||||
StackPane scrollArea = (StackPane) memoryViewCanvas.getParent();
|
||||
scrollArea.setPrefSize(memoryViewCanvas.getWidth() * zoom, memoryViewCanvas.getHeight() * zoom);
|
||||
if (memoryViewContents != null) {
|
||||
memoryViewContents.setScaleX(zoom);
|
||||
memoryViewContents.setScaleY(zoom);
|
||||
// StackPane scrollArea = (StackPane) memoryViewCanvas.getParent();
|
||||
// scrollArea.setPrefSize(memoryViewCanvas.getWidth() * zoom, memoryViewCanvas.getHeight() * zoom);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,188 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import javafx.scene.canvas.*?>
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Slider?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.TilePane?>
|
||||
|
||||
<AnchorPane id="AnchorPane" fx:id="rootPane" prefHeight="384.0" prefWidth="560.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jace.JaceUIController">
|
||||
<AnchorPane id="AnchorPane" fx:id="rootPane" prefHeight="384.0" prefWidth="560.0" style="-fx-background-color: black;" stylesheets="@../styles/style.css" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jace.JaceUIController">
|
||||
<children>
|
||||
<StackPane fx:id="stackPane" prefHeight="384.0" prefWidth="560.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<children>
|
||||
<ImageView fx:id="appleScreen" fitHeight="384.0" fitWidth="560.0" pickOnBounds="true" />
|
||||
<ImageView fx:id="appleScreen" fitHeight="384.0" fitWidth="560.0" pickOnBounds="true" style="-fx-background-color: BLACK;" />
|
||||
<HBox fx:id="notificationBox" alignment="BOTTOM_RIGHT" blendMode="SCREEN" fillHeight="false" maxHeight="45.0" mouseTransparent="true" prefHeight="45.0" prefWidth="560.0" StackPane.alignment="BOTTOM_CENTER" />
|
||||
<AnchorPane fx:id="menuButtonPane" prefHeight="200.0" prefWidth="200.0">
|
||||
<children>
|
||||
<Button fx:id="menuButton" layoutX="494.0" layoutY="14.0" mnemonicParsing="false" styleClass="menuButton" text="☰" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="14.0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<BorderPane fx:id="controlOverlay" visible="false">
|
||||
<center>
|
||||
<HBox maxHeight="64.0" prefHeight="64.0" styleClass="uiSpeedSlider" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Pane prefHeight="200.0" prefWidth="200.0" HBox.hgrow="SOMETIMES" />
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<ImageView layoutX="2.0" layoutY="2.0">
|
||||
<image>
|
||||
<Image url="@../styles/icons/slow.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<Slider fx:id="speedSlider" blockIncrement="1.0" majorTickUnit="5.0" max="10.0" minorTickCount="5" prefHeight="64.0" prefWidth="300.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" value="1.0" HBox.hgrow="ALWAYS">
|
||||
<HBox.margin>
|
||||
<Insets />
|
||||
</HBox.margin>
|
||||
<padding>
|
||||
<Insets top="18.0" />
|
||||
</padding>
|
||||
</Slider>
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<ImageView layoutX="2.0" layoutY="2.0">
|
||||
<image>
|
||||
<Image url="@../styles/icons/fast.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<Pane prefHeight="200.0" prefWidth="200.0" HBox.hgrow="SOMETIMES" />
|
||||
</children>
|
||||
</HBox>
|
||||
</center>
|
||||
<top>
|
||||
<HBox fillHeight="false" nodeOrientation="LEFT_TO_RIGHT" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<TilePane hgap="5.0" nodeOrientation="LEFT_TO_RIGHT" vgap="5.0" HBox.hgrow="SOMETIMES">
|
||||
<children>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Info">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/info.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Config">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/config.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</TilePane>
|
||||
<TilePane alignment="TOP_RIGHT" hgap="5.0" vgap="5.0" HBox.hgrow="ALWAYS">
|
||||
<children>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="IDE">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/ide.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Inspect">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/inspect.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button alignment="TOP_LEFT" contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Plug-in" TilePane.alignment="TOP_RIGHT">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/plugin.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</TilePane>
|
||||
</children>
|
||||
</HBox>
|
||||
</top>
|
||||
<bottom>
|
||||
<HBox fillHeight="false" nodeOrientation="LEFT_TO_RIGHT" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<TilePane hgap="5.0" nodeOrientation="LEFT_TO_RIGHT" vgap="5.0" HBox.hgrow="SOMETIMES">
|
||||
<children>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Fullscreen">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/fullscreen.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Aspect">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/aspect.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Gfx Mode">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/screenshot.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</TilePane>
|
||||
<TilePane alignment="TOP_RIGHT" hgap="5.0" vgap="5.0" HBox.hgrow="ALWAYS">
|
||||
<children>
|
||||
<Button contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Play">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/play.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button alignment="TOP_LEFT" contentDisplay="TOP" mnemonicParsing="false" styleClass="uiActionButton" text="Restart" TilePane.alignment="TOP_RIGHT">
|
||||
<graphic>
|
||||
<ImageView>
|
||||
<image>
|
||||
<Image url="@../styles/icons/reboot.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</TilePane>
|
||||
</children>
|
||||
</HBox>
|
||||
</bottom>
|
||||
<StackPane.margin>
|
||||
<Insets />
|
||||
</StackPane.margin>
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding>
|
||||
</BorderPane>
|
||||
</children>
|
||||
</StackPane>
|
||||
</children>
|
||||
|
|
|
@ -1,14 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import java.net.*?>
|
||||
<?import javafx.scene.canvas.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.SubScene?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.control.RadioButton?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.scene.control.SplitPane?>
|
||||
<?import javafx.scene.control.Tab?>
|
||||
<?import javafx.scene.control.TabPane?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.control.TitledPane?>
|
||||
<?import javafx.scene.control.ToggleGroup?>
|
||||
<?import javafx.scene.control.ToolBar?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.FlowPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.TilePane?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="520.0" prefWidth="710.0" stylesheets="@../styles/style.css" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jace.ui.MetacheatUI">
|
||||
<!--<?import javafx.scene.canvas.*?>-->
|
||||
|
||||
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="520.0" prefWidth="710.0" stylesheets="@../styles/style.css" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jace.ui.MetacheatUI">
|
||||
<stylesheets>
|
||||
<URL value="@/styles/style.css" />
|
||||
</stylesheets>
|
||||
|
@ -29,11 +51,7 @@
|
|||
<items>
|
||||
<ScrollPane fx:id="memoryViewPane" prefHeight="450.0" prefWidth="391.0">
|
||||
<content>
|
||||
<StackPane fx:id="memoryViewContents" prefHeight="150.0" prefWidth="200.0">
|
||||
<children>
|
||||
<Canvas fx:id="memoryViewCanvas" height="200.0" width="200.0" />
|
||||
</children>
|
||||
</StackPane>
|
||||
<StackPane fx:id="memoryViewContents" prefHeight="150.0" prefWidth="200.0"/>
|
||||
</content></ScrollPane>
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0">
|
||||
<content>
|
||||
|
|
BIN
src/main/resources/jace/logo.psd
Normal file
BIN
src/main/resources/jace/logo_black.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/main/resources/jace/logo_transparent.icns
Normal file
BIN
src/main/resources/jace/logo_transparent.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/main/resources/styles/icons/aspect.png
Normal file
After Width: | Height: | Size: 724 B |
BIN
src/main/resources/styles/icons/brun.png
Normal file
After Width: | Height: | Size: 976 B |
BIN
src/main/resources/styles/icons/config.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/main/resources/styles/icons/fast.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src/main/resources/styles/icons/fullscreen.png
Normal file
After Width: | Height: | Size: 914 B |
BIN
src/main/resources/styles/icons/ide.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/resources/styles/icons/info.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/main/resources/styles/icons/inspect.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/main/resources/styles/icons/paste.png
Normal file
After Width: | Height: | Size: 800 B |
BIN
src/main/resources/styles/icons/play.png
Normal file
After Width: | Height: | Size: 567 B |
BIN
src/main/resources/styles/icons/plugin.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/main/resources/styles/icons/reboot.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/resources/styles/icons/rewind.png
Normal file
After Width: | Height: | Size: 701 B |
BIN
src/main/resources/styles/icons/screenshot.png
Normal file
After Width: | Height: | Size: 959 B |
BIN
src/main/resources/styles/icons/slow.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src/main/resources/styles/icons/sound.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
|
@ -3,7 +3,7 @@
|
|||
}
|
||||
|
||||
.setting-row {
|
||||
-fx-padding: 5 0 0 4;
|
||||
-fx-padding: 5 0 0 4;
|
||||
}
|
||||
|
||||
.setting-label, .setting-keyboard-shortcut {
|
||||
|
@ -19,3 +19,30 @@
|
|||
-fx-font-size: 9pt;
|
||||
-fx-font-family: "Courier New";
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
-fx-font-size:16pt;
|
||||
-fx-border-radius: 10px;
|
||||
-fx-background-radius: 10px;
|
||||
}
|
||||
|
||||
.menuButton, .uiActionButton, .uiSpeedSlider ImageView, .uiSpeedSlider Slider, .uiSpeedSlider AnchorPane {
|
||||
-fx-background-color: rgba(0, 0, 0, 0.75);
|
||||
-fx-text-fill: #a0FFa0
|
||||
}
|
||||
|
||||
.uiActionButton ImageView, .uiSpeedSlider ImageView {
|
||||
-fx-effect: dropshadow(gaussian , rgba(128,255,128,0.75) , 2,1.0,0,0);
|
||||
}
|
||||
|
||||
.uiSpeedSlider AnchorPane {
|
||||
-fx-padding: 0 5 0 5
|
||||
}
|
||||
|
||||
.uiSpeedSlider Slider {
|
||||
-fx-padding: 18 0 10 0
|
||||
}
|
||||
|
||||
.uiSpeedSlider Slider NumberAxis {
|
||||
-fx-tick-label-fill: #80ff80
|
||||
}
|
79
src/test/java/jace/scripting/TestCommandlineArgs.java
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2019 Brendan Robert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package jace.scripting;
|
||||
|
||||
import jace.Emulator;
|
||||
import jace.apple2e.Apple2e;
|
||||
import jace.apple2e.MOS65C02;
|
||||
import jace.apple2e.VideoNTSC;
|
||||
import jace.apple2e.VideoNTSC.VideoMode;
|
||||
import jace.core.Computer;
|
||||
import jace.core.RAM;
|
||||
import jace.core.SoundMixer;
|
||||
import jace.core.Utility;
|
||||
import java.util.Arrays;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Check out various command line arguments to see if they do the right thing
|
||||
*/
|
||||
public class TestCommandlineArgs {
|
||||
|
||||
static Computer computer;
|
||||
static MOS65C02 cpu;
|
||||
static RAM ram;
|
||||
|
||||
@BeforeClass
|
||||
public static void initJavaFX() {
|
||||
new JFXPanel();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Utility.setHeadlessMode(true);
|
||||
SoundMixer.MUTE = true;
|
||||
computer = new Apple2e();
|
||||
cpu = (MOS65C02) computer.getCpu();
|
||||
ram = computer.getMemory();
|
||||
Emulator.computer = (Apple2e) computer;
|
||||
computer.pause();
|
||||
cpu.suspend();
|
||||
cpu.clearState();
|
||||
}
|
||||
|
||||
static String GRAPHICS_MODE = "vid.videomode";
|
||||
|
||||
@Test
|
||||
public void testVideoModes() {
|
||||
for (VideoMode mode : VideoMode.values()) {
|
||||
Emulator.instance.applyConfiguration(Arrays.asList("-" + GRAPHICS_MODE, mode.name().toLowerCase()));
|
||||
assertTrue("Should be NTSC video module", computer.video instanceof VideoNTSC);
|
||||
VideoNTSC video = (VideoNTSC) computer.video;
|
||||
assertEquals("Should have switched to " + mode.name() + " mode", mode, video.getVideoMode());
|
||||
|
||||
Emulator.instance.applyConfiguration(Arrays.asList("-" + GRAPHICS_MODE, mode.name().toUpperCase()));
|
||||
assertTrue("Should be NTSC video module", computer.video instanceof VideoNTSC);
|
||||
video = (VideoNTSC) computer.video;
|
||||
assertEquals("Should have switched to " + mode.name() + " mode", mode, video.getVideoMode());
|
||||
}
|
||||
}
|
||||
}
|