2 JPC-RR: A x86 PC Hardware Emulator
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 H. Ilari Liusvaara
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 Based on JPC x86 PC Hardware emulator,
22 A project from the Physics Dept, The University of Oxford
24 Details about original JPC can be found at:
26 www-jpc.physics.ox.ac.uk
30 package org
.jpc
.plugins
;
32 import java
.awt
.Dimension
;
35 import java
.security
.AccessControlException
;
37 import java
.awt
.dnd
.*;
38 import java
.awt
.datatransfer
.*;
39 import javax
.swing
.border
.EtchedBorder
;
41 import org
.jpc
.emulator
.HardwareComponent
;
42 import org
.jpc
.emulator
.PC
;
43 import org
.jpc
.emulator
.EventRecorder
;
44 import org
.jpc
.emulator
.TraceTrap
;
45 import org
.jpc
.emulator
.memory
.PhysicalAddressSpace
;
46 import org
.jpc
.emulator
.StatusDumper
;
47 import org
.jpc
.emulator
.Clock
;
48 import org
.jpc
.emulator
.VGADigitalOut
;
49 import org
.jpc
.diskimages
.BlockDevice
;
50 import org
.jpc
.diskimages
.DiskImageSet
;
51 import org
.jpc
.diskimages
.DiskImage
;
52 import org
.jpc
.pluginsaux
.PleaseWait
;
53 import org
.jpc
.pluginsaux
.AsyncGUITask
;
54 import org
.jpc
.pluginsaux
.NewDiskDialog
;
55 import org
.jpc
.pluginsaux
.AuthorsDialog
;
56 import org
.jpc
.pluginsaux
.PCConfigDialog
;
57 import org
.jpc
.pluginsaux
.DumpControlDialog
;
58 import org
.jpc
.pluginsaux
.MenuManager
;
59 import org
.jpc
.pluginsaux
.PCMonitorPanel
;
60 import org
.jpc
.pluginsaux
.PCMonitorPanelEmbedder
;
61 import org
.jpc
.pluginsaux
.ImportDiskImage
;
63 import org
.jpc
.pluginsbase
.*;
64 import org
.jpc
.jrsr
.*;
66 import static org
.jpc
.Misc
.randomHexes
;
67 import static org
.jpc
.Misc
.errorDialog
;
68 import static org
.jpc
.Misc
.callShowOptionDialog
;
69 import static org
.jpc
.Misc
.moveWindow
;
70 import static org
.jpc
.Misc
.parseStringToComponents
;
71 import static org
.jpc
.Misc
.nextParseLine
;
72 import static org
.jpc
.Misc
.renameFile
;
74 public class PCControl
implements Plugin
, PCMonitorPanelEmbedder
76 private static long PROFILE_ALWAYS
= 0;
77 private static long PROFILE_NO_PC
= 1;
78 private static long PROFILE_HAVE_PC
= 2;
79 private static long PROFILE_STOPPED
= 4;
80 private static long PROFILE_RUNNING
= 8;
81 private static long PROFILE_EVENTS
= 16;
82 private static long PROFILE_CDROM
= 32;
83 private static String SAVESTATE_LABEL
= "Savestating...";
84 private static String LOADSTATE_LABEL
= "Loadstating...";
85 private static String RAMDUMP_LABEL
= "Dumping RAM...";
86 private static String STATUSDUMP_LABEL
= "Dumping status...";
87 private static String DUMPCONTROL_LABEL
= "Dump control open...";
88 private static String ASSEMBLE_LABEL
= "Assembling system...";
89 private static String ADDDISK_LABEL
= "Adding new disk...";
90 private static String CHANGEAUTHORS_LABEL
= "Changing run authors...";
92 private static final long serialVersionUID
= 8;
93 private Plugins vPluginManager
;
95 private JFrame window
;
96 private JFileChooser snapshotFileChooser
;
97 private DropTarget dropTarget
;
98 private LoadstateDropTarget loadstateDropTarget
;
100 private Set
<String
> disks
;
104 private int trapFlags
;
106 private volatile boolean running
;
107 private volatile boolean waiting
;
108 private boolean uncompressedSave
;
109 private static final long[] stopTime
;
110 private static final String
[] stopLabel
;
111 private volatile long imminentTrapTime
;
112 private boolean shuttingDown
;
113 private int nativeWidth
;
114 private int nativeHeight
;
115 private PCConfigDialog configDialog
;
116 private DumpControlDialog dumpDialog
;
117 private MenuManager menuManager
;
118 private Map
<String
, List
<String
[]> > extraActions
;
119 private PCMonitorPanel panel
;
120 private JLabel statusBar
;
121 private volatile int currentResolutionWidth
;
122 private volatile int currentResolutionHeight
;
123 private volatile Runnable taskToDo
;
124 private volatile String taskLabel
;
125 private boolean cycleDone
;
127 private PC
.PCFullStatus currentProject
;
129 class LoadstateDropTarget
implements DropTargetListener
131 public void dragEnter(DropTargetDragEvent e
) {}
132 public void dragOver(DropTargetDragEvent e
) {}
133 public void dragExit(DropTargetEvent e
) {}
134 public void dropActionChanged(DropTargetDragEvent e
) {}
136 public void drop(DropTargetDropEvent e
)
142 e
.acceptDrop(DnDConstants
.ACTION_COPY
);
144 for(DataFlavor f
: e
.getCurrentDataFlavors()) {
146 Transferable t
= e
.getTransferable();
147 Object d
= t
.getTransferData(f
);
148 if(f
.isMimeTypeEqual("text/uri-list") && d
.getClass() == String
.class) {
149 String url
= (String
)d
;
150 if(url
.indexOf(10) >= 0) {
151 callShowOptionDialog(window
, "Hey, only single file at time!",
152 "DnD error", JOptionPane
.YES_NO_OPTION
, JOptionPane
.WARNING_MESSAGE
, null,
153 new String
[]{"Dismiss"}, "Dismiss");
154 e
.dropComplete(false);
157 e
.dropComplete(handleURLDropped(url
));
160 } catch(Exception ex
) {
161 errorDialog(ex
, "Failed to get DnD data", null, "Dismiss");
162 e
.dropComplete(false);
166 for(DataFlavor f
: e
.getCurrentDataFlavors()) {
170 Transferable t
= e
.getTransferable();
171 Object d
= t
.getTransferData(f
);
172 System
.err
.println("Notice: Format #" + i
+ ":" + d
.getClass().getName() + "(" + f
+ ")");
173 } catch(Exception ex
) {
174 System
.err
.println("Notice: Format #" + i
+ ": <ERROR>(" + f
+ ")");
177 callShowOptionDialog(window
, "Can't recognize file to load from drop (debugging information dumped to console).",
178 "DnD error", JOptionPane
.YES_NO_OPTION
, JOptionPane
.WARNING_MESSAGE
, null,
179 new String
[]{"Dismiss"}, "Dismiss");
180 e
.dropComplete(false);
184 private boolean handleURLDropped(String url
)
186 if(!url
.startsWith("file:///")) {
187 callShowOptionDialog(window
, "Can't load remote resource.",
188 "DnD error", JOptionPane
.YES_NO_OPTION
, JOptionPane
.WARNING_MESSAGE
, null,
189 new String
[]{"Dismiss"}, "Dismiss");
192 url
= url
.substring(7);
193 setTask(new LoadStateTask(url
, LoadStateTask
.MODE_NORMAL
), LOADSTATE_LABEL
);
199 stopTime
= new long[] {-1, 0, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000,
200 5000000, 10000000, 20000000, 50000000, 100000000, 200000000, 500000000, 1000000000, 2000000000,
201 5000000000L, 10000000000L, 20000000000L, 50000000000L};
202 stopLabel
= new String
[] {"(unbounded)", "(singlestep)", "1µs", "2µs", "5µs", "10µs", "20µs", "50µs", "100µs",
203 "200µs", "500µs","1ms", "2ms", "5ms", "10ms", "20ms", "50ms", "100ms", "200ms", "500ms", "1s", "2s", "5s",
204 "10s", "20s", "50s"};
207 public boolean systemShutdown()
209 if(!running
|| pc
== null)
211 //We are running. Do the absolute minimum since we are running in very delicate context.
217 public void reconnect(PC pc
)
219 pcStopping(); //Do the equivalent effects.
222 dumpDialog
.clearDumps();
225 public void notifySizeChange(int w
, int h
)
230 SwingUtilities
.invokeLater(new Runnable() { public void run() {
232 Dimension d
= window
.getSize();
233 nativeWidth
= d
.width
;
234 nativeHeight
= d
.height
;
235 currentResolutionWidth
= w2
;
236 currentResolutionHeight
= h2
;
237 updateStatusBarEventThread();
241 public void notifyFrameReceived(int w
, int h
)
243 currentResolutionWidth
= w
;
244 currentResolutionHeight
= h
;
248 private void setTrapFlags()
250 pc
.getTraceTrap().setTrapFlags(trapFlags
);
253 public void pcStarting()
255 long profile
= PROFILE_HAVE_PC
| PROFILE_RUNNING
;
256 if(currentProject
!= null && currentProject
.events
!= null);
257 profile
|= PROFILE_EVENTS
;
258 if(pc
.getCDROMIndex() >= 0)
259 profile
|= PROFILE_CDROM
;
261 menuManager
.setProfile(profile
);
268 Clock sysClock
= (Clock
)pc
.getComponent(Clock
.class);
269 long current
= sysClock
.getTime();
270 if(imminentTrapTime
> 0) {
271 pc
.getTraceTrap().setTrapTime(current
+ imminentTrapTime
);
272 } else if(imminentTrapTime
== 0) {
273 //Hack: We set trace trap to trap immediately. It comes too late to abort next instruction, but
274 //early enough to abort one after that.
275 pc
.getTraceTrap().setTrapTime(current
);
277 if(currentProject
.events
!= null)
278 currentProject
.events
.setPCRunStatus(true);
281 public void pcStopping()
283 if(currentProject
.events
!= null)
284 currentProject
.events
.setPCRunStatus(false);
286 return; //Don't mess with UI when shutting down.
289 long profile
= PROFILE_STOPPED
;
291 profile
|= PROFILE_HAVE_PC
;
293 profile
|= PROFILE_NO_PC
;
294 if(currentProject
!= null && currentProject
.events
!= null);
295 profile
|= PROFILE_EVENTS
;
296 if(pc
.getCDROMIndex() >= 0)
297 profile
|= PROFILE_CDROM
;
299 menuManager
.setProfile(profile
);
304 } catch(Exception e
) {
305 errorDialog(e
, "Failed to update disk menus", null, "Dismiss");
309 pc
.getTraceTrap().clearTrapTime();
310 pc
.getTraceTrap().getAndClearTrapActive();
314 private String
diskNameByIdx(int idx
)
316 return pc
.getDisks().lookupDisk(idx
).getName();
319 private void updateDisks() throws Exception
321 for(String x
: disks
)
322 menuManager
.removeMenuItem(x
);
329 DiskImageSet imageSet
= pc
.getDisks();
330 int[] floppies
= imageSet
.diskIndicesByType(BlockDevice
.Type
.FLOPPY
);
331 int[] cdroms
= imageSet
.diskIndicesByType(BlockDevice
.Type
.CDROM
);
333 for(int i
= 0; i
< floppies
.length
; i
++) {
334 String name
= diskNameByIdx(floppies
[i
]);
335 menuManager
.addMenuItem("Drives→fda→" + name
, this, "menuChangeDisk", new Object
[]{new Integer(0),
336 new Integer(floppies
[i
])}, PROFILE_HAVE_PC
);
337 menuManager
.addMenuItem("Drives→fdb→" + name
, this, "menuChangeDisk", new Object
[]{new Integer(1),
338 new Integer(floppies
[i
])}, PROFILE_HAVE_PC
);
339 menuManager
.addSelectableMenuItem("Drives→Write Protect→" + name
, this, "menuWriteProtect",
340 new Object
[]{new Integer(floppies
[i
])}, imageSet
.lookupDisk(floppies
[i
]).isReadOnly(),
342 disks
.add("Drives→fda→" + name
);
343 disks
.add("Drives→fdb→" + name
);
344 disks
.add("Drives→Write Protect→" + name
);
347 for(int i
= 0; i
< cdroms
.length
; i
++) {
348 String name
= diskNameByIdx(cdroms
[i
]);
349 menuManager
.addMenuItem("Drives→CD-ROM→" + name
, this, "menuChangeDisk", new Object
[]{new Integer(1),
350 new Integer(cdroms
[i
])}, PROFILE_HAVE_PC
| PROFILE_CDROM
);
351 disks
.add("Drives→CD-ROM→" + name
);
355 private synchronized boolean setTask(Runnable task
, String label
)
357 boolean run
= running
;
358 if(run
|| taskToDo
!= null)
359 return false; //Can't do tasks with PC running or existing task.
369 boolean wasRunning
= false;
370 while(true) { //We will be killed by JVM.
371 //Wait for us to become runnable again.
372 while((!running
|| pc
== null) && taskToDo
== null) {
373 if(!running
&& wasRunning
&& pc
!= null)
375 wasRunning
= running
;
378 if((running
&& pc
!= null) || taskToDo
!= null)
385 } catch(Exception e
) {
389 if(running
&& !wasRunning
)
391 wasRunning
= running
;
393 if(taskToDo
!= null) {
402 if(pc
.getHitTraceTrap()) {
403 if(pc
.getAndClearTripleFaulted())
404 callShowOptionDialog(window
, "CPU shut itself down due to triple fault. Rebooting the system.",
405 "Triple fault!", JOptionPane
.YES_NO_OPTION
, JOptionPane
.WARNING_MESSAGE
, null,
406 new String
[]{"Dismiss"}, "Dismiss");
407 SwingUtilities
.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
411 } catch (Exception e
) {
414 errorDialog(e
, "Hardware emulator internal error", window
, "Dismiss");
416 SwingUtilities
.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
417 } catch (Exception f
) {
424 public void connectPC(PC pc
)
426 currentProject
.pc
= pc
;
427 vPluginManager
.reconnect(pc
);
431 private void startExternal()
433 if(pc
!= null && !running
)
434 if(!SwingUtilities
.isEventDispatchThread())
436 SwingUtilities
.invokeAndWait(new Thread() { public void run() { PCControl
.this.start(); }});
437 } catch(Exception e
) {
443 private void stopExternal()
445 if(pc
!= null && running
)
446 if(!SwingUtilities
.isEventDispatchThread())
448 SwingUtilities
.invokeAndWait(new Thread() { public void run() { PCControl
.this.stop(); }});
449 } catch(Exception e
) {
455 public boolean eci_state_save(String filename
)
457 return setTask(new SaveStateTask(filename
, false), SAVESTATE_LABEL
);
460 public boolean eci_state_dump(String filename
)
462 return setTask(new StatusDumpTask(filename
), STATUSDUMP_LABEL
);
465 public boolean eci_movie_save(String filename
)
467 return setTask(new SaveStateTask(filename
, true), SAVESTATE_LABEL
);
470 public boolean eci_state_load(String filename
)
472 return setTask(new LoadStateTask(filename
, LoadStateTask
.MODE_NORMAL
), LOADSTATE_LABEL
);
475 public boolean eci_state_load_noevents(String filename
)
477 return setTask(new LoadStateTask(filename
, LoadStateTask
.MODE_PRESERVE
), LOADSTATE_LABEL
);
480 public boolean eci_movie_load(String filename
)
482 return setTask(new LoadStateTask(filename
, LoadStateTask
.MODE_MOVIEONLY
), LOADSTATE_LABEL
);
485 public boolean eci_pc_assemble()
487 return setTask(new AssembleTask(), ASSEMBLE_LABEL
);
490 public boolean eci_ram_dump_text(String filename
)
492 return setTask(new RAMDumpTask(filename
, false), RAMDUMP_LABEL
);
495 public boolean eci_ram_dump_binary(String filename
)
497 return setTask(new RAMDumpTask(filename
, true), RAMDUMP_LABEL
);
500 public void eci_trap_vretrace_start_on()
502 trapFlags
|= TraceTrap
.TRACE_STOP_VRETRACE_START
;
505 public void eci_trap_vretrace_start_off()
507 trapFlags
&= ~TraceTrap
.TRACE_STOP_VRETRACE_START
;
510 public void eci_trap_vretrace_end_on()
512 trapFlags
|= TraceTrap
.TRACE_STOP_VRETRACE_END
;
515 public void eci_trap_vretrace_end_off()
517 trapFlags
&= ~TraceTrap
.TRACE_STOP_VRETRACE_END
;
520 public void eci_trap_timed_disable()
522 this.imminentTrapTime
= -1;
525 public void eci_trap_timed(Long time
)
527 this.imminentTrapTime
= time
.longValue();
530 public void eci_pc_start()
535 public void eci_pc_stop()
540 public void eci_pccontrol_setwinpos(Integer x
, Integer y
)
542 moveWindow(window
, x
.intValue(), y
.intValue(), nativeWidth
, nativeHeight
);
545 public void eci_sendevent(String clazz
, String
[] rargs
)
547 System
.err
.println("Event to: '" + clazz
+ "':");
548 for(int i
= 0; i
< rargs
.length
; i
++) {
549 System
.err
.println("rargs[" + i
+ "]: '" + rargs
[i
] + "'.");
551 if(currentProject
.events
!= null) {
553 Class
<?
extends HardwareComponent
> x
= Class
.forName(clazz
).asSubclass(HardwareComponent
.class);
554 currentProject
.events
.addEvent(0L, x
, rargs
);
555 } catch(Exception e
) {
556 System
.err
.println("Error adding event: " + e
.getMessage());
561 public void eci_sendevent_lowbound(Long timeMin
, String clazz
, String
[] rargs
)
563 System
.err
.println("Event to: '" + clazz
+ "' (with low bound of " + timeMin
+ "):");
564 for(int i
= 0; i
< rargs
.length
; i
++) {
565 System
.err
.println("rargs[" + i
+ "]: '" + rargs
[i
] + "'.");
567 if(currentProject
.events
!= null) {
569 Class
<?
extends HardwareComponent
> x
= Class
.forName(clazz
).asSubclass(HardwareComponent
.class);
570 currentProject
.events
.addEvent(timeMin
, x
, rargs
);
571 } catch(Exception e
) {
572 System
.err
.println("Error adding event: " + e
.getMessage());
577 public void eci_memory_read(Long address
, Integer size
)
579 if(currentProject
.pc
!= null) {
580 long addr
= address
.longValue();
581 long _size
= size
.intValue();
583 PhysicalAddressSpace addrSpace
;
584 if(addr
< 0 || addr
> 0xFFFFFFFFL
|| (_size
!= 1 && _size
!= 2 && _size
!= 4))
587 addrSpace
= (PhysicalAddressSpace
)currentProject
.pc
.getComponent(PhysicalAddressSpace
.class);
589 ret
= (long)addrSpace
.getByte((int)addr
) & 0xFF;
591 ret
= (long)addrSpace
.getWord((int)addr
) & 0xFFFF;
593 ret
= (long)addrSpace
.getDoubleWord((int)addr
) & 0xFFFFFFFFL
;
595 vPluginManager
.returnValue(ret
);
599 public void eci_memory_write(Long address
, Long value
, Integer size
)
601 if(currentProject
.pc
!= null) {
602 long addr
= address
.longValue();
603 long _size
= size
.intValue();
604 long _value
= value
.longValue();
605 PhysicalAddressSpace addrSpace
;
606 if(addr
< 0 || addr
> 0xFFFFFFFFL
|| (_size
!= 1 && _size
!= 2 && _size
!= 4))
609 addrSpace
= (PhysicalAddressSpace
)currentProject
.pc
.getComponent(PhysicalAddressSpace
.class);
611 addrSpace
.setByte((int)addr
, (byte)_value
);
613 addrSpace
.setWord((int)addr
, (short)_value
);
615 addrSpace
.setDoubleWord((int)addr
, (int)_value
);
619 public PCControl(Plugins manager
, String args
) throws Exception
623 UTFInputLineStream file
= null;
624 Map
<String
, String
> params
= parseStringToComponents(args
);
625 Set
<String
> used
= new HashSet
<String
>();
626 String extramenu
= params
.get("extramenu");
627 String uncompress
= params
.get("uncompressedsave");
628 if(uncompress
!= null)
629 uncompressedSave
= true;
630 if(extramenu
== null)
633 file
= new UTFInputLineStream(new FileInputStream(extramenu
));
636 boolean exists
= false;
637 String
[] line
= nextParseLine(file
);
640 if(line
.length
< 3 || line
[0].charAt(0) == '→') {
641 System
.err
.println("Warning: Bad extra menu item '" + line
[0] + "'.");
644 if(line
[0].length() == 0 || line
[0].charAt(line
[0].length() - 1) == '→') {
645 System
.err
.println("Warning: Bad extra menu item '" + line
[0] + "'.");
648 if(line
[0].indexOf("→→") >= 0) {
649 System
.err
.println("Warning: Bad extra menu item '" + line
[0] + "'.");
652 if(used
.contains(line
[0]))
655 KeyStroke stroke
= null;
656 if(!line
[1].equals("<>")) {
657 stroke
= KeyStroke
.getKeyStroke(line
[1]);
659 System
.err
.println("Warning: Bad keystroke '" + line
[1] + "'.");
664 String
[] lineCommand
= Arrays
.copyOfRange(line
, 2, line
.length
);
666 List
<String
[]> commandList
= extraActions
.get(line
[0]);
667 if(commandList
== null)
668 extraActions
.put(line
[0], commandList
= new ArrayList
<String
[]>());
669 commandList
.add(lineCommand
);
672 menuManager
.addMenuItem("Extra→" + line
[0], this, "menuExtra", new String
[]{line
[0]}, PROFILE_ALWAYS
,
676 } catch(IOException e
) {
677 errorDialog(e
, "Failed to load extra menu defintions", null, "dismiss");
681 window
.setJMenuBar(menuManager
.getMainBar());
684 public PCControl(Plugins manager
) throws Exception
686 window
= new JFrame("JPC-RR" + Misc
.emuname
);
688 if(DiskImage
.getLibrary() == null)
689 throw new Exception("PCControl plugin requires disk library");
692 shuttingDown
= false;
693 configDialog
= new PCConfigDialog();
694 dumpDialog
= new DumpControlDialog(manager
);
695 extraActions
= new HashMap
<String
, List
<String
[]> >();
696 menuManager
= new MenuManager();
698 menuManager
.setProfile(PROFILE_NO_PC
| PROFILE_STOPPED
);
700 menuManager
.addMenuItem("System→Assemble", this, "menuAssemble", null, PROFILE_STOPPED
);
701 menuManager
.addMenuItem("System→Start", this, "menuStart", null, PROFILE_STOPPED
| PROFILE_HAVE_PC
);
702 menuManager
.addMenuItem("System→Stop", this, "menuStop", null, PROFILE_RUNNING
);
703 menuManager
.addMenuItem("System→Reset", this, "menuReset", null, PROFILE_HAVE_PC
);
704 menuManager
.addMenuItem("System→Dumping control", this, "menuDump", null, PROFILE_HAVE_PC
| PROFILE_STOPPED
);
705 menuManager
.addMenuItem("System→Quit", this, "menuQuit", null, PROFILE_ALWAYS
);
706 menuManager
.addSelectableMenuItem("Breakpoints→Trap VRetrace Start", this, "menuVRetraceStart", null, false,
708 menuManager
.addSelectableMenuItem("Breakpoints→Trap VRetrace End", this, "menuVRetraceEnd", null, false,
710 menuManager
.addMenuItem("Snapshot→Change Run Authors", this, "menuChangeAuthors", null, PROFILE_HAVE_PC
);
711 menuManager
.addMenuItem("Snapshot→Save→Snapshot", this, "menuSave", new Object
[]{new Boolean(false)},
712 PROFILE_HAVE_PC
| PROFILE_STOPPED
);
713 menuManager
.addMenuItem("Snapshot→Save→Movie", this, "menuSave", new Object
[]{new Boolean(true)},
714 PROFILE_HAVE_PC
| PROFILE_STOPPED
);
715 menuManager
.addMenuItem("Snapshot→Save→Status Dump", this, "menuStatusDump", null,
716 PROFILE_HAVE_PC
| PROFILE_STOPPED
);
717 menuManager
.addMenuItem("Snapshot→Load→Snapshot", this, "menuLoad",
718 new Object
[]{new Integer(LoadStateTask
.MODE_NORMAL
)}, PROFILE_STOPPED
);
719 menuManager
.addMenuItem("Snapshot→Load→Snapshot (preserve events)", this, "menuLoad",
720 new Object
[]{new Integer(LoadStateTask
.MODE_PRESERVE
)}, PROFILE_STOPPED
| PROFILE_EVENTS
);
721 menuManager
.addMenuItem("Snapshot→Load→Movie", this, "menuLoad",
722 new Object
[]{new Integer(LoadStateTask
.MODE_MOVIEONLY
)}, PROFILE_STOPPED
);
723 menuManager
.addMenuItem("Snapshot→RAM Dump→Hexadecimal", this, "menuRAMDump", new Object
[]{new Boolean(false)},
724 PROFILE_HAVE_PC
| PROFILE_STOPPED
);
725 menuManager
.addMenuItem("Snapshot→RAM Dump→Binary", this, "menuRAMDump", new Object
[]{new Boolean(true)},
726 PROFILE_HAVE_PC
| PROFILE_STOPPED
);
727 menuManager
.addMenuItem("Snapshot→Truncate Event Stream", this, "menuTruncate", null,
728 PROFILE_STOPPED
| PROFILE_EVENTS
);
730 for(int i
= 0; i
< stopLabel
.length
; i
++) {
731 menuManager
.addSelectableMenuItem("Breakpoints→Timed Stops→" + stopLabel
[i
], this, "menuTimedStop",
732 null, (i
== 0), PROFILE_ALWAYS
);
734 imminentTrapTime
= -1;
736 menuManager
.addMenuItem("Drives→fda→<Empty>", this, "menuChangeDisk", new Object
[]{new Integer(0),
737 new Integer(-1)}, PROFILE_HAVE_PC
);
738 menuManager
.addMenuItem("Drives→fdb→<Empty>", this, "menuChangeDisk", new Object
[]{new Integer(1),
739 new Integer(-1)}, PROFILE_HAVE_PC
);
740 menuManager
.addMenuItem("Drives→CD-ROM→<Empty>", this, "menuChangeDisk", new Object
[]{new Integer(2),
741 new Integer(-1)}, PROFILE_HAVE_PC
| PROFILE_CDROM
);
742 menuManager
.addMenuItem("Drives→Add image", this, "menuAddDisk", null, PROFILE_HAVE_PC
);
743 menuManager
.addMenuItem("Drives→Import Image", this, "menuImport", null, PROFILE_ALWAYS
);
745 menuManager
.addMenuItem("Debug→Hacks→NO_FPU", this, "menuNOFPU", null, PROFILE_HAVE_PC
);
746 menuManager
.addMenuItem("Debug→Hacks→VGA_DRAW", this, "menuVGADRAW", null, PROFILE_HAVE_PC
);
747 menuManager
.addMenuItem("Debug→Hacks→VGA_SCROLL_2", this, "menuVGASCROLL2", null, PROFILE_HAVE_PC
);
749 disks
= new HashSet
<String
>();
750 currentProject
= new PC
.PCFullStatus();
752 this.vPluginManager
= manager
;
754 panel
= new PCMonitorPanel(this);
755 loadstateDropTarget
= new LoadstateDropTarget();
756 dropTarget
= new DropTarget(panel
.getMonitorPanel(), loadstateDropTarget
);
757 statusBar
= new JLabel("");
758 statusBar
.setBorder(new EtchedBorder(EtchedBorder
.LOWERED
));
759 manager
.addSlaveObject(this, panel
);
762 window
.getContentPane().add("Center", panel
.getMonitorPanel());
763 window
.getContentPane().add("South", statusBar
);
764 JMenuBar bar
= menuManager
.getMainBar();
765 for(JMenu menu
: panel
.getMenusNeeded())
767 window
.setJMenuBar(bar
);
770 window
.setDefaultCloseOperation(JFrame
.EXIT_ON_CLOSE
);
771 } catch (AccessControlException e
) {
772 System
.err
.println("Error: Not able to add some components to frame: " + e
.getMessage());
775 snapshotFileChooser
= new JFileChooser(System
.getProperty("user.dir"));
777 window
.getContentPane().validate();
780 Dimension d
= window
.getSize();
781 nativeWidth
= d
.width
;
782 nativeHeight
= d
.height
;
783 updateStatusBarEventThread();
784 window
.setVisible(true);
787 private void updateStatusBar()
789 if(vPluginManager
.isShuttingDown())
790 return; //Too much of deadlock risk.
791 SwingUtilities
.invokeLater(new Runnable() { public void run() { updateStatusBarEventThread(); }});
794 private void updateStatusBarEventThread()
797 if(currentProject
.pc
!= null && taskToDo
== null) {
798 long timeNow
= ((Clock
)currentProject
.pc
.getComponent(Clock
.class)).getTime();
799 long timeEnd
= currentProject
.events
.getLastEventTime();
800 text1
= " Time: " + (timeNow
/ 1000000) + "ms, movie length: " + (timeEnd
/ 1000000) + "ms";
801 if(currentResolutionWidth
> 0 && currentResolutionHeight
> 0)
802 text1
= text1
+ ", resolution: " + currentResolutionWidth
+ "*" + currentResolutionHeight
;
804 text1
= text1
+ ", resolution: <No valid signal>";
805 if(currentProject
.events
.isAtMovieEnd())
806 text1
= text1
+ " (At movie end)";
807 } else if(taskToDo
!= null)
810 text1
= " NO PC CONNECTED";
812 statusBar
.setText(text1
);
815 public void menuExtra(String i
, Object
[] args
)
817 final List
<String
[]> commandList
= extraActions
.get(args
[0]);
818 if(commandList
== null) {
819 System
.err
.println("Warning: Called extra menu with unknown entry '" + args
[0] + "'.");
823 //Run the functions on seperate thread to avoid deadlocking.
824 (new Thread(new Runnable() { public void run() { menuExtraThreadFunc(commandList
); }}, "Extra action thread")).start();
827 private void menuExtraThreadFunc(List
<String
[]> actions
)
829 for(String
[] i
: actions
) {
831 vPluginManager
.invokeExternalCommandSynchronous(i
[0], null);
833 String
[] rest
= Arrays
.copyOfRange(i
, 1, i
.length
, String
[].class);
834 vPluginManager
.invokeExternalCommandSynchronous(i
[0], rest
);
839 public void menuAssemble(String i
, Object
[] args
)
841 setTask(new AssembleTask(), ASSEMBLE_LABEL
);
844 public void menuStart(String i
, Object
[] args
)
849 public void menuStop(String i
, Object
[] args
)
854 public void menuReset(String i
, Object
[] args
)
859 public void menuDump(String i
, Object
[] args
)
861 setTask(new DumpControlTask(), DUMPCONTROL_LABEL
);
864 public void menuImport(String i
, Object
[] args
)
867 new ImportDiskImage();
868 } catch(Exception e
) {
873 public void menuNOFPU(String i
, Object
[] args
)
878 public void menuVGADRAW(String i
, Object
[] args
)
883 public void menuVGASCROLL2(String i
, Object
[] args
)
885 pc
.setVGAScroll2Hack();
888 public void menuQuit(String i
, Object
[] args
)
890 vPluginManager
.shutdownEmulator();
893 public void menuVRetraceStart(String i
, Object
[] args
)
895 trapFlags ^
= TraceTrap
.TRACE_STOP_VRETRACE_START
;
896 menuManager
.setSelected("Breakpoints→Trap VRetrace Start",
897 (trapFlags
& TraceTrap
.TRACE_STOP_VRETRACE_START
) == TraceTrap
.TRACE_STOP_VRETRACE_START
);
900 public void menuVRetraceEnd(String i
, Object
[] args
)
902 trapFlags ^
= TraceTrap
.TRACE_STOP_VRETRACE_END
;
903 menuManager
.setSelected("Breakpoints→Trap VRetrace End",
904 (trapFlags
& TraceTrap
.TRACE_STOP_VRETRACE_END
) == TraceTrap
.TRACE_STOP_VRETRACE_END
);
907 public void menuTimedStop(String i
, Object
[] args
)
909 for(int j
= 0; j
< stopLabel
.length
; j
++) {
910 String label
= "Breakpoints→Timed Stops→" + stopLabel
[j
];
911 if(i
.equals(label
)) {
912 this.imminentTrapTime
= stopTime
[j
];
913 menuManager
.select(label
);
915 menuManager
.unselect(label
);
919 public void menuSave(String i
, Object
[] args
)
921 setTask(new SaveStateTask(((Boolean
)args
[0]).booleanValue()), SAVESTATE_LABEL
);
924 public void menuStatusDump(String i
, Object
[] args
)
926 setTask(new StatusDumpTask(), STATUSDUMP_LABEL
);
929 public void menuLoad(String i
, Object
[] args
)
931 setTask(new LoadStateTask(((Integer
)args
[0]).intValue()), LOADSTATE_LABEL
);
934 public void menuRAMDump(String i
, Object
[] args
)
936 setTask(new RAMDumpTask(((Boolean
)args
[0]).booleanValue()), RAMDUMP_LABEL
);
939 public void menuTruncate(String i
, Object
[] args
)
941 currentProject
.events
.truncateEventStream();
944 public void menuChangeDisk(String i
, Object
[] args
)
946 changeFloppy(((Integer
)args
[0]).intValue(), ((Integer
)args
[1]).intValue());
949 public void menuWriteProtect(String i
, Object
[] args
)
951 int disk
= ((Integer
)args
[0]).intValue();
952 writeProtect(disk
, menuManager
.isSelected(i
));
953 DiskImageSet imageSet
= pc
.getDisks();
954 menuManager
.setSelected(i
, imageSet
.lookupDisk(disk
).isReadOnly());
957 public void menuAddDisk(String i
, Object
[] args
)
959 setTask(new AddDiskTask(), ADDDISK_LABEL
);
962 public void menuChangeAuthors(String i
, Object
[] args
)
964 setTask(new ChangeAuthorsTask(), CHANGEAUTHORS_LABEL
);
967 public synchronized void start()
971 vPluginManager
.pcStarted();
976 private String
prettyPrintTime(long ts
)
981 s
= s
+ "" + (ts
/ 1000000000) + " ";
983 s
= s
+ "" + (ts
% 1000000000 / 100000000);
985 s
= s
+ "" + (ts
% 100000000 / 10000000);
987 s
= s
+ "" + (ts
% 10000000 / 1000000) + " ";
989 s
= s
+ "" + (ts
% 1000000 / 100000);
991 s
= s
+ "" + (ts
% 100000 / 10000);
993 s
= s
+ "" + (ts
% 10000 / 1000) + " ";
995 s
= s
+ "" + (ts
% 1000 / 100);
997 s
= s
+ "" + (ts
% 100 / 10);
998 s
= s
+ "" + (ts
% 10);
1002 protected synchronized void stopNoWait()
1005 vPluginManager
.pcStopped();
1006 Clock sysClock
= (Clock
)pc
.getComponent(Clock
.class);
1007 System
.err
.println("Notice: PC emulation stopped (at time sequence value " +
1008 prettyPrintTime(sysClock
.getTime()) + ")");
1011 public synchronized void stop()
1013 pc
.getTraceTrap().doPotentialTrap(TraceTrap
.TRACE_STOP_IMMEDIATE
);
1014 System
.err
.println("Informational: Waiting for PC to halt...");
1017 public JScrollPane
getMonitorPane()
1022 protected void reset()
1027 public synchronized boolean isRunning()
1032 private void changeFloppy(int drive
, int image
)
1036 PC
.DiskChanger changer
= (PC
.DiskChanger
)pc
.getComponent(PC
.DiskChanger
.class);
1037 changer
.changeFloppyDisk(drive
, image
);
1038 } catch (Exception e
) {
1039 System
.err
.println("Error: Failed to change disk");
1040 errorDialog(e
, "Failed to change disk", null, "Dismiss");
1044 private void writeProtect(int image
, boolean state
)
1048 PC
.DiskChanger changer
= (PC
.DiskChanger
)pc
.getComponent(PC
.DiskChanger
.class);
1049 changer
.wpFloppyDisk(image
, state
);
1050 } catch (Exception e
) {
1051 System
.err
.println("Error: Failed to change floppy write protect");
1052 errorDialog(e
, "Failed to write (un)protect floppy", null, "Dismiss");
1056 private class LoadStateTask
extends AsyncGUITask
1062 private static final int MODE_NORMAL
= 1;
1063 private static final int MODE_PRESERVE
= 2;
1064 private static final int MODE_MOVIEONLY
= 3;
1066 public LoadStateTask(int mode
)
1068 oTime
= System
.currentTimeMillis();
1073 public LoadStateTask(String name
, int mode
)
1076 choosen
= new File(name
);
1079 protected void runPrepare()
1081 if(choosen
== null) {
1083 if(_mode
== MODE_PRESERVE
)
1084 returnVal
= snapshotFileChooser
.showDialog(window
, "LOAD JPC-RR Snapshot (PE)");
1085 else if(_mode
== MODE_MOVIEONLY
)
1086 returnVal
= snapshotFileChooser
.showDialog(window
, "LOAD JPC-RR Snapshot (MO)");
1088 returnVal
= snapshotFileChooser
.showDialog(window
, "LOAD JPC-RR Snapshot");
1089 choosen
= snapshotFileChooser
.getSelectedFile();
1096 protected void runFinish()
1101 if(caught
== null) {
1103 connectPC(pc
= currentProject
.pc
);
1105 System
.err
.println("Informational: Loadstate done");
1106 } catch(Exception e
) {
1110 if(caught
!= null) {
1111 errorDialog(caught
, "Load savestate failed", window
, "Dismiss");
1113 System
.err
.println("Total save time: " + (System
.currentTimeMillis() - oTime
) + "ms.");
1114 PCControl
.this.vPluginManager
.signalCommandCompletion();
1117 protected void runTask()
1123 System
.err
.println("Informational: Loading a snapshot of JPC-RR");
1124 long times1
= System
.currentTimeMillis();
1125 JRSRArchiveReader reader
= new JRSRArchiveReader(choosen
.getAbsolutePath());
1127 PC
.PCFullStatus fullStatus
= PC
.loadSavestate(reader
, _mode
== MODE_PRESERVE
, _mode
== MODE_MOVIEONLY
,
1130 currentProject
= fullStatus
;
1133 long times2
= System
.currentTimeMillis();
1134 System
.err
.println("Informational: Loadstate complete (" + (times2
- times1
) + "ms).");
1135 } catch(Exception e
) {
1141 private void doCycle(PC _pc
)
1143 final PC _xpc
= _pc
;
1145 (new Thread(new Runnable() { public void run() { synchronized(PCControl
.this) {
1146 _xpc
.getVideoOutput().holdOutput(); cycleDone
= true; PCControl
.this.notifyAll();}}}, "VGA output cycle thread")).start();
1149 synchronized(this) {
1154 } catch(Exception e
) {
1158 private class SaveStateTask
extends AsyncGUITask
1165 public SaveStateTask(boolean movie
)
1167 oTime
= System
.currentTimeMillis();
1172 public SaveStateTask(String name
, boolean movie
)
1175 choosen
= new File(name
);
1178 protected void runPrepare()
1180 if(choosen
== null) {
1181 int returnVal
= snapshotFileChooser
.showDialog(window
, movieOnly ?
"Save JPC-RR Movie" :
1182 "Save JPC-RR Snapshot");
1183 choosen
= snapshotFileChooser
.getSelectedFile();
1190 protected void runFinish()
1192 if(caught
!= null) {
1193 errorDialog(caught
, "Saving savestate failed", window
, "Dismiss");
1195 System
.err
.println("Total save time: " + (System
.currentTimeMillis() - oTime
) + "ms.");
1196 PCControl
.this.vPluginManager
.signalCommandCompletion();
1199 protected void runTask()
1204 JRSRArchiveWriter writer
= null;
1207 System
.err
.println("Informational: Savestating...");
1208 long times1
= System
.currentTimeMillis();
1209 writer
= new JRSRArchiveWriter(choosen
.getAbsolutePath());
1210 PC
.saveSavestate(writer
, currentProject
, movieOnly
, uncompressedSave
);
1211 renameFile(choosen
, new File(choosen
.getAbsolutePath() + ".backup"));
1213 long times2
= System
.currentTimeMillis();
1214 System
.err
.println("Informational: Savestate complete (" + (times2
- times1
) + "ms).");
1215 } catch(Exception e
) {
1217 try { writer
.rollback(); } catch(Exception f
) {}
1223 private class StatusDumpTask
extends AsyncGUITask
1228 public StatusDumpTask()
1233 public StatusDumpTask(String name
)
1236 choosen
= new File(name
);
1239 protected void runPrepare()
1241 if(choosen
== null) {
1242 int returnVal
= snapshotFileChooser
.showDialog(window
, "Save Status dump");
1243 choosen
= snapshotFileChooser
.getSelectedFile();
1250 protected void runFinish()
1252 if(caught
!= null) {
1253 errorDialog(caught
, "Status dump failed", window
, "Dismiss");
1255 PCControl
.this.vPluginManager
.signalCommandCompletion();
1258 protected void runTask()
1264 OutputStream outb
= new BufferedOutputStream(new FileOutputStream(choosen
));
1265 PrintStream out
= new PrintStream(outb
, false, "UTF-8");
1266 StatusDumper sd
= new StatusDumper(out
);
1270 System
.err
.println("Informational: Dumped " + sd
.dumpedObjects() + " objects");
1271 } catch(Exception e
) {
1277 private class RAMDumpTask
extends AsyncGUITask
1283 public RAMDumpTask(boolean binFlag
)
1289 public RAMDumpTask(String name
, boolean binFlag
)
1292 choosen
= new File(name
);
1295 protected void runPrepare()
1297 if(choosen
== null) {
1300 returnVal
= snapshotFileChooser
.showDialog(window
, "Save RAM dump");
1302 returnVal
= snapshotFileChooser
.showDialog(window
, "Save RAM hexdump");
1303 choosen
= snapshotFileChooser
.getSelectedFile();
1310 protected void runFinish()
1312 if(caught
!= null) {
1313 errorDialog(caught
, "RAM dump failed", window
, "Dismiss");
1315 PCControl
.this.vPluginManager
.signalCommandCompletion();
1318 protected void runTask()
1324 OutputStream outb
= new BufferedOutputStream(new FileOutputStream(choosen
));
1325 byte[] pagebuf
= new byte[4096];
1326 PhysicalAddressSpace addr
= (PhysicalAddressSpace
)pc
.getComponent(PhysicalAddressSpace
.class);
1327 int lowBound
= addr
.findFirstRAMPage(0);
1328 int firstUndumped
= 0;
1331 while(lowBound
>= 0) {
1332 for(; firstUndumped
< lowBound
; firstUndumped
++)
1333 dumpPage(outb
, firstUndumped
, null);
1334 addr
.readRAMPage(firstUndumped
++, pagebuf
);
1335 dumpPage(outb
, lowBound
, pagebuf
);
1337 highBound
= lowBound
+ 1;
1338 lowBound
= addr
.findFirstRAMPage(++lowBound
);
1341 System
.err
.println("Informational: Dumped machine RAM (" + highBound
+ " pages examined, " +
1342 present
+ " pages present).");
1343 } catch(Exception e
) {
1348 private byte charForHex(int hvalue
)
1351 return (byte)(hvalue
+ 48);
1352 else if(hvalue
> 9 && hvalue
< 16)
1353 return (byte)(hvalue
+ 55);
1355 System
.err
.println("Unknown hex value: " + hvalue
+ ".");
1359 private void dumpPage(OutputStream stream
, int pageNo
, byte[] buffer
) throws IOException
1362 pageNo
= pageNo
& 0xFFFFF; //Cut page numbers out of range.
1363 if(!binary
&& buffer
== null)
1364 return; //Don't dump null pages in non-binary mode.
1366 pageBufSize
= 4096; //Binary page buffer is 4096 bytes.
1368 pageBufSize
= 14592; //Hexdump page buffer is 14592 bytes.
1369 byte[] outputPage
= new byte[pageBufSize
];
1370 if(buffer
!= null && binary
) {
1371 System
.arraycopy(buffer
, 0, outputPage
, 0, 4096);
1372 } else if(buffer
!= null) { //Hex mode
1373 for(int i
= 0; i
< 256; i
++) {
1374 for(int j
= 0; j
< 57; j
++) {
1376 outputPage
[57 * i
+ j
] = charForHex((pageNo
>>> (4 * (4 - j
))) & 0xF);
1378 outputPage
[57 * i
+ j
] = charForHex(i
/ 16);
1380 outputPage
[57 * i
+ j
] = charForHex(i
% 16);
1382 outputPage
[57 * i
+ j
] = 48;
1384 outputPage
[57 * i
+ j
] = 10;
1386 outputPage
[57 * i
+ j
] = 32;
1388 outputPage
[57 * i
+ j
] = charForHex(((int)buffer
[16 * i
+ j
/ 3 - 3] & 0xFF) / 16);
1390 outputPage
[57 * i
+ j
] = charForHex(buffer
[16 * i
+ j
/ 3 - 3] & 0xF);
1392 System
.err
.println("Error: dumpPage: unhandled j = " + j
+ ".");
1396 stream
.write(outputPage
);
1400 private class AssembleTask
extends AsyncGUITask
1405 public AssembleTask()
1410 protected void runPrepare()
1413 configDialog
.popUp();
1414 } catch(Exception e
) {
1419 protected void runFinish()
1421 if(caught
== null && !canceled
) {
1423 currentProject
.projectID
= randomHexes(24);
1424 currentProject
.rerecords
= 0;
1425 currentProject
.events
= new EventRecorder();
1426 currentProject
.events
.attach(pc
, null);
1427 currentProject
.savestateID
= null;
1428 currentProject
.extraHeaders
= null;
1429 currentProject
.events
.setRerecordCount(0);
1430 currentProject
.events
.setHeaders(currentProject
.extraHeaders
);
1432 } catch(Exception e
) {
1436 if(caught
!= null) {
1437 errorDialog(caught
, "PC Assembly failed", window
, "Dismiss");
1439 PCControl
.this.vPluginManager
.signalCommandCompletion();
1442 protected void runTask()
1446 PC
.PCHardwareInfo hw
= configDialog
.waitClose();
1453 pc
= PC
.createPC(hw
);
1454 } catch(Exception e
) {
1460 private class DumpControlTask
extends AsyncGUITask
1465 public DumpControlTask()
1469 protected void runPrepare()
1472 dumpDialog
.popUp(PCControl
.this.pc
);
1473 } catch(Exception e
) {
1478 protected void runFinish()
1480 if(caught
!= null) {
1481 errorDialog(caught
, "Opening dump control dialog failed", window
, "Dismiss");
1485 protected void runTask()
1489 dumpDialog
.waitClose();
1493 private class AddDiskTask
extends AsyncGUITask
1498 public AddDiskTask()
1500 dd
= new NewDiskDialog();
1503 protected void runPrepare()
1507 protected void runFinish()
1509 if(caught
!= null) {
1510 errorDialog(caught
, "Adding disk failed", window
, "Dismiss");
1514 } catch(Exception e
) {
1515 errorDialog(e
, "Failed to update disk menus", null, "Dismiss");
1517 PCControl
.this.vPluginManager
.signalCommandCompletion();
1520 protected void runTask()
1522 NewDiskDialog
.Response res
= dd
.waitClose();
1528 pc
.getDisks().addDisk(img
= new DiskImage(res
.diskFile
, false));
1529 img
.setName(res
.diskName
);
1530 } catch(Exception e
) {
1536 private class ChangeAuthorsTask
extends AsyncGUITask
1541 public ChangeAuthorsTask()
1545 AuthorsDialog
.AuthorElement
[] authorNames
= null;
1546 if(currentProject
!= null)
1547 authorNames
= AuthorsDialog
.readAuthorsFromHeaders(currentProject
.extraHeaders
);
1549 ad
= new AuthorsDialog(authorNames
);
1552 protected void runPrepare()
1556 protected void runFinish()
1558 if(caught
!= null) {
1559 errorDialog(caught
, "Changing authors failed", window
, "Dismiss");
1561 PCControl
.this.vPluginManager
.signalCommandCompletion();
1564 protected void runTask()
1566 AuthorsDialog
.Response res
= ad
.waitClose();
1571 currentProject
.extraHeaders
= AuthorsDialog
.rewriteHeaderAuthors(currentProject
.extraHeaders
,
1573 currentProject
.events
.setHeaders(currentProject
.extraHeaders
);
1574 } catch(Exception e
) {