Difference between revisions of "HOWTO-Engines"

From ScummVM :: Wiki
Jump to navigation Jump to search
(→‎Steps: Update the "configure" instruction. typos.)
m (Text replacement - "<source lang=" to "<syntaxhighlight lang=")
 
(41 intermediate revisions by 14 users not shown)
Line 2: Line 2:
 
This page is meant as a mini-HOWTO which roughly outlines the steps needed to add a new engine to ScummVM. It does '''not''' tell you how to create an engine for a given game; rather it is meant to tell a developer how to properly "hook" into ScummVM.  
 
This page is meant as a mini-HOWTO which roughly outlines the steps needed to add a new engine to ScummVM. It does '''not''' tell you how to create an engine for a given game; rather it is meant to tell a developer how to properly "hook" into ScummVM.  
  
I will assume that you are at least roughly familiar with ScummVM, and have a fresh checkout of our Subversion repository. Note that it's strongly adviced to base your work on the current development version of ScummVM, and not on a release version. This will ease integration of your work.
+
I will assume that you are at least roughly familiar with ScummVM, and have a recent checkout of our source code repository. Note that it's strongly advised to base your work on the current development version of ScummVM, and not on a release version. This will ease integration of your work.
  
 
== Overview ==
 
== Overview ==
Essentially, you will have to implement a subclass of the Engine class. Our Doxygen documentation is your friend and should hopefully explain enough about this, see here: http://scummvm.org/docs/doxygen/html/classEngine.php .  
+
Essentially, you will have to implement a subclass of the Engine class. Our Doxygen documentation is your friend and should hopefully explain enough about this: [http://doxygen.scummvm.org/d1/db6/classEngine.html Engine class].  
  
You also must hook yourself into the regular ScummVM main build system. Actually, some ports use custom build system, but their maintainers will usually add your new engine once it has been added to ScummVM.
+
You also must hook yourself into the regular ScummVM main build system. Actually, some ports use a custom build system, but their maintainers will usually add your new engine once it has been added to ScummVM.
  
 
Finally, you need to make ScummVM aware of your new engine by updating a couple source files (see below).
 
Finally, you need to make ScummVM aware of your new engine by updating a couple source files (see below).
Line 15: Line 15:
  
 
# Add a new directory <tt>engines/quux/</tt>
 
# Add a new directory <tt>engines/quux/</tt>
# Add <tt>engines/quux/module.mk</tt> (take a look at module.mk files of 2-3 existing engines to understand the content).
+
# Add <tt>engines/quux/configure.engine</tt> (looking at configure.engine files of existing engines should make it clear what you have to do).
# Add <tt>engines/quux/quux.h</tt> and <tt>engines/quux/quux.cpp</tt>; this will contain your Engine subclass (or at least parts of it). It will also contain the plugin interface code (more on that in the next section).
+
# Add <tt>engines/quux/module.mk</tt> (Again, just check out what is done for the existing engines).
# Modify <tt>engines/module.mk</tt> by adding your engine. It should be clear what to do by looking at what is done for the other engines there.
+
# Add <tt>engines/quux/quux.h</tt> and <tt>engines/quux/quux.cpp</tt>; this will contain your Engine subclass (or at least parts of it).
# Modify <tt>configure</tt> by adding a new add_engine line. Again, just check out what is done for the existing engines.
+
# Add <tt>engines/quux/detection.cpp</tt>; It will contain the plugin interface code (more on that in the next section).
# Modify <tt>base/plugins.cpp</tt>; in particular, you have to add your engine to the list in PluginManager::loadPlugins.
 
  
 
That's it. The difficult part is of course writing the Engine subclass. More on that in the next section!
 
That's it. The difficult part is of course writing the Engine subclass. More on that in the next section!
Line 26: Line 25:
  
 
=== File name conventions ===
 
=== File name conventions ===
Since of the course of its existence, many people will have to deal with the source code of a given engine (be it to fix bugs in it, modify it to still compile after changes made to tbe backend code, or to simply add new functionality), it is useful to adhere to various conventions used throughout all engines. Besides source code conventions (see [[Code Formatting Conventions]]), this affects filenames. We suggest you use the following names for specific parts of your engine:
+
Since of the course of its existence, many people will have to deal with the source code of a given engine (be it to fix bugs in it, modify it to still compile after changes made to the backend code, or to simply add new functionality), it is useful to adhere to various conventions used throughout all engines. Besides source code conventions (see [[Code Formatting Conventions]]), this affects filenames. We suggest you use the following names for specific parts of your engine:
  
 
{| border="1" cellpadding="2" width=100%
 
{| border="1" cellpadding="2" width=100%
Line 48: Line 47:
 
|inter.cpp, logic.cpp, script.cpp || Game logic, resp. script/bytecode interpreter
 
|inter.cpp, logic.cpp, script.cpp || Game logic, resp. script/bytecode interpreter
 
|}
 
|}
 +
 +
Additionally, the files saved by each engine should be consistent.
 +
 +
* '''Saves''': These should be named <targetid>.### (where ### is the slot id) or <gameid>.###. The latter should be used when the saves can be shared across all game variants.
 +
 +
* '''Other files''': These should be named <targetid>-<filename> or <gameid>-<filename>. Again the latter when the files can be shared acress all variants of the game (an example for these type of files would be when a minigame saves a high score record).
 +
 +
Here, only use the <gameid> scheme if you are '''absolutely''' sure that such files can be shared across '''all''' (that is every game platform, every game language, every game patch version, every game release, etc.) versions. If you are not sure whether this is the case, then stick to the <targetid> based scheme.
 +
 +
=== Subclassing MetaEngine ===
 +
Let's implement the plugin interface:<br>
 +
You'll have to create a custom MetaEngine subclass. This provides the information and functionality related to the engine that can be used by the launcher without loading and running the game engine, which includes detecting games, listing savegames and instancing the engine.
 +
 +
The following example illustrates this, '''but''':<br> It is recommended that most engines should instead subclass AdvancedMetaEngine.<br>
 +
This can be found in <tt>engines/advancedDetector.*</tt> and provides a standard framework for filename and MD5 based game detection. To use this, all you will have to provide is a standard data table of ADGameDescription entries describing each game variant, which is usually placed in a separate detection_tables.h header.<br>
 +
 +
Finally, in either case, you will then have to specify your MetaEngine class to the REGISTER_PLUGIN_* macros.
  
 
=== Subclassing Engine ===
 
=== Subclassing Engine ===
 
TODO: We should probably give some sample code, maybe even provide a full (empty) Engine demo class. Maybe even provide a real mini engine project somewhere on our site which demonstrates using events, drawing, etc. ? Not sure whether this would be worth the effort, though.
 
TODO: We should probably give some sample code, maybe even provide a full (empty) Engine demo class. Maybe even provide a real mini engine project somewhere on our site which demonstrates using events, drawing, etc. ? Not sure whether this would be worth the effort, though.
  
TODO: At the very least, describe the plugin interface: I.e. which functions *must* be implemented, and what they are supposed to do. Once again, sample code would be nice.
+
Important: If you're using the ScummVM GUI (g_gui and stuff) you have always to call g_gui.handleScreenChanged() if you received a OSystem::EVENT_SCREEN_CHANGED event, else it could be that your gui looks strange or even crashes ScummVM.
  
Important: If you're using the ScummVM GUI (g_gui and stuff) you have always to call g_gui.handleScreenChanged() if you received a OSystem::EVENT_SCREEN_CHANGED event, else it could be that your gui looks strange or even crashes ScummVM.
+
For opening files in your engine, see the [[HOWTO-Open Files|how to open files]] page.
  
 
=== Infrastructure services ===
 
=== Infrastructure services ===
Header file common/scummsys.h provides services needed by virtually any source file:
+
Header file <code>common/scummsys.h</code> provides services needed by virtually any source file:
  
 
* defines platform endianness
 
* defines platform endianness
Line 69: Line 85:
 
* provides a lean environment to build win32 executables/libraries
 
* provides a lean environment to build win32 executables/libraries
  
TODO: give descriptions for other commonly used header files.
+
The <code>common</code> directory contains many more useful things, e.g. various container classes (for lists, hashmaps, resizeable arrays etc.), code for dealing with transparent endianess handling, for file I/O, etc. We recommend that you browse this a bit to get an idea of what is there. Likewise you should familiarize yourself with <code>graphics</code> and <code>sound</code>; for example we already have decoders for quite some audio formats in there. Before you roll your own code for all sorts of basic things, have a look and see if we already have code for that, and maybe also ask some veterans for advice.
  
 
=== Common portability issues ===
 
=== Common portability issues ===
Line 77: Line 93:
 
   min() -> MIN()
 
   min() -> MIN()
 
   rand() -> use Common::RandomSource class
 
   rand() -> use Common::RandomSource class
   stricmp() -> scumm_stricmp()
+
   strcasecmp() / stricmp() -> scumm_stricmp()
   strnicmp() -> scumm_strnicmp()
+
   strncasecmp() / strnicmp() -> scumm_strnicmp()
 
   strrev() -> scumm_strrev()
 
   strrev() -> scumm_strrev()
  
Line 92: Line 108:
  
 
Additionally ScummVM offers way of recording all events and then playing them back on request. That could be used for "demoplay" mode. But to ensure that it will work for your engine, you have to register your RandomSource class instance. See example engine below.
 
Additionally ScummVM offers way of recording all events and then playing them back on request. That could be used for "demoplay" mode. But to ensure that it will work for your engine, you have to register your RandomSource class instance. See example engine below.
 +
 +
=== True RGB color support ===
 +
If you need to support more than 256 colors in your game engine, please refer to [[API-Truecolor|the truecolor API reference page]] for specifications and initialization protocol.
 +
 +
== Example ==
  
 
=== Example: engines/quux/quux.h ===
 
=== Example: engines/quux/quux.h ===
<pre>
+
<syntaxhighlight lang="cpp">
 
#ifndef QUUX_H
 
#ifndef QUUX_H
 
#define QUUX_H
 
#define QUUX_H
 
+
#include "base/engine.h"
+
#include "common/random.h"
 +
#include "engines/engine.h"
 
#include "gui/debugger.h"
 
#include "gui/debugger.h"
 
+
 
namespace Quux {
 
namespace Quux {
 
+
// our engine debug levels
+
class Console;
 +
 +
// our engine debug channels
 
enum {
 
enum {
 
kQuuxDebugExample = 1 << 0,
 
kQuuxDebugExample = 1 << 0,
 
kQuuxDebugExample2 = 1 << 1
 
kQuuxDebugExample2 = 1 << 1
// next new level must be 1 << 2 (4)
+
// next new channel must be 1 << 2 (4)
// the current limitation is 32 debug levels (1 << 31 is the last one)
+
// the current limitation is 32 debug channels (1 << 31 is the last one)
 
};
 
};
 
+
 
class QuuxEngine : public Engine {
 
class QuuxEngine : public Engine {
 
public:
 
public:
 
QuuxEngine(OSystem *syst);
 
QuuxEngine(OSystem *syst);
 
~QuuxEngine();
 
~QuuxEngine();
 
+
virtual int init(GameDetector &detector);
+
virtual Common::Error run();
virtual int go();
+
 
 
 
private:
 
private:
 
Console *_console;
 
Console *_console;
 
+
 
// We need random numbers
 
// We need random numbers
Common::RandomSource _rnd;
+
Common::RandomSource *_rnd;
 
};
 
};
 
+
// Example console
+
// Example console class
 
class Console : public GUI::Debugger {
 
class Console : public GUI::Debugger {
 
public:
 
public:
Console(QuuxEngine *vm);
+
Console(QuuxEngine *vm) {}
virtual ~Console(void);
+
virtual ~Console(void) {}
 
};
 
};
 
+
 
} // End of namespace Quux
 
} // End of namespace Quux
 
+
 
#endif
 
#endif
</pre>
+
</syntaxhighlight>
  
 
=== Example: engines/quux/quux.cpp ===
 
=== Example: engines/quux/quux.cpp ===
<pre>
+
<syntaxhighlight lang="cpp">
 
#include "common/scummsys.h"
 
#include "common/scummsys.h"
#include "common/events.h" // for getEventManager()
+
 
+
#include "common/config-manager.h"
#include "backends/fs/fs.h"
+
#include "common/debug.h"
#include "base/gameDetector.h"
+
#include "common/debug-channels.h"
#include "base/plugins.h"
+
#include "common/error.h"
 +
#include "gui/EventRecorder.h"
 +
#include "common/file.h"
 +
#include "common/fs.h"
 +
 +
#include "engines/util.h"
  
 
#include "quux/quux.h"
 
#include "quux/quux.h"
 
+
 
 
static const GameSettings quux_setting[] = {
 
{ "quux", "Quux the Example Module", 0 },
 
{ "quuxcd", "Quux the Example Module (CD version)", 0 },
 
{ 0, 0, 0 }
 
};
 
 
 
GameList Engine_QUUX_gameList() {
 
GameList games;
 
const GameSettings *g = quux_setting;
 
while (g->gameid) {
 
games.push_back(*g);
 
g++;
 
}
 
 
 
return games;
 
}
 
 
 
DetectedGameList Engine_QUUX_detectGames(const FSList &fslist) {
 
DetectedGameList detectedGames;
 
 
 
// Iterate over all files in the given directory
 
for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
 
if (!file->isDirectory()) {
 
const char *gameName = file->displayName().c_str();
 
 
 
if (0 == scumm_stricmp("README", gameName)) {
 
// You could check the contents of the file now if you need to.
 
detectedGames.push_back(quux_setting[0]);
 
break;
 
}
 
}
 
}
 
return detectedGames;
 
}
 
 
 
