Modifying a Master System cartridge for use with flash ROMs

Thursday, 22nd August 2019

I have a ToToTEK GG-PRO flash cartridge to run homebrew software on my Game Gear however I have never been able to get it to work on my current PC and it seems that it's hard to find a Master System equivalent these days. A contemporary alternative is the Master EverDrive and it is by all accounts an excellent piece of equipment however it is a very expensive product.

I had, however, heard that certain Master System cartridges could be modified to accommodate a flash memory chip in place of their stock mask ROM. I do say certain cartridges as it's the ones with separate mapper chips that need to be used. One such cartridge is After Burner, and as I was able to find an inexpensive loose copy on eBay I used that as the basis of my modifications.

Modified After Burner cartridge with flash memory chips

The memory mapper is used to map ROM banks or save RAM on the cartridge into one of three 16KB slots in the Z80's address space. Most Master System cartridges only contain a single chip that combines the ROM data for the game as well as the memory mapper logic. As such, these cartridges can't be modified for use with a generic flash memory chip as you can't access that internal memory mapper. Some cartridges, however, make use of a separate mapper chip and so you can remove the plain mask ROM chip and replace it with a flash memory chip. SMS Power! has this list of mappers and examples of cartridges in which they can be found.

Removing the old masked ROM chip

In the photo above you can see where I removed the mask ROM from the cartridge PCB and have left the mapper chip on the board. I didn't want to damage the old ROM chip (in case I wanted to play After Burner again) so I carefully unsoldered it rather than cutting it off the board. To do so I heated up each solder joint on the back of the board and used my spring-loaded solder sucker to remove the molten solder. After this I checked each pin by gently trying to move it in its hole; if it moved I knew it had been unsoldered and if it was stuck fast I knew I needed to try removing more solder. Once all pins were free the old ROM lifted cleanly out of the PCB.

One of convenient features of these cartridges is that the pinout of Sega's mask ROMs is virtually identical to the pinout of commonly-available flash memories like the 29F010 or 49F040. Only two pins need to be changed, as per the information on Charles MacDonald's website:

Pin Mask ROM Flash memory
1 Not connected A18
31 A18 /WE (Write Enable)

As I wanted to ensure that the cartridge was compatible with both the original mask ROM and the replacement flash memory chip I thought it best to install a switch to let me select the type of memory that is installed. To break the connection between the solder pad on the PCB and the leg on the memory chip I used an IC socket with legs 1 and 31 bent out and not soldered through their corresponding holes. Wires are soldered to the bent out legs and go via the switch to the corresponding solder pads on the bottom of the PCB.

The position of A18 definitely needed to be swapped between pin 31 on the mask ROM and pin 1 on the flash memory if I wanted to be able to address all 512KB of a 4 megabit ROM. I had heard reports that the write enable pin on the flash memory can be left disconnected however the datasheets for the flash memory chips I checked did seem to indicate that it should be held high during read operations so I thought it best to hold it high when in "flash" mode. This means that the function of both pins needed to be changed by the switch, so I used a DPDT to make this happen. The two different states are illustrated below, showing the connections to the six pins on the bottom of the switch:

The two switch positions that let you use the same socket for mask ROMs and flash memory

The heavy black lines show the position of the switch contacts when in the upper and lower positions. When in "MPR" mode you can see that pin 31 of the IC socket (A18) is connected to pin 31 of the PCB and pin 1 (NC) of the IC socket is not connected to anything. When in "FLASH" mode pin 31 of the IC socket (now /WE) is connected to Vcc and pin 1 of the IC socket (now A18) is connected to pin 31 of the PCB.

Close-up detail of the bent pins and soldered wires of the IC socket

The photos above show how the pins of the IC socket were bent outwards with very fine wires soldered to them. These fine wires run through holes on the PCB under the IC socket to the underside. I did stick very small pieces of electrical insulating tape under the points where the solder joints for the wires attached to the bent pins made contact with the PCB for a bit of added security. With those legs bent out and the wires threaded through the PCB the socket could be soldered down.

Photos showing holes being cut in the cartridge enclosure for access to the memory chip and mode switch

Unfortunately, one problem with using an IC socket is that the extra height means that the PCB no longer fits inside the cartridge shell. I needed to cut a rectangular hole in the cartridge enclosure for the chip to protrude through. I started by drilling two large round holes at the far ends of the chip - this allows me to use a chip puller (or small screwdriver) to pull (or lever) the chip out of the socket without needing to dismantle the cartridge each time. Due to the position of an internal support post very near to the memory chip a larger rectangular slot could not be cut – and I think this looks pretty neat anyway! A smaller rectangular hole was cut in the top of the cartridge shell for the switch to protrude through along with mounting holes for its two screws.

