Add more status to status display
[jpcrr.git] / org / jpc / plugins / PCControl.java
blob9cb88da0bd79da606ea3a6bbbfbcb236557ff2fa
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.math.BigInteger;
36 import java.lang.reflect.*;
37 import java.security.AccessControlException;
38 import java.awt.GridLayout;
39 import javax.swing.*;
40 import java.awt.dnd.*;
41 import java.awt.datatransfer.*;
42 import javax.swing.border.EtchedBorder;
44 import org.jpc.emulator.HardwareComponent;
45 import org.jpc.emulator.PC;
46 import org.jpc.emulator.EventRecorder;
47 import org.jpc.emulator.TraceTrap;
48 import org.jpc.emulator.DriveSet;
49 import org.jpc.emulator.DisplayController;
50 import org.jpc.emulator.memory.PhysicalAddressSpace;
51 import org.jpc.emulator.pci.peripheral.VGACard;
52 import org.jpc.emulator.StatusDumper;
53 import org.jpc.emulator.Clock;
54 import org.jpc.emulator.VGADigitalOut;
55 import org.jpc.diskimages.BlockDevice;
56 import org.jpc.diskimages.DiskImageSet;
57 import org.jpc.diskimages.DiskImage;
58 import org.jpc.plugins.RAWDumper;
59 import org.jpc.pluginsaux.PleaseWait;
60 import org.jpc.pluginsaux.AsyncGUITask;
61 import org.jpc.pluginsaux.NewDiskDialog;
62 import org.jpc.pluginsaux.AuthorsDialog;
63 import org.jpc.pluginsaux.PCConfigDialog;
64 import org.jpc.pluginsaux.MenuManager;
65 import org.jpc.pluginsaux.PCMonitorPanel;
66 import org.jpc.pluginsaux.PCMonitorPanelEmbedder;
67 import org.jpc.pluginsaux.ImportDiskImage;
68 import org.jpc.Misc;
69 import org.jpc.pluginsbase.*;
70 import org.jpc.jrsr.*;
72 import static org.jpc.Misc.randomHexes;
73 import static org.jpc.Misc.errorDialog;
74 import static org.jpc.Misc.callShowOptionDialog;
75 import static org.jpc.Misc.moveWindow;
76 import static org.jpc.Misc.parseStringToComponents;
77 import static org.jpc.Misc.nextParseLine;
78 import static org.jpc.Misc.renameFile;
80 public class PCControl implements Plugin, PCMonitorPanelEmbedder
82 private static long PROFILE_ALWAYS = 0;
83 private static long PROFILE_NO_PC = 1;
84 private static long PROFILE_HAVE_PC = 2;
85 private static long PROFILE_STOPPED = 4;
86 private static long PROFILE_RUNNING = 8;
87 private static long PROFILE_EVENTS = 16;
88 private static long PROFILE_CDROM = 32;
89 private static long PROFILE_DUMPING = 64;
90 private static long PROFILE_NOT_DUMPING = 128;
91 private static long PROFILE_HAVE_HDA = 256;
92 private static long PROFILE_HAVE_HDB = 512;
93 private static long PROFILE_HAVE_HDC = 1024;
94 private static long PROFILE_HAVE_HDD = 2048;
95 private static String SAVESTATE_LABEL = "Savestating...";
96 private static String LOADSTATE_LABEL = "Loadstating...";
97 private static String RAMDUMP_LABEL = "Dumping RAM...";
98 private static String IMAGEDUMP_LABEL = "Dumping Image...";
99 private static String STATUSDUMP_LABEL = "Dumping status...";
100 private static String ASSEMBLE_LABEL = "Assembling system...";
101 private static String ADDDISK_LABEL = "Adding new disk...";
102 private static String CHANGEAUTHORS_LABEL = "Changing run authors...";
104 private static final long serialVersionUID = 8;
105 private Plugins vPluginManager;
107 private JFrame window;
108 private JFileChooser snapshotFileChooser;
109 private JFileChooser otherFileChooser;
110 private DropTarget dropTarget;
111 private LoadstateDropTarget loadstateDropTarget;
112 private RAWDumper dumper;
114 private Set<String> disks;
116 protected PC pc;
118 private int trapFlags;
120 private volatile long profile;
121 private volatile boolean running;
122 private volatile boolean waiting;
123 private boolean uncompressedSave;
124 private static final long[] stopTime;
125 private static final String[] stopLabel;
126 private volatile long imminentTrapTime;
127 private boolean shuttingDown;
128 private int nativeWidth;
129 private int nativeHeight;
130 private PCConfigDialog configDialog;
131 private MenuManager menuManager;
132 private Map<String, List<String[]> > extraActions;
133 private PCMonitorPanel panel;
134 private JLabel statusBar;
135 private volatile int currentResolutionWidth;
136 private volatile int currentResolutionHeight;
137 private volatile Runnable taskToDo;
138 private volatile String taskLabel;
139 private boolean cycleDone;
140 private Map<String, Class<?>> debugInClass;
141 private Map<String, Boolean> debugState;
143 private JFrame statusWindow;
144 private JPanel statusPanel;
145 private Map<String, JLabel> statusLabels;
146 private Map<String, JLabel> statusValues;
147 private Map<String, String> statusLastValue;
150 private PC.PCFullStatus currentProject;
152 class LoadstateDropTarget implements DropTargetListener
154 public void dragEnter(DropTargetDragEvent e) {}
155 public void dragOver(DropTargetDragEvent e) {}
156 public void dragExit(DropTargetEvent e) {}
157 public void dropActionChanged(DropTargetDragEvent e) {}
159 public void drop(DropTargetDropEvent e)
161 if(running) {
162 e.rejectDrop();
163 return;
165 e.acceptDrop(DnDConstants.ACTION_COPY);
166 int i = 0;
167 for(DataFlavor f : e.getCurrentDataFlavors()) {
168 try {
169 Transferable t = e.getTransferable();
170 Object d = t.getTransferData(f);
171 if(f.isMimeTypeEqual("text/uri-list") && d.getClass() == String.class) {
172 String url = (String)d;
173 if(url.indexOf(10) >= 0) {
174 callShowOptionDialog(window, "Hey, only single file at time!",
175 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
176 new String[]{"Dismiss"}, "Dismiss");
177 e.dropComplete(false);
178 return;
180 e.dropComplete(handleURLDropped(url));
181 return;
183 } catch(Exception ex) {
184 errorDialog(ex, "Failed to get DnD data", null, "Dismiss");
185 e.dropComplete(false);
186 return;
189 for(DataFlavor f : e.getCurrentDataFlavors()) {
190 i = 0;
191 try {
192 i++;
193 Transferable t = e.getTransferable();
194 Object d = t.getTransferData(f);
195 System.err.println("Notice: Format #" + i + ":" + d.getClass().getName() + "(" + f + ")");
196 } catch(Exception ex) {
197 System.err.println("Notice: Format #" + i + ": <ERROR>(" + f + ")");
200 callShowOptionDialog(window, "Can't recognize file to load from drop (debugging information dumped to console).",
201 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
202 new String[]{"Dismiss"}, "Dismiss");
203 e.dropComplete(false);
207 private boolean handleURLDropped(String url)
209 if(!url.startsWith("file:///")) {
210 callShowOptionDialog(window, "Can't load remote resource.",
211 "DnD error", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
212 new String[]{"Dismiss"}, "Dismiss");
213 return false;
215 url = url.substring(7);
216 setTask(new LoadStateTask(url, LoadStateTask.MODE_NORMAL), LOADSTATE_LABEL);
217 return true;
220 static
222 stopTime = new long[] {-1, 0, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000,
223 5000000, 10000000, 20000000, 50000000, 100000000, 200000000, 500000000, 1000000000, 2000000000,
224 5000000000L, 10000000000L, 20000000000L, 50000000000L};
225 stopLabel = new String[] {"(unbounded)", "(singlestep)", "1µs", "2µs", "5µs", "10µs", "20µs", "50µs", "100µs",
226 "200µs", "500µs","1ms", "2ms", "5ms", "10ms", "20ms", "50ms", "100ms", "200ms", "500ms", "1s", "2s", "5s",
227 "10s", "20s", "50s"};
230 public boolean systemShutdown()
232 if(!running || pc == null)
233 return true;
234 //We are running. Do the absolute minimum since we are running in very delicate context.
235 shuttingDown = true;
236 stop();
237 while(running);
238 return true;
241 public void reconnect(PC pc)
243 panel.setPC(pc);
244 pcStopping(); //Do the equivalent effects.
245 updateStatusBar();
246 updateDebug();
249 public void notifySizeChange(int w, int h)
251 final int w2 = w;
252 final int h2 = h;
254 SwingUtilities.invokeLater(new Runnable() { public void run() {
255 window.pack();
256 Dimension d = window.getSize();
257 nativeWidth = d.width;
258 nativeHeight = d.height;
259 currentResolutionWidth = w2;
260 currentResolutionHeight = h2;
261 updateStatusBarEventThread();
262 }});
265 public void notifyFrameReceived(int w, int h)
267 currentResolutionWidth = w;
268 currentResolutionHeight = h;
269 updateStatusBar();
272 private void setTrapFlags()
274 pc.getTraceTrap().setTrapFlags(trapFlags);
277 public void pcStarting()
279 profile = PROFILE_HAVE_PC | PROFILE_RUNNING | (profile & (PROFILE_DUMPING | PROFILE_NOT_DUMPING));
280 if(currentProject != null && currentProject.events != null);
281 profile |= PROFILE_EVENTS;
282 if(pc.getCDROMIndex() >= 0)
283 profile |= PROFILE_CDROM;
285 menuManager.setProfile(profile);
287 if (running)
288 return;
290 setTrapFlags();
292 Clock sysClock = (Clock)pc.getComponent(Clock.class);
293 long current = sysClock.getTime();
294 if(imminentTrapTime > 0) {
295 pc.getTraceTrap().setTrapTime(current + imminentTrapTime);
296 } else if(imminentTrapTime == 0) {
297 //Hack: We set trace trap to trap immediately. It comes too late to abort next instruction, but
298 //early enough to abort one after that.
299 pc.getTraceTrap().setTrapTime(current);
301 if(currentProject.events != null)
302 currentProject.events.setPCRunStatus(true);
305 public void pcStopping()
307 if(currentProject.events != null)
308 currentProject.events.setPCRunStatus(false);
309 if(shuttingDown)
310 return; //Don't mess with UI when shutting down.
313 profile = PROFILE_STOPPED | (profile & (PROFILE_DUMPING | PROFILE_NOT_DUMPING));
314 if(pc != null)
315 profile |= PROFILE_HAVE_PC;
316 else
317 profile |= PROFILE_NO_PC;
318 if(currentProject != null && currentProject.events != null);
319 profile |= PROFILE_EVENTS;
320 if(pc.getCDROMIndex() >= 0)
321 profile |= PROFILE_CDROM;
323 menuManager.setProfile(profile);
324 updateStatusBar();
326 try {
327 updateDisks();
328 } catch(Exception e) {
329 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
332 if(pc != null) {
333 pc.getTraceTrap().clearTrapTime();
334 pc.getTraceTrap().getAndClearTrapActive();
338 private String diskNameByIdx(int idx)
340 return pc.getDisks().lookupDisk(idx).getName();
343 private void updateDisks() throws Exception
345 for(String x : disks)
346 menuManager.removeMenuItem(x);
348 disks.clear();
350 if(pc == null)
351 return;
353 DiskImageSet imageSet = pc.getDisks();
354 DriveSet driveset = pc.getDrives();
355 int[] floppies = imageSet.diskIndicesByType(BlockDevice.Type.FLOPPY);
356 int[] cdroms = imageSet.diskIndicesByType(BlockDevice.Type.CDROM);
358 for(int i = 0; i < floppies.length; i++) {
359 String name = diskNameByIdx(floppies[i]);
360 menuManager.addMenuItem("Drives→fda→" + name, this, "menuChangeDisk", new Object[]{new Integer(0),
361 new Integer(floppies[i])}, PROFILE_HAVE_PC);
362 menuManager.addMenuItem("Drives→fdb→" + name, this, "menuChangeDisk", new Object[]{new Integer(1),
363 new Integer(floppies[i])}, PROFILE_HAVE_PC);
364 menuManager.addMenuItem("Drives→dump→" + name, this, "menuDumpDisk", new Object[]{
365 new Integer(floppies[i])}, PROFILE_HAVE_PC);
366 menuManager.addSelectableMenuItem("Drives→Write Protect→" + name, this, "menuWriteProtect",
367 new Object[]{new Integer(floppies[i])}, imageSet.lookupDisk(floppies[i]).isReadOnly(),
368 PROFILE_HAVE_PC);
369 disks.add("Drives→fda→" + name);
370 disks.add("Drives→fdb→" + name);
371 disks.add("Drives→Write Protect→" + name);
372 disks.add("Drives→dump→" + name);
374 BlockDevice dev;
375 DriveSet drives = pc.getDrives();
376 profile = profile & ~(PROFILE_HAVE_HDA | PROFILE_HAVE_HDB | PROFILE_HAVE_HDC | PROFILE_HAVE_HDD);
377 dev = drives.getHardDrive(0);
378 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDA : 0);
379 dev = drives.getHardDrive(1);
380 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDB : 0);
381 dev = drives.getHardDrive(2);
382 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDC : 0);
383 dev = drives.getHardDrive(3);
384 profile = profile | ((dev != null && dev.getType() == BlockDevice.Type.HARDDRIVE) ? PROFILE_HAVE_HDD : 0);
385 menuManager.setProfile(profile);
388 for(int i = 0; i < cdroms.length; i++) {
389 String name = diskNameByIdx(cdroms[i]);
390 menuManager.addMenuItem("Drives→CD-ROM→" + name, this, "menuChangeDisk", new Object[]{new Integer(2),
391 new Integer(cdroms[i])}, PROFILE_HAVE_PC | PROFILE_CDROM);
392 disks.add("Drives→CD-ROM→" + name);
396 private synchronized boolean setTask(Runnable task, String label)
398 boolean run = running;
399 if(run || taskToDo != null)
400 return false; //Can't do tasks with PC running or existing task.
401 taskToDo = task;
402 taskLabel = label;
403 notifyAll();
404 updateStatusBar();
405 return true;
408 public void main()
410 boolean wasRunning = false;
411 while(true) { //We will be killed by JVM.
412 //Wait for us to become runnable again.
413 while((!running || pc == null) && taskToDo == null) {
414 if(!running && wasRunning && pc != null)
415 pc.stop();
416 wasRunning = running;
417 try {
418 synchronized(this) {
419 if((running && pc != null) || taskToDo != null)
420 continue;
421 waiting = true;
422 notifyAll();
423 wait();
424 waiting = false;
426 } catch(Exception e) {
430 if(running && !wasRunning)
431 pc.start();
432 wasRunning = running;
434 if(taskToDo != null) {
435 taskToDo.run();
436 taskToDo = null;
437 updateStatusBar();
438 continue;
441 try {
442 pc.execute();
443 if(pc.getHitTraceTrap()) {
444 if(pc.getAndClearTripleFaulted())
445 callShowOptionDialog(window, "CPU shut itself down due to triple fault. Rebooting the system.",
446 "Triple fault!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
447 new String[]{"Dismiss"}, "Dismiss");
448 if(shuttingDown)
449 stopNoWait();
450 else
451 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
452 running = false;
453 doCycle(pc);
455 } catch (Exception e) {
456 running = false;
457 doCycle(pc);
458 errorDialog(e, "Hardware emulator internal error", window, "Dismiss");
459 try {
460 if(shuttingDown)
461 stopNoWait();
462 else
463 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
464 SwingUtilities.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
465 } catch (Exception f) {
472 public void connectPC(PC pc)
474 currentProject.pc = pc;
475 vPluginManager.reconnect(pc);
476 this.pc = pc;
479 private void startExternal()
481 if(pc != null && !running)
482 if(!SwingUtilities.isEventDispatchThread())
483 try {
484 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.start(); }});
485 } catch(Exception e) {
487 else
488 start();
491 private void stopExternal()
493 if(pc != null && running)
494 if(!SwingUtilities.isEventDispatchThread())
495 try {
496 SwingUtilities.invokeAndWait(new Thread() { public void run() { PCControl.this.stop(); }});
497 } catch(Exception e) {
499 else
500 stop();
503 public String projectIDMangleFileName(String name)
505 String ID = (currentProject != null && currentProject.projectID != null) ? currentProject.projectID : "";
506 return name.replaceAll("\\|", ID);
509 public boolean eci_state_save(String filename)
511 return setTask(new SaveStateTask(projectIDMangleFileName(filename), false), SAVESTATE_LABEL);
514 public boolean eci_state_dump(String filename)
516 return setTask(new StatusDumpTask(filename), STATUSDUMP_LABEL);
519 public boolean eci_movie_save(String filename)
521 return setTask(new SaveStateTask(projectIDMangleFileName(filename), true), SAVESTATE_LABEL);
524 public boolean eci_state_load(String filename)
526 return setTask(new LoadStateTask(projectIDMangleFileName(filename), LoadStateTask.MODE_NORMAL),
527 LOADSTATE_LABEL);
530 public boolean eci_state_load_noevents(String filename)
532 return setTask(new LoadStateTask(projectIDMangleFileName(filename), LoadStateTask.MODE_PRESERVE),
533 LOADSTATE_LABEL);
536 public boolean eci_movie_load(String filename)
538 return setTask(new LoadStateTask(projectIDMangleFileName(filename), LoadStateTask.MODE_MOVIEONLY),
539 LOADSTATE_LABEL);
542 public boolean eci_pc_assemble()
544 return setTask(new AssembleTask(), ASSEMBLE_LABEL);
547 public boolean eci_ram_dump_text(String filename)
549 return setTask(new RAMDumpTask(filename, false), RAMDUMP_LABEL);
552 public boolean eci_image_dump(String filename, int index)
554 return setTask(new ImageDumpTask(filename, index), IMAGEDUMP_LABEL);
557 public boolean eci_ram_dump_binary(String filename)
559 return setTask(new RAMDumpTask(filename, true), RAMDUMP_LABEL);
562 public void eci_trap_vretrace_start_on()
564 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_START;
567 public void eci_trap_vretrace_start_off()
569 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_START;
572 public void eci_trap_vretrace_end_on()
574 trapFlags |= TraceTrap.TRACE_STOP_VRETRACE_END;
577 public void eci_trap_vretrace_end_off()
579 trapFlags &= ~TraceTrap.TRACE_STOP_VRETRACE_END;
582 public void eci_trap_bios_kbd_on()
584 trapFlags |= TraceTrap.TRACE_STOP_BIOS_KBD;
587 public void eci_trap_bios_kbd_off()
589 trapFlags &= ~TraceTrap.TRACE_STOP_BIOS_KBD;
594 public void eci_trap_timed_disable()
596 this.imminentTrapTime = -1;
599 public void eci_trap_timed(Long time)
601 this.imminentTrapTime = time.longValue();
604 public void eci_pc_start()
606 startExternal();
609 public void eci_pc_stop()
611 stopExternal();
614 public void eci_pccontrol_setwinpos(Integer x, Integer y)
616 moveWindow(window, x.intValue(), y.intValue(), nativeWidth, nativeHeight);
619 public void eci_sendevent(String clazz, String[] rargs)
621 System.err.println("Event to: '" + clazz + "':");
622 for(int i = 0; i < rargs.length; i++) {
623 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
625 if(currentProject.events != null) {
626 try {
627 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
628 currentProject.events.addEvent(0L, x, rargs);
629 } catch(Exception e) {
630 System.err.println("Error adding event: " + e.getMessage());
635 public void eci_sendevent_lowbound(Long timeMin, String clazz, String[] rargs)
637 System.err.println("Event to: '" + clazz + "' (with low bound of " + timeMin + "):");
638 for(int i = 0; i < rargs.length; i++) {
639 System.err.println("rargs[" + i + "]: '" + rargs[i] + "'.");
641 if(currentProject.events != null) {
642 try {
643 Class <? extends HardwareComponent> x = Class.forName(clazz).asSubclass(HardwareComponent.class);
644 currentProject.events.addEvent(timeMin, x, rargs);
645 } catch(Exception e) {
646 System.err.println("Error adding event: " + e.getMessage());
651 public void eci_memory_read(Long address, Integer size)
653 if(currentProject.pc != null) {
654 long addr = address.longValue();
655 long _size = size.intValue();
656 long ret = 0;
657 PhysicalAddressSpace addrSpace;
658 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
659 return;
661 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
662 if(_size == 1)
663 ret = (long)addrSpace.getByte((int)addr) & 0xFF;
664 else if(_size == 2)
665 ret = (long)addrSpace.getWord((int)addr) & 0xFFFF;
666 else if(_size == 4)
667 ret = (long)addrSpace.getDoubleWord((int)addr) & 0xFFFFFFFFL;
669 vPluginManager.returnValue(ret);
673 public void eci_memory_write(Long address, Long value, Integer size)
675 if(currentProject.pc != null) {
676 long addr = address.longValue();
677 long _size = size.intValue();
678 long _value = value.longValue();
679 PhysicalAddressSpace addrSpace;
680 if(addr < 0 || addr > 0xFFFFFFFFL || (_size != 1 && _size != 2 && _size != 4))
681 return;
683 addrSpace = (PhysicalAddressSpace)currentProject.pc.getComponent(PhysicalAddressSpace.class);
684 if(_size == 1)
685 addrSpace.setByte((int)addr, (byte)_value);
686 else if(_size == 2)
687 addrSpace.setWord((int)addr, (short)_value);
688 else if(_size == 4)
689 addrSpace.setDoubleWord((int)addr, (int)_value);
693 public PCControl(Plugins manager, String args) throws Exception
695 this(manager);
697 UTFInputLineStream file = null;
698 Map<String, String> params = parseStringToComponents(args);
699 Set<String> used = new HashSet<String>();
700 String extramenu = params.get("extramenu");
701 String uncompress = params.get("uncompressedsave");
702 if(uncompress != null)
703 uncompressedSave = true;
704 if(extramenu == null)
705 return;
706 try {
707 file = new UTFInputLineStream(new FileInputStream(extramenu));
709 while(true) {
710 boolean exists = false;
711 String[] line = nextParseLine(file);
712 if(line == null)
713 break;
714 if(line.length < 3 || line[0].charAt(0) == '→') {
715 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
716 continue;
718 if(line[0].length() == 0 || line[0].charAt(line[0].length() - 1) == '→') {
719 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
720 continue;
722 if(line[0].indexOf("→→") >= 0) {
723 System.err.println("Warning: Bad extra menu item '" + line[0] + "'.");
724 continue;
726 if(used.contains(line[0]))
727 exists = true;
729 KeyStroke stroke = null;
730 if(!line[1].equals("<>")) {
731 stroke = KeyStroke.getKeyStroke(line[1]);
732 if(stroke == null) {
733 System.err.println("Warning: Bad keystroke '" + line[1] + "'.");
738 String[] lineCommand = Arrays.copyOfRange(line, 2, line.length);
739 used.add(line[0]);
740 List<String[]> commandList = extraActions.get(line[0]);
741 if(commandList == null)
742 extraActions.put(line[0], commandList = new ArrayList<String[]>());
743 commandList.add(lineCommand);
745 if(!exists)
746 menuManager.addMenuItem("Extra→" + line[0], this, "menuExtra", new String[]{line[0]}, PROFILE_ALWAYS,
747 stroke);
749 file.close();
750 } catch(IOException e) {
751 errorDialog(e, "Failed to load extra menu defintions", null, "dismiss");
752 if(file != null)
753 file.close();
755 window.setJMenuBar(menuManager.getMainBar());
758 public PCControl(Plugins manager) throws Exception
760 window = new JFrame("JPC-RR" + Misc.emuname);
762 if(DiskImage.getLibrary() == null)
763 throw new Exception("PCControl plugin requires disk library");
765 running = false;
766 shuttingDown = false;
768 debugInClass = new HashMap<String, Class<?>>();
769 debugState = new HashMap<String, Boolean>();
771 configDialog = new PCConfigDialog();
772 extraActions = new HashMap<String, List<String[]> >();
773 menuManager = new MenuManager();
775 createStatusWindow();
777 menuManager.setProfile(profile = (PROFILE_NO_PC | PROFILE_STOPPED | PROFILE_NOT_DUMPING));
779 menuManager.addMenuItem("System→Assemble", this, "menuAssemble", null, PROFILE_STOPPED);
780 menuManager.addMenuItem("System→Start", this, "menuStart", null, PROFILE_STOPPED | PROFILE_HAVE_PC);
781 menuManager.addMenuItem("System→Stop", this, "menuStop", null, PROFILE_RUNNING);
782 menuManager.addMenuItem("System→Reset", this, "menuReset", null, PROFILE_HAVE_PC);
783 menuManager.addMenuItem("System→Start dumping", this, "menuStartDump", null, PROFILE_STOPPED | PROFILE_NOT_DUMPING);
784 menuManager.addMenuItem("System→Stop dumping", this, "menuStopDump", null, PROFILE_STOPPED | PROFILE_DUMPING);
785 menuManager.addMenuItem("System→Quit", this, "menuQuit", null, PROFILE_ALWAYS);
786 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace Start", this, "menuVRetraceStart", null, false,
787 PROFILE_ALWAYS);
788 menuManager.addSelectableMenuItem("Breakpoints→Trap VRetrace End", this, "menuVRetraceEnd", null, false,
789 PROFILE_ALWAYS);
790 menuManager.addSelectableMenuItem("Breakpoints→Trap BIOS Keyboard", this, "menuBIOSKbd", null, false,
791 PROFILE_ALWAYS);
792 menuManager.addMenuItem("Snapshot→Change Run Authors", this, "menuChangeAuthors", null, PROFILE_HAVE_PC);
793 menuManager.addMenuItem("Snapshot→Save→Snapshot", this, "menuSave", new Object[]{new Boolean(false)},
794 PROFILE_HAVE_PC | PROFILE_STOPPED);
795 menuManager.addMenuItem("Snapshot→Save→Movie", this, "menuSave", new Object[]{new Boolean(true)},
796 PROFILE_HAVE_PC | PROFILE_STOPPED);
797 menuManager.addMenuItem("Snapshot→Save→Status Dump", this, "menuStatusDump", null,
798 PROFILE_HAVE_PC | PROFILE_STOPPED);
799 menuManager.addMenuItem("Snapshot→Load→Snapshot", this, "menuLoad",
800 new Object[]{new Integer(LoadStateTask.MODE_NORMAL)}, PROFILE_STOPPED);
801 menuManager.addMenuItem("Snapshot→Load→Snapshot (preserve events)", this, "menuLoad",
802 new Object[]{new Integer(LoadStateTask.MODE_PRESERVE)}, PROFILE_STOPPED | PROFILE_EVENTS);
803 menuManager.addMenuItem("Snapshot→Load→Movie", this, "menuLoad",
804 new Object[]{new Integer(LoadStateTask.MODE_MOVIEONLY)}, PROFILE_STOPPED);
805 menuManager.addMenuItem("Snapshot→RAM Dump→Hexadecimal", this, "menuRAMDump", new Object[]{new Boolean(false)},
806 PROFILE_HAVE_PC | PROFILE_STOPPED);
807 menuManager.addMenuItem("Snapshot→RAM Dump→Binary", this, "menuRAMDump", new Object[]{new Boolean(true)},
808 PROFILE_HAVE_PC | PROFILE_STOPPED);
809 menuManager.addMenuItem("Snapshot→Truncate Event Stream", this, "menuTruncate", null,
810 PROFILE_STOPPED | PROFILE_EVENTS);
812 for(int i = 0; i < stopLabel.length; i++) {
813 menuManager.addSelectableMenuItem("Breakpoints→Timed Stops→" + stopLabel[i], this, "menuTimedStop",
814 null, (i == 0), PROFILE_ALWAYS);
816 imminentTrapTime = -1;
818 menuManager.addMenuItem("Drives→fda→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(0),
819 new Integer(-1)}, PROFILE_HAVE_PC);
820 menuManager.addMenuItem("Drives→fdb→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(1),
821 new Integer(-1)}, PROFILE_HAVE_PC);
822 menuManager.addMenuItem("Drives→CD-ROM→<Empty>", this, "menuChangeDisk", new Object[]{new Integer(2),
823 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_CDROM);
824 menuManager.addMenuItem("Drives→Add image", this, "menuAddDisk", null, PROFILE_HAVE_PC);
825 menuManager.addMenuItem("Drives→Import Image", this, "menuImport", null, PROFILE_ALWAYS);
826 menuManager.addMenuItem("Drives→dump→HDA", this, "menuDumpDisk", new Object[]{
827 new Integer(-1)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDA);
828 menuManager.addMenuItem("Drives→dump→HDB", this, "menuDumpDisk", new Object[]{
829 new Integer(-2)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDB);
830 menuManager.addMenuItem("Drives→dump→HDC", this, "menuDumpDisk", new Object[]{
831 new Integer(-3)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDC);
832 menuManager.addMenuItem("Drives→dump→HDD", this, "menuDumpDisk", new Object[]{
833 new Integer(-4)}, PROFILE_HAVE_PC | PROFILE_HAVE_HDD);
834 menuManager.addMenuItem("Debug→Hacks→VGA_DRAW", this, "menuVGADRAW", null, PROFILE_HAVE_PC);
835 menuManager.addMenuItem("Debug→Hacks→VGA_SCROLL_2", this, "menuVGASCROLL2", null, PROFILE_HAVE_PC);
836 menuManager.addMenuItem("Debug→Show frame rate", this, "menuFramerate", null, PROFILE_HAVE_PC);
837 menuManager.addMenuItem("Debug→Show CRTC register", this, "menuShowCRTC", null, PROFILE_HAVE_PC);
839 disks = new HashSet<String>();
840 currentProject = new PC.PCFullStatus();
841 this.pc = null;
842 this.vPluginManager = manager;
844 panel = new PCMonitorPanel(this, manager.getOutputConnector());
845 loadstateDropTarget = new LoadstateDropTarget();
846 dropTarget = new DropTarget(panel.getMonitorPanel(), loadstateDropTarget);
848 statusBar = new JLabel("");
849 statusBar.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
850 manager.addSlaveObject(this, panel);
851 panel.startThread();
853 window.getContentPane().add("Center", panel.getMonitorPanel());
854 window.getContentPane().add("South", statusBar);
855 JMenuBar bar = menuManager.getMainBar();
856 for(JMenu menu : panel.getMenusNeeded())
857 bar.add(menu);
858 window.setJMenuBar(bar);
860 try {
861 window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
862 } catch (AccessControlException e) {
863 System.err.println("Error: Not able to add some components to frame: " + e.getMessage());
866 snapshotFileChooser = new JFileChooser(System.getProperty("user.dir"));
867 otherFileChooser = new JFileChooser(System.getProperty("user.dir"));
869 window.getContentPane().validate();
870 window.validate();
871 window.pack();
872 Dimension d = window.getSize();
873 nativeWidth = d.width;
874 nativeHeight = d.height;
875 updateStatusBarEventThread();
877 window.setVisible(true);
880 public void sendMessage(String msg)
882 vPluginManager.invokeExternalCommand("luaplugin-sendmessage", new Object[]{msg});
885 private String translateName(String name)
887 StringBuffer buf = new StringBuffer();
888 for(int i = 0; i < name.length(); i++)
889 if(name.charAt(i) == '_')
890 buf.append(' ');
891 else
892 buf.append(name.charAt(i));
893 return buf.toString();
896 private String debugShowName(String name)
898 return translateName(name.substring(12));
901 private void addDebug(String name, Class<?> clazz)
903 if(debugInClass.get(name) != null)
904 return;
905 debugInClass.put(name, clazz);
906 debugState.put(name, false);
907 try {
908 menuManager.addSelectableMenuItem("Debug→" + debugShowName(name), this, "menuDEBUGOPTION",
909 new Object[]{name}, false, PROFILE_HAVE_PC);
910 } catch(Exception e) {
914 public void menuDEBUGOPTION(String i, Object[] args)
916 String name = (String)args[0];
917 String mName = "Debug→" + debugShowName(name);
918 debugState.put(name, !debugState.get(name));
919 setDebugOption(name);
920 menuManager.setSelected(mName, debugState.get(name));
923 private void setDebugOption(String name)
925 try {
926 debugInClass.get(name).getDeclaredMethod(name, boolean.class).invoke(pc.getComponent(
927 debugInClass.get(name)), debugState.get(name));
928 } catch(Exception e) {
929 e.printStackTrace();
933 private void setDebugOptions()
935 for(Map.Entry<String, Class<?>> opt : debugInClass.entrySet())
936 setDebugOption(opt.getKey());
939 private void updateDebug()
941 setDebugOptions();
942 for(HardwareComponent c : pc.allComponents()) {
943 Class<?> cl = c.getClass();
944 for(Method m : cl.getDeclaredMethods()) {
945 Class<?>[] p = m.getParameterTypes();
946 if(!m.getName().startsWith("DEBUGOPTION_"))
947 continue;
948 if(p.length != 1 || p[0] != boolean.class)
949 continue;
950 addDebug(m.getName(), cl);
955 public void notifyRenderer(org.jpc.pluginsaux.HUDRenderer r)
957 vPluginManager.addRenderer(r);
960 private void updateStatusBar()
962 if(vPluginManager.isShuttingDown())
963 return; //Too much of deadlock risk.
964 SwingUtilities.invokeLater(new Runnable() { public void run() { updateStatusBarEventThread(); }});
967 private void updateStatusBarEventThread()
969 String text1;
970 if(currentProject.pc != null && taskToDo == null) {
971 long timeNow = ((Clock)currentProject.pc.getComponent(Clock.class)).getTime();
972 long timeEnd = currentProject.events.getLastEventTime();
973 text1 = " Time: " + (timeNow / 1000000) + "ms, movie length: " + (timeEnd / 1000000) + "ms";
974 if(currentResolutionWidth > 0 && currentResolutionHeight > 0)
975 text1 = text1 + ", resolution: " + currentResolutionWidth + "*" + currentResolutionHeight;
976 else
977 text1 = text1 + ", resolution: <No valid signal>";
978 if(currentProject.events.isAtMovieEnd())
979 text1 = text1 + " (At movie end)";
980 } else if(taskToDo != null)
981 text1 = taskLabel;
982 else
983 text1 = " NO PC CONNECTED";
985 statusBar.setText(text1);
986 updateStatusMessages();
989 private void createStatusWindow()
991 statusWindow = new JFrame("Emulator Status");
992 GridLayout layout = new GridLayout(0, 2);
993 statusPanel = new JPanel();
994 statusPanel.setLayout(layout);
995 statusWindow.add(statusPanel);
996 statusLabels = new HashMap<String, JLabel>();
997 statusValues = new HashMap<String, JLabel>();
998 statusLastValue = new HashMap<String, String>();
999 statusWindow.pack();
1000 statusWindow.setVisible(true);
1003 private void updateStatusMessages()
1005 Map<String, String> values = new HashMap<String, String>();
1006 boolean altered = false;
1008 if(pc != null) {
1009 for(HardwareComponent c : pc.allComponents()) {
1010 Class<?> cl = c.getClass();
1011 for(Field m : cl.getDeclaredFields()) {
1012 if(!m.getName().startsWith("STATUS_"))
1013 continue;
1014 try {
1015 String sname = m.getName().substring(7);
1016 String value = m.get(c).toString();
1017 values.put(sname, value);
1018 } catch(Exception e) {
1019 e.printStackTrace();
1025 for(Map.Entry<String, JLabel> i : statusLabels.entrySet())
1026 if(!values.containsKey(i.getKey())) {
1027 String todel = i.getKey();
1028 statusPanel.remove(statusLabels.get(todel));
1029 statusPanel.remove(statusValues.get(todel));
1030 statusLabels.remove(todel);
1031 statusValues.remove(todel);
1032 statusLastValue.remove(todel);
1033 altered = true;
1036 for(Map.Entry<String, String> i : values.entrySet()) {
1037 String sname = i.getKey();
1038 String value = i.getValue();
1039 if(statusLabels.containsKey(sname)) {
1040 if(!i.getValue().equals(statusLastValue.get(sname)))
1041 statusValues.get(sname).setText(value);
1042 } else {
1043 String lname = translateName(sname) + " ";
1044 statusLabels.put(sname, new JLabel(lname));
1045 statusValues.put(sname, new JLabel(value));
1046 statusPanel.add(statusLabels.get(sname));
1047 statusPanel.add(statusValues.get(sname));
1049 statusLastValue.put(sname, value);
1051 statusWindow.pack();
1054 public void menuExtra(String i, Object[] args)
1056 final List<String[]> commandList = extraActions.get(args[0]);
1057 if(commandList == null) {
1058 System.err.println("Warning: Called extra menu with unknown entry '" + args[0] + "'.");
1059 return;
1062 //Run the functions on seperate thread to avoid deadlocking.
1063 (new Thread(new Runnable() { public void run() { menuExtraThreadFunc(commandList); }}, "Extra action thread")).start();
1066 private void menuExtraThreadFunc(List<String[]> actions)
1068 for(String[] i : actions) {
1069 if(i.length == 1) {
1070 vPluginManager.invokeExternalCommandSynchronous(i[0], null);
1071 } else {
1072 String[] rest = Arrays.copyOfRange(i, 1, i.length, String[].class);
1073 vPluginManager.invokeExternalCommandSynchronous(i[0], rest);
1078 public void menuAssemble(String i, Object[] args)
1080 setTask(new AssembleTask(), ASSEMBLE_LABEL);
1083 public void menuStart(String i, Object[] args)
1085 start();
1088 public void menuStartDump(String i, Object[] args)
1090 int returnVal = otherFileChooser.showDialog(window, "Dump to file");
1091 if(returnVal != 0)
1092 return;
1093 File choosen = otherFileChooser.getSelectedFile();
1094 try {
1095 dumper = new RAWDumper(vPluginManager, "rawoutput=" + choosen.getAbsolutePath());
1096 vPluginManager.registerPlugin(dumper);
1097 pc.refreshGameinfo(currentProject);
1098 } catch(Exception e) {
1099 errorDialog(e, "Failed to start dumping", null, "Dismiss");
1100 return;
1102 profile &= ~PROFILE_NOT_DUMPING;
1103 profile |= PROFILE_DUMPING;
1104 menuManager.setProfile(profile);
1107 public void menuStopDump(String i, Object[] args)
1109 vPluginManager.unregisterPlugin(dumper);
1110 profile &= ~PROFILE_DUMPING;
1111 profile |= PROFILE_NOT_DUMPING;
1112 menuManager.setProfile(profile);
1115 public void menuStop(String i, Object[] args)
1117 stop();
1120 public void menuReset(String i, Object[] args)
1122 reset();
1125 public void menuImport(String i, Object[] args)
1127 try {
1128 new ImportDiskImage();
1129 } catch(Exception e) {
1130 e.printStackTrace();
1134 public void menuVGADRAW(String i, Object[] args)
1136 pc.setVGADrawHack();
1139 public void menuVGASCROLL2(String i, Object[] args)
1141 pc.setVGAScroll2Hack();
1144 public void menuFramerate(String i, Object[] args)
1146 VGACard card = (VGACard)pc.getComponent(VGACard.class);
1147 if(card == null) {
1148 callShowOptionDialog(window, "Can't get current framerate!", "Error", JOptionPane.YES_NO_OPTION,
1149 JOptionPane.WARNING_MESSAGE, null, new String[]{"Dismiss"}, "Dismiss");
1150 return;
1152 callShowOptionDialog(window, "Current framerate is " + card.getFramerate() + " fps.", "Information",
1153 JOptionPane.YES_NO_OPTION,JOptionPane.INFORMATION_MESSAGE, null, new String[]{"Dismiss"}, "Dismiss");
1156 public void menuShowCRTC(String i, Object[] args)
1158 VGACard card = (VGACard)pc.getComponent(VGACard.class);
1159 if(card == null) {
1160 callShowOptionDialog(window, "Can't get current CTRC registers!", "Error", JOptionPane.YES_NO_OPTION,
1161 JOptionPane.WARNING_MESSAGE, null, new String[]{"Dismiss"}, "Dismiss");
1162 return;
1164 callShowOptionDialog(window, card.getCTRCDump(), "Information",
1165 JOptionPane.YES_NO_OPTION,JOptionPane.INFORMATION_MESSAGE, null, new String[]{"Dismiss"}, "Dismiss");
1168 public void menuQuit(String i, Object[] args)
1170 vPluginManager.shutdownEmulator();
1173 public void menuVRetraceStart(String i, Object[] args)
1175 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_START;
1176 menuManager.setSelected("Breakpoints→Trap VRetrace Start",
1177 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_START) == TraceTrap.TRACE_STOP_VRETRACE_START);
1180 public void menuVRetraceEnd(String i, Object[] args)
1182 trapFlags ^= TraceTrap.TRACE_STOP_VRETRACE_END;
1183 menuManager.setSelected("Breakpoints→Trap VRetrace End",
1184 (trapFlags & TraceTrap.TRACE_STOP_VRETRACE_END) == TraceTrap.TRACE_STOP_VRETRACE_END);
1187 public void menuBIOSKbd(String i, Object[] args)
1189 trapFlags ^= TraceTrap.TRACE_STOP_BIOS_KBD;
1190 menuManager.setSelected("Breakpoints→Trap BIOS Keyboard",
1191 (trapFlags & TraceTrap.TRACE_STOP_BIOS_KBD) == TraceTrap.TRACE_STOP_BIOS_KBD);
1194 public void menuTimedStop(String i, Object[] args)
1196 for(int j = 0; j < stopLabel.length; j++) {
1197 String label = "Breakpoints→Timed Stops→" + stopLabel[j];
1198 if(i.equals(label)) {
1199 this.imminentTrapTime = stopTime[j];
1200 menuManager.select(label);
1201 } else
1202 menuManager.unselect(label);
1206 public void menuSave(String i, Object[] args)
1208 setTask(new SaveStateTask(((Boolean)args[0]).booleanValue()), SAVESTATE_LABEL);
1211 public void menuStatusDump(String i, Object[] args)
1213 setTask(new StatusDumpTask(), STATUSDUMP_LABEL);
1216 public void menuLoad(String i, Object[] args)
1218 setTask(new LoadStateTask(((Integer)args[0]).intValue()), LOADSTATE_LABEL);
1221 public void menuRAMDump(String i, Object[] args)
1223 setTask(new RAMDumpTask(((Boolean)args[0]).booleanValue()), RAMDUMP_LABEL);
1226 public void menuDumpDisk(String i, Object[] args)
1228 setTask(new ImageDumpTask(((Integer)args[0]).intValue()), IMAGEDUMP_LABEL);
1231 public void menuTruncate(String i, Object[] args)
1233 currentProject.events.truncateEventStream();
1236 public void menuChangeDisk(String i, Object[] args)
1238 changeFloppy(((Integer)args[0]).intValue(), ((Integer)args[1]).intValue());
1241 public void menuWriteProtect(String i, Object[] args)
1243 int disk = ((Integer)args[0]).intValue();
1244 writeProtect(disk, menuManager.isSelected(i));
1245 DiskImageSet imageSet = pc.getDisks();
1246 menuManager.setSelected(i, imageSet.lookupDisk(disk).isReadOnly());
1249 public void menuAddDisk(String i, Object[] args)
1251 setTask(new AddDiskTask(), ADDDISK_LABEL);
1254 public void menuChangeAuthors(String i, Object[] args)
1256 setTask(new ChangeAuthorsTask(), CHANGEAUTHORS_LABEL);
1259 public synchronized void start()
1261 if(taskToDo != null)
1262 return;
1263 vPluginManager.pcStarted();
1264 running = true;
1265 notifyAll();
1268 private String prettyPrintTime(long ts)
1270 String s = "";
1272 if(ts >= 1000000000)
1273 s = s + "" + (ts / 1000000000) + " ";
1274 if(ts >= 100000000)
1275 s = s + "" + (ts % 1000000000 / 100000000);
1276 if(ts >= 10000000)
1277 s = s + "" + (ts % 100000000 / 10000000);
1278 if(ts >= 1000000)
1279 s = s + "" + (ts % 10000000 / 1000000) + " ";
1280 if(ts >= 100000)
1281 s = s + "" + (ts % 1000000 / 100000);
1282 if(ts >= 10000)
1283 s = s + "" + (ts % 100000 / 10000);
1284 if(ts >= 1000)
1285 s = s + "" + (ts % 10000 / 1000) + " ";
1286 if(ts >= 100)
1287 s = s + "" + (ts % 1000 / 100);
1288 if(ts >= 10)
1289 s = s + "" + (ts % 100 / 10);
1290 s = s + "" + (ts % 10);
1291 return s;
1294 protected synchronized void stopNoWait()
1296 running = false;
1297 vPluginManager.pcStopped();
1298 Clock sysClock = (Clock)pc.getComponent(Clock.class);
1299 System.err.println("Notice: PC emulation stopped (at time sequence value " +
1300 prettyPrintTime(sysClock.getTime()) + ")");
1303 public synchronized void stop()
1305 pc.getTraceTrap().doPotentialTrap(TraceTrap.TRACE_STOP_IMMEDIATE);
1306 System.err.println("Informational: Waiting for PC to halt...");
1309 public JScrollPane getMonitorPane()
1311 return null;
1314 protected void reset()
1316 pc.reboot();
1319 public synchronized boolean isRunning()
1321 return running;
1324 private void changeFloppy(int drive, int image)
1328 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
1329 changer.changeFloppyDisk(drive, image);
1330 } catch (Exception e) {
1331 System.err.println("Error: Failed to change disk");
1332 errorDialog(e, "Failed to change disk", null, "Dismiss");
1336 private void writeProtect(int image, boolean state)
1340 PC.DiskChanger changer = (PC.DiskChanger)pc.getComponent(PC.DiskChanger.class);
1341 changer.wpFloppyDisk(image, state);
1342 } catch (Exception e) {
1343 System.err.println("Error: Failed to change floppy write protect");
1344 errorDialog(e, "Failed to write (un)protect floppy", null, "Dismiss");
1348 static String chooseMovie(Set<String> choices)
1350 if(choices.isEmpty())
1351 return null;
1352 String[] x = new String[1];
1353 x = choices.toArray(x);
1354 int i = callShowOptionDialog(null, "Multiple initializations exist, pick one",
1355 "Multiple movies in one", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE , null,
1356 x, x[0]);
1357 return "initialization-" + x[i];
1360 static void parseSubmovies(UTFInputLineStream lines, Set<String> choices, boolean force) throws IOException
1362 String[] components = nextParseLine(lines);
1363 while(components != null) {
1364 if("SAVESTATEID".equals(components[0]) && !force) {
1365 choices.clear();
1366 return;
1368 if("INITIALSTATE".equals(components[0])) {
1369 if(components.length != 2)
1370 throw new IOException("Bad " + components[0] + " line in header segment: " +
1371 "expected 2 components, got " + components.length);
1372 choices.add(components[1]);
1374 components = nextParseLine(lines);
1378 private class LoadStateTask extends AsyncGUITask
1380 File chosen;
1381 Exception caught;
1382 int _mode;
1383 long oTime;
1384 private static final int MODE_NORMAL = 1;
1385 private static final int MODE_PRESERVE = 2;
1386 private static final int MODE_MOVIEONLY = 3;
1388 public LoadStateTask(int mode)
1390 oTime = System.currentTimeMillis();
1391 chosen = null;
1392 _mode = mode;
1395 public LoadStateTask(String name, int mode)
1397 this(mode);
1398 chosen = new File(name);
1401 protected void runPrepare()
1403 if(chosen == null) {
1404 int returnVal = 0;
1405 if(_mode == MODE_PRESERVE)
1406 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot (PE)");
1407 else if(_mode == MODE_MOVIEONLY)
1408 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot (MO)");
1409 else
1410 returnVal = snapshotFileChooser.showDialog(window, "LOAD JPC-RR Snapshot");
1411 chosen = snapshotFileChooser.getSelectedFile();
1413 if (returnVal != 0)
1414 chosen = null;
1418 protected void runFinish()
1420 if(chosen == null)
1421 return;
1423 if(caught == null) {
1424 try {
1425 connectPC(pc = currentProject.pc);
1426 doCycle(pc);
1427 System.err.println("Informational: Loadstate done on "+chosen.getAbsolutePath());
1428 } catch(Exception e) {
1429 caught = e;
1432 if(caught != null) {
1433 errorDialog(caught, "Load savestate failed", window, "Dismiss");
1435 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1436 PCControl.this.vPluginManager.signalCommandCompletion();
1439 protected void runTask()
1441 if(chosen == null)
1442 return;
1444 try {
1445 System.err.println("Informational: Loading a snapshot of JPC-RR");
1446 long times1 = System.currentTimeMillis();
1447 JRSRArchiveReader reader = new JRSRArchiveReader(chosen.getAbsolutePath());
1449 PC.PCFullStatus fullStatus;
1450 String choosenSubmovie = null;
1451 Set<String> submovies = new HashSet<String>();
1452 UTFInputLineStream lines = new UTFInputLineStream(reader.readMember("header"));
1453 parseSubmovies(lines, submovies, _mode == MODE_MOVIEONLY);
1454 if(!submovies.isEmpty())
1455 choosenSubmovie = chooseMovie(submovies);
1456 fullStatus = PC.loadSavestate(reader, _mode == MODE_PRESERVE, _mode == MODE_MOVIEONLY,
1457 currentProject, choosenSubmovie);
1459 currentProject = fullStatus;
1461 reader.close();
1462 long times2 = System.currentTimeMillis();
1463 System.err.println("Informational: Loadstate complete (" + (times2 - times1) + "ms).");
1464 } catch(Exception e) {
1465 caught = e;
1470 private synchronized void doCycleDedicatedThread(PC _pc)
1472 if(_pc == null) {
1473 cycleDone = true;
1474 return;
1476 DisplayController dc = (DisplayController)_pc.getComponent(DisplayController.class);
1477 dc.getOutputDevice().holdOutput(_pc.getTime());
1478 cycleDone = true;
1479 notifyAll();
1482 private void doCycle(PC _pc)
1484 final PC _xpc = _pc;
1485 cycleDone = false;
1486 (new Thread(new Runnable() { public void run() { doCycleDedicatedThread(_xpc); }}, "VGA output cycle thread")).start();
1487 while(cycleDone)
1488 try {
1489 synchronized(this) {
1490 if(cycleDone)
1491 break;
1492 wait();
1494 } catch(Exception e) {
1498 private class SaveStateTask extends AsyncGUITask
1500 File chosen;
1501 Exception caught;
1502 boolean movieOnly;
1503 long oTime;
1505 public SaveStateTask(boolean movie)
1507 oTime = System.currentTimeMillis();
1508 chosen = null;
1509 movieOnly = movie;
1512 public SaveStateTask(String name, boolean movie)
1514 this(movie);
1515 chosen = new File(name);
1518 protected void runPrepare()
1520 if(chosen == null) {
1521 int returnVal = snapshotFileChooser.showDialog(window, movieOnly ? "Save JPC-RR Movie" :
1522 "Save JPC-RR Snapshot");
1523 chosen = snapshotFileChooser.getSelectedFile();
1525 if (returnVal != 0)
1526 chosen = null;
1530 protected void runFinish()
1532 if(caught != null) {
1533 errorDialog(caught, "Saving savestate failed", window, "Dismiss");
1535 System.err.println("Total save time: " + (System.currentTimeMillis() - oTime) + "ms.");
1536 PCControl.this.vPluginManager.signalCommandCompletion();
1539 protected void runTask()
1541 if(chosen == null)
1542 return;
1544 JRSRArchiveWriter writer = null;
1546 try {
1547 System.err.println("Informational: Savestating...");
1548 long times1 = System.currentTimeMillis();
1549 writer = new JRSRArchiveWriter(chosen.getAbsolutePath());
1550 PC.saveSavestate(writer, currentProject, movieOnly, uncompressedSave);
1551 renameFile(chosen, new File(chosen.getAbsolutePath() + ".backup"));
1552 writer.close();
1553 long times2 = System.currentTimeMillis();
1554 System.err.println("Informational: Savestate complete (" + (times2 - times1) + "ms). on"+chosen.getAbsolutePath());
1555 } catch(Exception e) {
1556 if(writer != null)
1557 try { writer.rollback(); } catch(Exception f) {}
1558 caught = e;
1563 private class StatusDumpTask extends AsyncGUITask
1565 File chosen;
1566 Exception caught;
1568 public StatusDumpTask()
1570 chosen = null;
1573 public StatusDumpTask(String name)
1575 this();
1576 chosen = new File(name);
1579 protected void runPrepare()
1581 if(chosen == null) {
1582 int returnVal = otherFileChooser.showDialog(window, "Save Status dump");
1583 chosen = otherFileChooser.getSelectedFile();
1585 if (returnVal != 0)
1586 chosen = null;
1590 protected void runFinish()
1592 if(caught != null) {
1593 errorDialog(caught, "Status dump failed", window, "Dismiss");
1595 PCControl.this.vPluginManager.signalCommandCompletion();
1598 protected void runTask()
1600 if(chosen == null)
1601 return;
1603 try {
1604 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1605 PrintStream out = new PrintStream(outb, false, "UTF-8");
1606 StatusDumper sd = new StatusDumper(out);
1607 pc.dumpStatus(sd);
1608 out.flush();
1609 outb.flush();
1610 System.err.println("Informational: Dumped " + sd.dumpedObjects() + " objects");
1611 } catch(Exception e) {
1612 caught = e;
1617 private class RAMDumpTask extends AsyncGUITask
1619 File chosen;
1620 Exception caught;
1621 boolean binary;
1623 public RAMDumpTask(boolean binFlag)
1625 chosen = null;
1626 binary = binFlag;
1629 public RAMDumpTask(String name, boolean binFlag)
1631 this(binFlag);
1632 chosen = new File(name);
1635 protected void runPrepare()
1637 if(chosen == null) {
1638 int returnVal;
1639 if(binary)
1640 returnVal = otherFileChooser.showDialog(window, "Save RAM dump");
1641 else
1642 returnVal = otherFileChooser.showDialog(window, "Save RAM hexdump");
1643 chosen = otherFileChooser.getSelectedFile();
1645 if (returnVal != 0)
1646 chosen = null;
1650 protected void runFinish()
1652 if(caught != null) {
1653 errorDialog(caught, "RAM dump failed", window, "Dismiss");
1655 PCControl.this.vPluginManager.signalCommandCompletion();
1658 protected void runTask()
1660 if(chosen == null)
1661 return;
1663 try {
1664 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1665 byte[] pagebuf = new byte[4096];
1666 PhysicalAddressSpace addr = (PhysicalAddressSpace)pc.getComponent(PhysicalAddressSpace.class);
1667 int lowBound = addr.findFirstRAMPage(0);
1668 int firstUndumped = 0;
1669 int highBound = 0;
1670 int present = 0;
1671 while(lowBound >= 0) {
1672 for(; firstUndumped < lowBound; firstUndumped++)
1673 dumpPage(outb, firstUndumped, null);
1674 addr.readRAMPage(firstUndumped++, pagebuf);
1675 dumpPage(outb, lowBound, pagebuf);
1676 present++;
1677 highBound = lowBound + 1;
1678 lowBound = addr.findFirstRAMPage(++lowBound);
1680 outb.flush();
1681 System.err.println("Informational: Dumped machine RAM (" + highBound + " pages examined, " +
1682 present + " pages present).");
1683 } catch(Exception e) {
1684 caught = e;
1688 private byte charForHex(int hvalue)
1690 if(hvalue < 10)
1691 return (byte)(hvalue + 48);
1692 else if(hvalue > 9 && hvalue < 16)
1693 return (byte)(hvalue + 55);
1694 else
1695 System.err.println("Unknown hex value: " + hvalue + ".");
1696 return 90;
1699 private void dumpPage(OutputStream stream, int pageNo, byte[] buffer) throws IOException
1701 int pageBufSize;
1702 pageNo = pageNo & 0xFFFFF; //Cut page numbers out of range.
1703 if(!binary && buffer == null)
1704 return; //Don't dump null pages in non-binary mode.
1705 if(binary)
1706 pageBufSize = 4096; //Binary page buffer is 4096 bytes.
1707 else
1708 pageBufSize = 14592; //Hexdump page buffer is 14592 bytes.
1709 byte[] outputPage = new byte[pageBufSize];
1710 if(buffer != null && binary) {
1711 System.arraycopy(buffer, 0, outputPage, 0, 4096);
1712 } else if(buffer != null) { //Hex mode
1713 for(int i = 0; i < 256; i++) {
1714 for(int j = 0; j < 57; j++) {
1715 if(j < 5)
1716 outputPage[57 * i + j] = charForHex((pageNo >>> (4 * (4 - j))) & 0xF);
1717 else if(j == 5)
1718 outputPage[57 * i + j] = charForHex(i / 16);
1719 else if(j == 6)
1720 outputPage[57 * i + j] = charForHex(i % 16);
1721 else if(j == 7)
1722 outputPage[57 * i + j] = 48;
1723 else if(j == 56)
1724 outputPage[57 * i + j] = 10;
1725 else if(j % 3 == 2)
1726 outputPage[57 * i + j] = 32;
1727 else if(j % 3 == 0)
1728 outputPage[57 * i + j] = charForHex(((int)buffer[16 * i + j / 3 - 3] & 0xFF) / 16);
1729 else if(j % 3 == 1)
1730 outputPage[57 * i + j] = charForHex(buffer[16 * i + j / 3 - 3] & 0xF);
1731 else
1732 System.err.println("Error: dumpPage: unhandled j = " + j + ".");
1736 stream.write(outputPage);
1740 private class ImageDumpTask extends AsyncGUITask
1742 File chosen;
1743 Exception caught;
1744 int index;
1746 public ImageDumpTask(int _index)
1748 chosen = null;
1749 index = _index;
1752 public ImageDumpTask(String name, int index)
1754 this(index);
1755 chosen = new File(name);
1758 protected void runPrepare()
1760 if(chosen == null) {
1761 int returnVal;
1762 returnVal = otherFileChooser.showDialog(window, "Save Image dump");
1763 chosen = otherFileChooser.getSelectedFile();
1765 if (returnVal != 0)
1766 chosen = null;
1770 protected void runFinish()
1772 if(caught != null) {
1773 errorDialog(caught, "Image dump failed", window, "Dismiss");
1775 PCControl.this.vPluginManager.signalCommandCompletion();
1778 protected void runTask()
1780 if(chosen == null)
1781 return;
1783 try {
1784 DiskImage dev;
1785 if(index < 0)
1786 dev = pc.getDrives().getHardDrive(-1 - index).getImage();
1787 else
1788 dev = pc.getDisks().lookupDisk(index);
1789 if(dev == null)
1790 throw new IOException("Trying to dump nonexistent disk");
1791 OutputStream outb = new BufferedOutputStream(new FileOutputStream(chosen));
1792 byte[] buf = new byte[512];
1793 long sectors = dev.getTotalSectors();
1794 for(long i = 0; i < sectors; i++) {
1795 dev.read(i, buf, 1);
1796 outb.write(buf);
1798 outb.close();
1799 System.err.println("Informational: Dumped disk image (" + sectors + " sectors).");
1800 } catch(Exception e) {
1801 caught = e;
1806 private class AssembleTask extends AsyncGUITask
1808 Exception caught;
1809 boolean canceled;
1811 public AssembleTask()
1813 canceled = false;
1816 protected void runPrepare()
1818 try {
1819 configDialog.popUp();
1820 } catch(Exception e) {
1821 caught = e;
1825 protected void runFinish()
1827 if(caught == null && !canceled) {
1828 try {
1829 currentProject.projectID = randomHexes(24);
1830 currentProject.rerecords = new BigInteger("0");
1831 currentProject.events = new EventRecorder();
1832 currentProject.events.attach(pc, null);
1833 currentProject.savestateID = null;
1834 currentProject.extraHeaders = null;
1835 currentProject.events.setRerecordCount(new BigInteger("0"));
1836 currentProject.events.setHeaders(currentProject.extraHeaders);
1837 currentProject.events.setProjectID(currentProject.projectID);
1838 connectPC(pc);
1839 } catch(Exception e) {
1840 caught = e;
1843 if(caught != null) {
1844 errorDialog(caught, "PC Assembly failed", window, "Dismiss");
1846 PCControl.this.vPluginManager.signalCommandCompletion();
1849 protected void runTask()
1851 if(caught != null)
1852 return;
1853 PC.PCHardwareInfo hw = configDialog.waitClose();
1854 if(hw == null) {
1855 canceled = true;
1856 return;
1859 try {
1860 pc = PC.createPC(hw);
1861 } catch(Exception e) {
1862 caught = e;
1867 private class AddDiskTask extends AsyncGUITask
1869 Exception caught;
1870 NewDiskDialog dd;
1872 public AddDiskTask()
1874 dd = new NewDiskDialog();
1877 protected void runPrepare()
1881 protected void runFinish()
1883 if(caught != null) {
1884 errorDialog(caught, "Adding disk failed", window, "Dismiss");
1886 try {
1887 updateDisks();
1888 } catch(Exception e) {
1889 errorDialog(e, "Failed to update disk menus", null, "Dismiss");
1891 PCControl.this.vPluginManager.signalCommandCompletion();
1894 protected void runTask()
1896 NewDiskDialog.Response res = dd.waitClose();
1897 if(res == null) {
1898 return;
1900 try {
1901 DiskImage img;
1902 pc.getDisks().addDisk(img = new DiskImage(res.diskFile, false));
1903 img.setName(res.diskName);
1904 } catch(Exception e) {
1905 caught = e;
1910 private class ChangeAuthorsTask extends AsyncGUITask
1912 Exception caught;
1913 AuthorsDialog ad;
1915 public ChangeAuthorsTask()
1917 int authors = 0;
1918 int headers = 0;
1919 AuthorsDialog.AuthorElement[] authorNames = null;
1920 String gameName = "";
1921 if(currentProject != null)
1922 authorNames = AuthorsDialog.readAuthorsFromHeaders(currentProject.extraHeaders);
1923 if(currentProject != null)
1924 gameName = AuthorsDialog.readGameNameFromHeaders(currentProject.extraHeaders);
1925 ad = new AuthorsDialog(authorNames, gameName);
1928 protected void runPrepare()
1932 protected void runFinish()
1934 if(caught != null) {
1935 errorDialog(caught, "Changing authors failed", window, "Dismiss");
1937 PCControl.this.vPluginManager.signalCommandCompletion();
1940 protected void runTask()
1942 AuthorsDialog.Response res = ad.waitClose();
1943 if(res == null) {
1944 return;
1946 try {
1947 currentProject.extraHeaders = AuthorsDialog.rewriteHeaderAuthors(currentProject.extraHeaders,
1948 res.authors, res.gameName);
1949 currentProject.events.setHeaders(currentProject.extraHeaders);
1950 } catch(Exception e) {
1951 caught = e;