The Weekly Source Code 44 - Virtu, an Apple Emulator in C# for Silverlight, WPF and XNA
I really advocate folks reading as much source as they can because you become a better writer by reading as much as writing. That's the whole point of the Weekly Source Code - reading code to be a better developer.
Reading code in Open Source projects is a good way to learn, especially if the project has been around a while and been successful, or if you already respect the team of people working on it. Less reliably, you can find snippets of code by searching and sharing code.
I love Emulators. They are magical. Earlier this year I interviewed Pete Brown when he created a C64 Emulator in Silverlight.
Now, it's Apple IIe time. From the Virtu Project Site, you can see that this source has been around in various forms for years...morphing from form to form.
Originally developed for RISC OS (3.11) on the Acorn Archimedes in 1995 using some C but mostly ARM assembly language. Published on the cover disk of the October 1997 issue of Acorn User. Later that year we started porting Virtu to Microsoft Windows (95) on the 'PC' using only C++ with DirectX. A port to Microsoft Windows CE (2.11) soon followed. These were tweaked over the next couple of years but never published. Fast forward to the present and the latest incarnation of Virtu, this time ported to the Microsoft .NET Framework (3.5 SP 1) using only C# with Silverlight, WPF and XNA (on both Windows and Xbox 360, which is limited to the .NET Compact Framework).
In this form, Virtu was written by Sean Fausett with some help from Nick Westgate. This code is interesting for a number of reasons. First, because it's a freaking AppleIIe emulator in a language I like to read (*cough* Not C *cough*), but also because it is cleanly structured and includes Silverlight (that means Mac also!), WPF and XNA (Xbox360) versions. It illustrates a way one can factor their code into an engine and various hosts.
IMPORTANT NOTE: To run, Virtu needs two files that are not included: An image of the standard or preferably the enhanced Apple IIe monitor ROM needs to be copied as 'AppleIIe.rom' (16 KB) to the Roms directory. An image of the Disk II (16 sector) interface card ROM needs to be copied as 'DiskII.rom' (256 bytes) to the Roms directory. You'll also need some disk in the form of a ".nib" file like RasterBlaster.nib, for example. I can't give you those files.
After a successful build, you should be able to run the emulator and perform a self test by pressing the hallowed key combination Control+OpenApple+CloseApple+Reset.
Looking at the WpfKeyboardService.cs, I can see how those keys I don't have are mapped to keys I do:
ModifierKeys modifiers = keyboard.Modifiers;
IsOpenAppleKeyDown = keyboard.IsKeyDown(Key.LeftAlt);
IsCloseAppleKeyDown = keyboard.IsKeyDown(Key.RightAlt);
IsResetKeyDown = ((modifiers & ModifierKeys.Control) != 0) && keyboard.IsKeyDown(Key.F12);
IsCpuThrottleKeyDown = keyboard.IsKeyDown(Key.F8);
IsVideoFullScreenKeyDown = keyboard.IsKeyDown(Key.F11);
IsVideoMonochromeKeyDown = keyboard.IsKeyDown(Key.F9);
Looks like that's ALT, ALT, CTRL, F12 which gives me a weird series of self test screens then "System OK" which is a good sign.
This is nice, now I can do a little Applesoft BASIC by booting to the monitor with Ctrl-F12 then typing this, then RUN.
10 TEXT:HOME
20 ?"HELLO WORLD"
Thrilling!
It's really fun code to read and it's a lot cleaner than you'd think for an emulator, although there's the expected Giant Scary Switch Statements here and there. Other parts definitely feel like they've been brought along from the past, although, how else would you do them? (Don't look in VideoData.cs, your face will melt.) For example, here's how they draw text (remembering that we're not using Fonts here, we've got a REALLY low res screen):
private void DrawText40(int data, int x, int y)
{
int color = Machine.Settings.Video.IsMonochrome ? ColorMono00 : ColorWhite00;
int index = _charSet[data] * CharBitmapBytes;
int inverseMask = (_isTextInversed && !_memory.IsCharSetAlternate && (0x40 <= data) && (data <= 0x7F)) ? 0x7F : 0x00;
for (int i = 0; i < TextHeight; i++, y++)
{
data = CharBitmap[index + i] ^ inverseMask;
SetPixel(x + 0, y, color | (data & 0x01));
SetPixel(x + 1, y, color | (data & 0x01));
SetPixel(x + 2, y, color | (data & 0x02));
SetPixel(x + 3, y, color | (data & 0x02));
SetPixel(x + 4, y, color | (data & 0x04));
SetPixel(x + 5, y, color | (data & 0x04));
SetPixel(x + 6, y, color | (data & 0x08));
SetPixel(x + 7, y, color | (data & 0x08));
SetPixel(x + 8, y, color | (data & 0x10));
SetPixel(x + 9, y, color | (data & 0x10));
SetPixel(x + 10, y, color | (data & 0x20));
SetPixel(x + 11, y, color | (data & 0x20));
SetPixel(x + 12, y, color | (data & 0x40));
SetPixel(x + 13, y, color | (data & 0x40));
}
}
In Silverlight, they use the same (only) technique that Pete Brown's C64 emulator used to use, the new WriteableBitmap class. This means the XAML is just a single Image, and everything is a dynamically generated Bitmap. Here's the SilverlightVideoService.cs:
namespace Jellyfish.Virtu.Services
{
public sealed class SilverlightVideoService : VideoService
{
public SilverlightVideoService(Image image)
{
_image = image;
SetImageSize();
_bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight, BitmapPixelFormat);
_pixels = new uint[BitmapWidth * BitmapHeight];
Application.Current.Host.Content.Resized += (sender, e) => SetImageSize();
}
[SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "y*560")]
public override void SetPixel(int x, int y, uint color)
{
_pixels[y * BitmapWidth + x] = color;
_pixelsDirty = true;
}
public override void Update()
{
if (Application.Current.RunningOffline && /*_window.IsActive &&*/ (_isFullScreen != IsFullScreen))
{
_isFullScreen = IsFullScreen;
}
if (_pixelsDirty)
{
_pixelsDirty = false;
_bitmap.Lock();
for (int i = 0; i < BitmapWidth * BitmapHeight; i++)
{
_bitmap[i] = (int)_pixels[i];
}
_bitmap.Invalidate();
_bitmap.Unlock();
_image.Source = _bitmap; // shouldn't have to set source each frame; SL bug?
}
}
private void SetImageSize()
{
Content content = Application.Current.Host.Content;
int uniformScale = Math.Min((int)content.ActualWidth / BitmapWidth, (int)content.ActualHeight / BitmapHeight);
_image.Width = uniformScale * BitmapWidth;
_image.Height = uniformScale * BitmapHeight;
}
private const int BitmapWidth = 560;
private const int BitmapHeight = 384;
private static readonly PixelFormat BitmapPixelFormat = PixelFormats.Bgr32;
private Image _image;
private WriteableBitmap _bitmap;
private uint[] _pixels;
private bool _pixelsDirty;
private bool _isFullScreen;
}
}
It's a nice codebase and fun to step through. If you're interested in learning about emulation, check it out.
There are Wiki pages with details and quirks for each platform, WPF, XNA and Silverlight. There's still work to be done, so you might head over there and offer to help!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Time to break out the GOTO's, I say!
(Of course, you would not be able to work with an application that's running on a virtual Apple IIe that's running in Silverlight that's running in IE 6 that's running in Windows XP Mode that's running in Windows 7 that's running in VMware that's running on a Mac, since that would just be silly.)
It's a shame that the only parts of the code that get comments are the parts that would be too hard for the developers to understand without comments. I know there's a "less comments" movment among many developers and I agree with many of its tenets, but I don't think "no comments" is a good path.
At least now i can teach my kids to program the way i learnt. Break out the CALL -151, 6502 machine code rocks!
Comments are closed.
Pete