From 36fd7ec44574a4b1712fff1eb0ec43a76aab8f1c Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sun, 12 Oct 2003 18:40:12 +0000 Subject: some renaming for more consistent terminology (although we might want to reevaluate this): 'target' is what is in your config file; 'game' is what a frontend provide. E.g. the scumm frontend provides the game 'monkeyvga', and my config file has target 'monkeyvga-ger' configured to use that game svn-id: r10766 --- TODO | 2 +- base/engine.h | 2 +- base/gameDetector.cpp | 38 +++++++++++++++++++------------------- base/gameDetector.h | 12 ++++++------ base/main.cpp | 10 ++++++---- base/plugins.cpp | 38 +++++++++++++++++++------------------- base/plugins.h | 8 ++++---- gui/launcher.cpp | 36 ++++++++++++++++++------------------ queen/queen.cpp | 4 ++-- scumm/scummvm.cpp | 8 ++++---- simon/simon.cpp | 4 ++-- sky/sky.cpp | 4 ++-- sword2/sword2.cpp | 6 +++--- 13 files changed, 87 insertions(+), 85 deletions(-) diff --git a/TODO b/TODO index af1a8f9732..c949cea574 100644 --- a/TODO +++ b/TODO @@ -11,7 +11,7 @@ General * allow for return-to-launcher instead of a normal "quit" ? * improve the argv (command line args) parser * extend the Plugin API to provide for "game detection": instead of the - TargetSettings::detectname "hack" to detect files, provide a callback + GameSettings::detectname "hack" to detect files, provide a callback in each Plugin which given a FSList returns a list of candidate targets. This way, a plugin can implement tests more elaborate than filename checking, e.g. it could actually peek into the files. diff --git a/base/engine.h b/base/engine.h index e1bb4c71cc..25ad6e343e 100644 --- a/base/engine.h +++ b/base/engine.h @@ -59,7 +59,7 @@ enum GameId { class SoundMixer; class GameDetector; class Timer; -struct TargetSettings; +struct GameSettings; class Engine { public: diff --git a/base/gameDetector.cpp b/base/gameDetector.cpp index 9f4d5d4499..06430a5f58 100644 --- a/base/gameDetector.cpp +++ b/base/gameDetector.cpp @@ -242,7 +242,7 @@ void GameDetector::list_games() { // 2) List all available (configured) targets, including those with custom // names, e.g. "monkey-mac", "skycd-demo", ... const PluginList &plugins = PluginManager::instance().getPlugins(); - const TargetSettings *v; + const GameSettings *v; printf("Game Full Title \n" "---------------- ------------------------------------------------------\n"); @@ -250,26 +250,26 @@ void GameDetector::list_games() { PluginList::ConstIterator iter = plugins.begin(); for (iter = plugins.begin(); iter != plugins.end(); ++iter) { v = (*iter)->getTargets(); - while (v->targetName && v->description) { + while (v->gameName && v->description) { #if 1 - printf("%-17s%-56s\n", v->targetName, v->description); + printf("%-17s%-56s\n", v->gameName, v->description); #else - const char *config = (g_config->has_domain(v->targetName)) ? "Yes" : ""; - printf("%-17s%-56s%s\n", v->targetName, v->description, config); + const char *config = (g_config->has_domain(v->gameName)) ? "Yes" : ""; + printf("%-17s%-56s%s\n", v->gameName, v->description, config); #endif v++; } } } -const TargetSettings *GameDetector::findTarget(const String &targetName, const Plugin **plugin) const { - // Find the TargetSettings for this target - const TargetSettings *target; +const GameSettings *GameDetector::findGame(const String &gameName, const Plugin **plugin) const { + // Find the GameSettings for this target + const GameSettings *target; const PluginList &plugins = PluginManager::instance().getPlugins(); PluginList::ConstIterator iter = plugins.begin(); for (iter = plugins.begin(); iter != plugins.end(); ++iter) { - target = (*iter)->findTarget(targetName.c_str()); + target = (*iter)->findGame(gameName.c_str()); if (target) { if (plugin) *plugin = *iter; @@ -457,8 +457,8 @@ void GameDetector::parseCommandLine(int argc, char **argv) { // To verify this, check if there is either a game domain (i.e // a configured target) matching this argument, or if we can // find any target with that name. - if (i == (argc - 1) && (ConfMan.hasGameDomain(s) || findTarget(s))) { - setGame(s); + if (i == (argc - 1) && (ConfMan.hasGameDomain(s) || findGame(s))) { + setTarget(s); } else { if (current_option == NULL) current_option = s; @@ -475,8 +475,8 @@ ShowHelpAndExit: exit(1); } -void GameDetector::setGame(const String &name) { - _gameFileName = name; +void GameDetector::setTarget(const String &name) { + _targetName = name; ConfMan.setActiveDomain(name); } @@ -546,23 +546,23 @@ int GameDetector::parseMusicDriver(const String &str) { } bool GameDetector::detectGame() { - const TargetSettings *target; + const GameSettings *target; String realGame; if (ConfMan.hasKey("gameid")) realGame = ConfMan.get("gameid"); else - realGame = _gameFileName; + realGame = _targetName; printf("Looking for %s\n", realGame.c_str()); - target = findTarget(realGame, &_plugin); + target = findGame(realGame, &_plugin); if (target) { _game = *target; if (ConfMan.hasKey("basename")) { // FIXME: What is this good for? // FIXME: This leaks now! - _game.targetName = strdup(ConfMan.get("basename").c_str()); + _game.gameName = strdup(ConfMan.get("basename").c_str()); } printf("Trying to start game '%s'\n", _game.description); return true; @@ -573,13 +573,13 @@ bool GameDetector::detectGame() { } bool GameDetector::detectMain() { - if (_gameFileName.isEmpty()) { + if (_targetName.isEmpty()) { warning("No game was specified..."); return false; } if (!detectGame()) { - warning("%s is an invalid target. Use the -z parameter to list targets", _gameFileName.c_str()); + warning("%s is an invalid target. Use the -z parameter to list targets", _targetName.c_str()); return false; } diff --git a/base/gameDetector.h b/base/gameDetector.h index 7f96ecf4e4..a236d0b479 100644 --- a/base/gameDetector.h +++ b/base/gameDetector.h @@ -84,8 +84,8 @@ enum MidiDriverType { MDT_PREFER_NATIVE = 16 }; -struct TargetSettings { - const char *targetName; +struct GameSettings { + const char *gameName; const char *description; byte id, version; int midi; // MidiDriverType values @@ -102,8 +102,8 @@ public: void parseCommandLine(int argc, char **argv); bool detectMain(); - String _gameFileName; - TargetSettings _game; + String _targetName; + GameSettings _game; const Plugin *_plugin; bool _debugMode; @@ -120,14 +120,14 @@ public: MidiDriver *createMidi(); int getMidiDriverType(); // FIXME: Try to get rid of this, only Sky frontend uses it - void setGame(const String &name); + void setTarget(const String &name); static int parseGraphicsMode(const String &s); // Used in main() static int parseMusicDriver(const String &s); static Language parseLanguage(const String &s); static Platform parsePlatform(const String &s); - const TargetSettings *findTarget(const String &targetName, const Plugin **plugin = NULL) const; + const GameSettings *findGame(const String &gameName, const Plugin **plugin = NULL) const; protected: bool detectGame(void); diff --git a/base/main.cpp b/base/main.cpp index 2ca7a9966d..6b90e531f7 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -259,16 +259,18 @@ int main(int argc, char *argv[]) { g_gui = new NewGui(system); // Unless a game was specified, show the launcher dialog - if (detector._gameFileName.isEmpty()) + if (detector._targetName.isEmpty()) launcherDialog(detector, system); // Verify the given game name is a valid supported game if (detector.detectMain()) { // Set the window caption to the game name - prop.caption = ConfMan.get("description", detector._gameFileName).c_str(); + prop.caption = ConfMan.get("description", detector._targetName).c_str(); if (prop.caption == NULL) - prop.caption = detector._gameFileName.c_str(); + prop.caption = detector._game.description; + if (prop.caption == NULL) + prop.caption = detector._targetName.c_str(); if (prop.caption != NULL) system->property(OSystem::PROP_SET_WINDOW_CAPTION, &prop); @@ -277,7 +279,7 @@ int main(int argc, char *argv[]) { // should combine both checks into one. // See if the game should default to 1x scaler - if (!ConfMan.hasKey("gfx_mode", detector._gameFileName) && + if (!ConfMan.hasKey("gfx_mode", detector._targetName) && (detector._game.features & GF_DEFAULT_TO_1X_SCALER)) { prop.gfx_mode = GFX_NORMAL; system->property(OSystem::PROP_SET_GFX_MODE, &prop); diff --git a/base/plugins.cpp b/base/plugins.cpp index 45ffec8d7f..9d78f2ffa2 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -44,27 +44,27 @@ typedef Engine *(*EngineFactory)(GameDetector *detector, OSystem *syst); // 1) Clean seperation from the game modules (scumm, simon) and the generic code // 2) Faster (compiler doesn't have to parse lengthy header files) #ifndef DISABLE_SCUMM -extern const TargetSettings *Engine_SCUMM_targetList(); +extern const GameSettings *Engine_SCUMM_targetList(); extern Engine *Engine_SCUMM_create(GameDetector *detector, OSystem *syst); #endif #ifndef DISABLE_SIMON extern Engine *Engine_SIMON_create(GameDetector *detector, OSystem *syst); -extern const TargetSettings *Engine_SIMON_targetList(); +extern const GameSettings *Engine_SIMON_targetList(); #endif #ifndef DISABLE_SKY -extern const TargetSettings *Engine_SKY_targetList(); +extern const GameSettings *Engine_SKY_targetList(); extern Engine *Engine_SKY_create(GameDetector *detector, OSystem *syst); #endif #ifndef DISABLE_SWORD2 -extern const TargetSettings *Engine_SWORD2_targetList(); +extern const GameSettings *Engine_SWORD2_targetList(); extern Engine *Engine_SWORD2_create(GameDetector *detector, OSystem *syst); #endif #ifndef DISABLE_QUEEN -extern const TargetSettings *Engine_QUEEN_targetList(); +extern const GameSettings *Engine_QUEEN_targetList(); extern Engine *Engine_QUEEN_create(GameDetector *detector, OSystem *syst); #endif @@ -75,19 +75,19 @@ extern Engine *Engine_QUEEN_create(GameDetector *detector, OSystem *syst); int Plugin::countTargets() const { - const TargetSettings *target = getTargets(); + const GameSettings *target = getTargets(); int count; - for (count = 0; target->targetName; target++, count++) + for (count = 0; target->gameName; target++, count++) ; return count; } -const TargetSettings *Plugin::findTarget(const char *targetName) const { - // Find the TargetSettings for this target - const TargetSettings *target = getTargets(); - assert(targetName); - while (target->targetName) { - if (!scumm_stricmp(target->targetName, targetName)) { +const GameSettings *Plugin::findGame(const char *gameName) const { + // Find the GameSettings for this target + const GameSettings *target = getTargets(); + assert(gameName); + while (target->gameName) { + if (!scumm_stricmp(target->gameName, gameName)) { return target; } target++; @@ -101,11 +101,11 @@ const TargetSettings *Plugin::findTarget(const char *targetName) const { class StaticPlugin : public Plugin { const char *_name; - const TargetSettings *_targets; + const GameSettings *_targets; int _targetCount; EngineFactory _ef; public: - StaticPlugin(const char *name, const TargetSettings *targets, EngineFactory ef) + StaticPlugin(const char *name, const GameSettings *targets, EngineFactory ef) : _name(name), _targets(targets), _ef(ef) { _targetCount = Plugin::countTargets(); } @@ -113,7 +113,7 @@ public: const char *getName() const { return _name; } int countTargets() const { return _targetCount; } - const TargetSettings *getTargets() const { return _targets; } + const GameSettings *getTargets() const { return _targets; } Engine *createInstance(GameDetector *detector, OSystem *syst) const { return (*_ef)(detector, syst); @@ -131,7 +131,7 @@ class DynamicPlugin : public Plugin { Common::String _filename; Common::String _name; - const TargetSettings *_targets; + const GameSettings *_targets; int _targetCount; EngineFactory _ef; @@ -144,7 +144,7 @@ public: const char *getName() const { return _name.c_str(); } int countTargets() const { return _targetCount; } - const TargetSettings *getTargets() const { return _targets; } + const GameSettings *getTargets() const { return _targets; } Engine *createInstance(GameDetector *detector, OSystem *syst) const { assert(_ef); @@ -174,7 +174,7 @@ void *DynamicPlugin::findSymbol(const char *symbol) { } typedef const char *(*NameFunc)(); -typedef const TargetSettings *(*TargetListFunc)(); +typedef const GameSettings *(*TargetListFunc)(); bool DynamicPlugin::loadPlugin() { assert(!_dlHandle); diff --git a/base/plugins.h b/base/plugins.h index 990ad68703..817ec79f85 100644 --- a/base/plugins.h +++ b/base/plugins.h @@ -29,7 +29,7 @@ class Engine; class GameDetector; class OSystem; -struct TargetSettings; +struct GameSettings; /** * Abstract base class for the plugin system. @@ -47,8 +47,8 @@ public: virtual int getVersion() const { return 0; } // TODO! virtual int countTargets() const; - virtual const TargetSettings *getTargets() const = 0; - virtual const TargetSettings *findTarget(const char *targetName) const; + virtual const GameSettings *getTargets() const = 0; + virtual const GameSettings *findGame(const char *gameName) const; virtual Engine *createInstance(GameDetector *detector, OSystem *syst) const = 0; }; @@ -69,7 +69,7 @@ public: #define REGISTER_PLUGIN(name,targetListFactory,engineFactory) \ extern "C" { \ const char *PLUGIN_name() { return name; } \ - const TargetSettings *PLUGIN_getTargetList() { return targetListFactory(); } \ + const GameSettings *PLUGIN_getTargetList() { return targetListFactory(); } \ Engine *PLUGIN_createEngine(GameDetector *detector, OSystem *syst) { return engineFactory(detector, syst); } \ } #endif diff --git a/gui/launcher.cpp b/gui/launcher.cpp index ca8bfa17d5..a3a4d62ca7 100644 --- a/gui/launcher.cpp +++ b/gui/launcher.cpp @@ -50,7 +50,7 @@ enum { kQuitCmd = 'QUIT' }; -typedef Common::List GameList; +typedef Common::List GameList; /* * A dialog that allows the user to edit a config game entry. @@ -80,7 +80,7 @@ class EditGameDialog : public Dialog { typedef Common::String String; typedef Common::StringList StringList; public: - EditGameDialog(NewGui *gui, const String &domain, const TargetSettings *target); + EditGameDialog(NewGui *gui, const String &domain, const GameSettings *target); virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); @@ -92,7 +92,7 @@ protected: CheckboxWidget *_amigaCheckbox; }; -EditGameDialog::EditGameDialog(NewGui *gui, const String &domain, const TargetSettings *target) +EditGameDialog::EditGameDialog(NewGui *gui, const String &domain, const GameSettings *target) : Dialog(gui, 8, 50, 320 - 2 * 8, 200 - 2 * 40), _domain(domain) { @@ -247,7 +247,7 @@ void LauncherDialog::updateListing() { if (name.isEmpty()) name = iter->_key; if (description.isEmpty()) { - const TargetSettings *v = _detector.findTarget(name); + const GameSettings *v = _detector.findGame(name); if (v && v->description) description = v->description; } @@ -284,8 +284,8 @@ GameList findGame(FilesystemNode *dir) { const PluginList &plugins = PluginManager::instance().getPlugins(); int p; for (p = 0; p < plugins.size(); p++) { - const TargetSettings *v = plugins[p]->getTargets(); - while (v->targetName && v->description) { + const GameSettings *v = plugins[p]->getTargets(); + while (v->gameName && v->description) { // Determine the 'detectname' for this game, that is, the name of a // file that *must* be presented if the directory contains the data @@ -296,9 +296,9 @@ GameList findGame(FilesystemNode *dir) { strcat(detectName2, "."); detectName3[0] = '\0'; } else { - strcpy(detectName, v->targetName); - strcpy(detectName2, v->targetName); - strcpy(detectName3, v->targetName); + strcpy(detectName, v->gameName); + strcpy(detectName2, v->gameName); + strcpy(detectName3, v->gameName); strcat(detectName, ".000"); if (v->version >= 7) { strcat(detectName2, ".la0"); @@ -309,11 +309,11 @@ GameList findGame(FilesystemNode *dir) { // Iterate over all files in the given directory for (int i = 0; i < size; i++) { - const char *targetName = (*files)[i].displayName().c_str(); + const char *gameName = (*files)[i].displayName().c_str(); - if ((0 == scumm_stricmp(detectName, targetName)) || - (0 == scumm_stricmp(detectName2, targetName)) || - (0 == scumm_stricmp(detectName3, targetName))) { + if ((0 == scumm_stricmp(detectName, gameName)) || + (0 == scumm_stricmp(detectName2, gameName)) || + (0 == scumm_stricmp(detectName3, gameName))) { // Match found, add to list of candidates, then abort inner loop. list.push_back(v); break; @@ -350,7 +350,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat // ...so let's determine a list of candidates, games that // could be contained in the specified directory. GameList candidates = findGame(dir); - const TargetSettings *v = 0; + const GameSettings *v = 0; if (candidates.isEmpty()) { // No game was found in the specified directory @@ -376,7 +376,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat // The auto detector or the user made a choice. // Pick a domain name which does not yet exist (after all, we // are *adding* a game to the config, not replacing). - String domain(v->targetName); + String domain(v->gameName); if (ConfMan.hasGameDomain(domain)) { char suffix = 'a'; domain += suffix; @@ -385,7 +385,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat suffix++; domain += suffix; } - ConfMan.set("gameid", v->targetName, domain); + ConfMan.set("gameid", v->gameName, domain); ConfMan.set("description", v->description, domain); } ConfMan.set("path", dir->path(), domain); @@ -431,7 +431,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat String gameId(ConfMan.get("gameid", _domains[item])); if (gameId.isEmpty()) gameId = _domains[item]; - EditGameDialog editDialog(_gui, _domains[item], _detector.findTarget(gameId)); + EditGameDialog editDialog(_gui, _domains[item], _detector.findGame(gameId)); if (editDialog.runModal()) { // User pressed OK, so make changes permanent @@ -463,7 +463,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat case kListItemDoubleClickedCmd: // Print out what was selected assert(item >= 0); - _detector.setGame(_domains[item]); + _detector.setTarget(_domains[item]); close(); break; case kListSelectionChangedCmd: diff --git a/queen/queen.cpp b/queen/queen.cpp index 19e980d2f2..e4ab380c8e 100644 --- a/queen/queen.cpp +++ b/queen/queen.cpp @@ -36,13 +36,13 @@ extern bool draw_keyboard; #endif -static const TargetSettings queen_settings[] = { +static const GameSettings queen_settings[] = { /* Flight of the Amazon Queen */ { "queen", "Flight of the Amazon Queen", GID_QUEEN_FIRST, 99, MDT_ADLIB | MDT_NATIVE | MDT_PREFER_NATIVE, 0, "queen.1" }, { NULL, NULL, 0, 0, MDT_NONE, 0, NULL} }; -const TargetSettings *Engine_QUEEN_targetList() { +const GameSettings *Engine_QUEEN_targetList() { return queen_settings; } diff --git a/scumm/scummvm.cpp b/scumm/scummvm.cpp index fefe41ac70..65d2ce355a 100644 --- a/scumm/scummvm.cpp +++ b/scumm/scummvm.cpp @@ -76,7 +76,7 @@ enum MouseButtonStatus { // Use g_scumm from error() ONLY ScummEngine *g_scumm = 0; -static const TargetSettings scumm_settings[] = { +static const GameSettings scumm_settings[] = { /* Scumm Version 1 */ /* Scumm Version 2 */ @@ -595,8 +595,8 @@ ScummEngine::ScummEngine(GameDetector *detector, OSystem *syst) _debugLevel = ConfMan.getInt("debuglevel"); _dumpScripts = detector->_dumpScripts; _bootParam = ConfMan.getInt("boot_param"); - _exe_name = strdup(detector->_game.targetName); - _game_name = strdup(detector->_gameFileName.c_str()); + _exe_name = strdup(detector->_game.gameName); + _game_name = strdup(detector->_targetName.c_str()); _gameId = detector->_game.id; _version = detector->_game.version; setFeatures(detector->_game.features); @@ -2634,7 +2634,7 @@ int normalizeAngle(int angle) { using namespace Scumm; -const TargetSettings *Engine_SCUMM_targetList() { +const GameSettings *Engine_SCUMM_targetList() { return scumm_settings; } diff --git a/simon/simon.cpp b/simon/simon.cpp index 9692c0ed29..ecce6f9c78 100644 --- a/simon/simon.cpp +++ b/simon/simon.cpp @@ -45,7 +45,7 @@ extern bool draw_keyboard; #endif -static const TargetSettings simon_settings[] = { +static const GameSettings simon_settings[] = { // Simon the Sorcerer 1 & 2 (not SCUMM games) {"simon1acorn", "Simon the Sorcerer 1 (Acorn)", GID_SIMON_FIRST, 99, MDT_ADLIB | MDT_NATIVE, GAME_SIMON1ACORN, "DATA"}, {"simon1dos", "Simon the Sorcerer 1 (DOS)", GID_SIMON_FIRST, 99, MDT_ADLIB | MDT_NATIVE, GAME_SIMON1DOS, "GAMEPC"}, @@ -62,7 +62,7 @@ static const TargetSettings simon_settings[] = { {NULL, NULL, 0, 0, MDT_NONE, 0, NULL} }; -const TargetSettings *Engine_SIMON_targetList() { +const GameSettings *Engine_SIMON_targetList() { return simon_settings; } diff --git a/sky/sky.cpp b/sky/sky.cpp index a987235d7d..e1e681d58e 100644 --- a/sky/sky.cpp +++ b/sky/sky.cpp @@ -76,13 +76,13 @@ extern bool draw_keyboard; #undef WITH_DEBUG_CHEATS -static const TargetSettings sky_settings[] = { +static const GameSettings sky_settings[] = { /* Beneath a Steel Sky */ {"sky", "Beneath a Steel Sky", GID_SKY_FIRST, 99, MDT_ADLIB | MDT_NATIVE | MDT_PREFER_NATIVE, 0, "sky.dsk" }, {NULL, NULL, 0, 0, MDT_NONE, 0, NULL} }; -const TargetSettings *Engine_SKY_targetList() { +const GameSettings *Engine_SKY_targetList() { return sky_settings; } diff --git a/sword2/sword2.cpp b/sword2/sword2.cpp index b8b81e3609..3e7491261d 100644 --- a/sword2/sword2.cpp +++ b/sword2/sword2.cpp @@ -48,7 +48,7 @@ extern uint16 _debugLevel; -static const TargetSettings sword2_settings[] = { +static const GameSettings sword2_settings[] = { /* Broken Sword 2 */ {"sword2", "Broken Sword II", GID_SWORD2, 99, MDT_ADLIB | MDT_NATIVE, GF_DEFAULT_TO_1X_SCALER, "players.clu" }, {"sword2alt", "Broken Sword II (alt)", GID_SWORD2, 99, MDT_ADLIB | MDT_NATIVE, GF_DEFAULT_TO_1X_SCALER, "r2ctlns.ocx" }, @@ -56,7 +56,7 @@ static const TargetSettings sword2_settings[] = { {NULL, NULL, 0, 0, MDT_NONE, 0, NULL} }; -const TargetSettings *Engine_SWORD2_targetList() { +const GameSettings *Engine_SWORD2_targetList() { return sword2_settings; } @@ -104,7 +104,7 @@ Sword2Engine::Sword2Engine(GameDetector *detector, OSystem *syst) g_sword2 = this; _features = detector->_game.features; _gameId = detector->_game.id; - _gameName = strdup(detector->_gameFileName.c_str()); + _gameName = strdup(detector->_targetName.c_str()); _bootParam = ConfMan.getInt("boot_param"); _saveSlot = ConfMan.getInt("save_slot"); _debugLevel = ConfMan.getInt("debuglevel"); -- cgit v1.2.3