The switch and its soldered connections to the main PCB

Here the switch has been mounted inside the case with the wires from the IC socket soldered to the appropriate pins on the switch and other wires connected to the appropriate pads on the back of the back of the PCB.

Inserting the original mask ROM for After Burner and setting the switch to MPR mode lets me play After Burner; writing a ROM image to a flash memory chip and inserting that with the switch set to FLASH mode let me run that program instead. Putting the switch on the wrong mode would take me to the console's built-in game of Hang On as the BIOS is no longer able to read the cartridge as a valid Master System game (at least when using a 512KB memory that requires A18 to be connected to the right pin). All in all I'm now happy that I have a way to run programs on my Master System from flash memory and do some homebrew experiments of my own on real hardware.

Flight of Pigarus running on the Master System

The above photo shows the excellent homebrew Flight of Pigarus by Kagesan running on my Master System courtesy of the modified cartridge. I've tested it so far with an AM29F010B (128KB/1 megabit) and an AT49F040 (512KB/4 megabit) and have been using the Willem programmer (along with Remapped IO.DLL to get it to work with my PC's PCI parallel port) to program the chips.

A temporary solution for 3D games on the Master System without the 3D glasses adaptor

Monday, 29th July 2019

I bought my Sega Master System-compatible 3D glasses almost exactly ten years ago for use in my LCD Shutter Glasses Adaptor project.

More recently I've acquired a CRT television and an actual Sega Master System so I could in theory make use of the glasses as I had originally intended - with Sega Master System games.

Composite video adaptor for the LCD shutter glasses adaptor being tested with Zaxxon 3-D


I've been keeping an eye on eBay for the 3D Glasses Adaptor for the Master System. This is a device that plugs into the console's card slot and allows it to drive the glasses with the software controlling which LCD shutter is open and which is closed by writing to the card. Unfortunately, these cards are not too easy to find in the UK and when they do appear they usually came bundled with a broken pair of glasses (it seems very unusual for both arms to still be attached to the glasses, and I've seen a fair few pairs that are cracked down the middle too). I already own some compatible 3D glasses so didn't want to waste money on buying a broken set of original Master System ones!

I did eventually find someone selling a loose adaptor for a reasonable price so bought it and a few games. Zaxxon 3-D was the first to arrive and I was eager to test it out. Without the card adaptor I needed to find an alternative solution, so my thoughts turned to the LCD Shutter Glasses Adaptor I'd built a few years ago.

This is designed to sit between a PC and a VGA CRT monitor and drives the shutter glasses, alternating which LCD shutter is open and which is closed every vertical sync. It can also blank out alternate scanlines, simulating an interlaced signal from a progressive one by blanking odd scanlines on one frame and even scanlines on the next, but this is not useful in our case. The Master System is already alternating complete left and right eye views on its own, so we just need to catch its equivalent of a vsync pulse and feed that into the VGA port on the back of the shutter glasses adaptor.

Composite video adaptor for the LCD shutter glasses adaptor


Above is the device I built, attached to the existing 3D glasses adaptor. It has a single composite input which should be connected to the composite output from the console, either via a splitter (if the console is connected to the TV using composite video) or via some sort of SCART breakout box (I'm using the composite video output from my SCART switch box). It also has a power socket which is used to power the circuit inside which is also passed through to the LCD shutter glasses adaptor. The box has a DE-15 connector and 5.5x2.1mm barrel plug on the other side for connections:

The barrel plug and DE-15 VGA connectors on the output side of the CVBS adaptor


I'd cut the barrel plug off a faulty power supply years ago, I'm glad I kept it as it made the project a much neater solution than it might have other been!

The circuit inside is very simple indeed. As the original adaptor has its own 5V regulator inside and is designed to be powered from a 9V power supply I had to maintain the same convention for this device, so I use a 7805 regulator to convert the incoming voltage to 5V. This powers a textbook example of an LM1881 sync separator circuit - I'm using the reference circuit from the chip's datasheet, connecting the composite video input to the chip's composite video input via a 0.1µF capacitor (without termination as it's assumed the signal is being terminated by the TV) and I use the LM1881's composite sync output and vertical sync output for the VGA connector's horizontal and vertical sync connections respectively.

The insides of the CVBS adaptor


The circuit can be seen above, stuffed in the bottom of the box - along with copious amounts of hot glue to keep the barrel plug in place! How well does it work? Well, the below animation shows two views of the circuit in action, viewed through first one shutter and then the other of the 3D glasses:

Animated demonstration of the shutter glasses in action, showing alternate views through the shutters in turn


This isn't a perfect solution - the Master System expects to be able to explicitly specify which shutter is open whereas in our case we're simply be alternating every frame. This could mean that the eyes are swapped, however there is an eye swap switch on the shutter glasses adaptor to compensate. If the view looks wrong then the switch can be used to correct it, and once that's done as long as the software alternates the views every frame then it should be fine.

I'm certainly happy for now, as it lets me play my 3D games whilst waiting for the console's intended 3D glasses adaptor to arrive in the post.

New 3D renderer in Cogwheel

Monday, 10th August 2009

I have written a new 3D-compatible renderer for Cogwheel. It holds two textures, one for each eye, and uses one of a number of different effect file techniques to mix the two views.

Row-interleaved 3D

Based on the interlacing work from the previous entry, the first technique is one that uses interleaved rows. I'm not really sure if there's a good way to convert texture coordinates into device coordinates, so am passing in the viewport height as a parameter and hoping that floating point errors don't trip me up (they haven't, yet).

