1 /*
2  *  Copyright (C) 2016-2020 Team Kodi (https://kodi.tv)
3  *
4  *  SPDX-License-Identifier: GPL-2.0-or-later
5  *  See LICENSE.md for more information.
6  */
7 
8 #include "LibretroSettings.h"
9 #include "LanguageGenerator.h"
10 #include "SettingsGenerator.h"
11 #include "libretro/libretro.h"
12 #include "log/Log.h"
13 #include "client.h"
14 
15 #include <kodi/Filesystem.h>
16 
17 #include <algorithm>
18 #include <assert.h>
19 #include <utility>
20 
21 using namespace LIBRETRO;
22 
CLibretroSettings()23 CLibretroSettings::CLibretroSettings() :
24   m_addon(nullptr),
25   m_bChanged(true),
26   m_bGenerated(false)
27 {
28 }
29 
Initialize(CGameLibRetro * addon)30 void CLibretroSettings::Initialize(CGameLibRetro* addon)
31 {
32   m_addon = addon;
33   assert(m_addon != nullptr);
34 
35   m_profileDirectory = m_addon->ProfileDirectory();
36 }
37 
Deinitialize()38 void CLibretroSettings::Deinitialize()
39 {
40   m_addon = nullptr;
41 }
42 
Changed()43 bool CLibretroSettings::Changed()
44 {
45   std::unique_lock<std::mutex> lock(m_mutex);
46   return m_bChanged;
47 }
48 
SetUnchanged()49 void CLibretroSettings::SetUnchanged()
50 {
51   std::unique_lock<std::mutex> lock(m_mutex);
52   m_bChanged = false;
53 }
54 
SetAllSettings(const retro_variable * libretroVariables)55 void CLibretroSettings::SetAllSettings(const retro_variable* libretroVariables)
56 {
57   // Keep track of whether Kodi has the correct settings
58   bool bValid = true;
59 
60   std::unique_lock<std::mutex> lock(m_mutex);
61 
62   if (m_settings.empty())
63   {
64     for (const retro_variable* variable = libretroVariables; variable && variable->key && variable->value; variable++)
65     {
66       CLibretroSetting setting(variable);
67 
68       if (setting.Values().empty())
69       {
70         esyslog("Setting \"%s\": No pipe-delimited options: \"%s\"", variable->key, variable->value);
71         continue;
72       }
73 
74       // Query current value for setting from the frontend
75       std::string valueBuf;
76       if (kodi::CheckSettingString(variable->key, valueBuf))
77       {
78         if (std::find(setting.Values().begin(), setting.Values().end(), valueBuf) != setting.Values().end())
79         {
80           dsyslog("Setting %s has value \"%s\" in Kodi",  setting.Key().c_str(), valueBuf.c_str());
81           setting.SetCurrentValue(valueBuf);
82         }
83         else
84         {
85           esyslog("Setting %s: invalid value \"%s\" (values are: %s)", setting.Key().c_str(), valueBuf.c_str(), variable->value);
86           bValid = false;
87         }
88       }
89       else
90       {
91         esyslog("Setting %s not found by Kodi", setting.Key().c_str());
92         bValid = false;
93       }
94 
95       m_settings.insert(std::make_pair(setting.Key(), std::move(setting)));
96     }
97 
98     m_bChanged = true;
99   }
100 
101   if (!bValid)
102     GenerateSettings();
103 }
104 
GetCurrentValue(const std::string & settingName)105 const char* CLibretroSettings::GetCurrentValue(const std::string& settingName)
106 {
107   std::unique_lock<std::mutex> lock(m_mutex);
108 
109   auto it = m_settings.find(settingName);
110   if (it == m_settings.end())
111   {
112     esyslog("Unknown setting ID: %s", settingName.c_str());
113     return "";
114   }
115 
116   return it->second.CurrentValue().c_str();
117 }
118 
SetCurrentValue(const std::string & name,const std::string & value)119 void CLibretroSettings::SetCurrentValue(const std::string& name, const std::string& value)
120 {
121   std::unique_lock<std::mutex> lock(m_mutex);
122 
123   if (m_settings.empty())
124   {
125     // RETRO_ENVIRONMENT_SET_VARIABLES hasn't been called yet. We don't need to
126     // record the setting now because it will be retrieved from the frontend
127     // later.
128     return;
129   }
130 
131   // Keep track of whether Kodi has the correct settings
132   bool bValid = true;
133 
134   // Check to make sure value is a valid value reported by libretro
135   auto it = m_settings.find(name);
136   if (it == m_settings.end())
137   {
138     esyslog("Kodi setting %s unknown to libretro!", name.c_str());
139     bValid = false;
140   }
141   else if (it->second.CurrentValue() != value)
142   {
143     it->second.SetCurrentValue(value);
144     m_bChanged = true;
145   }
146 
147   if (!bValid)
148     GenerateSettings();
149 }
150 
GenerateSettings()151 void CLibretroSettings::GenerateSettings()
152 {
153   if (!m_bGenerated && !m_settings.empty())
154   {
155     isyslog("Invalid settings detected, generating new settings and language files");
156 
157     std::string generatedPath = m_profileDirectory;
158 
159     std::string addonId = kodi::vfs::GetFileName(generatedPath);
160 
161     generatedPath += "/" SETTINGS_GENERATED_DIRECTORY_NAME;
162 
163     // Ensure folder exists
164     if (!kodi::vfs::DirectoryExists(generatedPath))
165     {
166       dsyslog("Creating directory for settings and language files: %s", generatedPath.c_str());
167       kodi::vfs::CreateDirectory(generatedPath);
168     }
169 
170     bool bSuccess = false;
171 
172     CSettingsGenerator settingsGen(generatedPath);
173     if (!settingsGen.GenerateSettings(m_settings))
174       esyslog("Failed to generate %s", SETTINGS_GENERATED_SETTINGS_NAME);
175     else
176       bSuccess = true;
177 
178     generatedPath += "/" SETTINGS_GENERATED_LANGUAGE_SUBDIR;
179 
180     // Ensure language folder exists
181     if (!kodi::vfs::DirectoryExists(generatedPath))
182     {
183       dsyslog("Creating directory for settings and language files: %s", generatedPath.c_str());
184       kodi::vfs::CreateDirectory(generatedPath);
185     }
186 
187     generatedPath += "/" SETTINGS_GENERATED_LANGUAGE_ENGLISH_SUBDIR;
188 
189     // Ensure English folder exists
190     if (!kodi::vfs::DirectoryExists(generatedPath))
191     {
192       dsyslog("Creating directory for settings and language files: %s", generatedPath.c_str());
193       kodi::vfs::CreateDirectory(generatedPath);
194     }
195 
196     CLanguageGenerator languageGen(addonId, generatedPath);
197     if (!languageGen.GenerateLanguage(m_settings))
198       esyslog("Failed to generate %s", SETTINGS_GENERATED_LANGUAGE_NAME);
199     else
200       bSuccess = true;
201 
202     if (bSuccess)
203       isyslog("Settings and language files have been placed in %s", generatedPath.c_str());
204 
205     m_bGenerated = true;
206   }
207 }
208