Engine *Engine_QUUX_create(GameDetector *detector, OSystem *syst) {
 
// At this point you may want to perform a sanity check on the
 
// values in 'detecetor'.
 
return new Quux::QuuxEngine(syst);
 
}
 
 
 
REGISTER_PLUGIN(QUUX, "Quux the Example Module");
 
 
 
 
 
 
namespace Quux {
 
namespace Quux {
 
+
 
 
 
QuuxEngine::QuuxEngine(OSystem *syst)  
 
QuuxEngine::QuuxEngine(OSystem *syst)  
  : Engine(syst) {
+
  : Engine(syst), _console(nullptr) {
 
// Put your engine in a sane state, but do nothing big yet;
 
// Put your engine in a sane state, but do nothing big yet;
 
// in particular, do not load data from files; rather, if you
 
// in particular, do not load data from files; rather, if you
// need to do such things, do them from init().
+
// need to do such things, do them from run().
 
+
 
// Do not initialize graphics here
 
// Do not initialize graphics here
 
+
// Do not initialize audio devices here
 +
 
// However this is the place to specify all default directories
 
// However this is the place to specify all default directories
File::addDefaultDirectory(_gameDataPath + "sound/");
+
const Common::FSNode gameDataDir(ConfMan.get("path"));
 
+
SearchMan.addSubDirectoryMatching(gameDataDir, "sound");
// Here is the right place to set up the engine specific debug levels
+
Common::addSpecialDebugLevel(kQuuxDebugExample, "example", "this is just an example for a engine specific debug level");
+
// Here is the right place to set up the engine specific debug channels
Common::addSpecialDebugLevel(kQuuxDebugExample2, "example2", "also an example");
+
DebugMan.addDebugChannel(kQuuxDebugExample, "example", "this is just an example for a engine specific debug channel");
 
+
DebugMan.addDebugChannel(kQuuxDebugExample2, "example2", "also an example");
 +
 
// Don't forget to register your random source
 
// Don't forget to register your random source
syst->getEventManager()->registerRandomSource(_rnd, "quux");
+
_rnd = new Common::RandomSource("quux");
 
+
printf("QuuxEngine::QuuxEngine\n");
+
debug("QuuxEngine::QuuxEngine");
 
}
 
}
 +
 +
QuuxEngine::~QuuxEngine() {
 +
debug("QuuxEngine::~QuuxEngine");
  
QuuxEngine::~QuuxEngine() {
 
 
// Dispose your resources here
 
// Dispose your resources here
printf("QuuxEngine::~QuuxEngine\n");
+
delete _rnd;
 
+
 
// Remove all of our debug levels here
 
// Remove all of our debug levels here
Common::clearAllSpecialDebugLevels();
+
DebugMan.clearAllDebugChannels();
 
}
 
}
 
+
int QuuxEngine::init(GameDetector &detector) {
+
Common::Error QuuxEngine::run() {
// Initialize graphics using following template
+
// Initialize graphics using following:
// You have to use transactions
+
initGraphics(320, 200, false);
_system->beginGFXTransaction();
+
// This is handled by base Engine class and processes command
+
// You could use backend transactions directly as an alternative,
// line parameters
+
// but it isn't recommended, until you want to handle the error values
_vm->initCommonGFX(detector);
+
// from OSystem::endGFXTransaction yourself.
 
+
// This is just an example template:
// Specify dimensions of game graphics window
+
//_system->beginGFXTransaction();
_system->initSize(width, height);
+
// // This setup the graphics mode according to users seetings
_system->endGFXTransaction();
+
// initCommonGFX(false);
+
//
 +
// // Specify dimensions of game graphics window.
 +
// // In this example: 320x200
 +
// _system->initSize(320, 200);
 +
//FIXME: You really want to handle
 +
//OSystem::kTransactionSizeChangeFailed here
 +
//_system->endGFXTransaction();
 +
 
// Create debugger console. It requires GFX to be initialized
 
// Create debugger console. It requires GFX to be initialized
 
_console = new Console(this);
 
_console = new Console(this);
 
+
 
// Additional setup.
 
// Additional setup.
printf("QuuxEngine::init\n");
+
debug("QuuxEngine::init");
return 0;
+
}
 
 
 
int QuuxEngine::go() {
 
 
// Your main even loop should be (invoked from) here.
 
// Your main even loop should be (invoked from) here.
printf("QuuxEngine::go: Hello, World!\n");
+
debug("QuuxEngine::go: Hello, World!");
 
+
 
// This test will show up if -d1 and --debugflags=example are specified on the commandline
 
// This test will show up if -d1 and --debugflags=example are specified on the commandline
 
debugC(1, kQuuxDebugExample, "Example debug call");
 
debugC(1, kQuuxDebugExample, "Example debug call");
 
+
 
// This test will show up if --debugflags=example or --debugflags=example2 or both of them and -d3 are specified on the commandline
 
// This test will show up if --debugflags=example or --debugflags=example2 or both of them and -d3 are specified on the commandline
 
debugC(3, kQuuxDebugExample | kQuuxDebugExample2, "Example debug call two");
 
debugC(3, kQuuxDebugExample | kQuuxDebugExample2, "Example debug call two");
 
+
return 0;
+
return Common::kNoError;
 
}
 
}
 +
 +
} // End of namespace Quux
 +
</syntaxhighlight>
  
