1
1
forked from 0ad/0ad

Add engine support to load mods from config and restart into mods.

Restructure mod mounting code a bit to make it reusable, and use it for
replays. Fixes #2703.

This was SVN commit r15676.
This commit is contained in:
leper 2014-08-25 16:02:40 +00:00
parent 09e6d5ae02
commit 523d220ac5
13 changed files with 222 additions and 78 deletions

View File

@ -392,6 +392,7 @@ static void MainControllerInit()
static void MainControllerShutdown()
{
in_reset_handlers();
}
@ -412,12 +413,24 @@ void restart_mainloop_in_atlas()
restart_in_atlas = true;
}
static bool restart = false;
// trigger an orderly shutdown and restart the game.
void restart_engine()
{
quit = true;
restart = true;
}
extern CmdLineArgs g_args;
// moved into a helper function to ensure args is destroyed before
// exit(), which may result in a memory leak.
static void RunGameOrAtlas(int argc, const char* argv[])
{
CmdLineArgs args(argc, argv);
g_args = args;
// We need to initialise libxml2 in the main thread before
// any thread uses it. So initialise it here before we
// might run Atlas.
@ -433,11 +446,10 @@ static void RunGameOrAtlas(int argc, const char* argv[])
// run non-visual simulation replay if requested
if (args.Has("replay"))
{
// TODO: Support mods
Paths paths(args);
g_VFS = CreateVfs(20 * MiB);
g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
g_VFS->Mount(L"", paths.RData()/"mods"/"public", VFS_MOUNT_MUST_EXIST);
MountMods(paths, GetMods(args, INIT_MODS));
{
CReplayPlayer replay;
@ -481,13 +493,25 @@ static void RunGameOrAtlas(int argc, const char* argv[])
g_frequencyFilter = CreateFrequencyFilter(res, 30.0);
// run the game
Init(args, 0);
InitGraphics(args, 0);
MainControllerInit();
while(!quit)
Frame();
Shutdown(0);
MainControllerShutdown();
int flags = INIT_MODS;
do
{
restart = false;
quit = false;
if (!Init(args, flags))
{
flags &= ~INIT_MODS;
Shutdown(SHUTDOWN_FROM_CONFIG);
continue;
}
InitGraphics(args, 0);
MainControllerInit();
while (!quit)
Frame();
Shutdown(0);
MainControllerShutdown();
flags &= ~INIT_MODS;
} while (restart);
if (restart_in_atlas)
{
@ -519,7 +543,7 @@ extern "C" int main(int argc, char* argv[])
<< "This is not allowed because it can alter home directory \n"
<< "permissions and opens your system to vulnerabilities. \n"
<< "(You received this message because you were either \n"
<<" logged in as root or used e.g. the 'sudo' command.) \n"
<<" logged in as root or used e.g. the 'sudo' command.) \n"
<< "********************************************************\n\n";
return EXIT_FAILURE;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -71,6 +71,8 @@ public:
CStr(const tchar* String) : std::tstring(String) {}
CStr(const tchar* String, size_t Length) : std::tstring(String, Length) {}
CStr(const std::tstring& String) : std::tstring(String) {}
template <class InputIterator>
CStr (InputIterator first, InputIterator last) : std::tstring(first, last) {}
/**
* Repeat: Named constructor, to avoid overload overload.

View File

@ -111,7 +111,7 @@ CVFSFile::~CVFSFile()
{
}
PSRETURN CVFSFile::Load(const PIVFS& vfs, const VfsPath& filename)
PSRETURN CVFSFile::Load(const PIVFS& vfs, const VfsPath& filename, bool log /* = true */)
{
// Load should never be called more than once, so complain
if (m_Buffer)
@ -123,7 +123,8 @@ PSRETURN CVFSFile::Load(const PIVFS& vfs, const VfsPath& filename)
Status ret = vfs->LoadFile(filename, m_Buffer, m_BufferSize);
if (ret != INFO::OK)
{
LOGERROR(L"CVFSFile: file %ls couldn't be opened (vfs_load: %lld)", filename.string().c_str(), (long long)ret);
if (log)
LOGERROR(L"CVFSFile: file %ls couldn't be opened (vfs_load: %lld)", filename.string().c_str(), (long long)ret);
m_Buffer.reset();
m_BufferSize = 0;
return PSRETURN_CVFSFile_LoadFailed;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -75,8 +75,9 @@ public:
/**
* Returns either PSRETURN_OK or PSRETURN_CVFSFile_LoadFailed
* @note Dies if the file has already been successfully loaded
* @param log Whether to log a failure to load a file
*/
PSRETURN Load(const PIVFS& vfs, const VfsPath& filename);
PSRETURN Load(const PIVFS& vfs, const VfsPath& filename, bool log = true);
/**
* Returns buffer of this file as a stream of bytes

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -64,12 +64,12 @@
#include "ps/Hotkey.h"
#include "ps/Joystick.h"
#include "ps/Loader.h"
#include "ps/Mod.h"
#include "ps/Overlay.h"
#include "ps/Profile.h"
#include "ps/ProfileViewer.h"
#include "ps/Profiler2.h"
#include "ps/Pyrogenesis.h" // psSetLogDir
#include "ps/SavedGame.h"
#include "ps/scripting/JSInterface_Console.h"
#include "ps/TouchInput.h"
#include "ps/UserReport.h"
@ -101,8 +101,13 @@
extern void wmi_Shutdown();
#endif
extern void restart_engine();
#include <iostream>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
ERROR_GROUP(System);
ERROR_TYPE(System, SDLInitFailed);
ERROR_TYPE(System, VmodeFailed);
@ -392,22 +397,59 @@ ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(
return ERI_NOT_IMPLEMENTED;
}
static std::vector<CStr> GetMods(const CmdLineArgs& args)
std::vector<CStr>& GetMods(const CmdLineArgs& args, int flags)
{
std::vector<CStr> mods = args.GetMultiple("mod");
// List of the mods, to be used by the Gui
g_modsLoaded.clear();
for (size_t i = 0; i < mods.size(); ++i)
g_modsLoaded.push_back((std::string)mods[i]);
// TODO: It would be nice to remove this hard-coding
mods.insert(mods.begin(), "public");
const bool init_mods = (flags & INIT_MODS) == INIT_MODS;
const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod");
if (!init_mods)
{
// Add the user mod if it should be present
if (add_user && (g_modsLoaded.empty() || g_modsLoaded.back() != "user"))
g_modsLoaded.push_back("user");
return g_modsLoaded;
}
g_modsLoaded = args.GetMultiple("mod");
// TODO: It would be nice to remove this hard-coding of public
g_modsLoaded.insert(g_modsLoaded.begin(), "public");
// Add the user mod if not explicitly disabled or we have a dev copy so
// that saved files end up in version control and not in the user mod.
if (!InDevelopmentCopy() && !args.Has("noUserMod"))
mods.push_back("user");
if (add_user)
g_modsLoaded.push_back("user");
return mods;
return g_modsLoaded;
}
void MountMods(const Paths& paths, const std::vector<CStr>& mods)
{
OsPath modPath = paths.RData()/"mods";
OsPath modUserPath = paths.UserData()/"mods";
for (size_t i = 0; i < mods.size(); ++i)
{
size_t priority = (i+1)*2; // mods are higher priority than regular mountings, which default to priority 0
size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_REPLACEABLE;
size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST;
OsPath modName(mods[i]);
if (InDevelopmentCopy())
{
// We are running a dev copy, so only mount mods in the user mod path
// if the mod does not exist in the data path.
if (DirectoryExists(modPath / modName/""))
g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
else
g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority);
}
else
{
g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
// Ensure that user modified files are loaded, if they are present
g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority+1);
}
}
}
static void InitVfs(const CmdLineArgs& args, int flags)
@ -434,41 +476,13 @@ static void InitVfs(const CmdLineArgs& args, int flags)
const size_t cacheSize = ChooseCacheSize();
g_VFS = CreateVfs(cacheSize);
// Work out whether we are a dev version to make sure saved files
// (maps, etc) end up in version control.
const OsPath readonlyConfig = paths.RData()/"config"/"";
g_VFS->Mount(L"config/", readonlyConfig);
// Engine localization files.
g_VFS->Mount(L"l10n/", paths.RData()/"l10n"/"");
const std::vector<CStr> mods = GetMods(args);
OsPath modPath = paths.RData()/"mods";
OsPath modUserPath = paths.UserData()/"mods";
for (size_t i = 0; i < mods.size(); ++i)
{
size_t priority = (i+1)*2; // mods are higher priority than regular mountings, which default to priority 0
size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_REPLACEABLE;
size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST;
OsPath modName(mods[i]);
if (InDevelopmentCopy())
{
// We are running a dev copy, so only mount mods in the user mod path
// if the mod does not exist in the data path.
if (DirectoryExists(modPath / modName/""))
g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
else
g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority);
}
else
{
g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority);
// Ensure that user modified files are loaded, if they are present
g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority+1);
}
}
MountMods(paths, GetMods(args, flags));
// We mount these dirs last as otherwise writing could result in files being placed in a mod's dir.
g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/"");
@ -566,10 +580,6 @@ static void ShutdownPs()
SAFE_DELETE(g_Console);
// This is needed to ensure that no callbacks from the JSAPI try to use
// the profiler when it's already destructed
g_ScriptRuntime.reset();
// disable the special Windows cursor, or free textures for OGL cursors
cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y, false);
}
@ -670,16 +680,17 @@ void EndGame()
}
void Shutdown(int UNUSED(flags))
void Shutdown(int flags)
{
if ((flags & SHUTDOWN_FROM_CONFIG))
goto from_config;
EndGame();
SAFE_DELETE(g_XmppClient);
ShutdownPs();
in_reset_handlers();
TIMER_BEGIN(L"shutdown TexMan");
delete &g_TexMan;
TIMER_END(L"shutdown TexMan");
@ -705,15 +716,21 @@ void Shutdown(int UNUSED(flags))
g_UserReporter.Deinitialize();
TIMER_END(L"shutdown UserReporter");
// JS debugger temporarily disabled during the SpiderMonkey upgrade (check trac ticket #2348 for details)
//TIMER_BEGIN(L"shutdown DebuggingServer (if active)");
//delete g_DebuggingServer;
//TIMER_END(L"shutdown DebuggingServer (if active)");
from_config:
TIMER_BEGIN(L"shutdown ConfigDB");
delete &g_ConfigDB;
TIMER_END(L"shutdown ConfigDB");
// This is needed to ensure that no callbacks from the JSAPI try to use
// the profiler when it's already destructed
g_ScriptRuntime.reset();
// resource
// first shut down all resource owners, and then the handle manager.
TIMER_BEGIN(L"resource modules");
@ -850,7 +867,7 @@ void EarlyInit()
bool Autostart(const CmdLineArgs& args);
void Init(const CmdLineArgs& args, int flags)
bool Init(const CmdLineArgs& args, int flags)
{
h_mgr_init();
@ -898,7 +915,6 @@ void Init(const CmdLineArgs& args, int flags)
g_ScriptStatsTable = new CScriptStatsTable;
g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
#if CONFIG2_AUDIO
ISoundManager::CreateSoundManager();
#endif
@ -906,6 +922,27 @@ void Init(const CmdLineArgs& args, int flags)
// g_ConfigDB, command line args, globals
CONFIG_Init(args);
// Check if there are mods specified on the command line,
// or if we already set the mods (~INIT_MODS),
// else check if there are mods that should be loaded specified
// in the config and load those (by aborting init and restarting
// the engine).
if (!args.Has("mod") && (flags & INIT_MODS) == INIT_MODS)
{
CStr modstring;
CFG_GET_VAL("mod.enabledmods", String, modstring);
if (!modstring.empty())
{
std::vector<CStr> mods;
boost::split(mods, modstring, boost::is_any_of(" "), boost::token_compress_on);
std::swap(g_modsLoaded, mods);
// Abort init and restart
restart_engine();
return false;
}
}
// before scripting
// JS debugger temporarily disabled during the SpiderMonkey upgrade (check trac ticket #2348 for details)
//if (g_JSDebuggerEnabled)
@ -922,6 +959,7 @@ void Init(const CmdLineArgs& args, int flags)
g_UserReporter.Initialize(); // after config
PROFILE2_EVENT("Init finished");
return true;
}
void InitGraphics(const CmdLineArgs& args, int flags)
@ -1067,7 +1105,7 @@ void RenderCursor(bool RenderingState)
* games do not use a "map" (format) but a small JavaScript program which
* creates a map on the fly). It contains a section to initialize the game
* setup screen.
* @param mapPath Absolute path (from VSF root) to the map file to peek in.
* @param mapPath Absolute path (from VFS root) to the map file to peek in.
* @return ScriptSettings in JSON format extracted from the map.
*/
CStr8 LoadSettingsOfScenarioMap(const VfsPath &mapPath)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -46,7 +46,18 @@ enum InitFlags
// avoid setting display_error app hook
// needed by map editor because it has its own wx error display
INIT_HAVE_DISPLAY_ERROR = 4
INIT_HAVE_DISPLAY_ERROR = 4,
// initialize the mod folders from command line parameters
INIT_MODS = 8
};
enum ShutdownFlags
{
// start shutdown from config down
// needed for loading mods as specified in the config
// without having to go through a full init-shutdown cycle
SHUTDOWN_FROM_CONFIG = 1
};
/**
@ -62,7 +73,14 @@ extern void RenderCursor(bool RenderingState);
class CmdLineArgs;
extern void Init(const CmdLineArgs& args, int flags);
class Paths;
extern std::vector<CStr>& GetMods(const CmdLineArgs& args, int flags);
extern void MountMods(const Paths& paths, const std::vector<CStr>& mods);
/**
* Returns true if successful, false if mods changed and restart_engine was called.
* In the latter case the caller should call Shutdown() with SHUTDOWN_FROM_CONFIG.
*/
extern bool Init(const CmdLineArgs& args, int flags);
extern void InitGraphics(const CmdLineArgs& args, int flags);
extern void Shutdown(int flags);
extern void CancelLoad(const CStrW& message);

24
source/ps/Mod.cpp Normal file
View File

@ -0,0 +1,24 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "ps/Mod.h"
std::vector<CStr> g_modsLoaded;
CmdLineArgs g_args;

27
source/ps/Mod.h Normal file
View File

@ -0,0 +1,27 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_MOD
#define INCLUDED_MOD
#include "ps/CStr.h"
#include "ps/GameSetup/CmdLineArgs.h"
extern std::vector<CStr> g_modsLoaded;
extern CmdLineArgs g_args;
#endif // INCLUDED_MOD

View File

@ -28,12 +28,12 @@
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Mod.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
static const int SAVED_GAME_VERSION_MAJOR = 1; // increment on incompatible changes to the format
static const int SAVED_GAME_VERSION_MINOR = 0; // increment on compatible changes to the format
std::vector<std::string> g_modsLoaded; // list of mods loaded
// TODO: we ought to check version numbers when loading files

View File

@ -102,7 +102,4 @@ CScriptValRooted GetEngineInfo(ScriptInterface& scriptInterface);
}
// list of mods currently loaded
extern std::vector<std::string> g_modsLoaded;
#endif // INCLUDED_SAVEDGAME

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -17,7 +17,7 @@
#include "lib/self_test.h"
// usually defined by main.cpp, used by engine's scripting/ScriptGlue.cpp,
// usually defined by main.cpp, used by engine's scripting/ScriptFunctions.cpp,
// must be included here to placate linker.
void kill_mainloop()
{
@ -27,6 +27,10 @@ void restart_mainloop_in_atlas()
{
}
void restart_engine()
{
}
// just so that cxxtestgen doesn't complain "No tests defined"
class TestDummy : public CxxTest::TestSuite
{

View File

@ -421,6 +421,7 @@ VECTOR(u32)
VECTOR(u16)
VECTOR(std::string)
VECTOR(std::wstring)
VECTOR(CStr8)
VECTOR(CScriptValRooted)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -46,7 +46,14 @@ MESSAGEHANDLER(Init)
g_Quickstart = true;
Init(g_AtlasGameLoop->args, g_InitFlags);
// Mount mods if there are any specified as command line parameters
if (!Init(g_AtlasGameLoop->args, g_InitFlags | INIT_MODS))
{
// There are no mods specified on the command line,
// but there are in the config file, so mount those.
Shutdown(SHUTDOWN_FROM_CONFIG);
ENSURE(Init(g_AtlasGameLoop->args, g_InitFlags));
}
// Initialise some graphics state for Atlas.
// (This must be done after Init loads the config DB,