float4 RowInterleavedPixelShader(VertexPositionTexture input) : COLOR0 {
	float row = input.Texture.y * ViewportHeight * 0.5f;
	if (abs(round(row) - row) < 0.1f) {
		return tex2D(LeftEyeSampler, input.Texture);
	} else {
		return tex2D(RightEyeSampler, input.Texture);
	}
}

Alternate pixel centres may also pose a problem in the future. If anyone had any recommendations, suggestions or warnings on the way I'm detecting the evenness or oddness of a particular "scanline" then I'd appreciate hearing them!

Colunn-interleaved 3D Chequerboard-interleaved 3D

I have also added two other interleaving modes; one in columns and another in a chequerboard pattern. I included these two as I've seen that some 3D LCD panels use a column interleaving pattern (I suppose that with a lenticular lens in front of such a panel you may not even need 3D glasses) and apparently Sharp have displays that use the chequerboard pattern.

I have also taken advantage of pixel shaders to create colour and monochrome anaglyphs (previously calculated in software), though neither look as good as the above full-colour modes for shutter glasses or similar hardware.

There are a few issues I need to sort out first before I can release this; for example, there's no way to set whether the first row/column/pixel is for the left or right eye. More problematic is the removal of support for non power-of-two textures; the Master System's 256×192 display is fine, but the Game Gear's 160×144 display gets rounded up to 192 pixels wide (and yes, I know that's not a power of two) on my video card. I also mean to give Promit's SlimTune profiler a look to see if I can optimise some of the less efficient pieces of my code. The C# version of emu2413 is probably a good candidate, being a "dumb" translation from the original macro-heavy C.

C# emu2413

Thursday, 8th January 2009

This is fairly embarrassing; somebody sent me an email that was flagged as spam which I accidentally deleted. So if you sent me an email and I haven't replied, I'm not deliberately being rude; could you send it again? embarrass.gif



After encountering strange crashes (not .NET exceptions, full out crashes) with emu2413 I decided to port it to straight C# instead from its existing C incarnation (emu2413.h.cs and emu2413.c.cs). Even though the original was macro-heavy it was relatively simple to port, and so there's no dependency on an unmanaged DLL to generate FM sound any more. However, the C# version is significantly slower (Cogwheel now takes about 50% extra CPU time when FM sound is enabled), possibly due to many extraneous method calls that were macros in the original.

However, the emulator still crashes when FM sound is enabled. And I have no idea why, as it only happens in Release mode and outside the IDE. The Debug build works fine inside and outside the IDE, and Release mode works fine when run within the IDE. sad.gif

Controller input updates to Cogwheel

Monday, 5th January 2009

I hope you all had a good Christmas and New Year period!

I received an Xbox 360 controller for Christmas, so have done a bit of work on Cogwheel to add support for it. (You can download a copy of the latest version 1.0.2.0 with SlimDX here).

The first issue to deal with was the D-pad on the Xbox 360 controller. When treated as a conventional joystick or DirectInput device the D-pad state is returned via the point-of-view (POV) hat. The joystick input source class couldn't raise events generated by the POV hat so support for that had to be added. This now allows other controllers that used the POV hat for slightly bizarre reasons (eg the faceplate buttons on the PlayStation controller when using PPJoy) to work too.

The second issue was the slightly odd way that the Xbox 360's DirectInput driver returns the state of the triggers - as a single axis, with one trigger moving the axis in one direction, the other trigger moving it in the other. You cannot differentiate between both triggers being held and both being released, as both states return 0. To get around this, I've added support for XInput devices, where all buttons and triggers operate independently.

