1 /*
2 * This file is part of the Colobot: Gold Edition source code
3 * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam
4 * http://epsitec.ch; http://colobot.info; http://github.com/colobot
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see http://gnu.org/licenses
18 */
19
20 #include "app/modman.h"
21
22 #include "common/config.h"
23
24 #include "app/app.h"
25 #include "app/pathman.h"
26
27 #include "common/config_file.h"
28 #include "common/logger.h"
29
30 #include "common/resources/resourcemanager.h"
31
32 #include "level/parser/parser.h"
33
34 #include <algorithm>
35 #include <map>
36 #include <boost/filesystem.hpp>
37 #include <boost/algorithm/string.hpp>
38
39 using namespace boost::filesystem;
40
CModManager(CApplication * app,CPathManager * pathManager)41 CModManager::CModManager(CApplication* app, CPathManager* pathManager)
42 : m_app{app},
43 m_pathManager{pathManager}
44 {
45 }
46
FindMods()47 void CModManager::FindMods()
48 {
49 m_mods.clear();
50 m_userChanges = false;
51
52 // Load names from the config file
53 std::vector<std::string> savedModNames;
54 GetConfigFile().GetArrayProperty("Mods", "Names", savedModNames);
55 std::vector<bool> savedEnabled;
56 GetConfigFile().GetArrayProperty("Mods", "Enabled", savedEnabled);
57
58 // Transform the data into Mod structures
59 m_mods.reserve(savedModNames.size());
60 for (size_t i = 0; i < savedModNames.size(); ++i)
61 {
62 Mod mod{};
63 mod.name = savedModNames[i];
64 if (i < savedEnabled.size())
65 {
66 mod.enabled = savedEnabled[i];
67 }
68 mod.path = ""; // Find the path later
69 m_mods.push_back(mod);
70 }
71
72 // Search the folders for mods
73 auto rawPaths = m_pathManager->FindMods();
74 std::map<std::string, std::string> modPaths;
75 for (const auto& path : rawPaths)
76 {
77 auto modName = boost::filesystem::path(path).stem().string();
78 modPaths.insert(std::make_pair(modName, path));
79 }
80
81 // Find paths for already saved mods
82 auto it = m_mods.begin();
83 while (it != m_mods.end())
84 {
85 auto& mod = *it;
86 const auto pathsIt = modPaths.find(mod.name);
87 if (pathsIt != modPaths.end())
88 {
89 mod.path = (*pathsIt).second;
90 modPaths.erase(pathsIt);
91 ++it;
92 }
93 else
94 {
95 GetLogger()->Warn("Could not find mod %s, removing it from the list\n", mod.name.c_str());
96 it = m_mods.erase(it);
97 }
98 }
99
100 // Add the remaining found mods to the end of the list
101 for (const auto& newMod : modPaths)
102 {
103 Mod mod{};
104 mod.name = newMod.first;
105 mod.path = newMod.second;
106 m_mods.push_back(mod);
107 }
108
109 // Load the metadata for each mod
110
111 // Unfortunately, the paths are distinguished by their real paths, not mount points
112 // So we must unmount mods temporarily
113 for (const auto& path : m_mountedModPaths)
114 {
115 UnmountMod(path);
116 }
117
118 for (auto& mod : m_mods)
119 {
120 MountMod(mod, "/temp/mod");
121 LoadModData(mod);
122 UnmountMod(mod);
123 }
124
125 // Mount back
126 for (const auto& path : m_mountedModPaths)
127 {
128 MountMod(path);
129 }
130 }
131
ReloadMods()132 void CModManager::ReloadMods()
133 {
134 UnmountAllMountedMods();
135 MountAllMods();
136 ReloadResources();
137 }
138
EnableMod(size_t i)139 void CModManager::EnableMod(size_t i)
140 {
141 m_mods[i].enabled = true;
142 m_userChanges = true;
143 }
144
DisableMod(size_t i)145 void CModManager::DisableMod(size_t i)
146 {
147 m_mods[i].enabled = false;
148 m_userChanges = true;
149 }
150
MoveUp(size_t i)151 size_t CModManager::MoveUp(size_t i)
152 {
153 if (i != 0)
154 {
155 std::swap(m_mods[i - 1], m_mods[i]);
156 m_userChanges = true;
157 return i - 1;
158 }
159 else
160 {
161 return i;
162 }
163 }
164
MoveDown(size_t i)165 size_t CModManager::MoveDown(size_t i)
166 {
167 if (i != m_mods.size() - 1)
168 {
169 std::swap(m_mods[i], m_mods[i + 1]);
170 m_userChanges = true;
171 return i + 1;
172 }
173 else
174 {
175 return i;
176 }
177 }
178
Changes()179 bool CModManager::Changes()
180 {
181 std::vector<std::string> paths;
182 for (const auto& mod : m_mods)
183 {
184 if (mod.enabled)
185 {
186 paths.push_back(mod.path);
187 }
188 }
189 return paths != m_mountedModPaths || m_userChanges;
190 }
191
MountAllMods()192 void CModManager::MountAllMods()
193 {
194 for (const auto& mod : m_mods)
195 {
196 if (mod.enabled)
197 {
198 MountMod(mod);
199 m_mountedModPaths.push_back(mod.path);
200 }
201 }
202 }
203
ReloadResources()204 void CModManager::ReloadResources()
205 {
206 m_app->ReloadResources();
207 }
208
SaveMods()209 void CModManager::SaveMods()
210 {
211 std::vector<std::string> savedNames;
212 savedNames.reserve(m_mods.size());
213 std::transform(m_mods.begin(), m_mods.end(), std::back_inserter(savedNames), [](const Mod& mod) { return mod.name; });
214 GetConfigFile().SetArrayProperty("Mods", "Names", savedNames);
215
216 std::vector<bool> savedEnabled;
217 savedEnabled.reserve(m_mods.size());
218 std::transform(m_mods.begin(), m_mods.end(), std::back_inserter(savedEnabled), [](const Mod& mod) { return mod.enabled; });
219 GetConfigFile().SetArrayProperty("Mods", "Enabled", savedEnabled);
220
221 GetConfigFile().Save();
222
223 m_userChanges = false;
224 }
225
CountMods() const226 size_t CModManager::CountMods() const
227 {
228 return m_mods.size();
229 }
230
GetMod(size_t i) const231 const Mod& CModManager::GetMod(size_t i) const
232 {
233 return m_mods[i];
234 }
235
GetMods() const236 const std::vector<Mod>& CModManager::GetMods() const
237 {
238 return m_mods;
239 }
240
LoadModData(Mod & mod)241 void CModManager::LoadModData(Mod& mod)
242 {
243 auto& data = mod.data;
244
245 data.displayName = mod.name;
246
247 try
248 {
249 CLevelParser levelParser("temp/mod/manifest.txt");
250 if (levelParser.Exists())
251 {
252 levelParser.Load();
253
254 CLevelParserLine* line = nullptr;
255
256 // DisplayName
257 line = levelParser.GetIfDefined("DisplayName");
258 if (line != nullptr && line->GetParam("text")->IsDefined())
259 {
260 data.displayName = line->GetParam("text")->AsString();
261 }
262
263 // Author
264 line = levelParser.GetIfDefined("Author");
265 if (line != nullptr && line->GetParam("text")->IsDefined())
266 {
267 data.author = line->GetParam("text")->AsString();
268 }
269
270 // Version
271 line = levelParser.GetIfDefined("Version");
272 if (line != nullptr)
273 {
274 if (line->GetParam("text")->IsDefined())
275 {
276 data.version = line->GetParam("text")->AsString();
277 }
278 else if (line->GetParam("major")->IsDefined() && line->GetParam("minor")->IsDefined() && line->GetParam("patch")->IsDefined())
279 {
280 auto major = boost::lexical_cast<std::string>(line->GetParam("major")->AsInt());
281 auto minor = boost::lexical_cast<std::string>(line->GetParam("minor")->AsInt());
282 auto patch = boost::lexical_cast<std::string>(line->GetParam("patch")->AsInt());
283 data.version = boost::algorithm::join(std::vector<std::string>{ major, minor, patch }, ".");
284 }
285 }
286
287 // Website
288 line = levelParser.GetIfDefined("Website");
289 if (line != nullptr && line->GetParam("text")->IsDefined())
290 {
291 data.website = line->GetParam("text")->AsString();
292 }
293
294 // Summary
295 line = levelParser.GetIfDefined("Summary");
296 if (line != nullptr && line->GetParam("text")->IsDefined())
297 {
298 data.summary = line->GetParam("text")->AsString();
299 }
300 }
301 else
302 {
303 GetLogger()->Warn("No manifest file for mod %s\n", mod.name.c_str());
304 }
305 }
306 catch (CLevelParserException& e)
307 {
308 GetLogger()->Warn("Failed parsing manifest for mod %s: %s\n", mod.name.c_str(), e.what());
309 }
310
311 // Changes
312 data.changes = CResourceManager::ListDirectories("temp/mod");
313 auto levelsIt = std::find(data.changes.begin(), data.changes.end(), "levels");
314 if (levelsIt != data.changes.end())
315 {
316 auto levelsDirs = CResourceManager::ListDirectories("temp/mod/levels");
317 if (!levelsDirs.empty())
318 {
319 std::transform(levelsDirs.begin(), levelsDirs.end(), levelsDirs.begin(), [](const std::string& dir) { return "levels/" + dir; });
320 levelsIt = data.changes.erase(levelsIt);
321 data.changes.insert(levelsIt, levelsDirs.begin(), levelsDirs.end());
322 }
323 }
324 }
325
MountMod(const Mod & mod,const std::string & mountPoint)326 void CModManager::MountMod(const Mod& mod, const std::string& mountPoint)
327 {
328 MountMod(mod.path, mountPoint);
329 }
330
MountMod(const std::string & path,const std::string & mountPoint)331 void CModManager::MountMod(const std::string& path, const std::string& mountPoint)
332 {
333 GetLogger()->Debug("Mounting mod: '%s' at path %s\n", path.c_str(), mountPoint.c_str());
334 CResourceManager::AddLocation(path, true, mountPoint);
335 }
336
UnmountMod(const Mod & mod)337 void CModManager::UnmountMod(const Mod& mod)
338 {
339 UnmountMod(mod.path);
340 }
341
UnmountMod(const std::string & path)342 void CModManager::UnmountMod(const std::string& path)
343 {
344 if (CResourceManager::LocationExists(path))
345 {
346 GetLogger()->Debug("Unmounting mod: '%s'\n", path.c_str());
347 CResourceManager::RemoveLocation(path);
348 }
349 }
350
UnmountAllMountedMods()351 void CModManager::UnmountAllMountedMods()
352 {
353 for (const auto& path : m_mountedModPaths)
354 {
355 UnmountMod(path);
356 }
357 m_mountedModPaths.clear();
358 }
359