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