Fix error in jpcrr.project_id()
[jpcrr.git] / org / jpc / plugins / PCControl.java
blob255b581f10f85a2dbfc3e6b850643ab9612d8d18
1 /*
2 JPC-RR: A x86 PC Hardware Emulator
3 Release 1
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2011 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.pci.peripheral.VGACard;
50 import org.jpc.emulator.StatusDumper;
51 import org.jpc.emulator.Clock;
52 import org.jpc.emulator.VGADigitalOut;
53 import org.jpc.diskimages.BlockDevice;
54 import org.jpc.diskimages.DiskImageSet;
55 import org.jpc.diskimages.DiskImage;
56 import org.jpc.plugins.RAWDumper;
57 import org.jpc.pluginsaux.PleaseWait;
58 import org.jpc.pluginsaux.AsyncGUITask;
59 import org.jpc.pluginsaux.NewDiskDialog;
60 import org.jpc.pluginsaux.AuthorsDialog;
61 import org.jpc.pluginsaux.PCConfigDialog;
62 import org.jpc.pluginsaux.MenuManager;
63 import org.jpc.pluginsaux.PCMonitorPanel;
64 import org.jpc.pluginsaux.PCMonitorPanelEmbedder;
65 import org.jpc.pluginsaux.ImportDiskImage;
66 import org.jpc.Misc;
67 import org.jpc.pluginsbase.*;
68 import org.jpc.jrsr.*;
70 import static org.jpc.Misc.randomHexes;
71 import static org.jpc.Misc.errorDialog;
72 import static org.jpc.Misc.callShowOptionDialog;
73 import static org.jpc.Misc.moveWindow;
74 import static org.jpc.Misc.parseStringToComponents;
75 import static org.jpc.Misc.nextParseLine;
76 import static org.jpc.Misc.renameFile;
78 public class PCControl implements Plugin, PCMonitorPanelEmbedder
80 private static long PROFILE_ALWAYS = 0;
81 private static long PROFILE_NO_PC = 1;
82 private static long PROFILE_HAVE_PC = 2;
83 private static long PROFILE_STOPPED = 4;
84 private static long PROFILE_RUNNING = 8;
85 private static long PROFILE_EVENTS = 16;
86 private static long PROFILE_CDROM = 32;
87 private static long PROFILE_DUMPING = 64;
88 private static long PROFILE_NOT_DUMPING = 128;
89 private static long PROFILE_HAVE_HDA = 256;
90 private static long PROFILE_HAVE_HDB = 512;
91 private static long PROFILE_HAVE_HDC = 1024;
92 private static long PROFILE_HAVE_HDD = 2048;
93 private static String SAVESTATE_LABEL = "Savestating...";
94 private static String LOADSTATE_LABEL = "Loadstating...";
95 private static String RAMDUMP_LABEL = "Dumping RAM...";
96 private static String IMAGEDUMP_LABEL = "Dumping Image...";
97 private static String STATUSDUMP_LABEL = "Dumping status...";
98 private static String ASSEMBLE_LABEL = "Assembling system...";
99 private static String ADDDISK_LABEL = "Adding new disk...";
100 private static String CHANGEAUTHORS_LABEL = "Changing run authors...";
102 private static final long serialVersionUID = 8;
103 private Plugins vPluginManager;
105 private JFrame window;
106 private JFileChooser snapshotFileChooser;
107 private JFileChooser otherFileChooser;
108 private DropTarget dropTarget;
109 private LoadstateDropTarget loadstateDropTarget;
110 private RAWDumper dumper;
112 private Set<String> disks;
114 protected PC pc;
116 private int trapFlags;
118 private volatile long profile;
119 private volatile boolean running;
120 private volatile boolean waiting;
121 private boolean uncompressedSave;
122 private static final long[] stopTime;
123 private static final String[] stopLabel;
124 private volatile long imminentTrapTime;
125 private boolean shuttingDown;
126 private int nativeWidth;
127 private int nativeHeight;
128 private PCConfigDialog configDialog;
129 private MenuManager menuManager;
130 private Map<String, List<String[]> > extraActions;
131 private PCMonitorPanel panel;
132 private JLabel statusBar;
133 private volatile int currentResolutionWidth;
134 private volatile int currentResolutionHeight;
135 private volatile Runnable taskToDo;
136 private volatile String taskLabel;
137 private boolean cycleDone;
138 private Map<String, Class<?>> debugInClass;
139 private Map<String, Boolean> debugState;
141 private PC.PCFullStatus currentProject;
143 class LoadstateDropTarget implements DropTargetListener
145 public void dragEnter(DropTargetDragEvent e) {}
146 public void dragOver(DropTargetDragEvent e) {}
147 public void dragExit(DropTargetEvent e) {}
148 public void dropActionChanged(DropTargetDragEvent e) {}
150 public void drop(DropTargetDropEvent e)
152 if(running) {
153 e.rejectDrop();
154 return;
156 e.acceptDrop(DnDConstants.ACTION_COPY);
157 int i = 0;
158 for(DataFlavor f : e.getCurrentDataFlavors()) {
159 try {
160 Transferable t = e.getTransferable();
161 Object d = t.getTransferData(f);
162 if(f.isMimeTypeEqual("text/uri-list") && d.getClass() == String.class) {
163 String url = (String)d;
164 if(url.indexOf(10) >= 0) {
165 callShowOptionDialog(window, "Hey, only single file at time!",
166 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
167 new String[]{"Dismiss"}, "Dismiss");
168 e.dropComplete(false);
169 return;
171 e.dropComplete(handleURLDropped(url));
172 return;
174 } catch(Exception ex) {
175 errorDialog(ex, "Failed to get DnD data", null, "Dismiss");
176 e.dropComplete(false);
177 return;
180 for(DataFlavor f : e.getCurrentDataFlavors()) {
181 i = 0;
182 try {
183 i++;
184 Transferable t = e.getTransferable();
185 Object d = t.getTransferData(f);
186 System.err.println("Notice: Format #" + i + ":" + d.getClass().getName() + "(" + f + ")");
187 } catch(Exception ex) {
188 System.err.println("Notice: Format #" + i + ": <ERROR>(" + f + ")");
191 callShowOptionDialog(window, "Can't recognize file to load from drop (debugging information dumped to console).",
192 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
193 new String[]{"Dismiss"}, "Dismiss");
194 e.dropComplete(false);
198 private boolean handleURLDropped(String url)
200 if(!url.startsWith("file:///")) {
201 callShowOptionDialog(window, "Can't load remote resource.",
202 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
203 new String[]{"Dismiss"}, "Dismiss");
204 return false;
206 url = url.substring(7);
207 setTask(new LoadStateTask(url, LoadStateTask.MODE_NORMAL), LOADSTATE_LABEL);
208 return true;
211 static
213 stopTime = new long[] {-1, 0, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000,
214 5000000, 10000000, 20000000, 50000000, 100000000, 200000000, 500000000, 1000000000, 2000000000,
215 5000000000L, 10000000000L, 20000000000L, 50000000000L};
216 stopLabel = new String[] {"(unbounded)", "(singlestep)", "1µs", "2µs", "5µs", "10µs", "20µs", "50µs", "100µs",
217 "200µs", "500µs","1ms", "2ms", "5ms", "10ms", "20ms", "50ms", "100ms", "200ms", "500ms", "1s", "2s", "5s",
218 "10s", "20s", "50s"};
221 public boolean systemShutdown()
223 if(!running || pc == null)
224 return true;
225 //We are running. Do the absolute minimum since we are running in very delicate context.
226 shuttingDown = true;
227 stop();
228 while(running);
229 return true;
232 public void reconnect(PC pc)
234 panel.setPC(pc);
235 pcStopping(); //Do the equivalent effects.
236 updateStatusBar();
237 updateDebug();
240 public void notifySizeChange(int w, int h)
242 final int w2 = w;
243 final int h2 = h;
245 SwingUtilities.invokeLater(new Runnable() { public void run() {
246 window.pack();
247 Dimension d = window.getSize();
248 nativeWidth = d.width;
249 nativeHeight = d.height;
250 currentResolutionWidth = w2;
251 currentResolutionHeight = h2;
252 updateStatusBarEventThread();
253 }});
256 public void notifyFrameReceived(int w, int h)
258 currentResolutionWidth = w;
259 currentResolutionHeight = h;
260 updateStatusBar();
263 private void setTrapFlags()
265 pc.getTraceTrap().setTrapFlags(trapFlags);
268 public void pcStarting()
270 profile = PROFILE_HAVE_PC | PROFILE_RUNNING | (profile & (PROFILE_DUMPING | PROFILE_NOT_DUMPING));
271 if(currentProject != null && currentProject.events != null);
272 profile |= PROFILE_EVENTS;
273 if(pc.getCDROMIndex() >= 0)
274 profile |= PROFILE_CDROM;
276 menuManager.setProfile(profile);
278 if (running)
279 return;
281 setTrapFlags();
283 Clock sysClock = (Clock)pc.getComponent(Clock.class);
284 long current = sysClock.getTime();
285 if(imminentTrapTime > 0) {
286 pc.getTraceTrap().setTrapTime(current + imminentTrapTime);
287 } else if(imminentTrapTime == 0) {
288 //Hack: We set trace trap to trap immediately. It comes too late to abort next instruction, but
289 //early enough to abort one after that.
290 pc.getTraceTrap().setTrapTime(current);
292 if(currentProject.events != null)
293 currentProject.events.setPCRunStatus(true);
296 public void pcStopping()
298 if(currentProject.events != null)
299 currentProject.events.setPCRunStatus(false);
300 if(shuttingDown)
301 return; //Don't mess with UI when shutting down.
304 profile = PROFILE_STOPPED | (profile & (PROFILE_DUMPING | PROFILE_NOT_DUMPING));
305 if(pc != null)
306 profile |= PROFILE_HAVE_PC;
307 else
308 profile |= PROFILE_NO_PC;
309 if(currentProject != null && currentProject.events != null);
310 profile |= PROFILE_EVENTS;
311 if(pc.getCDROMIndex() >= 0)
312 profile |= PROFILE_CDROM;
314 menuManager.setProfile(profile);
315 updateStatusBar();
317 try {
318 updateDisks();
319 } catch(Exception e) {
320 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
323 if(pc != null) {
324 pc.getTraceTrap().clearTrapTime();
325 pc.getTraceTrap().getAndClearTrapActive();
329 private String diskNameByIdx(int idx)
331 return pc.getDisks().lookupDisk(idx).getName();
334 private void updateDisks() throws Exception
336 for(String x : disks)
337 menuManager.removeMenuItem(x);
339 disks.clear();
341 if(pc == null)
342 return;
344 DiskImageSet imageSet = pc.getDisks();
345 DriveSet driveset = pc.getDrives();
346 int[] floppies = imageSet.diskIndicesByType(BlockDevice.Type.FLOPPY);
347 int[] cdroms = imageSet.diskIndicesByType(BlockDevice.Type.CDROM);
349 for(int i = 0; i < floppies.length; i++) {
350 String name = diskNameByIdx(floppies[i]);
351 menuManager.addMenuItem("Drives→fda→" + name, this, "menuChangeDisk", new Object[]{new Integer(0),
352 new Integer(floppies[i])}, PROFILE_HAVE_PC);
353 menuManager.addMenuItem("Drives→fdb→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
354 new Integer(floppies[i])}, PROFILE_HAVE_PC);
355 menuManager.addMenuItem("Drives→dump→" + name, this, "menuDumpDisk", new Object[]{
356 new Integer(floppies[i])}, PROFILE_HAVE_PC);
357 menuManager.addSelectableMenuItem("Drives→Write Protect→" + name, this, "menuWriteProtect",
358 new Object[]{new Integer(floppies[i])}, imageSet.lookupDisk(floppies[i]).isReadOnly(),
359 PROFILE_HAVE_PC);
360 disks.add("Drives→fda→" + name);
361 disks.add("Drives→fdb→" + name);
362 disks.add("Drives→Write Protect→" + name);
363 disks.add("Drives→dump→" + name);
365 BlockDevice dev;
366 DriveSet drives = pc.getDrives();
367 profile = profile & ~(PROFILE_HAVE_HDA | PROFILE_HAVE_HDB | PROFILE_HAVE_HDC | PROFILE_HAVE_HDD);
368 dev = drives.getHardDrive(0);
369 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDA : 0);
370 dev = drives.getHardDrive(1);
371 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDB : 0);
372 dev = drives.getHardDrive(2);
373 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDC : 0);
374 dev = drives.getHardDrive(3);
375 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDD : 0);
376 menuManager.setProfile(profile);
379 for(int i = 0; i < cdroms.length; i++) {
380 String name = diskNameByIdx(cdroms[i]);
381 menuManager.addMenuItem("Drives→CD-ROM→" + name, this, "menuChangeDisk", new Object[]{new Integer(2),
382 new Integer(cdroms[i])}, PROFILE_HAVE_PC | PROFILE_CDROM);
383 disks.add("Drives→CD-ROM→" + name);
387 private synchronized boolean setTask(Runnable task, String label)
389 boolean run = running;
390 if(run || taskToDo != null)
391 return false; //Can't do tasks with PC running or existing task.
392 taskToDo = task;
393 taskLabel = label;
394 notifyAll();
395 updateStatusBar();
396 return true;
399 public void main()
401 boolean wasRunning = false;
402 while(true) { //We will be killed by JVM.
403 //Wait for us to become runnable again.
404 while((!running || pc == null) && taskToDo == null) {
405 if(!running && wasRunning && pc != null)
406 pc.stop();
407 wasRunning = running;
408 try {
409 synchronized(this) {
410 if((running && pc != null) || taskToDo != null)
411 continue;
412 waiting = true;
413 notifyAll();
414 wait();
415 waiting = false;
417 } catch(Exception e) {
421 if(running && !wasRunning)
422 pc.start();
423 wasRunning = running;
425 if(taskToDo != null) {
426 taskToDo.run();
427 taskToDo = null;
428 updateStatusBar();
429 continue;
432 try {
433 pc.execute();
434 if(pc.getHitTraceTrap()) {
435 if(pc.getAndClearTripleFaulted())
436 callShowOptionDialog(window, "CPU shut itself down due to triple fault. Rebooting the system.",
437 "Triple fault!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
438 new String[]{"Dismiss"}, "Dismiss");
439 if(shuttingDown)
440 stopNoWait();
441 else
442 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
443 running = false;
444 doCycle(pc);
446 } catch (Exception e) {
447 running = false;
448 doCycle(pc);
449 errorDialog(e, "Hardware emulator internal error", window, "Dismiss");
450 try {
451 if(shuttingDown)
452 stopNoWait();
453 else
454 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
455 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
456 } catch (Exception f) {
463 public void connectPC(PC pc)
465 currentProject.pc = pc;
466 vPluginManager.reconnect(pc);
467 this.pc = pc;
470 private void startExternal()
472 if(pc != null && !running)
473 if(!SwingUtilities.isEventDispatchThread())
474 try {
475 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.start(); }});
476 } catch(Exception e) {
478 else
479 start();
482 private void stopExternal()
484 if(pc != null && running)
485 if(!SwingUtilities.isEventDispatchThread())
486 try {
487 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.stop(); }});
488 } catch(Exception e) {
490 else
491 stop();
494 public String projectIDMangleFileName(String name)
496 String ID = (currentProject != null && currentProject.projectID != null) ? currentProject.projectID : "";
497 return name.replaceAll("\\|", ID);
500 public boolean eci_state_save(String filename)
502 return setTask(new SaveStateTask(projectIDMangleFileName(filename), false), SAVESTATE_LABEL);
505 public boolean eci_state_dump(String filename)
507 return setTask(new StatusDumpTask(filename), STATUSDUMP_LABEL);
510 public boolean eci_movie_save(String filename)
512 return setTask(new SaveStateTask(projectIDMangleFileName(filename), true), SAVESTATE_LABEL);
515 public boolean eci_state_load(String filename)
517 return setTask(new LoadStateTask(projectIDMangleFileName(filename), LoadStateTask.MODE_NORMAL),
518 LOADSTATE_LABEL);
521 public boolean eci_state_load_noevents(String filename)
523 return setTask(new LoadStateTask(projectIDMangleFileName(filename), LoadStateTask.MODE_PRESERVE),
524 LOADSTATE_LABEL);
527 public boolean eci_movie_load(String filename)
529 return setTask(new LoadStateTask(projectIDMangleFileName(filename), LoadStateTask.MODE_MOVIEONLY),
530 LOADSTATE_LABEL);
533 public boolean eci_pc_assemble()
535 return setTask(new AssembleTask(), ASSEMBLE_LABEL);
538 public boolean eci_ram_dump_text(String filename)
540 return setTask(new RAMDumpTask(filename, false), RAMDUMP_LABEL);
543 public boolean eci_image_dump(String filename, int index)
545 return setTask(new ImageDumpTask(filename, index), IMAGEDUMP_LABEL);
548 public boolean eci_ram_dump_binary(String filename)
550 return setTask(new RAMDumpTask(filename, true), RAMDUMP_LABEL);
553 public void eci_trap_vretrace_start_on()
555 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_START;
558 public void eci_trap_vretrace_start_off()
560 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_START;
563 public void eci_trap_vretrace_end_on()
565 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_END;
568 public void eci_trap_vretrace_end_off()
570 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_END;
573 public void eci_trap_bios_kbd_on()
575 trapFlags |= TraceTrap.TRACE_STOP_BIOS_KBD;
578 public void eci_trap_bios_kbd_off()
580 trapFlags &= ~TraceTrap.TRACE_STOP_BIOS_KBD;
585 public void eci_trap_timed_disable()
587 this.imminentTrapTime = -1;
590 public void eci_trap_timed(Long time)
592 this.imminentTrapTime = time.longValue();
595 public void eci_pc_start()
597 startExternal();
600 public void eci_pc_stop()
602 stopExternal();
605 public void eci_pccontrol_setwinpos(Integer x, Integer y)
607 moveWindow(window, x.intValue(), y.intValue(), nativeWidth, nativeHeight);
610 public void eci_sendevent(String clazz, String[] rargs)
612 System.err.println("Event to: '" + clazz + "':");
613 for(int i = 0; i < rargs.length; i++) {
614 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
616 if(currentProject.events != null) {
617 try {
618 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
619 currentProject.events.addEvent(0L, x, rargs);
620 } catch(Exception e) {
621 System.err.println("Error adding event: " + e.getMessage());
626 public void eci_sendevent_lowbound(Long timeMin, String clazz, String[] rargs)
628 System.err.println("Event to: '" + clazz + "' (with low bound of " + timeMin + "):");
629 for(int i = 0; i < rargs.length; i++) {
630 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
632 if(currentProject.events != null) {
633 try {
634 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
635 currentProject.events.addEvent(timeMin, x, rargs);
636 } catch(Exception e) {
637 System.err.println("Error adding event: " + e.getMessage());
642 public void eci_memory_read(Long address, Integer size)
644 if(currentProject.pc != null) {
645 long addr = address.longValue();
646 long _size = size.intValue();
647 long ret = 0;
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 ret = (long)addrSpace.getByte((int)addr) & 0xFF;
655 else if(_size == 2)
656 ret = (long)addrSpace.getWord((int)addr) & 0xFFFF;
657 else if(_size == 4)
658 ret = (long)addrSpace.getDoubleWord((int)addr) & 0xFFFFFFFFL;
660 vPluginManager.returnValue(ret);
664 public void eci_memory_write(Long address, Long value, Integer size)
666 if(currentProject.pc != null) {
667 long addr = address.longValue();
668 long _size = size.intValue();
669 long _value = value.longValue();
670 PhysicalAddressSpace addrSpace;
671 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
672 return;
674 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
675 if(_size == 1)
676 addrSpace.setByte((int)addr, (byte)_value);
677 else if(_size == 2)
678 addrSpace.setWord((int)addr, (short)_value);
679 else if(_size == 4)
680 addrSpace.setDoubleWord((int)addr, (int)_value);
684 public PCControl(Plugins manager, String args) throws Exception
686 this(manager);
688 UTFInputLineStream file = null;
689 Map<String, String> params = parseStringToComponents(args);
690 Set<String> used = new HashSet<String>();
691 String extramenu = params.get("extramenu");
692 String uncompress = params.get("uncompressedsave");
693 if(uncompress != null)
694 uncompressedSave = true;
695 if(extramenu == null)
696 return;
697 try {
698 file = new UTFInputLineStream(new FileInputStream(extramenu));
700 while(true) {
701 boolean exists = false;
702 String[] line = nextParseLine(file);
703 if(line == null)
704 break;
705 if(line.length < 3 || line[0].charAt(0) == '→') {
706 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
707 continue;
709 if(line[0].length() == 0 || line[0].charAt(line[0].length() - 1) == '→') {
710 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
711 continue;
713 if(line[0].indexOf("→→") >= 0) {
714 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
715 continue;
717 if(used.contains(line[0]))
718 exists = true;
720 KeyStroke stroke = null;
721 if(!line[1].equals("<>")) {
722 stroke = KeyStroke.getKeyStroke(line[1]);
723 if(stroke == null) {
724 System.err.println("Warning: Bad keystroke '" + line[1] + "'.");
729 String[] lineCommand = Arrays.copyOfRange(line, 2, line.length);
730 used.add(line[0]);
731 List<String[]> commandList = extraActions.get(line[0]);
732 if(commandList == null)
733 extraActions.put(line[0], commandList = new ArrayList<String[]>());
734 commandList.add(lineCommand);
736 if(!exists)
737 menuManager.addMenuItem("Extra→" + line[0], this, "menuExtra", new String[]{line[0]}, PROFILE_ALWAYS,
738 stroke);
740 file.close();
741 } catch(IOException e) {
742 errorDialog(e, "Failed to load extra menu defintions", null, "dismiss");
743 if(file != null)
744 file.close();
746 window.setJMenuBar(menuManager.getMainBar());
749 public PCControl(Plugins manager) throws Exception
751 window = new JFrame("JPC-RR" + Misc.emuname);
753 if(DiskImage.getLibrary() == null)
754 throw new Exception("PCControl plugin requires disk library");
756 running = false;
757 shuttingDown = false;
759 debugInClass = new HashMap<String, Class<?>>();
760 debugState = new HashMap<String, Boolean>();
762 configDialog = new PCConfigDialog();
763 extraActions = new HashMap<String, List<String[]> >();
764 menuManager = new MenuManager();
766 menuManager.setProfile(profile = (PROFILE_NO_PC | PROFILE_STOPPED | PROFILE_NOT_DUMPING));
768 menuManager.addMenuItem("System→Assemble", this, "menuAssemble", null, PROFILE_STOPPED);
769 menuManager.addMenuItem("System→Start", this, "menuStart", null, PROFILE_STOPPED | PROFILE_HAVE_PC);
770 menuManager.addMenuItem("System→Stop", this, "menuStop", null, PROFILE_RUNNING);
771 menuManager.addMenuItem("System→Reset", this, "menuReset", null, PROFILE_HAVE_PC);
772 menuManager.addMenuItem("System→Start dumping", this, "menuStartDump", null, PROFILE_STOPPED | PROFILE_NOT_DUMPING);
773 menuManager.addMenuItem("System→Stop dumping", this, "menuStopDump", null, PROFILE_STOPPED | PROFILE_DUMPING);
774 menuManager.addMenuItem("System→Quit", this, "menuQuit", null, PROFILE_ALWAYS);
775 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace Start", this, "menuVRetraceStart", null, false,
776 PROFILE_ALWAYS);
777 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace End", this, "menuVRetraceEnd", null, false,
778 PROFILE_ALWAYS);
779 menuManager.addSelectableMenuItem("Breakpoints→Trap BIOS Keyboard", this, "menuBIOSKbd", null, false,
780 PROFILE_ALWAYS);
781 menuManager.addMenuItem("Snapshot→Change Run Authors", this, "menuChangeAuthors", null, PROFILE_HAVE_PC);
782 menuManager.addMenuItem("Snapshot→Save→Snapshot", this, "menuSave", new Object[]{new Boolean(false)},
783 PROFILE_HAVE_PC | PROFILE_STOPPED);
784 menuManager.addMenuItem("Snapshot→Save→Movie", this, "menuSave", new Object[]{new Boolean(true)},
785 PROFILE_HAVE_PC | PROFILE_STOPPED);
786 menuManager.addMenuItem("Snapshot→Save→Status Dump", this, "menuStatusDump", null,
787 PROFILE_HAVE_PC | PROFILE_STOPPED);
788 menuManager.addMenuItem("Snapshot→Load→Snapshot", this, "menuLoad",
789 new Object[]{new Integer(LoadStateTask.MODE_NORMAL)}, PROFILE_STOPPED);
790 menuManager.addMenuItem("Snapshot→Load→Snapshot (preserve events)", this, "menuLoad",
791 new Object[]{new Integer(LoadStateTask.MODE_PRESERVE)}, PROFILE_STOPPED | PROFILE_EVENTS);
792 menuManager.addMenuItem("Snapshot→Load→Movie", this, "menuLoad",
793 new Object[]{new Integer(LoadStateTask.MODE_MOVIEONLY)}, PROFILE_STOPPED);
794 menuManager.addMenuItem("Snapshot→RAM Dump→Hexadecimal", this, "menuRAMDump", new Object[]{new Boolean(false)},
795 PROFILE_HAVE_PC | PROFILE_STOPPED);
796 menuManager.addMenuItem("Snapshot→RAM Dump→Binary", this, "menuRAMDump", new Object[]{new Boolean(true)},
797 PROFILE_HAVE_PC | PROFILE_STOPPED);
798 menuManager.addMenuItem("Snapshot→Truncate Event Stream", this, "menuTruncate", null,
799 PROFILE_STOPPED | PROFILE_EVENTS);
801 for(int i = 0; i < stopLabel.length; i++) {
802 menuManager.addSelectableMenuItem("Breakpoints→Timed Stops→" + stopLabel[i], this, "menuTimedStop",
803 null, (i == 0), PROFILE_ALWAYS);
805 imminentTrapTime = -1;
807 menuManager.addMenuItem("Drives→fda→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(0),
808 new Integer(-1)}, PROFILE_HAVE_PC);
809 menuManager.addMenuItem("Drives→fdb→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(1),
810 new Integer(-1)}, PROFILE_HAVE_PC);
811 menuManager.addMenuItem("Drives→CD-ROM→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(2),
812 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_CDROM);
813 menuManager.addMenuItem("Drives→Add image", this, "menuAddDisk", null, PROFILE_HAVE_PC);
814 menuManager.addMenuItem("Drives→Import Image", this, "menuImport", null, PROFILE_ALWAYS);
815 menuManager.addMenuItem("Drives→dump→HDA", this, "menuDumpDisk", new Object[]{
816 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDA);
817 menuManager.addMenuItem("Drives→dump→HDB", this, "menuDumpDisk", new Object[]{
818 new Integer(-2)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDB);
819 menuManager.addMenuItem("Drives→dump→HDC", this, "menuDumpDisk", new Object[]{
820 new Integer(-3)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDC);
821 menuManager.addMenuItem("Drives→dump→HDD", this, "menuDumpDisk", new Object[]{
822 new Integer(-4)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDD);
823 menuManager.addMenuItem("Debug→Hacks→NO_FPU", this, "menuNOFPU", null, PROFILE_HAVE_PC);
824 menuManager.addMenuItem("Debug→Hacks→VGA_DRAW", this, "menuVGADRAW", null, PROFILE_HAVE_PC);
825 menuManager.addMenuItem("Debug→Hacks→VGA_SCROLL_2", this, "menuVGASCROLL2", null, PROFILE_HAVE_PC);
826 menuManager.addMenuItem("Debug→Show frame rate", this, "menuFramerate", null, PROFILE_HAVE_PC);
827 menuManager.addMenuItem("Debug→Show CRTC register", this, "menuShowCRTC", null, PROFILE_HAVE_PC);
829 disks = new HashSet<String>();
830 currentProject = new PC.PCFullStatus();
831 this.pc = null;
832 this.vPluginManager = manager;
834 panel = new PCMonitorPanel(this, manager.getOutputConnector());
835 loadstateDropTarget = new LoadstateDropTarget();
836 dropTarget = new DropTarget(panel.getMonitorPanel(), loadstateDropTarget);
838 statusBar = new JLabel("");
839 statusBar.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
840 manager.addSlaveObject(this, panel);
841 panel.startThread();
843 window.getContentPane().add("Center", panel.getMonitorPanel());
844 window.getContentPane().add("South", statusBar);
845 JMenuBar bar = menuManager.getMainBar();
846 for(JMenu menu : panel.getMenusNeeded())
847 bar.add(menu);
848 window.setJMenuBar(bar);
850 try {
851 window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
852 } catch (AccessControlException e) {
853 System.err.println("Error: Not able to add some components to frame: " + e.getMessage());
856 snapshotFileChooser = new JFileChooser(System.getProperty("user.dir"));
857 otherFileChooser = new JFileChooser(System.getProperty("user.dir"));
859 window.getContentPane().validate();
860 window.validate();
861 window.pack();
862 Dimension d = window.getSize();
863 nativeWidth = d.width;
864 nativeHeight = d.height;
865 updateStatusBarEventThread();
867 window.setVisible(true);
870 public void sendMessage(String msg)
872 vPluginManager.invokeExternalCommand("luaplugin-sendmessage", new Object[]{msg});
875 private String debugShowName(String name)
877 name = name.substring(12);
878 StringBuffer buf = new StringBuffer();
879 for(int i = 0; i < name.length(); i++)
880 if(name.charAt(i) == '_')
881 buf.append(' ');
882 else
883 buf.append(name.charAt(i));
884 return buf.toString();
887 private void addDebug(String name, Class<?> clazz)
889 if(debugInClass.get(name) != null)
890 return;
891 debugInClass.put(name, clazz);
892 debugState.put(name, false);
893 try {
894 menuManager.addSelectableMenuItem("Debug→" + debugShowName(name), this, "menuDEBUGOPTION",
895 new Object[]{name}, false, PROFILE_HAVE_PC);
896 } catch(Exception e) {
900 public void menuDEBUGOPTION(String i, Object[] args)
902 String name = (String)args[0];
903 String mName = "Debug→" + debugShowName(name);
904 debugState.put(name, !debugState.get(name));
905 setDebugOption(name);
906 menuManager.setSelected(mName, debugState.get(name));
909 private void setDebugOption(String name)
911 try {
912 debugInClass.get(name).getDeclaredMethod(name, boolean.class).invoke(pc.getComponent(
913 debugInClass.get(name)), debugState.get(name));
914 } catch(Exception e) {
915 e.printStackTrace();
919 private void setDebugOptions()
921 for(Map.Entry<String, Class<?>> opt : debugInClass.entrySet())
922 setDebugOption(opt.getKey());
925 private void updateDebug()
927 setDebugOptions();
928 for(HardwareComponent c : pc.allComponents()) {
929 Class<?> cl = c.getClass();
930 for(Method m : cl.getDeclaredMethods()) {
931 Class<?>[] p = m.getParameterTypes();
932 if(!m.getName().startsWith("DEBUGOPTION_"))
933 continue;
934 if(p.length != 1 || p[0] != boolean.class)
935 continue;
936 addDebug(m.getName(), cl);
941 public void notifyRenderer(org.jpc.pluginsaux.HUDRenderer r)
943 vPluginManager.addRenderer(r);
946 private void updateStatusBar()
948 if(vPluginManager.isShuttingDown())
949 return; //Too much of deadlock risk.
950 SwingUtilities.invokeLater(new Runnable() { public void run() { updateStatusBarEventThread(); }});
953 private void updateStatusBarEventThread()
955 String text1;
956 if(currentProject.pc != null && taskToDo == null) {
957 long timeNow = ((Clock)currentProject.pc.getComponent(Clock.class)).getTime();
958 long timeEnd = currentProject.events.getLastEventTime();
959 text1 = " Time: " + (timeNow / 1000000) + "ms, movie length: " + (timeEnd / 1000000) + "ms";
960 if(currentResolutionWidth > 0 && currentResolutionHeight > 0)
961 text1 = text1 + ", resolution: " + currentResolutionWidth + "*" + currentResolutionHeight;
962 else
963 text1 = text1 + ", resolution: <No valid signal>";
964 if(currentProject.events.isAtMovieEnd())
965 text1 = text1 + " (At movie end)";
966 } else if(taskToDo != null)
967 text1 = taskLabel;
968 else
969 text1 = " NO PC CONNECTED";
971 statusBar.setText(text1);
974 public void menuExtra(String i, Object[] args)
976 final List<String[]> commandList = extraActions.get(args[0]);
977 if(commandList == null) {
978 System.err.println("Warning: Called extra menu with unknown entry '" + args[0] + "'.");
979 return;
982 //Run the functions on seperate thread to avoid deadlocking.
983 (new Thread(new Runnable() { public void run() { menuExtraThreadFunc(commandList); }}, "Extra action thread")).start();
986 private void menuExtraThreadFunc(List<String[]> actions)
988 for(String[] i : actions) {
989 if(i.length == 1) {
990 vPluginManager.invokeExternalCommandSynchronous(i[0], null);
991 } else {
992 String[] rest = Arrays.copyOfRange(i, 1, i.length, String[].class);
993 vPluginManager.invokeExternalCommandSynchronous(i[0], rest);
998 public void menuAssemble(String i, Object[] args)
1000 setTask(new AssembleTask(), ASSEMBLE_LABEL);
1003 public void menuStart(String i, Object[] args)
1005 start();
1008 public void menuStartDump(String i, Object[] args)
1010 int returnVal = otherFileChooser.showDialog(window, "Dump to file");
1011 if(returnVal != 0)
1012 return;
1013 File choosen = otherFileChooser.getSelectedFile();
1014 try {
1015 dumper = new RAWDumper(vPluginManager, "rawoutput=" + choosen.getAbsolutePath());
1016 vPluginManager.registerPlugin(dumper);
1017 pc.refreshGameinfo(currentProject);
1018 } catch(Exception e) {
1019 errorDialog(e, "Failed to start dumping", null, "Dismiss");
1020 return;
1022 profile &= ~PROFILE_NOT_DUMPING;
1023 profile |= PROFILE_DUMPING;
1024 menuManager.setProfile(profile);
1027 public void menuStopDump(String i, Object[] args)
1029 vPluginManager.unregisterPlugin(dumper);
1030 profile &= ~PROFILE_DUMPING;
1031 profile |= PROFILE_NOT_DUMPING;
1032 menuManager.setProfile(profile);
1035 public void menuStop(String i, Object[] args)
1037 stop();
1040 public void menuReset(String i, Object[] args)
1042 reset();
1045 public void menuImport(String i, Object[] args)
1047 try {
1048 new ImportDiskImage();
1049 } catch(Exception e) {
1050 e.printStackTrace();
1054 public void menuNOFPU(String i, Object[] args)
1056 pc.setFPUHack();
1059 public void menuVGADRAW(String i, Object[] args)
1061 pc.setVGADrawHack();
1064 public void menuVGASCROLL2(String i, Object[] args)
1066 pc.setVGAScroll2Hack();
1069 public void menuFramerate(String i, Object[] args)
1071 VGACard card = (VGACard)pc.getComponent(VGACard.class);
1072 if(card == null) {
1073 callShowOptionDialog(window, "Can't get current framerate!", "Error", JOptionPane.YES_NO_OPTION,
1074 JOptionPane.WARNING_MESSAGE, null, new String[]{"Dismiss"}, "Dismiss");
1075 return;
1077 callShowOptionDialog(window, "Current framerate is " + card.getFramerate() + " fps.", "Information",
1078 JOptionPane.YES_NO_OPTION,JOptionPane.INFORMATION_MESSAGE, null, new String[]{"Dismiss"}, "Dismiss");
1081 public void menuShowCRTC(String i, Object[] args)
1083 VGACard card = (VGACard)pc.getComponent(VGACard.class);
1084 if(card == null) {
1085 callShowOptionDialog(window, "Can't get current CTRC registers!", "Error", JOptionPane.YES_NO_OPTION,
1086 JOptionPane.WARNING_MESSAGE, null, new String[]{"Dismiss"}, "Dismiss");
1087 return;
1089 callShowOptionDialog(window, card.getCTRCDump(), "Information",
1090 JOptionPane.YES_NO_OPTION,JOptionPane.INFORMATION_MESSAGE, null, new String[]{"Dismiss"}, "Dismiss");
1093 public void menuQuit(String i, Object[] args)
1095 vPluginManager.shutdownEmulator();
1098 public void menuVRetraceStart(String i, Object[] args)
1100 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_START;
1101 menuManager.setSelected("Breakpoints→Trap VRetrace Start",
1102 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_START) == TraceTrap.TRACE_STOP_VRETRACE_START);
1105 public void menuVRetraceEnd(String i, Object[] args)
1107 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_END;
1108 menuManager.setSelected("Breakpoints→Trap VRetrace End",
1109 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_END) == TraceTrap.TRACE_STOP_VRETRACE_END);
1112 public void menuBIOSKbd(String i, Object[] args)
1114 trapFlags ^= TraceTrap.TRACE_STOP_BIOS_KBD;
1115 menuManager.setSelected("Breakpoints→Trap BIOS Keyboard",
1116 (trapFlags & TraceTrap.TRACE_STOP_BIOS_KBD) == TraceTrap.TRACE_STOP_BIOS_KBD);
1119 public void menuTimedStop(String i, Object[] args)
1121 for(int j = 0; j < stopLabel.length; j++) {
1122 String label = "Breakpoints→Timed Stops→" + stopLabel[j];
1123 if(i.equals(label)) {
1124 this.imminentTrapTime = stopTime[j];
1125 menuManager.select(label);
1126 } else
1127 menuManager.unselect(label);
1131 public void menuSave(String i, Object[] args)
1133 setTask(new SaveStateTask(((Boolean)args[0]).booleanValue()), SAVESTATE_LABEL);
1136 public void menuStatusDump(String i, Object[] args)
1138 setTask(new StatusDumpTask(), STATUSDUMP_LABEL);
1141 public void menuLoad(String i, Object[] args)
1143 setTask(new LoadStateTask(((Integer)args[0]).intValue()), LOADSTATE_LABEL);
1146 public void menuRAMDump(String i, Object[] args)
1148 setTask(new RAMDumpTask(((Boolean)args[0]).booleanValue()), RAMDUMP_LABEL);
1151 public void menuDumpDisk(String i, Object[] args)
1153 setTask(new ImageDumpTask(((Integer)args[0]).intValue()), IMAGEDUMP_LABEL);
1156 public void menuTruncate(String i, Object[] args)
1158 currentProject.events.truncateEventStream();
1161 public void menuChangeDisk(String i, Object[] args)
1163 changeFloppy(((Integer)args[0]).intValue(), ((Integer)args[1]).intValue());
1166 public void menuWriteProtect(String i, Object[] args)
1168 int disk = ((Integer)args[0]).intValue();
1169 writeProtect(disk, menuManager.isSelected(i));
1170 DiskImageSet imageSet = pc.getDisks();
1171 menuManager.setSelected(i, imageSet.lookupDisk(disk).isReadOnly());
1174 public void menuAddDisk(String i, Object[] args)
1176 setTask(new AddDiskTask(), ADDDISK_LABEL);
1179 public void menuChangeAuthors(String i, Object[] args)
1181 setTask(new ChangeAuthorsTask(), CHANGEAUTHORS_LABEL);
1184 public synchronized void start()
1186 if(taskToDo != null)
1187 return;
1188 vPluginManager.pcStarted();
1189 running = true;
1190 notifyAll();
1193 private String prettyPrintTime(long ts)
1195 String s = "";
1197 if(ts >= 1000000000)
1198 s = s + "" + (ts / 1000000000) + " ";
1199 if(ts >= 100000000)
1200 s = s + "" + (ts % 1000000000 / 100000000);
1201 if(ts >= 10000000)
1202 s = s + "" + (ts % 100000000 / 10000000);
1203 if(ts >= 1000000)
1204 s = s + "" + (ts % 10000000 / 1000000) + " ";
1205 if(ts >= 100000)
1206 s = s + "" + (ts % 1000000 / 100000);
1207 if(ts >= 10000)
1208 s = s + "" + (ts % 100000 / 10000);
1209 if(ts >= 1000)
1210 s = s + "" + (ts % 10000 / 1000) + " ";
1211 if(ts >= 100)
1212 s = s + "" + (ts % 1000 / 100);
1213 if(ts >= 10)
1214 s = s + "" + (ts % 100 / 10);
1215 s = s + "" + (ts % 10);
1216 return s;
1219 protected synchronized void stopNoWait()
1221 running = false;
1222 vPluginManager.pcStopped();
1223 Clock sysClock = (Clock)pc.getComponent(Clock.class);
1224 System.err.println("Notice: PC emulation stopped (at time sequence value " +
1225 prettyPrintTime(sysClock.getTime()) + ")");
1228 public synchronized void stop()
1230 pc.getTraceTrap().doPotentialTrap(TraceTrap.TRACE_STOP_IMMEDIATE);
1231 System.err.println("Informational: Waiting for PC to halt...");
1234 public JScrollPane getMonitorPane()
1236 return null;
1239 protected void reset()
1241 pc.reboot();
1244 public synchronized boolean isRunning()
1246 return running;
1249 private void changeFloppy(int drive, int image)
1253 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
1254 changer.changeFloppyDisk(drive, image);
1255 } catch (Exception e) {
1256 System.err.println("Error: Failed to change disk");
1257 errorDialog(e, "Failed to change disk", null, "Dismiss");
1261 private void writeProtect(int image, boolean state)
1265 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
1266 changer.wpFloppyDisk(image, state);
1267 } catch (Exception e) {
1268 System.err.println("Error: Failed to change floppy write protect");
1269 errorDialog(e, "Failed to write (un)protect floppy", null, "Dismiss");
1273 static String chooseMovie(Set<String> choices)
1275 if(choices.isEmpty())
1276 return null;
1277 String[] x = new String[1];
1278 x = choices.toArray(x);
1279 int i = callShowOptionDialog(null, "Multiple initializations exist, pick one",
1280 "Multiple movies in one", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE , null,
1281 x, x[0]);
1282 return "initialization-" + x[i];
1285 static void parseSubmovies(UTFInputLineStream lines, Set<String> choices, boolean force) throws IOException
1287 String[] components = nextParseLine(lines);
1288 while(components != null) {
1289 if("SAVESTATEID".equals(components[0]) && !force) {
1290 choices.clear();
1291 return;
1293 if("INITIALSTATE".equals(components[0])) {
1294 if(components.length != 2)
1295 throw new IOException("Bad " + components[0] + " line in header segment: " +
1296 "expected 2 components, got " + components.length);
1297 choices.add(components[1]);
1299 components = nextParseLine(lines);
1303 private class LoadStateTask extends AsyncGUITask
1305 File chosen;
1306 Exception caught;
1307 int _mode;
1308 long oTime;
1309 private static final int MODE_NORMAL = 1;
1310 private static final int MODE_PRESERVE = 2;
1311 private static final int MODE_MOVIEONLY = 3;
1313 public LoadStateTask(int mode)
1315 oTime = System.currentTimeMillis();
1316 chosen = null;
1317 _mode = mode;
1320 public LoadStateTask(String name, int mode)
1322 this(mode);
1323 chosen = new File(name);
1326 protected void runPrepare()
1328 if(chosen == null) {
1329 int returnVal = 0;
1330 if(_mode == MODE_PRESERVE)
1331 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot (PE)");
1332 else if(_mode == MODE_MOVIEONLY)
1333 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot (MO)");
1334 else
1335 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot");
1336 chosen = snapshotFileChooser.getSelectedFile();
1338 if (returnVal != 0)
1339 chosen = null;
1343 protected void runFinish()
1345 if(chosen == null)
1346 return;
1348 if(caught == null) {
1349 try {
1350 connectPC(pc = currentProject.pc);
1351 doCycle(pc);
1352 System.err.println("Informational: Loadstate done on "+chosen.getAbsolutePath());
1353 } catch(Exception e) {
1354 caught = e;
1357 if(caught != null) {
1358 errorDialog(caught, "Load savestate failed", window, "Dismiss");
1360 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1361 PCControl.this.vPluginManager.signalCommandCompletion();
1364 protected void runTask()
1366 if(chosen == null)
1367 return;
1369 try {
1370 System.err.println("Informational: Loading a snapshot of JPC-RR");
1371 long times1 = System.currentTimeMillis();
1372 JRSRArchiveReader reader = new JRSRArchiveReader(chosen.getAbsolutePath());
1374 PC.PCFullStatus fullStatus;
1375 String choosenSubmovie = null;
1376 Set<String> submovies = new HashSet<String>();
1377 UTFInputLineStream lines = new UTFInputLineStream(reader.readMember("header"));
1378 parseSubmovies(lines, submovies, _mode == MODE_MOVIEONLY);
1379 if(!submovies.isEmpty())
1380 choosenSubmovie = chooseMovie(submovies);
1381 fullStatus = PC.loadSavestate(reader, _mode == MODE_PRESERVE, _mode == MODE_MOVIEONLY,
1382 currentProject, choosenSubmovie);
1384 currentProject = fullStatus;
1386 reader.close();
1387 long times2 = System.currentTimeMillis();
1388 System.err.println("Informational: Loadstate complete (" + (times2 - times1) + "ms).");
1389 } catch(Exception e) {
1390 caught = e;
1395 private synchronized void doCycleDedicatedThread(PC _pc)
1397 if(_pc == null) {
1398 cycleDone = true;
1399 return;
1401 DisplayController dc = (DisplayController)_pc.getComponent(DisplayController.class);
1402 dc.getOutputDevice().holdOutput(_pc.getTime());
1403 cycleDone = true;
1404 notifyAll();
1407 private void doCycle(PC _pc)
1409 final PC _xpc = _pc;
1410 cycleDone = false;
1411 (new Thread(new Runnable() { public void run() { doCycleDedicatedThread(_xpc); }}, "VGA output cycle thread")).start();
1412 while(cycleDone)
1413 try {
1414 synchronized(this) {
1415 if(cycleDone)
1416 break;
1417 wait();
1419 } catch(Exception e) {
1423 private class SaveStateTask extends AsyncGUITask
1425 File chosen;
1426 Exception caught;
1427 boolean movieOnly;
1428 long oTime;
1430 public SaveStateTask(boolean movie)
1432 oTime = System.currentTimeMillis();
1433 chosen = null;
1434 movieOnly = movie;
1437 public SaveStateTask(String name, boolean movie)
1439 this(movie);
1440 chosen = new File(name);
1443 protected void runPrepare()
1445 if(chosen == null) {
1446 int returnVal = snapshotFileChooser.showDialog(window, movieOnly ? "Save JPC-RR Movie" :
1447 "Save JPC-RR Snapshot");
1448 chosen = snapshotFileChooser.getSelectedFile();
1450 if (returnVal != 0)
1451 chosen = null;
1455 protected void runFinish()
1457 if(caught != null) {
1458 errorDialog(caught, "Saving savestate failed", window, "Dismiss");
1460 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1461 PCControl.this.vPluginManager.signalCommandCompletion();
1464 protected void runTask()
1466 if(chosen == null)
1467 return;
1469 JRSRArchiveWriter writer = null;
1471 try {
1472 System.err.println("Informational: Savestating...");
1473 long times1 = System.currentTimeMillis();
1474 writer = new JRSRArchiveWriter(chosen.getAbsolutePath());
1475 PC.saveSavestate(writer, currentProject, movieOnly, uncompressedSave);
1476 renameFile(chosen, new File(chosen.getAbsolutePath() + ".backup"));
1477 writer.close();
1478 long times2 = System.currentTimeMillis();
1479 System.err.println("Informational: Savestate complete (" + (times2 - times1) + "ms). on"+chosen.getAbsolutePath());
1480 } catch(Exception e) {
1481 if(writer != null)
1482 try { writer.rollback(); } catch(Exception f) {}
1483 caught = e;
1488 private class StatusDumpTask extends AsyncGUITask
1490 File chosen;
1491 Exception caught;
1493 public StatusDumpTask()
1495 chosen = null;
1498 public StatusDumpTask(String name)
1500 this();
1501 chosen = new File(name);
1504 protected void runPrepare()
1506 if(chosen == null) {
1507 int returnVal = otherFileChooser.showDialog(window, "Save Status dump");
1508 chosen = otherFileChooser.getSelectedFile();
1510 if (returnVal != 0)
1511 chosen = null;
1515 protected void runFinish()
1517 if(caught != null) {
1518 errorDialog(caught, "Status dump failed", window, "Dismiss");
1520 PCControl.this.vPluginManager.signalCommandCompletion();
1523 protected void runTask()
1525 if(chosen == null)
1526 return;
1528 try {
1529 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1530 PrintStream out = new PrintStream(outb, false, "UTF-8");
1531 StatusDumper sd = new StatusDumper(out);
1532 pc.dumpStatus(sd);
1533 out.flush();
1534 outb.flush();
1535 System.err.println("Informational: Dumped " + sd.dumpedObjects() + " objects");
1536 } catch(Exception e) {
1537 caught = e;
1542 private class RAMDumpTask extends AsyncGUITask
1544 File chosen;
1545 Exception caught;
1546 boolean binary;
1548 public RAMDumpTask(boolean binFlag)
1550 chosen = null;
1551 binary = binFlag;
1554 public RAMDumpTask(String name, boolean binFlag)
1556 this(binFlag);
1557 chosen = new File(name);
1560 protected void runPrepare()
1562 if(chosen == null) {
1563 int returnVal;
1564 if(binary)
1565 returnVal = otherFileChooser.showDialog(window, "Save RAM dump");
1566 else
1567 returnVal = otherFileChooser.showDialog(window, "Save RAM hexdump");
1568 chosen = otherFileChooser.getSelectedFile();
1570 if (returnVal != 0)
1571 chosen = null;
1575 protected void runFinish()
1577 if(caught != null) {
1578 errorDialog(caught, "RAM dump failed", window, "Dismiss");
1580 PCControl.this.vPluginManager.signalCommandCompletion();
1583 protected void runTask()
1585 if(chosen == null)
1586 return;
1588 try {
1589 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1590 byte[] pagebuf = new byte[4096];
1591 PhysicalAddressSpace addr = (PhysicalAddressSpace)pc.getComponent(PhysicalAddressSpace.class);
1592 int lowBound = addr.findFirstRAMPage(0);
1593 int firstUndumped = 0;
1594 int highBound = 0;
1595 int present = 0;
1596 while(lowBound >= 0) {
1597 for(; firstUndumped < lowBound; firstUndumped++)
1598 dumpPage(outb, firstUndumped, null);
1599 addr.readRAMPage(firstUndumped++, pagebuf);
1600 dumpPage(outb, lowBound, pagebuf);
1601 present++;
1602 highBound = lowBound + 1;
1603 lowBound = addr.findFirstRAMPage(++lowBound);
1605 outb.flush();
1606 System.err.println("Informational: Dumped machine RAM (" + highBound + " pages examined, " +
1607 present + " pages present).");
1608 } catch(Exception e) {
1609 caught = e;
1613 private byte charForHex(int hvalue)
1615 if(hvalue < 10)
1616 return (byte)(hvalue + 48);
1617 else if(hvalue > 9 && hvalue < 16)
1618 return (byte)(hvalue + 55);
1619 else
1620 System.err.println("Unknown hex value: " + hvalue + ".");
1621 return 90;
1624 private void dumpPage(OutputStream stream, int pageNo, byte[] buffer) throws IOException
1626 int pageBufSize;
1627 pageNo = pageNo & 0xFFFFF; //Cut page numbers out of range.
1628 if(!binary && buffer == null)
1629 return; //Don't dump null pages in non-binary mode.
1630 if(binary)
1631 pageBufSize = 4096; //Binary page buffer is 4096 bytes.
1632 else
1633 pageBufSize = 14592; //Hexdump page buffer is 14592 bytes.
1634 byte[] outputPage = new byte[pageBufSize];
1635 if(buffer != null && binary) {
1636 System.arraycopy(buffer, 0, outputPage, 0, 4096);
1637 } else if(buffer != null) { //Hex mode
1638 for(int i = 0; i < 256; i++) {
1639 for(int j = 0; j < 57; j++) {
1640 if(j < 5)
1641 outputPage[57 * i + j] = charForHex((pageNo >>> (4 * (4 - j))) & 0xF);
1642 else if(j == 5)
1643 outputPage[57 * i + j] = charForHex(i / 16);
1644 else if(j == 6)
1645 outputPage[57 * i + j] = charForHex(i % 16);
1646 else if(j == 7)
1647 outputPage[57 * i + j] = 48;
1648 else if(j == 56)
1649 outputPage[57 * i + j] = 10;
1650 else if(j % 3 == 2)
1651 outputPage[57 * i + j] = 32;
1652 else if(j % 3 == 0)
1653 outputPage[57 * i + j] = charForHex(((int)buffer[16 * i + j / 3 - 3] & 0xFF) / 16);
1654 else if(j % 3 == 1)
1655 outputPage[57 * i + j] = charForHex(buffer[16 * i + j / 3 - 3] & 0xF);
1656 else
1657 System.err.println("Error: dumpPage: unhandled j = " + j + ".");
1661 stream.write(outputPage);
1665 private class ImageDumpTask extends AsyncGUITask
1667 File chosen;
1668 Exception caught;
1669 int index;
1671 public ImageDumpTask(int _index)
1673 chosen = null;
1674 index = _index;
1677 public ImageDumpTask(String name, int index)
1679 this(index);
1680 chosen = new File(name);
1683 protected void runPrepare()
1685 if(chosen == null) {
1686 int returnVal;
1687 returnVal = otherFileChooser.showDialog(window, "Save Image dump");
1688 chosen = otherFileChooser.getSelectedFile();
1690 if (returnVal != 0)
1691 chosen = null;
1695 protected void runFinish()
1697 if(caught != null) {
1698 errorDialog(caught, "Image dump failed", window, "Dismiss");
1700 PCControl.this.vPluginManager.signalCommandCompletion();
1703 protected void runTask()
1705 if(chosen == null)
1706 return;
1708 try {
1709 DiskImage dev;
1710 if(index < 0)
1711 dev = pc.getDrives().getHardDrive(-1 - index).getImage();
1712 else
1713 dev = pc.getDisks().lookupDisk(index);
1714 if(dev == null)
1715 throw new IOException("Trying to dump nonexistent disk");
1716 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1717 byte[] buf = new byte[512];
1718 long sectors = dev.getTotalSectors();
1719 for(long i = 0; i < sectors; i++) {
1720 dev.read(i, buf, 1);
1721 outb.write(buf);
1723 outb.close();
1724 System.err.println("Informational: Dumped disk image (" + sectors + " sectors).");
1725 } catch(Exception e) {
1726 caught = e;
1731 private class AssembleTask extends AsyncGUITask
1733 Exception caught;
1734 boolean canceled;
1736 public AssembleTask()
1738 canceled = false;
1741 protected void runPrepare()
1743 try {
1744 configDialog.popUp();
1745 } catch(Exception e) {
1746 caught = e;
1750 protected void runFinish()
1752 if(caught == null && !canceled) {
1753 try {
1754 currentProject.projectID = randomHexes(24);
1755 currentProject.rerecords = 0;
1756 currentProject.events = new EventRecorder();
1757 currentProject.events.attach(pc, null);
1758 currentProject.savestateID = null;
1759 currentProject.extraHeaders = null;
1760 currentProject.events.setRerecordCount(0);
1761 currentProject.events.setHeaders(currentProject.extraHeaders);
1762 currentProject.events.setProjectID(currentProject.projectID);
1763 connectPC(pc);
1764 } catch(Exception e) {
1765 caught = e;
1768 if(caught != null) {
1769 errorDialog(caught, "PC Assembly failed", window, "Dismiss");
1771 PCControl.this.vPluginManager.signalCommandCompletion();
1774 protected void runTask()
1776 if(caught != null)
1777 return;
1778 PC.PCHardwareInfo hw = configDialog.waitClose();
1779 if(hw == null) {
1780 canceled = true;
1781 return;
1784 try {
1785 pc = PC.createPC(hw);
1786 } catch(Exception e) {
1787 caught = e;
1792 private class AddDiskTask extends AsyncGUITask
1794 Exception caught;
1795 NewDiskDialog dd;
1797 public AddDiskTask()
1799 dd = new NewDiskDialog();
1802 protected void runPrepare()
1806 protected void runFinish()
1808 if(caught != null) {
1809 errorDialog(caught, "Adding disk failed", window, "Dismiss");
1811 try {
1812 updateDisks();
1813 } catch(Exception e) {
1814 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
1816 PCControl.this.vPluginManager.signalCommandCompletion();
1819 protected void runTask()
1821 NewDiskDialog.Response res = dd.waitClose();
1822 if(res == null) {
1823 return;
1825 try {
1826 DiskImage img;
1827 pc.getDisks().addDisk(img = new DiskImage(res.diskFile, false));
1828 img.setName(res.diskName);
1829 } catch(Exception e) {
1830 caught = e;
1835 private class ChangeAuthorsTask extends AsyncGUITask
1837 Exception caught;
1838 AuthorsDialog ad;
1840 public ChangeAuthorsTask()
1842 int authors = 0;
1843 int headers = 0;
1844 AuthorsDialog.AuthorElement[] authorNames = null;
1845 String gameName = "";
1846 if(currentProject != null)
1847 authorNames = AuthorsDialog.readAuthorsFromHeaders(currentProject.extraHeaders);
1848 if(currentProject != null)
1849 gameName = AuthorsDialog.readGameNameFromHeaders(currentProject.extraHeaders);
1850 ad = new AuthorsDialog(authorNames, gameName);
1853 protected void runPrepare()
1857 protected void runFinish()
1859 if(caught != null) {
1860 errorDialog(caught, "Changing authors failed", window, "Dismiss");
1862 PCControl.this.vPluginManager.signalCommandCompletion();
1865 protected void runTask()
1867 AuthorsDialog.Response res = ad.waitClose();
1868 if(res == null) {
1869 return;
1871 try {
1872 currentProject.extraHeaders = AuthorsDialog.rewriteHeaderAuthors(currentProject.extraHeaders,
1873 res.authors, res.gameName);
1874 currentProject.events.setHeaders(currentProject.extraHeaders);
1875 } catch(Exception e) {
1876 caught = e;