The Xbox 360 controller now shows up twice in the UI - once as an XInput device and again as a conventional joystick. Fortunately, you can check if a device is an XInput device by the presence of IG_ in its device ID. Here's some C# code that can be used to check with a joystick is an XInput device or not.

using System.Globalization;
using System.Management;
using System.Text.RegularExpressions;

namespace CogwheelSlimDX.JoystickInput {
    
    /// <summary>
    /// Provides methods for retrieving the state from a joystick.
    /// </summary>
    public class Joystick {

        /* ... */

        /// <summary>
        /// Gets the vendor identifier of the <see cref="Joystick"/>.
        /// </summary>
        public ushort VendorId { get; private set; }

        /// <summary>
        /// Gets the product identifier of the <see cref="Joystick"/>.
        /// </summary>
        public ushort ProductId { get; private set; }

        /* ... */

        /// <summary>
        /// Determines whether the device is an XInput device or not. Returns true if it is, false if it isn't.
        /// </summary>
        public bool IsXInputDevice {
            get {
                var ParseIds = new Regex(@"([VP])ID_([\da-fA-F]{4})"); // Used to grab the VID/PID components from the device ID string.

                // Iterate over all PNP devices.
                using (var QueryPnp = new ManagementObjectSearcher(@"\\.\root\cimv2", string.Format("Select * FROM Win32_PNPEntity"), new EnumerationOptions() { BlockSize = 20 })) {
                    foreach (var PnpDevice in QueryPnp.Get()) {

                        // Check if the DeviceId contains the tell-tale "IG_".
                        var DeviceId = (string)PnpDevice.Properties["DeviceID"].Value;
                        if (DeviceId.Contains("IG_")) {

                            // Check the VID/PID components against the joystick's.
                            var Ids = ParseIds.Matches(DeviceId);
                            if (Ids.Count == 2) {
                                ushort? VId = null, PId = null;
                                foreach (Match M in Ids) {
                                    ushort Value = ushort.Parse(M.Groups[2].Value, NumberStyles.HexNumber);
                                    switch (M.Groups[1].Value) {
                                        case "V": VId = Value; break;
                                        case "P": PId = Value; break;
                                    }
                                }
                                if (VId.HasValue && this.VendorId == VId && PId.HasValue && this.ProductId == PId) return true;
                            }
                        }
                    }
                }
                return false;
            }
        }

        /* ... */
    }
}

When the joysticks are enumerated they are only added to the input manager if they are not XInput devices.



To round up the entry, here's a screenshot of a minesweeper clone I've been working on in BBC BASIC.

2009.01.03.01.gif

You can view/download the code here and it will run in the shareware version of BBC BASIC for Windows. The code has been deliberately uglified (cramming multiple statements onto a single line, few comments, trimmed whitespace) to try and keep it within the shareware version's 8KB limit as this is a good limit to keep in mind for the TI-83+ version too.

Sega Master System emulation in Silverlight

Monday, 15th December 2008

I've had to quickly learn Silverlight for work recently, which has been an interesting experience. I've had to write new code, which is fine but doesn't really excite me as far as Silverlight is concerned - it doesn't really matter which language new code is developed in, as long as it gets the job done.

What does interest me more is that Silverlight is ".NET in your browser", and I'm a big fan of .NET technology with a handful of .NET-based projects under my belt. Silverlight therefore gives me the opportunity to run these projects within the browser, which is a fun idea. smile.gif

To this end, I've turned Cogwheel, a Sega 8-bit system emulator, into a Silverlight application. It took about an hour and a half, which was not as bad as I'd expected! (Skip to the bottom for instructions for the demo).

Raster graphics

Silverlight's raster graphics support is somewhat lacking. You can display raster graphics in Image elements, but - as far as I can see - that's about it. If you wish to generate and display images dynamically via primitive pixel-pushing, you're out of luck as far as Silverlight's class library is concerned.

Thankfully, Ian Griffiths has developed a class named PngGenerator that can speedily encode a PNG from an array of Colors that can then be displayed in an Image. Cogwheel's rasteriser returns pixel data as an array of integers so there's a small amount of overhead to convert these but other than that it's easy to push pixels, albeit in a fairly roundabout manner.

Render loop

The render loop is based around an empty Storyboard that invokes an Action every time it completes then restarts itself.

using System;
using System.Windows;
using System.Windows.Media.Animation;

namespace Cogwheel.Silverlight {

    public static class RenderLoop {