 +
=== Example: engines/quux/detection.cpp ===
 +
The following example implements a custom MetaEngine instead of using the AdvancedMetaEngine.
 +
<syntaxhighlight lang="cpp">
 +
#include "quux/quux.h"
 +
 +
#include "common/config-manager.h"
 +
#include "common/error.h"
 +
#include "common/fs.h"
  
 
+
#include "engines/metaengine.h"
} // End of namespace Quux
+
</pre>
+
static const PlainGameDescriptor quux_setting[] = {
 +
{ "quux", "Quux the Example Module" },
 +
{ "quuxcd", "Quux the Example Module (CD version)" },
 +
{ 0, 0 }
 +
};
 +
 +
class QuuxMetaEngine : public MetaEngine {
 +
public:
 +
virtual const char *getName() const {
 +
return "Quux the Example Module";
 +
}
 +
 +
virtual const char *getOriginalCopyright() const {
 +
return "Copyright (C) Quux Entertainment Ltd.";
 +
}
 +
 +
virtual GameList getSupportedGames() const {
 +
GameList games;
 +
const PlainGameDescriptor *g = quux_setting;
 +
while (g->gameid) {
 +
games.push_back(*g);
 +
g++;
 +
}
 +
 +
return games;
 +
}
 +
 +
virtual GameDescriptor findGame(const char *gameid) const {
 +
const PlainGameDescriptor *g = quux_setting;
 +
while (g->gameid) {
 +
if (0 == scumm_stricmp(gameid, g->gameid))
 +
break;
 +
g++;
 +
}
 +
return GameDescriptor(g->gameid, g->description);
 +
}
 +
 +
virtual GameList detectGames(const Common::FSList &fslist) const {
 +
GameList detectedGames;
 +
 +
// Iterate over all files in the given directory
 +
for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
 +
if (!file->isDirectory()) {
 +
const char *gameName = file->getName().c_str();
 +
 +
if (0 == scumm_stricmp("README", gameName)) {
 +
// You could check the contents of the file now if you need to.
 +
detectedGames.push_back(quux_setting[0]);
 +
break;
 +
}
 +
}
 +
}
 +
return detectedGames;
 +
}
 +
 +
