/* 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.*; import java.awt.geom.*; import java.awt.image.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.util.*; import java.util.zip.*; import java.util.jar.*; import javax.swing.*; import javax.imageio.*; import org.jpc.emulator.*; import org.jpc.emulator.pci.peripheral.*; import org.jpc.support.*; public class JPCApplet extends JApplet { public static final String VERSION = "2.035"; private static final String TITLE_TEXT = "Powered by JPC, the fast 100% Java PC Emulator (jpc.sourceforge.net)"; private static final String[] STAGE_TEXT = {"Downloading Hard Disk image", "Downloading Floppy Disk image", "Downloading CDRom ISO image", "Loading Compiled Blocks", "Downloading Snapshot", "Starting JPC"}; private static volatile boolean hasActiveInstance = false; private JPanel mainPanel; private PCConstructor constructor; private volatile int progressValue, constructionStage; private volatile boolean otherAppletDetected; private volatile String progressMessage; private volatile MonitorPanel monitorPanel; private volatile Throwable constructionError; static { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) {} } public void init() { System.out.println("JPC Applet version "+VERSION); mainPanel = new JPanel(new BorderLayout(10, 10)); LinkBorder tb = new LinkBorder(this, getAppletContext(), TITLE_TEXT, new Color(0x000066), 15); mainPanel.setBorder(tb); getContentPane().add("Center", mainPanel); } public void start() { stop(); synchronized (JPCApplet.class) { constructionError = null; monitorPanel = null; otherAppletDetected = false; if (!hasActiveInstance) hasActiveInstance = true; else otherAppletDetected = true; if (otherAppletDetected) return; setProgress(0, "Initialising JPC", -1); mainPanel.removeAll(); mainPanel.add("Center", new DownloadPanel()); mainPanel.revalidate(); constructor = new PCConstructor(); } } public void stop() { synchronized (JPCApplet.class) { if (hasActiveInstance) hasActiveInstance = false; if (monitorPanel != null) monitorPanel.stop(); monitorPanel = null; if (constructor != null) constructor.cancel(); constructor = null; } } public void destroy() { stop(); } static class MonitorPanel extends PCMonitor implements Runnable, PCControl { private volatile Thread runner; private volatile KeyTypingPanel keys; MonitorPanel(PC pc) { super(new BorderLayout(10, 10), pc); } public synchronized void stop() { if (runner == null) return; stopUpdateThread(); Thread th = runner; runner = null; try { th.join(1000); } catch (InterruptedException e) {} getPC().stop(); } public synchronized void start() { if (runner != null) return; startUpdateThread(); runner = new Thread(this, "PC Execute Task"); runner.start(); } public synchronized boolean isRunning() { return runner != null; } public void run() { PC pc = getPC(); try { pc.start(); while (runner != null) pc.execute(); } catch (ThreadDeath e) {} catch (Throwable e) { System.out.println("***** PC EXECUTE THREAD STOPPED ******"); e.printStackTrace(); } finally { pc.stop(); } } } public static class PlayPausePanel extends JComponent implements MouseListener { PCControl control; BufferedImage play, pause; public PlayPausePanel(PCControl control) { this.control = control; setPreferredSize(new Dimension(50, 50)); try { play = ImageIO.read(getClass().getClassLoader().getResourceAsStream("resources/smallplay.png")); pause = ImageIO.read(getClass().getClassLoader().getResourceAsStream("resources/smallpause.png")); setPreferredSize(new Dimension(play.getWidth(), play.getHeight())); } catch (Exception e) {} addMouseListener(this); } public void paint(Graphics g) { super.paint(g); Dimension s = getSize(); if (!control.isRunning()) { if (play != null) { int x = Math.max((s.width - play.getWidth())/2, 0); int y = Math.max((s.width - play.getHeight())/2, 0); int w = Math.min(s.width, play.getWidth()); int h = Math.min(s.width, play.getHeight()); g.drawImage(play, x, y, w, h, null); } setToolTipText("Play PC"); } else { if (pause != null) { int x = Math.max((s.width - pause.getWidth())/2, 0); int y = Math.max((s.width - pause.getHeight())/2, 0); int w = Math.min(s.width, pause.getWidth()); int h = Math.min(s.width, pause.getHeight()); g.drawImage(pause, x, y, w, h, null); } setToolTipText("Pause PC"); } } public void mouseClicked(MouseEvent e) { if (control.isRunning()) control.stop(); else control.start(); repaint(); } public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} } class PCConstructor implements Runnable { private volatile Thread runner; public PCConstructor() { constructionError = null; runner = new Thread(this); runner.start(); } public void cancel() { try { runner.stop(); } catch (Exception e) {} } public String commaSeparate(long n) { String s = Long.toString(n); if (s.length() < 4) return s; StringBuffer buf = new StringBuffer(); int offset = (s.length() % 3); if (offset == 0) { buf.append(s.substring(0, 3)); offset = 3; for (int i = 0; i < (int) ((s.length() - 1) / 3); i++) { buf.append(","); buf.append(s.substring(offset + 3 * i, offset + 3 * i + 3)); } } else { buf.append(s.substring(0, offset)); for (int i = 0; i < (int) ((s.length() - 1) / 3); i++) { buf.append(","); buf.append(s.substring(offset + 3 * i, offset + 3 * i + 3)); } } return buf.toString(); } private byte[] downloadData(int stage, URI source) throws Exception { setProgress(stage, -1); URLConnection conn = source.toURL().openConnection(); conn.setUseCaches(true); conn.setConnectTimeout(30000); conn.setReadTimeout(60000); ByteArrayOutputStream bout = null; int contentLength = conn.getContentLength(); if (contentLength > 0) { bout = new ByteArrayOutputStream(contentLength); progressValue = 0; } else bout = new ByteArrayOutputStream(256 * 1024); InputStream in = conn.getInputStream(); byte[] buffer = new byte[512 * 1024]; while (true) { int r = in.read(buffer); if (r < 0) break; bout.write(buffer, 0, r); if (contentLength > 0) setProgress(stage, "(" + commaSeparate(bout.size()) + " of " + commaSeparate(contentLength) + " bytes)", 100 * bout.size() / contentLength); else setProgress(stage, "(length unknown)", -1); } in.close(); setProgress(stage, "Download Complete", 100); return bout.toByteArray(); } private SeekableIODevice getSeekableIODevice(int stage, String name, boolean isWritable, boolean allowRemote) throws Exception { String value = getParameter(name); if (value == null) return null; if (value.startsWith("net:")) { if (!allowRemote) throw new IOException("Cannot create remote device for " + name); SeekableIODevice result = new RemoteSeekableIODevice(getCodeBase().toURI().resolve(value.substring(4))); return result; } ArrayBackedSeekableIODevice device = null; byte[] data = downloadData(stage, getCodeBase().toURI().resolve(value)); if (value.endsWith(".zip") || value.endsWith(".jar")) { setProgress(stage, "Unzipping downloaded drive (" + data.length + " compressed)", -1); ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(data)); zin.getNextEntry(); device = new ArrayBackedSeekableIODevice(value, zin); } else device = new ArrayBackedSeekableIODevice(value, data); return device; } private void preloadJars(int stage) { String jars = getParameter("preloadJars"); if (jars == null) return; String[] list = jars.split(","); for (int i = 0; i < list.length; i++) try { String archiveName = list[i]; if (!archiveName.endsWith(".jar")) continue; downloadData(stage, getCodeBase().toURI().resolve(archiveName)); } catch (Exception e) { System.out.println("Warning: preloading archive " + list[i] + " failed: " + e); } } public void run() { try { BlockDevice hdaDevice = null, fdaDevice = null, cdromDevice = null; SeekableIODevice device = getSeekableIODevice(0, "hda", true, true); if (device != null) hdaDevice = new HDBlockDevice(device); device = getSeekableIODevice(1, "fda", true, false); if (device != null) fdaDevice = new FloppyBlockDevice(device); device = getSeekableIODevice(2, "cdrom", false, true); if (device != null) cdromDevice = new CDROMBlockDevice(device); preloadJars(3); byte[] ssData = null; String ssName = getParameter("ss"); if (ssName != null) { URI ssURI = getCodeBase().toURI().resolve(ssName); ssData = downloadData(4, ssURI); } setProgress(5, -1); Thread.sleep(1000); String boot = getParameter("boot"); DriveSet.BootType boottype; if (boot.startsWith("fda")) boottype = DriveSet.BootType.FLOPPY; else if (boot.startsWith("hda")) boottype = DriveSet.BootType.HARD_DRIVE; else boottype = DriveSet.BootType.CDROM; PC pc = new PC(new VirtualClock(), new DriveSet(boottype, fdaDevice, null, hdaDevice, null, cdromDevice, null)); MonitorPanel panel = new MonitorPanel(pc); if (ssData != null) { ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(ssData)); zin.getNextEntry(); pc.loadState(zin); zin.closeEntry(); VGACard card = ((VGACard) pc.getComponent(VGACard.class)); card.setOriginalDisplaySize(); zin.getNextEntry(); panel.loadState(zin); zin.closeEntry(); zin.close(); } monitorPanel = panel; } catch (ThreadDeath d) {} catch (Throwable e) { constructionError = e; System.out.println("***** ERROR CONSTRUCTING PC ******"); e.printStackTrace(); } } } private synchronized void setProgress(int stage, int value) { setProgress(stage, null, value); } private synchronized void setProgress(int stage, String msg, int value) { constructionStage = stage; progressMessage = msg; progressValue = value; } class DownloadPanel extends JPanel implements ActionListener { private ProgressPanel progressPanel; private BufferedImage splashImage, tickImage; private JProgressBar progress; private javax.swing.Timer timer; private Font stageFont; DownloadPanel() { super(new BorderLayout(10, 10)); setBackground(Color.white); progress = new JProgressBar(); progress.setStringPainted(true); progress.setString(""); try { URL url = JPCApplet.class.getClassLoader().getResource("resources/JPCLogo.png"); splashImage = ImageIO.read(url); Graphics g = splashImage.createGraphics(); g.setColor(new Color(255, 255, 255, 190)); g.fillRect(0, 0, splashImage.getWidth(), splashImage.getHeight()); g.dispose(); } catch (Exception e) {} try { URL url = JPCApplet.class.getClassLoader().getResource("resources/tick.png"); tickImage = ImageIO.read(url); } catch (Exception e) {} stageFont = new Font("Dialog", Font.BOLD, 20); progressPanel = new ProgressPanel(); add("Center", progressPanel); add("South", progress); timer = new javax.swing.Timer(500, this); timer.start(); } class ProgressPanel extends JPanel { public void paint(Graphics g) { Dimension s = getSize(); g.setColor(Color.white); g.fillRect(0, 0, s.width, s.height); if (splashImage != null) { int x = (s.width - splashImage.getWidth())/2; int y = (s.height - splashImage.getHeight())/2; g.drawImage(splashImage, x, y, null); } g.setFont(stageFont); int xPos = Math.max(40, (s.width - 300)/2); for (int i=0; i<STAGE_TEXT.length; i++) { String messageLine = STAGE_TEXT[i]; if (i < constructionStage) { if (tickImage != null) g.drawImage(tickImage, xPos-30, 80 + 30*i, null); g.setColor(new Color(0, 0, 102)); } else if (i == constructionStage) { g.setColor(new Color(0, 50, 200)); messageLine = messageLine + "..."; } else g.setColor(new Color(150, 150, 150)); g.drawString(messageLine, xPos, 100 + 30*i); } } } public void update(String text, int value) { if (value < 0) progress.setIndeterminate(true); else { progress.setIndeterminate(false); progress.setValue(Math.min(100, value)); } String message = STAGE_TEXT[constructionStage]; if (text != null) message = message +" "+ text; progress.setString(message); progressPanel.repaint(); } public void actionPerformed(ActionEvent evt) { if (!isActive()) { timer.stop(); return; } if (!otherAppletDetected && (monitorPanel == null) && (constructionError == null)) { update(progressMessage, progressValue); return; } timer.stop(); mainPanel.removeAll(); if (otherAppletDetected) { JLabel msg = new JLabel("Another JPC applet is already running. Only one instance can run at a time."); msg.setHorizontalTextPosition(SwingConstants.CENTER); mainPanel.add("Center", msg); } else if (monitorPanel != null) { mainPanel.add("Center", monitorPanel); KeyTypingPanel keys = new KeyTypingPanel(monitorPanel); PlayPausePanel pp = new PlayPausePanel(monitorPanel); JPanel p1 = new JPanel(new BorderLayout(10, 10)); p1.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); p1.add("Center", keys); p1.add("East", pp); mainPanel.add("South", p1); monitorPanel.requestFocus(); monitorPanel.start(); } else if (constructionError != null) { ByteArrayOutputStream bout = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(bout); ps.println("Fatal Error Constructing JPC Instance: " + constructionError); ps.println("***** Refreshing the Web page may help *****"); for (Throwable t = constructionError; t != null; t = t.getCause()) t.printStackTrace(ps); JTextArea txt = new JTextArea(new String(bout.toByteArray())); txt.setEditable(false); mainPanel.add("Center", new JScrollPane(txt)); } else System.out.println("Unexpected state - JPC should start but there's no error or PC ready"); mainPanel.revalidate(); mainPanel.repaint(); } } }