FC64 is a low level Commodore 64 emulator for Flash Player. The project is based on libfc64, an AS3 library responsible for the emulation. The source code presents the mentioned lib and two demos, an Android based app and a shell based app.
Sample
/* * Copyright notice * * (c) 2005-2010 Darron Schall, Claus Wahlers. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package { import c64.events.DebuggerEvent; import c64.events.FrameRateInfoEvent; import components.FC64Sprite; import core.events.CPUResetEvent; import flash.desktop.NativeApplication; import flash.display.Screen; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageOrientation; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.InvokeEvent; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.events.StageOrientationEvent; import flash.geom.Rectangle; import flash.ui.Keyboard; import flash.utils.ByteArray; import flash.utils.Endian; import flash.utils.getTimer; import flash.utils.setTimeout; import mx.core.ByteArrayAsset; /** * */ public class FC64 extends Sprite { private var fc64:FC64Sprite; /** * The amount of time (in ms) to wait before we similate a press and * release of a key so that it registers in the FC64 cpu. */ private static const FC64_CPU_KEY_REGISTER_DELAY:int = 200; /** * The timing threshold that we use to determine if a keyDown/keyUp event * handling sequence is coming from the keyboard (when the time between * event handlers is less than this delay) or the trackball (when the * time between is greater than the delay). */ private static const VIRTUAL_KEY_DELAY_THRESHOLD:int = 10; // FIXME: Remove this and let the user select roms from SD card [Embed( source="/assets/roms/COLOURGALAGA.PRG", mimeType="application/octet-stream" )] public static const GALAGA_COLOR:Class; /** * Flag indicating if a rom is currently loaded. Used to determine * what the back key does. */ private var romLoaded:Boolean = false; /** * Capture the time of a keyDown event so we can measure how soon * the corresponding keyUp comes to differentiate between a keypress * on the virtual keyboard and the trackball press. */ protected var keyDownTime:int; /** * Flag indicating if we should scale the renderer to fit the screen * or leave it at it's native width/height. */ protected var scaleToFit:Boolean = true; /** * */ // TODO: We can probably replace with a getter that checks // stage.deviceOrientation against StageOrientation constants private var isPortrait:Boolean = true; /** * Constructor */ [SWF( width="480", height="800", frameRate="60" )] public function FC64() { super(); addEventListener( Event.ADDED_TO_STAGE, onAddedToStage ); addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage ); // NativeApplication.nativeApplication.addEventListener( InvokeEvent.INVOKE, onInvoke ); // NativeApplication.nativeApplication.addEventListener( Event.ACTIVATE, onActivate ); NativeApplication.nativeApplication.addEventListener( Event.DEACTIVATE, onDeactivate ); init(); } /** * */ private function init():void { fc64 = new FC64Sprite(); fc64.addEventListener( CPUResetEvent.CPU_RESET, onCPUReset ); fc64.addEventListener( FrameRateInfoEvent.FRAME_RATE_INFO, onFrameRateInfo ); fc64.addEventListener( DebuggerEvent.STOP, onStop ); addChild( fc64 ); // Start up in portrait mode initially alignPortrait(); // Listen for orientation changes stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; // TODO: Probably don't need both orientation change AND resize change - // resize is probably enough and we can test width > height for orientation // or try using stage.deviceOrientation stage.addEventListener( StageOrientationEvent.ORIENTATION_CHANGE, onOrientationChange ); stage.addEventListener( Event.RESIZE, onResizeChange ); stage.addEventListener( MouseEvent.CLICK, onClick ); // Start renderer fc64.renderer.start(); } /** * */ private function onInvoke( event:InvokeEvent ):void { // init() } /** * */ private function onAddedToStage( event:Event ):void { stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyDown ); stage.addEventListener( KeyboardEvent.KEY_UP, onKeyUp ); } /** * */ private function onRemovedFromStage( event:Event ):void { stage.removeEventListener( KeyboardEvent.KEY_DOWN, onKeyDown ); stage.removeEventListener( KeyboardEvent.KEY_UP, onKeyUp ); } /** * */ private function onActivate( event:Event ):void { // fc64.renderer.start(); } /** * */ private function onDeactivate( event:Event ):void { // fc64.renderer.stop(); // Close the application when sent to the background // FIXME: This probably isn't the desired action NativeApplication.nativeApplication.exit(); } /** * */ private function onOrientationChange( event:StageOrientationEvent ):void { alignToOrientation( event.afterOrientation ); } /** * */ private function alignToOrientation( orientation:String ):void { // Landscape if ( orientation == StageOrientation.ROTATED_LEFT || orientation == StageOrientation.ROTATED_RIGHT ) { isPortrait = false; } else // Portrait { isPortrait = true; } } /** * */ private function onResizeChange( event:Event ):void { updateDisplay(); } /** * */ private function updateDisplay():void { if ( isPortrait ) { alignPortrait(); } else { alignLandscape(); } } /** * */ private function alignLandscape():void { var screenBounds:Rectangle = Screen.mainScreen.bounds; var deviceWidth:int = screenBounds.width; var deviceHeight:int = screenBounds.height; fc64.scaleX = fc64.scaleY = 1; // Scale FC64 to match the height if ( scaleToFit ) { var newScale:Number = deviceHeight / fc64.height; fc64.scaleX = fc64.scaleY = newScale; fc64.y = 0; } else { // Center height fc64.y = ( deviceHeight - fc64.height ) / 2; } // Center width fc64.x = ( deviceWidth - fc64.width ) / 2; } /** * */ private function alignPortrait():void { var screenBounds:Rectangle = Screen.mainScreen.bounds; var deviceWidth:int = screenBounds.width; //var deviceHeight:int = screenBounds.height; fc64.scaleX = fc64.scaleY = 1; // Scale FC64 to match the width if ( scaleToFit ) { var newScale:Number = deviceWidth / fc64.width; fc64.scaleX = fc64.scaleY = newScale; fc64.x = 0; } else { // Center width fc64.x = ( deviceWidth - fc64.width ) / 2; } fc64.y = 0; } /** * Change whether or not we scale the renderer. */ private function onClick( event:MouseEvent ):void { scaleToFit = !scaleToFit; updateDisplay(); } /** * */ protected function onKeyDown( event:KeyboardEvent ):void { var keyCode:int = event.keyCode; if ( keyCode == Keyboard.BACK ) { // If we want to handle the Back button differently, we can // prevent the event and put our own logic here event.preventDefault(); // FIXME: This is temporary - Load galaga color into memory. If already // loaded then reset the CPU if ( romLoaded ) { // Reset the CPU fc64.cpu.reset(); romLoaded = false; } else { loadProgram( new GALAGA_COLOR() as ByteArrayAsset ); romLoaded = true; } } else if ( keyCode == Keyboard.MENU ) { // FIXME: Open menu with "reset" and rom selections (file browser to load // roms from arbitrary locations on SD card?). } else { // Record when the key down happened, since when interacting with the // keyboard we get a key down immediately followed by a key up, but // when using the track ball we get a key down immediately on track ball // press and a key up on track ball release. We can calculate the time // between keyDown and keyUp to differentiate pressing ENTER on the vritual // keyboard and pressing the trackball. keyDownTime = getTimer(); } } /** * */ protected function onKeyUp( event:KeyboardEvent ):void { var keyUpTime:int = getTimer(); var keyCode:int = event.keyCode; if ( keyUpTime - keyDownTime < VIRTUAL_KEY_DELAY_THRESHOLD ) { // We received an immedaite press and then release - interacting // with the virtual keyboard we so need to release the key // in fc64, but we have to do this after enough of a delay so // that the fc64 internal cpu picks up the key press. fc64.pressKey( keyCode ); setTimeout( fc64.releaseKey, FC64_CPU_KEY_REGISTER_DELAY, keyCode ); } else if ( keyCode == Keyboard.ENTER ) { // Delay between down and up, assuming it is coming from trackball // Turn the ENTER of the trackball into a space to better simulate // joystick suppot. keyCode = Keyboard.SPACE; // Press and release the key fc64.pressKey( keyCode ); setTimeout( fc64.releaseKey, FC64_CPU_KEY_REGISTER_DELAY, keyCode ); } else { // Too much of a delay between presses and not ENTER, so this is probably // a long press and release on maybe the menu key or something. We // can ignore it. } } /** * */ private function onCPUReset( event:CPUResetEvent ):void { fc64.cpu.setBreakpoint( 0xA483, 255 ); } /** * */ private function onFrameRateInfo( event:FrameRateInfoEvent ):void { // if ( isPortrait ) // { // fpsDisplay.text = event.frameTime + " ms/frame " + event.fps + " fps"; // } // else // { // fpsDisplay.text = event.frameTime + " ms\n /frame\n\n" + event.fps + " fps"; // } } /** * */ private function onStop( event:DebuggerEvent ):void { if ( event.breakpointType == 255 ) { // if ( state == "loading" ) // { // var fileName:String = software.selectedItem.filename; // var request:URLRequest = new URLRequest( fileName ); // var loader:URLLoader = new URLLoader(); // loader.dataFormat = URLLoaderDataFormat.BINARY; // loader.addEventListener( Event.COMPLETE, onLoadPRG ); // loader.load( request ); // } // else // { // software.enabled = true; // } fc64.renderer.start(); } } /** * */ private function onLoadPRG( event:Event ):void { // loadProgram( ByteArray( e.target.data ) ); // software.enabled = true; // software.selectedIndex = -1; // loadButton.enabled = false; // state = "normal"; } /** * */ private function loadProgram( ba:ByteArray ):void { // get start address ba.endian = Endian.LITTLE_ENDIAN; var startAddress:int = ba.readShort(); // copy contents var addr:int = startAddress; for ( var i:uint = 0x02; i < ba.length; i++ ) { fc64.mem.write( addr++, ba[ i ] ); } if ( startAddress == 0x0801 ) { // run command var charsInBuffer:uint = fc64.mem.read( 0xc6 ); if ( charsInBuffer < fc64.mem.read( 0x0289 ) - 4 ) { var keyboardBuffer:uint = 0x0277 + charsInBuffer + 1; fc64.mem.write( keyboardBuffer++, 82 ); // R fc64.mem.write( keyboardBuffer++, 85 ); // U fc64.mem.write( keyboardBuffer++, 78 ); // N fc64.mem.write( keyboardBuffer++, 13 ); // Return fc64.mem.write( 0xc6, charsInBuffer + 5 ); } } else { fc64.cpu.pc = startAddress; } } // private function onOSInitialized( event:OSInitializedEvent ):void // { // } } }
RT @as3gamegears http://t.co/enqTlA5P #as3 #flash #gamedev: New: FC64 (low level Commodore 64 emulator) http://… http://t.co/bydq1wOt
RT @as3gamegears New: FC64 (low level Commodore 64 emulator) http://t.co/a2SeHUQa #as3 #flash #gamedev