Keep focus on main window on savestate/loadstate
[jpcrr.git] / org / jpc / plugins / PCControl.java
blob2d2bf606245fb3d0d5eb27c6de506aa1811fb0f0
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.security.AccessControlException;
36 import javax.swing.*;
38 import org.jpc.emulator.HardwareComponent;
39 import org.jpc.emulator.PC;
40 import org.jpc.emulator.EventRecorder;
41 import org.jpc.emulator.TraceTrap;
42 import org.jpc.emulator.memory.PhysicalAddressSpace;
43 import org.jpc.emulator.StatusDumper;
44 import org.jpc.emulator.Clock;
45 import org.jpc.diskimages.BlockDevice;
46 import org.jpc.diskimages.DiskImageSet;
47 import org.jpc.diskimages.DiskImage;
48 import org.jpc.pluginsaux.PleaseWait;
49 import org.jpc.pluginsaux.AsyncGUITask;
50 import org.jpc.pluginsaux.NewDiskDialog;
51 import org.jpc.pluginsaux.AuthorsDialog;
52 import org.jpc.pluginsaux.PCConfigDialog;
53 import org.jpc.pluginsaux.DumpControlDialog;
54 import org.jpc.pluginsaux.MenuManager;
55 import org.jpc.pluginsaux.ImportDiskImage;
56 import org.jpc.pluginsbase.*;
57 import org.jpc.jrsr.*;
59 import static org.jpc.Misc.randomHexes;
60 import static org.jpc.Misc.errorDialog;
61 import static org.jpc.Misc.callShowOptionDialog;
62 import static org.jpc.Misc.moveWindow;
63 import static org.jpc.Misc.parseStringToComponents;
64 import static org.jpc.Misc.nextParseLine;
65 import static org.jpc.Misc.renameFile;
67 public class PCControl extends JFrame implements Plugin
69 private static long PROFILE_ALWAYS = 0;
70 private static long PROFILE_NO_PC = 1;
71 private static long PROFILE_HAVE_PC = 2;
72 private static long PROFILE_STOPPED = 4;
73 private static long PROFILE_RUNNING = 8;
74 private static long PROFILE_EVENTS = 16;
75 private static long PROFILE_CDROM = 32;
77 private static final long serialVersionUID = 8;
78 private Plugins vPluginManager;
80 private JFileChooser snapshotFileChooser;
82 private Set<String> disks;
84 protected PC pc;
86 private int trapFlags;
88 private volatile boolean running;
89 private volatile boolean waiting;
90 private boolean uncompressedSave;
91 private boolean willCleanup;
92 private static final long[] stopTime;
93 private static final String[] stopLabel;
94 private volatile long imminentTrapTime;
95 private boolean shuttingDown;
96 private int nativeWidth;
97 private int nativeHeight;
98 private PCConfigDialog configDialog;
99 private DumpControlDialog dumpDialog;
100 private MenuManager menuManager;
101 private volatile boolean restoreFocus;
103 private PC.PCFullStatus currentProject;
105 static
107 stopTime = new long[] {-1, 0, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000,
108 5000000, 10000000, 20000000, 50000000, 100000000, 200000000, 500000000, 1000000000, 2000000000,
109 5000000000L, 10000000000L, 20000000000L, 50000000000L};
110 stopLabel = new String[] {"(unbounded)", "(singlestep)", "1µs", "2µs", "5µs", "10µs", "20µs", "50µs", "100µs",
111 "200µs", "500µs","1ms", "2ms", "5ms", "10ms", "20ms", "50ms", "100ms", "200ms", "500ms", "1s", "2s", "5s",
112 "10s", "20s", "50s"};
115 public boolean systemShutdown()
117 if(!running || pc == null)
118 return true;
119 //We are running. Do the absolute minimum since we are running in very delicate context.
120 shuttingDown = true;
121 stop();
122 return true;
125 public void reconnect(PC pc)
127 pcStopping(); //Do the equivalent effects.
128 dumpDialog.clearDumps();
132 private void setTrapFlags()
134 pc.getTraceTrap().setTrapFlags(trapFlags);
137 public void pcStarting()
139 long profile = PROFILE_HAVE_PC | PROFILE_RUNNING;
140 if(currentProject != null && currentProject.events != null);
141 profile |= PROFILE_EVENTS;
142 if(pc.getCDROMIndex() >= 0)
143 profile |= PROFILE_CDROM;
145 menuManager.setProfile(profile);
147 if (running)
148 return;
150 setTrapFlags();
152 Clock sysClock = (Clock)pc.getComponent(Clock.class);
153 long current = sysClock.getTime();
154 if(imminentTrapTime > 0) {
155 pc.getTraceTrap().setTrapTime(current + imminentTrapTime);
156 } else if(imminentTrapTime == 0) {
157 //Hack: We set trace trap to trap immediately. It comes too late to abort next instruction, but
158 //early enough to abort one after that.
159 pc.getTraceTrap().setTrapTime(current);
161 if(currentProject.events != null)
162 currentProject.events.setPCRunStatus(true);
165 public void pcStopping()
167 if(currentProject.events != null)
168 currentProject.events.setPCRunStatus(false);
169 if(shuttingDown)
170 return; //Don't mess with UI when shutting down.
173 long profile = PROFILE_STOPPED;
174 if(pc != null)
175 profile |= PROFILE_HAVE_PC;
176 else
177 profile |= PROFILE_NO_PC;
178 if(currentProject != null && currentProject.events != null);
179 profile |= PROFILE_EVENTS;
180 if(pc.getCDROMIndex() >= 0)
181 profile |= PROFILE_CDROM;
183 menuManager.setProfile(profile);
185 try {
186 updateDisks();
187 } catch(Exception e) {
188 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
191 if(pc != null) {
192 pc.getTraceTrap().clearTrapTime();
193 pc.getTraceTrap().getAndClearTrapActive();
197 private String diskNameByIdx(int idx)
199 return pc.getDisks().lookupDisk(idx).getName();
202 private void updateDisks() throws Exception
204 for(String x : disks)
205 menuManager.removeMenuItem(x);
207 disks.clear();
209 if(pc == null)
210 return;
212 DiskImageSet imageSet = pc.getDisks();
213 int[] floppies = imageSet.diskIndicesByType(BlockDevice.Type.FLOPPY);
214 int[] cdroms = imageSet.diskIndicesByType(BlockDevice.Type.CDROM);
216 for(int i = 0; i < floppies.length; i++) {
217 String name = diskNameByIdx(floppies[i]);
218 menuManager.addMenuItem("Drives→fda→" + name, this, "menuChangeDisk", new Object[]{new Integer(0),
219 new Integer(floppies[i])}, PROFILE_HAVE_PC);
220 menuManager.addMenuItem("Drives→fdb→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
221 new Integer(floppies[i])}, PROFILE_HAVE_PC);
222 menuManager.addSelectableMenuItem("Drives→Write Protect→" + name, this, "menuWriteProtect",
223 new Object[]{new Integer(floppies[i])}, imageSet.lookupDisk(floppies[i]).isReadOnly(),
224 PROFILE_HAVE_PC);
225 disks.add("Drives→fda→" + name);
226 disks.add("Drives→fdb→" + name);
227 disks.add("Drives→Write Protect→" + name);
230 for(int i = 0; i < cdroms.length; i++) {
231 String name = diskNameByIdx(cdroms[i]);
232 menuManager.addMenuItem("Drives→CD-ROM→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
233 new Integer(cdroms[i])}, PROFILE_HAVE_PC | PROFILE_CDROM);
234 disks.add("Drives→CD-ROM→" + name);
238 public void main()
240 boolean wasRunning = false;
241 while(true) { //We will be killed by JVM.
242 //Wait for us to become runnable again.
243 while(!running || pc == null) {
244 if(wasRunning && pc != null)
245 pc.stop();
246 wasRunning = running;
247 try {
248 synchronized(this) {
249 waiting = true;
250 notifyAll();
251 wait();
252 waiting = false;
254 } catch(Exception e) {
258 if(!wasRunning)
259 pc.start();
260 wasRunning = running;
262 try {
263 pc.execute();
264 if(pc.getHitTraceTrap()) {
265 if(pc.getAndClearTripleFaulted())
266 callShowOptionDialog(this, "CPU shut itself down due to triple fault. Rebooting the system.", "Triple fault!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[]{"Dismiss"}, "Dismiss");
267 if(!willCleanup)
268 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
269 running = false;
271 } catch (Exception e) {
272 errorDialog(e, "Hardware emulator internal error", this, "Dismiss");
273 try {
274 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
275 } catch (Exception f) {
282 public void connectPC(PC pc)
284 currentProject.pc = pc;
285 vPluginManager.reconnect(pc);
286 this.pc = pc;
289 private void startExternal()
291 if(pc != null && !running)
292 if(!SwingUtilities.isEventDispatchThread())
293 try {
294 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.start(); }});
295 } catch(Exception e) {
297 else
298 start();
301 private void stopExternal()
303 if(pc != null && running)
304 if(!SwingUtilities.isEventDispatchThread())
305 try {
306 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.stop(); }});
307 } catch(Exception e) {
309 else
310 stop();
313 public boolean eci_state_save(String filename)
315 if(!running)
316 (new Thread(new SaveStateTask(filename, false))).start();
317 return !running;
320 public boolean eci_state_dump(String filename)
322 if(!running)
323 (new Thread(new StatusDumpTask(filename))).start();
324 return !running;
327 public boolean eci_movie_save(String filename)
329 if(!running)
330 (new Thread(new SaveStateTask(filename, true))).start();
331 return !running;
334 public boolean eci_state_load(String filename)
336 if(!running)
337 (new Thread(new LoadStateTask(filename, LoadStateTask.MODE_NORMAL))).start();
338 return !running;
341 public boolean eci_state_load_noevents(String filename)
343 if(!running)
344 (new Thread(new LoadStateTask(filename, LoadStateTask.MODE_PRESERVE))).start();
345 return !running;
348 public boolean eci_movie_load(String filename)
350 if(!running)
351 (new Thread(new LoadStateTask(filename, LoadStateTask.MODE_MOVIEONLY))).start();
352 return !running;
355 public boolean eci_pc_assemble()
357 if(!running)
358 (new Thread(new AssembleTask())).start();
359 return !running;
362 public boolean eci_ram_dump_text(String filename)
364 if(!running)
365 (new Thread(new RAMDumpTask(filename, false))).start();
366 return !running;
369 public boolean eci_ram_dump_binary(String filename)
371 if(!running)
372 (new Thread(new RAMDumpTask(filename, true))).start();
373 return !running;
376 public void eci_trap_vretrace_start_on()
378 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_START;
381 public void eci_trap_vretrace_start_off()
383 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_START;
386 public void eci_trap_vretrace_end_on()
388 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_END;
391 public void eci_trap_vretrace_end_off()
393 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_END;
396 public void eci_trap_timed_disable()
398 this.imminentTrapTime = -1;
401 public void eci_trap_timed(Long time)
403 this.imminentTrapTime = time.longValue();
406 public void eci_pc_start()
408 startExternal();
411 public void eci_pc_stop()
413 stopExternal();
416 public void eci_pccontrol_setwinpos(Integer x, Integer y)
418 moveWindow(this, x.intValue(), y.intValue(), nativeWidth, nativeHeight);
421 public void eci_sendevent(String clazz, String[] rargs)
423 System.err.println("Event to: '" + clazz + "':");
424 for(int i = 0; i < rargs.length; i++) {
425 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
427 if(currentProject.events != null) {
428 try {
429 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
430 currentProject.events.addEvent(0L, x, rargs);
431 } catch(Exception e) {
432 System.err.println("Error adding event: " + e.getMessage());
437 public void eci_memory_read(Long address, Integer size)
439 if(currentProject.pc != null) {
440 long addr = address.longValue();
441 long _size = size.intValue();
442 long ret = 0;
443 PhysicalAddressSpace addrSpace;
444 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
445 return;
447 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
448 if(_size == 1)
449 ret = (long)addrSpace.getByte((int)addr) & 0xFF;
450 else if(_size == 2)
451 ret = (long)addrSpace.getWord((int)addr) & 0xFFFF;
452 else if(_size == 4)
453 ret = (long)addrSpace.getDoubleWord((int)addr) & 0xFFFFFFFFL;
455 vPluginManager.returnValue(ret);
459 public void eci_memory_write(Long address, Long value, Integer size)
461 if(currentProject.pc != null) {
462 long addr = address.longValue();
463 long _size = size.intValue();
464 long _value = value.longValue();
465 PhysicalAddressSpace addrSpace;
466 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
467 return;
469 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
470 if(_size == 1)
471 addrSpace.setByte((int)addr, (byte)_value);
472 else if(_size == 2)
473 addrSpace.setWord((int)addr, (short)_value);
474 else if(_size == 4)
475 addrSpace.setDoubleWord((int)addr, (int)_value);
479 public PCControl(Plugins manager, String args) throws Exception
481 this(manager);
483 UTFInputLineStream file = null;
484 Map<String, String> params = parseStringToComponents(args);
485 Set<String> used = new HashSet<String>();
486 String extramenu = params.get("extramenu");
487 String uncompress = params.get("uncompressedsave");
488 if(uncompress != null)
489 uncompressedSave = true;
490 if(extramenu == null)
491 return;
492 try {
493 file = new UTFInputLineStream(new FileInputStream(extramenu));
495 while(true) {
496 String[] line = nextParseLine(file);
497 if(line == null)
498 break;
499 if(line.length < 3 || line[0].charAt(0) == '→') {
500 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
501 continue;
503 if(line[0].length() == 0 || line[0].charAt(line[0].length() - 1) == '→') {
504 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
505 continue;
507 if(line[0].indexOf("→→") >= 0) {
508 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
509 continue;
511 if(used.contains(line[0])) {
512 System.err.println("Warning: Duplicate extra menu item '" + line[0] + "'.");
513 continue;
515 KeyStroke stroke = null;
516 if(!line[1].equals("<>")) {
517 stroke = KeyStroke.getKeyStroke(line[1]);
518 if(stroke == null) {
519 System.err.println("Warning: Bad keystroke '" + line[1] + "'.");
524 String[] lineCommand = Arrays.copyOfRange(line, 2, line.length);
525 used.add(line[0]);
526 menuManager.addMenuItem("Extra→" + line[0], this, "menuExtra", lineCommand, PROFILE_ALWAYS, stroke);
528 file.close();
529 } catch(IOException e) {
530 errorDialog(e, "Failed to load extra menu defintions", null, "dismiss");
531 if(file != null)
532 file.close();
534 setJMenuBar(menuManager.getMainBar());
537 public PCControl(Plugins manager) throws Exception
539 super("JPC-RR");
541 if(DiskImage.getLibrary() == null)
542 throw new Exception("PCControl plugin requires disk library");
544 running = false;
545 this.willCleanup = false;
546 shuttingDown = false;
547 configDialog = new PCConfigDialog();
548 dumpDialog = new DumpControlDialog(manager);
550 menuManager = new MenuManager();
552 menuManager.setProfile(PROFILE_NO_PC | PROFILE_STOPPED);
554 menuManager.addMenuItem("System→Assemble", this, "menuAssemble", null, PROFILE_STOPPED);
555 menuManager.addMenuItem("System→Start", this, "menuStart", null, PROFILE_STOPPED | PROFILE_HAVE_PC);
556 menuManager.addMenuItem("System→Stop", this, "menuStop", null, PROFILE_RUNNING);
557 menuManager.addMenuItem("System→Reset", this, "menuReset", null, PROFILE_HAVE_PC);
558 menuManager.addMenuItem("System→Dumping control", this, "menuDump", null, PROFILE_HAVE_PC | PROFILE_STOPPED);
559 menuManager.addMenuItem("System→Quit", this, "menuQuit", null, PROFILE_ALWAYS);
560 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace Start", this, "menuVRetraceStart", null, false,
561 PROFILE_ALWAYS);
562 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace End", this, "menuVRetraceEnd", null, false,
563 PROFILE_ALWAYS);
564 menuManager.addMenuItem("Snapshot→Change Run Authors", this, "menuChangeAuthors", null, PROFILE_HAVE_PC);
565 menuManager.addMenuItem("Snapshot→Save→Snapshot", this, "menuSave", new Object[]{new Boolean(false)},
566 PROFILE_HAVE_PC | PROFILE_STOPPED);
567 menuManager.addMenuItem("Snapshot→Save→Movie", this, "menuSave", new Object[]{new Boolean(true)},
568 PROFILE_HAVE_PC | PROFILE_STOPPED);
569 menuManager.addMenuItem("Snapshot→Save→Status Dump", this, "menuStatusDump", null,
570 PROFILE_HAVE_PC | PROFILE_STOPPED);
571 menuManager.addMenuItem("Snapshot→Load→Snapshot", this, "menuLoad",
572 new Object[]{new Integer(LoadStateTask.MODE_NORMAL)}, PROFILE_STOPPED);
573 menuManager.addMenuItem("Snapshot→Load→Snapshot (preserve events)", this, "menuLoad",
574 new Object[]{new Integer(LoadStateTask.MODE_PRESERVE)}, PROFILE_STOPPED | PROFILE_EVENTS);
575 menuManager.addMenuItem("Snapshot→Load→Movie", this, "menuLoad",
576 new Object[]{new Integer(LoadStateTask.MODE_MOVIEONLY)}, PROFILE_STOPPED);
577 menuManager.addMenuItem("Snapshot→RAM Dump→Hexadecimal", this, "menuRAMDump", new Object[]{new Boolean(false)},
578 PROFILE_HAVE_PC | PROFILE_STOPPED);
579 menuManager.addMenuItem("Snapshot→RAM Dump→Binary", this, "menuRAMDump", new Object[]{new Boolean(true)},
580 PROFILE_HAVE_PC | PROFILE_STOPPED);
581 menuManager.addMenuItem("Snapshot→Truncate Event Stream", this, "menuTruncate", null,
582 PROFILE_STOPPED | PROFILE_EVENTS);
584 for(int i = 0; i < stopLabel.length; i++) {
585 menuManager.addSelectableMenuItem("Breakpoints→Timed Stops→" + stopLabel[i], this, "menuTimedStop",
586 null, (i == 0), PROFILE_ALWAYS);
588 imminentTrapTime = -1;
590 menuManager.addMenuItem("Drives→fda→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(0),
591 new Integer(-1)}, PROFILE_HAVE_PC);
592 menuManager.addMenuItem("Drives→fdb→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(1),
593 new Integer(-1)}, PROFILE_HAVE_PC);
594 menuManager.addMenuItem("Drives→CD-ROM→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(2),
595 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_CDROM);
596 menuManager.addMenuItem("Drives→Add image", this, "menuAddDisk", null, PROFILE_HAVE_PC);
597 menuManager.addMenuItem("Drives→Import Image", this, "menuImport", null, PROFILE_ALWAYS);
599 menuManager.addMenuItem("Debug→Hacks→NO_FPU", this, "menuNOFPU", null, PROFILE_HAVE_PC);
600 menuManager.addMenuItem("Debug→Hacks→VGA_DRAW", this, "menuVGADRAW", null, PROFILE_HAVE_PC);
602 disks = new HashSet<String>();
603 currentProject = new PC.PCFullStatus();
604 this.pc = null;
605 this.vPluginManager = manager;
607 setJMenuBar(menuManager.getMainBar());
611 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
613 catch (AccessControlException e)
615 System.err.println("Error: Not able to add some components to frame: " + e.getMessage());
618 snapshotFileChooser = new JFileChooser(System.getProperty("user.dir"));
620 getContentPane().validate();
621 validate();
622 pack();
623 Dimension d = getSize();
624 nativeWidth = d.width;
625 nativeHeight = d.height;
626 setVisible(true);
629 public void menuExtra(String i, Object[] args)
631 if(args.length == 1) {
632 vPluginManager.invokeExternalCommand((String)args[0], null);
633 } else {
634 String[] rest = Arrays.copyOfRange(args, 1, args.length, String[].class);
635 vPluginManager.invokeExternalCommand((String)args[0], rest);
639 public void menuAssemble(String i, Object[] args)
641 (new Thread(new AssembleTask())).start();
644 public void menuStart(String i, Object[] args)
646 start();
649 public void menuStop(String i, Object[] args)
651 stop();
654 public void menuReset(String i, Object[] args)
656 reset();
659 public void menuDump(String i, Object[] args)
661 (new Thread(new DumpControlTask())).start();
664 public void menuImport(String i, Object[] args)
666 try {
667 new ImportDiskImage();
668 } catch(Exception e) {
669 e.printStackTrace();
673 public void menuNOFPU(String i, Object[] args)
675 pc.setFPUHack();
678 public void menuVGADRAW(String i, Object[] args)
680 pc.setVGADrawHack();
683 public void menuQuit(String i, Object[] args)
685 vPluginManager.shutdownEmulator();
688 public void menuVRetraceStart(String i, Object[] args)
690 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_START;
691 menuManager.setSelected("Breakpoints→Trap VRetrace Start",
692 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_START) == TraceTrap.TRACE_STOP_VRETRACE_START);
695 public void menuVRetraceEnd(String i, Object[] args)
697 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_END;
698 menuManager.setSelected("Breakpoints→Trap VRetrace End",
699 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_END) == TraceTrap.TRACE_STOP_VRETRACE_END);
702 public void menuTimedStop(String i, Object[] args)
704 for(int j = 0; j < stopLabel.length; j++) {
705 String label = "Breakpoints→Timed Stops→" + stopLabel[j];
706 if(i.equals(label)) {
707 this.imminentTrapTime = stopTime[j];
708 menuManager.select(label);
709 } else
710 menuManager.unselect(label);
714 public void menuSave(String i, Object[] args)
716 restoreFocus = true;
717 (new Thread(new SaveStateTask(((Boolean)args[0]).booleanValue()))).start();
720 public void menuStatusDump(String i, Object[] args)
722 restoreFocus = true;
723 (new Thread(new StatusDumpTask())).start();
726 public void menuLoad(String i, Object[] args)
728 restoreFocus = true;
729 (new Thread(new LoadStateTask(((Integer)args[0]).intValue()))).start();
732 public void menuRAMDump(String i, Object[] args)
734 restoreFocus = true;
735 (new Thread(new RAMDumpTask(((Boolean)args[0]).booleanValue()))).start();
738 public void menuTruncate(String i, Object[] args)
740 currentProject.events.truncateEventStream();
743 public void menuChangeDisk(String i, Object[] args)
745 changeFloppy(((Integer)args[0]).intValue(), ((Integer)args[1]).intValue());
748 public void menuWriteProtect(String i, Object[] args)
750 int disk = ((Integer)args[0]).intValue();
751 writeProtect(disk, menuManager.isSelected(i));
752 DiskImageSet imageSet = pc.getDisks();
753 menuManager.setSelected(i, imageSet.lookupDisk(disk).isReadOnly());
756 public void menuAddDisk(String i, Object[] args)
758 restoreFocus = true;
759 (new Thread(new AddDiskTask())).start();
762 public void menuChangeAuthors(String i, Object[] args)
764 restoreFocus = true;
765 (new Thread(new ChangeAuthorsTask())).start();
768 public synchronized void start()
770 vPluginManager.pcStarted();
771 running = true;
772 notifyAll();
775 private String prettyPrintTime(long ts)
777 String s = "";
779 if(ts >= 1000000000)
780 s = s + "" + (ts / 1000000000) + " ";
781 if(ts >= 100000000)
782 s = s + "" + (ts % 1000000000 / 100000000);
783 if(ts >= 10000000)
784 s = s + "" + (ts % 100000000 / 10000000);
785 if(ts >= 1000000)
786 s = s + "" + (ts % 10000000 / 1000000) + " ";
787 if(ts >= 100000)
788 s = s + "" + (ts % 1000000 / 100000);
789 if(ts >= 10000)
790 s = s + "" + (ts % 100000 / 10000);
791 if(ts >= 1000)
792 s = s + "" + (ts % 10000 / 1000) + " ";
793 if(ts >= 100)
794 s = s + "" + (ts % 1000 / 100);
795 if(ts >= 10)
796 s = s + "" + (ts % 100 / 10);
797 s = s + "" + (ts % 10);
798 return s;
801 protected synchronized void stopNoWait()
803 running = false;
804 vPluginManager.pcStopped();
805 Clock sysClock = (Clock)pc.getComponent(Clock.class);
806 System.err.println("Notice: PC emulation stopped (at time sequence value " +
807 prettyPrintTime(sysClock.getTime()) + ")");
810 public synchronized void stop()
812 willCleanup = true;
813 pc.getTraceTrap().doPotentialTrap(TraceTrap.TRACE_STOP_IMMEDIATE);
814 running = false;
815 System.err.println("Informational: Waiting for PC to halt...");
816 while(!waiting)
817 try {
818 wait();
819 } catch(Exception e) {
821 willCleanup = false;
822 stopNoWait();
825 public JScrollPane getMonitorPane()
827 return null;
830 protected void reset()
832 pc.reboot();
835 public synchronized boolean isRunning()
837 return running;
840 private void changeFloppy(int drive, int image)
844 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
845 changer.changeFloppyDisk(drive, image);
846 } catch (Exception e) {
847 System.err.println("Error: Failed to change disk");
848 errorDialog(e, "Failed to change disk", null, "Dismiss");
852 private void writeProtect(int image, boolean state)
856 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
857 changer.wpFloppyDisk(image, state);
858 } catch (Exception e) {
859 System.err.println("Error: Failed to change floppy write protect");
860 errorDialog(e, "Failed to write (un)protect floppy", null, "Dismiss");
864 private class LoadStateTask extends AsyncGUITask
866 File choosen;
867 Exception caught;
868 PleaseWait pw;
869 int _mode;
870 long oTime;
871 private static final int MODE_NORMAL = 1;
872 private static final int MODE_PRESERVE = 2;
873 private static final int MODE_MOVIEONLY = 3;
875 public LoadStateTask(int mode)
877 oTime = System.currentTimeMillis();
878 choosen = null;
879 _mode = mode;
880 pw = new PleaseWait("Loading savestate...");
883 public LoadStateTask(String name, int mode)
885 this(mode);
886 choosen = new File(name);
889 protected void runPrepare()
891 PCControl.this.setEnabled(false);
892 if(choosen == null) {
893 int returnVal = 0;
894 if(_mode == MODE_PRESERVE)
895 returnVal = snapshotFileChooser.showDialog(PCControl.this, "LOAD JPC-RR Snapshot (PE)");
896 else if(_mode == MODE_MOVIEONLY)
897 returnVal = snapshotFileChooser.showDialog(PCControl.this, "LOAD JPC-RR Snapshot (MO)");
898 else
899 returnVal = snapshotFileChooser.showDialog(PCControl.this, "LOAD JPC-RR Snapshot");
900 choosen = snapshotFileChooser.getSelectedFile();
902 if (returnVal != 0)
903 choosen = null;
905 pw.popUp();
908 protected void runFinish()
910 if(caught == null) {
911 try {
912 connectPC(pc = currentProject.pc);
913 System.err.println("Informational: Loadstate done");
914 } catch(Exception e) {
915 caught = e;
918 pw.popDown();
919 if(caught != null) {
920 errorDialog(caught, "Load savestate failed", PCControl.this, "Dismiss");
922 PCControl.this.setEnabled(true);
923 if(restoreFocus)
924 PCControl.this.requestFocus(true);
925 restoreFocus = false;
926 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
927 PCControl.this.vPluginManager.signalCommandCompletion();
930 protected void runTask()
932 if(choosen == null)
933 return;
935 try {
936 System.err.println("Informational: Loading a snapshot of JPC-RR");
937 long times1 = System.currentTimeMillis();
938 JRSRArchiveReader reader = new JRSRArchiveReader(choosen.getAbsolutePath());
940 PC.PCFullStatus fullStatus = PC.loadSavestate(reader, (_mode == MODE_PRESERVE) ?
941 currentProject.events : null, (_mode == MODE_MOVIEONLY));
942 if(currentProject.projectID != null && fullStatus.projectID.equals(currentProject.projectID))
943 if(currentProject.rerecords > fullStatus.rerecords)
944 fullStatus.rerecords = currentProject.rerecords + 1;
945 else
946 fullStatus.rerecords++;
947 else
948 fullStatus.rerecords++;
950 currentProject = fullStatus;
952 reader.close();
953 long times2 = System.currentTimeMillis();
954 System.err.println("Informational: Loadstate complete (" + (times2 - times1) + "ms).");
955 } catch(Exception e) {
956 caught = e;
961 private class SaveStateTask extends AsyncGUITask
963 File choosen;
964 Exception caught;
965 boolean movieOnly;
966 PleaseWait pw;
967 long oTime;
969 public SaveStateTask(boolean movie)
971 oTime = System.currentTimeMillis();
972 choosen = null;
973 movieOnly = movie;
974 pw = new PleaseWait("Saving savestate...");
977 public SaveStateTask(String name, boolean movie)
979 this(movie);
980 choosen = new File(name);
983 protected void runPrepare()
985 PCControl.this.setEnabled(false);
986 if(choosen == null) {
987 int returnVal = snapshotFileChooser.showDialog(PCControl.this, movieOnly ? "Save JPC-RR Movie" :
988 "Save JPC-RR Snapshot");
989 choosen = snapshotFileChooser.getSelectedFile();
991 if (returnVal != 0)
992 choosen = null;
994 pw.popUp();
997 protected void runFinish()
999 pw.popDown();
1000 if(caught != null) {
1001 errorDialog(caught, "Saving savestate failed", PCControl.this, "Dismiss");
1003 PCControl.this.setEnabled(true);
1004 if(restoreFocus)
1005 PCControl.this.requestFocus(true);
1006 restoreFocus = false;
1007 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1008 PCControl.this.vPluginManager.signalCommandCompletion();
1011 protected void runTask()
1013 if(choosen == null)
1014 return;
1016 JRSRArchiveWriter writer = null;
1018 try {
1019 System.err.println("Informational: Savestating...");
1020 long times1 = System.currentTimeMillis();
1021 writer = new JRSRArchiveWriter(choosen.getAbsolutePath());
1022 PC.saveSavestate(writer, currentProject, movieOnly, uncompressedSave);
1023 renameFile(choosen, new File(choosen.getAbsolutePath() + ".backup"));
1024 writer.close();
1025 long times2 = System.currentTimeMillis();
1026 System.err.println("Informational: Savestate complete (" + (times2 - times1) + "ms).");
1027 } catch(Exception e) {
1028 if(writer != null)
1029 try { writer.rollback(); } catch(Exception f) {}
1030 caught = e;
1035 private class StatusDumpTask extends AsyncGUITask
1037 File choosen;
1038 Exception caught;
1039 PleaseWait pw;
1041 public StatusDumpTask()
1043 choosen = null;
1044 pw = new PleaseWait("Saving status dump...");
1047 public StatusDumpTask(String name)
1049 this();
1050 choosen = new File(name);
1053 protected void runPrepare()
1055 PCControl.this.setEnabled(false);
1056 if(choosen == null) {
1057 int returnVal = snapshotFileChooser.showDialog(PCControl.this, "Save Status dump");
1058 choosen = snapshotFileChooser.getSelectedFile();
1060 if (returnVal != 0)
1061 choosen = null;
1063 pw.popUp();
1066 protected void runFinish()
1068 pw.popDown();
1069 if(caught != null) {
1070 errorDialog(caught, "Status dump failed", PCControl.this, "Dismiss");
1072 PCControl.this.setEnabled(true);
1073 if(restoreFocus)
1074 PCControl.this.requestFocus(true);
1075 restoreFocus = false;
1076 PCControl.this.vPluginManager.signalCommandCompletion();
1079 protected void runTask()
1081 if(choosen == null)
1082 return;
1084 try {
1085 OutputStream outb = new BufferedOutputStream(new FileOutputStream(choosen));
1086 PrintStream out = new PrintStream(outb, false, "UTF-8");
1087 StatusDumper sd = new StatusDumper(out);
1088 pc.dumpStatus(sd);
1089 out.flush();
1090 outb.flush();
1091 System.err.println("Informational: Dumped " + sd.dumpedObjects() + " objects");
1092 } catch(Exception e) {
1093 caught = e;
1098 private class RAMDumpTask extends AsyncGUITask
1100 File choosen;
1101 Exception caught;
1102 PleaseWait pw;
1103 boolean binary;
1105 public RAMDumpTask(boolean binFlag)
1107 choosen = null;
1108 pw = new PleaseWait("Saving RAM dump...");
1109 binary = binFlag;
1112 public RAMDumpTask(String name, boolean binFlag)
1114 this(binFlag);
1115 choosen = new File(name);
1118 protected void runPrepare()
1120 PCControl.this.setEnabled(false);
1121 if(choosen == null) {
1122 int returnVal;
1123 if(binary)
1124 returnVal = snapshotFileChooser.showDialog(PCControl.this, "Save RAM dump");
1125 else
1126 returnVal = snapshotFileChooser.showDialog(PCControl.this, "Save RAM hexdump");
1127 choosen = snapshotFileChooser.getSelectedFile();
1129 if (returnVal != 0)
1130 choosen = null;
1132 pw.popUp();
1135 protected void runFinish()
1137 pw.popDown();
1138 if(caught != null) {
1139 errorDialog(caught, "RAM dump failed", PCControl.this, "Dismiss");
1141 PCControl.this.setEnabled(true);
1142 if(restoreFocus)
1143 PCControl.this.requestFocus(true);
1144 restoreFocus = false;
1145 PCControl.this.vPluginManager.signalCommandCompletion();
1148 protected void runTask()
1150 if(choosen == null)
1151 return;
1153 try {
1154 OutputStream outb = new BufferedOutputStream(new FileOutputStream(choosen));
1155 byte[] pagebuf = new byte[4096];
1156 PhysicalAddressSpace addr = (PhysicalAddressSpace)pc.getComponent(PhysicalAddressSpace.class);
1157 int lowBound = addr.findFirstRAMPage(0);
1158 int firstUndumped = 0;
1159 int highBound = 0;
1160 int present = 0;
1161 while(lowBound >= 0) {
1162 for(; firstUndumped < lowBound; firstUndumped++)
1163 dumpPage(outb, firstUndumped, null);
1164 addr.readRAMPage(firstUndumped++, pagebuf);
1165 dumpPage(outb, lowBound, pagebuf);
1166 present++;
1167 highBound = lowBound + 1;
1168 lowBound = addr.findFirstRAMPage(++lowBound);
1170 outb.flush();
1171 System.err.println("Informational: Dumped machine RAM (" + highBound + " pages examined, " +
1172 present + " pages present).");
1173 } catch(Exception e) {
1174 caught = e;
1178 private byte charForHex(int hvalue)
1180 if(hvalue < 10)
1181 return (byte)(hvalue + 48);
1182 else if(hvalue > 9 && hvalue < 16)
1183 return (byte)(hvalue + 55);
1184 else
1185 System.err.println("Unknown hex value: " + hvalue + ".");
1186 return 90;
1189 private void dumpPage(OutputStream stream, int pageNo, byte[] buffer) throws IOException
1191 int pageBufSize;
1192 pageNo = pageNo & 0xFFFFF; //Cut page numbers out of range.
1193 if(!binary && buffer == null)
1194 return; //Don't dump null pages in non-binary mode.
1195 if(binary)
1196 pageBufSize = 4096; //Binary page buffer is 4096 bytes.
1197 else
1198 pageBufSize = 14592; //Hexdump page buffer is 14592 bytes.
1199 byte[] outputPage = new byte[pageBufSize];
1200 if(buffer != null && binary) {
1201 System.arraycopy(buffer, 0, outputPage, 0, 4096);
1202 } else if(buffer != null) { //Hex mode
1203 for(int i = 0; i < 256; i++) {
1204 for(int j = 0; j < 57; j++) {
1205 if(j < 5)
1206 outputPage[57 * i + j] = charForHex((pageNo >>> (4 * (4 - j))) & 0xF);
1207 else if(j == 5)
1208 outputPage[57 * i + j] = charForHex(i / 16);
1209 else if(j == 6)
1210 outputPage[57 * i + j] = charForHex(i % 16);
1211 else if(j == 7)
1212 outputPage[57 * i + j] = 48;
1213 else if(j == 56)
1214 outputPage[57 * i + j] = 10;
1215 else if(j % 3 == 2)
1216 outputPage[57 * i + j] = 32;
1217 else if(j % 3 == 0)
1218 outputPage[57 * i + j] = charForHex(((int)buffer[16 * i + j / 3 - 3] & 0xFF) / 16);
1219 else if(j % 3 == 1)
1220 outputPage[57 * i + j] = charForHex(buffer[16 * i + j / 3 - 3] & 0xF);
1221 else
1222 System.err.println("Error: dumpPage: unhandled j = " + j + ".");
1226 stream.write(outputPage);
1230 private class AssembleTask extends AsyncGUITask
1232 Exception caught;
1233 PleaseWait pw;
1234 boolean canceled;
1236 public AssembleTask()
1238 pw = new PleaseWait("Assembling PC...");
1239 canceled = false;
1242 protected void runPrepare()
1244 PCControl.this.setEnabled(false);
1245 try {
1246 configDialog.popUp();
1247 } catch(Exception e) {
1248 caught = e;
1252 protected void runFinish()
1254 if(caught == null && !canceled) {
1255 try {
1256 currentProject.projectID = randomHexes(24);
1257 currentProject.rerecords = 0;
1258 currentProject.events = new EventRecorder();
1259 currentProject.events.attach(pc, null);
1260 currentProject.savestateID = null;
1261 currentProject.extraHeaders = null;
1262 connectPC(pc);
1263 } catch(Exception e) {
1264 caught = e;
1267 if(!canceled)
1268 pw.popDown();
1269 if(caught != null) {
1270 errorDialog(caught, "PC Assembly failed", PCControl.this, "Dismiss");
1272 PCControl.this.setEnabled(true);
1273 if(restoreFocus)
1274 PCControl.this.requestFocus(true);
1275 restoreFocus = false;
1276 PCControl.this.vPluginManager.signalCommandCompletion();
1279 protected void runTask()
1281 if(caught != null)
1282 return;
1283 PC.PCHardwareInfo hw = configDialog.waitClose();
1284 if(hw == null) {
1285 canceled = true;
1286 return;
1288 try {
1289 SwingUtilities.invokeAndWait(new Thread() { public void run() { pw.popUp(); }});
1290 } catch(Exception e) {
1293 try {
1294 pc = PC.createPC(hw);
1295 } catch(Exception e) {
1296 caught = e;
1301 private class DumpControlTask extends AsyncGUITask
1303 Exception caught;
1304 boolean canceled;
1306 public DumpControlTask()
1310 protected void runPrepare()
1312 PCControl.this.setEnabled(false);
1313 try {
1314 dumpDialog.popUp(PCControl.this.pc);
1315 } catch(Exception e) {
1316 caught = e;
1320 protected void runFinish()
1322 if(caught != null) {
1323 errorDialog(caught, "Opening dump control dialog failed", PCControl.this, "Dismiss");
1325 PCControl.this.setEnabled(true);
1326 if(restoreFocus)
1327 PCControl.this.requestFocus(true);
1328 restoreFocus = false;
1331 protected void runTask()
1333 if(caught != null)
1334 return;
1335 dumpDialog.waitClose();
1339 private class AddDiskTask extends AsyncGUITask
1341 Exception caught;
1342 NewDiskDialog dd;
1344 public AddDiskTask()
1346 dd = new NewDiskDialog();
1347 PCControl.this.setEnabled(false);
1350 protected void runPrepare()
1354 protected void runFinish()
1356 if(caught != null) {
1357 errorDialog(caught, "Adding disk failed", PCControl.this, "Dismiss");
1359 PCControl.this.setEnabled(true);
1360 if(restoreFocus)
1361 PCControl.this.requestFocus(true);
1362 restoreFocus = false;
1363 try {
1364 updateDisks();
1365 } catch(Exception e) {
1366 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
1368 PCControl.this.vPluginManager.signalCommandCompletion();
1371 protected void runTask()
1373 NewDiskDialog.Response res = dd.waitClose();
1374 if(res == null) {
1375 return;
1377 try {
1378 DiskImage img;
1379 pc.getDisks().addDisk(img = new DiskImage(res.diskFile, false));
1380 img.setName(res.diskName);
1381 } catch(Exception e) {
1382 caught = e;
1387 private class ChangeAuthorsTask extends AsyncGUITask
1389 Exception caught;
1390 AuthorsDialog ad;
1392 public ChangeAuthorsTask()
1394 int authors = 0;
1395 int headers = 0;
1396 String[] authorNames = null;
1398 if(currentProject != null && currentProject.extraHeaders != null) {
1399 headers = currentProject.extraHeaders.length;
1400 for(int i = 0; i < headers; i++)
1401 if(currentProject.extraHeaders[i][0].equals("AUTHORS"))
1402 authors += (currentProject.extraHeaders[i].length - 1);
1404 if(authors > 0) {
1405 int j = 0;
1406 authorNames = new String[authors];
1407 for(int i = 0; i < headers; i++) {
1408 if(currentProject.extraHeaders[i][0].equals("AUTHORS")) {
1409 System.arraycopy(currentProject.extraHeaders[i], 1, authorNames, j,
1410 currentProject.extraHeaders[i].length - 1);
1411 j += (currentProject.extraHeaders[i].length - 1);
1416 ad = new AuthorsDialog(authorNames);
1417 PCControl.this.setEnabled(false);
1420 protected void runPrepare()
1424 protected void runFinish()
1426 if(caught != null) {
1427 errorDialog(caught, "Changing authors failed", PCControl.this, "Dismiss");
1429 PCControl.this.setEnabled(true);
1430 if(restoreFocus)
1431 PCControl.this.requestFocus(true);
1432 restoreFocus = false;
1433 PCControl.this.vPluginManager.signalCommandCompletion();
1436 protected void runTask()
1438 AuthorsDialog.Response res = ad.waitClose();
1439 if(res == null) {
1440 return;
1442 try {
1443 int newAuthors = 0;
1444 int oldAuthors = 0;
1445 int headers = 0;
1446 if(currentProject != null && currentProject.extraHeaders != null) {
1447 headers = currentProject.extraHeaders.length;
1448 for(int i = 0; i < headers; i++)
1449 if(currentProject.extraHeaders[i][0].equals("AUTHORS"))
1450 oldAuthors++;
1452 if(res.authors != null) {
1453 for(int i = 0; i < res.authors.length; i++)
1454 if(res.authors[i] != null)
1455 newAuthors++;
1457 if(headers == oldAuthors && newAuthors == 0) {
1458 //Remove all extra headers.
1459 currentProject.extraHeaders = null;
1460 return;
1463 String[][] newHeaders = new String[headers + newAuthors - oldAuthors][];
1464 int writePos = 0;
1466 //Copy the non-authors headers.
1467 if(currentProject != null && currentProject.extraHeaders != null) {
1468 for(int i = 0; i < headers; i++)
1469 if(!currentProject.extraHeaders[i][0].equals("AUTHORS"))
1470 newHeaders[writePos++] = currentProject.extraHeaders[i];
1472 if(res.authors != null)
1473 for(int i = 0; i < res.authors.length; i++)
1474 if(res.authors[i] != null)
1475 newHeaders[writePos++] = new String[]{"AUTHORS", res.authors[i]};
1476 currentProject.extraHeaders = newHeaders;
1477 } catch(Exception e) {
1478 caught = e;