2 JPC-RR: A x86 PC Hardware Emulator
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009 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
.pluginsaux
;
32 import org
.jpc
.pluginsaux
.ConstantTableLayout
;
33 import static org
.jpc
.Misc
.errorDialog
;
34 import org
.jpc
.diskimages
.TreeDirectoryFile
;
35 import org
.jpc
.diskimages
.ImageMaker
;
36 import org
.jpc
.diskimages
.ImageLibrary
;
37 import org
.jpc
.diskimages
.DiskImage
;
38 import org
.jpc
.diskimages
.RawDiskImage
;
39 import org
.jpc
.diskimages
.TreeRawDiskImage
;
40 import org
.jpc
.diskimages
.FileRawDiskImage
;
41 import static org
.jpc
.Misc
.tempname
;
42 import static org
.jpc
.Misc
.callShowOptionDialog
;
46 import java
.awt
.event
.*;
50 public class ImportDiskImage
implements ActionListener
, KeyListener
52 private JFrame window
;
54 private JLabel imageNameLabel
;
55 private JTextField imageName
;
56 private JLabel imageFileLabel
;
57 private JLabel imageTypeLabel
;
58 private JTextField imageFile
;
59 private JComboBox imageType
;
60 private JLabel feedback
;
61 private JButton importDisk
;
62 private JButton cancel
;
63 private JCheckBox stdGeometry
;
64 private JCheckBox doublesided
;
65 private JLabel sidesLabel
;
66 private JTextField sides
;
67 private JLabel sidesFixed
;
68 private JLabel sectorsLabel
;
69 private JTextField sectors
;
70 private JLabel sectorsFixed
;
71 private JLabel tracksLabel
;
72 private JTextField tracks
;
73 private JLabel tracksFixed
;
74 private JCheckBox volumeLabelLabel
;
75 private JTextField volumeLabel
;
76 private JCheckBox createTimeLabel
;
77 private JTextField createTime
;
78 private static String NOT_VALID
= "<No valid choice>";
79 private static String FLOPPY
= "Floppy Disk";
80 private static String HDD
= "Hard Disk";
81 private static String CDROM
= "CD-ROM Disk";
82 private static String BIOS
= "BIOS image";
84 private boolean fileSelected
;
85 private long fileSelectedLength
;
86 private boolean directorySelected
;
87 private boolean constructed
;
88 private long fileCase
;
90 public ImportDiskImage()
93 fileCase
= -1; //Initial.
94 window
= new JFrame("Import Disk Image");
95 ConstantTableLayout layout
= new ConstantTableLayout();
96 panel
= new JPanel(layout
);
99 add(imageNameLabel
= new JLabel("New image name"), 0, 0, 1, 1);
100 add(imageName
= new JTextField("", 50), 1, 0, 1, 1);
101 imageName
.addKeyListener(this);
103 add(imageFileLabel
= new JLabel("Image file/directory"), 0, 1, 1, 1);
104 add(imageFile
= new JTextField("", 50), 1, 1, 1, 1);
105 imageFile
.addKeyListener(this);
107 add(imageTypeLabel
= new JLabel("Image Type"), 0, 2, 1, 1);
108 add(imageType
= new JComboBox(), 1, 2, 1, 1);
109 imageType
.addActionListener(this);
110 setNoValidChoice(imageType
);
112 add(stdGeometry
= new JCheckBox("Standard geometry"), 0, 3, 1, 1);
113 stdGeometry
.setEnabled(false);
114 stdGeometry
.addActionListener(this);
116 add(doublesided
= new JCheckBox("Double-sided"), 1, 3, 1, 1);
117 doublesided
.setEnabled(false);
118 doublesided
.addActionListener(this);
120 add(sidesLabel
= new JLabel("Sides"), 0, 4, 1, 1);
121 add(sides
= new JTextField("16", 50), 1, 4, 1, 1);
122 add(sidesFixed
= new JLabel("N/A"), 1, 4, 1, 1);
123 sides
.setVisible(false);
124 sides
.addKeyListener(this);
126 add(sectorsLabel
= new JLabel("Sectors"), 0, 5, 1, 1);
127 add(sectors
= new JTextField("63", 50), 1, 5, 1, 1);
128 add(sectorsFixed
= new JLabel("N/A"), 1, 5, 1, 1);
129 sectors
.setVisible(false);
130 sectors
.addKeyListener(this);
132 add(tracksLabel
= new JLabel("Tracks"), 0, 6, 1, 1);
133 add(tracks
= new JTextField("16", 50), 1, 6, 1, 1);
134 add(tracksFixed
= new JLabel("N/A"), 1, 6, 1, 1);
135 tracks
.setVisible(false);
136 tracks
.addKeyListener(this);
138 add(volumeLabelLabel
= new JCheckBox("Volume label"), 0, 7, 1, 1);
139 add(volumeLabel
= new JTextField("", 50), 1, 7, 1, 1);
140 volumeLabel
.setEnabled(false);
141 volumeLabelLabel
.setEnabled(false);
142 volumeLabelLabel
.addActionListener(this);
143 volumeLabel
.addKeyListener(this);
145 add(createTimeLabel
= new JCheckBox("File timestamps"), 0, 8, 1, 1);
146 add(createTime
= new JTextField("19900101000000", 50), 1, 8, 1, 1);
147 createTime
.setEnabled(false);
148 createTimeLabel
.setEnabled(false);
149 createTimeLabel
.addActionListener(this);
150 createTime
.addKeyListener(this);
152 add(importDisk
= new JButton("Import"), 0, height
, 1, 1);
153 add(cancel
= new JButton("Cancel"), 1, height
, 1, 1);
154 importDisk
.setActionCommand("IMPORT");
155 importDisk
.addActionListener(this);
156 importDisk
.setEnabled(false);
157 cancel
.setActionCommand("CANCEL");
158 cancel
.addActionListener(this);
160 add(feedback
= new JLabel(""), 0, height
+ 1, 2, 1);
167 window
.setDefaultCloseOperation(JFrame
.DISPOSE_ON_CLOSE
);
168 window
.setVisible(true);
171 private void setNoValidChoice(JComboBox box
)
173 box
.removeAllItems();
174 box
.addItem(NOT_VALID
);
175 box
.setSelectedItem(NOT_VALID
);
176 box
.setEnabled(false);
179 private void add(JComponent component
, int x
, int y
, int w
, int h
)
181 ConstantTableLayout
.Placement c
= new ConstantTableLayout
.Placement(x
, y
, w
, h
);
182 panel
.add(component
, c
);
185 private int checkVolumeLabel(String text
)
187 if(text
.length() > 11)
189 for(int i
= 0; i
< text
.length(); i
++) {
190 char c
= text
.charAt(i
);
191 if(c
== 34 || c
== 124)
193 if(c
>= 33 && c
<= 41)
197 if(c
>= 48 && c
<= 57)
199 if(c
>= 64 && c
<= 90)
201 if(c
>= 94 && c
<= 126)
203 if(c
>= 160 && c
<= 255)
210 private boolean checkTimeStamp(String text
)
213 TreeDirectoryFile
.dosFormatTimeStamp(text
);
215 } catch(Exception e
) {
220 private void revalidateForm()
222 if("".equals(imageName
.getText())) {
223 feedback
.setText("No image name specified");
224 importDisk
.setEnabled(false);
227 if(imageName
.getText().startsWith("/")) {
228 feedback
.setText("Image name can't start with '/'");
229 importDisk
.setEnabled(false);
232 if(imageName
.getText().indexOf("//") >= 0) {
233 feedback
.setText("Image name can't have '//'");
234 importDisk
.setEnabled(false);
237 if(imageName
.getText().lastIndexOf("/") == imageName
.getText().length() - 1) {
238 feedback
.setText("Image name can't end in '/'");
239 importDisk
.setEnabled(false);
242 if("".equals(imageFile
.getText())) {
243 feedback
.setText("No image file specified");
244 importDisk
.setEnabled(false);
247 String _filename
= imageFile
.getText();
248 File fileObject
= new File(_filename
);
249 if(!fileObject
.exists()) {
250 feedback
.setText("Image file does not exist");
251 importDisk
.setEnabled(false);
254 if(!fileObject
.isFile() && !fileObject
.isDirectory()) {
255 feedback
.setText("Special devices not allowed as image files");
256 importDisk
.setEnabled(false);
260 int sides
= getSides();
261 int sectors
= getSectors();
262 int tracks
= getTracks();
263 String type
= (String
)imageType
.getSelectedItem();
264 if(FLOPPY
.equals(type
)) {
265 if(sides
< 1 || sides
> 2) {
266 feedback
.setText("Sides out of range (1-2)");
267 importDisk
.setEnabled(false);
270 if(sectors
< 1 || sectors
> 255) {
271 feedback
.setText("Sectors out of range (1-255)");
272 importDisk
.setEnabled(false);
275 if(tracks
< 1 || tracks
> 256) {
276 feedback
.setText("Tracks out of range (1-256)");
277 importDisk
.setEnabled(false);
280 } else if(HDD
.equals(type
)) {
281 if(sides
< 1 || sides
> 16) {
282 feedback
.setText("Sides out of range (1-16)");
283 importDisk
.setEnabled(false);
286 if(sectors
< 1 || sectors
> 63) {
287 feedback
.setText("Sectors out of range (1-63)");
288 importDisk
.setEnabled(false);
291 if(tracks
< 1 || tracks
> 1024) {
292 feedback
.setText("Tracks out of range (1-1024)");
293 importDisk
.setEnabled(false);
296 } else if(CDROM
.equals(type
)) {
297 } else if(BIOS
.equals(type
)) {
299 feedback
.setText("Bad image type");
300 importDisk
.setEnabled(false);
303 if(volumeLabelLabel
.isEnabled() && volumeLabelLabel
.isSelected()) {
305 res
= checkVolumeLabel(volumeLabel
.getText());
307 feedback
.setText("Volume label too long");
308 importDisk
.setEnabled(false);
312 feedback
.setText("Invalid character in volume label");
313 importDisk
.setEnabled(false);
317 if(createTimeLabel
.isEnabled() && createTimeLabel
.isSelected()) {
318 if(!checkTimeStamp(createTime
.getText())) {
319 feedback
.setText("Bad timestamp (YYYYMMDDHHMMSS)");
320 importDisk
.setEnabled(false);
324 feedback
.setText("");
325 importDisk
.setEnabled(true);
328 public class ImportTask
implements Runnable
339 private byte[] writeImage(RandomAccessFile out
, String src
, ImageMaker
.IFormat format
) throws IOException
342 File srcFile
= new File(src
);
343 if(format
.typeCode
== 3) {
345 if(!srcFile
.isFile())
346 throw new IOException("BIOS images can only be made out of regular files");
347 RandomAccessFile input2
= new RandomAccessFile(src
, "r");
348 return ImageMaker
.makeBIOSImage(out
, input2
, format
);
349 } else if(format
.typeCode
== 2) {
350 if(!srcFile
.isFile())
351 throw new IOException("CD images can only be made out of regular files");
352 FileRawDiskImage input2
= new FileRawDiskImage(src
);
353 return ImageMaker
.makeCDROMImage(out
, input2
, format
);
354 } else if(format
.typeCode
== 0 || format
.typeCode
== 1) {
355 if(srcFile
.isFile()) {
356 input
= new FileRawDiskImage(src
);
357 } else if(srcFile
.isDirectory()) {
358 TreeDirectoryFile root
= TreeDirectoryFile
.importTree(src
, format
.volumeLabel
, format
.timestamp
);
359 input
= new TreeRawDiskImage(root
, format
, format
.volumeLabel
);
361 throw new IOException("Source is neither regular file nor directory");
362 return ImageMaker
.makeFloppyHDDImage(out
, input
, format
);
364 throw new IOException("BUG: Invalid image type code " + format
.typeCode
);
367 private byte[] warpedRun() throws Exception
370 RandomAccessFile output
;
371 String finalName
= DiskImage
.getLibrary().getPathPrefix() + name
;
372 ImageMaker
.IFormat fmt
= new ImageMaker
.IFormat(null);
373 fmt
.typeCode
= typeCode
;
375 fmt
.sectors
= sectors
;
377 fmt
.timestamp
= timestamp
;
378 fmt
.volumeLabel
= label
;
380 File dirFile
= new File(finalName
.substring(0, finalName
.lastIndexOf("/")));
381 if(!dirFile
.isDirectory())
382 if(!dirFile
.mkdirs())
383 throw new IOException("Can't create directory '" + dirFile
.getAbsolutePath() + "'");
385 String temporaryName
= tempname(finalName
);
386 File firstArgFile
= new File(temporaryName
);
387 while(firstArgFile
.exists())
388 firstArgFile
= new File(temporaryName
= tempname(finalName
));
389 firstArgFile
.deleteOnExit();
391 output
= new RandomAccessFile(firstArgFile
, "rw");
393 id
= writeImage(output
, file
, fmt
);
394 } catch(Exception e
) {
396 firstArgFile
.delete();
399 firstArgFile
.renameTo(new File(finalName
));
400 DiskImage
.getLibrary().insertFileName(new ImageLibrary
.ByteArray(id
), finalName
, name
);
409 } catch(Exception e
) {
410 doImportFinished(e
, null);
413 doImportFinished(null, id
);
417 private void importDisk() throws IOException
419 String _imageName
= imageName
.getText();
420 String _imageFile
= imageFile
.getText();
421 String _imageType
= (String
)imageType
.getSelectedItem();
422 int sides
= getSides();
423 int sectors
= getSectors();
424 int tracks
= getTracks();
426 if(volumeLabelLabel
.isEnabled() && volumeLabelLabel
.isSelected())
427 label
= volumeLabel
.getText();
428 String timestamp
= null;
429 if(createTimeLabel
.isEnabled() && createTimeLabel
.isSelected())
430 timestamp
= createTime
.getText();
432 if(FLOPPY
.equals(_imageType
)) {
434 if(sides
< 1 || sides
> 2 || sectors
< 1 || sectors
> 255 || tracks
< 1 || tracks
> 256)
435 throw new IOException("Illegal floppy geometry " + sides
+ " sides " + sectors
+ " sectors " +
437 } else if(HDD
.equals(_imageType
)) {
439 if(sides
< 1 || sides
> 16 || sectors
< 1 || sectors
> 63 || tracks
< 1 || tracks
> 1024)
440 throw new IOException("Illegal HDD geometry " + sides
+ " sides " + sectors
+ " sectors " +
442 } else if(CDROM
.equals(_imageType
)) {
444 } else if(BIOS
.equals(_imageType
)) {
447 throw new IOException("Illegal Image type: " + _imageType
);
449 ImportTask t
= new ImportTask();
452 t
.typeCode
= typeCode
;
457 t
.timestamp
= timestamp
;
458 (new Thread(t
)).start();
461 private void doImportFinished(Exception failure
, byte[] id
)
463 final Exception failure2
= failure
;
464 final byte[] id2
= id
;
466 SwingUtilities
.invokeLater(new Runnable() { public void run() { importFinished(failure2
, id2
); }});
467 } catch(Exception e
) {
471 private void importFinished(Exception failure
, byte[] id
)
473 if(failure
!= null) {
474 errorDialog(failure
, "Error making image", window
, "Dismiss");
475 cancel
.setEnabled(true);
479 callShowOptionDialog(null, "New image (ID " + (new ImageLibrary
.ByteArray(id
)) + ") imported",
480 "Image imported", JOptionPane
.OK_OPTION
, JOptionPane
.WARNING_MESSAGE
, null, new String
[]{"Dismiss"},
486 public void actionPerformed(ActionEvent evt
)
488 String command
= evt
.getActionCommand();
489 if("IMPORT".equals(command
)) {
490 feedback
.setText("Importing disk...");
491 importDisk
.setEnabled(false);
492 cancel
.setEnabled(false);
495 } catch(Exception e
) {
496 errorDialog(e
, "Error making image", window
, "Dismiss");
497 cancel
.setEnabled(true);
500 } else if("CANCEL".equals(command
)) {
507 public void keyPressed(KeyEvent event
)
512 public void keyReleased(KeyEvent event
)
517 private void updateFile()
519 String _filename
= imageFile
.getText();
520 if(!"".equals(_filename
)) {
521 String firstType
= null;
522 File fileObject
= new File(_filename
);
523 if(fileObject
.exists() && fileObject
.isFile()) {
525 directorySelected
= false;
526 fileSelectedLength
= fileObject
.length();
527 if(fileCase
== fileSelectedLength
)
529 fileCase
= fileSelectedLength
;
530 imageType
.removeAllItems();
531 if(fileSelectedLength
> 0 && fileSelectedLength
<= 262144) {
532 imageType
.addItem(BIOS
);
533 if(firstType
== null)
536 if(fileObject
.length() > 0 && (fileObject
.length() % 512) == 0) {
537 imageType
.addItem(FLOPPY
);
538 imageType
.addItem(HDD
);
539 imageType
.addItem(CDROM
);
540 if(firstType
== null)
544 if(fileObject
.exists() && fileObject
.isDirectory()) {
548 fileSelected
= false;
549 directorySelected
= true;
550 imageType
.removeAllItems();
551 imageType
.addItem(FLOPPY
);
552 imageType
.addItem(HDD
);
553 if(firstType
== null)
556 if(firstType
!= null) {
557 imageType
.setSelectedItem(firstType
);
558 imageType
.setEnabled(true);
563 fileSelected
= false;
564 directorySelected
= false;
565 setNoValidChoice(imageType
);
571 fileSelected
= false;
572 directorySelected
= false;
573 setNoValidChoice(imageType
);
577 public void keyTyped(KeyEvent event
)
585 if(directorySelected
) {
586 createTimeLabel
.setEnabled(true);
587 volumeLabelLabel
.setEnabled(true);
588 if(createTimeLabel
.isSelected())
589 createTime
.setEnabled(true);
591 createTime
.setEnabled(false);
592 if(volumeLabelLabel
.isSelected())
593 volumeLabel
.setEnabled(true);
595 volumeLabel
.setEnabled(false);
597 volumeLabel
.setEnabled(false);
598 volumeLabelLabel
.setEnabled(false);
599 createTime
.setEnabled(false);
600 createTimeLabel
.setEnabled(false);
607 private int textToInt(String text
)
612 int v
= Integer
.parseInt(text
);
616 } catch(NumberFormatException e
) {
621 //40, 41, 42, 43 tracks, 1 sides, 1-15 sectors (up to 320kB).
623 private boolean stdGeometryValid(long len
)
627 if(len
< 320 * 1024 && (len
% 20480) != 0)
628 return true; //40 tracks, single sided.
629 if(len
< 320 * 1024 && (len
% 20992) != 0)
630 return true; //41 tracks, single sided.
631 if(len
< 320 * 1024 && (len
% 21504) != 0)
632 return true; //42 tracks, single sided.
633 if(len
< 320 * 1024 && (len
% 22016) != 0)
634 return true; //43 tracks, single sided.
636 return false; //Cutoff for single-sided.
637 if(len
< 720 * 1024 && (len
% 40960) != 0)
638 return true; //40 tracks, double sided.
639 if(len
< 720 * 1024 && (len
% 41984) != 0)
640 return true; //41 tracks, double sided.
641 if(len
< 720 * 1024 && (len
% 43008) != 0)
642 return true; //42 tracks, double sided.
643 if(len
< 720 * 1024 && (len
% 44032) != 0)
644 return true; //43 tracks, double sided.
646 return false; //Cutoff for 40 tracks double sided.
648 return false; //exceeds 83 tracks, 48 sectors double sided.
649 if((len
% 81920) != 0)
650 return true; //80 tracks, double sided.
651 if((len
% 82944) != 0)
652 return true; //81 tracks, double sided.
653 if((len
% 83968) != 0)
654 return true; //82 tracks, double sided.
655 if((len
% 84992) != 0)
656 return true; //83 tracks, double sided.
660 private int stdGeometrySides(long len
)
668 private int getSides()
670 String type
= (String
)imageType
.getSelectedItem();
671 if(FLOPPY
.equals(type
)) {
672 if(stdGeometry
.isEnabled() && stdGeometry
.isSelected())
673 return stdGeometrySides(fileSelectedLength
);
674 return doublesided
.isSelected() ?
2 : 1;
675 } else if(HDD
.equals(type
)) {
676 return textToInt(sides
.getText());
681 private boolean forceSides()
683 String type
= (String
)imageType
.getSelectedItem();
689 private long sideSectors(long len
)
697 private int stdGeometrySectors(long len
)
699 long sideSects
= sideSectors(len
);
703 for(int i
= 0; i
< 4; i
++) {
704 if(((int)sideSects
% (baseTracks
+ i
)) == 0)
705 return (int)sideSects
/ (baseTracks
+ i
);
710 private int getSectors()
712 String type
= (String
)imageType
.getSelectedItem();
713 if(stdGeometry
.isEnabled() && stdGeometry
.isSelected())
714 return stdGeometrySectors(fileSelectedLength
);
715 if(HDD
.equals(type
) || FLOPPY
.equals(type
))
716 return textToInt(sectors
.getText());
721 private boolean forceSectors()
723 String type
= (String
)imageType
.getSelectedItem();
724 if(stdGeometry
.isEnabled() && stdGeometry
.isSelected())
726 if(HDD
.equals(type
) || FLOPPY
.equals(type
))
731 private int stdGeometryTracks(long len
)
733 long sideSects
= sideSectors(len
);
737 for(int i
= 0; i
< 4; i
++) {
738 if(((int)sideSects
% (baseTracks
+ i
)) == 0)
739 return baseTracks
+ i
;
744 private int getTracks()
746 String type
= (String
)imageType
.getSelectedItem();
747 if(!HDD
.equals(type
) && !FLOPPY
.equals(type
))
749 if(stdGeometry
.isEnabled() && stdGeometry
.isSelected())
750 return stdGeometryTracks(fileSelectedLength
);
751 if(directorySelected
)
752 return textToInt(tracks
.getText());
753 else if(fileSelected
) {
754 int trackBound
= 256;
757 if((fileSelectedLength
% 512) != 0)
759 long totalSectors
= fileSelectedLength
/ 512;
760 if((totalSectors
% getSides()) != 0)
762 totalSectors
/= getSides();
763 if((totalSectors
% getSectors()) != 0)
765 totalSectors
/= getSectors();
766 if(totalSectors
< 1 || totalSectors
> trackBound
)
768 return (int)totalSectors
;
774 private boolean forceTracks()
776 String type
= (String
)imageType
.getSelectedItem();
777 if(!HDD
.equals(type
) && !FLOPPY
.equals(type
))
779 if(stdGeometry
.isEnabled() && stdGeometry
.isSelected())
781 if(directorySelected
)
786 private void changeGeometry()
788 String type
= (String
)imageType
.getSelectedItem();
789 if(FLOPPY
.equals(type
) && fileSelected
)
790 stdGeometry
.setEnabled(stdGeometryValid(fileSelectedLength
));
792 stdGeometry
.setEnabled(false);
793 if(FLOPPY
.equals(type
) && !stdGeometry
.isSelected())
794 doublesided
.setEnabled(true);
796 doublesided
.setEnabled(false);
799 sides
.setVisible(false);
800 sidesFixed
.setVisible(true);
803 sidesFixed
.setText("" + x
);
805 sidesFixed
.setText("N/A");
807 sidesFixed
.setText("Unsatisfiable");
809 sides
.setVisible(true);
810 sidesFixed
.setVisible(false);
814 sectors
.setVisible(false);
815 sectorsFixed
.setVisible(true);
816 int x
= getSectors();
818 sectorsFixed
.setText("" + x
);
820 sectorsFixed
.setText("N/A");
822 sectorsFixed
.setText("Unsatisfiable");
824 sectors
.setVisible(true);
825 sectorsFixed
.setVisible(false);
829 tracks
.setVisible(false);
830 tracksFixed
.setVisible(true);
833 tracksFixed
.setText("" + x
);
835 tracksFixed
.setText("N/A");
837 tracksFixed
.setText("Unsatisfiable");
839 tracks
.setVisible(true);
840 tracksFixed
.setVisible(false);