JPSX is a fast and efficient PlayStation emulator written entirely in Java. You can view an old demo of its abilities and performance on YouTube. If it doesn't work at least that well on your computer for those games, there might be an issue with your runtime environment!
That said the code was written a long time ago, and I finally gave up trying to find time to get around to fixing a bunch of embarrassing or old things up and have just open sourced it as people frequently ask (albeit about 14 years later than planned).
The rest of these instructions are old, but you get the idea...
I just tried, and am able to run on my MacBook Pro with JDK8, and Ubuntu 18.04 with JDK8 and JDK11, and pretty well without sound on a Raspberry Pi 4 using JDK 11 (don't use JDK 8). Note I used OpenJDK 11
./unix.sh
(which launches you into console from where you have to press g
)
or ./unix.sh launch
which launches anyway; pass image=foo.cue
to run a particular game. I haven't tried on Windows, but it used to work on XP :-)
The biggest problem nowadays can be the speed of blit via AWT (this used to work fine years ago, and seems good again in JDK11, or in general on a Mac), however
for a while there I had to use ./unix.sh lwjgl
however this will probably not work on reent OSes - it is a very old version. For Raspberry Pi 4 pass no-sound
on
the command line as the machine name, although there are a few long pauses where the emu seems to freeze atm.
Now I have actually open sourced; I'll try to at least add some issues that need fixing or changes that I planned to make. Note that JPSX
will certainly play quite a lot of games correctly, but may fail miserably on others. I recommend passing
speculativeExecution=false
on the command line if your game doesn't work (e.g. Final Fantasy VII); speculative execution
should only really be needed on very slow machines, and breaks some code that uses overlays.
JPSX is very simple to build. Just run ant
, and it will build jar
files in the ship
folder.
To run JPSX in its default configuration, run the following command:
java -XX:-DontCompileHugeMethods -XX:-OmitStackTraceInFastThrow -jar ship/jpsx.jar image=path/to/game.cue
Note that JPSX does not have a GUI; you must launch it from the command line. Also, you need a PlayStation BIOS image named bios.bin
.
Right now CUE/BIN CD image files are the only image format supported (though it should be easy to add support for additional formats). Note that you can (and it is quite gratifying) use the CD player in the BIOS if you provide a CUE/BIN image of a music CD.
JPSX supports a bunch of configuration options in jpsx.xml
. Configuration options are encapsulated within a machine XML tag. You can specify a machine to use on the command line like so:
java -XX:-DontCompileHugeMethods -XX:-OmitStackTraceInFastThrow -jar ship/jpsx.jar [machine name] image=path/to/game.cue
**Once you launch JPSX, you need to type g
into the console to start the emulator. unless you use the launch
on the command line **
By default, JPSX uses the "default" machine defined in jpsx.xml
. Each machine lists the components that make it up, possible including (and optionally overriding) components from another machine definition. This makes it easy to tweak components; simply define a new machine with the properties you want.
For configuration options that use the lwjgl
display class, you need to use the following command line instead (note this probably no longer runs):
java -XX:-DontCompileHugeMethods -XX:-UseSplitVerifier -XX:-OmitStackTraceInFastThrow -Djava.library.path=external/lwjgl-2.9.1/native/[platformname] -cp ship/jpsx.jar:ship/jpsx-lwjgl.jar [machine name] image=path/to/game.cue
unix.sh
is provided as an example script for OS X and UNIX
For example, ./unix.sh image=/rips/gt.cue
will
set the image property used by the CUE/BIN CD drive to a specific file (note I think right not that the CUE must specify an absolute path to the BIN file).
For example, ./unix.sh lwjgl
will use the machine definition called lwjgl that uses LWJGLDisplay
in place of AWTDisplay
The following are required command line options for HotSpot, or things won't work properly/at all.
Option | Description |
---|---|
-XX:-DontCompileHugeMethods | Needed on everything otherwise things may be slow. Otherwise, HotSpot will refuse to compile some of the byte code that JPSX generates. |
-XX:-UseSplitVerifier | needed on JDK7; invalid after JDK8 (BCEL code gen isnt' supported by JVM on JDK7 otherwise) |
-XX:-OmitStackTraceInFastThrow | needed on newer JVMs which otherwise break JPSX by removing required line number information |
The default machine definition includes a Console
component... this is interactive sort of like gdb
. So to get stuff to run in this mode you need to enter g
for go. You can look at the code for Console to figure out some other commands. b
breaks for example
By default the pad is controlled by giving focus to the display window.
These are the default mappings for controller 1 copied from AWTKeyboardController
, which you can currently only change in the source:
Button | Key | Button | Key | |
---|---|---|---|---|
⬆ | ⬆ | △ | 8 or keypad ⬆ | |
➡ | ➡ | ⓞ | I or keypad ➡ | |
⬇ | ⬇ | ⓧ | K or keypad ⬇ | |
⬅ | ⬅ | □ | U or keypad ⬅ | |
L1 | 1 | R1 | 2 | |
L2 | Q | R2 | W | |
Select | S | Start | Space |
The current displays also support a few keys (don't forget fn on OS X).
- F12: Change window size (picks from a preset list of resolutions).
- F9: Toggle display of all VRAM - this is kinda cool.
I wrote it back in 2003 basically just because it was exactly the sort of thing people were saying Java was too slow for. I had written a C/C++ emu in the late 1990s that I never made publicly available (though some of the code found a home), so I had already done a bunch of the reverse engineering work.
I have actually done very little to it over time, other than periodically trying it on new JVMs (it should work on anything JDK1.4+
Since I was writing it in Java, I thought it'd be nice to make it do it in an object oriented style. Therefore there are well encapsulated classes representing the different physical hardware and internal emulation components.
That said, this was written back with the HotSpot client compiler in mind (the server compiler caused way too many compilation pauses back then for game use), so generally I always picked runtime speed over pretty code.
There are all kinds of optimizations in there (you can read about some of it here). Some still make sense, but others are no longer necessary on modern versions of HotSpot. Those which are stupid on a modern JVM will probably be removed, though equally I expect that this codebase is likely to be used on lower spec Java platforms in the future where a lot of this stuff will still matter, so I don't expect to rip it all out if it doesn't actively hurt.
At least at the time... there seemed to be a lot of focus in emulators about cycle counting. This certainly makes sense in a lot of cases especially on older platforms where, for example, the program needed to know where the CRT electron beam was when a cycle executed, or it needed to write a sound sample out at an exact moment.
The PSX appeared to be a new breed of gaming machine, with a more mature set of components loosely coupled (via IRQ and DMA) which otherwise carried on largely independently. For that reason I decided to emulate each component largely independently too. Sometimes in separate threads, sometimes synced to external timing events, but coupled via Java synchronization. As such, for example, the R3000 CPU emulation (well more accurately R300 code is transcoded/recompiled/reoptimized into bytecode) runs as fast as it can and then blocks.
This turns out to work remarkably well. Of course, JPSX has to detect busy wait loops so as not to hog (busy wait on) the native CPU, and because the PSX level loop timeouts are WAY too short for native hardware.
With that said, there are badly written bits of PSX code with race conditions which need to be patched or worked around, and some perhaps badly designed hardware (I spent a LONG time trying to figure out a robust interaction between the CPU thread and CD controller which it seems may never have existed). Despite this, unless it proves impossible, I'd like JPSX to continue with the same basic design of largely independent components, as this particular design is the reason why JPSX is lean and efficient.
JPSX is built entirely of pluggable components. If you look at jpsx.xml
, you'll see the default definitions that I use.
The XML format is just a (in)convenience - that might be one of the first things to go now this is open sourced.
This is parsed into a MachineDefintion internally (see MachineDefinition.java
)
Although any part of the emulator can be replaced, it makes less sense to do so with some parts than others; I have three main groupings:
- core: internal stuff like address space (that you probably don't want to swap out) - this certainly needs some work in places, but I doubt we'll end up with different implementations
- hardware: implementations of emulated hardware that you might want to swap out
- emulator: implementations of emulator components such as the recompiler
While the emu doesn't force any particular interfaces on the implementation, obviously for different pieces of the emulator to work together they have to know how to talk to each other. This is done by well known (to each other) named connections (basically an interface), and these happen to live in the API tree.
An example connection is Display
which is used by the current software GPU (which could of course be replaced). The Display implementation provides an int[]
(basically RGB32) for the GPU to draw into, and the Display is only responsible for displaying the correct portion of VRAM to the user when told to.
Similarly there are some abstractions for serial ports and devices, CD sector providers etc.
todo
Note here is a 2006 JavaOne talk I gave on JPSX: JPSX JavaOne Talk
Thanks to John Vilk https://github.com/jvilk for his help in getting me closer to open sourcing a few years back!