1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "TextureBundleXBT.h"
10
11 #include "ServiceBroker.h"
12 #include "Texture.h"
13 #include "XBTF.h"
14 #include "XBTFReader.h"
15 #include "filesystem/SpecialProtocol.h"
16 #include "filesystem/XbtManager.h"
17 #include "settings/Settings.h"
18 #include "settings/SettingsComponent.h"
19 #include "utils/StringUtils.h"
20 #include "utils/URIUtils.h"
21 #include "utils/log.h"
22 #include "windowing/GraphicContext.h"
23
24 #include <inttypes.h>
25
26 #include <lzo/lzo1x.h>
27
28 #ifdef TARGET_WINDOWS_DESKTOP
29 #ifdef NDEBUG
30 #pragma comment(lib,"lzo2.lib")
31 #else
32 #pragma comment(lib, "lzo2d.lib")
33 #endif
34 #endif
35
CTextureBundleXBT()36 CTextureBundleXBT::CTextureBundleXBT()
37 : m_TimeStamp{0}
38 , m_themeBundle{false}
39 {
40 }
41
CTextureBundleXBT(bool themeBundle)42 CTextureBundleXBT::CTextureBundleXBT(bool themeBundle)
43 : m_TimeStamp{0}
44 , m_themeBundle{themeBundle}
45 {
46 }
47
~CTextureBundleXBT(void)48 CTextureBundleXBT::~CTextureBundleXBT(void)
49 {
50 CloseBundle();
51 }
52
CloseBundle()53 void CTextureBundleXBT::CloseBundle()
54 {
55 if (m_XBTFReader != nullptr && m_XBTFReader->IsOpen())
56 {
57 XFILE::CXbtManager::GetInstance().Release(CURL(m_path));
58 CLog::Log(LOGDEBUG, "%s - Closed %sbundle", __FUNCTION__, m_themeBundle ? "theme " : "");
59 }
60 }
61
OpenBundle()62 bool CTextureBundleXBT::OpenBundle()
63 {
64 // Find the correct texture file (skin or theme)
65
66 auto mediaDir = CServiceBroker::GetWinSystem()->GetGfxContext().GetMediaDir();
67 if (mediaDir.empty())
68 {
69 mediaDir = CSpecialProtocol::TranslatePath(
70 URIUtils::AddFileToFolder("special://home/addons",
71 CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN)));
72 }
73
74 if (m_themeBundle)
75 {
76 // if we are the theme bundle, we only load if the user has chosen
77 // a valid theme (or the skin has a default one)
78 std::string theme = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKINTHEME);
79 if (!theme.empty() && !StringUtils::EqualsNoCase(theme, "SKINDEFAULT"))
80 {
81 std::string themeXBT(URIUtils::ReplaceExtension(theme, ".xbt"));
82 m_path = URIUtils::AddFileToFolder(CServiceBroker::GetWinSystem()->GetGfxContext().GetMediaDir(), "media", themeXBT);
83 }
84 else
85 {
86 return false;
87 }
88 }
89 else
90 {
91 m_path = URIUtils::AddFileToFolder(CServiceBroker::GetWinSystem()->GetGfxContext().GetMediaDir(), "media", "Textures.xbt");
92 }
93
94 m_path = CSpecialProtocol::TranslatePathConvertCase(m_path);
95
96 // Load the texture file
97 if (!XFILE::CXbtManager::GetInstance().GetReader(CURL(m_path), m_XBTFReader))
98 {
99 return false;
100 }
101
102 CLog::Log(LOGDEBUG, "%s - Opened bundle %s", __FUNCTION__, m_path.c_str());
103
104 m_TimeStamp = m_XBTFReader->GetLastModificationTimestamp();
105
106 if (lzo_init() != LZO_E_OK)
107 {
108 return false;
109 }
110
111 return true;
112 }
113
HasFile(const std::string & Filename)114 bool CTextureBundleXBT::HasFile(const std::string& Filename)
115 {
116 if ((m_XBTFReader == nullptr || !m_XBTFReader->IsOpen()) && !OpenBundle())
117 return false;
118
119 if (m_XBTFReader->GetLastModificationTimestamp() > m_TimeStamp)
120 {
121 CLog::Log(LOGINFO, "Texture bundle has changed, reloading");
122 if (!OpenBundle())
123 return false;
124 }
125
126 std::string name = Normalize(Filename);
127 return m_XBTFReader->Exists(name);
128 }
129
GetTexturesFromPath(const std::string & path,std::vector<std::string> & textures)130 void CTextureBundleXBT::GetTexturesFromPath(const std::string &path, std::vector<std::string> &textures)
131 {
132 if (path.size() > 1 && path[1] == ':')
133 return;
134
135 if ((m_XBTFReader == nullptr || !m_XBTFReader->IsOpen()) && !OpenBundle())
136 return;
137
138 std::string testPath = Normalize(path);
139 URIUtils::AddSlashAtEnd(testPath);
140
141 std::vector<CXBTFFile> files = m_XBTFReader->GetFiles();
142 for (size_t i = 0; i < files.size(); i++)
143 {
144 std::string path = files[i].GetPath();
145 if (StringUtils::StartsWithNoCase(path, testPath))
146 textures.push_back(path);
147 }
148 }
149
LoadTexture(const std::string & Filename,CTexture ** ppTexture,int & width,int & height)150 bool CTextureBundleXBT::LoadTexture(const std::string& Filename,
151 CTexture** ppTexture,
152 int& width,
153 int& height)
154 {
155 std::string name = Normalize(Filename);
156
157 CXBTFFile file;
158 if (!m_XBTFReader->Get(name, file))
159 return false;
160
161 if (file.GetFrames().empty())
162 return false;
163
164 CXBTFFrame& frame = file.GetFrames().at(0);
165 if (!ConvertFrameToTexture(Filename, frame, ppTexture))
166 {
167 return false;
168 }
169
170 width = frame.GetWidth();
171 height = frame.GetHeight();
172
173 return true;
174 }
175
LoadAnim(const std::string & Filename,CTexture *** ppTextures,int & width,int & height,int & nLoops,int ** ppDelays)176 int CTextureBundleXBT::LoadAnim(const std::string& Filename,
177 CTexture*** ppTextures,
178 int& width,
179 int& height,
180 int& nLoops,
181 int** ppDelays)
182 {
183 std::string name = Normalize(Filename);
184
185 CXBTFFile file;
186 if (!m_XBTFReader->Get(name, file))
187 return false;
188
189 if (file.GetFrames().empty())
190 return false;
191
192 size_t nTextures = file.GetFrames().size();
193 *ppTextures = new CTexture*[nTextures];
194 *ppDelays = new int[nTextures];
195
196 for (size_t i = 0; i < nTextures; i++)
197 {
198 CXBTFFrame& frame = file.GetFrames().at(i);
199
200 if (!ConvertFrameToTexture(Filename, frame, &((*ppTextures)[i])))
201 {
202 return false;
203 }
204
205 (*ppDelays)[i] = frame.GetDuration();
206 }
207
208 width = file.GetFrames().at(0).GetWidth();
209 height = file.GetFrames().at(0).GetHeight();
210 nLoops = file.GetLoop();
211
212 return nTextures;
213 }
214
ConvertFrameToTexture(const std::string & name,CXBTFFrame & frame,CTexture ** ppTexture)215 bool CTextureBundleXBT::ConvertFrameToTexture(const std::string& name,
216 CXBTFFrame& frame,
217 CTexture** ppTexture)
218 {
219 // found texture - allocate the necessary buffers
220 unsigned char *buffer = new unsigned char [(size_t)frame.GetPackedSize()];
221 if (buffer == NULL)
222 {
223 CLog::Log(LOGERROR, "Out of memory loading texture: %s (need %" PRIu64" bytes)", name.c_str(), frame.GetPackedSize());
224 return false;
225 }
226
227 // load the compressed texture
228 if (!m_XBTFReader->Load(frame, buffer))
229 {
230 CLog::Log(LOGERROR, "Error loading texture: %s", name.c_str());
231 delete[] buffer;
232 return false;
233 }
234
235 // check if it's packed with lzo
236 if (frame.IsPacked())
237 { // unpack
238 unsigned char *unpacked = new unsigned char[(size_t)frame.GetUnpackedSize()];
239 if (unpacked == NULL)
240 {
241 CLog::Log(LOGERROR, "Out of memory unpacking texture: %s (need %" PRIu64" bytes)", name.c_str(), frame.GetUnpackedSize());
242 delete[] buffer;
243 return false;
244 }
245 lzo_uint s = (lzo_uint)frame.GetUnpackedSize();
246 if (lzo1x_decompress_safe(buffer, (lzo_uint)frame.GetPackedSize(), unpacked, &s, NULL) != LZO_E_OK ||
247 s != frame.GetUnpackedSize())
248 {
249 CLog::Log(LOGERROR, "Error loading texture: %s: Decompression error", name.c_str());
250 delete[] buffer;
251 delete[] unpacked;
252 return false;
253 }
254 delete[] buffer;
255 buffer = unpacked;
256 }
257
258 // create an xbmc texture
259 *ppTexture = CTexture::CreateTexture();
260 (*ppTexture)->LoadFromMemory(frame.GetWidth(), frame.GetHeight(), 0, frame.GetFormat(), frame.HasAlpha(), buffer);
261
262 delete[] buffer;
263
264 return true;
265 }
266
SetThemeBundle(bool themeBundle)267 void CTextureBundleXBT::SetThemeBundle(bool themeBundle)
268 {
269 m_themeBundle = themeBundle;
270 }
271
272 // normalize to how it's stored within the bundle
273 // lower case + using forward slash rather than back slash
Normalize(const std::string & name)274 std::string CTextureBundleXBT::Normalize(const std::string &name)
275 {
276 std::string newName(name);
277
278 StringUtils::Trim(newName);
279 StringUtils::ToLower(newName);
280 StringUtils::Replace(newName, '\\','/');
281
282 return newName;
283 }
284
UnpackFrame(const CXBTFReader & reader,const CXBTFFrame & frame)285 uint8_t* CTextureBundleXBT::UnpackFrame(const CXBTFReader& reader, const CXBTFFrame& frame)
286 {
287 uint8_t* packedBuffer = new uint8_t[static_cast<size_t>(frame.GetPackedSize())];
288 if (packedBuffer == nullptr)
289 {
290 CLog::Log(LOGERROR, "CTextureBundleXBT: out of memory loading frame with %" PRIu64" packed bytes", frame.GetPackedSize());
291 return nullptr;
292 }
293
294 // load the compressed texture
295 if (!reader.Load(frame, packedBuffer))
296 {
297 CLog::Log(LOGERROR, "CTextureBundleXBT: error loading frame");
298 delete[] packedBuffer;
299 return nullptr;
300 }
301
302 // if the frame isn't packed there's nothing else to be done
303 if (!frame.IsPacked())
304 return packedBuffer;
305
306 uint8_t* unpackedBuffer = new uint8_t[static_cast<size_t>(frame.GetUnpackedSize())];
307 if (unpackedBuffer == nullptr)
308 {
309 CLog::Log(LOGERROR, "CTextureBundleXBT: out of memory loading frame with %" PRIu64" unpacked bytes", frame.GetPackedSize());
310 delete[] packedBuffer;
311 return nullptr;
312 }
313
314 // make sure lzo is initialized
315 if (lzo_init() != LZO_E_OK)
316 {
317 CLog::Log(LOGERROR, "CTextureBundleXBT: failed to initialize lzo");
318 delete[] packedBuffer;
319 delete[] unpackedBuffer;
320 return nullptr;
321 }
322
323 lzo_uint size = static_cast<lzo_uint>(frame.GetUnpackedSize());
324 if (lzo1x_decompress_safe(packedBuffer, static_cast<lzo_uint>(frame.GetPackedSize()), unpackedBuffer, &size, nullptr) != LZO_E_OK || size != frame.GetUnpackedSize())
325 {
326 CLog::Log(LOGERROR, "CTextureBundleXBT: failed to decompress frame with %" PRIu64" unpacked bytes to %" PRIu64" bytes", frame.GetPackedSize(), frame.GetUnpackedSize());
327 delete[] packedBuffer;
328 delete[] unpackedBuffer;
329 return nullptr;
330 }
331
332 delete[] packedBuffer;
333
334 return unpackedBuffer;
335 }
336