virtual Common::Error createInstance(OSystem *syst, Engine **engine) const {
 +
assert(syst);
 +
assert(engine);
 +
 +
// Scan the target directory for files (error out if it does not exist)
 +
Common::FSList fslist;
 +
Common::FSNode dir(ConfMan.get("path"));
 +
if (!dir.getChildren(fslist, Common::FSNode::kListAll)) {
 +
return Common::kNoGameDataFoundError;
 +
}
 +
 +
// Invoke the detector
 +
Common::String gameid = ConfMan.get("gameid");
 +
GameList detectedGames = detectGames(fslist);
 +
 +
for (uint i = 0; i < detectedGames.size(); i++) {
 +
if (detectedGames[i].gameid() == gameid) {
 +
// At this point you may want to perform additional sanity checks.
 +
*engine = new Quux::QuuxEngine(syst);
 +
return Common::kNoError;
 +
}
 +
}
 +
 +
// Failed to find any game data
 +
return Common::kNoGameDataFoundError;
 +
}
 +
};
 +
 +
#if PLUGIN_ENABLED_DYNAMIC(QUUX)
 +
REGISTER_PLUGIN_DYNAMIC(QUUX, PLUGIN_TYPE_ENGINE, QuuxMetaEngine);
 +
#else
 +
REGISTER_PLUGIN_STATIC(QUUX, PLUGIN_TYPE_ENGINE, QuuxMetaEngine);
 +
