2 JPC-RR: A x86 PC Hardware Emulator
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
;
35 import java
.math
.BigInteger
;
36 import java
.lang
.reflect
.*;
37 import java
.security
.AccessControlException
;
38 import java
.awt
.GridLayout
;
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
;
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
;
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
)
165 e
.acceptDrop(DnDConstants
.ACTION_COPY
);
167 for(DataFlavor f
: e
.getCurrentDataFlavors()) {
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);
180 e
.dropComplete(handleURLDropped(url
));
183 } catch(Exception ex
) {
184 errorDialog(ex
, "Failed to get DnD data", null, "Dismiss");
185 e
.dropComplete(false);
189 for(DataFlavor f
: e
.getCurrentDataFlavors()) {
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");
215 url
= url
.substring(7);
216 setTask(new LoadStateTask(url
, LoadStateTask
.MODE_NORMAL
), LOADSTATE_LABEL
);
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)
234 //We are running. Do the absolute minimum since we are running in very delicate context.
241 public void reconnect(PC pc
)
244 pcStopping(); //Do the equivalent effects.
249 public void notifySizeChange(int w
, int h
)
254 SwingUtilities
.invokeLater(new Runnable() { public void run() {
256 Dimension d
= window
.getSize();
257 nativeWidth
= d
.width
;
258 nativeHeight
= d
.height
;
259 currentResolutionWidth
= w2
;
260 currentResolutionHeight
= h2
;
261 updateStatusBarEventThread();
265 public void notifyFrameReceived(int w
, int h
)
267 currentResolutionWidth
= w
;
268 currentResolutionHeight
= h
;
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
);
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);
310 return; //Don't mess with UI when shutting down.
313 profile
= PROFILE_STOPPED
| (profile
& (PROFILE_DUMPING
| PROFILE_NOT_DUMPING
));
315 profile
|= PROFILE_HAVE_PC
;
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
);
328 } catch(Exception e
) {
329 errorDialog(e
, "Failed to update disk menus", null, "Dismiss");
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
);
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(),
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
);
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.
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)
416 wasRunning
= running
;
419 if((running
&& pc
!= null) || taskToDo
!= null)
426 } catch(Exception e
) {
430 if(running
&& !wasRunning
)
432 wasRunning
= running
;
434 if(taskToDo
!= null) {
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");
451 SwingUtilities
.invokeAndWait(new Thread() { public void run() { stopNoWait(); }});
455 } catch (Exception e
) {
458 errorDialog(e
, "Hardware emulator internal error", window
, "Dismiss");
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
);
479 private void startExternal()
481 if(pc
!= null && !running
)
482 if(!SwingUtilities
.isEventDispatchThread())
484 SwingUtilities
.invokeAndWait(new Thread() { public void run() { PCControl
.this.start(); }});
485 } catch(Exception e
) {
491 private void stopExternal()
493 if(pc
!= null && running
)
494 if(!SwingUtilities
.isEventDispatchThread())
496 SwingUtilities
.invokeAndWait(new Thread() { public void run() { PCControl
.this.stop(); }});
497 } catch(Exception e
) {
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
),
530 public boolean eci_state_load_noevents(String filename
)
532 return setTask(new LoadStateTask(projectIDMangleFileName(filename
), LoadStateTask
.MODE_PRESERVE
),
536 public boolean eci_movie_load(String filename
)
538 return setTask(new LoadStateTask(projectIDMangleFileName(filename
), LoadStateTask
.MODE_MOVIEONLY
),
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()
609 public void eci_pc_stop()
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) {
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) {
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();
657 PhysicalAddressSpace addrSpace
;
658 if(addr
< 0 || addr
> 0xFFFFFFFFL
|| (_size
!= 1 && _size
!= 2 && _size
!= 4))
661 addrSpace
= (PhysicalAddressSpace
)currentProject
.pc
.getComponent(PhysicalAddressSpace
.class);
663 ret
= (long)addrSpace
.getByte((int)addr
) & 0xFF;
665 ret
= (long)addrSpace
.getWord((int)addr
) & 0xFFFF;
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))
683 addrSpace
= (PhysicalAddressSpace
)currentProject
.pc
.getComponent(PhysicalAddressSpace
.class);
685 addrSpace
.setByte((int)addr
, (byte)_value
);
687 addrSpace
.setWord((int)addr
, (short)_value
);
689 addrSpace
.setDoubleWord((int)addr
, (int)_value
);
693 public PCControl(Plugins manager
, String args
) throws Exception
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)
707 file
= new UTFInputLineStream(new FileInputStream(extramenu
));
710 boolean exists
= false;
711 String
[] line
= nextParseLine(file
);
714 if(line
.length
< 3 || line
[0].charAt(0) == '→') {
715 System
.err
.println("Warning: Bad extra menu item '" + line
[0] + "'.");
718 if(line
[0].length() == 0 || line
[0].charAt(line
[0].length() - 1) == '→') {
719 System
.err
.println("Warning: Bad extra menu item '" + line
[0] + "'.");
722 if(line
[0].indexOf("→→") >= 0) {
723 System
.err
.println("Warning: Bad extra menu item '" + line
[0] + "'.");
726 if(used
.contains(line
[0]))
729 KeyStroke stroke
= null;
730 if(!line
[1].equals("<>")) {
731 stroke
= KeyStroke
.getKeyStroke(line
[1]);
733 System
.err
.println("Warning: Bad keystroke '" + line
[1] + "'.");
738 String
[] lineCommand
= Arrays
.copyOfRange(line
, 2, line
.length
);
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
);
746 menuManager
.addMenuItem("Extra→" + line
[0], this, "menuExtra", new String
[]{line
[0]}, PROFILE_ALWAYS
,
750 } catch(IOException e
) {
751 errorDialog(e
, "Failed to load extra menu defintions", null, "dismiss");
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");
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,
788 menuManager
.addSelectableMenuItem("Breakpoints→Trap VRetrace End", this, "menuVRetraceEnd", null, false,
790 menuManager
.addSelectableMenuItem("Breakpoints→Trap BIOS Keyboard", this, "menuBIOSKbd", null, false,
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();
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
);
853 window
.getContentPane().add("Center", panel
.getMonitorPanel());
854 window
.getContentPane().add("South", statusBar
);
855 JMenuBar bar
= menuManager
.getMainBar();
856 for(JMenu menu
: panel
.getMenusNeeded())
858 window
.setJMenuBar(bar
);
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();
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
) == '_')
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)
905 debugInClass
.put(name
, clazz
);
906 debugState
.put(name
, false);
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
)
926 debugInClass
.get(name
).getDeclaredMethod(name
, boolean.class).invoke(pc
.getComponent(
927 debugInClass
.get(name
)), debugState
.get(name
));
928 } catch(Exception e
) {
933 private void setDebugOptions()
935 for(Map
.Entry
<String
, Class
<?
>> opt
: debugInClass
.entrySet())
936 setDebugOption(opt
.getKey());
939 private void updateDebug()
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_"))
948 if(p
.length
!= 1 || p
[0] != boolean.class)
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()
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
;
977 text1
= text1
+ ", resolution: <No valid signal>";
978 if(currentProject
.events
.isAtMovieEnd())
979 text1
= text1
+ " (At movie end)";
980 } else if(taskToDo
!= null)
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
>();
1000 statusWindow
.setVisible(true);
1003 private void updateStatusMessages()
1005 Map
<String
, String
> values
= new HashMap
<String
, String
>();
1006 boolean altered
= false;
1009 for(HardwareComponent c
: pc
.allComponents()) {
1010 Class
<?
> cl
= c
.getClass();
1011 for(Field m
: cl
.getDeclaredFields()) {
1012 if(!m
.getName().startsWith("STATUS_"))
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
);
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
);
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] + "'.");
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
) {
1070 vPluginManager
.invokeExternalCommandSynchronous(i
[0], null);
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
)
1088 public void menuStartDump(String i
, Object
[] args
)
1090 int returnVal
= otherFileChooser
.showDialog(window
, "Dump to file");
1093 File choosen
= otherFileChooser
.getSelectedFile();
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");
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
)
1120 public void menuReset(String i
, Object
[] args
)
1125 public void menuImport(String i
, Object
[] args
)
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);
1148 callShowOptionDialog(window
, "Can't get current framerate!", "Error", JOptionPane
.YES_NO_OPTION
,
1149 JOptionPane
.WARNING_MESSAGE
, null, new String
[]{"Dismiss"}, "Dismiss");
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);
1160 callShowOptionDialog(window
, "Can't get current CTRC registers!", "Error", JOptionPane
.YES_NO_OPTION
,
1161 JOptionPane
.WARNING_MESSAGE
, null, new String
[]{"Dismiss"}, "Dismiss");
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
);
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)
1263 vPluginManager
.pcStarted();
1268 private String
prettyPrintTime(long ts
)
1272 if(ts
>= 1000000000)
1273 s
= s
+ "" + (ts
/ 1000000000) + " ";
1275 s
= s
+ "" + (ts
% 1000000000 / 100000000);
1277 s
= s
+ "" + (ts
% 100000000 / 10000000);
1279 s
= s
+ "" + (ts
% 10000000 / 1000000) + " ";
1281 s
= s
+ "" + (ts
% 1000000 / 100000);
1283 s
= s
+ "" + (ts
% 100000 / 10000);
1285 s
= s
+ "" + (ts
% 10000 / 1000) + " ";
1287 s
= s
+ "" + (ts
% 1000 / 100);
1289 s
= s
+ "" + (ts
% 100 / 10);
1290 s
= s
+ "" + (ts
% 10);
1294 protected synchronized void stopNoWait()
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()
1314 protected void reset()
1319 public synchronized boolean isRunning()
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())
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,
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
) {
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
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();
1395 public LoadStateTask(String name
, int mode
)
1398 chosen
= new File(name
);
1401 protected void runPrepare()
1403 if(chosen
== null) {
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)");
1410 returnVal
= snapshotFileChooser
.showDialog(window
, "LOAD JPC-RR Snapshot");
1411 chosen
= snapshotFileChooser
.getSelectedFile();
1418 protected void runFinish()
1423 if(caught
== null) {
1425 connectPC(pc
= currentProject
.pc
);
1427 System
.err
.println("Informational: Loadstate done on "+chosen
.getAbsolutePath());
1428 } catch(Exception 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()
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
;
1462 long times2
= System
.currentTimeMillis();
1463 System
.err
.println("Informational: Loadstate complete (" + (times2
- times1
) + "ms).");
1464 } catch(Exception e
) {
1470 private synchronized void doCycleDedicatedThread(PC _pc
)
1476 DisplayController dc
= (DisplayController
)_pc
.getComponent(DisplayController
.class);
1477 dc
.getOutputDevice().holdOutput(_pc
.getTime());
1482 private void doCycle(PC _pc
)
1484 final PC _xpc
= _pc
;
1486 (new Thread(new Runnable() { public void run() { doCycleDedicatedThread(_xpc
); }}, "VGA output cycle thread")).start();
1489 synchronized(this) {
1494 } catch(Exception e
) {
1498 private class SaveStateTask
extends AsyncGUITask
1505 public SaveStateTask(boolean movie
)
1507 oTime
= System
.currentTimeMillis();
1512 public SaveStateTask(String name
, boolean 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();
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()
1544 JRSRArchiveWriter writer
= null;
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"));
1553 long times2
= System
.currentTimeMillis();
1554 System
.err
.println("Informational: Savestate complete (" + (times2
- times1
) + "ms). on"+chosen
.getAbsolutePath());
1555 } catch(Exception e
) {
1557 try { writer
.rollback(); } catch(Exception f
) {}
1563 private class StatusDumpTask
extends AsyncGUITask
1568 public StatusDumpTask()
1573 public StatusDumpTask(String name
)
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();
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()
1604 OutputStream outb
= new BufferedOutputStream(new FileOutputStream(chosen
));
1605 PrintStream out
= new PrintStream(outb
, false, "UTF-8");
1606 StatusDumper sd
= new StatusDumper(out
);
1610 System
.err
.println("Informational: Dumped " + sd
.dumpedObjects() + " objects");
1611 } catch(Exception e
) {
1617 private class RAMDumpTask
extends AsyncGUITask
1623 public RAMDumpTask(boolean binFlag
)
1629 public RAMDumpTask(String name
, boolean binFlag
)
1632 chosen
= new File(name
);
1635 protected void runPrepare()
1637 if(chosen
== null) {
1640 returnVal
= otherFileChooser
.showDialog(window
, "Save RAM dump");
1642 returnVal
= otherFileChooser
.showDialog(window
, "Save RAM hexdump");
1643 chosen
= otherFileChooser
.getSelectedFile();
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()
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;
1671 while(lowBound
>= 0) {
1672 for(; firstUndumped
< lowBound
; firstUndumped
++)
1673 dumpPage(outb
, firstUndumped
, null);
1674 addr
.readRAMPage(firstUndumped
++, pagebuf
);
1675 dumpPage(outb
, lowBound
, pagebuf
);
1677 highBound
= lowBound
+ 1;
1678 lowBound
= addr
.findFirstRAMPage(++lowBound
);
1681 System
.err
.println("Informational: Dumped machine RAM (" + highBound
+ " pages examined, " +
1682 present
+ " pages present).");
1683 } catch(Exception e
) {
1688 private byte charForHex(int hvalue
)
1691 return (byte)(hvalue
+ 48);
1692 else if(hvalue
> 9 && hvalue
< 16)
1693 return (byte)(hvalue
+ 55);
1695 System
.err
.println("Unknown hex value: " + hvalue
+ ".");
1699 private void dumpPage(OutputStream stream
, int pageNo
, byte[] buffer
) throws IOException
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.
1706 pageBufSize
= 4096; //Binary page buffer is 4096 bytes.
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
++) {
1716 outputPage
[57 * i
+ j
] = charForHex((pageNo
>>> (4 * (4 - j
))) & 0xF);
1718 outputPage
[57 * i
+ j
] = charForHex(i
/ 16);
1720 outputPage
[57 * i
+ j
] = charForHex(i
% 16);
1722 outputPage
[57 * i
+ j
] = 48;
1724 outputPage
[57 * i
+ j
] = 10;
1726 outputPage
[57 * i
+ j
] = 32;
1728 outputPage
[57 * i
+ j
] = charForHex(((int)buffer
[16 * i
+ j
/ 3 - 3] & 0xFF) / 16);
1730 outputPage
[57 * i
+ j
] = charForHex(buffer
[16 * i
+ j
/ 3 - 3] & 0xF);
1732 System
.err
.println("Error: dumpPage: unhandled j = " + j
+ ".");
1736 stream
.write(outputPage
);
1740 private class ImageDumpTask
extends AsyncGUITask
1746 public ImageDumpTask(int _index
)
1752 public ImageDumpTask(String name
, int index
)
1755 chosen
= new File(name
);
1758 protected void runPrepare()
1760 if(chosen
== null) {
1762 returnVal
= otherFileChooser
.showDialog(window
, "Save Image dump");
1763 chosen
= otherFileChooser
.getSelectedFile();
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()
1786 dev
= pc
.getDrives().getHardDrive(-1 - index
).getImage();
1788 dev
= pc
.getDisks().lookupDisk(index
);
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);
1799 System
.err
.println("Informational: Dumped disk image (" + sectors
+ " sectors).");
1800 } catch(Exception e
) {
1806 private class AssembleTask
extends AsyncGUITask
1811 public AssembleTask()
1816 protected void runPrepare()
1819 configDialog
.popUp();
1820 } catch(Exception e
) {
1825 protected void runFinish()
1827 if(caught
== null && !canceled
) {
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
);
1839 } catch(Exception e
) {
1843 if(caught
!= null) {
1844 errorDialog(caught
, "PC Assembly failed", window
, "Dismiss");
1846 PCControl
.this.vPluginManager
.signalCommandCompletion();
1849 protected void runTask()
1853 PC
.PCHardwareInfo hw
= configDialog
.waitClose();
1860 pc
= PC
.createPC(hw
);
1861 } catch(Exception e
) {
1867 private class AddDiskTask
extends AsyncGUITask
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");
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();
1902 pc
.getDisks().addDisk(img
= new DiskImage(res
.diskFile
, false));
1903 img
.setName(res
.diskName
);
1904 } catch(Exception e
) {
1910 private class ChangeAuthorsTask
extends AsyncGUITask
1915 public ChangeAuthorsTask()
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();
1947 currentProject
.extraHeaders
= AuthorsDialog
.rewriteHeaderAuthors(currentProject
.extraHeaders
,
1948 res
.authors
, res
.gameName
);
1949 currentProject
.events
.setHeaders(currentProject
.extraHeaders
);
1950 } catch(Exception e
) {