        public static void AttachRenderLoop(this FrameworkElement c, Action update) {
            var Board = new Storyboard();
            c.Resources.Add("RenderLoop", Board);
            Board.Completed += (sender, e) => {
                if (update != null) update();
                Board.Begin();
            };
            Board.Begin();
        }

        public static void DetachRenderLoop(this FrameworkElement c) {
            var Board = (Storyboard)c.Resources["RenderLoop"];
            Board.Stop();
            c.Resources.Remove("RenderLoop");
        }
    
    }
}

I'm not sure if this is the best way to do it, but it works well enough and is easy to use - just grab any FrameworkElement (in my case the Page UserControl) and call AttachRenderLoop:

private void UserControl_Loaded(object sender, RoutedEventArgs e) {
    this.UserControlRoot.AttachRenderLoop(() => {
        /* Update/render loop in here. */
    });
}

Missing .NET framework class library features

This is the big one; Silverlight does not cover the entire .NET framework class library, and so bits of it are missing. Fortunately this can be resolved, the difficulty depending on how you want the functionality of the original app to be affected.

Missing types you're not interested in.

These are the easiest to deal with, and this includes attributes and interfaces that the existing code uses that you're not especially interested in. For example, Cogwheel uses some of .NET's serialisation features for save states - a feature I wasn't intending on implementing in the Silverlight version. The [Serializable] and [NonSerialized] attributes are not available in Silverlight, nor is the IDeserializationCallback interface. To get the project to compile some dummy types were created.

namespace System {

    class SerializableAttribute : Attribute { }
    class NonSerializedAttribute : Attribute { }
    
    interface IDeserializationCallback {
        void OnDeserialization(object sender);
    }

}

Missing types or methods that you don't mind partially losing.

Cogwheel features some zip file handling code that uses System.IO.Compression.DeflateStream, a class not available in Silverlight. Rather than remove the zip classes entirely (which would require modifications to other files that relied on them) it was easier to use conditional compilation to skip over the DeflateStream where required.

switch (this.Method) {
    case CompressionMethod.Store:
        CompressingStream = CompressedStream;
        break;
    
    #if !SILVERLIGHT
    case CompressionMethod.Deflate:
        CompressingStream = new DeflateStream(CompressedStream, CompressionMode.Compress, true);
        break;
    #endif
    
    default:
        throw new NotSupportedException();
}

Missing instance methods.

C# 3.0 adds support for extension methods - user-defined methods that can be used to extend the functionality of existing classes that you cannot modify directly. Silverlight is missing a number of instance methods on certain classes, such as string.ToLowerInvariant();. By using extension methods the missing methods can be restored.

namespace System {

    public static class Extensions {
        public static string ToLowerInvariant(this string s) { return s.ToLower(CultureInfo.InvariantCulture); }
        public static string ToUpperInvariant(this string s) { return s.ToUpper(CultureInfo.InvariantCulture); }
    }

}

Missing static methods.

These are the most work to fix as extension methods only work on instance methods, not static methods. This requires a change at the place the method is called as well as the code for the method itself.

I've got around this by creating new static classes with Ex appended to the name then using using to alias the types. For example, Silverlight lacks the Array.ConvertAll method.

namespace System {

    static class ArrayEx {
        public static TOut[] ConvertAll<TIn, TOut>(TIn[] input, Func<TIn, TOut> fn) {
            TOut[] result = new TOut[input.Length];
            for (int i = 0; i < input.Length; i++) {
                result[i] = fn(input[i]);
            }
            return result;
        }
    }

}

First, a replacement method is written with Ex appended to the class name. Secondly, any file that contains a reference to the method has this added to the top:

#if SILVERLIGHT
using ArrayEx = System.ArrayEx;
#else
using System.IO.Compression;
using ArrayEx = System.Array;
#endif

Finally, anywhere in the code that calls Array.ConvertAll is modified to call ArrayEx.ConvertAll instead. When compiling for Silverlight it calls the new routine, otherwise it calls the regular Array.Convert.

Demo

The links below launch the emulator with the selected ROM image.

To run your own ROM image, click on the folder image in the bottom-right corner of the browser window to bring up a standard open file dialog.

Zip files are not handled correctly, but if you type *.* into the filename box, right-click a zip file, pick Open, then select the ROM from inside that it should work (it does on Vista at any rate).

The cursor keys act as you'd expect; Ctrl or Z is button 1/Start; Alt, Shift or X is 2 (Alt brings up the menu in IE). Space is Pause if you use an SMS ROM and Start if you use a Game Gear ROM. Keys don't work at all in Opera for some reason, but they should work fine in IE 8 and Firefox 3. You may need to click on the application first!

