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