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