[guilib] fix labelcontrols with auto width always being marked as dirty if they speci...
[vuplus_xbmc] / xbmc / guilib / TextureBundleXPR.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "system.h"
22 #include "TextureBundleXPR.h"
23 #include "Texture.h"
24 #include "GraphicContext.h"
25 #include "DirectXGraphics.h"
26 #include "utils/log.h"
27 #ifndef TARGET_POSIX
28 #include <sys/stat.h>
29 #include "utils/CharsetConverter.h"
30 #endif
31 #include <lzo/lzo1x.h>
32 #include "addons/Skin.h"
33 #include "settings/Settings.h"
34 #include "filesystem/SpecialProtocol.h"
35 #include "utils/EndianSwap.h"
36 #include "utils/URIUtils.h"
37 #include "utils/StringUtils.h"
38
39 #ifdef TARGET_WINDOWS
40 #pragma comment(lib,"liblzo2.lib")
41 #endif
42
43 // alignment of file blocks - should be a multiple of the sector size of the disk and a power of 2
44 // HDD sector = 512 bytes, DVD/CD sector = 2048 bytes
45 #undef ALIGN
46 #define ALIGN (512)
47
48
49 enum XPR_FLAGS
50 {
51   XPRFLAG_PALETTE = 0x00000001,
52   XPRFLAG_ANIM = 0x00000002
53 };
54
55 class CAutoBuffer
56 {
57   BYTE* p;
58 public:
59   CAutoBuffer() { p = 0; }
60   explicit CAutoBuffer(size_t s) { p = (BYTE*)malloc(s); }
61   ~CAutoBuffer() { free(p); }
62 operator BYTE*() const { return p; }
63   void Set(BYTE* buf) { free(p); p = buf; }
64   bool Resize(size_t s);
65 void Release() { p = 0; }
66
67 private:
68   CAutoBuffer(const CAutoBuffer&);
69   CAutoBuffer& operator=(const CAutoBuffer&);
70 };
71
72 bool CAutoBuffer::Resize(size_t s)
73 {
74   if (s == 0)
75   {
76     if (!p)
77       return false;
78     free(p);
79     p = 0;
80     return true;
81   }
82   void* q = realloc(p, s);
83   if (q)
84   {
85     p = (BYTE*)q;
86     return true;
87   }
88   return false;
89 }
90
91 // as above but for texture allocation (do not change from XPhysicalAlloc!)
92 class CAutoTexBuffer
93 {
94   BYTE* p;
95 public:
96   CAutoTexBuffer() { p = 0; }
97   explicit CAutoTexBuffer(size_t s) { p = (BYTE*)XPhysicalAlloc(s, MAXULONG_PTR, 128, PAGE_READWRITE); }
98   ~CAutoTexBuffer() { if (p) XPhysicalFree(p); }
99 operator BYTE*() const { return p; }
100   BYTE* Set(BYTE* buf) { if (p) XPhysicalFree(p); return p = buf; }
101 void Release() { p = 0; }
102 };
103
104 CTextureBundleXPR::CTextureBundleXPR(void)
105 {
106   m_hFile = NULL;
107   m_themeBundle = false;
108   m_TimeStamp = 0;
109 }
110
111 CTextureBundleXPR::~CTextureBundleXPR(void)
112 {
113   if (m_hFile != NULL)
114     fclose(m_hFile);
115 }
116
117 bool CTextureBundleXPR::OpenBundle()
118 {
119   DWORD AlignedSize;
120   DWORD HeaderSize;
121   int Version;
122   XPR_HEADER* pXPRHeader;
123
124   if (m_hFile != NULL)
125     Cleanup();
126
127   CStdString strPath;
128
129   if (m_themeBundle)
130   {
131     // if we are the theme bundle, we only load if the user has chosen
132     // a valid theme (or the skin has a default one)
133     CStdString theme = CSettings::Get().GetString("lookandfeel.skintheme");
134     if (!theme.empty() && !StringUtils::EqualsNoCase(theme, "SKINDEFAULT"))
135     {
136       CStdString themeXPR(URIUtils::ReplaceExtension(theme, ".xpr"));
137       strPath = URIUtils::AddFileToFolder(g_graphicsContext.GetMediaDir(), "media");
138       strPath = URIUtils::AddFileToFolder(strPath, themeXPR);
139     }
140     else
141       return false;
142   }
143   else
144     strPath = URIUtils::AddFileToFolder(g_graphicsContext.GetMediaDir(), "media/Textures.xpr");
145
146   strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
147
148 #ifndef TARGET_POSIX
149   CStdStringW strPathW;
150   g_charsetConverter.utf8ToW(CSpecialProtocol::TranslatePath(strPath), strPathW, false);
151   m_hFile = _wfopen(strPathW.c_str(), L"rb");
152 #else
153   m_hFile = fopen(strPath.c_str(), "rb");
154 #endif
155   if (m_hFile == NULL)
156     return false;
157
158   struct stat fileStat;
159   if (fstat(fileno(m_hFile), &fileStat) == -1)
160     return false;
161   m_TimeStamp = fileStat.st_mtime;
162
163   CAutoBuffer HeaderBuf(ALIGN);
164   DWORD n;
165
166   n = fread(HeaderBuf, 1, ALIGN, m_hFile);
167   if (n < ALIGN)
168     goto LoadError;
169
170   pXPRHeader = (XPR_HEADER*)(BYTE*)HeaderBuf;
171   pXPRHeader->dwMagic = Endian_SwapLE32(pXPRHeader->dwMagic);
172   Version = (pXPRHeader->dwMagic >> 24) - '0';
173   pXPRHeader->dwMagic -= Version << 24;
174   Version &= 0x0f;
175
176   if (pXPRHeader->dwMagic != XPR_MAGIC_VALUE || Version < 2)
177     goto LoadError;
178
179   HeaderSize = Endian_SwapLE32(pXPRHeader->dwHeaderSize);
180   AlignedSize = (HeaderSize - 1) & ~(ALIGN - 1); // align to sector, but remove the first sector
181   HeaderBuf.Resize(AlignedSize + ALIGN);
182
183   if (fseek(m_hFile, ALIGN, SEEK_SET) == -1)
184     goto LoadError;
185   n = fread(HeaderBuf + ALIGN, 1, AlignedSize, m_hFile);
186   if (n < ALIGN)
187     goto LoadError;
188
189   struct DiskFileHeader_t
190   {
191     char Name[116];
192     DWORD Offset;
193     DWORD UnpackedSize;
194     DWORD PackedSize;
195   }
196   *FileHeader;
197   FileHeader = (DiskFileHeader_t*)(HeaderBuf + sizeof(XPR_HEADER));
198
199   n = (HeaderSize - sizeof(XPR_HEADER)) / sizeof(DiskFileHeader_t);
200   for (unsigned i = 0; i < n; ++i)
201   {
202     std::pair<CStdString, FileHeader_t> entry;
203     entry.first = Normalize(FileHeader[i].Name);
204     entry.second.Offset = Endian_SwapLE32(FileHeader[i].Offset);
205     entry.second.UnpackedSize = Endian_SwapLE32(FileHeader[i].UnpackedSize);
206     entry.second.PackedSize = Endian_SwapLE32(FileHeader[i].PackedSize);
207     m_FileHeaders.insert(entry);
208   }
209
210   if (lzo_init() != LZO_E_OK)
211     goto LoadError;
212
213   return true;
214
215 LoadError:
216   CLog::Log(LOGERROR, "Unable to load file: %s: %s", strPath.c_str(), strerror(errno));
217   fclose(m_hFile);
218   m_hFile = NULL;
219
220   return false;
221 }
222
223 void CTextureBundleXPR::Cleanup()
224 {
225   if (m_hFile != NULL)
226     fclose(m_hFile);
227   m_hFile = NULL;
228
229   m_FileHeaders.clear();
230 }
231
232 bool CTextureBundleXPR::HasFile(const CStdString& Filename)
233 {
234   if (m_hFile == NULL && !OpenBundle())
235     return false;
236
237   struct stat fileStat;
238   if (fstat(fileno(m_hFile), &fileStat) == -1)
239     return false;
240   if (fileStat.st_mtime > m_TimeStamp)
241   {
242     CLog::Log(LOGINFO, "Texture bundle has changed, reloading");
243     Cleanup();
244     if (!OpenBundle())
245       return false;
246   }
247
248   CStdString name = Normalize(Filename);
249   return m_FileHeaders.find(name) != m_FileHeaders.end();
250 }
251
252 void CTextureBundleXPR::GetTexturesFromPath(const CStdString &path, std::vector<CStdString> &textures)
253 {
254   if (path.size() > 1 && path[1] == ':')
255     return;
256
257   if (m_hFile == NULL && !OpenBundle())
258     return;
259
260   CStdString testPath = Normalize(path);
261   if (!URIUtils::HasSlashAtEnd(testPath))
262     testPath += "\\";
263   std::map<CStdString, FileHeader_t>::iterator it;
264   for (it = m_FileHeaders.begin(); it != m_FileHeaders.end(); ++it)
265   {
266     if (StringUtils::StartsWithNoCase(it->first, testPath))
267       textures.push_back(it->first);
268   }
269 }
270
271 bool CTextureBundleXPR::LoadFile(const CStdString& Filename, CAutoTexBuffer& UnpackedBuf)
272 {
273   CStdString name = Normalize(Filename);
274
275   std::map<CStdString, FileHeader_t>::iterator file = m_FileHeaders.find(name);
276   if (file == m_FileHeaders.end())
277     return false;
278
279   // found texture - allocate the necessary buffers
280   DWORD ReadSize = (file->second.PackedSize + (ALIGN - 1)) & ~(ALIGN - 1);
281   BYTE *buffer = (BYTE*)malloc(ReadSize);
282
283   if (!buffer || !UnpackedBuf.Set((BYTE*)XPhysicalAlloc(file->second.UnpackedSize, MAXULONG_PTR, 128, PAGE_READWRITE)))
284   { // failed due to lack of memory
285 #ifndef TARGET_POSIX
286     MEMORYSTATUSEX stat;
287     stat.dwLength = sizeof(MEMORYSTATUSEX);
288     GlobalMemoryStatusEx(&stat);
289     CLog::Log(LOGERROR, "Out of memory loading texture: %s (need %lu bytes, have %"PRIu64" bytes)", name.c_str(),
290               file->second.UnpackedSize + file->second.PackedSize, stat.ullAvailPhys);
291 #elif defined(TARGET_DARWIN) || defined(TARGET_FREEBSD)
292     CLog::Log(LOGERROR, "Out of memory loading texture: %s (need %d bytes)", name.c_str(),
293               file->second.UnpackedSize + file->second.PackedSize);
294 #else
295     struct sysinfo info;
296     sysinfo(&info);
297     CLog::Log(LOGERROR, "Out of memory loading texture: %s "
298                         "(need %u bytes, have %lu bytes)",
299               name.c_str(), file->second.UnpackedSize + file->second.PackedSize,
300               info.totalram);
301 #endif
302     free(buffer);
303     return false;
304   }
305
306   // read the file into our buffer
307   if (fseek(m_hFile, file->second.Offset, SEEK_SET) != 0)
308   {
309     CLog::Log(LOGERROR, "Error loading texture: %s: %s: Seek error", Filename.c_str(), strerror(ferror(m_hFile)));
310     free(buffer);
311     return false;            
312   }
313   
314   DWORD n = fread(buffer, 1, ReadSize, m_hFile);
315   if (n < ReadSize && !feof(m_hFile))
316   {
317     CLog::Log(LOGERROR, "Error loading texture: %s: %s: Read error", Filename.c_str(), strerror(ferror(m_hFile)));
318     free(buffer);
319     return false;
320   }
321
322   // allocate a buffer for our unpacked texture
323   lzo_uint s = file->second.UnpackedSize;
324   bool success = true;
325   if (lzo1x_decompress(buffer, file->second.PackedSize, UnpackedBuf, &s, NULL) != LZO_E_OK ||
326       s != file->second.UnpackedSize)
327   {
328     CLog::Log(LOGERROR, "Error loading texture: %s: Decompression error", Filename.c_str());
329     success = false;
330   }
331
332   try
333   {
334     free(buffer);
335   }
336   catch (...)
337   {
338     CLog::Log(LOGERROR, "Error freeing preload buffer.");
339   }
340
341   return success;
342 }
343
344 bool CTextureBundleXPR::LoadTexture(const CStdString& Filename, CBaseTexture** ppTexture,
345                                      int &width, int &height)
346 {
347   DWORD ResDataOffset;
348   *ppTexture = NULL;
349
350   CAutoTexBuffer UnpackedBuf;
351   if (!LoadFile(Filename, UnpackedBuf))
352     return false;
353
354   D3DTexture *pTex = (D3DTexture *)(new char[sizeof (D3DTexture)]);
355   D3DPalette* pPal = 0;
356   void* ResData = 0;
357
358   WORD RealSize[2];
359
360   enum XPR_FLAGS
361   {
362     XPRFLAG_PALETTE = 0x00000001,
363     XPRFLAG_ANIM = 0x00000002
364   };
365
366   BYTE* Next = UnpackedBuf;
367
368   DWORD flags = Endian_SwapLE32(*(DWORD*)Next);
369   Next += sizeof(DWORD);
370   if ((flags & XPRFLAG_ANIM) || (flags >> 16) > 1)
371     goto PackedLoadError;
372
373   if (flags & XPRFLAG_PALETTE)
374     Next += sizeof(D3DPalette);
375
376   memcpy(pTex, Next, sizeof(D3DTexture));
377   pTex->Common = Endian_SwapLE32(pTex->Common);
378   pTex->Data = Endian_SwapLE32(pTex->Data);
379   pTex->Lock = Endian_SwapLE32(pTex->Lock);
380   pTex->Format = Endian_SwapLE32(pTex->Format);
381   pTex->Size = Endian_SwapLE32(pTex->Size);
382   Next += sizeof(D3DTexture);
383
384   memcpy(RealSize, Next, 4);
385   Next += 4;
386
387   ResDataOffset = ((Next - UnpackedBuf) + 127) & ~127;
388   ResData = UnpackedBuf + ResDataOffset;
389
390   if ((pTex->Common & D3DCOMMON_TYPE_MASK) != D3DCOMMON_TYPE_TEXTURE)
391     goto PackedLoadError;
392
393   GetTextureFromData(pTex, ResData, ppTexture);
394   delete[] pTex;
395
396   width = Endian_SwapLE16(RealSize[0]);
397   height = Endian_SwapLE16(RealSize[1]);
398 /* DXMERGE - this was previously used to specify the format of the image - probably only affects directx?
399 #ifndef HAS_SDL
400   D3DSURFACE_DESC desc;
401   (*ppTexture)->GetLevelDesc(0, &desc);
402   pInfo->Format = desc.Format;
403 #endif
404 */
405   return true;
406
407 PackedLoadError:
408   CLog::Log(LOGERROR, "Error loading texture: %s: Invalid data", Filename.c_str());
409   delete[] pTex;
410   delete pPal;
411   return false;
412 }
413
414 int CTextureBundleXPR::LoadAnim(const CStdString& Filename, CBaseTexture*** ppTextures,
415                               int &width, int &height, int& nLoops, int** ppDelays)
416 {
417   DWORD ResDataOffset;
418   int nTextures = 0;
419
420   *ppTextures = NULL; *ppDelays = NULL;
421
422   CAutoTexBuffer UnpackedBuf;
423   if (!LoadFile(Filename, UnpackedBuf))
424     return 0;
425
426   struct AnimInfo_t
427   {
428     DWORD nLoops;
429     WORD RealSize[2];
430   }
431   *pAnimInfo;
432
433   D3DTexture** ppTex = 0;
434   void* ResData = 0;
435
436   BYTE* Next = UnpackedBuf;
437
438   DWORD flags = Endian_SwapLE32(*(DWORD*)Next);
439   Next += sizeof(DWORD);
440   if (!(flags & XPRFLAG_ANIM))
441     goto PackedAnimError;
442
443   pAnimInfo = (AnimInfo_t*)Next;
444   Next += sizeof(AnimInfo_t);
445   nLoops = Endian_SwapLE32(pAnimInfo->nLoops);
446
447   if (flags & XPRFLAG_PALETTE)
448     Next += sizeof(D3DPalette);
449
450   nTextures = flags >> 16;
451   ppTex = new D3DTexture * [nTextures];
452   *ppDelays = new int[nTextures];
453   for (int i = 0; i < nTextures; ++i)
454   {
455     ppTex[i] = (D3DTexture *)(new char[sizeof (D3DTexture)+ sizeof (DWORD)]);
456
457     memcpy(ppTex[i], Next, sizeof(D3DTexture));
458     ppTex[i]->Common = Endian_SwapLE32(ppTex[i]->Common);
459     ppTex[i]->Data = Endian_SwapLE32(ppTex[i]->Data);
460     ppTex[i]->Lock = Endian_SwapLE32(ppTex[i]->Lock);
461     ppTex[i]->Format = Endian_SwapLE32(ppTex[i]->Format);
462     ppTex[i]->Size = Endian_SwapLE32(ppTex[i]->Size);
463     Next += sizeof(D3DTexture);
464
465     (*ppDelays)[i] = Endian_SwapLE32(*(int*)Next);
466     Next += sizeof(int);
467   }
468
469   ResDataOffset = ((DWORD)(Next - UnpackedBuf) + 127) & ~127;
470   ResData = UnpackedBuf + ResDataOffset;
471
472   *ppTextures = new CBaseTexture*[nTextures];
473   for (int i = 0; i < nTextures; ++i)
474   {
475     if ((ppTex[i]->Common & D3DCOMMON_TYPE_MASK) != D3DCOMMON_TYPE_TEXTURE)
476       goto PackedAnimError;
477
478     GetTextureFromData(ppTex[i], ResData, &(*ppTextures)[i]);
479     delete[] ppTex[i];
480   }
481
482   delete[] ppTex;
483   ppTex = 0;
484
485   width = Endian_SwapLE16(pAnimInfo->RealSize[0]);
486   height = Endian_SwapLE16(pAnimInfo->RealSize[1]);
487
488   return nTextures;
489
490 PackedAnimError:
491   CLog::Log(LOGERROR, "Error loading texture: %s: Invalid data", Filename.c_str());
492   if (ppTex)
493   {
494     for (int i = 0; i < nTextures; ++i)
495       delete [] ppTex[i];
496     delete [] ppTex;
497   }
498   delete [] *ppDelays;
499   return 0;
500 }
501
502 void CTextureBundleXPR::SetThemeBundle(bool themeBundle)
503 {
504   m_themeBundle = themeBundle;
505 }
506
507 // normalize to how it's stored within the bundle
508 // lower case + using \\ rather than /
509 CStdString CTextureBundleXPR::Normalize(const CStdString &name)
510 {
511   CStdString newName(name);
512   StringUtils::Trim(newName);
513   StringUtils::ToLower(newName);
514   StringUtils::Replace(newName, '/','\\');
515   return newName;
516 }