/** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.codesourcery.jasm16.emulator; import java.io.File; import java.util.List; import java.util.NoSuchElementException; import org.apache.commons.lang.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import de.codesourcery.jasm16.emulator.IEmulator.EmulationSpeed; import de.codesourcery.jasm16.emulator.ILogger.LogLevel; import de.codesourcery.jasm16.emulator.devices.IDevice; import de.codesourcery.jasm16.emulator.devices.impl.DefaultClock; import de.codesourcery.jasm16.emulator.devices.impl.DefaultFloppyDrive; import de.codesourcery.jasm16.emulator.devices.impl.DefaultKeyboard; import de.codesourcery.jasm16.emulator.devices.impl.DefaultScreen; import de.codesourcery.jasm16.emulator.devices.impl.DefaultVectorDisplay; import de.codesourcery.jasm16.emulator.devices.impl.FileBasedFloppyDisk; /** * Emulation options. * * @author tobias.gierke@code-sourcery.de */ public final class EmulationOptions { private static final EmulationSpeed DEFAULT_EMULATION_SPEED = EmulationSpeed.REAL_SPEED; /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * Adjust the following locations when * adding/removing configuration options: * * - COPY constructor !! * - loadEmulationOptions() * - saveEmulationOptions() * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ private boolean enableDebugOutput = false; private boolean memoryProtectionEnabled = false; private boolean ignoreAccessToUnknownDevices = false; private boolean useLegacyKeyboardBuffer = false; private boolean mapVideoRamUponAddDevice = true; private boolean mapFontRamUponAddDevice = false; private boolean runFloppyAtFullSpeed = false; private EmulationSpeed emulationSpeed = DEFAULT_EMULATION_SPEED; private boolean crashOnStoreWithImmediate = true; private InsertedDisk insertedDisk; private boolean newEmulatorInstanceRequired = false; public static final class InsertedDisk { private final File file; private final boolean writeProtected; public InsertedDisk(File file, boolean writeProtected) { this.file = file; this.writeProtected = writeProtected; } public File getFile() { return file; } public boolean isWriteProtected() { return writeProtected; } } public EmulationOptions() { } public EmulationSpeed getEmulationSpeed() { return emulationSpeed; } /** * Returns whether the emulation should stop when an * instruction like <code>SET 0x1000 ,A</code> is encountered. * * @return <code>true</code> if emulation should stop, <code>false</code> if emulation * should silently continue */ public boolean isCrashOnStoreWithImmediate() { return crashOnStoreWithImmediate; } /** * Sets whether the emulation should when an * instruction like <code>SET 0x1000 ,A</code> is encountered. * * @param doCrashOnImmediate */ public void setCrashOnStoreWithImmediate(boolean doCrashOnImmediate) { this.crashOnStoreWithImmediate = doCrashOnImmediate; } public void setEmulationSpeed(EmulationSpeed emulationSpeed) { if (emulationSpeed == null) { throw new IllegalArgumentException("emulationSpeed must not be null"); } this.emulationSpeed = emulationSpeed; } public void setEnableDebugOutput(boolean enableDebugOutput) { this.enableDebugOutput = enableDebugOutput; } public boolean isEnableDebugOutput() { return enableDebugOutput; } public EmulationOptions(EmulationOptions other) { if (other == null) { throw new IllegalArgumentException("options must not be null"); } this.enableDebugOutput = other.enableDebugOutput; this.memoryProtectionEnabled = other.memoryProtectionEnabled; this.ignoreAccessToUnknownDevices = other.ignoreAccessToUnknownDevices; this.useLegacyKeyboardBuffer = other.useLegacyKeyboardBuffer; this.mapFontRamUponAddDevice = other.mapFontRamUponAddDevice; this.mapVideoRamUponAddDevice = other.mapVideoRamUponAddDevice; this.runFloppyAtFullSpeed = other.runFloppyAtFullSpeed; this.newEmulatorInstanceRequired = other.newEmulatorInstanceRequired; this.insertedDisk = other.insertedDisk; this.emulationSpeed = other.emulationSpeed; this.crashOnStoreWithImmediate = other.crashOnStoreWithImmediate; } public InsertedDisk getInsertedDisk() { return insertedDisk; } public void setInsertedDisk(InsertedDisk disk) { this.insertedDisk = disk; } public void setRunFloppyAtFullSpeed(boolean runFloppyAtFullSpeed) { this.runFloppyAtFullSpeed = runFloppyAtFullSpeed; } public boolean isRunFloppyAtFullSpeed() { return runFloppyAtFullSpeed; } public boolean isMemoryProtectionEnabled() { return memoryProtectionEnabled; } public void setMemoryProtectionEnabled(boolean memoryProtectionEnabled) { this.memoryProtectionEnabled = memoryProtectionEnabled; } public boolean isIgnoreAccessToUnknownDevices() { return ignoreAccessToUnknownDevices; } public void setIgnoreAccessToUnknownDevices(boolean ignoreAccessToUnknownDevices) { this.ignoreAccessToUnknownDevices = ignoreAccessToUnknownDevices; } public boolean isUseLegacyKeyboardBuffer() { return useLegacyKeyboardBuffer; } public void setUseLegacyKeyboardBuffer(boolean useLegacyKeyboardBuffer) { newEmulatorInstanceRequired |= this.useLegacyKeyboardBuffer != useLegacyKeyboardBuffer; this.useLegacyKeyboardBuffer = useLegacyKeyboardBuffer; } public boolean isMapVideoRamUponAddDevice() { return mapVideoRamUponAddDevice; } public void setMapVideoRamUponAddDevice(boolean mapVideoRamUponAddDevice) { newEmulatorInstanceRequired |= this.mapVideoRamUponAddDevice != mapVideoRamUponAddDevice; this.mapVideoRamUponAddDevice = mapVideoRamUponAddDevice; } public boolean isMapFontRamUponAddDevice() { return mapFontRamUponAddDevice; } public void setMapFontRamUponAddDevice(boolean mapFontRamUponAddDevice) { this.newEmulatorInstanceRequired |= this.mapFontRamUponAddDevice != mapFontRamUponAddDevice; this.mapFontRamUponAddDevice = mapFontRamUponAddDevice; } public boolean isNewEmulatorInstanceRequired() { return newEmulatorInstanceRequired; } public void apply(IEmulator emulator) { final ILogger outLogger = new PrintStreamLogger( System.out ); if ( enableDebugOutput ) { outLogger.setLogLevel( LogLevel.DEBUG ); } emulator.setOutput( outLogger ); emulator.setMemoryProtectionEnabled( memoryProtectionEnabled ); emulator.setIgnoreAccessToUnknownDevices( ignoreAccessToUnknownDevices ); emulator.setEmulationSpeed( emulationSpeed ); emulator.setCrashOnStoreWithImmediate( crashOnStoreWithImmediate ); try { final DefaultFloppyDrive drive = getFloppyDrive( emulator ); drive.setRunAtMaxSpeed( runFloppyAtFullSpeed ); insertDisk( drive ); } catch(NoSuchElementException e) { // ok , no floppy attached } } public void saveEmulationOptions(Element element,Document document) { if ( isEnableDebugOutput() ) { element.setAttribute("debug" , "true" ); } if ( isIgnoreAccessToUnknownDevices() ) { element.setAttribute("ignoreAccessToUnknownDevices" , "true" ); } if ( isMapFontRamUponAddDevice() ) { element.setAttribute("mapFontRamUponAddDevice" , "true" ); } if ( isMapVideoRamUponAddDevice() ) { element.setAttribute("mapVideoRamUponAddDevice" , "true" ); } if ( isMemoryProtectionEnabled() ) { element.setAttribute("memoryProtectionEnabled" , "true" ); } if ( isUseLegacyKeyboardBuffer() ) { element.setAttribute("useLegacyKeyboardBuffer" , "true" ); } if ( isRunFloppyAtFullSpeed() ) { element.setAttribute("runFloppyAtFullSpeed" , "true" ); } if ( isCrashOnStoreWithImmediate() ) { element.setAttribute("crashOnStoreWithImmediate" , "true" ); } element.setAttribute( "emulationSpeed" , emulationSpeedToString( this.emulationSpeed ) ); if ( getInsertedDisk() != null ) { final Element disks = document.createElement("disks" ); element.appendChild( disks); final Element disk = document.createElement("disk" ); disks.appendChild( disk ); disk.setAttribute("writeProtected" , getInsertedDisk().isWriteProtected() ? "true" : "false" ); disk.setAttribute("file" , getInsertedDisk().getFile().getAbsolutePath() ); } } public static EmulationOptions loadEmulationOptions(Element element) { final EmulationOptions result = new EmulationOptions(); result.setEnableDebugOutput( isSet(element,"debug" ) ); result.setIgnoreAccessToUnknownDevices( isSet(element,"ignoreAccessToUnknownDevices" ) ); result.setMapFontRamUponAddDevice( isSet(element,"mapFontRamUponAddDevice" ) ); result.setMapVideoRamUponAddDevice( isSet(element,"mapVideoRamUponAddDevice" ) ); result.setMemoryProtectionEnabled( isSet(element,"memoryProtectionEnabled" ) ); result.setUseLegacyKeyboardBuffer( isSet(element,"useLegacyKeyboardBuffer" ) ); result.setRunFloppyAtFullSpeed( isSet(element,"runFloppyAtFullSpeed" ) ); result.setEmulationSpeed( emulationSpeedFromString( element.getAttribute("emulationSpeed") ) ); result.setCrashOnStoreWithImmediate( isSet(element,"crashOnStoreWithImmediate" ) ); Element disks = getChildElement( element , "disks"); if ( disks != null ) { Element disk = getChildElement(element,"disk" ); if ( disk != null ) { final boolean writeProtected = isSet( disk , "writeProtected" ); final File file = new File( disk.getAttribute( "file" ) ); result.setInsertedDisk( new InsertedDisk(file,writeProtected ) ); } } return result; } private static String emulationSpeedToString(EmulationSpeed speed) { switch( speed ) { case MAX_SPEED: return "max"; case REAL_SPEED: return "real"; default: throw new RuntimeException("Unhandled speed: "+speed); } } private static EmulationSpeed emulationSpeedFromString(String s) { if ( StringUtils.isBlank(s ) ) { return DEFAULT_EMULATION_SPEED; } switch( s ) { case "max": return EmulationSpeed.MAX_SPEED; case "real": return EmulationSpeed.REAL_SPEED; default: throw new RuntimeException("Unhandled speed: '"+s+"'"); } } private static Element getChildElement(Element parent,String tagName) { final NodeList nodeList = parent.getElementsByTagName( tagName ); if ( nodeList.getLength() == 1 ) { return (Element) nodeList.item(0); } if ( nodeList.getLength() > 1 ) { throw new RuntimeException("Parse error, more than one <disks/> node in file?"); } return null; } private static boolean isSet(Element element,String attribute) { final String value = element.getAttribute(attribute); return "true".equals( value ); } public Emulator createEmulator() { final Emulator result = new Emulator(); result.addDevice( new DefaultClock() ); result.addDevice( new DefaultKeyboard( useLegacyKeyboardBuffer ) ); result.addDevice( new DefaultScreen( mapVideoRamUponAddDevice , mapFontRamUponAddDevice ) ); result.addDevice( new DefaultFloppyDrive( runFloppyAtFullSpeed ) ); result.addDevice( new DefaultVectorDisplay() ); apply( result ); newEmulatorInstanceRequired = false; return result; } private void insertDisk(DefaultFloppyDrive diskDrive) { final InsertedDisk disk = getInsertedDisk(); if ( disk != null ) { diskDrive.setDisk( new FileBasedFloppyDisk( disk.getFile() , disk.isWriteProtected() ) ); } else { diskDrive.eject(); } } public DefaultFloppyDrive getFloppyDrive(IEmulator emulator) { List<IDevice> result = emulator.getDevicesByDescriptor( DefaultFloppyDrive.DESC ); if ( result.isEmpty() ) { throw new NoSuchElementException("Internal error, found no floppy drive?"); } if ( result.size() > 1 ) { throw new RuntimeException("Internal error, found more than one floppy drive?"); } return (DefaultFloppyDrive) result.get(0); } public DefaultScreen getScreen(IEmulator emulator) { List<IDevice> result = emulator.getDevicesByDescriptor( DefaultScreen.DESC ); if ( result.isEmpty() ) { throw new NoSuchElementException("Internal error, found no default screen?"); } if ( result.size() > 1 ) { throw new RuntimeException("Internal error, found more than one default screen?"); } return (DefaultScreen) result.get(0); } public DefaultVectorDisplay getVectorDisplay(IEmulator emulator) { List<IDevice> result = emulator.getDevicesByDescriptor( DefaultVectorDisplay.DEVICE_DESCRIPTOR ); if ( result.isEmpty() ) { throw new NoSuchElementException("Internal error, found no default vector display ?"); } if ( result.size() > 1 ) { throw new RuntimeException("Internal error, found more than one vector display ?"); } return (DefaultVectorDisplay) result.get(0); } public DefaultKeyboard getKeyboard(IEmulator emulator) { List<IDevice> result = emulator.getDevicesByDescriptor( DefaultKeyboard.DESC ); if ( result.isEmpty() ) { throw new NoSuchElementException("Internal error, found no default keyboard ?"); } if ( result.size() > 1 ) { throw new RuntimeException("Internal error, found more than one default keyboard ?"); } return (DefaultKeyboard) result.get(0); } }