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