Send clicks to monitor panel to Lua (for instance for mouse emulation)
[jpcrr.git] / org / jpc / plugins / PCControl.java
blobddcf9416ae58e56a927457a401163239640a3a64
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 JFileChooser otherFileChooser;
107 private DropTarget dropTarget;
108 private LoadstateDropTarget loadstateDropTarget;
109 private RAWDumper dumper;
111 private Set<String> disks;
113 protected PC pc;
115 private int trapFlags;
117 private volatile long profile;
118 private volatile boolean running;
119 private volatile boolean waiting;
120 private boolean uncompressedSave;
121 private static final long[] stopTime;
122 private static final String[] stopLabel;
123 private volatile long imminentTrapTime;
124 private boolean shuttingDown;
125 private int nativeWidth;
126 private int nativeHeight;
127 private PCConfigDialog configDialog;
128 private MenuManager menuManager;
129 private Map<String, List<String[]> > extraActions;
130 private PCMonitorPanel panel;
131 private JLabel statusBar;
132 private volatile int currentResolutionWidth;
133 private volatile int currentResolutionHeight;
134 private volatile Runnable taskToDo;
135 private volatile String taskLabel;
136 private boolean cycleDone;
137 private Map<String, Class<?>> debugInClass;
138 private Map<String, Boolean> debugState;
140 private PC.PCFullStatus currentProject;
142 class LoadstateDropTarget implements DropTargetListener
144 public void dragEnter(DropTargetDragEvent e) {}
145 public void dragOver(DropTargetDragEvent e) {}
146 public void dragExit(DropTargetEvent e) {}
147 public void dropActionChanged(DropTargetDragEvent e) {}
149 public void drop(DropTargetDropEvent e)
151 if(running) {
152 e.rejectDrop();
153 return;
155 e.acceptDrop(DnDConstants.ACTION_COPY);
156 int i = 0;
157 for(DataFlavor f : e.getCurrentDataFlavors()) {
158 try {
159 Transferable t = e.getTransferable();
160 Object d = t.getTransferData(f);
161 if(f.isMimeTypeEqual("text/uri-list") && d.getClass() == String.class) {
162 String url = (String)d;
163 if(url.indexOf(10) >= 0) {
164 callShowOptionDialog(window, "Hey, only single file at time!",
165 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
166 new String[]{"Dismiss"}, "Dismiss");
167 e.dropComplete(false);
168 return;
170 e.dropComplete(handleURLDropped(url));
171 return;
173 } catch(Exception ex) {
174 errorDialog(ex, "Failed to get DnD data", null, "Dismiss");
175 e.dropComplete(false);
176 return;
179 for(DataFlavor f : e.getCurrentDataFlavors()) {
180 i = 0;
181 try {
182 i++;
183 Transferable t = e.getTransferable();
184 Object d = t.getTransferData(f);
185 System.err.println("Notice: Format #" + i + ":" + d.getClass().getName() + "(" + f + ")");
186 } catch(Exception ex) {
187 System.err.println("Notice: Format #" + i + ": <ERROR>(" + f + ")");
190 callShowOptionDialog(window, "Can't recognize file to load from drop (debugging information dumped to console).",
191 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
192 new String[]{"Dismiss"}, "Dismiss");
193 e.dropComplete(false);
197 private boolean handleURLDropped(String url)
199 if(!url.startsWith("file:///")) {
200 callShowOptionDialog(window, "Can't load remote resource.",
201 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
202 new String[]{"Dismiss"}, "Dismiss");
203 return false;
205 url = url.substring(7);
206 setTask(new LoadStateTask(url, LoadStateTask.MODE_NORMAL), LOADSTATE_LABEL);
207 return true;
210 static
212 stopTime = new long[] {-1, 0, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000,
213 5000000, 10000000, 20000000, 50000000, 100000000, 200000000, 500000000, 1000000000, 2000000000,
214 5000000000L, 10000000000L, 20000000000L, 50000000000L};
215 stopLabel = new String[] {"(unbounded)", "(singlestep)", "1µs", "2µs", "5µs", "10µs", "20µs", "50µs", "100µs",
216 "200µs", "500µs","1ms", "2ms", "5ms", "10ms", "20ms", "50ms", "100ms", "200ms", "500ms", "1s", "2s", "5s",
217 "10s", "20s", "50s"};
220 public boolean systemShutdown()
222 if(!running || pc == null)
223 return true;
224 //We are running. Do the absolute minimum since we are running in very delicate context.
225 shuttingDown = true;
226 stop();
227 while(running);
228 return true;
231 public void reconnect(PC pc)
233 panel.setPC(pc);
234 pcStopping(); //Do the equivalent effects.
235 updateStatusBar();
236 updateDebug();
239 public void notifySizeChange(int w, int h)
241 final int w2 = w;
242 final int h2 = h;
244 SwingUtilities.invokeLater(new Runnable() { public void run() {
245 window.pack();
246 Dimension d = window.getSize();
247 nativeWidth = d.width;
248 nativeHeight = d.height;
249 currentResolutionWidth = w2;
250 currentResolutionHeight = h2;
251 updateStatusBarEventThread();
252 }});
255 public void notifyFrameReceived(int w, int h)
257 currentResolutionWidth = w;
258 currentResolutionHeight = h;
259 updateStatusBar();
262 private void setTrapFlags()
264 pc.getTraceTrap().setTrapFlags(trapFlags);
267 public void pcStarting()
269 profile = PROFILE_HAVE_PC | PROFILE_RUNNING | (profile & (PROFILE_DUMPING | PROFILE_NOT_DUMPING));
270 if(currentProject != null && currentProject.events != null);
271 profile |= PROFILE_EVENTS;
272 if(pc.getCDROMIndex() >= 0)
273 profile |= PROFILE_CDROM;
275 menuManager.setProfile(profile);
277 if (running)
278 return;
280 setTrapFlags();
282 Clock sysClock = (Clock)pc.getComponent(Clock.class);
283 long current = sysClock.getTime();
284 if(imminentTrapTime > 0) {
285 pc.getTraceTrap().setTrapTime(current + imminentTrapTime);
286 } else if(imminentTrapTime == 0) {
287 //Hack: We set trace trap to trap immediately. It comes too late to abort next instruction, but
288 //early enough to abort one after that.
289 pc.getTraceTrap().setTrapTime(current);
291 if(currentProject.events != null)
292 currentProject.events.setPCRunStatus(true);
295 public void pcStopping()
297 if(currentProject.events != null)
298 currentProject.events.setPCRunStatus(false);
299 if(shuttingDown)
300 return; //Don't mess with UI when shutting down.
303 profile = PROFILE_STOPPED | (profile & (PROFILE_DUMPING | PROFILE_NOT_DUMPING));
304 if(pc != null)
305 profile |= PROFILE_HAVE_PC;
306 else
307 profile |= PROFILE_NO_PC;
308 if(currentProject != null && currentProject.events != null);
309 profile |= PROFILE_EVENTS;
310 if(pc.getCDROMIndex() >= 0)
311 profile |= PROFILE_CDROM;
313 menuManager.setProfile(profile);
314 updateStatusBar();
316 try {
317 updateDisks();
318 } catch(Exception e) {
319 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
322 if(pc != null) {
323 pc.getTraceTrap().clearTrapTime();
324 pc.getTraceTrap().getAndClearTrapActive();
328 private String diskNameByIdx(int idx)
330 return pc.getDisks().lookupDisk(idx).getName();
333 private void updateDisks() throws Exception
335 for(String x : disks)
336 menuManager.removeMenuItem(x);
338 disks.clear();
340 if(pc == null)
341 return;
343 DiskImageSet imageSet = pc.getDisks();
344 DriveSet driveset = pc.getDrives();
345 int[] floppies = imageSet.diskIndicesByType(BlockDevice.Type.FLOPPY);
346 int[] cdroms = imageSet.diskIndicesByType(BlockDevice.Type.CDROM);
348 for(int i = 0; i < floppies.length; i++) {
349 String name = diskNameByIdx(floppies[i]);
350 menuManager.addMenuItem("Drives→fda→" + name, this, "menuChangeDisk", new Object[]{new Integer(0),
351 new Integer(floppies[i])}, PROFILE_HAVE_PC);
352 menuManager.addMenuItem("Drives→fdb→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
353 new Integer(floppies[i])}, PROFILE_HAVE_PC);
354 menuManager.addMenuItem("Drives→dump→" + name, this, "menuDumpDisk", new Object[]{
355 new Integer(floppies[i])}, PROFILE_HAVE_PC);
356 menuManager.addSelectableMenuItem("Drives→Write Protect→" + name, this, "menuWriteProtect",
357 new Object[]{new Integer(floppies[i])}, imageSet.lookupDisk(floppies[i]).isReadOnly(),
358 PROFILE_HAVE_PC);
359 disks.add("Drives→fda→" + name);
360 disks.add("Drives→fdb→" + name);
361 disks.add("Drives→Write Protect→" + name);
362 disks.add("Drives→dump→" + name);
364 BlockDevice dev;
365 DriveSet drives = pc.getDrives();
366 profile = profile & ~(PROFILE_HAVE_HDA | PROFILE_HAVE_HDB | PROFILE_HAVE_HDC | PROFILE_HAVE_HDD);
367 dev = drives.getHardDrive(0);
368 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDA : 0);
369 dev = drives.getHardDrive(1);
370 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDB : 0);
371 dev = drives.getHardDrive(2);
372 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDC : 0);
373 dev = drives.getHardDrive(3);
374 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDD : 0);
375 menuManager.setProfile(profile);
378 for(int i = 0; i < cdroms.length; i++) {
379 String name = diskNameByIdx(cdroms[i]);
380 menuManager.addMenuItem("Drives→CD-ROM→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
381 new Integer(cdroms[i])}, PROFILE_HAVE_PC | PROFILE_CDROM);
382 disks.add("Drives→CD-ROM→" + name);
386 private synchronized boolean setTask(Runnable task, String label)
388 boolean run = running;
389 if(run || taskToDo != null)
390 return false; //Can't do tasks with PC running or existing task.
391 taskToDo = task;
392 taskLabel = label;
393 notifyAll();
394 updateStatusBar();
395 return true;
398 public void main()
400 boolean wasRunning = false;
401 while(true) { //We will be killed by JVM.
402 //Wait for us to become runnable again.
403 while((!running || pc == null) && taskToDo == null) {
404 if(!running && wasRunning && pc != null)
405 pc.stop();
406 wasRunning = running;
407 try {
408 synchronized(this) {
409 if((running && pc != null) || taskToDo != null)
410 continue;
411 waiting = true;
412 notifyAll();
413 wait();
414 waiting = false;
416 } catch(Exception e) {
420 if(running && !wasRunning)
421 pc.start();
422 wasRunning = running;
424 if(taskToDo != null) {
425 taskToDo.run();
426 taskToDo = null;
427 updateStatusBar();
428 continue;
431 try {
432 pc.execute();
433 if(pc.getHitTraceTrap()) {
434 if(pc.getAndClearTripleFaulted())
435 callShowOptionDialog(window, "CPU shut itself down due to triple fault. Rebooting the system.",
436 "Triple fault!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
437 new String[]{"Dismiss"}, "Dismiss");
438 if(shuttingDown)
439 stopNoWait();
440 else
441 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
442 running = false;
443 doCycle(pc);
445 } catch (Exception e) {
446 running = false;
447 doCycle(pc);
448 errorDialog(e, "Hardware emulator internal error", window, "Dismiss");
449 try {
450 if(shuttingDown)
451 stopNoWait();
452 else
453 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
454 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
455 } catch (Exception f) {
462 public void connectPC(PC pc)
464 currentProject.pc = pc;
465 vPluginManager.reconnect(pc);
466 this.pc = pc;
469 private void startExternal()
471 if(pc != null && !running)
472 if(!SwingUtilities.isEventDispatchThread())
473 try {
474 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.start(); }});
475 } catch(Exception e) {
477 else
478 start();
481 private void stopExternal()
483 if(pc != null && running)
484 if(!SwingUtilities.isEventDispatchThread())
485 try {
486 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.stop(); }});
487 } catch(Exception e) {
489 else
490 stop();
493 public boolean eci_state_save(String filename)
495 return setTask(new SaveStateTask(filename, false), SAVESTATE_LABEL);
498 public boolean eci_state_dump(String filename)
500 return setTask(new StatusDumpTask(filename), STATUSDUMP_LABEL);
503 public boolean eci_movie_save(String filename)
505 return setTask(new SaveStateTask(filename, true), SAVESTATE_LABEL);
508 public boolean eci_state_load(String filename)
510 return setTask(new LoadStateTask(filename, LoadStateTask.MODE_NORMAL), LOADSTATE_LABEL);
513 public boolean eci_state_load_noevents(String filename)
515 return setTask(new LoadStateTask(filename, LoadStateTask.MODE_PRESERVE), LOADSTATE_LABEL);
518 public boolean eci_movie_load(String filename)
520 return setTask(new LoadStateTask(filename, LoadStateTask.MODE_MOVIEONLY), LOADSTATE_LABEL);
523 public boolean eci_pc_assemble()
525 return setTask(new AssembleTask(), ASSEMBLE_LABEL);
528 public boolean eci_ram_dump_text(String filename)
530 return setTask(new RAMDumpTask(filename, false), RAMDUMP_LABEL);
533 public boolean eci_image_dump(String filename, int index)
535 return setTask(new ImageDumpTask(filename, index), IMAGEDUMP_LABEL);
538 public boolean eci_ram_dump_binary(String filename)
540 return setTask(new RAMDumpTask(filename, true), RAMDUMP_LABEL);
543 public void eci_trap_vretrace_start_on()
545 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_START;
548 public void eci_trap_vretrace_start_off()
550 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_START;
553 public void eci_trap_vretrace_end_on()
555 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_END;
558 public void eci_trap_vretrace_end_off()
560 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_END;
563 public void eci_trap_timed_disable()
565 this.imminentTrapTime = -1;
568 public void eci_trap_timed(Long time)
570 this.imminentTrapTime = time.longValue();
573 public void eci_pc_start()
575 startExternal();
578 public void eci_pc_stop()
580 stopExternal();
583 public void eci_pccontrol_setwinpos(Integer x, Integer y)
585 moveWindow(window, x.intValue(), y.intValue(), nativeWidth, nativeHeight);
588 public void eci_sendevent(String clazz, String[] rargs)
590 System.err.println("Event to: '" + clazz + "':");
591 for(int i = 0; i < rargs.length; i++) {
592 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
594 if(currentProject.events != null) {
595 try {
596 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
597 currentProject.events.addEvent(0L, x, rargs);
598 } catch(Exception e) {
599 System.err.println("Error adding event: " + e.getMessage());
604 public void eci_sendevent_lowbound(Long timeMin, String clazz, String[] rargs)
606 System.err.println("Event to: '" + clazz + "' (with low bound of " + timeMin + "):");
607 for(int i = 0; i < rargs.length; i++) {
608 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
610 if(currentProject.events != null) {
611 try {
612 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
613 currentProject.events.addEvent(timeMin, x, rargs);
614 } catch(Exception e) {
615 System.err.println("Error adding event: " + e.getMessage());
620 public void eci_memory_read(Long address, Integer size)
622 if(currentProject.pc != null) {
623 long addr = address.longValue();
624 long _size = size.intValue();
625 long ret = 0;
626 PhysicalAddressSpace addrSpace;
627 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
628 return;
630 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
631 if(_size == 1)
632 ret = (long)addrSpace.getByte((int)addr) & 0xFF;
633 else if(_size == 2)
634 ret = (long)addrSpace.getWord((int)addr) & 0xFFFF;
635 else if(_size == 4)
636 ret = (long)addrSpace.getDoubleWord((int)addr) & 0xFFFFFFFFL;
638 vPluginManager.returnValue(ret);
642 public void eci_memory_write(Long address, Long value, Integer size)
644 if(currentProject.pc != null) {
645 long addr = address.longValue();
646 long _size = size.intValue();
647 long _value = value.longValue();
648 PhysicalAddressSpace addrSpace;
649 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
650 return;
652 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
653 if(_size == 1)
654 addrSpace.setByte((int)addr, (byte)_value);
655 else if(_size == 2)
656 addrSpace.setWord((int)addr, (short)_value);
657 else if(_size == 4)
658 addrSpace.setDoubleWord((int)addr, (int)_value);
662 public PCControl(Plugins manager, String args) throws Exception
664 this(manager);
666 UTFInputLineStream file = null;
667 Map<String, String> params = parseStringToComponents(args);
668 Set<String> used = new HashSet<String>();
669 String extramenu = params.get("extramenu");
670 String uncompress = params.get("uncompressedsave");
671 if(uncompress != null)
672 uncompressedSave = true;
673 if(extramenu == null)
674 return;
675 try {
676 file = new UTFInputLineStream(new FileInputStream(extramenu));
678 while(true) {
679 boolean exists = false;
680 String[] line = nextParseLine(file);
681 if(line == null)
682 break;
683 if(line.length < 3 || line[0].charAt(0) == '→') {
684 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
685 continue;
687 if(line[0].length() == 0 || line[0].charAt(line[0].length() - 1) == '→') {
688 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
689 continue;
691 if(line[0].indexOf("→→") >= 0) {
692 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
693 continue;
695 if(used.contains(line[0]))
696 exists = true;
698 KeyStroke stroke = null;
699 if(!line[1].equals("<>")) {
700 stroke = KeyStroke.getKeyStroke(line[1]);
701 if(stroke == null) {
702 System.err.println("Warning: Bad keystroke '" + line[1] + "'.");
707 String[] lineCommand = Arrays.copyOfRange(line, 2, line.length);
708 used.add(line[0]);
709 List<String[]> commandList = extraActions.get(line[0]);
710 if(commandList == null)
711 extraActions.put(line[0], commandList = new ArrayList<String[]>());
712 commandList.add(lineCommand);
714 if(!exists)
715 menuManager.addMenuItem("Extra→" + line[0], this, "menuExtra", new String[]{line[0]}, PROFILE_ALWAYS,
716 stroke);
718 file.close();
719 } catch(IOException e) {
720 errorDialog(e, "Failed to load extra menu defintions", null, "dismiss");
721 if(file != null)
722 file.close();
724 window.setJMenuBar(menuManager.getMainBar());
727 public PCControl(Plugins manager) throws Exception
729 window = new JFrame("JPC-RR" + Misc.emuname);
731 if(DiskImage.getLibrary() == null)
732 throw new Exception("PCControl plugin requires disk library");
734 running = false;
735 shuttingDown = false;
737 debugInClass = new HashMap<String, Class<?>>();
738 debugState = new HashMap<String, Boolean>();
740 configDialog = new PCConfigDialog();
741 extraActions = new HashMap<String, List<String[]> >();
742 menuManager = new MenuManager();
744 menuManager.setProfile(profile = (PROFILE_NO_PC | PROFILE_STOPPED | PROFILE_NOT_DUMPING));
746 menuManager.addMenuItem("System→Assemble", this, "menuAssemble", null, PROFILE_STOPPED);
747 menuManager.addMenuItem("System→Start", this, "menuStart", null, PROFILE_STOPPED | PROFILE_HAVE_PC);
748 menuManager.addMenuItem("System→Stop", this, "menuStop", null, PROFILE_RUNNING);
749 menuManager.addMenuItem("System→Reset", this, "menuReset", null, PROFILE_HAVE_PC);
750 menuManager.addMenuItem("System→Start dumping", this, "menuStartDump", null, PROFILE_STOPPED | PROFILE_NOT_DUMPING);
751 menuManager.addMenuItem("System→Stop dumping", this, "menuStopDump", null, PROFILE_STOPPED | PROFILE_DUMPING);
752 menuManager.addMenuItem("System→Quit", this, "menuQuit", null, PROFILE_ALWAYS);
753 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace Start", this, "menuVRetraceStart", null, false,
754 PROFILE_ALWAYS);
755 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace End", this, "menuVRetraceEnd", null, false,
756 PROFILE_ALWAYS);
757 menuManager.addMenuItem("Snapshot→Change Run Authors", this, "menuChangeAuthors", null, PROFILE_HAVE_PC);
758 menuManager.addMenuItem("Snapshot→Save→Snapshot", this, "menuSave", new Object[]{new Boolean(false)},
759 PROFILE_HAVE_PC | PROFILE_STOPPED);
760 menuManager.addMenuItem("Snapshot→Save→Movie", this, "menuSave", new Object[]{new Boolean(true)},
761 PROFILE_HAVE_PC | PROFILE_STOPPED);
762 menuManager.addMenuItem("Snapshot→Save→Status Dump", this, "menuStatusDump", null,
763 PROFILE_HAVE_PC | PROFILE_STOPPED);
764 menuManager.addMenuItem("Snapshot→Load→Snapshot", this, "menuLoad",
765 new Object[]{new Integer(LoadStateTask.MODE_NORMAL)}, PROFILE_STOPPED);
766 menuManager.addMenuItem("Snapshot→Load→Snapshot (preserve events)", this, "menuLoad",
767 new Object[]{new Integer(LoadStateTask.MODE_PRESERVE)}, PROFILE_STOPPED | PROFILE_EVENTS);
768 menuManager.addMenuItem("Snapshot→Load→Movie", this, "menuLoad",
769 new Object[]{new Integer(LoadStateTask.MODE_MOVIEONLY)}, PROFILE_STOPPED);
770 menuManager.addMenuItem("Snapshot→RAM Dump→Hexadecimal", this, "menuRAMDump", new Object[]{new Boolean(false)},
771 PROFILE_HAVE_PC | PROFILE_STOPPED);
772 menuManager.addMenuItem("Snapshot→RAM Dump→Binary", this, "menuRAMDump", new Object[]{new Boolean(true)},
773 PROFILE_HAVE_PC | PROFILE_STOPPED);
774 menuManager.addMenuItem("Snapshot→Truncate Event Stream", this, "menuTruncate", null,
775 PROFILE_STOPPED | PROFILE_EVENTS);
777 for(int i = 0; i < stopLabel.length; i++) {
778 menuManager.addSelectableMenuItem("Breakpoints→Timed Stops→" + stopLabel[i], this, "menuTimedStop",
779 null, (i == 0), PROFILE_ALWAYS);
781 imminentTrapTime = -1;
783 menuManager.addMenuItem("Drives→fda→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(0),
784 new Integer(-1)}, PROFILE_HAVE_PC);
785 menuManager.addMenuItem("Drives→fdb→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(1),
786 new Integer(-1)}, PROFILE_HAVE_PC);
787 menuManager.addMenuItem("Drives→CD-ROM→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(2),
788 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_CDROM);
789 menuManager.addMenuItem("Drives→Add image", this, "menuAddDisk", null, PROFILE_HAVE_PC);
790 menuManager.addMenuItem("Drives→Import Image", this, "menuImport", null, PROFILE_ALWAYS);
791 menuManager.addMenuItem("Drives→dump→HDA", this, "menuDumpDisk", new Object[]{
792 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDA);
793 menuManager.addMenuItem("Drives→dump→HDB", this, "menuDumpDisk", new Object[]{
794 new Integer(-2)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDB);
795 menuManager.addMenuItem("Drives→dump→HDC", this, "menuDumpDisk", new Object[]{
796 new Integer(-3)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDC);
797 menuManager.addMenuItem("Drives→dump→HDD", this, "menuDumpDisk", new Object[]{
798 new Integer(-4)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDD);
799 menuManager.addMenuItem("Debug→Hacks→NO_FPU", this, "menuNOFPU", null, PROFILE_HAVE_PC);
800 menuManager.addMenuItem("Debug→Hacks→VGA_DRAW", this, "menuVGADRAW", null, PROFILE_HAVE_PC);
801 menuManager.addMenuItem("Debug→Hacks→VGA_SCROLL_2", this, "menuVGASCROLL2", null, PROFILE_HAVE_PC);
803 disks = new HashSet<String>();
804 currentProject = new PC.PCFullStatus();
805 this.pc = null;
806 this.vPluginManager = manager;
808 panel = new PCMonitorPanel(this, manager.getOutputConnector());
809 loadstateDropTarget = new LoadstateDropTarget();
810 dropTarget = new DropTarget(panel.getMonitorPanel(), loadstateDropTarget);
812 statusBar = new JLabel("");
813 statusBar.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
814 manager.addSlaveObject(this, panel);
815 panel.startThread();
817 window.getContentPane().add("Center", panel.getMonitorPanel());
818 window.getContentPane().add("South", statusBar);
819 JMenuBar bar = menuManager.getMainBar();
820 for(JMenu menu : panel.getMenusNeeded())
821 bar.add(menu);
822 window.setJMenuBar(bar);
824 try {
825 window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
826 } catch (AccessControlException e) {
827 System.err.println("Error: Not able to add some components to frame: " + e.getMessage());
830 snapshotFileChooser = new JFileChooser(System.getProperty("user.dir"));
831 otherFileChooser = new JFileChooser(System.getProperty("user.dir"));
833 window.getContentPane().validate();
834 window.validate();
835 window.pack();
836 Dimension d = window.getSize();
837 nativeWidth = d.width;
838 nativeHeight = d.height;
839 updateStatusBarEventThread();
841 window.setVisible(true);
844 public void sendMessage(String msg)
846 vPluginManager.invokeExternalCommand("luaplugin-sendmessage", new Object[]{msg});
849 private String debugShowName(String name)
851 name = name.substring(12);
852 StringBuffer buf = new StringBuffer();
853 for(int i = 0; i < name.length(); i++)
854 if(name.charAt(i) == '_')
855 buf.append(' ');
856 else
857 buf.append(name.charAt(i));
858 return buf.toString();
861 private void addDebug(String name, Class<?> clazz)
863 if(debugInClass.get(name) != null)
864 return;
865 debugInClass.put(name, clazz);
866 debugState.put(name, false);
867 try {
868 menuManager.addSelectableMenuItem("Debug→" + debugShowName(name), this, "menuDEBUGOPTION",
869 new Object[]{name}, false, PROFILE_HAVE_PC);
870 } catch(Exception e) {
874 public void menuDEBUGOPTION(String i, Object[] args)
876 String name = (String)args[0];
877 String mName = "Debug→" + debugShowName(name);
878 debugState.put(name, !debugState.get(name));
879 setDebugOption(name);
880 menuManager.setSelected(mName, debugState.get(name));
883 private void setDebugOption(String name)
885 try {
886 debugInClass.get(name).getDeclaredMethod(name, boolean.class).invoke(pc.getComponent(
887 debugInClass.get(name)), debugState.get(name));
888 } catch(Exception e) {
889 e.printStackTrace();
893 private void setDebugOptions()
895 for(Map.Entry<String, Class<?>> opt : debugInClass.entrySet())
896 setDebugOption(opt.getKey());
899 private void updateDebug()
901 setDebugOptions();
902 for(HardwareComponent c : pc.allComponents()) {
903 Class<?> cl = c.getClass();
904 for(Method m : cl.getDeclaredMethods()) {
905 Class<?>[] p = m.getParameterTypes();
906 if(!m.getName().startsWith("DEBUGOPTION_"))
907 continue;
908 if(p.length != 1 || p[0] != boolean.class)
909 continue;
910 addDebug(m.getName(), cl);
915 public void notifyRenderer(org.jpc.pluginsaux.HUDRenderer r)
917 vPluginManager.addRenderer(r);
920 private void updateStatusBar()
922 if(vPluginManager.isShuttingDown())
923 return; //Too much of deadlock risk.
924 SwingUtilities.invokeLater(new Runnable() { public void run() { updateStatusBarEventThread(); }});
927 private void updateStatusBarEventThread()
929 String text1;
930 if(currentProject.pc != null && taskToDo == null) {
931 long timeNow = ((Clock)currentProject.pc.getComponent(Clock.class)).getTime();
932 long timeEnd = currentProject.events.getLastEventTime();
933 text1 = " Time: " + (timeNow / 1000000) + "ms, movie length: " + (timeEnd / 1000000) + "ms";
934 if(currentResolutionWidth > 0 && currentResolutionHeight > 0)
935 text1 = text1 + ", resolution: " + currentResolutionWidth + "*" + currentResolutionHeight;
936 else
937 text1 = text1 + ", resolution: <No valid signal>";
938 if(currentProject.events.isAtMovieEnd())
939 text1 = text1 + " (At movie end)";
940 } else if(taskToDo != null)
941 text1 = taskLabel;
942 else
943 text1 = " NO PC CONNECTED";
945 statusBar.setText(text1);
948 public void menuExtra(String i, Object[] args)
950 final List<String[]> commandList = extraActions.get(args[0]);
951 if(commandList == null) {
952 System.err.println("Warning: Called extra menu with unknown entry '" + args[0] + "'.");
953 return;
956 //Run the functions on seperate thread to avoid deadlocking.
957 (new Thread(new Runnable() { public void run() { menuExtraThreadFunc(commandList); }}, "Extra action thread")).start();
960 private void menuExtraThreadFunc(List<String[]> actions)
962 for(String[] i : actions) {
963 if(i.length == 1) {
964 vPluginManager.invokeExternalCommandSynchronous(i[0], null);
965 } else {
966 String[] rest = Arrays.copyOfRange(i, 1, i.length, String[].class);
967 vPluginManager.invokeExternalCommandSynchronous(i[0], rest);
972 public void menuAssemble(String i, Object[] args)
974 setTask(new AssembleTask(), ASSEMBLE_LABEL);
977 public void menuStart(String i, Object[] args)
979 start();
982 public void menuStartDump(String i, Object[] args)
984 int returnVal = otherFileChooser.showDialog(window, "Dump to file");
985 if(returnVal != 0)
986 return;
987 File choosen = otherFileChooser.getSelectedFile();
988 try {
989 dumper = new RAWDumper(vPluginManager, "rawoutput=" + choosen.getAbsolutePath());
990 vPluginManager.registerPlugin(dumper);
991 pc.refreshGameinfo(currentProject);
992 } catch(Exception e) {
993 errorDialog(e, "Failed to start dumping", null, "Dismiss");
994 return;
996 profile &= ~PROFILE_NOT_DUMPING;
997 profile |= PROFILE_DUMPING;
998 menuManager.setProfile(profile);
1001 public void menuStopDump(String i, Object[] args)
1003 vPluginManager.unregisterPlugin(dumper);
1004 profile &= ~PROFILE_DUMPING;
1005 profile |= PROFILE_NOT_DUMPING;
1006 menuManager.setProfile(profile);
1009 public void menuStop(String i, Object[] args)
1011 stop();
1014 public void menuReset(String i, Object[] args)
1016 reset();
1019 public void menuImport(String i, Object[] args)
1021 try {
1022 new ImportDiskImage();
1023 } catch(Exception e) {
1024 e.printStackTrace();
1028 public void menuNOFPU(String i, Object[] args)
1030 pc.setFPUHack();
1033 public void menuVGADRAW(String i, Object[] args)
1035 pc.setVGADrawHack();
1038 public void menuVGASCROLL2(String i, Object[] args)
1040 pc.setVGAScroll2Hack();
1043 public void menuQuit(String i, Object[] args)
1045 vPluginManager.shutdownEmulator();
1048 public void menuVRetraceStart(String i, Object[] args)
1050 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_START;
1051 menuManager.setSelected("Breakpoints→Trap VRetrace Start",
1052 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_START) == TraceTrap.TRACE_STOP_VRETRACE_START);
1055 public void menuVRetraceEnd(String i, Object[] args)
1057 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_END;
1058 menuManager.setSelected("Breakpoints→Trap VRetrace End",
1059 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_END) == TraceTrap.TRACE_STOP_VRETRACE_END);
1062 public void menuTimedStop(String i, Object[] args)
1064 for(int j = 0; j < stopLabel.length; j++) {
1065 String label = "Breakpoints→Timed Stops→" + stopLabel[j];
1066 if(i.equals(label)) {
1067 this.imminentTrapTime = stopTime[j];
1068 menuManager.select(label);
1069 } else
1070 menuManager.unselect(label);
1074 public void menuSave(String i, Object[] args)
1076 setTask(new SaveStateTask(((Boolean)args[0]).booleanValue()), SAVESTATE_LABEL);
1079 public void menuStatusDump(String i, Object[] args)
1081 setTask(new StatusDumpTask(), STATUSDUMP_LABEL);
1084 public void menuLoad(String i, Object[] args)
1086 setTask(new LoadStateTask(((Integer)args[0]).intValue()), LOADSTATE_LABEL);
1089 public void menuRAMDump(String i, Object[] args)
1091 setTask(new RAMDumpTask(((Boolean)args[0]).booleanValue()), RAMDUMP_LABEL);
1094 public void menuDumpDisk(String i, Object[] args)
1096 setTask(new ImageDumpTask(((Integer)args[0]).intValue()), IMAGEDUMP_LABEL);
1099 public void menuTruncate(String i, Object[] args)
1101 currentProject.events.truncateEventStream();
1104 public void menuChangeDisk(String i, Object[] args)
1106 changeFloppy(((Integer)args[0]).intValue(), ((Integer)args[1]).intValue());
1109 public void menuWriteProtect(String i, Object[] args)
1111 int disk = ((Integer)args[0]).intValue();
1112 writeProtect(disk, menuManager.isSelected(i));
1113 DiskImageSet imageSet = pc.getDisks();
1114 menuManager.setSelected(i, imageSet.lookupDisk(disk).isReadOnly());
1117 public void menuAddDisk(String i, Object[] args)
1119 setTask(new AddDiskTask(), ADDDISK_LABEL);
1122 public void menuChangeAuthors(String i, Object[] args)
1124 setTask(new ChangeAuthorsTask(), CHANGEAUTHORS_LABEL);
1127 public synchronized void start()
1129 if(taskToDo != null)
1130 return;
1131 vPluginManager.pcStarted();
1132 running = true;
1133 notifyAll();
1136 private String prettyPrintTime(long ts)
1138 String s = "";
1140 if(ts >= 1000000000)
1141 s = s + "" + (ts / 1000000000) + " ";
1142 if(ts >= 100000000)
1143 s = s + "" + (ts % 1000000000 / 100000000);
1144 if(ts >= 10000000)
1145 s = s + "" + (ts % 100000000 / 10000000);
1146 if(ts >= 1000000)
1147 s = s + "" + (ts % 10000000 / 1000000) + " ";
1148 if(ts >= 100000)
1149 s = s + "" + (ts % 1000000 / 100000);
1150 if(ts >= 10000)
1151 s = s + "" + (ts % 100000 / 10000);
1152 if(ts >= 1000)
1153 s = s + "" + (ts % 10000 / 1000) + " ";
1154 if(ts >= 100)
1155 s = s + "" + (ts % 1000 / 100);
1156 if(ts >= 10)
1157 s = s + "" + (ts % 100 / 10);
1158 s = s + "" + (ts % 10);
1159 return s;
1162 protected synchronized void stopNoWait()
1164 running = false;
1165 vPluginManager.pcStopped();
1166 Clock sysClock = (Clock)pc.getComponent(Clock.class);
1167 System.err.println("Notice: PC emulation stopped (at time sequence value " +
1168 prettyPrintTime(sysClock.getTime()) + ")");
1171 public synchronized void stop()
1173 pc.getTraceTrap().doPotentialTrap(TraceTrap.TRACE_STOP_IMMEDIATE);
1174 System.err.println("Informational: Waiting for PC to halt...");
1177 public JScrollPane getMonitorPane()
1179 return null;
1182 protected void reset()
1184 pc.reboot();
1187 public synchronized boolean isRunning()
1189 return running;
1192 private void changeFloppy(int drive, int image)
1196 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
1197 changer.changeFloppyDisk(drive, image);
1198 } catch (Exception e) {
1199 System.err.println("Error: Failed to change disk");
1200 errorDialog(e, "Failed to change disk", null, "Dismiss");
1204 private void writeProtect(int image, boolean state)
1208 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
1209 changer.wpFloppyDisk(image, state);
1210 } catch (Exception e) {
1211 System.err.println("Error: Failed to change floppy write protect");
1212 errorDialog(e, "Failed to write (un)protect floppy", null, "Dismiss");
1216 private class LoadStateTask extends AsyncGUITask
1218 File chosen;
1219 Exception caught;
1220 int _mode;
1221 long oTime;
1222 private static final int MODE_NORMAL = 1;
1223 private static final int MODE_PRESERVE = 2;
1224 private static final int MODE_MOVIEONLY = 3;
1226 public LoadStateTask(int mode)
1228 oTime = System.currentTimeMillis();
1229 chosen = null;
1230 _mode = mode;
1233 public LoadStateTask(String name, int mode)
1235 this(mode);
1236 chosen = new File(name);
1239 protected void runPrepare()
1241 if(chosen == null) {
1242 int returnVal = 0;
1243 if(_mode == MODE_PRESERVE)
1244 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot (PE)");
1245 else if(_mode == MODE_MOVIEONLY)
1246 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot (MO)");
1247 else
1248 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot");
1249 chosen = snapshotFileChooser.getSelectedFile();
1251 if (returnVal != 0)
1252 chosen = null;
1256 protected void runFinish()
1258 if(chosen == null)
1259 return;
1261 if(caught == null) {
1262 try {
1263 connectPC(pc = currentProject.pc);
1264 doCycle(pc);
1265 System.err.println("Informational: Loadstate done on "+chosen.getAbsolutePath());
1266 } catch(Exception e) {
1267 caught = e;
1270 if(caught != null) {
1271 errorDialog(caught, "Load savestate failed", window, "Dismiss");
1273 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1274 PCControl.this.vPluginManager.signalCommandCompletion();
1277 protected void runTask()
1279 if(chosen == null)
1280 return;
1282 try {
1283 System.err.println("Informational: Loading a snapshot of JPC-RR");
1284 long times1 = System.currentTimeMillis();
1285 JRSRArchiveReader reader = new JRSRArchiveReader(chosen.getAbsolutePath());
1287 PC.PCFullStatus fullStatus = PC.loadSavestate(reader, _mode == MODE_PRESERVE, _mode == MODE_MOVIEONLY,
1288 currentProject);
1290 currentProject = fullStatus;
1292 reader.close();
1293 long times2 = System.currentTimeMillis();
1294 System.err.println("Informational: Loadstate complete (" + (times2 - times1) + "ms).");
1295 } catch(Exception e) {
1296 caught = e;
1301 private synchronized void doCycleDedicatedThread(PC _pc)
1303 if(_pc == null) {
1304 cycleDone = true;
1305 return;
1307 DisplayController dc = (DisplayController)_pc.getComponent(DisplayController.class);
1308 dc.getOutputDevice().holdOutput(_pc.getTime());
1309 cycleDone = true;
1310 notifyAll();
1313 private void doCycle(PC _pc)
1315 final PC _xpc = _pc;
1316 cycleDone = false;
1317 (new Thread(new Runnable() { public void run() { doCycleDedicatedThread(_xpc); }}, "VGA output cycle thread")).start();
1318 while(cycleDone)
1319 try {
1320 synchronized(this) {
1321 if(cycleDone)
1322 break;
1323 wait();
1325 } catch(Exception e) {
1329 private class SaveStateTask extends AsyncGUITask
1331 File chosen;
1332 Exception caught;
1333 boolean movieOnly;
1334 long oTime;
1336 public SaveStateTask(boolean movie)
1338 oTime = System.currentTimeMillis();
1339 chosen = null;
1340 movieOnly = movie;
1343 public SaveStateTask(String name, boolean movie)
1345 this(movie);
1346 chosen = new File(name);
1349 protected void runPrepare()
1351 if(chosen == null) {
1352 int returnVal = snapshotFileChooser.showDialog(window, movieOnly ? "Save JPC-RR Movie" :
1353 "Save JPC-RR Snapshot");
1354 chosen = snapshotFileChooser.getSelectedFile();
1356 if (returnVal != 0)
1357 chosen = null;
1361 protected void runFinish()
1363 if(caught != null) {
1364 errorDialog(caught, "Saving savestate failed", window, "Dismiss");
1366 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1367 PCControl.this.vPluginManager.signalCommandCompletion();
1370 protected void runTask()
1372 if(chosen == null)
1373 return;
1375 JRSRArchiveWriter writer = null;
1377 try {
1378 System.err.println("Informational: Savestating...");
1379 long times1 = System.currentTimeMillis();
1380 writer = new JRSRArchiveWriter(chosen.getAbsolutePath());
1381 PC.saveSavestate(writer, currentProject, movieOnly, uncompressedSave);
1382 renameFile(chosen, new File(chosen.getAbsolutePath() + ".backup"));
1383 writer.close();
1384 long times2 = System.currentTimeMillis();
1385 System.err.println("Informational: Savestate complete (" + (times2 - times1) + "ms). on"+chosen.getAbsolutePath());
1386 } catch(Exception e) {
1387 if(writer != null)
1388 try { writer.rollback(); } catch(Exception f) {}
1389 caught = e;
1394 private class StatusDumpTask extends AsyncGUITask
1396 File chosen;
1397 Exception caught;
1399 public StatusDumpTask()
1401 chosen = null;
1404 public StatusDumpTask(String name)
1406 this();
1407 chosen = new File(name);
1410 protected void runPrepare()
1412 if(chosen == null) {
1413 int returnVal = otherFileChooser.showDialog(window, "Save Status dump");
1414 chosen = otherFileChooser.getSelectedFile();
1416 if (returnVal != 0)
1417 chosen = null;
1421 protected void runFinish()
1423 if(caught != null) {
1424 errorDialog(caught, "Status dump failed", window, "Dismiss");
1426 PCControl.this.vPluginManager.signalCommandCompletion();
1429 protected void runTask()
1431 if(chosen == null)
1432 return;
1434 try {
1435 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1436 PrintStream out = new PrintStream(outb, false, "UTF-8");
1437 StatusDumper sd = new StatusDumper(out);
1438 pc.dumpStatus(sd);
1439 out.flush();
1440 outb.flush();
1441 System.err.println("Informational: Dumped " + sd.dumpedObjects() + " objects");
1442 } catch(Exception e) {
1443 caught = e;
1448 private class RAMDumpTask extends AsyncGUITask
1450 File chosen;
1451 Exception caught;
1452 boolean binary;
1454 public RAMDumpTask(boolean binFlag)
1456 chosen = null;
1457 binary = binFlag;
1460 public RAMDumpTask(String name, boolean binFlag)
1462 this(binFlag);
1463 chosen = new File(name);
1466 protected void runPrepare()
1468 if(chosen == null) {
1469 int returnVal;
1470 if(binary)
1471 returnVal = otherFileChooser.showDialog(window, "Save RAM dump");
1472 else
1473 returnVal = otherFileChooser.showDialog(window, "Save RAM hexdump");
1474 chosen = otherFileChooser.getSelectedFile();
1476 if (returnVal != 0)
1477 chosen = null;
1481 protected void runFinish()
1483 if(caught != null) {
1484 errorDialog(caught, "RAM dump failed", window, "Dismiss");
1486 PCControl.this.vPluginManager.signalCommandCompletion();
1489 protected void runTask()
1491 if(chosen == null)
1492 return;
1494 try {
1495 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1496 byte[] pagebuf = new byte[4096];
1497 PhysicalAddressSpace addr = (PhysicalAddressSpace)pc.getComponent(PhysicalAddressSpace.class);
1498 int lowBound = addr.findFirstRAMPage(0);
1499 int firstUndumped = 0;
1500 int highBound = 0;
1501 int present = 0;
1502 while(lowBound >= 0) {
1503 for(; firstUndumped < lowBound; firstUndumped++)
1504 dumpPage(outb, firstUndumped, null);
1505 addr.readRAMPage(firstUndumped++, pagebuf);
1506 dumpPage(outb, lowBound, pagebuf);
1507 present++;
1508 highBound = lowBound + 1;
1509 lowBound = addr.findFirstRAMPage(++lowBound);
1511 outb.flush();
1512 System.err.println("Informational: Dumped machine RAM (" + highBound + " pages examined, " +
1513 present + " pages present).");
1514 } catch(Exception e) {
1515 caught = e;
1519 private byte charForHex(int hvalue)
1521 if(hvalue < 10)
1522 return (byte)(hvalue + 48);
1523 else if(hvalue > 9 && hvalue < 16)
1524 return (byte)(hvalue + 55);
1525 else
1526 System.err.println("Unknown hex value: " + hvalue + ".");
1527 return 90;
1530 private void dumpPage(OutputStream stream, int pageNo, byte[] buffer) throws IOException
1532 int pageBufSize;
1533 pageNo = pageNo & 0xFFFFF; //Cut page numbers out of range.
1534 if(!binary && buffer == null)
1535 return; //Don't dump null pages in non-binary mode.
1536 if(binary)
1537 pageBufSize = 4096; //Binary page buffer is 4096 bytes.
1538 else
1539 pageBufSize = 14592; //Hexdump page buffer is 14592 bytes.
1540 byte[] outputPage = new byte[pageBufSize];
1541 if(buffer != null && binary) {
1542 System.arraycopy(buffer, 0, outputPage, 0, 4096);
1543 } else if(buffer != null) { //Hex mode
1544 for(int i = 0; i < 256; i++) {
1545 for(int j = 0; j < 57; j++) {
1546 if(j < 5)
1547 outputPage[57 * i + j] = charForHex((pageNo >>> (4 * (4 - j))) & 0xF);
1548 else if(j == 5)
1549 outputPage[57 * i + j] = charForHex(i / 16);
1550 else if(j == 6)
1551 outputPage[57 * i + j] = charForHex(i % 16);
1552 else if(j == 7)
1553 outputPage[57 * i + j] = 48;
1554 else if(j == 56)
1555 outputPage[57 * i + j] = 10;
1556 else if(j % 3 == 2)
1557 outputPage[57 * i + j] = 32;
1558 else if(j % 3 == 0)
1559 outputPage[57 * i + j] = charForHex(((int)buffer[16 * i + j / 3 - 3] & 0xFF) / 16);
1560 else if(j % 3 == 1)
1561 outputPage[57 * i + j] = charForHex(buffer[16 * i + j / 3 - 3] & 0xF);
1562 else
1563 System.err.println("Error: dumpPage: unhandled j = " + j + ".");
1567 stream.write(outputPage);
1571 private class ImageDumpTask extends AsyncGUITask
1573 File chosen;
1574 Exception caught;
1575 int index;
1577 public ImageDumpTask(int _index)
1579 chosen = null;
1580 index = _index;
1583 public ImageDumpTask(String name, int index)
1585 this(index);
1586 chosen = new File(name);
1589 protected void runPrepare()
1591 if(chosen == null) {
1592 int returnVal;
1593 returnVal = otherFileChooser.showDialog(window, "Save Image dump");
1594 chosen = otherFileChooser.getSelectedFile();
1596 if (returnVal != 0)
1597 chosen = null;
1601 protected void runFinish()
1603 if(caught != null) {
1604 errorDialog(caught, "Image dump failed", window, "Dismiss");
1606 PCControl.this.vPluginManager.signalCommandCompletion();
1609 protected void runTask()
1611 if(chosen == null)
1612 return;
1614 try {
1615 DiskImage dev;
1616 if(index < 0)
1617 dev = pc.getDrives().getHardDrive(-1 - index).getImage();
1618 else
1619 dev = pc.getDisks().lookupDisk(index);
1620 if(dev == null)
1621 throw new IOException("Trying to dump nonexistent disk");
1622 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1623 byte[] buf = new byte[512];
1624 long sectors = dev.getTotalSectors();
1625 for(long i = 0; i < sectors; i++) {
1626 dev.read(i, buf, 1);
1627 outb.write(buf);
1629 outb.close();
1630 System.err.println("Informational: Dumped disk image (" + sectors + " sectors).");
1631 } catch(Exception e) {
1632 caught = e;
1637 private class AssembleTask extends AsyncGUITask
1639 Exception caught;
1640 boolean canceled;
1642 public AssembleTask()
1644 canceled = false;
1647 protected void runPrepare()
1649 try {
1650 configDialog.popUp();
1651 } catch(Exception e) {
1652 caught = e;
1656 protected void runFinish()
1658 if(caught == null && !canceled) {
1659 try {
1660 currentProject.projectID = randomHexes(24);
1661 currentProject.rerecords = 0;
1662 currentProject.events = new EventRecorder();
1663 currentProject.events.attach(pc, null);
1664 currentProject.savestateID = null;
1665 currentProject.extraHeaders = null;
1666 currentProject.events.setRerecordCount(0);
1667 currentProject.events.setHeaders(currentProject.extraHeaders);
1668 connectPC(pc);
1669 } catch(Exception e) {
1670 caught = e;
1673 if(caught != null) {
1674 errorDialog(caught, "PC Assembly failed", window, "Dismiss");
1676 PCControl.this.vPluginManager.signalCommandCompletion();
1679 protected void runTask()
1681 if(caught != null)
1682 return;
1683 PC.PCHardwareInfo hw = configDialog.waitClose();
1684 if(hw == null) {
1685 canceled = true;
1686 return;
1689 try {
1690 pc = PC.createPC(hw);
1691 } catch(Exception e) {
1692 caught = e;
1697 private class AddDiskTask extends AsyncGUITask
1699 Exception caught;
1700 NewDiskDialog dd;
1702 public AddDiskTask()
1704 dd = new NewDiskDialog();
1707 protected void runPrepare()
1711 protected void runFinish()
1713 if(caught != null) {
1714 errorDialog(caught, "Adding disk failed", window, "Dismiss");
1716 try {
1717 updateDisks();
1718 } catch(Exception e) {
1719 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
1721 PCControl.this.vPluginManager.signalCommandCompletion();
1724 protected void runTask()
1726 NewDiskDialog.Response res = dd.waitClose();
1727 if(res == null) {
1728 return;
1730 try {
1731 DiskImage img;
1732 pc.getDisks().addDisk(img = new DiskImage(res.diskFile, false));
1733 img.setName(res.diskName);
1734 } catch(Exception e) {
1735 caught = e;
1740 private class ChangeAuthorsTask extends AsyncGUITask
1742 Exception caught;
1743 AuthorsDialog ad;
1745 public ChangeAuthorsTask()
1747 int authors = 0;
1748 int headers = 0;
1749 AuthorsDialog.AuthorElement[] authorNames = null;
1750 if(currentProject != null)
1751 authorNames = AuthorsDialog.readAuthorsFromHeaders(currentProject.extraHeaders);
1753 ad = new AuthorsDialog(authorNames);
1756 protected void runPrepare()
1760 protected void runFinish()
1762 if(caught != null) {
1763 errorDialog(caught, "Changing authors failed", window, "Dismiss");
1765 PCControl.this.vPluginManager.signalCommandCompletion();
1768 protected void runTask()
1770 AuthorsDialog.Response res = ad.waitClose();
1771 if(res == null) {
1772 return;
1774 try {
1775 currentProject.extraHeaders = AuthorsDialog.rewriteHeaderAuthors(currentProject.extraHeaders,
1776 res.authors);
1777 currentProject.events.setHeaders(currentProject.extraHeaders);
1778 } catch(Exception e) {
1779 caught = e;