Game Drivers

From MAMEDEV Wiki
Revision as of 21:28, 16 July 2008 by Aaron (talk | contribs) (New page: Probably the most common question when it comes down to how MAME works is: how do I write a driver? In order to understand how to write a driver, you need to understand how drivers are con...)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Probably the most common question when it comes down to how MAME works is: how do I write a driver? In order to understand how to write a driver, you need to understand how drivers are connected into MAME, and what the various driver-related pieces of the puzzle are. This article should provide you with a basic overview of what goes into a driver at the topmost level. Future articles will address many of the details beyond that.

So, to get started, you will want to take a peek at the code in driver.c. Essentially all this module does is produce a list of pointers to all the drivers supported by MAME. The problem is that for each driver, we need to first declare it:

extern struct GameDriver driver_puckman;
extern struct GameDriver driver_puckmana;
...

and after all the drivers have been declared, then we need to declare a list of the drivers like this:

const struct GameDriver *drivers[] =
{
    &driver_puckman,
    &driver_puckmana,
    ...,
    0
};

This is a very annoying fact of using C, because it means for each driver we would need to add two things to driver.c, a declaration at the top, and an entry in the list at the bottom. In fact, we did this for many years before we came up with some whizzy preprocessor magic to simplify things. If you're a C guru, you can probably figure it out, but if not, never fear. The important thing is that to specify a driver to be added to the master list, you only need to declare it once using the special DRIVER macro, like so:

DRIVER( puckman )
DRIVER( puckmana )
...

With those entries inserted into the list alongside all the other entries, your driver is now officially referenced by the master driver list in MAME! Of course, this now means that you actually need to have a GameDriver object with the same name living elsewhere in MAME in order to successfully link the application. So how exactly do you define a game driver?

The basic GameDriver structure is pretty simple. It provides a very basic list of information about a given driver, which mostly amounts to descriptive data (game name, manufacturer, year, etc.) and a few pointers to other structures which actually describe the hardware of the particular game. Rather than defining and populating a GameDriver structure yourself, you should use one of the provided macros which fill in all the details in a more easy-to-use fashion. If you look at the bottom of any of the files in the drivers directory, you will see a number of these macros specifying the drivers defined in that file:

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )
...

The main disadvantage to using a macro like this is that it is not immediately obvious what each parameter is for. A number of them have the same name even ("pacman"). This is something you get used to, however. Going through the entries from left to right, here is what is being defined:

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The first parameter (1980) is obviously the year that MAME specifies for the game. In general, we try to use the copyright year as shown in the game itself as the "definitive" year. It may not be perfectly accurate, but it is usually close, and this definition allows us to at least be consistent across games. For games that don't display a copyright, we have to guess at the year using anecdotal evidence collected elsewhere.

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The second parameter (puckman/puckmana) is the name of the driver itself. This name should match the name you use in the DRIVER() macro in driver.c. Both the GAME() macro here and the DRIVER() macro in driver.c will prepend "driver_" before the name you specify here in order to keep a consistent naming scheme on all the game drivers in the system. Thus, in the example above, this will expand to driver_puckman and driver_puckmana, which will match the naming scheme described in the previous section.

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The third parameter (0/puckman) is the name of the "parent" driver. In MAME, a "parent" driver is the driver for the most recent version of a game. The parent driver always specifies 0 for this parameter. All other versions of a game are considered "clones", and must specify the name of the parent in this field. In the example above, puckman is the parent driver, and puckmana is the clone driver. As with the driver name parameter, the GAME macro will automatically prepend "driver_" before the name you specify here.

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The fourth parameter (pacman) is the name of the machine driver, which describes the hardware that makes up the arcade machine. Machine drivers are worth a whole article themselves, so they won't be discussed in much detail here. It is quite common for multiple game drivers to all share one machine driver, especially on systems that had a common set of hardware for a number of games. One interesting aspect of machine drivers is that, unlike game drivers which are represented by a GameDriver struct, machine drivers are not represented by a struct but rather are constructed at runtime by code that is generated by macros. This means that this fourth parameter in the GAME macro points not to a struct, but rather to a "constructor" function. Again, in order to maintain consistency, the GAME() macro will prepend "construct_" to the front of any name you specify here. In the example above, the macro will expand this parameter into a pointer to the function construct_pacman, which will be defined later.

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The fifth parameter (also pacman) is the name of the input port definitions. Input ports will be described elsewhere, but in short, they describe how all of the various inputs to the game (controls and DIP switches) are mapped. Like the machine driver, input ports are constructed at runtime by code, and this macro prepends "consruct_ipt_" to the front of the name you specify here. In the example above, the macro will generate a pointer to the function construct_ipt_pacman.

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The sixth parameter (0 in this case, but present in a number of other drivers) is the name of the driver-specific initialization function. This function is intended to be used to perform code decryption, if necessary, or to dynamically alter the memory maps of the CPUs in ways that are specific to the particular game driver. The reason this is useful is because most of the game's hardware is controlled by the machine driver definition, which is often shared among multiple game drivers. But in reality, even when games ran on the same hardware, there were often minor differences between them. The init function allows for driver-specific tweaks to be applied before the virtual hardware is set up. The macro will prepend "init_" to the front of the name you give here.

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The seventh parameter (ROT90 here) specifies the orientation of the game's primary monitor. All games are actually designed to output their display to a standard TV-shaped display. Games like Pac Man, however, have the monitor rotated 90 degrees clockwise in the cabinet. All the graphics in the game are internally rotated to account for this so that it looks right-side up after rotation. Similarly, there are a number of games that were designed to have the monitor rotated 90 degrees counterclockwise. There are also some games that were designed to be reflected from a mirror, and so everything is drawn in mirror image. All of these transformations can be described by this orientation parameter.

