/*
JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
Release Version 2.4
A project from the Physics Dept, The University of Oxford
Copyright (C) 2007-2010 The University of Oxford
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as published by
the Free Software Foundation.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Details (including contact information) can be found at:
jpc.sourceforge.net
or the developer website
sourceforge.net/projects/jpc/
Conceived and Developed by:
Rhys Newman, Ian Preston, Chris Dennis
End of licence header
*/
package org.jpc.j2se;
import java.awt.Component;
import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.*;
import java.util.logging.*;
import java.util.zip.*;
import javax.swing.*;
import org.jpc.emulator.PC;
import org.jpc.emulator.pci.peripheral.EthernetCard;
import org.jpc.emulator.pci.peripheral.VGACard;
import org.jpc.support.*;
public class JPCApplication extends PCMonitorFrame implements PCControl
{
private static final Logger LOGGING = Logger.getLogger(JPCApplication.class.getName());
private static final URI JPC_URI = URI.create("http://jpc.sourceforge.net/");
private static final String IMAGES_PATH = "resources/images/";
private static final int MONITOR_WIDTH = 720;
private static final int MONITOR_HEIGHT = 400 + 100;
private static final String[] DEFAULT_ARGS =
{
"-fda", "mem:resources/images/floppy.img",
"-hda", "mem:resources/images/dosgames.img",
"-boot", "fda"
};
private static final String ABOUT_US =
"JPC: Developed since August 2005 in Oxford University's Subdepartment of Particle Physics, and subsequently rewritten by Ian Preston.\n\n" +
"For more information visit our website at:\n" + JPC_URI.toASCIIString();
private static final String LICENCE_HTML =
"JPC is released under GPL Version 2 and comes with absolutely no warranty<br/><br/>" +
"See " + JPC_URI.toASCIIString() + " for more details";
private static JEditorPane LICENCE;
static
{
ClassLoader context = Thread.currentThread().getContextClassLoader();
URL licence = context.getResource("resources/licence.html");
if (licence != null)
{
try
{
LICENCE = new JEditorPane(licence);
} catch (IOException e)
{
LICENCE = new JEditorPane("text/html", LICENCE_HTML);
}
} else
{
LICENCE = new JEditorPane("text/html", LICENCE_HTML);
}
LICENCE.setEditable(false);
}
private KeyTypingPanel keys;
private JFileChooser diskImageChooser;
private JFileChooser snapshotFileChooser;
public JPCApplication(String[] args, PC pc) throws Exception
{
super("JPC - " + ArgProcessor.findVariable(args, "hda", null), pc, args);
diskImageChooser = new JFileChooser(System.getProperty("user.dir"));
snapshotFileChooser = new JFileChooser(System.getProperty("user.dir"));
String snapShot = ArgProcessor.findVariable(args, "ss", null);
if (snapShot != null)
loadSnapshot(new File(snapShot));
JMenuBar bar = getJMenuBar();
JMenu snap = new JMenu("Snapshot");
snap.add("Save Snapshot").addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ev)
{
stop();
if (snapshotFileChooser.showDialog(JPCApplication.this, "Save JPC Snapshot") == JFileChooser.APPROVE_OPTION)
{
try
{
saveSnapshot(snapshotFileChooser.getSelectedFile());
}
catch (IOException e)
{
LOGGING.log(Level.WARNING, "Exception saving snapshot.", e);
}
}
start();
}
});
snap.add("Load Snapshot").addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ev)
{
int cancel = JOptionPane.showOptionDialog(JPCApplication.this, "Selecting a snapshot now will discard the current state of the emulated PC. Are you sure you want to continue?", "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[]
{
"Continue", "Cancel"
}, "Continue");
if (cancel == 0)
{
stop();
if (snapshotFileChooser.showDialog(JPCApplication.this, "Load Snapshot") == JFileChooser.APPROVE_OPTION)
{
try
{
loadSnapshot(snapshotFileChooser.getSelectedFile());
} catch (IOException e)
{
LOGGING.log(Level.SEVERE, "Exception during snapshot load", e);
}
}
monitor.revalidate();
monitor.requestFocus();
}
}
});
bar.add(snap);
DriveSet drives = (DriveSet) pc.getComponent(DriveSet.class);
JMenu disks = new JMenu("Disks");
disks.add("Create disk").addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ev)
{
createBlankDisk();
}
});
disks.add("Create disk from directory").addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ev)
{
createDiskFromDirectory();
}
});
for (int i = 0; i < 2; i++)
{
BlockDevice drive = drives.getFloppyDrive(i);
JMenu top = new JMenu();
if (drive == null)
top.setText("FD" + i + " [none]");
else
top.setText("FD" + i + " " + drive.toString());
JMenu included = new JMenu("Included Images...");
JMenuItem file = new JMenuItem("Choose Image...");
ActionListener handler = new FloppyDriveChangeHandler(i, top, included, file);
Iterator<String> itt = getResources(IMAGES_PATH);
while (itt.hasNext())
{
String path = (String) itt.next();
if (path.startsWith(IMAGES_PATH))
path = path.substring(IMAGES_PATH.length());
included.add(path).addActionListener(handler);
}
top.add(included);
top.add(file).addActionListener(handler);
disks.add(top);
}
for (int i = 0; i < 4; i++)
{
BlockDevice drive = drives.getHardDrive(i);
JMenu top = new JMenu();
if (drive == null)
top.setText("HD" + i + " [none]");
else
top.setText("HD" + i + " " + drive.toString());
JMenu included = new JMenu("Included Images...");
JMenuItem file = new JMenuItem("Choose Image...");
JMenuItem directory = new JMenuItem("Choose Directory...");
ActionListener handler = new HardDriveChangeHandler(i, top, included, file, directory);
Iterator<String> itt = getResources(IMAGES_PATH);
while (itt.hasNext())
{
String path = (String) itt.next();
if (path.startsWith(IMAGES_PATH))
path = path.substring(IMAGES_PATH.length());
included.add(path).addActionListener(handler);
}
top.add(included);
top.add(file).addActionListener(handler);
top.add(directory).addActionListener(handler);
disks.add(top);
}
bar.add(disks);
JMenu help = new JMenu("Help");
help.add("Getting Started").addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
JFrame help = new JFrame("JPC - Getting Started");
help.setIconImage(Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemResource("resources/icon.png")));
help.getContentPane().add("Center", new JScrollPane(LICENCE));
help.setBounds(300, 200, MONITOR_WIDTH + 20, MONITOR_HEIGHT - 70);
help.setVisible(true);
getContentPane().validate();
getContentPane().repaint();
}
});
help.add("About JPC").addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
Object[] buttons =
{
"Visit our Website", "Ok"
};
if (JOptionPane.showOptionDialog(JPCApplication.this, ABOUT_US, "About JPC", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, null, buttons, buttons[1]) == 0)
{
if (Desktop.isDesktopSupported())
{
try
{
Desktop.getDesktop().browse(JPC_URI);
} catch (IOException e)
{
LOGGING.log(Level.INFO, "Couldn't find or launch the default browser.", e);
} catch (UnsupportedOperationException e)
{
LOGGING.log(Level.INFO, "Browse action not supported.", e);
} catch (SecurityException e)
{
LOGGING.log(Level.INFO, "Browse action not permitted.", e);
}
}
}
}
});
bar.add(help);
keys = new KeyTypingPanel(monitor);
JPCApplet.PlayPausePanel pp = new JPCApplet.PlayPausePanel(this);
JPanel p1 = new JPanel(new BorderLayout(10, 10));
p1.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
p1.add("Center", keys);
p1.add("East", pp);
//getContentPane().add("South", p1);
setSize(monitor.getPreferredSize());
LICENCE.setPreferredSize(monitor.getPreferredSize());
getMonitorPane().setViewportView(LICENCE);
getContentPane().validate();
this.getRootPane().addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent evt) {
Component c = (Component)evt.getSource();
Dimension newSize = c.getSize();
int width = (int)newSize.getWidth();
int height = (int)newSize.getHeight();
//System.out.println("new width="+width+" new height="+height);
monitor.scaleDisplay(width, height);
}
});
}
public void setSize(Dimension d)
{
super.setSize(new Dimension(monitor.getPreferredSize().width, d.height + keys.getPreferredSize().height + 60));
getMonitorPane().setPreferredSize(new Dimension(monitor.getPreferredSize().width + 2, monitor.getPreferredSize().height + 2));
}
public synchronized void start()
{
super.start();
getMonitorPane().setViewportView(monitor);
monitor.validate();
monitor.requestFocus();
}
public synchronized void stop()
{
super.stop();
}
public synchronized boolean isRunning()
{
return super.isRunning();
}
private void loadSnapshot(File file) throws IOException
{
ZipInputStream zin = new ZipInputStream(new FileInputStream(file));
zin.getNextEntry();
pc.loadState(zin);
zin.closeEntry();
VGACard card = ((VGACard) pc.getComponent(VGACard.class));
card.setOriginalDisplaySize();
zin.getNextEntry();
monitor.loadState(zin);
zin.closeEntry();
zin.close();
}
private void saveSnapshot(File file) throws IOException
{
ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file));
zip.putNextEntry(new ZipEntry("pc"));
pc.saveState(zip);
zip.closeEntry();
zip.putNextEntry(new ZipEntry("monitor"));
monitor.saveState(zip);
zip.closeEntry();
zip.finish();
zip.close();
}
private void createBlankDisk()
{
try {
JFileChooser chooser = diskImageChooser;
if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) {
return;
}
String sizeString = JOptionPane.showInputDialog(this, "Enter the size in MB for the disk", "Disk Image Creation", JOptionPane.QUESTION_MESSAGE);
if (sizeString == null) {
return;
}
long size = Long.parseLong(sizeString) * 1024l * 1024l;
if (size < 0) {
throw new Exception("Negative file size");
}
RandomAccessFile f = new RandomAccessFile(chooser.getSelectedFile(), "rw");
f.setLength(size);
f.close();
} catch (Exception e) {
JOptionPane.showMessageDialog(rootPane, "Failed to create blank disk " + e, "Create Disk", JOptionPane.ERROR_MESSAGE);
}
}
private void createDiskFromDirectory()
{
try {
JFileChooser chooser = diskImageChooser;
if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) {
return;
}
// select directory to make drive from
JFileChooser directory = new JFileChooser(System.getProperty("user.dir"));
directory.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (directory.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) {
return;
}
File out = chooser.getSelectedFile();
File root = directory.getSelectedFile();
if (!out.exists())
out.createNewFile();
TreeBlockDevice tbd = new TreeBlockDevice(root, true);
DataOutput dataout = new DataOutputStream(new FileOutputStream(out));
tbd.writeImage(dataout);
System.out.println("Done saving disk image");
} catch (Exception e) {
JOptionPane.showMessageDialog(rootPane, "Failed to create disk from directory" + e, "Create Disk", JOptionPane.ERROR_MESSAGE);
}
}
private class FloppyDriveChangeHandler implements ActionListener
{
private int driveIndex;
private JMenu included;
private JMenuItem file;
private JMenuItem top;
public FloppyDriveChangeHandler(int index, JMenuItem top, JMenu included, JMenuItem file)
{
driveIndex = index;
this.included = included;
this.file = file;
this.top = top;
}
public void actionPerformed(ActionEvent e)
{
Component source = (Component) e.getSource();
if (included.isMenuComponent(source))
change(IMAGES_PATH + ((JMenuItem) source).getText());
else if (source == file)
{
int result = diskImageChooser.showDialog(JPCApplication.this, "Load FD" + driveIndex + " Image");
if (result != JFileChooser.APPROVE_OPTION)
return;
change(diskImageChooser.getSelectedFile());
}
}
private void change(File image)
{
try
{
change(new FileBackedSeekableIODevice(image.getAbsolutePath()));
}
catch (IOException e)
{
LOGGING.log(Level.INFO, "Exception changing floppy disk.", e);
}
}
private void change(String resource)
{
try
{
change(new ArrayBackedSeekableIODevice(resource));
}
catch (IOException e)
{
LOGGING.log(Level.INFO, "Exception changing floppy disk.", e);
}
}
private void change(SeekableIODevice device)
{
BlockDevice bd = new FloppyBlockDevice(device);
pc.changeFloppyDisk(bd, driveIndex);
top.setText("FD" + driveIndex + " " + bd.toString());
}
}
private class HardDriveChangeHandler implements ActionListener
{
private int driveIndex;
private JMenu included;
private JMenuItem file;
private JMenuItem directory;
private JMenu top;
public HardDriveChangeHandler(int index, JMenu top, JMenu included, JMenuItem file, JMenuItem directory)
{
driveIndex = index;
this.included = included;
this.file = file;
this.directory = directory;
this.top = top;
}
public void actionPerformed(ActionEvent e)
{
Component source = (Component) e.getSource();
if (included.isMenuComponent(source))
{
stop();
change(IMAGES_PATH + ((JMenuItem) source).getText());
reset();
monitor.revalidate();
monitor.requestFocus();
}
else if (source == file)
{
stop();
diskImageChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
int result = diskImageChooser.showDialog(JPCApplication.this, "Load HD" + driveIndex + " Image");
if (result != JFileChooser.APPROVE_OPTION)
return;
changeFile(diskImageChooser.getSelectedFile());
reset();
monitor.revalidate();
monitor.requestFocus();
}
else if (source == directory)
{
stop();
diskImageChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int result = diskImageChooser.showDialog(JPCApplication.this, "Select HD" + driveIndex + " Directory");
if (result != JFileChooser.APPROVE_OPTION)
return;
changeDirectory(diskImageChooser.getSelectedFile());
reset();
monitor.revalidate();
monitor.requestFocus();
}
}
private void changeFile(File image)
{
try
{
SeekableIODevice ioDevice = new FileBackedSeekableIODevice(image.getAbsolutePath());
change(new HDBlockDevice(ioDevice));
}
catch (IOException e)
{
LOGGING.log(Level.INFO, "Exception changing floppy disk.", e);
}
}
private void changeDirectory(File directory)
{
try
{
change(new TreeBlockDevice(directory, false));
}
catch (IOException e)
{
LOGGING.log(Level.INFO, "Exception changing floppy disk.", e);
}
}
private void change(String resource)
{
try
{
SeekableIODevice ioDevice = new ArrayBackedSeekableIODevice(resource);
change(new HDBlockDevice(ioDevice));
}
catch (IOException e)
{
LOGGING.log(Level.INFO, "Exception changing floppy disk.", e);
}
}
private void change(BlockDevice device)
{
DriveSet drives = (DriveSet) pc.getComponent(DriveSet.class);
drives.setHardDrive(driveIndex, device);
top.setText("HD" + driveIndex + " " + device.toString());
}
}
private static final Iterator<String> getResources(String directory)
{
ClassLoader context = Thread.currentThread().getContextClassLoader();
List<String> resources = new ArrayList<String>();
ClassLoader cl = JPCApplication.class.getClassLoader();
if (!(cl instanceof URLClassLoader))
throw new IllegalStateException();
URL[] urls = ((URLClassLoader) cl).getURLs();
int slash = directory.lastIndexOf("/");
String dir = directory.substring(0, slash + 1);
for (int i=0; i<urls.length; i++)
{
if (!urls[i].toString().endsWith(".jar"))
continue;
try
{
JarInputStream jarStream = new JarInputStream(urls[i].openStream());
while (true)
{
ZipEntry entry = jarStream.getNextEntry();
if (entry == null)
break;
if (entry.isDirectory())
continue;
String name = entry.getName();
slash = name.lastIndexOf("/");
String thisDir = "";
if (slash >= 0)
thisDir = name.substring(0, slash + 1);
if (!dir.equals(thisDir))
continue;
resources.add(name);
}
jarStream.close();
}
catch (IOException e) { e.printStackTrace();}
}
InputStream stream = context.getResourceAsStream(directory);
try
{
if (stream != null)
{
Reader r = new InputStreamReader(stream);
StringBuilder sb = new StringBuilder();
char[] buffer = new char[1024];
try
{
while (true)
{
int length = r.read(buffer);
if (length < 0)
{
break;
}
sb.append(buffer, 0, length);
}
} finally
{
r.close();
}
for (String s : sb.toString().split("\n"))
{
if (context.getResource(directory + s) != null)
{
resources.add(s);
}
}
}
}
catch (IOException e)
{
LOGGING.log(Level.INFO, "Exception reading images directory stream", e);
}
return resources.iterator();
}
public static void main(String[] args) throws Exception
{
if ((args.length > 0) && args[0].equals("-disam"))
{
boolean is32Bit = args[1].equals("32");
byte[] m = new byte[args.length-2];
for (int i=0; i < m.length; i++)
m[i] = (byte) Integer.parseInt(args[i+2], 16);
System.out.println(PC.disam(m, 1, is32Bit));
return;
}
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e)
{
LOGGING.log(Level.INFO, "System Look-and-Feel not loaded", e);
}
args = Option.parse(args);
if (Option.help.isSet())
{
Option.printHelp();
System.exit(0);
}
if ((args.length == 0) && (!Option.boot.isSet() && !Option.hda.isSet() && !Option.cdrom.isSet() && !Option.fda.isSet()))
{
ClassLoader cl = JPCApplication.class.getClassLoader();
if (cl instanceof URLClassLoader)
{
for (URL url : ((URLClassLoader) cl).getURLs())
{
InputStream in = url.openStream();
try
{
JarInputStream jar = new JarInputStream(in);
Manifest manifest = jar.getManifest();
if (manifest == null)
{
continue;
}
String defaultArgs = manifest.getMainAttributes().getValue("Default-Args");
if (defaultArgs == null)
{
continue;
}
args = defaultArgs.split("\\s");
break;
}
catch (IOException e)
{
System.err.println("Not a JAR file " + url);
}
finally
{
try
{
in.close();
} catch (IOException e) {}
}
}
}
if (args.length == 0)
{
LOGGING.log(Level.INFO, "No configuration specified, using defaults");
args = DEFAULT_ARGS;
}
else
{
LOGGING.log(Level.INFO, "Using configuration specified in manifest");
}
}
else
{
LOGGING.log(Level.INFO, "Using configuration specified on command line");
}
PC pc = new PC(new VirtualClock(), args);
String net = ArgProcessor.findVariable(args, "net", "no");
if (net.startsWith("hub:"))
{
int port = 80;
String server;
int index = net.indexOf(":", 5);
if (index != -1) {
port = Integer.parseInt(net.substring(index+1));
server = net.substring(4, index);
}
else
server = net.substring(4);
EthernetOutput hub = new EthernetHub(server, port);
EthernetCard card = (EthernetCard) pc.getComponent(EthernetCard.class);
card.setOutputDevice(hub);
}
final JPCApplication app = new JPCApplication(args, pc);
app.setBounds(100, 100, MONITOR_WIDTH + 20, MONITOR_HEIGHT + 70);
try
{
app.setIconImage(Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemResource("resources/icon.png")));
} catch (Exception e) {}
app.validate();
app.setVisible(true);
app.start();
}
}