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 "LibretroEnvironment.h"
9 #include "ClientBridge.h"
10 #include "FrontendBridge.h"
11 #include "libretro.h"
12 #include "LibretroDLL.h"
13 #include "LibretroTranslator.h"
14 #include "input/InputManager.h"
15 #include "log/Log.h"
16 #include "settings/Settings.h"
17 #include "video/VideoGeometry.h"
18 #include "client.h"
19 
20 #include <kodi/General.h>
21 
22 using namespace LIBRETRO;
23 
24 namespace LIBRETRO
25 {
EnvCallback(unsigned cmd,void * data)26   bool EnvCallback(unsigned cmd, void* data)
27   {
28     return CLibretroEnvironment::Get().EnvironmentCallback(cmd, data);
29   }
30 }
31 
CLibretroEnvironment(void)32 CLibretroEnvironment::CLibretroEnvironment(void) :
33   m_addon(nullptr),
34   m_client(nullptr),
35   m_clientBridge(nullptr),
36   m_videoFormat(GAME_PIXEL_FORMAT_0RGB1555), // Default libretro format
37   m_videoRotation(GAME_VIDEO_ROTATION_0)
38 {
39 }
40 
Get(void)41 CLibretroEnvironment& CLibretroEnvironment::Get(void)
42 {
43   static CLibretroEnvironment _instance;
44   return _instance;
45 }
46 
Initialize(CGameLibRetro * addon,CLibretroDLL * client,CClientBridge * clientBridge)47 void CLibretroEnvironment::Initialize(CGameLibRetro*                addon,
48                                       CLibretroDLL*                 client,
49                                       CClientBridge*                clientBridge)
50 {
51   m_addon        = addon;
52   m_client       = client;
53   m_clientBridge = clientBridge;
54 
55   m_videoStream.Initialize(m_addon);
56   m_audioStream.Initialize(m_addon);
57 
58   m_settings.Initialize(m_addon);
59   m_resources.Initialize(m_addon);
60 
61   // Install environment callback
62   m_client->retro_set_environment(EnvCallback);
63 
64   // Install remaining callbacks
65   m_client->retro_set_video_refresh(CFrontendBridge::VideoRefresh);
66   m_client->retro_set_audio_sample(CFrontendBridge::AudioFrame);
67   m_client->retro_set_audio_sample_batch(CFrontendBridge::AudioFrames);
68   m_client->retro_set_input_poll(CFrontendBridge::InputPoll);
69   m_client->retro_set_input_state(CFrontendBridge::InputState);
70 }
71 
Deinitialize()72 void CLibretroEnvironment::Deinitialize()
73 {
74   CloseStreams();
75 
76   m_resources.Deinitialize();
77   m_settings.Deinitialize();
78 }
79 
CloseStreams()80 void CLibretroEnvironment::CloseStreams()
81 {
82   m_videoStream.Deinitialize();
83   m_audioStream.Deinitialize();
84 }
85 
UpdateVideoGeometry(const retro_game_geometry & geometry)86 void CLibretroEnvironment::UpdateVideoGeometry(const retro_game_geometry &geometry)
87 {
88   CVideoGeometry videoGeometry(geometry);
89   m_videoStream.SetGeometry(videoGeometry);
90 }
91 
SetSetting(const std::string & name,const std::string & value)92 void CLibretroEnvironment::SetSetting(const std::string& name, const std::string& value)
93 {
94   m_settings.SetCurrentValue(name, value);
95 }
96 
GetResourcePath(const char * relPath)97 std::string CLibretroEnvironment::GetResourcePath(const char* relPath)
98 {
99   return m_resources.GetFullPath(relPath);
100 }
101 
OnFrameEnd()102 void CLibretroEnvironment::OnFrameEnd()
103 {
104   m_videoStream.OnFrameEnd();
105 }
106 
EnvironmentCallback(unsigned int cmd,void * data)107 bool CLibretroEnvironment::EnvironmentCallback(unsigned int cmd, void *data)
108 {
109   if (!m_addon || !m_clientBridge)
110     return false;
111 
112   switch (cmd)
113   {
114   case RETRO_ENVIRONMENT_SET_ROTATION:
115     {
116       unsigned* typedData = reinterpret_cast<unsigned*>(data);
117       if (typedData)
118         m_videoRotation = LibretroTranslator::GetVideoRotation(*typedData);
119       break;
120     }
121   case RETRO_ENVIRONMENT_GET_OVERSCAN:
122     {
123       bool* typedData = reinterpret_cast<bool*>(data);
124       if (typedData)
125         *typedData = !CSettings::Get().CropOverscan();
126       break;
127     }
128   case RETRO_ENVIRONMENT_GET_CAN_DUPE:
129     {
130       bool* typedData = reinterpret_cast<bool*>(data);
131       if (typedData)
132         *typedData = true;
133       break;
134     }
135   case RETRO_ENVIRONMENT_SET_MESSAGE:
136     {
137       // Sets a message to be displayed. Generally not for trivial messages.
138       const retro_message* typedData = reinterpret_cast<const retro_message*>(data);
139       if (typedData)
140       {
141         const char* msg = typedData->msg;
142         kodi::QueueFormattedNotification(QUEUE_INFO, msg);
143       }
144       break;
145     }
146   case RETRO_ENVIRONMENT_SHUTDOWN:
147     {
148       m_addon->CloseGame();
149       break;
150     }
151   case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL:
152     {
153       const unsigned* typedData = reinterpret_cast<const unsigned*>(data);
154       // Removed from Game API
155       (void)typedData;
156       break;
157     }
158   case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
159     {
160       const char** typedData = reinterpret_cast<const char**>(data);
161       if (typedData)
162       {
163         *typedData = m_resources.GetSystemDir();
164       }
165       break;
166     }
167   case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
168     {
169       const retro_pixel_format* typedData = reinterpret_cast<const retro_pixel_format*>(data);
170       if (!typedData)
171         return false;
172 
173       dsyslog("Setting libretro pixel format \"%s\"", LibretroTranslator::VideoFormatToString(*typedData));
174 
175       m_videoFormat = LibretroTranslator::GetVideoFormat(*typedData);
176 
177       break;
178     }
179   case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
180     {
181       const retro_input_descriptor* typedData = reinterpret_cast<const retro_input_descriptor*>(data);
182       if (typedData)
183         CInputManager::Get().LogInputDescriptors(typedData);
184       break;
185     }
186   case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK:
187     {
188       const retro_keyboard_callback* typedData = reinterpret_cast<const retro_keyboard_callback*>(data);
189       if (typedData)
190       {
191         // Store callback from libretro client
192         m_clientBridge->SetKeyboardEvent(typedData->callback);
193       }
194       break;
195     }
196   case RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE:
197     {
198       const retro_disk_control_callback *typedData = reinterpret_cast<const retro_disk_control_callback*>(data);
199       if (typedData)
200       {
201         // Disk control interface not implemented
202         return false;
203       }
204       break;
205     }
206   case RETRO_ENVIRONMENT_SET_HW_RENDER:
207     {
208       retro_hw_render_callback* typedData = reinterpret_cast<retro_hw_render_callback*>(data);
209       if (typedData)
210       {
211         // Translate struct and report hw info to frontend
212         game_stream_hw_framebuffer_properties hw_info;
213         hw_info.context_type       = LibretroTranslator::GetHWContextType(typedData->context_type);
214         hw_info.depth              = typedData->depth;
215         hw_info.stencil            = typedData->stencil;
216         hw_info.bottom_left_origin = typedData->bottom_left_origin;
217         hw_info.version_major      = typedData->version_major;
218         hw_info.version_minor      = typedData->version_minor;
219         hw_info.cache_context      = typedData->cache_context;
220         hw_info.debug_context      = typedData->debug_context;
221         m_videoStream.EnableHardwareRendering(hw_info);
222 
223         // Store callbacks from libretro client
224         m_clientBridge->SetHwContextReset(typedData->context_reset);
225         m_clientBridge->SetHwContextDestroy(typedData->context_destroy);
226 
227         // Expose frontend callbacks to libretro client
228         typedData->get_current_framebuffer = CFrontendBridge::HwGetCurrentFramebuffer;
229         typedData->get_proc_address        = CFrontendBridge::HwGetProcAddress;
230       }
231       break;
232     }
233   case RETRO_ENVIRONMENT_GET_VARIABLE:
234     {
235       retro_variable* typedData = reinterpret_cast<retro_variable*>(data);
236       if (typedData)
237       {
238         const char* strKey = typedData->key;
239         if (strKey == nullptr)
240           return false;
241 
242         typedData->value = m_settings.GetCurrentValue(strKey);
243 
244         // Assume libretro core is retrieving all variables at a time
245         m_settings.SetUnchanged();
246        }
247        break;
248     }
249   case RETRO_ENVIRONMENT_SET_VARIABLES:
250     {
251       const retro_variable* typedData = reinterpret_cast<const retro_variable*>(data);
252       if (typedData)
253       {
254         m_settings.SetAllSettings(typedData);
255       }
256       break;
257     }
258   case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
259     {
260       bool* typedData = reinterpret_cast<bool*>(data);
261       if (typedData)
262       {
263         *typedData = m_settings.Changed();
264       }
265       break;
266     }
267   case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
268     {
269       const bool* typedData = reinterpret_cast<const bool*>(data);
270       if (typedData)
271       {
272         const bool bSupportsNoGame = *typedData;
273         if (bSupportsNoGame)
274           kodi::Log(ADDON_LOG_DEBUG, "Libretro client supports loading with no game");
275       }
276       break;
277     }
278   case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH:
279     {
280       const char** typedData = reinterpret_cast<const char**>(data);
281       if (typedData)
282       {
283         *typedData = m_resources.GetContentDirectory();
284       }
285       break;
286     }
287   case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK:
288     {
289       const retro_audio_callback *typedData = reinterpret_cast<const retro_audio_callback*>(data);
290       if (typedData)
291       {
292         // Store callbacks from libretro client
293         m_clientBridge->SetAudioAvailable(typedData->callback);
294         m_clientBridge->SetAudioEnable(typedData->set_state);
295       }
296       break;
297     }
298   case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK:
299     {
300       const retro_frame_time_callback *typedData = reinterpret_cast<const retro_frame_time_callback*>(data);
301       if (typedData)
302       {
303         // Store callbacks from libretro client.
304         m_clientBridge->SetFrameTime(typedData->callback);
305         return false;
306       }
307       break;
308     }
309   case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE:
310     {
311       retro_rumble_interface* typedData = reinterpret_cast<retro_rumble_interface*>(data);
312       if (typedData)
313       {
314         // Expose callback to libretro core
315         typedData->set_rumble_state = CFrontendBridge::RumbleSetState;
316       }
317       break;
318     }
319   case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES:
320     {
321       uint64_t* typedData = reinterpret_cast<uint64_t*>(data);
322       if (typedData)
323         *typedData = CInputManager::Get().GetDeviceCaps();
324       break;
325     }
326   case RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE:
327     {
328       retro_sensor_interface* typedData = reinterpret_cast<retro_sensor_interface*>(data);
329       if (typedData)
330       {
331         // Expose callbacks to libretro core
332         typedData->set_sensor_state = CFrontendBridge::SensorSetState;
333         typedData->get_sensor_input = CFrontendBridge::SensorGetInput;
334       }
335       break;
336     }
337   case RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE:
338     {
339       retro_camera_callback* typedData = reinterpret_cast<retro_camera_callback*>(data);
340       if (typedData)
341       {
342         // Camera interface not implemented
343         return false;
344       }
345       break;
346     }
347   case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
348     {
349       retro_log_callback* typedData = reinterpret_cast<retro_log_callback*>(data);
350       if (typedData)
351       {
352         // Expose callback to libretro core
353         typedData->log = CFrontendBridge::LogFrontend; // libretro logging forwards to Kodi add-on log function
354       }
355       break;
356     }
357   case RETRO_ENVIRONMENT_GET_PERF_INTERFACE:
358     {
359       retro_perf_callback* typedData = reinterpret_cast<retro_perf_callback*>(data);
360       if (typedData)
361       {
362         // Expose callbacks to libretro core
363         typedData->get_time_usec    = CFrontendBridge::PerfGetTimeUsec;
364         typedData->get_cpu_features = CFrontendBridge::PerfGetCpuFeatures;
365         typedData->get_perf_counter = CFrontendBridge::PerfGetCounter;
366         typedData->perf_register    = CFrontendBridge::PerfRegister;
367         typedData->perf_start       = CFrontendBridge::PerfStart;
368         typedData->perf_stop        = CFrontendBridge::PerfStop;
369         typedData->perf_log         = CFrontendBridge::PerfLog;
370       }
371       break;
372     }
373   case RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE:
374     {
375       retro_location_callback* typedData = reinterpret_cast<retro_location_callback*>(data);
376       if (typedData)
377       {
378         // Expose callbacks to libretro core
379         typedData->start         = CFrontendBridge::StartLocation;
380         typedData->stop          = CFrontendBridge::StopLocation;
381         typedData->get_position  = CFrontendBridge::GetLocation;
382         typedData->set_interval  = CFrontendBridge::SetLocationInterval;
383         typedData->initialized   = CFrontendBridge::LocationInitialized;
384         typedData->deinitialized = CFrontendBridge::LocationDeinitialized;
385       }
386       break;
387     }
388   case RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY:
389     {
390       const char** typedData = reinterpret_cast<const char**>(data);
391       if (typedData)
392       {
393         *typedData = m_resources.GetContentDirectory();
394       }
395       break;
396     }
397   case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
398     {
399       const char** typedData = reinterpret_cast<const char**>(data);
400       if (typedData)
401       {
402         *typedData = m_resources.GetSaveDirectory();
403       }
404       break;
405     }
406   case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
407     {
408       const retro_system_av_info* typedData = reinterpret_cast<const retro_system_av_info*>(data);
409       if (!typedData)
410         return false;
411 
412       CVideoGeometry videoGeometry(typedData->geometry);
413       m_videoStream.SetGeometry(videoGeometry);
414 
415       //! @todo Reopen streams if geometry changes
416 
417       //! @todo Report updating timing info to frontend
418       const double fps = typedData->timing.fps;
419       const double sampleRate = typedData->timing.sample_rate;
420 
421       break;
422     }
423   case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK:
424   {
425     const retro_get_proc_address_interface* typedData = reinterpret_cast<const retro_get_proc_address_interface*>(data);
426     if (typedData)
427     {
428       // get_proc_address() interface not implemented
429       return false;
430     }
431     break;
432   }
433   case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
434   {
435     const retro_subsystem_info* typedData = reinterpret_cast<const retro_subsystem_info*>(data);
436     if (typedData)
437     {
438       // Not implemented
439       return false;
440     }
441     break;
442   }
443   case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
444     {
445       const retro_controller_info* typedData = reinterpret_cast<const retro_controller_info*>(data);
446       if (typedData)
447       {
448         CInputManager::Get().SetControllerInfo(typedData);
449       }
450       break;
451     }
452   case RETRO_ENVIRONMENT_SET_MEMORY_MAPS:
453   {
454     const retro_memory_map* typedData = reinterpret_cast<const retro_memory_map*>(data);
455     if (typedData)
456     {
457       // Not implemented
458       return false;
459     }
460     break;
461   }
462   case RETRO_ENVIRONMENT_SET_GEOMETRY:
463   {
464     const retro_game_geometry* typedData = reinterpret_cast<const retro_game_geometry*>(data);
465     if (typedData)
466     {
467       // Not implemented
468       return false;
469     }
470     break;
471   }
472   case RETRO_ENVIRONMENT_GET_USERNAME:
473   {
474     const char** typedData = reinterpret_cast<const char**>(data);
475     if (typedData)
476     {
477       // Not implemented
478       return false;
479     }
480     break;
481   }
482   case RETRO_ENVIRONMENT_GET_LANGUAGE:
483   {
484     unsigned int* typedData = reinterpret_cast<unsigned int*>(data);
485     if (typedData)
486     {
487       // Not implemented
488       return false;
489     }
490     break;
491   }
492   case RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER:
493   {
494     retro_framebuffer* typedData = reinterpret_cast<retro_framebuffer*>(data);
495     if (typedData)
496     {
497       // Get framebuffer params from core
498       const unsigned int accessFlags = typedData->access_flags;
499       const unsigned int width = typedData->width;
500       const unsigned int height = typedData->height;
501 
502       // Reading framebuffers not supported
503       if (accessFlags & RETRO_MEMORY_ACCESS_READ)
504         return false;
505 
506       game_stream_sw_framebuffer_buffer framebuffer{};
507       if (!m_videoStream.GetSwFramebuffer(width, height, m_videoFormat, framebuffer))
508         return false;
509 
510       // Report framebuffer info to frontend
511       typedData->data = framebuffer.data;
512       typedData->pitch = framebuffer.size / height;
513       typedData->format = LibretroTranslator::GetLibretroVideoFormat(framebuffer.format);
514       typedData->memory_flags = 0;
515     }
516     break;
517   }
518   case RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT:
519   {
520     // Not implemented
521     return false;
522   }
523   case RETRO_ENVIRONMENT_GET_VFS_INTERFACE:
524   {
525     const uint32_t supported_vfs_version = 1;
526 
527     retro_vfs_interface_info* typedData = reinterpret_cast<retro_vfs_interface_info*>(data);
528     if (typedData)
529     {
530       // Not implemented
531       return false;
532     }
533     break;
534   }
535   case RETRO_ENVIRONMENT_GET_LED_INTERFACE:
536   {
537     retro_led_interface* typedData = reinterpret_cast<retro_led_interface*>(data);
538     if (typedData)
539     {
540       // Not implemented
541       return false;
542     }
543     break;
544   }
545   case RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE:
546   {
547     const retro_hw_render_interface* typedData = reinterpret_cast<const retro_hw_render_interface*>(data);
548     if (typedData)
549     {
550       // Not implemented
551       return false;
552     }
553     break;
554   }
555   case RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS:
556   {
557     const bool* typedData = reinterpret_cast<const bool*>(data);
558     if (typedData)
559     {
560       // Not implemented
561       return false;
562     }
563     break;
564   }
565   case RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE:
566   {
567     const retro_hw_render_context_negotiation_interface* typedData = reinterpret_cast<const retro_hw_render_context_negotiation_interface*>(data);
568     if (typedData)
569     {
570       // Not implemented
571       return false;
572     }
573     break;
574   }
575   case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS:
576   {
577     const retro_hw_render_context_negotiation_interface* typedData = reinterpret_cast<const retro_hw_render_context_negotiation_interface*>(data);
578     if (typedData)
579     {
580       // Not implemented
581       return false;
582     }
583     break;
584   }
585   default:
586     return false;
587   }
588 
589   return true;
590 }
591