#endif
 +
</syntaxhighlight>
  
 
=== Example: engines/quux/module.mk ===
 
=== Example: engines/quux/module.mk ===
<pre>
+
<syntaxhighlight lang="make">
 
MODULE := engines/quux
 
MODULE := engines/quux
 
+
 
MODULE_OBJS := \
 
MODULE_OBJS := \
 +
detection.o \
 
quux.o
 
quux.o
 
+
 
MODULE_DIRS += \
 
MODULE_DIRS += \
 
engines/quux
 
engines/quux
 
+
 
# This module can be built as a plugin
 
# This module can be built as a plugin
ifdef BUILD_PLUGINS
+
ifeq ($(ENABLE_QUUX), DYNAMIC_PLUGIN)
 
PLUGIN := 1
 
PLUGIN := 1
 
endif
 
endif
 +
 +
# Include common rules
 +
include $(srcdir)/rules.mk
 +
</syntaxhighlight>
  
# Include common rules
+
=== Example: engines/quux/configure.engine ===
include $(srcdir)/common.rules
+
<syntaxhighlight lang="bash">
</pre>
+
# This file is included from the main "configure" script
 +
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
 +
add_engine quux "Quux" no
 +
</syntaxhighlight>

Latest revision as of 17:08, 25 October 2018