Issues

There are a number of issues I have yet to address. Performance is an obvious one; it's a little choppy even with 100% usage of one the cores on a Core 2 Duo. Sound is missing, and I'm not sure what Opera's doing with keys. Other than that, I thought it was a fun experiment. smile.gif

Once I've tidied it up a bit I'll merge the source code with the existing source repository.

3D glasses and CPU cycle counting

Monday, 7th April 2008

I reintroduced joystick support to the emulator front-end over the weekend, using a much more sensible input manager. The control editor form uses the same interface design for keyboard and joysticks - you click the button you wish to edit, it appears pressed, you press the key (or joystick button) you wish to bind to it and it pops back out again. It'll also check to see if you've moved the joystick in a particular direction on any of its reported axes.

Another feature I added was better support for games that used the 3D glasses, using a simple red-cyan anaglyph output blender.

MazeHunter3D.png    PoseidonWars3D.png

BladeEagle3D.png    SpaceHarrier3D.png

If the memory address range that the glasses respond to has been written to within the last three frames, it switches to the blending mode.

Irritatingly, with one glaring bug fix I managed to lower overall compatibility. Some instructions weren't being timed correctly (or at all) meaning that the emulator was executing too many instructions for the number of clock cycles it was asked to run. During the time each video scanline is run, 228 CPU cycles are executed. Upping this to 248 cycles (just for experimentation) fixes all known bugs - including the long-standing flickering pixels at the top of the screen in Cosmic Spacehead and display corruption in GP Rider. (However, digitised speech plays at a noticably higher pitch).

I'm not entirely sure why this is - it could be that some instructions are claiming to take too long, it could be yet another interrupt problem. To call this mildly frustrating is a bit of an understatement!

Sound, at long last.

Friday, 7th March 2008

I have finally got around to adding sound to Cogwheel using Ianier Munoz's waveOut API wrapper.

RistarSoundTest.png

The technique used is fairly simple. I start with a sound buffer that is a multiple a number of video frames in length (1/60th of a second is one frame) - four seems a good number. This buffer needs to be periodically topped up with sound samples (every four frames in the above example).

I run the emulator for one frame, then generate a frame's worth of audio. I add these samples to a queue. The sound callback then periodically dequeues these samples and appends them to its buffer.

// This is called once every video frame.
// 735 samples at 44100Hz = 1/60th second.
// (Multiplied by two for stereo).
this.Emulator.RunFrame();
short[] Buffer = new short[735 * 2];
this.Emulator.Sound.CreateSamples(Buffer);
this.GeneratedSoundSamples.Enqueue(Buffer);

The important thing is that the sound is always generated after the video frame (and thus after any hardware writes). I log writes to the sound hardware over the period of a frame (along with the number of CPU cycles that have elapsed), then space them out when generating the sound samples so that they play in synch. My previous problems were caused by the sound emulation trying to "look ahead" past what had already been generated.

However, there is a potential problem with this - as the video and sound emulation are not locked in synch with eachother, there are two cases that could crop up:

  1. The emulator runs faster than 60Hz, generating too many sound samples.
  2. The emulator runs slower than 60Hz, not generating enough sound samples.

The first is the easiest to deal with. In most instances you'd want a couple of extra frames of sound data left in the queue after topping up the sound buffer, in case in the next period not enough are generated. However, if I notice that the queue is longer than entire sound buffer after topping it up, I clear it completely. This would make the sound a little choppy, but so far this hasn't happened in my tests.

The latter is a little more complex. If I just left it the sound buffer would have gaps in it, causing noticable pops (this I have noticed in some of the more processor-intensive games). To cover up the gaps, I generate enough extra frames of sound data to fill the gap. As no sound hardware writes are made, this has the effect of extending any tones that were currently playing, so the sound will play back slightly out of time. However, slightly out of time by a few 60ths of a second is a better solution than a pop.

// This is called when the sound buffer needs topping up.
// That's about once every four frames.
private void SoundBufferFiller(IntPtr data, int size) {

	// Temporary buffer to store the generated samples.
	short[] Generated = new short[size / 2];

	for (int i = 0; i < Generated.Length; i += 735 * 2) {
		if (this.GeneratedSoundSamples.Count > 0) {
			// We've already queued up some sound samples.
			Array.Copy(this.GeneratedSoundSamples.Dequeue(), 0, Generated, i, 735 * 2);
		} else {
			// Erk, we're out of samples... force generate some more and use those instead.
			// (This avoids popping).
			short[] Temp = new short[735 * 2];
			this.Emulator.Sound.CreateSamples(Temp);
			Array.Copy(Temp, 0, Generated, i, 735 * 2);
		}
	}

	// Copy to the sound buffer.
	Marshal.Copy(Generated, 0, data, size / 2);
	
	// If too many samples are being generated (FPS > 60Hz) then make sure it doesn't go out of control.
	while (this.GeneratedSoundSamples.Count > this.SoundBufferSizeInFrames) this.GeneratedSoundSamples.Dequeue();

}