The orientation parameter actually consists of 3 bits, which are all controllable independently. The first bit (ORIENTATION_FLIP_X) indicates that the display should be drawn in a mirror image left-to-right. The second bit (ORIENTATION_FLIP_Y) indicates that the display should be drawn in a mirror image top-to-bottom. And the third bit (ORIENTATION_SWAP_XY) indicates that the display should be drawn in a mirror image across the diagonal from the top-left corner to the bottom-right corner. If you think about things hard enough, you will realize that combining these features together, under the assumption that SWAP_XY happens first, can describe all possible rotations and mirrorings. For example, a 90 degree clockwise rotation is a SWAP_XY combined with a FLIP_X.

For the most part, this complexity is hidden from you, so you can use the predefined combination macros ROT0, ROT90, ROT180, and ROT270 to indicate no rotation, 90 degrees clockwise rotation, 180 degrees rotation (flip X and Y), and 90 degrees counterclockwise rotation, respectively.

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The 8th parameter ("Namco") is the name of the manufacturer. Unfortunately, this isn't always as easy to determine as it seems. Many times, games were licensed from one manufacturer to another. For example, Pac Man was licensed to Midway for release in the U.S. In this case, the manufacturer should be specified as "[Namco] (Midway license)". Things get even messier for bootlegs. Half the time, bootleggers just tweaked a character or two in the name, and aren't legitimate companies worth mentioning. For the most part, bootlegs use "bootleg" or "hack" as the manufacturer name.

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The 9th parameter ("PuckMan (Japan set X)") is the full friendly name of the game. This name should represent the name of the game as it is displayed in the attract mode, if possible, followed in parentheses by any version or region information. In this case, the region (Japan) is specified, along with some distinguishing details between multiple versions. In this case, as with many arcade games, the actual version numbers are never specified, so we are left to make up our own versioning scheme without really knowing the full details of the differences.

GAME( 1980, puckman,  0,       pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)", GAME_SUPPORTS_SAVE )
GAME( 1980, puckmana, puckman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)", GAME_SUPPORTS_SAVE )

The final parameter (GAME_SUPPORTS_SAVE) is one or more flags describing the status of the driver. If no flags apply, a value of 0 is simply placed in this field. If multiple flags are desired, they are ORed together. The current set of flags and their meanings are listed below:

  • GAME_NOT_WORKING — means that the game is not fully working; this could be due to anything from incomplete emulation to a very subtle bug that prevents the game from being played through to the end
  • GAME_UNEMULATED_PROTECTION — means that there is some form of anti-piracy protection in the game which is preventing it from working fully; this flag is almost always used in conjunction with GAME_NOT_WORKING
  • GAME_WRONG_COLORS — means that the color decoding used by this game is not understood at all; often this is due to a missing color PROM which is usually needed to understand how the colors are mapped
  • GAME_IMPERFECT_COLORS — means that color decoding is generally understood, but there may be some edge cases or other issues that prevent the color from being correct in all cases, such as missing shadows or hilights
  • GAME_IMPREFECT_GRAPHICS — means that there are some known problems with the graphics display; again, this can range from very obvious problems (such as nothing being displayed!) down to very subtle problems (incorrect priorities and other issues)
  • GAME_NO_COCKTAIL — means that the game supports cocktail mode, but either the mechanism for enabling it is not understood or else the driver author was too lazy to implement support in the video system for it
  • GAME_NO_SOUND — means that the game has no sound support whatsoever; often this is because the sound hardware is not known
  • GAME_IMPERFECT_SOUND — means that the sound emulation for a game is incorrect in some way; this can range from some missing sound chip emulation to missing filters to any of a number of other audio problems
  • GAME_SUPPORTS_SAVE — means that the driver has been verified at the code level and via testing to support save states; if you use the -autosave option on the command line, games with this flag will be automatically saved when you exit and will automatically pick up from that save state the next time you run them
  • NOT_A_DRIVER — means that the given game entry is not a game in itself, but rather a BIOS entry that serves as a parent to a collection of games that share the same core BIOS