2015-08-31 21:35:33 -07:00
// Copyright 2015 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
# pragma once
2018-08-10 15:12:26 -07:00
# include <array>
2015-08-31 21:35:33 -07:00
# include <atomic>
2018-08-20 17:36:36 -07:00
# include <map>
# include <memory>
2018-08-29 06:42:53 -07:00
# include <unordered_map>
2018-08-06 10:36:05 -07:00
# include <utility>
2018-08-29 06:42:53 -07:00
# include <QCoreApplication>
2016-04-13 14:04:05 -07:00
# include <QImage>
2018-08-29 06:42:53 -07:00
# include <QObject>
2015-08-31 21:35:33 -07:00
# include <QRunnable>
# include <QStandardItem>
# include <QString>
2018-08-29 06:42:53 -07:00
# include "common/logging/log.h"
2016-09-17 17:38:01 -07:00
# include "common/string_util.h"
2018-08-20 17:36:36 -07:00
# include "core/file_sys/content_archive.h"
2018-07-28 09:32:16 -07:00
# include "ui_settings.h"
2018-01-16 10:05:21 -08:00
# include "yuzu/util/util.h"
2016-04-13 14:04:05 -07:00
/**
* Gets the default icon ( for games without valid SMDH )
* @ param large If true , returns large icon ( 48 x48 ) , otherwise returns small icon ( 24 x24 )
* @ return QPixmap default icon
*/
2018-07-28 09:32:16 -07:00
static QPixmap GetDefaultIcon ( u32 size ) {
2016-04-13 14:04:05 -07:00
QPixmap icon ( size , size ) ;
icon . fill ( Qt : : transparent ) ;
return icon ;
}
2018-08-29 06:42:53 -07:00
static auto FindMatchingCompatibilityEntry (
const std : : unordered_map < std : : string , std : : pair < QString , QString > > & compatibility_list ,
u64 program_id ) {
return std : : find_if (
compatibility_list . begin ( ) , compatibility_list . end ( ) ,
[ program_id ] ( const std : : pair < std : : string , std : : pair < QString , QString > > & element ) {
std : : string pid = fmt : : format ( " {:016X} " , program_id ) ;
return element . first = = pid ;
} ) ;
}
2015-08-31 21:35:33 -07:00
class GameListItem : public QStandardItem {
public :
2018-08-06 09:58:46 -07:00
GameListItem ( ) = default ;
explicit GameListItem ( const QString & string ) : QStandardItem ( string ) { }
2015-08-31 21:35:33 -07:00
} ;
/**
* A specialization of GameListItem for path values .
* This class ensures that for every full path value it holds , a correct string representation
* of just the filename ( with no extension ) will be displayed to the user .
2016-10-20 07:26:59 -07:00
* If this class receives valid SMDH data , it will also display game icons and titles .
2015-08-31 21:35:33 -07:00
*/
class GameListItemPath : public GameListItem {
public :
static const int FullPathRole = Qt : : UserRole + 1 ;
2016-04-13 14:04:05 -07:00
static const int TitleRole = Qt : : UserRole + 2 ;
2016-12-15 01:55:03 -08:00
static const int ProgramIdRole = Qt : : UserRole + 3 ;
2018-07-28 09:32:16 -07:00
static const int FileTypeRole = Qt : : UserRole + 4 ;
2015-08-31 21:35:33 -07:00
2018-08-06 09:58:46 -07:00
GameListItemPath ( ) = default ;
2018-07-28 09:32:16 -07:00
GameListItemPath ( const QString & game_path , const std : : vector < u8 > & picture_data ,
2018-08-10 15:07:43 -07:00
const QString & game_name , const QString & game_type , u64 program_id ) {
2015-08-31 21:35:33 -07:00
setData ( game_path , FullPathRole ) ;
2018-07-28 09:32:16 -07:00
setData ( game_name , TitleRole ) ;
2016-12-15 01:55:03 -08:00
setData ( qulonglong ( program_id ) , ProgramIdRole ) ;
2018-07-28 09:32:16 -07:00
setData ( game_type , FileTypeRole ) ;
2018-08-10 15:15:06 -07:00
const u32 size = UISettings : : values . icon_size ;
2018-07-28 09:32:16 -07:00
QPixmap picture ;
2018-08-10 15:15:06 -07:00
if ( ! picture . loadFromData ( picture_data . data ( ) , static_cast < u32 > ( picture_data . size ( ) ) ) ) {
2018-07-28 09:32:16 -07:00
picture = GetDefaultIcon ( size ) ;
2018-08-10 15:15:06 -07:00
}
2018-07-28 09:32:16 -07:00
picture = picture . scaled ( size , size ) ;
setData ( picture , Qt : : DecorationRole ) ;
2015-08-31 21:35:33 -07:00
}
2016-04-13 14:04:05 -07:00
QVariant data ( int role ) const override {
if ( role = = Qt : : DisplayRole ) {
2015-08-31 21:35:33 -07:00
std : : string filename ;
2016-09-17 17:38:01 -07:00
Common : : SplitPath ( data ( FullPathRole ) . toString ( ) . toStdString ( ) , nullptr , & filename ,
nullptr ) ;
2018-07-28 09:32:16 -07:00
2018-08-10 15:12:26 -07:00
const std : : array < QString , 4 > row_data { {
2018-07-28 09:32:16 -07:00
QString : : fromStdString ( filename ) ,
data ( FileTypeRole ) . toString ( ) ,
QString : : fromStdString ( fmt : : format ( " 0x{:016X} " , data ( ProgramIdRole ) . toULongLong ( ) ) ) ,
data ( TitleRole ) . toString ( ) ,
2018-08-10 15:12:26 -07:00
} } ;
2018-07-28 09:32:16 -07:00
2018-08-10 15:12:26 -07:00
const auto & row1 = row_data . at ( UISettings : : values . row_1_text_id ) ;
const auto & row2 = row_data . at ( UISettings : : values . row_2_text_id ) ;
2018-07-28 09:32:16 -07:00
if ( row1 . isEmpty ( ) | | row1 = = row2 )
return row2 ;
if ( row2 . isEmpty ( ) )
return row1 ;
return row1 + " \n " + row2 ;
2015-08-31 21:35:33 -07:00
}
2018-08-10 15:12:26 -07:00
return GameListItem : : data ( role ) ;
2015-08-31 21:35:33 -07:00
}
} ;
2018-08-29 06:42:53 -07:00
class GameListItemCompat : public GameListItem {
Q_DECLARE_TR_FUNCTIONS ( GameListItemCompat )
public :
static const int CompatNumberRole = Qt : : UserRole + 1 ;
GameListItemCompat ( ) = default ;
explicit GameListItemCompat ( const QString & compatiblity ) {
struct CompatStatus {
QString color ;
const char * text ;
const char * tooltip ;
} ;
// clang-format off
static const std : : map < QString , CompatStatus > status_data = {
{ " 0 " , { " #5c93ed " , QT_TR_NOOP ( " Perfect " ) , QT_TR_NOOP ( " Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without \n any workarounds needed. " ) } } ,
{ " 1 " , { " #47d35c " , QT_TR_NOOP ( " Great " ) , QT_TR_NOOP ( " Game functions with minor graphical or audio glitches and is playable from start to finish. May require some \n workarounds. " ) } } ,
{ " 2 " , { " #94b242 " , QT_TR_NOOP ( " Okay " ) , QT_TR_NOOP ( " Game functions with major graphical or audio glitches, but game is playable from start to finish with \n workarounds. " ) } } ,
{ " 3 " , { " #f2d624 " , QT_TR_NOOP ( " Bad " ) , QT_TR_NOOP ( " Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches \n even with workarounds. " ) } } ,
{ " 4 " , { " #FF0000 " , QT_TR_NOOP ( " Intro/Menu " ) , QT_TR_NOOP ( " Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start \n Screen. " ) } } ,
{ " 5 " , { " #828282 " , QT_TR_NOOP ( " Won't Boot " ) , QT_TR_NOOP ( " The game crashes when attempting to startup. " ) } } ,
{ " 99 " , { " #000000 " , QT_TR_NOOP ( " Not Tested " ) , QT_TR_NOOP ( " The game has not yet been tested. " ) } } } ;
// clang-format on
auto iterator = status_data . find ( compatiblity ) ;
if ( iterator = = status_data . end ( ) ) {
LOG_WARNING ( Frontend , " Invalid compatibility number {} " , compatiblity . toStdString ( ) ) ;
return ;
}
CompatStatus status = iterator - > second ;
setData ( compatiblity , CompatNumberRole ) ;
setText ( QObject : : tr ( status . text ) ) ;
setToolTip ( QObject : : tr ( status . tooltip ) ) ;
setData ( CreateCirclePixmapFromColor ( status . color ) , Qt : : DecorationRole ) ;
}
bool operator < ( const QStandardItem & other ) const override {
return data ( CompatNumberRole ) < other . data ( CompatNumberRole ) ;
}
} ;
2015-08-31 21:35:33 -07:00
/**
* A specialization of GameListItem for size values .
* This class ensures that for every numerical size value it holds ( in bytes ) , a correct
* human - readable string representation will be displayed to the user .
*/
class GameListItemSize : public GameListItem {
public :
static const int SizeRole = Qt : : UserRole + 1 ;
2018-08-06 09:58:46 -07:00
GameListItemSize ( ) = default ;
explicit GameListItemSize ( const qulonglong size_bytes ) {
2015-08-31 21:35:33 -07:00
setData ( size_bytes , SizeRole ) ;
}
2016-09-17 17:38:01 -07:00
void setData ( const QVariant & value , int role ) override {
2015-08-31 21:35:33 -07:00
// By specializing setData for SizeRole, we can ensure that the numerical and string
// representations of the data are always accurate and in the correct format.
if ( role = = SizeRole ) {
qulonglong size_bytes = value . toULongLong ( ) ;
GameListItem : : setData ( ReadableByteSize ( size_bytes ) , Qt : : DisplayRole ) ;
GameListItem : : setData ( value , SizeRole ) ;
} else {
GameListItem : : setData ( value , role ) ;
}
}
/**
* This operator is , in practice , only used by the TreeView sorting systems .
2016-09-17 17:38:01 -07:00
* Override it so that it will correctly sort by numerical value instead of by string
* representation .
2015-08-31 21:35:33 -07:00
*/
2016-09-17 17:38:01 -07:00
bool operator < ( const QStandardItem & other ) const override {
2015-08-31 21:35:33 -07:00
return data ( SizeRole ) . toULongLong ( ) < other . data ( SizeRole ) . toULongLong ( ) ;
}
} ;
/**
* Asynchronous worker object for populating the game list .
* Communicates with other threads through Qt ' s signal / slot system .
*/
class GameListWorker : public QObject , public QRunnable {
Q_OBJECT
public :
2018-08-29 06:42:53 -07:00
GameListWorker (
FileSys : : VirtualFilesystem vfs , QString dir_path , bool deep_scan ,
const std : : unordered_map < std : : string , std : : pair < QString , QString > > & compatibility_list )
: vfs ( std : : move ( vfs ) ) , dir_path ( std : : move ( dir_path ) ) , deep_scan ( deep_scan ) ,
compatibility_list ( compatibility_list ) { }
2015-08-31 21:35:33 -07:00
public slots :
/// Starts the processing of directory tree information.
void run ( ) override ;
/// Tells the worker that it should no longer continue processing. Thread-safe.
void Cancel ( ) ;
signals :
/**
* The ` EntryReady ` signal is emitted once an entry has been prepared and is ready
* to be added to the game list .
* @ param entry_items a list with ` QStandardItem ` s that make up the columns of the new entry .
*/
void EntryReady ( QList < QStandardItem * > entry_items ) ;
2017-04-17 19:53:40 -07:00
/**
* After the worker has traversed the game directory looking for entries , this signal is emmited
* with a list of folders that should be watched for changes as well .
*/
void Finished ( QStringList watch_list ) ;
2015-08-31 21:35:33 -07:00
private :
2018-08-03 08:51:48 -07:00
FileSys : : VirtualFilesystem vfs ;
2018-08-11 19:48:27 -07:00
std : : map < u64 , std : : shared_ptr < FileSys : : NCA > > nca_control_map ;
2017-04-17 19:53:40 -07:00
QStringList watch_list ;
2015-08-31 21:35:33 -07:00
QString dir_path ;
bool deep_scan ;
2018-08-29 06:42:53 -07:00
const std : : unordered_map < std : : string , std : : pair < QString , QString > > & compatibility_list ;
2015-08-31 21:35:33 -07:00
std : : atomic_bool stop_processing ;
2018-08-16 14:08:44 -07:00
void AddInstalledTitlesToGameList ( std : : shared_ptr < FileSys : : RegisteredCache > cache ) ;
2018-08-11 19:48:27 -07:00
void FillControlMap ( const std : : string & dir_path ) ;
2015-09-05 23:59:04 -07:00
void AddFstEntriesToGameList ( const std : : string & dir_path , unsigned int recursion = 0 ) ;
2015-08-31 21:35:33 -07:00
} ;