1 // Copyright 2016 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 #include "Core/ConfigLoaders/GameConfigLoader.h"
6 
7 #include <algorithm>
8 #include <array>
9 #include <list>
10 #include <map>
11 #include <optional>
12 #include <sstream>
13 #include <string>
14 #include <tuple>
15 #include <utility>
16 #include <vector>
17 
18 #include <fmt/format.h>
19 
20 #include "Common/CommonPaths.h"
21 #include "Common/Config/Config.h"
22 #include "Common/FileUtil.h"
23 #include "Common/IniFile.h"
24 #include "Common/Logging/Log.h"
25 #include "Common/MsgHandler.h"
26 #include "Common/StringUtil.h"
27 
28 #include "Core/Config/SYSCONFSettings.h"
29 #include "Core/ConfigLoaders/IsSettingSaveable.h"
30 
31 namespace ConfigLoaders
32 {
33 // Returns all possible filenames in ascending order of priority
GetGameIniFilenames(const std::string & id,std::optional<u16> revision)34 std::vector<std::string> GetGameIniFilenames(const std::string& id, std::optional<u16> revision)
35 {
36   std::vector<std::string> filenames;
37 
38   if (id.empty())
39     return filenames;
40 
41   // Using the first letter or the 3 letters of the ID only makes sense
42   // if the ID is an actual game ID (which has 6 characters).
43   if (id.length() == 6)
44   {
45     // INIs that match the system code (unique for each Virtual Console system)
46     filenames.push_back(id.substr(0, 1) + ".ini");
47 
48     // INIs that match all regions
49     filenames.push_back(id.substr(0, 3) + ".ini");
50   }
51 
52   // Regular INIs
53   filenames.push_back(id + ".ini");
54 
55   // INIs with specific revisions
56   if (revision)
57     filenames.push_back(id + fmt::format("r{}", *revision) + ".ini");
58 
59   return filenames;
60 }
61 
62 using Location = Config::Location;
63 using INIToLocationMap = std::map<std::pair<std::string, std::string>, Location>;
64 using INIToSectionMap = std::map<std::string, std::pair<Config::System, std::string>>;
65 
66 // This is a mapping from the legacy section-key pairs to Locations.
67 // New settings do not need to be added to this mapping.
68 // See also: MapINIToRealLocation and GetINILocationFromConfig.
GetINIToLocationMap()69 static const INIToLocationMap& GetINIToLocationMap()
70 {
71   static const INIToLocationMap ini_to_location = {
72       {{"Core", "ProgressiveScan"}, {Config::SYSCONF_PROGRESSIVE_SCAN.location}},
73       {{"Core", "PAL60"}, {Config::SYSCONF_PAL60.location}},
74       {{"Wii", "Widescreen"}, {Config::SYSCONF_WIDESCREEN.location}},
75       {{"Wii", "Language"}, {Config::SYSCONF_LANGUAGE.location}},
76   };
77   return ini_to_location;
78 }
79 
80 // This is a mapping from the legacy section names to system + section.
81 // New settings do not need to be added to this mapping.
82 // See also: MapINIToRealLocation and GetINILocationFromConfig.
GetINIToSectionMap()83 static const INIToSectionMap& GetINIToSectionMap()
84 {
85   static const INIToSectionMap ini_to_section = {
86       {"Core", {Config::System::Main, "Core"}},
87       {"Display", {Config::System::Main, "Display"}},
88       {"Video_Hardware", {Config::System::GFX, "Hardware"}},
89       {"Video_Settings", {Config::System::GFX, "Settings"}},
90       {"Video_Enhancements", {Config::System::GFX, "Enhancements"}},
91       {"Video_Stereoscopy", {Config::System::GFX, "Stereoscopy"}},
92       {"Video_Hacks", {Config::System::GFX, "Hacks"}},
93       {"Video", {Config::System::GFX, "GameSpecific"}},
94   };
95   return ini_to_section;
96 }
97 
98 // Converts from a legacy GameINI section-key tuple to a Location.
99 // Also supports the following format:
100 // [System.Section]
101 // Key = Value
MapINIToRealLocation(const std::string & section,const std::string & key)102 static Location MapINIToRealLocation(const std::string& section, const std::string& key)
103 {
104   static const INIToLocationMap& ini_to_location = GetINIToLocationMap();
105   const auto it = ini_to_location.find({section, key});
106   if (it != ini_to_location.end())
107     return it->second;
108 
109   static const INIToSectionMap& ini_to_section = GetINIToSectionMap();
110   const auto it2 = ini_to_section.find(section);
111   if (it2 != ini_to_section.end())
112     return {it2->second.first, it2->second.second, key};
113 
114   // Attempt to load it as a configuration option
115   // It will be in the format of '<System>.<Section>'
116   std::istringstream buffer(section);
117   std::string system_str, config_section;
118 
119   bool fail = false;
120   std::getline(buffer, system_str, '.');
121   fail |= buffer.fail();
122   std::getline(buffer, config_section, '.');
123   fail |= buffer.fail();
124 
125   const std::optional<Config::System> system = Config::GetSystemFromName(system_str);
126   if (!fail && system)
127     return {*system, config_section, key};
128 
129   WARN_LOG(CORE, "Unknown game INI option in section %s: %s", section.c_str(), key.c_str());
130   return {Config::System::Main, "", ""};
131 }
132 
GetINILocationFromConfig(const Location & location)133 static std::pair<std::string, std::string> GetINILocationFromConfig(const Location& location)
134 {
135   static const INIToLocationMap& ini_to_location = GetINIToLocationMap();
136   const auto it = std::find_if(ini_to_location.begin(), ini_to_location.end(),
137                                [&location](const auto& entry) { return entry.second == location; });
138   if (it != ini_to_location.end())
139     return it->first;
140 
141   static const INIToSectionMap& ini_to_section = GetINIToSectionMap();
142   const auto it2 =
143       std::find_if(ini_to_section.begin(), ini_to_section.end(), [&location](const auto& entry) {
144         return entry.second.first == location.system && entry.second.second == location.section;
145       });
146   if (it2 != ini_to_section.end())
147     return {it2->first, location.key};
148 
149   return {Config::GetSystemName(location.system) + "." + location.section, location.key};
150 }
151 
152 // INI Game layer configuration loader
153 class INIGameConfigLayerLoader final : public Config::ConfigLayerLoader
154 {
155 public:
INIGameConfigLayerLoader(const std::string & id,u16 revision,bool global)156   INIGameConfigLayerLoader(const std::string& id, u16 revision, bool global)
157       : ConfigLayerLoader(global ? Config::LayerType::GlobalGame : Config::LayerType::LocalGame),
158         m_id(id), m_revision(revision)
159   {
160   }
161 
Load(Config::Layer * layer)162   void Load(Config::Layer* layer) override
163   {
164     IniFile ini;
165     if (layer->GetLayer() == Config::LayerType::GlobalGame)
166     {
167       for (const std::string& filename : GetGameIniFilenames(m_id, m_revision))
168         ini.Load(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename, true);
169     }
170     else
171     {
172       for (const std::string& filename : GetGameIniFilenames(m_id, m_revision))
173         ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + filename, true);
174     }
175 
176     const std::list<IniFile::Section>& system_sections = ini.GetSections();
177 
178     for (const auto& section : system_sections)
179     {
180       LoadFromSystemSection(layer, section);
181     }
182 
183     LoadControllerConfig(layer);
184   }
185 
186   void Save(Config::Layer* layer) override;
187 
188 private:
LoadControllerConfig(Config::Layer * layer) const189   void LoadControllerConfig(Config::Layer* layer) const
190   {
191     // Game INIs can have controller profiles embedded in to them
192     static const std::array<char, 4> nums = {{'1', '2', '3', '4'}};
193 
194     if (m_id == "00000000")
195       return;
196 
197     const std::array<std::tuple<std::string, std::string, Config::System>, 2> profile_info = {{
198         std::make_tuple("Pad", "GCPad", Config::System::GCPad),
199         std::make_tuple("Wiimote", "Wiimote", Config::System::WiiPad),
200     }};
201 
202     for (const auto& use_data : profile_info)
203     {
204       std::string type = std::get<0>(use_data);
205       std::string path = "Profiles/" + std::get<1>(use_data) + "/";
206 
207       const auto control_section = [&](std::string key) {
208         return Config::Location{std::get<2>(use_data), "Controls", key};
209       };
210 
211       for (const char num : nums)
212       {
213         if (auto profile = layer->Get<std::string>(control_section(type + "Profile" + num)))
214         {
215           std::string ini_path = File::GetUserPath(D_CONFIG_IDX) + path + *profile + ".ini";
216           if (!File::Exists(ini_path))
217           {
218             // TODO: PanicAlert shouldn't be used for this.
219             PanicAlertT("Selected controller profile does not exist");
220             continue;
221           }
222 
223           IniFile profile_ini;
224           profile_ini.Load(ini_path);
225 
226           const IniFile::Section* ini_section = profile_ini.GetOrCreateSection("Profile");
227           const IniFile::Section::SectionMap& section_map = ini_section->GetValues();
228           for (const auto& value : section_map)
229           {
230             Config::Location location{std::get<2>(use_data), std::get<1>(use_data) + num,
231                                       value.first};
232             layer->Set(location, value.second);
233           }
234         }
235       }
236     }
237   }
238 
LoadFromSystemSection(Config::Layer * layer,const IniFile::Section & section) const239   void LoadFromSystemSection(Config::Layer* layer, const IniFile::Section& section) const
240   {
241     const std::string section_name = section.GetName();
242 
243     // Regular key,value pairs
244     const IniFile::Section::SectionMap& section_map = section.GetValues();
245 
246     for (const auto& value : section_map)
247     {
248       const auto location = MapINIToRealLocation(section_name, value.first);
249 
250       if (location.section.empty() && location.key.empty())
251         continue;
252 
253       layer->Set(location, value.second);
254     }
255   }
256 
257   const std::string m_id;
258   const u16 m_revision;
259 };
260 
Save(Config::Layer * layer)261 void INIGameConfigLayerLoader::Save(Config::Layer* layer)
262 {
263   if (layer->GetLayer() != Config::LayerType::LocalGame)
264     return;
265 
266   IniFile ini;
267   for (const std::string& file_name : GetGameIniFilenames(m_id, m_revision))
268     ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + file_name, true);
269 
270   for (const auto& config : layer->GetLayerMap())
271   {
272     const Config::Location& location = config.first;
273     const std::optional<std::string>& value = config.second;
274 
275     if (!IsSettingSaveable(location))
276       continue;
277 
278     const auto ini_location = GetINILocationFromConfig(location);
279     if (ini_location.first.empty() && ini_location.second.empty())
280       continue;
281 
282     if (value)
283     {
284       IniFile::Section* ini_section = ini.GetOrCreateSection(ini_location.first);
285       ini_section->Set(ini_location.second, *value);
286     }
287     else
288     {
289       ini.DeleteKey(ini_location.first, ini_location.second);
290     }
291   }
292 
293   // Try to save to the revision specific INI first, if it exists.
294   const std::string gameini_with_rev =
295       File::GetUserPath(D_GAMESETTINGS_IDX) + m_id + fmt::format("r{}", m_revision) + ".ini";
296   if (File::Exists(gameini_with_rev))
297   {
298     ini.Save(gameini_with_rev);
299     return;
300   }
301 
302   // Otherwise, save to the game INI. We don't try any INI broader than that because it will
303   // likely cause issues with cheat codes and game patches.
304   const std::string gameini = File::GetUserPath(D_GAMESETTINGS_IDX) + m_id + ".ini";
305   ini.Save(gameini);
306 }
307 
308 // Loader generation
GenerateGlobalGameConfigLoader(const std::string & id,u16 revision)309 std::unique_ptr<Config::ConfigLayerLoader> GenerateGlobalGameConfigLoader(const std::string& id,
310                                                                           u16 revision)
311 {
312   return std::make_unique<INIGameConfigLayerLoader>(id, revision, true);
313 }
314 
GenerateLocalGameConfigLoader(const std::string & id,u16 revision)315 std::unique_ptr<Config::ConfigLayerLoader> GenerateLocalGameConfigLoader(const std::string& id,
316                                                                          u16 revision)
317 {
318   return std::make_unique<INIGameConfigLayerLoader>(id, revision, false);
319 }
320 }  // namespace ConfigLoaders
321