Skip to content

Commit f9c19ba

Browse files
committed
Add new routines to query game devices and poll from them.
Implement comparator to joystick voltages. Fix reversed controls, add CA2 auto-transition when bit 4 is zero. Add ability to set joystick via config file
1 parent 43a7222 commit f9c19ba

File tree

12 files changed

+556
-33
lines changed

12 files changed

+556
-33
lines changed

README.md

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,24 @@
99

1010
1. [What Is It?](#what-is-it)
1111
2. [License](#license)
12-
3. [Compiling](#compiling)
13-
4. [Running](#running)
12+
3. [Requirements](#requirements)
13+
1. [Linux](#linux)
14+
2. [Windows](#windows)
15+
4. [Compiling From Source](#compiling-from-source)
16+
5. [Running](#running)
1417
1. [Specifying a System ROM](#specifying-a-system-rom)
1518
2. [Trace Mode](#trace-mode)
16-
5. [Cassette Tapes](#cassette-tapes)
19+
6. [Cassette Tapes](#cassette-tapes)
1720
1. [Reading](#reading)
1821
2. [Writing](#writing)
19-
6. [Disk Drives](#disk-drives)
22+
7. [Disk Drives](#disk-drives)
2023
1. [Loading a Disk Image](#loading-a-disk-image)
2124
2. [Saving a Disk Image](#saving-a-disk-image)
22-
7. [Configuration File](#configuration-file)
23-
8. [Keyboard](#keyboard)
25+
8. [Configuration File](#configuration-file)
26+
9. [Keyboard](#keyboard)
2427
1. [Emulated Keyboard](#emulated-keyboard)
2528
2. [Pass-through Keyboard](#pass-through-keyboard)
26-
9. [Current Status](#current-status)
29+
10. [Current Status](#current-status)
2730

2831
## What Is It?
2932

@@ -53,13 +56,87 @@ Third Party Licenses and Attributions below for more information on those
5356
software components.
5457

5558

56-
## Compiling
59+
## Requirements
5760

58-
You will need a copy of the Java Development Kit (JDK) version 8 or greater
59-
installed in to compile the JAR file. I strongly recommend using an open-source
60-
licensed JDK build (GPL v2 with Classpath Exception), available at
61-
[https://adoptopenjdk.net](https://adoptopenjdk.net) and install OpenJDK 8 or
62-
OpenJDK 11.
61+
The project needs several different packages installed in order to run the
62+
emulator properly. Please see the platform specific steps below for
63+
more information.
64+
65+
### Linux
66+
67+
At a minimum, you will need to install the Java Runtime Environment (JRE) 17 or
68+
higher. Additionally, if you wish to use joysticks, you will need to install the
69+
`libjinput` API bindings, add your username to the `group` file, as well as fix a
70+
potential API binding bug.
71+
72+
1. *Required* - a Java Runtime Environment (JRE) version 17 or higher. The simplest way to
73+
do this is to install OpenJDK 17 or higher. On Ubuntu or Debian systems, this can
74+
be done with :
75+
76+
```bash
77+
sudo apt update
78+
sudo apt install openjdk-17-jre
79+
```
80+
81+
2. *Optional* - for proper joystick support you will need to install the `jinput` joystick
82+
library API on the system with:
83+
84+
```bash
85+
sudo apt install libjinput-java libjinput-jni
86+
```
87+
88+
Once installed, you may need to correct a missing file problem with the `libjinput-jni`
89+
installation. The emulator dependencies require a shared object file called
90+
`libjinput-linux64.so` to be present in the `/usr/lib/jni` directory. However, the
91+
`libjinput-jni` package may only install a file called `libjinput.so`. The
92+
solution is to create a symbolic link from `libjinput.so` to `linjinput-linux64.so`.
93+
First, check to see if the `libjinput-linux64.so` file already exists with:
94+
95+
```bash
96+
ls -l /usr/lib/jni
97+
```
98+
99+
If the `libjinput-linux64.so` file is *NOT* listed, you will need to create a symbolic
100+
link with the following command:
101+
102+
```bash
103+
sudo ln -s /usr/lib/jni/libjinput.so /usr/lib/jni/libjinput-linux64.so
104+
```
105+
106+
Finally, you will need to add yourself to the `input` group so that the emulator can read
107+
joystick information:
108+
109+
```bash
110+
sudo usermod -a -G input <your username>
111+
```
112+
113+
You will need to end your current session and restart in order for the group information
114+
to be updated. In some cases, you may need to reboot for the group change to take effect.
115+
116+
### Windows
117+
118+
_Under construction._
119+
120+
## Compiling From Source
121+
122+
_Note this section is optional - this is only if you want to compile the project
123+
yourself from source code._
124+
125+
If you want to build the emulator from source code, you will need a copy of the
126+
Java Development Kit (JDK) version 17 or greater
127+
installed in to compile the JAR file. For most Linux distributions
128+
there is likely an `openjdk-17-jdk` package that will do this for you automatically.
129+
On Ubuntu and Debian based systems, this is typically:
130+
131+
```bash
132+
sudo apt update
133+
sudo apt install openjdk-17-jdk
134+
```
135+
136+
For Windows, I recommend using Eclipse Temurin (formerly AdoptJDK) as the software
137+
is licensed under the GNU license version 2 with classpath exception. The latest
138+
JDK builds are available at [https://adoptium.net/en-GB/temurin/releases](https://adoptium.net/en-GB/temurin/releases)
139+
(make sure you select _JDK_ as the type you wish to download).
63140

64141
To build the project, switch to the root of the source directory, and type:
65142

@@ -73,16 +150,14 @@ On Windows, switch to the root of the source directory, and type:
73150
gradlew.bat build
74151
```
75152

76-
The compiled Jar file will be placed in the `build/libs` directory.
153+
The compiled Jar file will be placed in the `build/libs` directory. Note that
154+
for some components such as joystick detection and control to work correctly,
155+
operating-specific steps may be required. See the _Requirements_ section above
156+
to install necessary sub-systems.
77157

78158

79159
## Running
80160

81-
For the emulator to run, you will need to have the Java 8 Runtime Environment (JRE)
82-
installed on your computer. See [Oracle's Java SE Runtime Environment Download](https://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html)
83-
page for more information on installing the JRE. Alternatively, you can
84-
use [OpenJDK](https://openjdk.java.net/install/).
85-
86161
Simply double-clicking the jar file will start the emulator running. By
87162
default, the emulator will be in paused mode until you attach a system
88163
ROM to it. You can do so by clicking *ROM*, *Load System ROM*. You can

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ dependencies {
3232
implementation 'org.apache.commons:commons-lang3:3.20.0'
3333
implementation 'com.beust:jcommander:1.82'
3434
implementation 'org.yaml:snakeyaml:2.5'
35+
implementation 'net.java.jinput:jinput:2.0.10'
36+
runtimeOnly 'net.java.jinput:jinput-platform:2.0.7:natives-linux'
37+
implementation 'net.java.jutils:jutils:1.0.0'
3538
testImplementation 'junit:junit:4.13.2'
3639
testImplementation 'org.mockito:mockito-core:5.20.0'
3740
}

src/main/java/ca/craigthomas/yacoco3e/components/Emulator.java

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66

77
import ca.craigthomas.yacoco3e.datatypes.*;
88
import ca.craigthomas.yacoco3e.listeners.*;
9+
import net.java.games.input.Controller;
10+
import net.java.games.input.ControllerEnvironment;
911

1012
import javax.swing.*;
1113
import java.awt.*;
1214
import java.awt.event.KeyEvent;
1315
import java.time.Duration;
1416
import java.time.Instant;
1517
import java.time.temporal.ChronoUnit;
18+
import java.util.ArrayList;
1619
import java.util.TimerTask;
1720
import java.util.Timer;
1821
import java.util.logging.Logger;
@@ -31,6 +34,8 @@ public class Emulator extends Thread
3134
private IOController io;
3235
private Cassette cassette;
3336
private Memory memory;
37+
private int leftJoystickNumber;
38+
private int rightJoystickNumber;
3439

3540
// The Canvas on which all the drawing will take place
3641
private Canvas canvas;
@@ -39,6 +44,8 @@ public class Emulator extends Thread
3944
private JFrame container;
4045
private JMenuBar menuBar;
4146

47+
Controller [] controllers;
48+
4249
/* State variables */
4350
public boolean trace;
4451
private boolean verbose;
@@ -117,6 +124,7 @@ private Emulator(Builder builder) {
117124
io = new IOController(memory, new RegisterSet(), keyboard, screen, cassette, builder.useDAC);
118125
cpu = new CPU(io);
119126
io.setCPU(cpu);
127+
120128
trace = builder.trace;
121129
verbose = builder.verbose;
122130
status = EmulatorStatus.STOPPED;
@@ -129,11 +137,9 @@ private Emulator(Builder builder) {
129137
}
130138
}
131139
} catch (Exception e) {
132-
System.out.println("Nimbus LAF not available");
140+
LOGGER.warning("Nimbus LAF not available");
133141
}
134142

135-
initEmulatorJFrame();
136-
137143
// Check to see if we specified a configuration file
138144
ConfigFile builderConfig = ConfigFile.parseConfigFile(builder.configFile);
139145

@@ -142,6 +148,9 @@ private Emulator(Builder builder) {
142148

143149
// Attempt to load the assets for the emulator
144150
loadAssets(builderConfig, commandLineConfig);
151+
152+
// Initialize the main emulator JFrame
153+
initEmulatorJFrame();
145154
}
146155

147156
/**
@@ -197,13 +206,37 @@ public void loadFromConfigFile(ConfigFile config) {
197206
}
198207
}
199208

209+
// Load drive images
200210
String drive0 = config.getDrive0Image();
201211
if (drive0 != null) {
202212
JV1Disk disk = new JV1Disk();
203213
if (disk.loadFile(drive0)) {
204214
io.disk[0].loadFromVirtualDisk(disk);
205215
}
206216
}
217+
218+
// Enumerate joystick controllers and set current joystick types
219+
controllers = ControllerEnvironment.getDefaultEnvironment().getControllers();
220+
LOGGER.info("Joystick #0, Name: 'No Joystick', Type: None");
221+
leftJoystickNumber = 0;
222+
rightJoystickNumber = 0;
223+
int index = 1;
224+
for (Controller controller : controllers) {
225+
Controller.Type controllerType = controller.getType();
226+
if (controllerType != Controller.Type.MOUSE && controllerType != Controller.Type.KEYBOARD) {
227+
LOGGER.info("Joystick #" + index + ", Name: '" + controller.getName() + "', Type: " + controller.getType());
228+
if (controller.getName().stripTrailing().equals(config.getLeftJoystick())) {
229+
leftJoystickNumber = index;
230+
}
231+
if (controller.getName().stripTrailing().equals(config.getRightJoystick())) {
232+
rightJoystickNumber = index;
233+
}
234+
index++;
235+
}
236+
}
237+
io.setJoystickControllers(controllers);
238+
io.setLeftJoystickNumber(leftJoystickNumber);
239+
io.setRightJoystickNumber(rightJoystickNumber);
207240
}
208241

209242
/**
@@ -344,6 +377,74 @@ private void initEmulatorJFrame() {
344377

345378
menuBar.add(keyboardMenu);
346379

380+
// Joystick menu
381+
JMenu joystickMenu = new JMenu("Joystick");
382+
joystickMenu.setMnemonic(KeyEvent.VK_J);
383+
384+
JMenu leftJoystickMenuItem = new JMenu("Left Joystick");
385+
leftJoystickMenuItem.setMnemonic(KeyEvent.VK_L);
386+
387+
JRadioButtonMenuItem leftJoystickNoneMenuItem = new JRadioButtonMenuItem("No Joystick");
388+
leftJoystickNoneMenuItem.setSelected(leftJoystickNumber == 0);
389+
leftJoystickMenuItem.add(leftJoystickNoneMenuItem);
390+
391+
// Enumerate all joystick devices and add menu items for each
392+
ArrayList<JRadioButtonMenuItem> options = new ArrayList<>();
393+
options.add(leftJoystickNoneMenuItem);
394+
395+
// Loop through all the joystick controllers and generate menu items for each
396+
int index = 1;
397+
for (Controller controller : controllers) {
398+
Controller.Type controllerType = controller.getType();
399+
if (controllerType != Controller.Type.MOUSE && controllerType != Controller.Type.KEYBOARD) {
400+
JRadioButtonMenuItem leftJoystickControllerMenuItem = new JRadioButtonMenuItem(controller.getName());
401+
leftJoystickControllerMenuItem.setSelected(leftJoystickNumber == index);
402+
leftJoystickMenuItem.add(leftJoystickControllerMenuItem);
403+
options.add(leftJoystickControllerMenuItem);
404+
index++;
405+
}
406+
}
407+
408+
// Reset the joystick index and add the list of joystick menu items to the menu
409+
index = 0;
410+
for (JRadioButtonMenuItem button : options) {
411+
button.addActionListener(new SetLeftJoystickMenuItemActionListener(this, options.toArray(new JRadioButtonMenuItem[0]), index));
412+
index++;
413+
}
414+
415+
joystickMenu.add(leftJoystickMenuItem);
416+
417+
JMenu rightJoystickMenuItem = new JMenu("Right Joystick");
418+
rightJoystickMenuItem.setMnemonic(KeyEvent.VK_R);
419+
420+
JRadioButtonMenuItem rightJoystickNoneMenuItem = new JRadioButtonMenuItem("No Joystick");
421+
rightJoystickNoneMenuItem.setSelected(rightJoystickNumber == 0);
422+
rightJoystickMenuItem.add(rightJoystickNoneMenuItem);
423+
424+
options = new ArrayList<>();
425+
options.add(rightJoystickNoneMenuItem);
426+
427+
index = 1;
428+
for (Controller controller : controllers) {
429+
Controller.Type controllerType = controller.getType();
430+
if (controllerType != Controller.Type.MOUSE && controllerType != Controller.Type.KEYBOARD) {
431+
JRadioButtonMenuItem rightJoystickControllerMenuItem = new JRadioButtonMenuItem(controller.getName());
432+
rightJoystickControllerMenuItem.setSelected(rightJoystickNumber == index);
433+
rightJoystickMenuItem.add(rightJoystickControllerMenuItem);
434+
options.add(rightJoystickControllerMenuItem);
435+
}
436+
}
437+
438+
// Reset the joystick index and add the list of joystick menu items to the menu
439+
index = 0;
440+
for (JRadioButtonMenuItem button : options) {
441+
button.addActionListener(new SetRightJoystickMenuItemActionListener(this, options.toArray(new JRadioButtonMenuItem[0]), index));
442+
index++;
443+
}
444+
445+
joystickMenu.add(rightJoystickMenuItem);
446+
menuBar.add(joystickMenu);
447+
347448
// Debug menu
348449
JMenu debugMenu = new JMenu("Debugging");
349450
debugMenu.setMnemonic(KeyEvent.VK_U);
@@ -403,6 +504,24 @@ public void switchKeyListener(Keyboard newKeyboard) {
403504
io.setKeyboard(keyboard);
404505
}
405506

507+
/**
508+
* Switches out the existing left joystick for a new one.
509+
*
510+
* @param newJoystickNumber the new joystick device number to use (0 for none)
511+
*/
512+
public void switchLeftJoystick(int newJoystickNumber) {
513+
io.setLeftJoystickNumber(newJoystickNumber);
514+
}
515+
516+
/**
517+
* Switches out the existing right joystick for a new one.
518+
*
519+
* @param newJoystickNumber the new joystick device number to use (0 for none)
520+
*/
521+
public void switchRightJoystick(int newJoystickNumber) {
522+
io.setRightJoystickNumber(newJoystickNumber);
523+
}
524+
406525
/**
407526
* Will redraw the contents of the screen to the emulator window. Optionally, if
408527
* isInTraceMode is True, will also draw the contents of the overlayScreen to the screen.
@@ -430,6 +549,7 @@ public void start() {
430549
screenRefreshTimer = new Timer();
431550
screenRefreshTimerTask = new TimerTask() {
432551
public void run() {
552+
io.pollJoysticks();
433553
screen.refreshScreen();
434554
refreshScreen();
435555
}

0 commit comments

Comments
 (0)