Machines define most of their hardware configuration in the form of an "md" file. Long ago, this was done by directly writing a .h file with the required information. The .h file approach was more cumbersome and somewhat error- prone.
The md file is "compiled" by the script genmachine, which writes out a .h file and also some .c files that get linked in to the final program. genmachine will do some consistency checking on the input to make sure it is sane.
It will enforce naming consistency between identifiers, declarations, and strings. For example, the start button gets a define for its switch number (SW_START_BUTTON), an identifier for callback events (sw_start_button), and a string in the menus ("START BUTTON"). gendefine can produce all of these automatically from a single "Start Button:" declaration in the .md file.
Though the syntax is fairly strict, there is a bit of magic in how different categories need to be written. The easiest way to write a config for a new game is to copy from another one. The Twilight Zone and World Cup Soccer configs are the most complete. There is more documentation in those files on how things should be set up.
The "md" file is just a text file. Blank lines and lines beginning with a '#' are ignored, like in a shell script.
Long lines can be broken up like in C, with a backslash at the end of each line. A comma also acts as a line continuation character, which is useful when defining long lists.
The file is divided into sections, which begin with a section header in square brackets. For example:
global-statements [section1] section1-statements [section2] section2-statements
Only section names that are known to genmachine are permitted. The list of
permitted sections is listed near the top of the genmachine
script, and
includes: switches, lamp, drives, gi, lamplists, containers, etc.
Notice that there is a global section that is in effect at the top of a file, before you declare any sections.
Within a section, you declare items that fit that category. All declarations take this form:
key: property1 [, property2...]
A declaration begins with a key, followed by a colon, followed by a comma- separated list of properties. The key and/or properties can contain any characters, including spaces, but not commas or colons.
There are two types of declarations: fixed objects and dynamic objects. They are written similarly but not exactly the same.
In one style, used mostly for hardware layout, the key refers to the object's physical identification, such as the switch, lamp, or solenoid number. Here the key is generally numeric, although for solenoids the syntax is slightly different to specify the solenoid bank. The first property of these declarations should always be the human readable name of the object. Depending on the type of declaration, genmachine can validate that the key name is valid.
An example:
[switches] 14: Tilt, tilt, ingame, noplay
The key, 14, identifies which switch is being described. For switches and lamps, the key is given as a pair of column/row digits, as it would be listed in the game manual. The first property, Tilt, gives it a name. This is used to generate the defines and strings related to the switch.
Everything else identifies the properties of the switch. Different types of objects will have different properties. Here, we say three things: (1) it is an instance of a well-known switch class, called 'tilt'; this ties it directly to system code for processing tilts automatically. (2) The tilt switch should only be serviced during a game. (3) A closure does not mark valid playfield.
In the second type of object definition, there is no physical identifier, and so the name before the colon is the human readable name. These are generally used for software constructs. For these, genmachine automatically assigns a number based on the order of the declarations. There can be an unlimited number of objects of these types, unlike those tied to hardware where there is a physical limit.
For example:
[deffs] Multiball Start: page(MACHINE_PAGE), PRI_GAME_QUICK6
This defines a display effect for multiball start. A #define is generated, DEFF_MULTIBALL_START, which is a numeric ID used to refer to the effect. The IDs for all deffs are assigned sequentially, and do not need to be specified as with the switches above. Everything following the colon is treat as a property just as above.
This example also shows a variation in the property syntax. Above, we saw properties 'ingame' and 'noplay', which are binary properties: just stating them causes them to be turned on. Binary properties can be listed in any order; genmachine knows what all of the allowable binary properties are and will handle them correctly. In the deff declaration above, there is a 'page' property, which is not binary – it has a value, MACHINE_PAGE. For these valued properties, the syntax is always ‘variable(value)’.
The 'include' directive can be used to include config syntax from another file, much like a C '#include'. This is used to bring in common definitions for the platform, that can be shared across games.
For example, no game defines a switch entry for the "ALWAYS CLOSED" switch, which is the same in every WPC game. This can be put into a file shared by all machines.
By convention, the machine-specific file includes the platform-specific file, which may itself include other files. The WPC platform provides different files for the different hardware generations, one per variation and one that is common for all.
All include files are read and parsed before any of the output is generated. It is thus possible to override definitions that were seen in an earlier include. The default WPC md file provides names for all of the switches and lamps, so if you omit one from the machine file, you get a default definition. The tester ROM uses this facility.
The 'define' directive is used for miscellaneous settings. It gets translated to a C '#define' in the output file mach-config.h. For example,
define MACHINE_NUMBER 531
is converted to:
#define MACHINE_NUMBER 531
Note that the pound sign is not included in the mdfile, as it would be treated as a comment.
Certain things need to be defined in the global sections, using Key: Value syntax. The human readable name of the machine, the system type, and a few other things can be given here. These do not appear directly in the mach-config.h, but are used by genmachine to guide the compilation. Again, see any existing config file for an example.
Here is a list of the sections that can appear.
switches
The possible attributes for switches are:
opto
edge
noplay
ingame
intest
button
noscore
Additionally, these attributes are used to tag special switches. There should only be one switch of each type.
outhole
shooter
tilt
slam-tilt
buyin-button
launch-button
start-button
trough-stack
lamps
These attributes are used to tag special lamps:
start
buyin
extra-ball
shoot-again
drives
flash
attribute to say
which ones are flashers. Use motor
to say which ones are motors.
These attributes are used to tag special drives:
ballserve
knocker
launch
gi
lamplists
[lamps]
section;
a range of lamps in the form lamp_start..
lamp_end; or another
lamplist name, allowing for nested declarations.
containers
The trough container should be marked with the attribute trough
.
templates
driver
that
implements it. The remaining properties give values to all of the driver's
parameters.
tests
deffs
leffs
adjustments
audits
system_sounds
system_music
highscores
flags
globalflags
scores
fonts
timers
genmachine is a Perl script. It parses all of the md commands and builds a giant hash with all of the data. genmachine is invoked multiple times with different options, requesting that different output files be generated. All of the output files are C or H files put into the 'build' subdirectory, which are then compiled normally.
For the Perl programmer, each object declaration is itself a Perl anonymous hash, where each property of the object is one of the hash entries. Using the variable(value) syntax, it is possible to put anything into the object definition. However, only certain keys are recognized by the output functions. Adding new properties generally doesn't require a parser change, but only a change to the output routines. Binary properties and well-known object classes do need to be stated – there are constant tables at the top of the script that declare these.