2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
22 #include "TextureBundleXPR.h"
24 #include "GraphicContext.h"
25 #include "DirectXGraphics.h"
26 #include "utils/log.h"
29 #include "utils/CharsetConverter.h"
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"
40 #pragma comment(lib,"liblzo2.lib")
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
51 XPRFLAG_PALETTE = 0x00000001,
52 XPRFLAG_ANIM = 0x00000002
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; }
68 CAutoBuffer(const CAutoBuffer&);
69 CAutoBuffer& operator=(const CAutoBuffer&);
72 bool CAutoBuffer::Resize(size_t s)
82 void* q = realloc(p, s);
91 // as above but for texture allocation (do not change from XPhysicalAlloc!)
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; }
104 CTextureBundleXPR::CTextureBundleXPR(void)
107 m_themeBundle = false;
111 CTextureBundleXPR::~CTextureBundleXPR(void)
117 bool CTextureBundleXPR::OpenBundle()
122 XPR_HEADER* pXPRHeader;
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"))
136 CStdString themeXPR(URIUtils::ReplaceExtension(theme, ".xpr"));
137 strPath = URIUtils::AddFileToFolder(g_graphicsContext.GetMediaDir(), "media");
138 strPath = URIUtils::AddFileToFolder(strPath, themeXPR);
144 strPath = URIUtils::AddFileToFolder(g_graphicsContext.GetMediaDir(), "media/Textures.xpr");
146 strPath = CSpecialProtocol::TranslatePathConvertCase(strPath);
149 CStdStringW strPathW;
150 g_charsetConverter.utf8ToW(CSpecialProtocol::TranslatePath(strPath), strPathW, false);
151 m_hFile = _wfopen(strPathW.c_str(), L"rb");
153 m_hFile = fopen(strPath.c_str(), "rb");
158 struct stat fileStat;
159 if (fstat(fileno(m_hFile), &fileStat) == -1)
161 m_TimeStamp = fileStat.st_mtime;
163 CAutoBuffer HeaderBuf(ALIGN);
166 n = fread(HeaderBuf, 1, ALIGN, m_hFile);
170 pXPRHeader = (XPR_HEADER*)(BYTE*)HeaderBuf;
171 pXPRHeader->dwMagic = Endian_SwapLE32(pXPRHeader->dwMagic);
172 Version = (pXPRHeader->dwMagic >> 24) - '0';
173 pXPRHeader->dwMagic -= Version << 24;
176 if (pXPRHeader->dwMagic != XPR_MAGIC_VALUE || Version < 2)
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);
183 if (fseek(m_hFile, ALIGN, SEEK_SET) == -1)
185 n = fread(HeaderBuf + ALIGN, 1, AlignedSize, m_hFile);
189 struct DiskFileHeader_t
197 FileHeader = (DiskFileHeader_t*)(HeaderBuf + sizeof(XPR_HEADER));
199 n = (HeaderSize - sizeof(XPR_HEADER)) / sizeof(DiskFileHeader_t);
200 for (unsigned i = 0; i < n; ++i)
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);
210 if (lzo_init() != LZO_E_OK)
216 CLog::Log(LOGERROR, "Unable to load file: %s: %s", strPath.c_str(), strerror(errno));
223 void CTextureBundleXPR::Cleanup()
229 m_FileHeaders.clear();
232 bool CTextureBundleXPR::HasFile(const CStdString& Filename)
234 if (m_hFile == NULL && !OpenBundle())
237 struct stat fileStat;
238 if (fstat(fileno(m_hFile), &fileStat) == -1)
240 if (fileStat.st_mtime > m_TimeStamp)
242 CLog::Log(LOGINFO, "Texture bundle has changed, reloading");
248 CStdString name = Normalize(Filename);
249 return m_FileHeaders.find(name) != m_FileHeaders.end();
252 void CTextureBundleXPR::GetTexturesFromPath(const CStdString &path, std::vector<CStdString> &textures)
254 if (path.size() > 1 && path[1] == ':')
257 if (m_hFile == NULL && !OpenBundle())
260 CStdString testPath = Normalize(path);
261 if (!URIUtils::HasSlashAtEnd(testPath))
263 std::map<CStdString, FileHeader_t>::iterator it;
264 for (it = m_FileHeaders.begin(); it != m_FileHeaders.end(); ++it)
266 if (StringUtils::StartsWithNoCase(it->first, testPath))
267 textures.push_back(it->first);
271 bool CTextureBundleXPR::LoadFile(const CStdString& Filename, CAutoTexBuffer& UnpackedBuf)
273 CStdString name = Normalize(Filename);
275 std::map<CStdString, FileHeader_t>::iterator file = m_FileHeaders.find(name);
276 if (file == m_FileHeaders.end())
279 // found texture - allocate the necessary buffers
280 DWORD ReadSize = (file->second.PackedSize + (ALIGN - 1)) & ~(ALIGN - 1);
281 BYTE *buffer = (BYTE*)malloc(ReadSize);
283 if (!buffer || !UnpackedBuf.Set((BYTE*)XPhysicalAlloc(file->second.UnpackedSize, MAXULONG_PTR, 128, PAGE_READWRITE)))
284 { // failed due to lack of memory
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);
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,
306 // read the file into our buffer
307 if (fseek(m_hFile, file->second.Offset, SEEK_SET) != 0)
309 CLog::Log(LOGERROR, "Error loading texture: %s: %s: Seek error", Filename.c_str(), strerror(ferror(m_hFile)));
314 DWORD n = fread(buffer, 1, ReadSize, m_hFile);
315 if (n < ReadSize && !feof(m_hFile))
317 CLog::Log(LOGERROR, "Error loading texture: %s: %s: Read error", Filename.c_str(), strerror(ferror(m_hFile)));
322 // allocate a buffer for our unpacked texture
323 lzo_uint s = file->second.UnpackedSize;
325 if (lzo1x_decompress(buffer, file->second.PackedSize, UnpackedBuf, &s, NULL) != LZO_E_OK ||
326 s != file->second.UnpackedSize)
328 CLog::Log(LOGERROR, "Error loading texture: %s: Decompression error", Filename.c_str());
338 CLog::Log(LOGERROR, "Error freeing preload buffer.");
344 bool CTextureBundleXPR::LoadTexture(const CStdString& Filename, CBaseTexture** ppTexture,
345 int &width, int &height)
350 CAutoTexBuffer UnpackedBuf;
351 if (!LoadFile(Filename, UnpackedBuf))
354 D3DTexture *pTex = (D3DTexture *)(new char[sizeof (D3DTexture)]);
355 D3DPalette* pPal = 0;
362 XPRFLAG_PALETTE = 0x00000001,
363 XPRFLAG_ANIM = 0x00000002
366 BYTE* Next = UnpackedBuf;
368 DWORD flags = Endian_SwapLE32(*(DWORD*)Next);
369 Next += sizeof(DWORD);
370 if ((flags & XPRFLAG_ANIM) || (flags >> 16) > 1)
371 goto PackedLoadError;
373 if (flags & XPRFLAG_PALETTE)
374 Next += sizeof(D3DPalette);
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);
384 memcpy(RealSize, Next, 4);
387 ResDataOffset = ((Next - UnpackedBuf) + 127) & ~127;
388 ResData = UnpackedBuf + ResDataOffset;
390 if ((pTex->Common & D3DCOMMON_TYPE_MASK) != D3DCOMMON_TYPE_TEXTURE)
391 goto PackedLoadError;
393 GetTextureFromData(pTex, ResData, ppTexture);
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?
400 D3DSURFACE_DESC desc;
401 (*ppTexture)->GetLevelDesc(0, &desc);
402 pInfo->Format = desc.Format;
408 CLog::Log(LOGERROR, "Error loading texture: %s: Invalid data", Filename.c_str());
414 int CTextureBundleXPR::LoadAnim(const CStdString& Filename, CBaseTexture*** ppTextures,
415 int &width, int &height, int& nLoops, int** ppDelays)
420 *ppTextures = NULL; *ppDelays = NULL;
422 CAutoTexBuffer UnpackedBuf;
423 if (!LoadFile(Filename, UnpackedBuf))
433 D3DTexture** ppTex = 0;
436 BYTE* Next = UnpackedBuf;
438 DWORD flags = Endian_SwapLE32(*(DWORD*)Next);
439 Next += sizeof(DWORD);
440 if (!(flags & XPRFLAG_ANIM))
441 goto PackedAnimError;
443 pAnimInfo = (AnimInfo_t*)Next;
444 Next += sizeof(AnimInfo_t);
445 nLoops = Endian_SwapLE32(pAnimInfo->nLoops);
447 if (flags & XPRFLAG_PALETTE)
448 Next += sizeof(D3DPalette);
450 nTextures = flags >> 16;
451 ppTex = new D3DTexture * [nTextures];
452 *ppDelays = new int[nTextures];
453 for (int i = 0; i < nTextures; ++i)
455 ppTex[i] = (D3DTexture *)(new char[sizeof (D3DTexture)+ sizeof (DWORD)]);
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);
465 (*ppDelays)[i] = Endian_SwapLE32(*(int*)Next);
469 ResDataOffset = ((DWORD)(Next - UnpackedBuf) + 127) & ~127;
470 ResData = UnpackedBuf + ResDataOffset;
472 *ppTextures = new CBaseTexture*[nTextures];
473 for (int i = 0; i < nTextures; ++i)
475 if ((ppTex[i]->Common & D3DCOMMON_TYPE_MASK) != D3DCOMMON_TYPE_TEXTURE)
476 goto PackedAnimError;
478 GetTextureFromData(ppTex[i], ResData, &(*ppTextures)[i]);
485 width = Endian_SwapLE16(pAnimInfo->RealSize[0]);
486 height = Endian_SwapLE16(pAnimInfo->RealSize[1]);
491 CLog::Log(LOGERROR, "Error loading texture: %s: Invalid data", Filename.c_str());
494 for (int i = 0; i < nTextures; ++i)
502 void CTextureBundleXPR::SetThemeBundle(bool themeBundle)
504 m_themeBundle = themeBundle;
507 // normalize to how it's stored within the bundle
508 // lower case + using \\ rather than /
509 CStdString CTextureBundleXPR::Normalize(const CStdString &name)
511 CStdString newName(name);
512 StringUtils::Trim(newName);
513 StringUtils::ToLower(newName);
514 StringUtils::Replace(newName, '/','\\');