Joysticks and Game Genie codes

Monday, 3rd March 2008

The biggest update to the emulator relates to input.

ConfigureControls.png

I've added a new control panel that lets you customise key bindings. For the keyboard, you simply click on the button you wish to customise, then press the key on your keyboard you wish to bind. As you can probably tell from the screenshot, I've also added joystick support (via the Multimedia Joystick Functions with a bit of P/Invoke for ease) which means that with a simple adapter and a free driver you can use original SMS pads with the emulator.

I haven't added support for the POV hat, which needs doing as the PPJoy PlayStation controller driver exposes the d-pad as a POV hat.

GameGenieEditor.png

I've also added an interface for adding Game Genie codes. The Game Genie was a cheating device that could be used to patch memory.

I have also started reintroducing (albeit in a slightly buggy fashion) multiple memory devices. Previously I've just been emulating a cartridge ROM and work RAM; the real consoles had multiple memory devices including a card slot, ROM BIOS and expansion slots. By emulating this system one can boot with the original BIOS, and (for example) not insert a cartridge to watch the BIOS animation or play its integrated game.

For ease, there's a quick-load ROM menu option that will load anything into the cartridge slot and disable all others for quick game playing. For those who wish to play with the hardware more, there's an "advanced load" dialog that will let you pick what goes in which slot, and also force various options (such as locale, hardware model, video system and so on) instead of the automatically guessed options. I'd also like it to be able to pick patch files (such as translations) so you don't need to use an external tool. Currently this dialog only supports cartridge ROM and BIOS ROM loading, though.

Cogwheel on Google Code

Sunday, 24th February 2008

On bakery2k1's suggestion, I took a look at my sprite collision flag code. It only checked for collisions between sprites appearing in the foreground - sprites that were hidden behind a background tile weren't checked, which caused the Fantastic Dizzy bug.

FantasticDizzyFixedSpriteCollision.png

I have decided to give Google's free project hosting a whirl. As usual, they can't get it working in Opera flaming.gif (no big surprises there) and it's horrifically slow, but I can't really complain given that it's free, and the issue tracking is pretty handy.

Gauntlet.png

Another game that has been fixed is the conversion of that arcade classic, Gauntlet. This was partially due to buggy controller port emulation, but a long-standing bug has been the lack of the y-scrolling inhibition flag. Games could set a flag on the VDP that would prevent the rightmost 64 pixels from scrolling vertically - this is useful to create a locked status bar on the right for a vertical scrolling game, for example.

SmsVdpTest1.png SmsVdpTest2.png

I'm slowly getting there with FluBBa's VDP tester...

/INTerminable Interrupt Problems

Friday, 22nd February 2008

WCLBGolf1.png WCLBGolf2.png
World Class Leaderboard Golf

Well, I've just about got it the way it was before with the new interrupt code; most games work again, but the few that have always given problems (such as Desert Speedtrap) still don't work properly. sad.gif I think this is the stage where I have to start trawling through disassemblies to try and work out why they're not working.

CosmicSpacehead.png
Cosmic Spacehead - one of the few games to use the 256×224 mode.

One problem I still haven't got to the bottom of is with the Dizzy games. They either reset when starting a new game or lock up. I'm hoping this is a problem with my implementation of the Codemasters mapper. I guess that The Excellent Dizzy Collection has a simple front-end game selection screen that switches to the requisite ROM page for the selected game, then jumps to the start of that page - in my case this jumps back to the initial Codemasters screen.

I'm not sure where Fantastic Dizzy's problems originate. It looks like an interrupt problem (maybe the palette should be switched when the VDP has finished with the status bar at the top?), but could also be related to the other Codemasters problems.

Interestingly, Micro Machines (1 and 2) and Cosmic Spacehead - both using the Codemasters ROM mapper - seem to work fine.

Interrupts: A Fresh Start

Thursday, 21st February 2008

I gave in and rewrote all of the Z80's interrupt emulation from scratch, finding some rather horrible bugs in the existing implementation as I went.