Introduction

This page is meant as a mini-HOWTO which roughly outlines the steps needed to add a new engine to ScummVM. It does not tell you how to create an engine for a given game; rather it is meant to tell a developer how to properly "hook" into ScummVM.

I will assume that you are at least roughly familiar with ScummVM, and have a recent checkout of our source code repository. Note that it's strongly advised to base your work on the current development version of ScummVM, and not on a release version. This will ease integration of your work.

Overview

Essentially, you will have to implement a subclass of the Engine class. Our Doxygen documentation is your friend and should hopefully explain enough about this: Engine class.

You also must hook yourself into the regular ScummVM main build system. Actually, some ports use a custom build system, but their maintainers will usually add your new engine once it has been added to ScummVM.

Finally, you need to make ScummVM aware of your new engine by updating a couple source files (see below).

Steps

In the following I assume your engine is called "quux".

  1. Add a new directory engines/quux/
  2. Add engines/quux/configure.engine (looking at configure.engine files of existing engines should make it clear what you have to do).
  3. Add engines/quux/module.mk (Again, just check out what is done for the existing engines).
  4. Add engines/quux/quux.h and engines/quux/quux.cpp; this will contain your Engine subclass (or at least parts of it).
  5. Add engines/quux/detection.cpp; It will contain the plugin interface code (more on that in the next section).

That's it. The difficult part is of course writing the Engine subclass. More on that in the next section!

Important note: Use a C++ namespace for all your work, e.g. "namespace Quux" in this case.

File name conventions

Since of the course of its existence, many people will have to deal with the source code of a given engine (be it to fix bugs in it, modify it to still compile after changes made to the backend code, or to simply add new functionality), it is useful to adhere to various conventions used throughout all engines. Besides source code conventions (see Code Formatting Conventions), this affects filenames. We suggest you use the following names for specific parts of your engine:

ENGINENAME.h Contains your (primary) Engine subclass.
ENGINENAME.cpp Contains at least the constructor and destructor of your (primary) Engine subclass, as well as the implementations of the mandatory (i.e. pure virtual) Engine methods.
detection.cpp Code related to game detection. Also contains the implementation of the plugin interface, as described in base/plugins.h.
saveload.cpp Code related to savegames
debug.cpp, debugger.cpp (console) debugger
gfx.cpp (alt: graphics.cpp) Graphics code
sound.cpp Sound code
music.cpp Music code
inter.cpp, logic.cpp, script.cpp Game logic, resp. script/bytecode interpreter

Additionally, the files saved by each engine should be consistent.

  • Saves: These should be named <targetid>.### (where ### is the slot id) or <gameid>.###. The latter should be used when the saves can be shared across all game variants.
  • Other files: These should be named <targetid>-<filename> or <gameid>-<filename>. Again the latter when the files can be shared acress all variants of the game (an example for these type of files would be when a minigame saves a high score record).

Here, only use the <gameid> scheme if you are absolutely sure that such files can be shared across all (that is every game platform, every game language, every game patch version, every game release, etc.) versions. If you are not sure whether this is the case, then stick to the <targetid> based scheme.

Subclassing MetaEngine

Let's implement the plugin interface:
You'll have to create a custom MetaEngine subclass. This provides the information and functionality related to the engine that can be used by the launcher without loading and running the game engine, which includes detecting games, listing savegames and instancing the engine.

The following example illustrates this, but:
It is recommended that most engines should instead subclass AdvancedMetaEngine.
This can be found in engines/advancedDetector.* and provides a standard framework for filename and MD5 based game detection. To use this, all you will have to provide is a standard data table of ADGameDescription entries describing each game variant, which is usually placed in a separate detection_tables.h header.

Finally, in either case, you will then have to specify your MetaEngine class to the REGISTER_PLUGIN_* macros.

Subclassing Engine

