Allow action combos from extra menu
[jpcrr.git] / org / jpc / plugins / PCControl.java
blob860f811d1401a42084feed344cc41c136df13082
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;
102 private Map<String, List<String[]> > extraActions;
104 private PC.PCFullStatus currentProject;
106 static
108 stopTime = new long[] {-1, 0, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000,
109 5000000, 10000000, 20000000, 50000000, 100000000, 200000000, 500000000, 1000000000, 2000000000,
110 5000000000L, 10000000000L, 20000000000L, 50000000000L};
111 stopLabel = new String[] {"(unbounded)", "(singlestep)", "1µs", "2µs", "5µs", "10µs", "20µs", "50µs", "100µs",
112 "200µs", "500µs","1ms", "2ms", "5ms", "10ms", "20ms", "50ms", "100ms", "200ms", "500ms", "1s", "2s", "5s",
113 "10s", "20s", "50s"};
116 public boolean systemShutdown()
118 if(!running || pc == null)
119 return true;
120 //We are running. Do the absolute minimum since we are running in very delicate context.
121 shuttingDown = true;
122 stop();
123 return true;
126 public void reconnect(PC pc)
128 pcStopping(); //Do the equivalent effects.
129 dumpDialog.clearDumps();
133 private void setTrapFlags()
135 pc.getTraceTrap().setTrapFlags(trapFlags);
138 public void pcStarting()
140 long profile = PROFILE_HAVE_PC | PROFILE_RUNNING;
141 if(currentProject != null && currentProject.events != null);
142 profile |= PROFILE_EVENTS;
143 if(pc.getCDROMIndex() >= 0)
144 profile |= PROFILE_CDROM;
146 menuManager.setProfile(profile);
148 if (running)
149 return;
151 setTrapFlags();
153 Clock sysClock = (Clock)pc.getComponent(Clock.class);
154 long current = sysClock.getTime();
155 if(imminentTrapTime > 0) {
156 pc.getTraceTrap().setTrapTime(current + imminentTrapTime);
157 } else if(imminentTrapTime == 0) {
158 //Hack: We set trace trap to trap immediately. It comes too late to abort next instruction, but
159 //early enough to abort one after that.
160 pc.getTraceTrap().setTrapTime(current);
162 if(currentProject.events != null)
163 currentProject.events.setPCRunStatus(true);
166 public void pcStopping()
168 if(currentProject.events != null)
169 currentProject.events.setPCRunStatus(false);
170 if(shuttingDown)
171 return; //Don't mess with UI when shutting down.
174 long profile = PROFILE_STOPPED;
175 if(pc != null)
176 profile |= PROFILE_HAVE_PC;
177 else
178 profile |= PROFILE_NO_PC;
179 if(currentProject != null && currentProject.events != null);
180 profile |= PROFILE_EVENTS;
181 if(pc.getCDROMIndex() >= 0)
182 profile |= PROFILE_CDROM;
184 menuManager.setProfile(profile);
186 try {
187 updateDisks();
188 } catch(Exception e) {
189 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
192 if(pc != null) {
193 pc.getTraceTrap().clearTrapTime();
194 pc.getTraceTrap().getAndClearTrapActive();
198 private String diskNameByIdx(int idx)
200 return pc.getDisks().lookupDisk(idx).getName();
203 private void updateDisks() throws Exception
205 for(String x : disks)
206 menuManager.removeMenuItem(x);
208 disks.clear();
210 if(pc == null)
211 return;
213 DiskImageSet imageSet = pc.getDisks();
214 int[] floppies = imageSet.diskIndicesByType(BlockDevice.Type.FLOPPY);
215 int[] cdroms = imageSet.diskIndicesByType(BlockDevice.Type.CDROM);
217 for(int i = 0; i < floppies.length; i++) {
218 String name = diskNameByIdx(floppies[i]);
219 menuManager.addMenuItem("Drives→fda→" + name, this, "menuChangeDisk", new Object[]{new Integer(0),
220 new Integer(floppies[i])}, PROFILE_HAVE_PC);
221 menuManager.addMenuItem("Drives→fdb→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
222 new Integer(floppies[i])}, PROFILE_HAVE_PC);
223 menuManager.addSelectableMenuItem("Drives→Write Protect→" + name, this, "menuWriteProtect",
224 new Object[]{new Integer(floppies[i])}, imageSet.lookupDisk(floppies[i]).isReadOnly(),
225 PROFILE_HAVE_PC);
226 disks.add("Drives→fda→" + name);
227 disks.add("Drives→fdb→" + name);
228 disks.add("Drives→Write Protect→" + name);
231 for(int i = 0; i < cdroms.length; i++) {
232 String name = diskNameByIdx(cdroms[i]);
233 menuManager.addMenuItem("Drives→CD-ROM→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
234 new Integer(cdroms[i])}, PROFILE_HAVE_PC | PROFILE_CDROM);
235 disks.add("Drives→CD-ROM→" + name);
239 public void main()
241 boolean wasRunning = false;
242 while(true) { //We will be killed by JVM.
243 //Wait for us to become runnable again.
244 while(!running || pc == null) {
245 if(wasRunning && pc != null)
246 pc.stop();
247 wasRunning = running;
248 try {
249 synchronized(this) {
250 waiting = true;
251 notifyAll();
252 wait();
253 waiting = false;
255 } catch(Exception e) {
259 if(!wasRunning)
260 pc.start();
261 wasRunning = running;
263 try {
264 pc.execute();
265 if(pc.getHitTraceTrap()) {
266 if(pc.getAndClearTripleFaulted())
267 callShowOptionDialog(this, "CPU shut itself down due to triple fault. Rebooting the system.",
268 "Triple fault!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
269 new String[]{"Dismiss"}, "Dismiss");
270 if(!willCleanup)
271 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
272 running = false;
274 } catch (Exception e) {
275 errorDialog(e, "Hardware emulator internal error", this, "Dismiss");
276 try {
277 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
278 } catch (Exception f) {
285 public void connectPC(PC pc)
287 currentProject.pc = pc;
288 vPluginManager.reconnect(pc);
289 this.pc = pc;
292 private void startExternal()
294 if(pc != null && !running)
295 if(!SwingUtilities.isEventDispatchThread())
296 try {
297 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.start(); }});
298 } catch(Exception e) {
300 else
301 start();
304 private void stopExternal()
306 if(pc != null && running)
307 if(!SwingUtilities.isEventDispatchThread())
308 try {
309 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.stop(); }});
310 } catch(Exception e) {
312 else
313 stop();
316 public boolean eci_state_save(String filename)
318 if(!running)
319 (new Thread(new SaveStateTask(filename, false))).start();
320 return !running;
323 public boolean eci_state_dump(String filename)
325 if(!running)
326 (new Thread(new StatusDumpTask(filename))).start();
327 return !running;
330 public boolean eci_movie_save(String filename)
332 if(!running)
333 (new Thread(new SaveStateTask(filename, true))).start();
334 return !running;
337 public boolean eci_state_load(String filename)
339 if(!running)
340 (new Thread(new LoadStateTask(filename, LoadStateTask.MODE_NORMAL))).start();
341 return !running;
344 public boolean eci_state_load_noevents(String filename)
346 if(!running)
347 (new Thread(new LoadStateTask(filename, LoadStateTask.MODE_PRESERVE))).start();
348 return !running;
351 public boolean eci_movie_load(String filename)
353 if(!running)
354 (new Thread(new LoadStateTask(filename, LoadStateTask.MODE_MOVIEONLY))).start();
355 return !running;
358 public boolean eci_pc_assemble()
360 if(!running)
361 (new Thread(new AssembleTask())).start();
362 return !running;
365 public boolean eci_ram_dump_text(String filename)
367 if(!running)
368 (new Thread(new RAMDumpTask(filename, false))).start();
369 return !running;
372 public boolean eci_ram_dump_binary(String filename)
374 if(!running)
375 (new Thread(new RAMDumpTask(filename, true))).start();
376 return !running;
379 public void eci_trap_vretrace_start_on()
381 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_START;
384 public void eci_trap_vretrace_start_off()
386 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_START;
389 public void eci_trap_vretrace_end_on()
391 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_END;
394 public void eci_trap_vretrace_end_off()
396 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_END;
399 public void eci_trap_timed_disable()
401 this.imminentTrapTime = -1;
404 public void eci_trap_timed(Long time)
406 this.imminentTrapTime = time.longValue();
409 public void eci_pc_start()
411 startExternal();
414 public void eci_pc_stop()
416 stopExternal();
419 public void eci_pccontrol_setwinpos(Integer x, Integer y)
421 moveWindow(this, x.intValue(), y.intValue(), nativeWidth, nativeHeight);
424 public void eci_sendevent(String clazz, String[] rargs)
426 System.err.println("Event to: '" + clazz + "':");
427 for(int i = 0; i < rargs.length; i++) {
428 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
430 if(currentProject.events != null) {
431 try {
432 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
433 currentProject.events.addEvent(0L, x, rargs);
434 } catch(Exception e) {
435 System.err.println("Error adding event: " + e.getMessage());
440 public void eci_memory_read(Long address, Integer size)
442 if(currentProject.pc != null) {
443 long addr = address.longValue();
444 long _size = size.intValue();
445 long ret = 0;
446 PhysicalAddressSpace addrSpace;
447 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
448 return;
450 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
451 if(_size == 1)
452 ret = (long)addrSpace.getByte((int)addr) & 0xFF;
453 else if(_size == 2)
454 ret = (long)addrSpace.getWord((int)addr) & 0xFFFF;
455 else if(_size == 4)
456 ret = (long)addrSpace.getDoubleWord((int)addr) & 0xFFFFFFFFL;
458 vPluginManager.returnValue(ret);
462 public void eci_memory_write(Long address, Long value, Integer size)
464 if(currentProject.pc != null) {
465 long addr = address.longValue();
466 long _size = size.intValue();
467 long _value = value.longValue();
468 PhysicalAddressSpace addrSpace;
469 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
470 return;
472 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
473 if(_size == 1)
474 addrSpace.setByte((int)addr, (byte)_value);
475 else if(_size == 2)
476 addrSpace.setWord((int)addr, (short)_value);
477 else if(_size == 4)
478 addrSpace.setDoubleWord((int)addr, (int)_value);
482 public PCControl(Plugins manager, String args) throws Exception
484 this(manager);
486 UTFInputLineStream file = null;
487 Map<String, String> params = parseStringToComponents(args);
488 Set<String> used = new HashSet<String>();
489 String extramenu = params.get("extramenu");
490 String uncompress = params.get("uncompressedsave");
491 if(uncompress != null)
492 uncompressedSave = true;
493 if(extramenu == null)
494 return;
495 try {
496 file = new UTFInputLineStream(new FileInputStream(extramenu));
498 while(true) {
499 boolean exists = false;
500 String[] line = nextParseLine(file);
501 if(line == null)
502 break;
503 if(line.length < 3 || line[0].charAt(0) == '→') {
504 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
505 continue;
507 if(line[0].length() == 0 || line[0].charAt(line[0].length() - 1) == '→') {
508 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
509 continue;
511 if(line[0].indexOf("→→") >= 0) {
512 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
513 continue;
515 if(used.contains(line[0]))
516 exists = true;
518 KeyStroke stroke = null;
519 if(!line[1].equals("<>")) {
520 stroke = KeyStroke.getKeyStroke(line[1]);
521 if(stroke == null) {
522 System.err.println("Warning: Bad keystroke '" + line[1] + "'.");
527 String[] lineCommand = Arrays.copyOfRange(line, 2, line.length);
528 used.add(line[0]);
529 List<String[]> commandList = extraActions.get(line[0]);
530 if(commandList == null)
531 extraActions.put(line[0], commandList = new ArrayList<String[]>());
532 commandList.add(lineCommand);
534 if(!exists)
535 menuManager.addMenuItem("Extra→" + line[0], this, "menuExtra", new String[]{line[0]}, PROFILE_ALWAYS,
536 stroke);
538 file.close();
539 } catch(IOException e) {
540 errorDialog(e, "Failed to load extra menu defintions", null, "dismiss");
541 if(file != null)
542 file.close();
544 setJMenuBar(menuManager.getMainBar());
547 public PCControl(Plugins manager) throws Exception
549 super("JPC-RR");
551 if(DiskImage.getLibrary() == null)
552 throw new Exception("PCControl plugin requires disk library");
554 running = false;
555 this.willCleanup = false;
556 shuttingDown = false;
557 configDialog = new PCConfigDialog();
558 dumpDialog = new DumpControlDialog(manager);
559 extraActions = new HashMap<String, List<String[]> >();
560 menuManager = new MenuManager();
562 menuManager.setProfile(PROFILE_NO_PC | PROFILE_STOPPED);
564 menuManager.addMenuItem("System→Assemble", this, "menuAssemble", null, PROFILE_STOPPED);
565 menuManager.addMenuItem("System→Start", this, "menuStart", null, PROFILE_STOPPED | PROFILE_HAVE_PC);
566 menuManager.addMenuItem("System→Stop", this, "menuStop", null, PROFILE_RUNNING);
567 menuManager.addMenuItem("System→Reset", this, "menuReset", null, PROFILE_HAVE_PC);
568 menuManager.addMenuItem("System→Dumping control", this, "menuDump", null, PROFILE_HAVE_PC | PROFILE_STOPPED);
569 menuManager.addMenuItem("System→Quit", this, "menuQuit", null, PROFILE_ALWAYS);
570 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace Start", this, "menuVRetraceStart", null, false,
571 PROFILE_ALWAYS);
572 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace End", this, "menuVRetraceEnd", null, false,
573 PROFILE_ALWAYS);
574 menuManager.addMenuItem("Snapshot→Change Run Authors", this, "menuChangeAuthors", null, PROFILE_HAVE_PC);
575 menuManager.addMenuItem("Snapshot→Save→Snapshot", this, "menuSave", new Object[]{new Boolean(false)},
576 PROFILE_HAVE_PC | PROFILE_STOPPED);
577 menuManager.addMenuItem("Snapshot→Save→Movie", this, "menuSave", new Object[]{new Boolean(true)},
578 PROFILE_HAVE_PC | PROFILE_STOPPED);
579 menuManager.addMenuItem("Snapshot→Save→Status Dump", this, "menuStatusDump", null,
580 PROFILE_HAVE_PC | PROFILE_STOPPED);
581 menuManager.addMenuItem("Snapshot→Load→Snapshot", this, "menuLoad",
582 new Object[]{new Integer(LoadStateTask.MODE_NORMAL)}, PROFILE_STOPPED);
583 menuManager.addMenuItem("Snapshot→Load→Snapshot (preserve events)", this, "menuLoad",
584 new Object[]{new Integer(LoadStateTask.MODE_PRESERVE)}, PROFILE_STOPPED | PROFILE_EVENTS);
585 menuManager.addMenuItem("Snapshot→Load→Movie", this, "menuLoad",
586 new Object[]{new Integer(LoadStateTask.MODE_MOVIEONLY)}, PROFILE_STOPPED);
587 menuManager.addMenuItem("Snapshot→RAM Dump→Hexadecimal", this, "menuRAMDump", new Object[]{new Boolean(false)},
588 PROFILE_HAVE_PC | PROFILE_STOPPED);
589 menuManager.addMenuItem("Snapshot→RAM Dump→Binary", this, "menuRAMDump", new Object[]{new Boolean(true)},
590 PROFILE_HAVE_PC | PROFILE_STOPPED);
591 menuManager.addMenuItem("Snapshot→Truncate Event Stream", this, "menuTruncate", null,
592 PROFILE_STOPPED | PROFILE_EVENTS);
594 for(int i = 0; i < stopLabel.length; i++) {
595 menuManager.addSelectableMenuItem("Breakpoints→Timed Stops→" + stopLabel[i], this, "menuTimedStop",
596 null, (i == 0), PROFILE_ALWAYS);
598 imminentTrapTime = -1;
600 menuManager.addMenuItem("Drives→fda→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(0),
601 new Integer(-1)}, PROFILE_HAVE_PC);
602 menuManager.addMenuItem("Drives→fdb→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(1),
603 new Integer(-1)}, PROFILE_HAVE_PC);
604 menuManager.addMenuItem("Drives→CD-ROM→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(2),
605 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_CDROM);
606 menuManager.addMenuItem("Drives→Add image", this, "menuAddDisk", null, PROFILE_HAVE_PC);
607 menuManager.addMenuItem("Drives→Import Image", this, "menuImport", null, PROFILE_ALWAYS);
609 menuManager.addMenuItem("Debug→Hacks→NO_FPU", this, "menuNOFPU", null, PROFILE_HAVE_PC);
610 menuManager.addMenuItem("Debug→Hacks→VGA_DRAW", this, "menuVGADRAW", null, PROFILE_HAVE_PC);
612 disks = new HashSet<String>();
613 currentProject = new PC.PCFullStatus();
614 this.pc = null;
615 this.vPluginManager = manager;
617 setJMenuBar(menuManager.getMainBar());
621 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
623 catch (AccessControlException e)
625 System.err.println("Error: Not able to add some components to frame: " + e.getMessage());
628 snapshotFileChooser = new JFileChooser(System.getProperty("user.dir"));
630 getContentPane().validate();
631 validate();
632 pack();
633 Dimension d = getSize();
634 nativeWidth = d.width;
635 nativeHeight = d.height;
636 setVisible(true);
639 public void menuExtra(String i, Object[] args)
641 final List<String[]> commandList = extraActions.get(args[0]);
642 if(commandList == null) {
643 System.err.println("Warning: Called extra menu with unknown entry '" + args[0] + "'.");
644 return;
647 //Run the functions on seperate thread to avoid deadlocking.
648 (new Thread(new Runnable() { public void run() { menuExtraThreadFunc(commandList); }})).start();
651 private void menuExtraThreadFunc(List<String[]> actions)
653 for(String[] i : actions) {
654 if(i.length == 1) {
655 vPluginManager.invokeExternalCommandSynchronous(i[0], null);
656 } else {
657 String[] rest = Arrays.copyOfRange(i, 1, i.length, String[].class);
658 vPluginManager.invokeExternalCommandSynchronous(i[0], rest);
663 public void menuAssemble(String i, Object[] args)
665 (new Thread(new AssembleTask())).start();
668 public void menuStart(String i, Object[] args)
670 start();
673 public void menuStop(String i, Object[] args)
675 stop();
678 public void menuReset(String i, Object[] args)
680 reset();
683 public void menuDump(String i, Object[] args)
685 (new Thread(new DumpControlTask())).start();
688 public void menuImport(String i, Object[] args)
690 try {
691 new ImportDiskImage();
692 } catch(Exception e) {
693 e.printStackTrace();
697 public void menuNOFPU(String i, Object[] args)
699 pc.setFPUHack();
702 public void menuVGADRAW(String i, Object[] args)
704 pc.setVGADrawHack();
707 public void menuQuit(String i, Object[] args)
709 vPluginManager.shutdownEmulator();
712 public void menuVRetraceStart(String i, Object[] args)
714 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_START;
715 menuManager.setSelected("Breakpoints→Trap VRetrace Start",
716 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_START) == TraceTrap.TRACE_STOP_VRETRACE_START);
719 public void menuVRetraceEnd(String i, Object[] args)
721 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_END;
722 menuManager.setSelected("Breakpoints→Trap VRetrace End",
723 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_END) == TraceTrap.TRACE_STOP_VRETRACE_END);
726 public void menuTimedStop(String i, Object[] args)
728 for(int j = 0; j < stopLabel.length; j++) {
729 String label = "Breakpoints→Timed Stops→" + stopLabel[j];
730 if(i.equals(label)) {
731 this.imminentTrapTime = stopTime[j];
732 menuManager.select(label);
733 } else
734 menuManager.unselect(label);
738 public void menuSave(String i, Object[] args)
740 restoreFocus = true;
741 (new Thread(new SaveStateTask(((Boolean)args[0]).booleanValue()))).start();
744 public void menuStatusDump(String i, Object[] args)
746 restoreFocus = true;
747 (new Thread(new StatusDumpTask())).start();
750 public void menuLoad(String i, Object[] args)
752 restoreFocus = true;
753 (new Thread(new LoadStateTask(((Integer)args[0]).intValue()))).start();
756 public void menuRAMDump(String i, Object[] args)
758 restoreFocus = true;
759 (new Thread(new RAMDumpTask(((Boolean)args[0]).booleanValue()))).start();
762 public void menuTruncate(String i, Object[] args)
764 currentProject.events.truncateEventStream();
767 public void menuChangeDisk(String i, Object[] args)
769 changeFloppy(((Integer)args[0]).intValue(), ((Integer)args[1]).intValue());
772 public void menuWriteProtect(String i, Object[] args)
774 int disk = ((Integer)args[0]).intValue();
775 writeProtect(disk, menuManager.isSelected(i));
776 DiskImageSet imageSet = pc.getDisks();
777 menuManager.setSelected(i, imageSet.lookupDisk(disk).isReadOnly());
780 public void menuAddDisk(String i, Object[] args)
782 restoreFocus = true;
783 (new Thread(new AddDiskTask())).start();
786 public void menuChangeAuthors(String i, Object[] args)
788 restoreFocus = true;
789 (new Thread(new ChangeAuthorsTask())).start();
792 public synchronized void start()
794 vPluginManager.pcStarted();
795 running = true;
796 notifyAll();
799 private String prettyPrintTime(long ts)
801 String s = "";
803 if(ts >= 1000000000)
804 s = s + "" + (ts / 1000000000) + " ";
805 if(ts >= 100000000)
806 s = s + "" + (ts % 1000000000 / 100000000);
807 if(ts >= 10000000)
808 s = s + "" + (ts % 100000000 / 10000000);
809 if(ts >= 1000000)
810 s = s + "" + (ts % 10000000 / 1000000) + " ";
811 if(ts >= 100000)
812 s = s + "" + (ts % 1000000 / 100000);
813 if(ts >= 10000)
814 s = s + "" + (ts % 100000 / 10000);
815 if(ts >= 1000)
816 s = s + "" + (ts % 10000 / 1000) + " ";
817 if(ts >= 100)
818 s = s + "" + (ts % 1000 / 100);
819 if(ts >= 10)
820 s = s + "" + (ts % 100 / 10);
821 s = s + "" + (ts % 10);
822 return s;
825 protected synchronized void stopNoWait()
827 running = false;
828 vPluginManager.pcStopped();
829 Clock sysClock = (Clock)pc.getComponent(Clock.class);
830 System.err.println("Notice: PC emulation stopped (at time sequence value " +
831 prettyPrintTime(sysClock.getTime()) + ")");
834 public synchronized void stop()
836 willCleanup = true;
837 pc.getTraceTrap().doPotentialTrap(TraceTrap.TRACE_STOP_IMMEDIATE);
838 running = false;
839 System.err.println("Informational: Waiting for PC to halt...");
840 while(!waiting)
841 try {
842 wait();
843 } catch(Exception e) {
845 willCleanup = false;
846 stopNoWait();
849 public JScrollPane getMonitorPane()
851 return null;
854 protected void reset()
856 pc.reboot();
859 public synchronized boolean isRunning()
861 return running;
864 private void changeFloppy(int drive, int image)
868 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
869 changer.changeFloppyDisk(drive, image);
870 } catch (Exception e) {
871 System.err.println("Error: Failed to change disk");
872 errorDialog(e, "Failed to change disk", null, "Dismiss");
876 private void writeProtect(int image, boolean state)
880 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
881 changer.wpFloppyDisk(image, state);
882 } catch (Exception e) {
883 System.err.println("Error: Failed to change floppy write protect");
884 errorDialog(e, "Failed to write (un)protect floppy", null, "Dismiss");
888 private class LoadStateTask extends AsyncGUITask
890 File choosen;
891 Exception caught;
892 PleaseWait pw;
893 int _mode;
894 long oTime;
895 private static final int MODE_NORMAL = 1;
896 private static final int MODE_PRESERVE = 2;
897 private static final int MODE_MOVIEONLY = 3;
899 public LoadStateTask(int mode)
901 oTime = System.currentTimeMillis();
902 choosen = null;
903 _mode = mode;
904 pw = new PleaseWait("Loading savestate...");
907 public LoadStateTask(String name, int mode)
909 this(mode);
910 choosen = new File(name);
913 protected void runPrepare()
915 PCControl.this.setEnabled(false);
916 if(choosen == null) {
917 int returnVal = 0;
918 if(_mode == MODE_PRESERVE)
919 returnVal = snapshotFileChooser.showDialog(PCControl.this, "LOAD JPC-RR Snapshot (PE)");
920 else if(_mode == MODE_MOVIEONLY)
921 returnVal = snapshotFileChooser.showDialog(PCControl.this, "LOAD JPC-RR Snapshot (MO)");
922 else
923 returnVal = snapshotFileChooser.showDialog(PCControl.this, "LOAD JPC-RR Snapshot");
924 choosen = snapshotFileChooser.getSelectedFile();
926 if (returnVal != 0)
927 choosen = null;
929 pw.popUp();
932 protected void runFinish()
934 if(caught == null) {
935 try {
936 connectPC(pc = currentProject.pc);
937 System.err.println("Informational: Loadstate done");
938 } catch(Exception e) {
939 caught = e;
942 pw.popDown();
943 if(caught != null) {
944 errorDialog(caught, "Load savestate failed", PCControl.this, "Dismiss");
946 PCControl.this.setEnabled(true);
947 if(restoreFocus)
948 PCControl.this.requestFocus(true);
949 restoreFocus = false;
950 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
951 PCControl.this.vPluginManager.signalCommandCompletion();
954 protected void runTask()
956 if(choosen == null)
957 return;
959 try {
960 System.err.println("Informational: Loading a snapshot of JPC-RR");
961 long times1 = System.currentTimeMillis();
962 JRSRArchiveReader reader = new JRSRArchiveReader(choosen.getAbsolutePath());
964 PC.PCFullStatus fullStatus = PC.loadSavestate(reader, _mode == MODE_PRESERVE, _mode == MODE_MOVIEONLY,
965 currentProject);
967 currentProject = fullStatus;
969 reader.close();
970 long times2 = System.currentTimeMillis();
971 System.err.println("Informational: Loadstate complete (" + (times2 - times1) + "ms).");
972 } catch(Exception e) {
973 caught = e;
978 private class SaveStateTask extends AsyncGUITask
980 File choosen;
981 Exception caught;
982 boolean movieOnly;
983 PleaseWait pw;
984 long oTime;
986 public SaveStateTask(boolean movie)
988 oTime = System.currentTimeMillis();
989 choosen = null;
990 movieOnly = movie;
991 pw = new PleaseWait("Saving savestate...");
994 public SaveStateTask(String name, boolean movie)
996 this(movie);
997 choosen = new File(name);
1000 protected void runPrepare()
1002 PCControl.this.setEnabled(false);
1003 if(choosen == null) {
1004 int returnVal = snapshotFileChooser.showDialog(PCControl.this, movieOnly ? "Save JPC-RR Movie" :
1005 "Save JPC-RR Snapshot");
1006 choosen = snapshotFileChooser.getSelectedFile();
1008 if (returnVal != 0)
1009 choosen = null;
1011 pw.popUp();
1014 protected void runFinish()
1016 pw.popDown();
1017 if(caught != null) {
1018 errorDialog(caught, "Saving savestate failed", PCControl.this, "Dismiss");
1020 PCControl.this.setEnabled(true);
1021 if(restoreFocus)
1022 PCControl.this.requestFocus(true);
1023 restoreFocus = false;
1024 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1025 PCControl.this.vPluginManager.signalCommandCompletion();
1028 protected void runTask()
1030 if(choosen == null)
1031 return;
1033 JRSRArchiveWriter writer = null;
1035 try {
1036 System.err.println("Informational: Savestating...");
1037 long times1 = System.currentTimeMillis();
1038 writer = new JRSRArchiveWriter(choosen.getAbsolutePath());
1039 PC.saveSavestate(writer, currentProject, movieOnly, uncompressedSave);
1040 renameFile(choosen, new File(choosen.getAbsolutePath() + ".backup"));
1041 writer.close();
1042 long times2 = System.currentTimeMillis();
1043 System.err.println("Informational: Savestate complete (" + (times2 - times1) + "ms).");
1044 } catch(Exception e) {
1045 if(writer != null)
1046 try { writer.rollback(); } catch(Exception f) {}
1047 caught = e;
1052 private class StatusDumpTask extends AsyncGUITask
1054 File choosen;
1055 Exception caught;
1056 PleaseWait pw;
1058 public StatusDumpTask()
1060 choosen = null;
1061 pw = new PleaseWait("Saving status dump...");
1064 public StatusDumpTask(String name)
1066 this();
1067 choosen = new File(name);
1070 protected void runPrepare()
1072 PCControl.this.setEnabled(false);
1073 if(choosen == null) {
1074 int returnVal = snapshotFileChooser.showDialog(PCControl.this, "Save Status dump");
1075 choosen = snapshotFileChooser.getSelectedFile();
1077 if (returnVal != 0)
1078 choosen = null;
1080 pw.popUp();
1083 protected void runFinish()
1085 pw.popDown();
1086 if(caught != null) {
1087 errorDialog(caught, "Status dump failed", PCControl.this, "Dismiss");
1089 PCControl.this.setEnabled(true);
1090 if(restoreFocus)
1091 PCControl.this.requestFocus(true);
1092 restoreFocus = false;
1093 PCControl.this.vPluginManager.signalCommandCompletion();
1096 protected void runTask()
1098 if(choosen == null)
1099 return;
1101 try {
1102 OutputStream outb = new BufferedOutputStream(new FileOutputStream(choosen));
1103 PrintStream out = new PrintStream(outb, false, "UTF-8");
1104 StatusDumper sd = new StatusDumper(out);
1105 pc.dumpStatus(sd);
1106 out.flush();
1107 outb.flush();
1108 System.err.println("Informational: Dumped " + sd.dumpedObjects() + " objects");
1109 } catch(Exception e) {
1110 caught = e;
1115 private class RAMDumpTask extends AsyncGUITask
1117 File choosen;
1118 Exception caught;
1119 PleaseWait pw;
1120 boolean binary;
1122 public RAMDumpTask(boolean binFlag)
1124 choosen = null;
1125 pw = new PleaseWait("Saving RAM dump...");
1126 binary = binFlag;
1129 public RAMDumpTask(String name, boolean binFlag)
1131 this(binFlag);
1132 choosen = new File(name);
1135 protected void runPrepare()
1137 PCControl.this.setEnabled(false);
1138 if(choosen == null) {
1139 int returnVal;
1140 if(binary)
1141 returnVal = snapshotFileChooser.showDialog(PCControl.this, "Save RAM dump");
1142 else
1143 returnVal = snapshotFileChooser.showDialog(PCControl.this, "Save RAM hexdump");
1144 choosen = snapshotFileChooser.getSelectedFile();
1146 if (returnVal != 0)
1147 choosen = null;
1149 pw.popUp();
1152 protected void runFinish()
1154 pw.popDown();
1155 if(caught != null) {
1156 errorDialog(caught, "RAM dump failed", PCControl.this, "Dismiss");
1158 PCControl.this.setEnabled(true);
1159 if(restoreFocus)
1160 PCControl.this.requestFocus(true);
1161 restoreFocus = false;
1162 PCControl.this.vPluginManager.signalCommandCompletion();
1165 protected void runTask()
1167 if(choosen == null)
1168 return;
1170 try {
1171 OutputStream outb = new BufferedOutputStream(new FileOutputStream(choosen));
1172 byte[] pagebuf = new byte[4096];
1173 PhysicalAddressSpace addr = (PhysicalAddressSpace)pc.getComponent(PhysicalAddressSpace.class);
1174 int lowBound = addr.findFirstRAMPage(0);
1175 int firstUndumped = 0;
1176 int highBound = 0;
1177 int present = 0;
1178 while(lowBound >= 0) {
1179 for(; firstUndumped < lowBound; firstUndumped++)
1180 dumpPage(outb, firstUndumped, null);
1181 addr.readRAMPage(firstUndumped++, pagebuf);
1182 dumpPage(outb, lowBound, pagebuf);
1183 present++;
1184 highBound = lowBound + 1;
1185 lowBound = addr.findFirstRAMPage(++lowBound);
1187 outb.flush();
1188 System.err.println("Informational: Dumped machine RAM (" + highBound + " pages examined, " +
1189 present + " pages present).");
1190 } catch(Exception e) {
1191 caught = e;
1195 private byte charForHex(int hvalue)
1197 if(hvalue < 10)
1198 return (byte)(hvalue + 48);
1199 else if(hvalue > 9 && hvalue < 16)
1200 return (byte)(hvalue + 55);
1201 else
1202 System.err.println("Unknown hex value: " + hvalue + ".");
1203 return 90;
1206 private void dumpPage(OutputStream stream, int pageNo, byte[] buffer) throws IOException
1208 int pageBufSize;
1209 pageNo = pageNo & 0xFFFFF; //Cut page numbers out of range.
1210 if(!binary && buffer == null)
1211 return; //Don't dump null pages in non-binary mode.
1212 if(binary)
1213 pageBufSize = 4096; //Binary page buffer is 4096 bytes.
1214 else
1215 pageBufSize = 14592; //Hexdump page buffer is 14592 bytes.
1216 byte[] outputPage = new byte[pageBufSize];
1217 if(buffer != null && binary) {
1218 System.arraycopy(buffer, 0, outputPage, 0, 4096);
1219 } else if(buffer != null) { //Hex mode
1220 for(int i = 0; i < 256; i++) {
1221 for(int j = 0; j < 57; j++) {
1222 if(j < 5)
1223 outputPage[57 * i + j] = charForHex((pageNo >>> (4 * (4 - j))) & 0xF);
1224 else if(j == 5)
1225 outputPage[57 * i + j] = charForHex(i / 16);
1226 else if(j == 6)
1227 outputPage[57 * i + j] = charForHex(i % 16);
1228 else if(j == 7)
1229 outputPage[57 * i + j] = 48;
1230 else if(j == 56)
1231 outputPage[57 * i + j] = 10;
1232 else if(j % 3 == 2)
1233 outputPage[57 * i + j] = 32;
1234 else if(j % 3 == 0)
1235 outputPage[57 * i + j] = charForHex(((int)buffer[16 * i + j / 3 - 3] & 0xFF) / 16);
1236 else if(j % 3 == 1)
1237 outputPage[57 * i + j] = charForHex(buffer[16 * i + j / 3 - 3] & 0xF);
1238 else
1239 System.err.println("Error: dumpPage: unhandled j = " + j + ".");
1243 stream.write(outputPage);
1247 private class AssembleTask extends AsyncGUITask
1249 Exception caught;
1250 PleaseWait pw;
1251 boolean canceled;
1253 public AssembleTask()
1255 pw = new PleaseWait("Assembling PC...");
1256 canceled = false;
1259 protected void runPrepare()
1261 PCControl.this.setEnabled(false);
1262 try {
1263 configDialog.popUp();
1264 } catch(Exception e) {
1265 caught = e;
1269 protected void runFinish()
1271 if(caught == null && !canceled) {
1272 try {
1273 currentProject.projectID = randomHexes(24);
1274 currentProject.rerecords = 0;
1275 currentProject.events = new EventRecorder();
1276 currentProject.events.attach(pc, null);
1277 currentProject.savestateID = null;
1278 currentProject.extraHeaders = null;
1279 currentProject.events.setRerecordCount(0);
1280 currentProject.events.setHeaders(currentProject.extraHeaders);
1281 connectPC(pc);
1282 } catch(Exception e) {
1283 caught = e;
1286 if(!canceled)
1287 pw.popDown();
1288 if(caught != null) {
1289 errorDialog(caught, "PC Assembly failed", PCControl.this, "Dismiss");
1291 PCControl.this.setEnabled(true);
1292 if(restoreFocus)
1293 PCControl.this.requestFocus(true);
1294 restoreFocus = false;
1295 PCControl.this.vPluginManager.signalCommandCompletion();
1298 protected void runTask()
1300 if(caught != null)
1301 return;
1302 PC.PCHardwareInfo hw = configDialog.waitClose();
1303 if(hw == null) {
1304 canceled = true;
1305 return;
1307 try {
1308 SwingUtilities.invokeAndWait(new Thread() { public void run() { pw.popUp(); }});
1309 } catch(Exception e) {
1312 try {
1313 pc = PC.createPC(hw);
1314 } catch(Exception e) {
1315 caught = e;
1320 private class DumpControlTask extends AsyncGUITask
1322 Exception caught;
1323 boolean canceled;
1325 public DumpControlTask()
1329 protected void runPrepare()
1331 PCControl.this.setEnabled(false);
1332 try {
1333 dumpDialog.popUp(PCControl.this.pc);
1334 } catch(Exception e) {
1335 caught = e;
1339 protected void runFinish()
1341 if(caught != null) {
1342 errorDialog(caught, "Opening dump control dialog failed", PCControl.this, "Dismiss");
1344 PCControl.this.setEnabled(true);
1345 if(restoreFocus)
1346 PCControl.this.requestFocus(true);
1347 restoreFocus = false;
1350 protected void runTask()
1352 if(caught != null)
1353 return;
1354 dumpDialog.waitClose();
1358 private class AddDiskTask extends AsyncGUITask
1360 Exception caught;
1361 NewDiskDialog dd;
1363 public AddDiskTask()
1365 dd = new NewDiskDialog();
1366 PCControl.this.setEnabled(false);
1369 protected void runPrepare()
1373 protected void runFinish()
1375 if(caught != null) {
1376 errorDialog(caught, "Adding disk failed", PCControl.this, "Dismiss");
1378 PCControl.this.setEnabled(true);
1379 if(restoreFocus)
1380 PCControl.this.requestFocus(true);
1381 restoreFocus = false;
1382 try {
1383 updateDisks();
1384 } catch(Exception e) {
1385 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
1387 PCControl.this.vPluginManager.signalCommandCompletion();
1390 protected void runTask()
1392 NewDiskDialog.Response res = dd.waitClose();
1393 if(res == null) {
1394 return;
1396 try {
1397 DiskImage img;
1398 pc.getDisks().addDisk(img = new DiskImage(res.diskFile, false));
1399 img.setName(res.diskName);
1400 } catch(Exception e) {
1401 caught = e;
1406 private class ChangeAuthorsTask extends AsyncGUITask
1408 Exception caught;
1409 AuthorsDialog ad;
1411 public ChangeAuthorsTask()
1413 int authors = 0;
1414 int headers = 0;
1415 AuthorsDialog.AuthorElement[] authorNames = null;
1416 if(currentProject != null)
1417 authorNames = AuthorsDialog.readAuthorsFromHeaders(currentProject.extraHeaders);
1419 ad = new AuthorsDialog(authorNames);
1420 PCControl.this.setEnabled(false);
1423 protected void runPrepare()
1427 protected void runFinish()
1429 if(caught != null) {
1430 errorDialog(caught, "Changing authors failed", PCControl.this, "Dismiss");
1432 PCControl.this.setEnabled(true);
1433 if(restoreFocus)
1434 PCControl.this.requestFocus(true);
1435 restoreFocus = false;
1436 PCControl.this.vPluginManager.signalCommandCompletion();
1439 protected void runTask()
1441 AuthorsDialog.Response res = ad.waitClose();
1442 if(res == null) {
1443 return;
1445 try {
1446 currentProject.extraHeaders = AuthorsDialog.rewriteHeaderAuthors(currentProject.extraHeaders,
1447 res.authors);
1448 currentProject.events.setHeaders(currentProject.extraHeaders);
1449 } catch(Exception e) {
1450 caught = e;