Some of the highlights included non-maskable interrupts ignoring the state of the IFF1 flag (this flag is automatically cleared when an interrupt is serviced, and is used to prevent the interrupt handler from being called again before it has finished) and the RETN instruction not copying the state of the IFF2 flag back to IFF1. When non-maskable interrupts are serviced, the state of IFF1 is copied to IFF2 before it gets cleared, the idea being that if you use RETN interrupts are automatically re-enabled on exit of the NMI ISR. (Contrast this with maskable interrupts, where both flags are cleared, and you need an explicit EI to re-enable them).

The HALT instruction (executes NOPs until an interrupt is requested or the CPU is reset) was also completely incorrectly (and bizarrely) implemented. The rewrite just sets a Halted property, which prevents the CPU from fetching or executing any instructions. The interrupt-triggering code simply resets this property.

This has fixed numerous bugs (I'm not sure when they were introduced, as it was all working a while back). It's gone from "not working at all" to "just about working", but some games or demos that rely on precise interrupt timing don't work properly.

HicolorDemoCorruption.png DesertSpeedtrapMisaligned.png
Game Gear Hicolor Demo and Desert Speedtrap

Both problems in the above screenshots relate to line-based interrupts from the VDP (Video Display Processor). Some other games simply hang at startup. sad.gif

Loading ROMs

Wednesday, 20th February 2008

OutRunEuropa1.png OutRunEuropa3.png
OutRun Europa

I've been adding a series of new features to Cogwheel. I wrote a basic zip file class a while back (only supporting store and deflate compression - the majority of zip files use deflate), and have added a Utility namespace with methods for help with loading ROMs.

For example, there is a method that is passed a filename by reference, and it'll return an array of bytes of the loaded file. If the passed filename was a zip file, by any chance, it'll search for ROMs inside the zip, and modify the filename (so it might end up as C:\Path\SomeFile.zip\Game.sms).

Once this is done, the CRC-32 checksum of the file is calculated then looked up against a database of known ROM dumps. I'm currently using the SMS Checker .romdata files as the database source. These contain information about known bad dumps, and can be used to strip headers and footers, patch bytes and remove redundant overdump data to end up with a valid ROM image.

Finally, the mapper is detected. Currently, I have three mappers - RAM (which is just 64KB of RAM), Standard (the standard Sega Master System ROM mapper) and Codemasters. I use the RAM mapper for all SG-1000 and SC-3000 games (I'm not sure what they're meant to use, here, but some games - such as The Castle - don't work with the standard SMS mapper).

The Codemasters mapper uses a different method to swap pages, which means that the conventional SMS BIOS checksumming code fails to swap in pages correctly and thus doesn't check the entire cartridge. The games therefore have their own checksumming routines and store their own checksum in a Codemasters-specific header; this makes detecting Codemasters games fairly easy, as all you need to do is check for the extra Codemasters checksum.

As the BIOS doesn't check the checksum, the cartridge provides code to do so itself. You can run it by holding 1 and 2 down as the game boots.

ExcellentDizzyCollectionChecksum.png
The Excellent Dizzy Collection
MicroMachinesChecksum.png
Micro Machines

I seem to have broken interrupts somewhere along the line; most noticable are non-maskable interrupts (as generated by the Pause button) which crash the emulator (it appears that once they fire, interrupts are never re-enabled). I'm not sure what's causing this, but hopefully it won't be too unpleasant to fix!

Necromancy

Friday, 8th February 2008

After seeing Scet and Drilian's work on their respective emulator projects I decided I needed to do something with the stagnating Cogwheel source on my hard disk drive.

The only ROM I have tested where I can't find an explanation for a bug is the Game Gear Garfield: Caught in the Act game. Like many games, when left at the title screen it'll run a demo loop of the game in action. At one point Garfield would walk to the left of the screen, jump over a totem pole, shunt it to the right and use it as a way to jump out of a pit. However, in Cogwheel he would not jump far enough to the left, and not clearing the totem pole he'd just walk back to the right and not have anything to jump out of the pit on.

I remembered a post on MaxCoderz discussing a long-standing tradition of thinking that when a JP <condition>, <address> failed the instruction took a single clock cycle. You can see this misreported here, for example. This document, on the other hand, claims it always takes 10 clock cycles - and most importantly of all, the official user manual backs this up.

Garfield.png

So, Garfield can now get out of his pit. The user interface has changed (again) - I'm now using SlimDX to dump pixels to a Panel, which seems to be the least hassle distribution-wise and doesn't throw LoaderLockExceptions.

Subscribe to an RSS feed that only contains items with the Sega Master System tag.

FirstLast RSSSearchBrowse by dateIndexTags