TODO: We should probably give some sample code, maybe even provide a full (empty) Engine demo class. Maybe even provide a real mini engine project somewhere on our site which demonstrates using events, drawing, etc. ? Not sure whether this would be worth the effort, though.

Important: If you're using the ScummVM GUI (g_gui and stuff) you have always to call g_gui.handleScreenChanged() if you received a OSystem::EVENT_SCREEN_CHANGED event, else it could be that your gui looks strange or even crashes ScummVM.

For opening files in your engine, see the how to open files page.

Infrastructure services

Header file common/scummsys.h provides services needed by virtually any source file:

  • defines platform endianness
  • defines portable types
  • defines common constants and macros

Moreover, it deals with providing suitable building environment for different platforms:

  • provides common names for non-standard library functions
  • disables bogus and/or annoying warnings
  • provides a lean environment to build win32 executables/libraries

The common directory contains many more useful things, e.g. various container classes (for lists, hashmaps, resizeable arrays etc.), code for dealing with transparent endianess handling, for file I/O, etc. We recommend that you browse this a bit to get an idea of what is there. Likewise you should familiarize yourself with graphics and sound; for example we already have decoders for quite some audio formats in there. Before you roll your own code for all sorts of basic things, have a look and see if we already have code for that, and maybe also ask some veterans for advice.

Common portability issues

There are wrapper around number of non-portable functions. These are:

 max() -> MAX()
 min() -> MIN()
 rand() -> use Common::RandomSource class
 strcasecmp() / stricmp() -> scumm_stricmp()
 strncasecmp() / strnicmp() -> scumm_strnicmp()
 strrev() -> scumm_strrev()

Also we have predefined common integer types. Please, use them instead of rolling your own:

 byte
 int8
 uint8
 int16
 uint16
 int32
 uint32

Additionally ScummVM offers way of recording all events and then playing them back on request. That could be used for "demoplay" mode. But to ensure that it will work for your engine, you have to register your RandomSource class instance. See example engine below.

True RGB color support

If you need to support more than 256 colors in your game engine, please refer to the truecolor API reference page for specifications and initialization protocol.

Example

Example: engines/quux/quux.h

#ifndef QUUX_H
#define QUUX_H
 
#include "common/random.h"
#include "engines/engine.h"
#include "gui/debugger.h"
 
namespace Quux {
 
class Console;
 
// our engine debug channels
enum {
	kQuuxDebugExample = 1 << 0,
	kQuuxDebugExample2 = 1 << 1
	// next new channel must be 1 << 2 (4)
	// the current limitation is 32 debug channels (1 << 31 is the last one)
};
 
class QuuxEngine : public Engine {
public:
	QuuxEngine(OSystem *syst);
	~QuuxEngine();
 
	virtual Common::Error run();
 
private:
	Console *_console;
 
	// We need random numbers
	Common::RandomSource *_rnd;
};
 
// Example console class
class Console : public GUI::Debugger {
public:
	Console(QuuxEngine *vm) {}
	virtual ~Console(void) {}
};
 
} // End of namespace Quux
 
#endif

Example: engines/quux/quux.cpp

#include "common/scummsys.h"
 
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/debug-channels.h"
#include "common/error.h"
#include "gui/EventRecorder.h"
#include "common/file.h"
#include "common/fs.h"
 
#include "engines/util.h"

#include "quux/quux.h"
 
namespace Quux {
 
QuuxEngine::QuuxEngine(OSystem *syst) 
 : Engine(syst), _console(nullptr) {
	// Put your engine in a sane state, but do nothing big yet;
	// in particular, do not load data from files; rather, if you
	// need to do such things, do them from run().
 
	// Do not initialize graphics here
	// Do not initialize audio devices here
 
	// However this is the place to specify all default directories
	const Common::FSNode gameDataDir(ConfMan.get("path"));
	SearchMan.addSubDirectoryMatching(gameDataDir, "sound");
 
	// Here is the right place to set up the engine specific debug channels
	DebugMan.addDebugChannel(kQuuxDebugExample, "example", "this is just an example for a engine specific debug channel");
	DebugMan.addDebugChannel(kQuuxDebugExample2, "example2", "also an example");
 
	// Don't forget to register your random source
	_rnd = new Common::RandomSource("quux");
 
	debug("QuuxEngine::QuuxEngine");
}
 
QuuxEngine::~QuuxEngine() {
	debug("QuuxEngine::~QuuxEngine");

	// Dispose your resources here
	delete _rnd;
 
	// Remove all of our debug levels here
	DebugMan.clearAllDebugChannels();
}
 
Common::Error QuuxEngine::run() {
	// Initialize graphics using following:
	initGraphics(320, 200, false);
 
	// You could use backend transactions directly as an alternative,
	// but it isn't recommended, until you want to handle the error values
	// from OSystem::endGFXTransaction yourself.
	// This is just an example template:
	//_system->beginGFXTransaction();
	//	// This setup the graphics mode according to users seetings
	//	initCommonGFX(false);
	//
	//	// Specify dimensions of game graphics window.
	//	// In this example: 320x200
	//	_system->initSize(320, 200);
	//FIXME: You really want to handle
	//OSystem::kTransactionSizeChangeFailed here
	//_system->endGFXTransaction();
 
	// Create debugger console. It requires GFX to be initialized
	_console = new Console(this);
 
	// Additional setup.
	debug("QuuxEngine::init");
 
	// Your main even loop should be (invoked from) here.
	debug("QuuxEngine::go: Hello, World!");
 
	// This test will show up if -d1 and --debugflags=example are specified on the commandline
	debugC(1, kQuuxDebugExample, "Example debug call");
 
	// This test will show up if --debugflags=example or --debugflags=example2 or both of them and -d3 are specified on the commandline
	debugC(3, kQuuxDebugExample | kQuuxDebugExample2, "Example debug call two");
 
	return Common::kNoError;
}
 
} // End of namespace Quux

