Add disk image dumping support
[jpcrr.git] / org / jpc / plugins / PCControl.java
blob35a385c3f0fc3f217482118b2f10a363dddad569
1 /*
2 JPC-RR: A x86 PC Hardware Emulator
3 Release 1
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 H. Ilari Liusvaara
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 Based on JPC x86 PC Hardware emulator,
22 A project from the Physics Dept, The University of Oxford
24 Details about original JPC can be found at:
26 www-jpc.physics.ox.ac.uk
30 package org.jpc.plugins;
32 import java.awt.Dimension;
33 import java.io.*;
34 import java.util.*;
35 import java.lang.reflect.*;
36 import java.security.AccessControlException;
37 import javax.swing.*;
38 import java.awt.dnd.*;
39 import java.awt.datatransfer.*;
40 import javax.swing.border.EtchedBorder;
42 import org.jpc.emulator.HardwareComponent;
43 import org.jpc.emulator.PC;
44 import org.jpc.emulator.EventRecorder;
45 import org.jpc.emulator.TraceTrap;
46 import org.jpc.emulator.DriveSet;
47 import org.jpc.emulator.DisplayController;
48 import org.jpc.emulator.memory.PhysicalAddressSpace;
49 import org.jpc.emulator.StatusDumper;
50 import org.jpc.emulator.Clock;
51 import org.jpc.emulator.VGADigitalOut;
52 import org.jpc.diskimages.BlockDevice;
53 import org.jpc.diskimages.DiskImageSet;
54 import org.jpc.diskimages.DiskImage;
55 import org.jpc.plugins.RAWDumper;
56 import org.jpc.pluginsaux.PleaseWait;
57 import org.jpc.pluginsaux.AsyncGUITask;
58 import org.jpc.pluginsaux.NewDiskDialog;
59 import org.jpc.pluginsaux.AuthorsDialog;
60 import org.jpc.pluginsaux.PCConfigDialog;
61 import org.jpc.pluginsaux.MenuManager;
62 import org.jpc.pluginsaux.PCMonitorPanel;
63 import org.jpc.pluginsaux.PCMonitorPanelEmbedder;
64 import org.jpc.pluginsaux.ImportDiskImage;
65 import org.jpc.Misc;
66 import org.jpc.pluginsbase.*;
67 import org.jpc.jrsr.*;
69 import static org.jpc.Misc.randomHexes;
70 import static org.jpc.Misc.errorDialog;
71 import static org.jpc.Misc.callShowOptionDialog;
72 import static org.jpc.Misc.moveWindow;
73 import static org.jpc.Misc.parseStringToComponents;
74 import static org.jpc.Misc.nextParseLine;
75 import static org.jpc.Misc.renameFile;
77 public class PCControl implements Plugin, PCMonitorPanelEmbedder
79 private static long PROFILE_ALWAYS = 0;
80 private static long PROFILE_NO_PC = 1;
81 private static long PROFILE_HAVE_PC = 2;
82 private static long PROFILE_STOPPED = 4;
83 private static long PROFILE_RUNNING = 8;
84 private static long PROFILE_EVENTS = 16;
85 private static long PROFILE_CDROM = 32;
86 private static long PROFILE_DUMPING = 64;
87 private static long PROFILE_NOT_DUMPING = 128;
88 private static long PROFILE_HAVE_HDA = 256;
89 private static long PROFILE_HAVE_HDB = 512;
90 private static long PROFILE_HAVE_HDC = 1024;
91 private static long PROFILE_HAVE_HDD = 2048;
92 private static String SAVESTATE_LABEL = "Savestating...";
93 private static String LOADSTATE_LABEL = "Loadstating...";
94 private static String RAMDUMP_LABEL = "Dumping RAM...";
95 private static String IMAGEDUMP_LABEL = "Dumping Image...";
96 private static String STATUSDUMP_LABEL = "Dumping status...";
97 private static String ASSEMBLE_LABEL = "Assembling system...";
98 private static String ADDDISK_LABEL = "Adding new disk...";
99 private static String CHANGEAUTHORS_LABEL = "Changing run authors...";
101 private static final long serialVersionUID = 8;
102 private Plugins vPluginManager;
104 private JFrame window;
105 private JFileChooser snapshotFileChooser;
106 private DropTarget dropTarget;
107 private LoadstateDropTarget loadstateDropTarget;
108 private RAWDumper dumper;
110 private Set<String> disks;
112 protected PC pc;
114 private int trapFlags;
116 private volatile long profile;
117 private volatile boolean running;
118 private volatile boolean waiting;
119 private boolean uncompressedSave;
120 private static final long[] stopTime;
121 private static final String[] stopLabel;
122 private volatile long imminentTrapTime;
123 private boolean shuttingDown;
124 private int nativeWidth;
125 private int nativeHeight;
126 private PCConfigDialog configDialog;
127 private MenuManager menuManager;
128 private Map<String, List<String[]> > extraActions;
129 private PCMonitorPanel panel;
130 private JLabel statusBar;
131 private volatile int currentResolutionWidth;
132 private volatile int currentResolutionHeight;
133 private volatile Runnable taskToDo;
134 private volatile String taskLabel;
135 private boolean cycleDone;
136 private Map<String, Class<?>> debugInClass;
137 private Map<String, Boolean> debugState;
139 private PC.PCFullStatus currentProject;
141 class LoadstateDropTarget implements DropTargetListener
143 public void dragEnter(DropTargetDragEvent e) {}
144 public void dragOver(DropTargetDragEvent e) {}
145 public void dragExit(DropTargetEvent e) {}
146 public void dropActionChanged(DropTargetDragEvent e) {}
148 public void drop(DropTargetDropEvent e)
150 if(running) {
151 e.rejectDrop();
152 return;
154 e.acceptDrop(DnDConstants.ACTION_COPY);
155 int i = 0;
156 for(DataFlavor f : e.getCurrentDataFlavors()) {
157 try {
158 Transferable t = e.getTransferable();
159 Object d = t.getTransferData(f);
160 if(f.isMimeTypeEqual("text/uri-list") && d.getClass() == String.class) {
161 String url = (String)d;
162 if(url.indexOf(10) >= 0) {
163 callShowOptionDialog(window, "Hey, only single file at time!",
164 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
165 new String[]{"Dismiss"}, "Dismiss");
166 e.dropComplete(false);
167 return;
169 e.dropComplete(handleURLDropped(url));
170 return;
172 } catch(Exception ex) {
173 errorDialog(ex, "Failed to get DnD data", null, "Dismiss");
174 e.dropComplete(false);
175 return;
178 for(DataFlavor f : e.getCurrentDataFlavors()) {
179 i = 0;
180 try {
181 i++;
182 Transferable t = e.getTransferable();
183 Object d = t.getTransferData(f);
184 System.err.println("Notice: Format #" + i + ":" + d.getClass().getName() + "(" + f + ")");
185 } catch(Exception ex) {
186 System.err.println("Notice: Format #" + i + ": <ERROR>(" + f + ")");
189 callShowOptionDialog(window, "Can't recognize file to load from drop (debugging information dumped to console).",
190 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
191 new String[]{"Dismiss"}, "Dismiss");
192 e.dropComplete(false);
196 private boolean handleURLDropped(String url)
198 if(!url.startsWith("file:///")) {
199 callShowOptionDialog(window, "Can't load remote resource.",
200 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
201 new String[]{"Dismiss"}, "Dismiss");
202 return false;
204 url = url.substring(7);
205 setTask(new LoadStateTask(url, LoadStateTask.MODE_NORMAL), LOADSTATE_LABEL);
206 return true;
209 static
211 stopTime = new long[] {-1, 0, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000,
212 5000000, 10000000, 20000000, 50000000, 100000000, 200000000, 500000000, 1000000000, 2000000000,
213 5000000000L, 10000000000L, 20000000000L, 50000000000L};
214 stopLabel = new String[] {"(unbounded)", "(singlestep)", "1µs", "2µs", "5µs", "10µs", "20µs", "50µs", "100µs",
215 "200µs", "500µs","1ms", "2ms", "5ms", "10ms", "20ms", "50ms", "100ms", "200ms", "500ms", "1s", "2s", "5s",
216 "10s", "20s", "50s"};
219 public boolean systemShutdown()
221 if(!running || pc == null)
222 return true;
223 //We are running. Do the absolute minimum since we are running in very delicate context.
224 shuttingDown = true;
225 stop();
226 while(running);
227 return true;
230 public void reconnect(PC pc)
232 pcStopping(); //Do the equivalent effects.
233 updateStatusBar();
234 updateDebug();
237 public void notifySizeChange(int w, int h)
239 final int w2 = w;
240 final int h2 = h;
242 SwingUtilities.invokeLater(new Runnable() { public void run() {
243 window.pack();
244 Dimension d = window.getSize();
245 nativeWidth = d.width;
246 nativeHeight = d.height;
247 currentResolutionWidth = w2;
248 currentResolutionHeight = h2;
249 updateStatusBarEventThread();
250 }});
253 public void notifyFrameReceived(int w, int h)
255 currentResolutionWidth = w;
256 currentResolutionHeight = h;
257 updateStatusBar();
260 private void setTrapFlags()
262 pc.getTraceTrap().setTrapFlags(trapFlags);
265 public void pcStarting()
267 profile = PROFILE_HAVE_PC | PROFILE_RUNNING | (profile & (PROFILE_DUMPING | PROFILE_NOT_DUMPING));
268 if(currentProject != null && currentProject.events != null);
269 profile |= PROFILE_EVENTS;
270 if(pc.getCDROMIndex() >= 0)
271 profile |= PROFILE_CDROM;
273 menuManager.setProfile(profile);
275 if (running)
276 return;
278 setTrapFlags();
280 Clock sysClock = (Clock)pc.getComponent(Clock.class);
281 long current = sysClock.getTime();
282 if(imminentTrapTime > 0) {
283 pc.getTraceTrap().setTrapTime(current + imminentTrapTime);
284 } else if(imminentTrapTime == 0) {
285 //Hack: We set trace trap to trap immediately. It comes too late to abort next instruction, but
286 //early enough to abort one after that.
287 pc.getTraceTrap().setTrapTime(current);
289 if(currentProject.events != null)
290 currentProject.events.setPCRunStatus(true);
293 public void pcStopping()
295 if(currentProject.events != null)
296 currentProject.events.setPCRunStatus(false);
297 if(shuttingDown)
298 return; //Don't mess with UI when shutting down.
301 profile = PROFILE_STOPPED | (profile & (PROFILE_DUMPING | PROFILE_NOT_DUMPING));
302 if(pc != null)
303 profile |= PROFILE_HAVE_PC;
304 else
305 profile |= PROFILE_NO_PC;
306 if(currentProject != null && currentProject.events != null);
307 profile |= PROFILE_EVENTS;
308 if(pc.getCDROMIndex() >= 0)
309 profile |= PROFILE_CDROM;
311 menuManager.setProfile(profile);
312 updateStatusBar();
314 try {
315 updateDisks();
316 } catch(Exception e) {
317 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
320 if(pc != null) {
321 pc.getTraceTrap().clearTrapTime();
322 pc.getTraceTrap().getAndClearTrapActive();
326 private String diskNameByIdx(int idx)
328 return pc.getDisks().lookupDisk(idx).getName();
331 private void updateDisks() throws Exception
333 for(String x : disks)
334 menuManager.removeMenuItem(x);
336 disks.clear();
338 if(pc == null)
339 return;
341 DiskImageSet imageSet = pc.getDisks();
342 DriveSet driveset = pc.getDrives();
343 int[] floppies = imageSet.diskIndicesByType(BlockDevice.Type.FLOPPY);
344 int[] cdroms = imageSet.diskIndicesByType(BlockDevice.Type.CDROM);
346 for(int i = 0; i < floppies.length; i++) {
347 String name = diskNameByIdx(floppies[i]);
348 menuManager.addMenuItem("Drives→fda→" + name, this, "menuChangeDisk", new Object[]{new Integer(0),
349 new Integer(floppies[i])}, PROFILE_HAVE_PC);
350 menuManager.addMenuItem("Drives→fdb→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
351 new Integer(floppies[i])}, PROFILE_HAVE_PC);
352 menuManager.addMenuItem("Drives→dump→" + name, this, "menuDumpDisk", new Object[]{
353 new Integer(floppies[i])}, PROFILE_HAVE_PC);
354 menuManager.addSelectableMenuItem("Drives→Write Protect→" + name, this, "menuWriteProtect",
355 new Object[]{new Integer(floppies[i])}, imageSet.lookupDisk(floppies[i]).isReadOnly(),
356 PROFILE_HAVE_PC);
357 disks.add("Drives→fda→" + name);
358 disks.add("Drives→fdb→" + name);
359 disks.add("Drives→Write Protect→" + name);
360 disks.add("Drives→dump→" + name);
362 BlockDevice dev;
363 DriveSet drives = pc.getDrives();
364 profile = profile & ~(PROFILE_HAVE_HDA | PROFILE_HAVE_HDB | PROFILE_HAVE_HDC | PROFILE_HAVE_HDD);
365 dev = drives.getHardDrive(0);
366 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDA : 0);
367 dev = drives.getHardDrive(1);
368 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDB : 0);
369 dev = drives.getHardDrive(2);
370 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDC : 0);
371 dev = drives.getHardDrive(3);
372 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDD : 0);
373 menuManager.setProfile(profile);
376 for(int i = 0; i < cdroms.length; i++) {
377 String name = diskNameByIdx(cdroms[i]);
378 menuManager.addMenuItem("Drives→CD-ROM→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
379 new Integer(cdroms[i])}, PROFILE_HAVE_PC | PROFILE_CDROM);
380 disks.add("Drives→CD-ROM→" + name);
384 private synchronized boolean setTask(Runnable task, String label)
386 boolean run = running;
387 if(run || taskToDo != null)
388 return false; //Can't do tasks with PC running or existing task.
389 taskToDo = task;
390 taskLabel = label;
391 notifyAll();
392 updateStatusBar();
393 return true;
396 public void main()
398 boolean wasRunning = false;
399 while(true) { //We will be killed by JVM.
400 //Wait for us to become runnable again.
401 while((!running || pc == null) && taskToDo == null) {
402 if(!running && wasRunning && pc != null)
403 pc.stop();
404 wasRunning = running;
405 try {
406 synchronized(this) {
407 if((running && pc != null) || taskToDo != null)
408 continue;
409 waiting = true;
410 notifyAll();
411 wait();
412 waiting = false;
414 } catch(Exception e) {
418 if(running && !wasRunning)
419 pc.start();
420 wasRunning = running;
422 if(taskToDo != null) {
423 taskToDo.run();
424 taskToDo = null;
425 updateStatusBar();
426 continue;
429 try {
430 pc.execute();
431 if(pc.getHitTraceTrap()) {
432 if(pc.getAndClearTripleFaulted())
433 callShowOptionDialog(window, "CPU shut itself down due to triple fault. Rebooting the system.",
434 "Triple fault!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
435 new String[]{"Dismiss"}, "Dismiss");
436 if(shuttingDown)
437 stopNoWait();
438 else
439 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
440 running = false;
441 doCycle(pc);
443 } catch (Exception e) {
444 running = false;
445 doCycle(pc);
446 errorDialog(e, "Hardware emulator internal error", window, "Dismiss");
447 try {
448 if(shuttingDown)
449 stopNoWait();
450 else
451 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
452 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
453 } catch (Exception f) {
460 public void connectPC(PC pc)
462 currentProject.pc = pc;
463 vPluginManager.reconnect(pc);
464 this.pc = pc;
467 private void startExternal()
469 if(pc != null && !running)
470 if(!SwingUtilities.isEventDispatchThread())
471 try {
472 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.start(); }});
473 } catch(Exception e) {
475 else
476 start();
479 private void stopExternal()
481 if(pc != null && running)
482 if(!SwingUtilities.isEventDispatchThread())
483 try {
484 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.stop(); }});
485 } catch(Exception e) {
487 else
488 stop();
491 public boolean eci_state_save(String filename)
493 return setTask(new SaveStateTask(filename, false), SAVESTATE_LABEL);
496 public boolean eci_state_dump(String filename)
498 return setTask(new StatusDumpTask(filename), STATUSDUMP_LABEL);
501 public boolean eci_movie_save(String filename)
503 return setTask(new SaveStateTask(filename, true), SAVESTATE_LABEL);
506 public boolean eci_state_load(String filename)
508 return setTask(new LoadStateTask(filename, LoadStateTask.MODE_NORMAL), LOADSTATE_LABEL);
511 public boolean eci_state_load_noevents(String filename)
513 return setTask(new LoadStateTask(filename, LoadStateTask.MODE_PRESERVE), LOADSTATE_LABEL);
516 public boolean eci_movie_load(String filename)
518 return setTask(new LoadStateTask(filename, LoadStateTask.MODE_MOVIEONLY), LOADSTATE_LABEL);
521 public boolean eci_pc_assemble()
523 return setTask(new AssembleTask(), ASSEMBLE_LABEL);
526 public boolean eci_ram_dump_text(String filename)
528 return setTask(new RAMDumpTask(filename, false), RAMDUMP_LABEL);
531 public boolean eci_image_dump(String filename, int index)
533 return setTask(new ImageDumpTask(filename, index), IMAGEDUMP_LABEL);
536 public boolean eci_ram_dump_binary(String filename)
538 return setTask(new RAMDumpTask(filename, true), RAMDUMP_LABEL);
541 public void eci_trap_vretrace_start_on()
543 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_START;
546 public void eci_trap_vretrace_start_off()
548 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_START;
551 public void eci_trap_vretrace_end_on()
553 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_END;
556 public void eci_trap_vretrace_end_off()
558 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_END;
561 public void eci_trap_timed_disable()
563 this.imminentTrapTime = -1;
566 public void eci_trap_timed(Long time)
568 this.imminentTrapTime = time.longValue();
571 public void eci_pc_start()
573 startExternal();
576 public void eci_pc_stop()
578 stopExternal();
581 public void eci_pccontrol_setwinpos(Integer x, Integer y)
583 moveWindow(window, x.intValue(), y.intValue(), nativeWidth, nativeHeight);
586 public void eci_sendevent(String clazz, String[] rargs)
588 System.err.println("Event to: '" + clazz + "':");
589 for(int i = 0; i < rargs.length; i++) {
590 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
592 if(currentProject.events != null) {
593 try {
594 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
595 currentProject.events.addEvent(0L, x, rargs);
596 } catch(Exception e) {
597 System.err.println("Error adding event: " + e.getMessage());
602 public void eci_sendevent_lowbound(Long timeMin, String clazz, String[] rargs)
604 System.err.println("Event to: '" + clazz + "' (with low bound of " + timeMin + "):");
605 for(int i = 0; i < rargs.length; i++) {
606 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
608 if(currentProject.events != null) {
609 try {
610 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
611 currentProject.events.addEvent(timeMin, x, rargs);
612 } catch(Exception e) {
613 System.err.println("Error adding event: " + e.getMessage());
618 public void eci_memory_read(Long address, Integer size)
620 if(currentProject.pc != null) {
621 long addr = address.longValue();
622 long _size = size.intValue();
623 long ret = 0;
624 PhysicalAddressSpace addrSpace;
625 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
626 return;
628 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
629 if(_size == 1)
630 ret = (long)addrSpace.getByte((int)addr) & 0xFF;
631 else if(_size == 2)
632 ret = (long)addrSpace.getWord((int)addr) & 0xFFFF;
633 else if(_size == 4)
634 ret = (long)addrSpace.getDoubleWord((int)addr) & 0xFFFFFFFFL;
636 vPluginManager.returnValue(ret);
640 public void eci_memory_write(Long address, Long value, Integer size)
642 if(currentProject.pc != null) {
643 long addr = address.longValue();
644 long _size = size.intValue();
645 long _value = value.longValue();
646 PhysicalAddressSpace addrSpace;
647 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
648 return;
650 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
651 if(_size == 1)
652 addrSpace.setByte((int)addr, (byte)_value);
653 else if(_size == 2)
654 addrSpace.setWord((int)addr, (short)_value);
655 else if(_size == 4)
656 addrSpace.setDoubleWord((int)addr, (int)_value);
660 public PCControl(Plugins manager, String args) throws Exception
662 this(manager);
664 UTFInputLineStream file = null;
665 Map<String, String> params = parseStringToComponents(args);
666 Set<String> used = new HashSet<String>();
667 String extramenu = params.get("extramenu");
668 String uncompress = params.get("uncompressedsave");
669 if(uncompress != null)
670 uncompressedSave = true;
671 if(extramenu == null)
672 return;
673 try {
674 file = new UTFInputLineStream(new FileInputStream(extramenu));
676 while(true) {
677 boolean exists = false;
678 String[] line = nextParseLine(file);
679 if(line == null)
680 break;
681 if(line.length < 3 || line[0].charAt(0) == '→') {
682 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
683 continue;
685 if(line[0].length() == 0 || line[0].charAt(line[0].length() - 1) == '→') {
686 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
687 continue;
689 if(line[0].indexOf("→→") >= 0) {
690 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
691 continue;
693 if(used.contains(line[0]))
694 exists = true;
696 KeyStroke stroke = null;
697 if(!line[1].equals("<>")) {
698 stroke = KeyStroke.getKeyStroke(line[1]);
699 if(stroke == null) {
700 System.err.println("Warning: Bad keystroke '" + line[1] + "'.");
705 String[] lineCommand = Arrays.copyOfRange(line, 2, line.length);
706 used.add(line[0]);
707 List<String[]> commandList = extraActions.get(line[0]);
708 if(commandList == null)
709 extraActions.put(line[0], commandList = new ArrayList<String[]>());
710 commandList.add(lineCommand);
712 if(!exists)
713 menuManager.addMenuItem("Extra→" + line[0], this, "menuExtra", new String[]{line[0]}, PROFILE_ALWAYS,
714 stroke);
716 file.close();
717 } catch(IOException e) {
718 errorDialog(e, "Failed to load extra menu defintions", null, "dismiss");
719 if(file != null)
720 file.close();
722 window.setJMenuBar(menuManager.getMainBar());
725 public PCControl(Plugins manager) throws Exception
727 window = new JFrame("JPC-RR" + Misc.emuname);
729 if(DiskImage.getLibrary() == null)
730 throw new Exception("PCControl plugin requires disk library");
732 running = false;
733 shuttingDown = false;
735 debugInClass = new HashMap<String, Class<?>>();
736 debugState = new HashMap<String, Boolean>();
738 configDialog = new PCConfigDialog();
739 extraActions = new HashMap<String, List<String[]> >();
740 menuManager = new MenuManager();
742 menuManager.setProfile(profile = (PROFILE_NO_PC | PROFILE_STOPPED | PROFILE_NOT_DUMPING));
744 menuManager.addMenuItem("System→Assemble", this, "menuAssemble", null, PROFILE_STOPPED);
745 menuManager.addMenuItem("System→Start", this, "menuStart", null, PROFILE_STOPPED | PROFILE_HAVE_PC);
746 menuManager.addMenuItem("System→Stop", this, "menuStop", null, PROFILE_RUNNING);
747 menuManager.addMenuItem("System→Reset", this, "menuReset", null, PROFILE_HAVE_PC);
748 menuManager.addMenuItem("System→Start dumping", this, "menuStartDump", null, PROFILE_STOPPED | PROFILE_NOT_DUMPING);
749 menuManager.addMenuItem("System→Stop dumping", this, "menuStopDump", null, PROFILE_STOPPED | PROFILE_DUMPING);
750 menuManager.addMenuItem("System→Quit", this, "menuQuit", null, PROFILE_ALWAYS);
751 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace Start", this, "menuVRetraceStart", null, false,
752 PROFILE_ALWAYS);
753 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace End", this, "menuVRetraceEnd", null, false,
754 PROFILE_ALWAYS);
755 menuManager.addMenuItem("Snapshot→Change Run Authors", this, "menuChangeAuthors", null, PROFILE_HAVE_PC);
756 menuManager.addMenuItem("Snapshot→Save→Snapshot", this, "menuSave", new Object[]{new Boolean(false)},
757 PROFILE_HAVE_PC | PROFILE_STOPPED);
758 menuManager.addMenuItem("Snapshot→Save→Movie", this, "menuSave", new Object[]{new Boolean(true)},
759 PROFILE_HAVE_PC | PROFILE_STOPPED);
760 menuManager.addMenuItem("Snapshot→Save→Status Dump", this, "menuStatusDump", null,
761 PROFILE_HAVE_PC | PROFILE_STOPPED);
762 menuManager.addMenuItem("Snapshot→Load→Snapshot", this, "menuLoad",
763 new Object[]{new Integer(LoadStateTask.MODE_NORMAL)}, PROFILE_STOPPED);
764 menuManager.addMenuItem("Snapshot→Load→Snapshot (preserve events)", this, "menuLoad",
765 new Object[]{new Integer(LoadStateTask.MODE_PRESERVE)}, PROFILE_STOPPED | PROFILE_EVENTS);
766 menuManager.addMenuItem("Snapshot→Load→Movie", this, "menuLoad",
767 new Object[]{new Integer(LoadStateTask.MODE_MOVIEONLY)}, PROFILE_STOPPED);
768 menuManager.addMenuItem("Snapshot→RAM Dump→Hexadecimal", this, "menuRAMDump", new Object[]{new Boolean(false)},
769 PROFILE_HAVE_PC | PROFILE_STOPPED);
770 menuManager.addMenuItem("Snapshot→RAM Dump→Binary", this, "menuRAMDump", new Object[]{new Boolean(true)},
771 PROFILE_HAVE_PC | PROFILE_STOPPED);
772 menuManager.addMenuItem("Snapshot→Truncate Event Stream", this, "menuTruncate", null,
773 PROFILE_STOPPED | PROFILE_EVENTS);
775 for(int i = 0; i < stopLabel.length; i++) {
776 menuManager.addSelectableMenuItem("Breakpoints→Timed Stops→" + stopLabel[i], this, "menuTimedStop",
777 null, (i == 0), PROFILE_ALWAYS);
779 imminentTrapTime = -1;
781 menuManager.addMenuItem("Drives→fda→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(0),
782 new Integer(-1)}, PROFILE_HAVE_PC);
783 menuManager.addMenuItem("Drives→fdb→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(1),
784 new Integer(-1)}, PROFILE_HAVE_PC);
785 menuManager.addMenuItem("Drives→CD-ROM→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(2),
786 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_CDROM);
787 menuManager.addMenuItem("Drives→Add image", this, "menuAddDisk", null, PROFILE_HAVE_PC);
788 menuManager.addMenuItem("Drives→Import Image", this, "menuImport", null, PROFILE_ALWAYS);
789 menuManager.addMenuItem("Drives→dump→HDA", this, "menuDumpDisk", new Object[]{
790 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDA);
791 menuManager.addMenuItem("Drives→dump→HDB", this, "menuDumpDisk", new Object[]{
792 new Integer(-2)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDB);
793 menuManager.addMenuItem("Drives→dump→HDC", this, "menuDumpDisk", new Object[]{
794 new Integer(-3)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDC);
795 menuManager.addMenuItem("Drives→dump→HDD", this, "menuDumpDisk", new Object[]{
796 new Integer(-4)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDD);
797 menuManager.addMenuItem("Debug→Hacks→NO_FPU", this, "menuNOFPU", null, PROFILE_HAVE_PC);
798 menuManager.addMenuItem("Debug→Hacks→VGA_DRAW", this, "menuVGADRAW", null, PROFILE_HAVE_PC);
799 menuManager.addMenuItem("Debug→Hacks→VGA_SCROLL_2", this, "menuVGASCROLL2", null, PROFILE_HAVE_PC);
801 disks = new HashSet<String>();
802 currentProject = new PC.PCFullStatus();
803 this.pc = null;
804 this.vPluginManager = manager;
806 panel = new PCMonitorPanel(this, manager.getOutputConnector());
807 loadstateDropTarget = new LoadstateDropTarget();
808 dropTarget = new DropTarget(panel.getMonitorPanel(), loadstateDropTarget);
810 statusBar = new JLabel("");
811 statusBar.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
812 manager.addSlaveObject(this, panel);
813 panel.startThread();
815 window.getContentPane().add("Center", panel.getMonitorPanel());
816 window.getContentPane().add("South", statusBar);
817 JMenuBar bar = menuManager.getMainBar();
818 for(JMenu menu : panel.getMenusNeeded())
819 bar.add(menu);
820 window.setJMenuBar(bar);
822 try {
823 window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
824 } catch (AccessControlException e) {
825 System.err.println("Error: Not able to add some components to frame: " + e.getMessage());
828 snapshotFileChooser = new JFileChooser(System.getProperty("user.dir"));
830 window.getContentPane().validate();
831 window.validate();
832 window.pack();
833 Dimension d = window.getSize();
834 nativeWidth = d.width;
835 nativeHeight = d.height;
836 updateStatusBarEventThread();
838 window.setVisible(true);
841 private String debugShowName(String name)
843 name = name.substring(12);
844 StringBuffer buf = new StringBuffer();
845 for(int i = 0; i < name.length(); i++)
846 if(name.charAt(i) == '_')
847 buf.append(' ');
848 else
849 buf.append(name.charAt(i));
850 return buf.toString();
853 private void addDebug(String name, Class<?> clazz)
855 if(debugInClass.get(name) != null)
856 return;
857 debugInClass.put(name, clazz);
858 debugState.put(name, false);
859 try {
860 menuManager.addSelectableMenuItem("Debug→" + debugShowName(name), this, "menuDEBUGOPTION",
861 new Object[]{name}, false, PROFILE_HAVE_PC);
862 } catch(Exception e) {
866 public void menuDEBUGOPTION(String i, Object[] args)
868 String name = (String)args[0];
869 String mName = "Debug→" + debugShowName(name);
870 debugState.put(name, !debugState.get(name));
871 setDebugOption(name);
872 menuManager.setSelected(mName, debugState.get(name));
875 private void setDebugOption(String name)
877 try {
878 debugInClass.get(name).getDeclaredMethod(name, boolean.class).invoke(pc.getComponent(
879 debugInClass.get(name)), debugState.get(name));
880 } catch(Exception e) {
881 e.printStackTrace();
885 private void setDebugOptions()
887 for(Map.Entry<String, Class<?>> opt : debugInClass.entrySet())
888 setDebugOption(opt.getKey());
891 private void updateDebug()
893 setDebugOptions();
894 for(HardwareComponent c : pc.allComponents()) {
895 Class<?> cl = c.getClass();
896 for(Method m : cl.getDeclaredMethods()) {
897 Class<?>[] p = m.getParameterTypes();
898 if(!m.getName().startsWith("DEBUGOPTION_"))
899 continue;
900 if(p.length != 1 || p[0] != boolean.class)
901 continue;
902 addDebug(m.getName(), cl);
907 private void updateStatusBar()
909 if(vPluginManager.isShuttingDown())
910 return; //Too much of deadlock risk.
911 SwingUtilities.invokeLater(new Runnable() { public void run() { updateStatusBarEventThread(); }});
914 private void updateStatusBarEventThread()
916 String text1;
917 if(currentProject.pc != null && taskToDo == null) {
918 long timeNow = ((Clock)currentProject.pc.getComponent(Clock.class)).getTime();
919 long timeEnd = currentProject.events.getLastEventTime();
920 text1 = " Time: " + (timeNow / 1000000) + "ms, movie length: " + (timeEnd / 1000000) + "ms";
921 if(currentResolutionWidth > 0 && currentResolutionHeight > 0)
922 text1 = text1 + ", resolution: " + currentResolutionWidth + "*" + currentResolutionHeight;
923 else
924 text1 = text1 + ", resolution: <No valid signal>";
925 if(currentProject.events.isAtMovieEnd())
926 text1 = text1 + " (At movie end)";
927 } else if(taskToDo != null)
928 text1 = taskLabel;
929 else
930 text1 = " NO PC CONNECTED";
932 statusBar.setText(text1);
935 public void menuExtra(String i, Object[] args)
937 final List<String[]> commandList = extraActions.get(args[0]);
938 if(commandList == null) {
939 System.err.println("Warning: Called extra menu with unknown entry '" + args[0] + "'.");
940 return;
943 //Run the functions on seperate thread to avoid deadlocking.
944 (new Thread(new Runnable() { public void run() { menuExtraThreadFunc(commandList); }}, "Extra action thread")).start();
947 private void menuExtraThreadFunc(List<String[]> actions)
949 for(String[] i : actions) {
950 if(i.length == 1) {
951 vPluginManager.invokeExternalCommandSynchronous(i[0], null);
952 } else {
953 String[] rest = Arrays.copyOfRange(i, 1, i.length, String[].class);
954 vPluginManager.invokeExternalCommandSynchronous(i[0], rest);
959 public void menuAssemble(String i, Object[] args)
961 setTask(new AssembleTask(), ASSEMBLE_LABEL);
964 public void menuStart(String i, Object[] args)
966 start();
969 public void menuStartDump(String i, Object[] args)
971 int returnVal = snapshotFileChooser.showDialog(window, "Dump to file");
972 if(returnVal != 0)
973 return;
974 File choosen = snapshotFileChooser.getSelectedFile();
975 try {
976 dumper = new RAWDumper(vPluginManager, "rawoutput=" + choosen.getAbsolutePath());
977 vPluginManager.registerPlugin(dumper);
978 } catch(Exception e) {
979 errorDialog(e, "Failed to start dumping", null, "Dismiss");
980 return;
982 profile &= ~PROFILE_NOT_DUMPING;
983 profile |= PROFILE_DUMPING;
984 menuManager.setProfile(profile);
987 public void menuStopDump(String i, Object[] args)
989 vPluginManager.unregisterPlugin(dumper);
990 profile &= ~PROFILE_DUMPING;
991 profile |= PROFILE_NOT_DUMPING;
992 menuManager.setProfile(profile);
995 public void menuStop(String i, Object[] args)
997 stop();
1000 public void menuReset(String i, Object[] args)
1002 reset();
1005 public void menuImport(String i, Object[] args)
1007 try {
1008 new ImportDiskImage();
1009 } catch(Exception e) {
1010 e.printStackTrace();
1014 public void menuNOFPU(String i, Object[] args)
1016 pc.setFPUHack();
1019 public void menuVGADRAW(String i, Object[] args)
1021 pc.setVGADrawHack();
1024 public void menuVGASCROLL2(String i, Object[] args)
1026 pc.setVGAScroll2Hack();
1029 public void menuQuit(String i, Object[] args)
1031 vPluginManager.shutdownEmulator();
1034 public void menuVRetraceStart(String i, Object[] args)
1036 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_START;
1037 menuManager.setSelected("Breakpoints→Trap VRetrace Start",
1038 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_START) == TraceTrap.TRACE_STOP_VRETRACE_START);
1041 public void menuVRetraceEnd(String i, Object[] args)
1043 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_END;
1044 menuManager.setSelected("Breakpoints→Trap VRetrace End",
1045 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_END) == TraceTrap.TRACE_STOP_VRETRACE_END);
1048 public void menuTimedStop(String i, Object[] args)
1050 for(int j = 0; j < stopLabel.length; j++) {
1051 String label = "Breakpoints→Timed Stops→" + stopLabel[j];
1052 if(i.equals(label)) {
1053 this.imminentTrapTime = stopTime[j];
1054 menuManager.select(label);
1055 } else
1056 menuManager.unselect(label);
1060 public void menuSave(String i, Object[] args)
1062 setTask(new SaveStateTask(((Boolean)args[0]).booleanValue()), SAVESTATE_LABEL);
1065 public void menuStatusDump(String i, Object[] args)
1067 setTask(new StatusDumpTask(), STATUSDUMP_LABEL);
1070 public void menuLoad(String i, Object[] args)
1072 setTask(new LoadStateTask(((Integer)args[0]).intValue()), LOADSTATE_LABEL);
1075 public void menuRAMDump(String i, Object[] args)
1077 setTask(new RAMDumpTask(((Boolean)args[0]).booleanValue()), RAMDUMP_LABEL);
1080 public void menuDumpDisk(String i, Object[] args)
1082 setTask(new ImageDumpTask(((Integer)args[0]).intValue()), IMAGEDUMP_LABEL);
1085 public void menuTruncate(String i, Object[] args)
1087 currentProject.events.truncateEventStream();
1090 public void menuChangeDisk(String i, Object[] args)
1092 changeFloppy(((Integer)args[0]).intValue(), ((Integer)args[1]).intValue());
1095 public void menuWriteProtect(String i, Object[] args)
1097 int disk = ((Integer)args[0]).intValue();
1098 writeProtect(disk, menuManager.isSelected(i));
1099 DiskImageSet imageSet = pc.getDisks();
1100 menuManager.setSelected(i, imageSet.lookupDisk(disk).isReadOnly());
1103 public void menuAddDisk(String i, Object[] args)
1105 setTask(new AddDiskTask(), ADDDISK_LABEL);
1108 public void menuChangeAuthors(String i, Object[] args)
1110 setTask(new ChangeAuthorsTask(), CHANGEAUTHORS_LABEL);
1113 public synchronized void start()
1115 if(taskToDo != null)
1116 return;
1117 vPluginManager.pcStarted();
1118 running = true;
1119 notifyAll();
1122 private String prettyPrintTime(long ts)
1124 String s = "";
1126 if(ts >= 1000000000)
1127 s = s + "" + (ts / 1000000000) + " ";
1128 if(ts >= 100000000)
1129 s = s + "" + (ts % 1000000000 / 100000000);
1130 if(ts >= 10000000)
1131 s = s + "" + (ts % 100000000 / 10000000);
1132 if(ts >= 1000000)
1133 s = s + "" + (ts % 10000000 / 1000000) + " ";
1134 if(ts >= 100000)
1135 s = s + "" + (ts % 1000000 / 100000);
1136 if(ts >= 10000)
1137 s = s + "" + (ts % 100000 / 10000);
1138 if(ts >= 1000)
1139 s = s + "" + (ts % 10000 / 1000) + " ";
1140 if(ts >= 100)
1141 s = s + "" + (ts % 1000 / 100);
1142 if(ts >= 10)
1143 s = s + "" + (ts % 100 / 10);
1144 s = s + "" + (ts % 10);
1145 return s;
1148 protected synchronized void stopNoWait()
1150 running = false;
1151 vPluginManager.pcStopped();
1152 Clock sysClock = (Clock)pc.getComponent(Clock.class);
1153 System.err.println("Notice: PC emulation stopped (at time sequence value " +
1154 prettyPrintTime(sysClock.getTime()) + ")");
1157 public synchronized void stop()
1159 pc.getTraceTrap().doPotentialTrap(TraceTrap.TRACE_STOP_IMMEDIATE);
1160 System.err.println("Informational: Waiting for PC to halt...");
1163 public JScrollPane getMonitorPane()
1165 return null;
1168 protected void reset()
1170 pc.reboot();
1173 public synchronized boolean isRunning()
1175 return running;
1178 private void changeFloppy(int drive, int image)
1182 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
1183 changer.changeFloppyDisk(drive, image);
1184 } catch (Exception e) {
1185 System.err.println("Error: Failed to change disk");
1186 errorDialog(e, "Failed to change disk", null, "Dismiss");
1190 private void writeProtect(int image, boolean state)
1194 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
1195 changer.wpFloppyDisk(image, state);
1196 } catch (Exception e) {
1197 System.err.println("Error: Failed to change floppy write protect");
1198 errorDialog(e, "Failed to write (un)protect floppy", null, "Dismiss");
1202 private class LoadStateTask extends AsyncGUITask
1204 File chosen;
1205 Exception caught;
1206 int _mode;
1207 long oTime;
1208 private static final int MODE_NORMAL = 1;
1209 private static final int MODE_PRESERVE = 2;
1210 private static final int MODE_MOVIEONLY = 3;
1212 public LoadStateTask(int mode)
1214 oTime = System.currentTimeMillis();
1215 chosen = null;
1216 _mode = mode;
1219 public LoadStateTask(String name, int mode)
1221 this(mode);
1222 chosen = new File(name);
1225 protected void runPrepare()
1227 if(chosen == null) {
1228 int returnVal = 0;
1229 if(_mode == MODE_PRESERVE)
1230 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot (PE)");
1231 else if(_mode == MODE_MOVIEONLY)
1232 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot (MO)");
1233 else
1234 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot");
1235 chosen = snapshotFileChooser.getSelectedFile();
1237 if (returnVal != 0)
1238 chosen = null;
1242 protected void runFinish()
1244 if(chosen == null)
1245 return;
1247 if(caught == null) {
1248 try {
1249 connectPC(pc = currentProject.pc);
1250 doCycle(pc);
1251 System.err.println("Informational: Loadstate done on "+chosen.getAbsolutePath());
1252 } catch(Exception e) {
1253 caught = e;
1256 if(caught != null) {
1257 errorDialog(caught, "Load savestate failed", window, "Dismiss");
1259 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1260 PCControl.this.vPluginManager.signalCommandCompletion();
1263 protected void runTask()
1265 if(chosen == null)
1266 return;
1268 try {
1269 System.err.println("Informational: Loading a snapshot of JPC-RR");
1270 long times1 = System.currentTimeMillis();
1271 JRSRArchiveReader reader = new JRSRArchiveReader(chosen.getAbsolutePath());
1273 PC.PCFullStatus fullStatus = PC.loadSavestate(reader, _mode == MODE_PRESERVE, _mode == MODE_MOVIEONLY,
1274 currentProject);
1276 currentProject = fullStatus;
1278 reader.close();
1279 long times2 = System.currentTimeMillis();
1280 System.err.println("Informational: Loadstate complete (" + (times2 - times1) + "ms).");
1281 } catch(Exception e) {
1282 caught = e;
1287 private synchronized void doCycleDedicatedThread(PC _pc)
1289 if(_pc == null) {
1290 cycleDone = true;
1291 return;
1293 DisplayController dc = (DisplayController)_pc.getComponent(DisplayController.class);
1294 dc.getOutputDevice().holdOutput(_pc.getTime());
1295 cycleDone = true;
1296 notifyAll();
1299 private void doCycle(PC _pc)
1301 final PC _xpc = _pc;
1302 cycleDone = false;
1303 (new Thread(new Runnable() { public void run() { doCycleDedicatedThread(_xpc); }}, "VGA output cycle thread")).start();
1304 while(cycleDone)
1305 try {
1306 synchronized(this) {
1307 if(cycleDone)
1308 break;
1309 wait();
1311 } catch(Exception e) {
1315 private class SaveStateTask extends AsyncGUITask
1317 File chosen;
1318 Exception caught;
1319 boolean movieOnly;
1320 long oTime;
1322 public SaveStateTask(boolean movie)
1324 oTime = System.currentTimeMillis();
1325 chosen = null;
1326 movieOnly = movie;
1329 public SaveStateTask(String name, boolean movie)
1331 this(movie);
1332 chosen = new File(name);
1335 protected void runPrepare()
1337 if(chosen == null) {
1338 int returnVal = snapshotFileChooser.showDialog(window, movieOnly ? "Save JPC-RR Movie" :
1339 "Save JPC-RR Snapshot");
1340 chosen = snapshotFileChooser.getSelectedFile();
1342 if (returnVal != 0)
1343 chosen = null;
1347 protected void runFinish()
1349 if(caught != null) {
1350 errorDialog(caught, "Saving savestate failed", window, "Dismiss");
1352 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1353 PCControl.this.vPluginManager.signalCommandCompletion();
1356 protected void runTask()
1358 if(chosen == null)
1359 return;
1361 JRSRArchiveWriter writer = null;
1363 try {
1364 System.err.println("Informational: Savestating...");
1365 long times1 = System.currentTimeMillis();
1366 writer = new JRSRArchiveWriter(chosen.getAbsolutePath());
1367 PC.saveSavestate(writer, currentProject, movieOnly, uncompressedSave);
1368 renameFile(chosen, new File(chosen.getAbsolutePath() + ".backup"));
1369 writer.close();
1370 long times2 = System.currentTimeMillis();
1371 System.err.println("Informational: Savestate complete (" + (times2 - times1) + "ms). on"+chosen.getAbsolutePath());
1372 } catch(Exception e) {
1373 if(writer != null)
1374 try { writer.rollback(); } catch(Exception f) {}
1375 caught = e;
1380 private class StatusDumpTask extends AsyncGUITask
1382 File chosen;
1383 Exception caught;
1385 public StatusDumpTask()
1387 chosen = null;
1390 public StatusDumpTask(String name)
1392 this();
1393 chosen = new File(name);
1396 protected void runPrepare()
1398 if(chosen == null) {
1399 int returnVal = snapshotFileChooser.showDialog(window, "Save Status dump");
1400 chosen = snapshotFileChooser.getSelectedFile();
1402 if (returnVal != 0)
1403 chosen = null;
1407 protected void runFinish()
1409 if(caught != null) {
1410 errorDialog(caught, "Status dump failed", window, "Dismiss");
1412 PCControl.this.vPluginManager.signalCommandCompletion();
1415 protected void runTask()
1417 if(chosen == null)
1418 return;
1420 try {
1421 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1422 PrintStream out = new PrintStream(outb, false, "UTF-8");
1423 StatusDumper sd = new StatusDumper(out);
1424 pc.dumpStatus(sd);
1425 out.flush();
1426 outb.flush();
1427 System.err.println("Informational: Dumped " + sd.dumpedObjects() + " objects");
1428 } catch(Exception e) {
1429 caught = e;
1434 private class RAMDumpTask extends AsyncGUITask
1436 File chosen;
1437 Exception caught;
1438 boolean binary;
1440 public RAMDumpTask(boolean binFlag)
1442 chosen = null;
1443 binary = binFlag;
1446 public RAMDumpTask(String name, boolean binFlag)
1448 this(binFlag);
1449 chosen = new File(name);
1452 protected void runPrepare()
1454 if(chosen == null) {
1455 int returnVal;
1456 if(binary)
1457 returnVal = snapshotFileChooser.showDialog(window, "Save RAM dump");
1458 else
1459 returnVal = snapshotFileChooser.showDialog(window, "Save RAM hexdump");
1460 chosen = snapshotFileChooser.getSelectedFile();
1462 if (returnVal != 0)
1463 chosen = null;
1467 protected void runFinish()
1469 if(caught != null) {
1470 errorDialog(caught, "RAM dump failed", window, "Dismiss");
1472 PCControl.this.vPluginManager.signalCommandCompletion();
1475 protected void runTask()
1477 if(chosen == null)
1478 return;
1480 try {
1481 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1482 byte[] pagebuf = new byte[4096];
1483 PhysicalAddressSpace addr = (PhysicalAddressSpace)pc.getComponent(PhysicalAddressSpace.class);
1484 int lowBound = addr.findFirstRAMPage(0);
1485 int firstUndumped = 0;
1486 int highBound = 0;
1487 int present = 0;
1488 while(lowBound >= 0) {
1489 for(; firstUndumped < lowBound; firstUndumped++)
1490 dumpPage(outb, firstUndumped, null);
1491 addr.readRAMPage(firstUndumped++, pagebuf);
1492 dumpPage(outb, lowBound, pagebuf);
1493 present++;
1494 highBound = lowBound + 1;
1495 lowBound = addr.findFirstRAMPage(++lowBound);
1497 outb.flush();
1498 System.err.println("Informational: Dumped machine RAM (" + highBound + " pages examined, " +
1499 present + " pages present).");
1500 } catch(Exception e) {
1501 caught = e;
1505 private byte charForHex(int hvalue)
1507 if(hvalue < 10)
1508 return (byte)(hvalue + 48);
1509 else if(hvalue > 9 && hvalue < 16)
1510 return (byte)(hvalue + 55);
1511 else
1512 System.err.println("Unknown hex value: " + hvalue + ".");
1513 return 90;
1516 private void dumpPage(OutputStream stream, int pageNo, byte[] buffer) throws IOException
1518 int pageBufSize;
1519 pageNo = pageNo & 0xFFFFF; //Cut page numbers out of range.
1520 if(!binary && buffer == null)
1521 return; //Don't dump null pages in non-binary mode.
1522 if(binary)
1523 pageBufSize = 4096; //Binary page buffer is 4096 bytes.
1524 else
1525 pageBufSize = 14592; //Hexdump page buffer is 14592 bytes.
1526 byte[] outputPage = new byte[pageBufSize];
1527 if(buffer != null && binary) {
1528 System.arraycopy(buffer, 0, outputPage, 0, 4096);
1529 } else if(buffer != null) { //Hex mode
1530 for(int i = 0; i < 256; i++) {
1531 for(int j = 0; j < 57; j++) {
1532 if(j < 5)
1533 outputPage[57 * i + j] = charForHex((pageNo >>> (4 * (4 - j))) & 0xF);
1534 else if(j == 5)
1535 outputPage[57 * i + j] = charForHex(i / 16);
1536 else if(j == 6)
1537 outputPage[57 * i + j] = charForHex(i % 16);
1538 else if(j == 7)
1539 outputPage[57 * i + j] = 48;
1540 else if(j == 56)
1541 outputPage[57 * i + j] = 10;
1542 else if(j % 3 == 2)
1543 outputPage[57 * i + j] = 32;
1544 else if(j % 3 == 0)
1545 outputPage[57 * i + j] = charForHex(((int)buffer[16 * i + j / 3 - 3] & 0xFF) / 16);
1546 else if(j % 3 == 1)
1547 outputPage[57 * i + j] = charForHex(buffer[16 * i + j / 3 - 3] & 0xF);
1548 else
1549 System.err.println("Error: dumpPage: unhandled j = " + j + ".");
1553 stream.write(outputPage);
1557 private class ImageDumpTask extends AsyncGUITask
1559 File chosen;
1560 Exception caught;
1561 int index;
1563 public ImageDumpTask(int _index)
1565 chosen = null;
1566 index = _index;
1569 public ImageDumpTask(String name, int index)
1571 this(index);
1572 chosen = new File(name);
1575 protected void runPrepare()
1577 if(chosen == null) {
1578 int returnVal;
1579 returnVal = snapshotFileChooser.showDialog(window, "Save Image dump");
1580 chosen = snapshotFileChooser.getSelectedFile();
1582 if (returnVal != 0)
1583 chosen = null;
1587 protected void runFinish()
1589 if(caught != null) {
1590 errorDialog(caught, "Image dump failed", window, "Dismiss");
1592 PCControl.this.vPluginManager.signalCommandCompletion();
1595 protected void runTask()
1597 if(chosen == null)
1598 return;
1600 try {
1601 DiskImage dev;
1602 if(index < 0)
1603 dev = pc.getDrives().getHardDrive(-index).getImage();
1604 else
1605 dev = pc.getDisks().lookupDisk(index);
1606 if(dev == null)
1607 throw new IOException("Trying to dump nonexistent disk");
1608 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1609 byte[] buf = new byte[512];
1610 long sectors = dev.getTotalSectors();
1611 for(long i = 0; i < sectors; i++) {
1612 dev.read(i, buf, 1);
1613 outb.write(buf);
1615 outb.close();
1616 System.err.println("Informational: Dumped disk image (" + sectors + " sectors).");
1617 } catch(Exception e) {
1618 caught = e;
1623 private class AssembleTask extends AsyncGUITask
1625 Exception caught;
1626 boolean canceled;
1628 public AssembleTask()
1630 canceled = false;
1633 protected void runPrepare()
1635 try {
1636 configDialog.popUp();
1637 } catch(Exception e) {
1638 caught = e;
1642 protected void runFinish()
1644 if(caught == null && !canceled) {
1645 try {
1646 currentProject.projectID = randomHexes(24);
1647 currentProject.rerecords = 0;
1648 currentProject.events = new EventRecorder();
1649 currentProject.events.attach(pc, null);
1650 currentProject.savestateID = null;
1651 currentProject.extraHeaders = null;
1652 currentProject.events.setRerecordCount(0);
1653 currentProject.events.setHeaders(currentProject.extraHeaders);
1654 connectPC(pc);
1655 } catch(Exception e) {
1656 caught = e;
1659 if(caught != null) {
1660 errorDialog(caught, "PC Assembly failed", window, "Dismiss");
1662 PCControl.this.vPluginManager.signalCommandCompletion();
1665 protected void runTask()
1667 if(caught != null)
1668 return;
1669 PC.PCHardwareInfo hw = configDialog.waitClose();
1670 if(hw == null) {
1671 canceled = true;
1672 return;
1675 try {
1676 pc = PC.createPC(hw);
1677 } catch(Exception e) {
1678 caught = e;
1683 private class AddDiskTask extends AsyncGUITask
1685 Exception caught;
1686 NewDiskDialog dd;
1688 public AddDiskTask()
1690 dd = new NewDiskDialog();
1693 protected void runPrepare()
1697 protected void runFinish()
1699 if(caught != null) {
1700 errorDialog(caught, "Adding disk failed", window, "Dismiss");
1702 try {
1703 updateDisks();
1704 } catch(Exception e) {
1705 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
1707 PCControl.this.vPluginManager.signalCommandCompletion();
1710 protected void runTask()
1712 NewDiskDialog.Response res = dd.waitClose();
1713 if(res == null) {
1714 return;
1716 try {
1717 DiskImage img;
1718 pc.getDisks().addDisk(img = new DiskImage(res.diskFile, false));
1719 img.setName(res.diskName);
1720 } catch(Exception e) {
1721 caught = e;
1726 private class ChangeAuthorsTask extends AsyncGUITask
1728 Exception caught;
1729 AuthorsDialog ad;
1731 public ChangeAuthorsTask()
1733 int authors = 0;
1734 int headers = 0;
1735 AuthorsDialog.AuthorElement[] authorNames = null;
1736 if(currentProject != null)
1737 authorNames = AuthorsDialog.readAuthorsFromHeaders(currentProject.extraHeaders);
1739 ad = new AuthorsDialog(authorNames);
1742 protected void runPrepare()
1746 protected void runFinish()
1748 if(caught != null) {
1749 errorDialog(caught, "Changing authors failed", window, "Dismiss");
1751 PCControl.this.vPluginManager.signalCommandCompletion();
1754 protected void runTask()
1756 AuthorsDialog.Response res = ad.waitClose();
1757 if(res == null) {
1758 return;
1760 try {
1761 currentProject.extraHeaders = AuthorsDialog.rewriteHeaderAuthors(currentProject.extraHeaders,
1762 res.authors);
1763 currentProject.events.setHeaders(currentProject.extraHeaders);
1764 } catch(Exception e) {
1765 caught = e;