It is assumed that you've already installed the ER5 SDK. Note that the
ER5 SDK may be installed on any drive. Nowhere in this document or in any
of the scripts will be refered to absolute drive letters but rather in
paths relative to the root directory.
Getting the source code
EMame is based on XMame so you'll need the XMame source distribution
to build EMame. Also, you'll need the zlib package as well. Here's where
you can get them:
1) Latest EMame version
2) XMame version 0.37b7.1
3) The source code for zlib 113 (failing that, look for it on the Zlib home page)
First, unzip XMame on \ afterwards rename the directory \xmame-0.37b7.1
to \mame (note: not \emame !).
Next unzip zlib into \mame\src\zlib and finally EMame into \. Note that emame will overwrite 3 exisiting files in \mame\src and subdirectories of \mame\src\cpu.
Your directory structure should now look something like this (most sub directories are not shown):
\mame\App (<-- EmameApp.App)Building instructions
\mame\src\epoc (<-- Emame.exe)
There are 2 seperate builds: EMameApp.app and EMame.exe,
these are discussed below.
For a wins build the steps are:
makmake emameapp wins
nmake -f emameapp.wins
For marm the steps are quite similar:
makmake emameapp marm
nmake -f emameapp.marm
Building EMame.exe is a complex process. I strongly recommended you build it as is shown below. If you interested in how the actual build comes together read the next section.
cd \mame\src\epoc\groupand for marm
open \mame\src\epoc\group\emame.dsp in DevStudio 5
You may safely ignore all the warnings you see flying by, start worrying if you encounter errors. If that happens send me an email!
The gory details
You may safely skip this section if you are only interested in the binaries. However, reading on will gain you some handy insights in the EMame build process.
About the MAME build process
The MAME build process revolves around makfiles. These makefile will build the following libraries:zlib, sound,sndhrdw, vidhrdw, cpus, drivers, machine, core and unix. Of these Unix and zlib are of no particular interest to us. Unix contains the OS dependent source code and is therefor not used in EMame and Zlib is a straitforward standalone library. Core contains the stuf that is needed regardsless of what game or hardware MAME support; it consists of all the source files in \mame\src. Ultimately, these libraries are combined into one big executable file.
By default, MAME compiles almost everything by including support of nearly all games and all hardware it needs to emulate. The end result is an executable file of many MBs. This is unwanted in EMame for two reasons. Firstly, when running EMame on say the Series 5mx most games are not playable because the game runs on hardware that is more CPU intensive to emulate. The other reason is that we'd like to keep the executable file size as small as possible by only including what's needed.
In order to understand how to steer to MAME build process we need to
understand how MAME is designed to support a particular game. This support
revolves around the concept of game drivers. Each game driver
declares a global variable of type GameDriver which decribes what
is needed (rom files, hardware, CPUs etc) to run this game. The MAME core
maintains a global table of all these game drivers variables in \mame\drivers.c.
Each game driver has compile and link dependencies against source files in the modules: machine, sound, vidhrdw and drivers. This can easily be seen whan looking at the UN*X makefiles. For example, for pacman the makefile includes the following entry:
$(OBJ)/pacman.a: \Actually, this pacman library (.a extentions in UN*X are libraries, the .lib equivilant of windows) supports more than one game. It contains pacman clones and games running on the same hardare as well. Again, for each such game there is global variable in one of source files in the drivers directory. In the pacman example above you will find the supported games in \mame\drivers\pacman.c, \mame\drivers\jrpacman.c and \mame\drivers\pengo.c. Listed below is a snippet from pacman.c:
$(OBJ)/drivers/jrpacman.o $(OBJ)/vidhrdw/jrpacman.o \
$(OBJ)/vidhrdw/pengo.o $(OBJ)/drivers/pengo.o \
GAME( 1980, pacman, 0, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 1)" )
GAME( 1980, pacmanjp, pacman, pacman, pacman, 0, ROT90, "Namco", "PuckMan (Japan set 2)" )
GAME( 1980, pacmanm, pacman, pacman, pacman, 0, ROT90, "[Namco] (Midway license)", "Pac-Man (Midway)" )
GAME( 1983, vanvans, vanvan, vanvan, vanvans, 0, ROT270, "Sanritsu", "Van Van Car (Sanritsu)" )
GAME( 1982, alibaba, 0, alibaba, alibaba, 0, ROT90, "Sega", "Ali Baba and 40 Thieves" )
GAME is a fancy macro that expands into the declaration of a global variable of type GameDriver. The second parameter in this macro is the name of the game which is unique throughout MAME; if the game is clone, the third parament specifies the parent game. So, the first entry is a game called 'pacman' and the second is 'pacmanjp' which is a clone of pacman.
Apart from each game needing a particular set of source files to satisfy
compile and link dependencies, they also have dynamic dependencies on CPU
cores. CPU cores come in two flavours, general purpose CPUs, such
as the Z80 or the M68000, and sound CPUs. The first ones live in
sub directories of \mame\src\cpu, the second ones in the directory
For each general purpose CPU there is data structure, called cpu_interface, to describe the attributes of the CPU core. In the MAME core in \mame\src\cpuintrf.c there is a global table holding an entry for each supported general purpose CPU. In similar vein, the data structure for sound CPUs is called snd_interface and the MAME core maintains a table of them in \mame\src\sndintrf.c.
For each CPU core (either general purpose or sound) there is macro defined to say whether MAME should include support for this CPU. For example, to include support for the Z80 you must somwhere include the definition HAS_Z80=1. In MAME, all these macros are defined in the makefiles.
In cpuintrf.c (and in similar vein in sndintrf.c) each entry specifying a CPU core is guarded by an #ifdef with the CPU core's define. A snippet from cpuintrf.c is shown below:
struct cpu_interface cpuintf =The macro's CPU0 and CPU1 will expand into an cpu_interface entry which describes a particular CPU core. They will refer to functions which are defined in the CPU's according sub directory in \mame\src\cpu. Only if HAS_Z80 has been defined, the datastructure for the Z80 CPU will be both defined and included in the cpuintrf table. If not defined then there is no need to include any of the sources from the \mame\src\cpu\z80 directory.
CPU0(DUMMY, Dummy, 1, 0,1.00,0, -1, -1, 16, 0,16,LE,1, 1,16 ),
CPU1(Z80, z80, 1,255,1.00,Z80_IGNORE_INT, Z80_IRQ_INT, Z80_NMI_INT, 16, 0,16,LE,1, 4,16 ),
CPU0(Z80GB, z80gb, 5,255,1.00,Z80GB_IGNORE_INT, 0, 1, 16, 0,16,LE,1, 4,16 ),
1) In \mame\src\drivers.c there is a global table of all built in game drivers.
2) In \mame\src\cpuintrf.c there is a global table of all built in general purpose CPU cores (Z80, 68000 etc.).
3) In \mame\src\sndintrf.c there is a global table of all built in sound CPU cores.
4) Each game driver has link dependencies on source files from machine, sound, vidhrdw and drivers.
5) Each driver needs one or more general purpose CPUs and or sound CPUs.
6) There is no compile or link dependency between the game driver and the CPUs it needs. The game driver simply refers to the CPU by number and the MAME core resolves this to a CPU core via the CPU table in either \mame\src\cpuintrf.c or \mame\src\sndintrf.c.
7) For each CPU there is macro that specifies whether the CPU should be included in the build. Depending on this macro an entry will be included in a global table for this CPU core.
The EMame build process
As is clear from the above, the way to steer the final executable size by only including in the three tables for game drivers, general purpose CPUs and sound CPUs the entries that are actually needed.
I will spare you the details of all my failed attempts in setting up a suitbale build process for EMame. Instead, I'll describe the build process that succeded at the end. Note that since building EMameApp.app is so trivial it won't be discussed here, the focus is only on how to build emame.exe.
The build process I finally end up with will first build one huge static
library, called mameengine.lib, which has the exact same support
for game drivers and CPUs as MAME itself. The EPOC dependent source is
not included in this library but rather specified in the emame.mmp
project. Together with EPOC dependent source, emame.mmp also includes
a local copy of driver.c, cpuintrf.c and sndintrf.c.
In these local copies we selectively build the three global tables with
the global variables of games drivers and cpu interfaces we actually want
to support. This emame.mmp project will link against the huge
The major benefit of this approach is that I can control the supported games quite easily in the three copied source files (driver.c, cpuintrf.c and sndintrf.c) and let the linker do all the hard work in figuring out what to include in the final executable file. Also, the latter will guarantee we get the smallest executable size possible given the games drivers and CPUs we've included in the three global tables!
The three basic steps to do the above are:
1) Generating EPOC mmp files;
2) Building static library mameengine.lib;
3) Building emame.exe.
Note that the first step is particularly tricky and costed me some blood, sweat and tears. When building from source this first step can be ommited as I've included the generated mmp files in the source distribution. It is only usefull in upgrading EMame to newer versions of MAME as it allows for mmp file generation using a (semi-) automated process.
1) Generating EPOC mmp files
Most mmp files in EMame are generated from both static information and
information gathered dynamcically. The static information is specified
in the individual files in \mame\src\epoc\group\tmpl and
they contain the obvious things like target type, target name etc. The
dynamcic information is obtained from parsing the MAME makefiles. The information
extracted from these makefiles are: a list of macros needed to compile
MAME and a list of all the .c files that need to be compiled. The
list of macros is generated into the file \mame\src\epoc\group\userdefs
which is a merge of the macros defined in \mame\src\epoc\group\tmpl\userdefs
and the macros parsed from the makefile. The pearl script \mame\src\epoc\group\genmmp.pl
will do the makefile parsing and merges that information with the template
files. The script generates mmp files for: core, zlib,
drivers. The emame.mmp file is hand written.
It turns out that the makefiles generated by the ER5 toolchain include $(USERDEFS) to the compile flags. To define your own flags you have to include an extra line in the makefile with a USERDEFS=... statement.
Apart from emame.mmp, all the mmp files specify a static link library as target. Contrary to popular belief, you can build static libraries in ER5, that is, if you know how... :-) Apart from the norm target types: exe, app and exedll, the ER5 toolchain allows a target type called lib. Generating a makefile for target type lib result in an almost correct makefile. The makefile for wins is only incorrect in that it performs an unnecesary file copy, apart from that the makefile is perfectly usefull and produces the wanted library. The marm makefile is not usefull in its generated form. For example, calling makmake on \mame\src\epoc\group\zlib.mmp results in a makefile which contains the following snippet:
"$(EPOCTRGREL)\ZLIB.lib" : "$(EPOCBLDREL)\ZLIB.in"The dlltool step is needed for dynamic link libraries but should be skipped for static link libraries. Also, the file copy is incorrect. The section should have read:
dlltool --output-lib "$(EPOCTRGREL)\ZLIB.lib" \
--dllname "ZLIB.LIB" \
copy "$(EPOCBLDREL)\ADLER32.o" "$(EPOCTRGREL)\ZLIB.o"
"$(EPOCTRGREL)\ZLIB.lib" : "$(EPOCBLDREL)\ZLIB.in"If patched like this, zlib.marm will correctly produce library \epoc32\release\marm\rel\zlib.lib.
copy "$(EPOCBLDREL)\ZLIB.in" "$(EPOCTRGREL)\ZLIB.lib"
The pearl script (yes, my pearl programming skills have improved darmatically)
is doing precisely that for marm makefiles. Note that the shell script
\mame\src\epoc\group\makmake.cmd will call the patchmakefile.pl
if appropriate, making the patch work transparent.
2) Building static library emameengine.lib
After building all the individual libraries for core, sound etc they
have to combined into one library. Why? Well, it turns out that the gnu
linker is quite particular in the way it links objects files and multiple
libraries together. Unfortunately, there is a mutual dependency between
the core.lib and the cpus.lib making it difficult if not impossible to
satistfy the link without fully including one of two libraries in
the final executable.
The easiest way I found to build emameengine.lib is to simply link all the object files from in the individual libraries. Since genmmp.pl is already intimately familiar with which object files are generated, it will generate a list of object files in mameengineobjs.wins and mameengineobjs.marm. Both files specifiy object files for the same source set, however, wins object files have extention .obj whereas marm object files have extention .o.
Mameengineobj.wins and mameengineobj.marm are included in the makefiles
and \mame\src\epoc\group\mameengine.marm respectively. The latter
two are hand written.
Finally building mameengine simply requires nmake -f mameengine.wins or nmake -f mameengine.marm.
3) Building emame.exe
Emame.exe is build from a hand written emame.mmp file. I couldn't get
the generatered normally makefiles to work satisfactory for both wins and
marm. For marm the problem is the way emame.exe links against static libraries,
for wins the order of libraries was relevant.
For wins I use the .dsp file, so I manually patched both emame.dsp and emame.marm. If you want to know the changes I recommend you generate them using makmake and run windiff.