Compare commits

...

28 Commits

Author SHA1 Message Date
kuki
d87210849f NOTHING 2022-08-23 10:54:26 +08:00
kuki
46b6c20458 rebase to my gitrepo 2022-08-23 09:44:26 +08:00
Brendan Robert
9674e59f1e
Merge pull request #39 from badvision/dependabot/maven/junit-junit-4.13.1
Bump junit from 4.10 to 4.13.1
2021-09-28 18:16:52 -05:00
dependabot[bot]
774f706f68
Bump junit from 4.10 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 4.10 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.10.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.10...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 01:37:17 +00:00
Brendan Robert
cc0cead894 Added better standalone testing for command line arguments. Got video mode to be a first-class startup parameter 2019-06-19 00:47:04 -05:00
Brendan Robert
553d439ff8
Merge pull request #36 from Michaelangel007/master
Fix spelling, and detect if maven isn't installed.
2018-11-29 12:52:49 -06:00
Brendan Robert
15be6e3436 Added accelerator support, also refined how child devices are tracked to better support a new abstraction for sound devices 2018-06-02 13:50:13 -05:00
Brendan Robert
8bd9ec1781 Wolfenstein cheats: Added "Day in the office" hack mode for cheap laughs 2018-05-26 01:46:31 -05:00
Brendan Robert
f7ca7c198c Wolfenstein cheat module updated to keep the big boss alive so you can drop him yourself, if you so choose. 2018-05-26 01:00:48 -05:00
Brendan Robert
1f2eff2e42 Added Beyond Wolfenstein cheats 2018-05-26 00:47:57 -05:00
Brendan Robert
d4073b9096 UI and Metacheat improvements, also new Wolfenstein trainer module! 2018-05-24 01:53:24 -05:00
Brendan Robert
9cee0cece9 Upstream fixes from Lawless Legends for keyboard focus and speed handling 2018-05-23 00:50:56 -05:00
Brendan Robert
142ee2df2a BEHOLD! The Resurrection of MetaCheat! 2018-05-23 00:38:22 -05:00
Brendan Robert
9118b83a43 Remove dead code 2018-05-23 00:38:02 -05:00
Michaelangel007
8525330d53 Test and Display if maven isn't installed 2018-05-22 22:24:48 -06:00
Michaelangel007
ddc41ec84e Fix spelling 2018-05-22 22:24:07 -06:00
Brendan Robert
eb776d44af Committing upstream changes from Lawless Legends to address mockingboard init issues. 2018-05-22 22:30:33 -05:00
Brendan Robert
ad9da99cb8
Update README.md 2018-01-15 15:20:50 -06:00
Brendan Robert
79c1ee825c
Update README.md 2018-01-15 15:19:19 -06:00
Brendan Robert
0d07d65b82
Update README.md 2018-01-15 15:17:42 -06:00
Brendan Robert
769d7f4302
Update README.md 2018-01-15 15:16:03 -06:00
Brendan Robert
d8ab357d84
Update README.md 2018-01-15 15:14:36 -06:00
Brendan Robert
ce9027cb6b
Update README.md 2018-01-15 15:05:32 -06:00
Brendan Robert
ede44af6d1
Update README.md 2018-01-15 15:04:24 -06:00
Brendan Robert
4425e8884d
Shaking the can...
(putting kids through college is a bigger challenge than I anticipated...)
2018-01-15 15:03:48 -06:00
Brendan Robert
0c0b2c107c Backported changes from Lawless Legends app experience; namely better boot behavior and also UI Controls Overlay. :) 2018-01-12 23:27:56 -06:00
Brendan Robert
dba57e6e89 Created application logo 2017-12-26 16:46:12 -06:00
Brendan Robert
09c1d78832 Fixed font missing issue when loading a packaged version of the application on OSX 2017-12-26 14:35:13 -06:00
57 changed files with 2613 additions and 1745 deletions

1
.gitignore vendored
View File

@ -20,3 +20,4 @@ hs_err_pid*
*.DS_Store
!/lib/nestedvm.jar
_acme_tmp*
/.idea

View File

@ -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.

View File

@ -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
View File

@ -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>

View File

@ -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++) {

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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";
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}
}
}
}

View File

@ -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) -> {

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);

View 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
*/
}

View File

@ -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);
});

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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++) {

View File

@ -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);
}

View File

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

View File

@ -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;

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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");

View 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;
}
}

View File

@ -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;
}
}

View File

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

View 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;
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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
}

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