Example: engines/quux/detection.cpp

The following example implements a custom MetaEngine instead of using the AdvancedMetaEngine.

#include "quux/quux.h"
 
#include "common/config-manager.h"
#include "common/error.h"
#include "common/fs.h"

#include "engines/metaengine.h"
 
static const PlainGameDescriptor quux_setting[] = {
	{ "quux", "Quux the Example Module" },
	{ "quuxcd", "Quux the Example Module (CD version)" },
	{ 0, 0 }
};
 
class QuuxMetaEngine : public MetaEngine {
public:
	virtual const char *getName() const {
		return "Quux the Example Module";
	}
 
	virtual const char *getOriginalCopyright() const {
		return "Copyright (C) Quux Entertainment Ltd.";
	}
 
	virtual GameList getSupportedGames() const {
		GameList games;
		const PlainGameDescriptor *g = quux_setting;
		while (g->gameid) {
			games.push_back(*g);
			g++;
		}
 
		return games;
	}
 
	virtual GameDescriptor findGame(const char *gameid) const {
		const PlainGameDescriptor *g = quux_setting;
		while (g->gameid) {
			if (0 == scumm_stricmp(gameid, g->gameid))
				break;
			g++;
		}
		return GameDescriptor(g->gameid, g->description);
	}
 
	virtual GameList detectGames(const Common::FSList &fslist) const {
		GameList detectedGames;
 
		// Iterate over all files in the given directory
		for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
			if (!file->isDirectory()) {
				const char *gameName = file->getName().c_str();
 
				if (0 == scumm_stricmp("README", gameName)) {
					// You could check the contents of the file now if you need to.
					detectedGames.push_back(quux_setting[0]);
					break;
				}
			}
		}
		return detectedGames;
	}
 
	virtual Common::Error createInstance(OSystem *syst, Engine **engine) const {
		assert(syst);
		assert(engine);
 
		// Scan the target directory for files (error out if it does not exist)
		Common::FSList fslist;
		Common::FSNode dir(ConfMan.get("path"));
		if (!dir.getChildren(fslist, Common::FSNode::kListAll)) {
			return Common::kNoGameDataFoundError;
		}
 
		// Invoke the detector
		Common::String gameid = ConfMan.get("gameid");
		GameList detectedGames = detectGames(fslist);
 
		for (uint i = 0; i < detectedGames.size(); i++) {
			if (detectedGames[i].gameid() == gameid) {
				// At this point you may want to perform additional sanity checks.
				*engine = new Quux::QuuxEngine(syst);
				return Common::kNoError;
			}
		}
 
		// Failed to find any game data
		return Common::kNoGameDataFoundError;
	}
};
 
#if PLUGIN_ENABLED_DYNAMIC(QUUX)
	REGISTER_PLUGIN_DYNAMIC(QUUX, PLUGIN_TYPE_ENGINE, QuuxMetaEngine);
#else
	REGISTER_PLUGIN_STATIC(QUUX, PLUGIN_TYPE_ENGINE, QuuxMetaEngine);
#endif

Example: engines/quux/module.mk

MODULE := engines/quux
 
MODULE_OBJS := \
	detection.o \
	quux.o
 
MODULE_DIRS += \
	engines/quux
 
# This module can be built as a plugin
ifeq ($(ENABLE_QUUX), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
 
# Include common rules 
include $(srcdir)/rules.mk

Example: engines/quux/configure.engine

# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
add_engine quux "Quux" no