1 /*
2  *  Copyright (C) 2014-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 "GameInfoLoader.h"
9 #include "log/Log.h"
10 
11 #include <kodi/Filesystem.h>
12 
13 #include <stdint.h>
14 
15 using namespace LIBRETRO;
16 
17 #define READ_SIZE      (100 * 1024)         // Read from VFS 100KB at a time (if file size is unknown)
18 #define MAX_READ_SIZE  (100 * 1024 * 1024)  // Read at most 100MB from VFS
19 
CGameInfoLoader(const std::string & path,bool bSupportsVFS)20 CGameInfoLoader::CGameInfoLoader(const std::string& path, bool bSupportsVFS)
21  : m_path(path),
22    m_bSupportsVfs(bSupportsVFS)
23 {
24 }
25 
Load(void)26 bool CGameInfoLoader::Load(void)
27 {
28   if (!m_bSupportsVfs)
29     return false;
30 
31   kodi::vfs::FileStatus statStruct;
32   bool bExists = kodi::vfs::StatFile(m_path, statStruct);
33 
34   // Not all VFS protocols necessarily support StatFile(), so also check if file exists
35   if (!bExists)
36   {
37     bExists = kodi::vfs::FileExists(m_path, true);
38     if (bExists)
39     {
40       dsyslog("Failed to stat (but file exists): %s", m_path.c_str());
41     }
42     else
43     {
44       esyslog("File doesn't exist: %s", m_path.c_str());
45       return false;
46     }
47   }
48 
49   kodi::vfs::CFile file;
50   if (!file.OpenFile(m_path))
51   {
52     esyslog("Failed to open file: %s", m_path.c_str());
53     return false;
54   }
55 
56   int64_t size = statStruct.GetSize();
57   if (size > 0)
58   {
59     // Size is known, read entire file at once (unless it is too big)
60     if (size <= MAX_READ_SIZE)
61     {
62       m_dataBuffer.resize((size_t)size);
63       file.Read(m_dataBuffer.data(), size);
64     }
65     else
66     {
67       dsyslog("File size (%d MB) is greater than memory limit (%d MB), loading by path",
68               size / (1024 * 1024), MAX_READ_SIZE / (1024 * 1024));
69       return false;
70     }
71   }
72   else
73   {
74     // Read file in chunks
75     unsigned int bytesRead;
76     uint8_t buffer[READ_SIZE];
77     while ((bytesRead = file.Read(buffer, sizeof(buffer))) > 0)
78     {
79       m_dataBuffer.insert(m_dataBuffer.end(), buffer, buffer + bytesRead);
80 
81       // If we read less than READ_SIZE, assume we hit the end of the file
82       if (bytesRead < READ_SIZE)
83         break;
84 
85       // If we have exceeded the VFS file size limit, don't try to load by
86       // VFS and fall back to loading by path
87       if (m_dataBuffer.size() > MAX_READ_SIZE)
88       {
89         dsyslog("File exceeds memory limit (%d MB), loading by path",
90                 MAX_READ_SIZE / (1024 * 1024));
91         return false;
92       }
93     }
94   }
95 
96   if (m_dataBuffer.empty())
97   {
98     dsyslog("Failed to read file (no data), loading by path");
99     return false;
100   }
101 
102   dsyslog("Loaded file into memory (%d bytes): %s", m_dataBuffer.size(), m_path.c_str());
103 
104   return true;
105 }
106 
GetMemoryStruct(retro_game_info & info) const107 bool CGameInfoLoader::GetMemoryStruct(retro_game_info& info) const
108 {
109   if (!m_dataBuffer.empty())
110   {
111     //! @todo path is null according to libretro API, but many cores expect
112     //        the frontend to set this. Do so to guard against
113     //        noncompliant cores.
114     info.path = m_path.c_str();
115 
116     info.data = m_dataBuffer.data();
117     info.size = m_dataBuffer.size();
118     info.meta = nullptr;
119     return true;
120   }
121   return false;
122 }
123 
GetPathStruct(retro_game_info & info) const124 bool CGameInfoLoader::GetPathStruct(retro_game_info& info) const
125 {
126   info.path = m_path.c_str();
127   info.data = nullptr;
128   info.size = 0;
129   info.meta = nullptr;
130   return true;
131 }
132