1 /*
2  *  Copyright (C) 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 "SavestateFlatBuffer.h"
10 
11 #include "savestate_generated.h"
12 #include "utils/log.h"
13 
14 using namespace KODI;
15 using namespace RETRO;
16 
17 namespace
18 {
19 const uint8_t SCHEMA_VERSION = 1;
20 
21 /*!
22  * \brief The initial size of the FlatBuffer's memory buffer
23  *
24  * 1024 is the default size in the FlatBuffers header. We might as well use
25  * this until our size requirements are more known.
26  */
27 const size_t INITIAL_FLATBUFFER_SIZE = 1024;
28 
29 /*!
30  * \brief Translate the save type (RetroPlayer to FlatBuffers)
31  */
TranslateType(SAVE_TYPE type)32 SaveType TranslateType(SAVE_TYPE type)
33 {
34   switch (type)
35   {
36     case SAVE_TYPE::AUTO:
37       return SaveType_Auto;
38     case SAVE_TYPE::MANUAL:
39       return SaveType_Manual;
40     default:
41       break;
42   }
43 
44   return SaveType_Unknown;
45 }
46 
47 /*!
48  * \brief Translate the save type (FlatBuffers to RetroPlayer)
49  */
TranslateType(SaveType type)50 SAVE_TYPE TranslateType(SaveType type)
51 {
52   switch (type)
53   {
54     case SaveType_Auto:
55       return SAVE_TYPE::AUTO;
56     case SaveType_Manual:
57       return SAVE_TYPE::MANUAL;
58     default:
59       break;
60   }
61 
62   return SAVE_TYPE::UNKNOWN;
63 }
64 } // namespace
65 
CSavestateFlatBuffer()66 CSavestateFlatBuffer::CSavestateFlatBuffer()
67 {
68   Reset();
69 }
70 
71 CSavestateFlatBuffer::~CSavestateFlatBuffer() = default;
72 
Reset()73 void CSavestateFlatBuffer::Reset()
74 {
75   m_builder.reset(new flatbuffers::FlatBufferBuilder(INITIAL_FLATBUFFER_SIZE));
76   m_data.clear();
77   m_savestate = nullptr;
78 }
79 
Serialize(const uint8_t * & data,size_t & size) const80 bool CSavestateFlatBuffer::Serialize(const uint8_t*& data, size_t& size) const
81 {
82   // Check if savestate was deserialized from vector or built with FlatBuffers
83   if (!m_data.empty())
84   {
85     data = m_data.data();
86     size = m_data.size();
87   }
88   else
89   {
90     data = m_builder->GetBufferPointer();
91     size = m_builder->GetSize();
92   }
93 
94   return true;
95 }
96 
Type() const97 SAVE_TYPE CSavestateFlatBuffer::Type() const
98 {
99   if (m_savestate != nullptr)
100     return TranslateType(m_savestate->type());
101 
102   return SAVE_TYPE::UNKNOWN;
103 }
104 
SetType(SAVE_TYPE type)105 void CSavestateFlatBuffer::SetType(SAVE_TYPE type)
106 {
107   m_type = type;
108 }
109 
Slot() const110 uint8_t CSavestateFlatBuffer::Slot() const
111 {
112   if (m_savestate != nullptr)
113     return m_savestate->slot();
114 
115   return 0;
116 }
117 
SetSlot(uint8_t slot)118 void CSavestateFlatBuffer::SetSlot(uint8_t slot)
119 {
120   m_slot = slot;
121 }
122 
Label() const123 std::string CSavestateFlatBuffer::Label() const
124 {
125   std::string label;
126 
127   if (m_savestate != nullptr && m_savestate->label())
128     label = m_savestate->label()->c_str();
129 
130   return label;
131 }
132 
SetLabel(const std::string & label)133 void CSavestateFlatBuffer::SetLabel(const std::string& label)
134 {
135   m_labelOffset.reset(new StringOffset{m_builder->CreateString(label)});
136 }
137 
Created() const138 CDateTime CSavestateFlatBuffer::Created() const
139 {
140   CDateTime created;
141 
142   if (m_savestate != nullptr && m_savestate->created())
143     created.SetFromRFC1123DateTime(m_savestate->created()->c_str());
144 
145   return created;
146 }
147 
SetCreated(const CDateTime & created)148 void CSavestateFlatBuffer::SetCreated(const CDateTime& created)
149 {
150   m_createdOffset.reset(new StringOffset{m_builder->CreateString(created.GetAsRFC1123DateTime())});
151 }
152 
GameFileName() const153 std::string CSavestateFlatBuffer::GameFileName() const
154 {
155   std::string gameFileName;
156 
157   if (m_savestate != nullptr && m_savestate->game_file_name())
158     gameFileName = m_savestate->game_file_name()->c_str();
159 
160   return gameFileName;
161 }
162 
SetGameFileName(const std::string & gameFileName)163 void CSavestateFlatBuffer::SetGameFileName(const std::string& gameFileName)
164 {
165   m_gameFileNameOffset.reset(new StringOffset{m_builder->CreateString(gameFileName)});
166 }
167 
TimestampFrames() const168 uint64_t CSavestateFlatBuffer::TimestampFrames() const
169 {
170   return m_savestate->timestamp_frames();
171 }
172 
SetTimestampFrames(uint64_t timestampFrames)173 void CSavestateFlatBuffer::SetTimestampFrames(uint64_t timestampFrames)
174 {
175   m_timestampFrames = timestampFrames;
176 }
177 
TimestampWallClock() const178 double CSavestateFlatBuffer::TimestampWallClock() const
179 {
180   if (m_savestate != nullptr)
181     return static_cast<double>(m_savestate->timestamp_wall_clock_ns()) / 1000.0 / 1000.0 / 1000.0;
182 
183   return 0.0;
184 }
185 
SetTimestampWallClock(double timestampWallClock)186 void CSavestateFlatBuffer::SetTimestampWallClock(double timestampWallClock)
187 {
188   m_timestampWallClock = timestampWallClock;
189 }
190 
GameClientID() const191 std::string CSavestateFlatBuffer::GameClientID() const
192 {
193   std::string gameClientId;
194 
195   if (m_savestate != nullptr && m_savestate->emulator_addon_id())
196     gameClientId = m_savestate->emulator_addon_id()->c_str();
197 
198   return gameClientId;
199 }
200 
SetGameClientID(const std::string & gameClientId)201 void CSavestateFlatBuffer::SetGameClientID(const std::string& gameClientId)
202 {
203   m_emulatorAddonIdOffset.reset(new StringOffset{m_builder->CreateString(gameClientId)});
204 }
205 
GameClientVersion() const206 std::string CSavestateFlatBuffer::GameClientVersion() const
207 {
208   std::string gameClientVersion;
209 
210   if (m_savestate != nullptr && m_savestate->emulator_version())
211     gameClientVersion = m_savestate->emulator_version()->c_str();
212 
213   return gameClientVersion;
214 }
215 
SetGameClientVersion(const std::string & gameClientVersion)216 void CSavestateFlatBuffer::SetGameClientVersion(const std::string& gameClientVersion)
217 {
218   m_emulatorVersionOffset.reset(new StringOffset{m_builder->CreateString(gameClientVersion)});
219 }
220 
GetMemoryData() const221 const uint8_t* CSavestateFlatBuffer::GetMemoryData() const
222 {
223   if (m_savestate != nullptr && m_savestate->memory_data())
224     return m_savestate->memory_data()->data();
225 
226   return nullptr;
227 }
228 
GetMemorySize() const229 size_t CSavestateFlatBuffer::GetMemorySize() const
230 {
231   if (m_savestate != nullptr && m_savestate->memory_data())
232     return m_savestate->memory_data()->size();
233 
234   return 0;
235 }
236 
GetMemoryBuffer(size_t size)237 uint8_t* CSavestateFlatBuffer::GetMemoryBuffer(size_t size)
238 {
239   uint8_t* memoryBuffer = nullptr;
240 
241   m_memoryDataOffset.reset(
242       new VectorOffset{m_builder->CreateUninitializedVector(size, &memoryBuffer)});
243 
244   return memoryBuffer;
245 }
246 
Finalize()247 void CSavestateFlatBuffer::Finalize()
248 {
249   // Helper class to build the nested Savestate table
250   SavestateBuilder savestateBuilder(*m_builder);
251 
252   savestateBuilder.add_version(SCHEMA_VERSION);
253 
254   savestateBuilder.add_type(TranslateType(m_type));
255 
256   savestateBuilder.add_slot(m_slot);
257 
258   if (m_labelOffset)
259   {
260     savestateBuilder.add_label(*m_labelOffset);
261     m_labelOffset.reset();
262   }
263 
264   if (m_createdOffset)
265   {
266     savestateBuilder.add_created(*m_createdOffset);
267     m_createdOffset.reset();
268   }
269 
270   if (m_gameFileNameOffset)
271   {
272     savestateBuilder.add_game_file_name(*m_gameFileNameOffset);
273     m_gameFileNameOffset.reset();
274   }
275 
276   savestateBuilder.add_timestamp_frames(m_timestampFrames);
277 
278   const uint64_t wallClockNs =
279       static_cast<uint64_t>(m_timestampWallClock * 1000.0 * 1000.0 * 1000.0);
280   savestateBuilder.add_timestamp_wall_clock_ns(wallClockNs);
281 
282   if (m_emulatorAddonIdOffset)
283   {
284     savestateBuilder.add_emulator_addon_id(*m_emulatorAddonIdOffset);
285     m_emulatorAddonIdOffset.reset();
286   }
287 
288   if (m_emulatorVersionOffset)
289   {
290     savestateBuilder.add_emulator_version(*m_emulatorVersionOffset);
291     m_emulatorVersionOffset.reset();
292   }
293 
294   if (m_memoryDataOffset)
295   {
296     savestateBuilder.add_memory_data(*m_memoryDataOffset);
297     m_memoryDataOffset.reset();
298   }
299 
300   auto savestate = savestateBuilder.Finish();
301   FinishSavestateBuffer(*m_builder, savestate);
302 
303   m_savestate = GetSavestate(m_builder->GetBufferPointer());
304 }
305 
Deserialize(std::vector<uint8_t> data)306 bool CSavestateFlatBuffer::Deserialize(std::vector<uint8_t> data)
307 {
308   flatbuffers::Verifier verifier(data.data(), data.size());
309   if (VerifySavestateBuffer(verifier))
310   {
311     const Savestate* savestate = GetSavestate(data.data());
312 
313     if (savestate->version() != SCHEMA_VERSION)
314     {
315       CLog::Log(LOGERROR, "RetroPlayer[SAVE): Schema version %u not supported, must be version %u",
316                 savestate->version(), SCHEMA_VERSION);
317     }
318     else
319     {
320       m_data = std::move(data);
321       m_savestate = GetSavestate(m_data.data());
322       return true;
323     }
324   }
325 
326   return false;
327 }
328