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