1 /*
2  *  Copyright (C) 2005-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 "Autorun.h"
10 
11 #include <stdlib.h>
12 
13 #include "Application.h"
14 #include "GUIPassword.h"
15 #include "GUIUserMessages.h"
16 #include "PlayListPlayer.h"
17 #include "ServiceBroker.h"
18 #include "cores/playercorefactory/PlayerCoreFactory.h"
19 #include "filesystem/StackDirectory.h"
20 #include "filesystem/Directory.h"
21 #include "filesystem/DirectoryFactory.h"
22 #include "filesystem/File.h"
23 #include "messaging/ApplicationMessenger.h"
24 #include "messaging/helpers/DialogHelper.h"
25 #include "profiles/ProfileManager.h"
26 #include "settings/Settings.h"
27 #include "settings/SettingsComponent.h"
28 #include "settings/lib/Setting.h"
29 #include "settings/lib/SettingDefinitions.h"
30 #include "playlists/PlayList.h"
31 #include "guilib/GUIComponent.h"
32 #include "guilib/GUIWindowManager.h"
33 #include "guilib/LocalizeStrings.h"
34 #include "storage/MediaManager.h"
35 #include "video/VideoDatabase.h"
36 #include "utils/StringUtils.h"
37 #include "utils/URIUtils.h"
38 #include "utils/log.h"
39 #include "utils/Variant.h"
40 
41 #ifdef HAS_CDDA_RIPPER
42 #include "cdrip/CDDARipper.h"
43 #endif
44 
45 using namespace XFILE;
46 using namespace PLAYLIST;
47 using namespace MEDIA_DETECT;
48 using namespace KODI::MESSAGING;
49 
50 using KODI::MESSAGING::HELPERS::DialogResponse;
51 
CAutorun()52 CAutorun::CAutorun()
53 {
54   m_bEnable = true;
55 }
56 
57 CAutorun::~CAutorun() = default;
58 
ExecuteAutorun(const std::string & path,bool bypassSettings,bool ignoreplaying,bool startFromBeginning)59 void CAutorun::ExecuteAutorun(const std::string& path, bool bypassSettings, bool ignoreplaying, bool startFromBeginning )
60 {
61   if (!ignoreplaying)
62   {
63     if (g_application.GetAppPlayer().IsPlayingAudio() ||
64         g_application.GetAppPlayer().IsPlayingVideo() ||
65         CServiceBroker::GetGUI()->GetWindowManager().HasModalDialog(true))
66     {
67       return;
68     }
69   }
70 
71   if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_LOGIN_SCREEN)
72     return;
73 
74   CCdInfo* pInfo = CServiceBroker::GetMediaManager().GetCdInfo(path);
75 
76   if ( pInfo == NULL )
77     return ;
78 
79   g_application.ResetScreenSaver();
80   g_application.WakeUpScreenSaverAndDPMS();  // turn off the screensaver if it's active
81 
82 #ifdef HAS_CDDA_RIPPER
83   if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_AUDIOCDS_AUTOACTION) == AUTOCD_RIP &&
84       pInfo->IsAudio(1) && !CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().musicLocked())
85   {
86     CCDDARipper::GetInstance().RipCD();
87   }
88   else
89 #endif
90 
91   PlayDisc(path, bypassSettings, startFromBeginning);
92 }
93 
PlayDisc(const std::string & path,bool bypassSettings,bool startFromBeginning)94 bool CAutorun::PlayDisc(const std::string& path, bool bypassSettings, bool startFromBeginning)
95 {
96   if ( !bypassSettings && CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_AUDIOCDS_AUTOACTION) != AUTOCD_PLAY && !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_DVDS_AUTORUN))
97     return false;
98 
99   int nSize = CServiceBroker::GetPlaylistPlayer().GetPlaylist( PLAYLIST_MUSIC ).size();
100   int nAddedToPlaylist = 0;
101 
102   std::string mediaPath;
103 
104   CCdInfo* pInfo = CServiceBroker::GetMediaManager().GetCdInfo(path);
105   if (pInfo == NULL)
106     return false;
107 
108   if (mediaPath.empty() && pInfo->IsAudio(1))
109     mediaPath = "cdda://local/";
110 
111   if (mediaPath.empty() && (pInfo->IsISOUDF(1) || pInfo->IsISOHFS(1) || pInfo->IsIso9660(1) || pInfo->IsIso9660Interactive(1)))
112     mediaPath = "iso9660://";
113 
114   if (mediaPath.empty())
115     mediaPath = path;
116 
117   if (mediaPath.empty() || mediaPath == "iso9660://")
118     mediaPath = CServiceBroker::GetMediaManager().GetDiscPath();
119 
120   const CURL pathToUrl(mediaPath);
121   std::unique_ptr<IDirectory> pDir ( CDirectoryFactory::Create( pathToUrl ));
122   bool bPlaying = RunDisc(pDir.get(), mediaPath, nAddedToPlaylist, true, bypassSettings, startFromBeginning);
123 
124   if ( !bPlaying && nAddedToPlaylist > 0 )
125   {
126     CGUIMessage msg( GUI_MSG_PLAYLIST_CHANGED, 0, 0 );
127     CServiceBroker::GetGUI()->GetWindowManager().SendMessage( msg );
128     CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_MUSIC);
129     // Start playing the items we inserted
130     return CServiceBroker::GetPlaylistPlayer().Play(nSize, "");
131   }
132 
133   return bPlaying;
134 }
135 
136 /**
137  * This method tries to determine what type of disc is located in the given drive and starts to play the content appropriately.
138  */
RunDisc(IDirectory * pDir,const std::string & strDrive,int & nAddedToPlaylist,bool bRoot,bool bypassSettings,bool startFromBeginning)139 bool CAutorun::RunDisc(IDirectory* pDir, const std::string& strDrive, int& nAddedToPlaylist, bool bRoot, bool bypassSettings /* = false */, bool startFromBeginning /* = false */)
140 {
141   if (!pDir)
142   {
143     CLog::Log(LOGDEBUG, "CAutorun::{}: cannot run disc. is it properly mounted?", __FUNCTION__);
144     return false;
145   }
146 
147   bool bPlaying(false);
148   CFileItemList vecItems;
149 
150   const CURL pathToUrl(strDrive);
151   if ( !pDir->GetDirectory( pathToUrl, vecItems ) )
152   {
153     return false;
154   }
155 
156   // Sorting necessary for easier HDDVD handling
157   vecItems.Sort(SortByLabel, SortOrderAscending);
158 
159   bool bAllowVideo = true;
160 //  bool bAllowPictures = true;
161   bool bAllowMusic = true;
162   if (!g_passwordManager.IsMasterLockUnlocked(false))
163   {
164     const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
165 
166     bAllowVideo = !profileManager->GetCurrentProfile().videoLocked();
167 //    bAllowPictures = !profileManager->GetCurrentProfile().picturesLocked();
168     bAllowMusic = !profileManager->GetCurrentProfile().musicLocked();
169   }
170 
171   // is this a root folder we have to check the content to determine a disc type
172   if (bRoot)
173   {
174     std::string hddvdname = "";
175     CFileItemPtr phddvdItem;
176     bool bAutorunDVDs = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_DVDS_AUTORUN);
177 
178     // check root folders next, for normal structured dvd's
179     for (const auto& pItem : vecItems)
180     {
181       // is the current item a (non system) folder?
182       if (pItem->m_bIsFolder && pItem->GetPath() != "." && pItem->GetPath() != "..")
183       {
184         std::string name = pItem->GetPath();
185         URIUtils::RemoveSlashAtEnd(name);
186         name = URIUtils::GetFileName(name);
187 
188         // Check if the current foldername indicates a DVD structure (name is "VIDEO_TS")
189         if (StringUtils::EqualsNoCase(name, "VIDEO_TS") && bAllowVideo
190         && (bypassSettings || bAutorunDVDs))
191         {
192           std::string path = URIUtils::AddFileToFolder(pItem->GetPath(), "VIDEO_TS.IFO");
193           if(!CFile::Exists(path))
194             path = URIUtils::AddFileToFolder(pItem->GetPath(), "video_ts.ifo");
195           CFileItemPtr item(new CFileItem(path, false));
196           item->SetLabel(CServiceBroker::GetMediaManager().GetDiskLabel(strDrive));
197           item->GetVideoInfoTag()->m_strFileNameAndPath =
198               CServiceBroker::GetMediaManager().GetDiskUniqueId(strDrive);
199 
200           if (!startFromBeginning && !item->GetVideoInfoTag()->m_strFileNameAndPath.empty())
201             item->m_lStartOffset = STARTOFFSET_RESUME;
202 
203           CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST_VIDEO);
204           CServiceBroker::GetPlaylistPlayer().SetShuffle (PLAYLIST_VIDEO, false);
205           CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST_VIDEO, item);
206           CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_VIDEO);
207           CServiceBroker::GetPlaylistPlayer().Play(0, "");
208           return true;
209         }
210 
211         // Check if the current foldername indicates a Blu-Ray structure (default is "BDMV").
212         // A BR should also include an "AACS" folder for encryption, Sony-BRs can also include update folders for PS3 (PS3_UPDATE / PS3_VPRM).
213         //! @todo for the time being, the DVD autorun settings are used to determine if the BR should be started automatically.
214         if (StringUtils::EqualsNoCase(name, "BDMV") && bAllowVideo
215         && (bypassSettings || bAutorunDVDs))
216         {
217           CFileItemPtr item(new CFileItem(URIUtils::AddFileToFolder(pItem->GetPath(), "index.bdmv"), false));
218           item->SetLabel(CServiceBroker::GetMediaManager().GetDiskLabel(strDrive));
219           item->GetVideoInfoTag()->m_strFileNameAndPath =
220               CServiceBroker::GetMediaManager().GetDiskUniqueId(strDrive);
221 
222           if (!startFromBeginning && !item->GetVideoInfoTag()->m_strFileNameAndPath.empty())
223             item->m_lStartOffset = STARTOFFSET_RESUME;
224 
225           CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST_VIDEO);
226           CServiceBroker::GetPlaylistPlayer().SetShuffle (PLAYLIST_VIDEO, false);
227           CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST_VIDEO, item);
228           CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_VIDEO);
229           CServiceBroker::GetPlaylistPlayer().Play(0, "");
230           return true;
231         }
232 
233         // Check if the current foldername indicates a HD DVD structure (default is "HVDVD_TS").
234         // Most HD DVD will also include an "ADV_OBJ" folder for advanced content. This folder should be handled first.
235         //! @todo for the time being, the DVD autorun settings are used to determine if the HD DVD should be started automatically.
236         CFileItemList items, sitems;
237 
238         // Advanced Content HD DVD (most discs?)
239         if (StringUtils::EqualsNoCase(name, "ADV_OBJ"))
240         {
241           CLog::Log(LOGINFO,"HD DVD: Checking for playlist.");
242           // find playlist file
243           CDirectory::GetDirectory(pItem->GetPath(), items, "*.xpl", DIR_FLAG_DEFAULTS);
244           if (items.Size())
245           {
246             // HD DVD Standard says the highest numbered playlist has to be handled first.
247             CLog::Log(LOGINFO,"HD DVD: Playlist found. Set filetypes to *.xpl for external player.");
248             items.Sort(SortByLabel, SortOrderDescending);
249             phddvdItem = pItem;
250             hddvdname = URIUtils::GetFileName(items[0]->GetPath());
251             CLog::Log(LOGINFO,"HD DVD: %s", items[0]->GetPath().c_str());
252           }
253         }
254 
255         // Standard Content HD DVD (few discs?)
256         if (StringUtils::EqualsNoCase(name, "HVDVD_TS") && bAllowVideo
257         && (bypassSettings || bAutorunDVDs))
258         {
259           if (hddvdname == "")
260           {
261             CLog::Log(LOGINFO,"HD DVD: Checking for ifo.");
262             // find Video Manager or Title Set Information
263             CDirectory::GetDirectory(pItem->GetPath(), items, "HV*.ifo", DIR_FLAG_DEFAULTS);
264             if (items.Size())
265             {
266               // HD DVD Standard says the lowest numbered ifo has to be handled first.
267               CLog::Log(LOGINFO,"HD DVD: IFO found. Set filename to HV* and filetypes to *.ifo for external player.");
268               items.Sort(SortByLabel, SortOrderAscending);
269               phddvdItem = pItem;
270               hddvdname = URIUtils::GetFileName(items[0]->GetPath());
271               CLog::Log(LOGINFO,"HD DVD: %s",items[0]->GetPath().c_str());
272             }
273           }
274           // Find and sort *.evo files for internal playback.
275           // While this algorithm works for all of my HD DVDs, it may fail on other discs. If there are very large extras which are
276           // alphabetically before the main movie they will be sorted to the top of the playlist and get played first.
277           CDirectory::GetDirectory(pItem->GetPath(), items, "*.evo", DIR_FLAG_DEFAULTS);
278           if (items.Size())
279           {
280             // Sort *.evo files in alphabetical order.
281             items.Sort(SortByLabel, SortOrderAscending);
282             int64_t asize = 0;
283             int ecount = 0;
284             // calculate average size of elements above 1gb
285             for (int j = 0; j < items.Size(); j++)
286               if (items[j]->m_dwSize > 1000000000)
287               {
288                 ecount++;
289                 asize = asize + items[j]->m_dwSize;
290               }
291             if (ecount > 0)
292               asize = asize / ecount;
293             // Put largest files in alphabetical order to top of new list.
294             for (int j = 0; j < items.Size(); j++)
295               if (items[j]->m_dwSize >= asize)
296                 sitems.Add (items[j]);
297             // Sort *.evo files by size.
298             items.Sort(SortBySize, SortOrderDescending);
299             // Add other files with descending size to bottom of new list.
300             for (int j = 0; j < items.Size(); j++)
301               if (items[j]->m_dwSize < asize)
302                 sitems.Add (items[j]);
303             // Replace list with optimized list.
304             items.Clear();
305             items.Copy (sitems);
306             sitems.Clear();
307           }
308           if (hddvdname != "")
309           {
310             CFileItem item(URIUtils::AddFileToFolder(phddvdItem->GetPath(), hddvdname), false);
311             item.SetLabel(CServiceBroker::GetMediaManager().GetDiskLabel(strDrive));
312             item.GetVideoInfoTag()->m_strFileNameAndPath =
313                 CServiceBroker::GetMediaManager().GetDiskUniqueId(strDrive);
314 
315             if (!startFromBeginning && !item.GetVideoInfoTag()->m_strFileNameAndPath.empty())
316             item.m_lStartOffset = STARTOFFSET_RESUME;
317 
318             // get playername
319             std::string hdVideoPlayer = CServiceBroker::GetPlayerCoreFactory().GetDefaultPlayer(item);
320 
321             // Single *.xpl or *.ifo files require an external player to handle playback.
322             // If no matching rule was found, VideoPlayer will be default player.
323             if (hdVideoPlayer != "VideoPlayer")
324             {
325               CLog::Log(LOGINFO,"HD DVD: External singlefile playback initiated: %s",hddvdname.c_str());
326               g_application.PlayFile(item, hdVideoPlayer, false);
327               return true;
328             } else
329               CLog::Log(LOGINFO,"HD DVD: No external player found. Fallback to internal one.");
330           }
331 
332           //  internal *.evo playback.
333           CLog::Log(LOGINFO,"HD DVD: Internal multifile playback initiated.");
334           CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST_VIDEO);
335           CServiceBroker::GetPlaylistPlayer().SetShuffle (PLAYLIST_VIDEO, false);
336           CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST_VIDEO, items);
337           CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_VIDEO);
338           CServiceBroker::GetPlaylistPlayer().Play(0, "");
339           return true;
340         }
341 
342         // Video CDs can have multiple file formats. First we need to determine which one is used on the CD
343         std::string strExt;
344         if (StringUtils::EqualsNoCase(name, "MPEGAV"))
345           strExt = ".dat";
346         if (StringUtils::EqualsNoCase(name, "MPEG2"))
347           strExt = ".mpg";
348 
349         // If a file format was extracted we are sure this is a VCD. Autoplay if settings indicate we should.
350         if (!strExt.empty() && bAllowVideo
351              && (bypassSettings || bAutorunDVDs))
352         {
353           CFileItemList items;
354           CDirectory::GetDirectory(pItem->GetPath(), items, strExt, DIR_FLAG_DEFAULTS);
355           if (items.Size())
356           {
357             items.Sort(SortByLabel, SortOrderAscending);
358             CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST_VIDEO);
359             CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST_VIDEO, items);
360             CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_VIDEO);
361             CServiceBroker::GetPlaylistPlayer().Play(0, "");
362             return true;
363           }
364         }
365         /* Probably want this if/when we add some automedia action dialog...
366         else if (pItem->GetPath().Find("PICTURES") != -1 && bAllowPictures
367               && (bypassSettings))
368         {
369           bPlaying = true;
370           std::string strExec = StringUtils::Format("RecursiveSlideShow(%s)", pItem->GetPath().c_str());
371           CBuiltins::Execute(strExec);
372           return true;
373         }
374         */
375       }
376     }
377   }
378 
379   // check video first
380   if (!nAddedToPlaylist && !bPlaying && (bypassSettings || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_DVDS_AUTORUN)))
381   {
382     // stack video files
383     CFileItemList tempItems;
384     tempItems.Append(vecItems);
385     if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_STACKVIDEOS))
386       tempItems.Stack();
387     CFileItemList itemlist;
388 
389     for (int i = 0; i < tempItems.Size(); i++)
390     {
391       CFileItemPtr pItem = tempItems[i];
392       if (!pItem->m_bIsFolder && pItem->IsVideo())
393       {
394         bPlaying = true;
395         if (pItem->IsStack())
396         {
397           //! @todo remove this once the app/player is capable of handling stacks immediately
398           CStackDirectory dir;
399           CFileItemList items;
400           dir.GetDirectory(pItem->GetURL(), items);
401           itemlist.Append(items);
402         }
403         else
404           itemlist.Add(pItem);
405       }
406     }
407     if (itemlist.Size())
408     {
409       if (!bAllowVideo)
410       {
411         if (!bypassSettings)
412           return false;
413 
414         if (!g_passwordManager.IsMasterLockUnlocked(true))
415           return false;
416       }
417       CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST_VIDEO);
418       CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST_VIDEO, itemlist);
419       CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_VIDEO);
420       CServiceBroker::GetPlaylistPlayer().Play(0, "");
421     }
422   }
423   // then music
424   if (!bPlaying && (bypassSettings || CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_AUDIOCDS_AUTOACTION) == AUTOCD_PLAY) && bAllowMusic)
425   {
426     for (int i = 0; i < vecItems.Size(); i++)
427     {
428       CFileItemPtr pItem = vecItems[i];
429       if (!pItem->m_bIsFolder && pItem->IsAudio())
430       {
431         nAddedToPlaylist++;
432         CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST_MUSIC, pItem);
433       }
434     }
435   }
436   /* Probably want this if/when we add some automedia action dialog...
437   // and finally pictures
438   if (!nAddedToPlaylist && !bPlaying && bypassSettings && bAllowPictures)
439   {
440     for (int i = 0; i < vecItems.Size(); i++)
441     {
442       CFileItemPtr pItem = vecItems[i];
443       if (!pItem->m_bIsFolder && pItem->IsPicture())
444       {
445         bPlaying = true;
446         std::string strExec = StringUtils::Format("RecursiveSlideShow(%s)", strDrive.c_str());
447         CBuiltins::Execute(strExec);
448         break;
449       }
450     }
451   }
452   */
453 
454   // check subdirs if we are not playing yet
455   if (!bPlaying)
456   {
457     for (int i = 0; i < vecItems.Size(); i++)
458     {
459       CFileItemPtr  pItem = vecItems[i];
460       if (pItem->m_bIsFolder)
461       {
462         if (pItem->GetPath() != "." && pItem->GetPath() != ".." )
463         {
464           if (RunDisc(pDir, pItem->GetPath(), nAddedToPlaylist, false, bypassSettings, startFromBeginning))
465           {
466             bPlaying = true;
467             break;
468           }
469         }
470       } // if (non system) folder
471     } // for all items in directory
472   } // if root folder
473 
474   return bPlaying;
475 }
476 
HandleAutorun()477 void CAutorun::HandleAutorun()
478 {
479 #ifndef TARGET_WINDOWS
480   if (!m_bEnable)
481   {
482     CDetectDVDMedia::m_evAutorun.Reset();
483     return ;
484   }
485 
486   if (CDetectDVDMedia::m_evAutorun.WaitMSec(0))
487   {
488     ExecuteAutorun();
489     CDetectDVDMedia::m_evAutorun.Reset();
490   }
491 #endif
492 }
493 
Enable()494 void CAutorun::Enable()
495 {
496   m_bEnable = true;
497 }
498 
Disable()499 void CAutorun::Disable()
500 {
501   m_bEnable = false;
502 }
503 
IsEnabled() const504 bool CAutorun::IsEnabled() const
505 {
506   return m_bEnable;
507 }
508 
PlayDiscAskResume(const std::string & path)509 bool CAutorun::PlayDiscAskResume(const std::string& path)
510 {
511   return PlayDisc(path, true, !CanResumePlayDVD(path) ||
512     HELPERS::ShowYesNoDialogText(CVariant{341}, CVariant{""}, CVariant{13404}, CVariant{12021}) ==
513     DialogResponse::YES);
514 }
515 
CanResumePlayDVD(const std::string & path)516 bool CAutorun::CanResumePlayDVD(const std::string& path)
517 {
518   std::string strUniqueId = CServiceBroker::GetMediaManager().GetDiskUniqueId(path);
519   if (!strUniqueId.empty())
520   {
521     CVideoDatabase dbs;
522     dbs.Open();
523     CBookmark bookmark;
524     if (dbs.GetResumeBookMark(strUniqueId, bookmark))
525       return true;
526   }
527   return false;
528 }
529 
SettingOptionAudioCdActionsFiller(const SettingConstPtr & setting,std::vector<IntegerSettingOption> & list,int & current,void * data)530 void CAutorun::SettingOptionAudioCdActionsFiller(const SettingConstPtr& setting,
531                                                  std::vector<IntegerSettingOption>& list,
532                                                  int& current,
533                                                  void* data)
534 {
535   list.emplace_back(g_localizeStrings.Get(16018), AUTOCD_NONE);
536   list.emplace_back(g_localizeStrings.Get(14098), AUTOCD_PLAY);
537 #ifdef HAS_CDDA_RIPPER
538   list.emplace_back(g_localizeStrings.Get(14096), AUTOCD_RIP);
539 #endif
540 }
541