DolphinWX: defer gamelist scanning and switch to single-file cache.

This commit is contained in:
Shawn Hoffman 2017-06-20 16:36:59 -07:00
parent 79961b6f76
commit f16599f4a8
12 changed files with 378 additions and 393 deletions

View File

@ -296,125 +296,3 @@ private:
*ptr += size; *ptr += size;
} }
}; };
// NOTE: this class is only used in DolphinWX/ISOFile.cpp for caching loaded
// ISO data. It will be removed when DolphinWX is, so please don't use it.
class CChunkFileReader
{
public:
// Load file template
template <class T>
static bool Load(const std::string& _rFilename, u32 _Revision, T& _class)
{
INFO_LOG(COMMON, "ChunkReader: Loading %s", _rFilename.c_str());
if (!File::Exists(_rFilename))
return false;
// Check file size
const u64 fileSize = File::GetSize(_rFilename);
static const u64 headerSize = sizeof(SChunkHeader);
if (fileSize < headerSize)
{
ERROR_LOG(COMMON, "ChunkReader: File too small");
return false;
}
File::IOFile pFile(_rFilename, "rb");
if (!pFile)
{
ERROR_LOG(COMMON, "ChunkReader: Can't open file for reading");
return false;
}
// read the header
SChunkHeader header;
if (!pFile.ReadArray(&header, 1))
{
ERROR_LOG(COMMON, "ChunkReader: Bad header size");
return false;
}
// Check revision
if (header.Revision != _Revision)
{
ERROR_LOG(COMMON, "ChunkReader: Wrong file revision, got %d expected %d", header.Revision,
_Revision);
return false;
}
// get size
const u32 sz = (u32)(fileSize - headerSize);
if (header.ExpectedSize != sz)
{
ERROR_LOG(COMMON, "ChunkReader: Bad file size, got %d expected %d", sz, header.ExpectedSize);
return false;
}
// read the state
std::vector<u8> buffer(sz);
if (!pFile.ReadArray(&buffer[0], sz))
{
ERROR_LOG(COMMON, "ChunkReader: Error reading file");
return false;
}
u8* ptr = &buffer[0];
PointerWrap p(&ptr, PointerWrap::MODE_READ);
_class.DoState(p);
INFO_LOG(COMMON, "ChunkReader: Done loading %s", _rFilename.c_str());
return true;
}
// Save file template
template <class T>
static bool Save(const std::string& _rFilename, u32 _Revision, T& _class)
{
INFO_LOG(COMMON, "ChunkReader: Writing %s", _rFilename.c_str());
File::IOFile pFile(_rFilename, "wb");
if (!pFile)
{
ERROR_LOG(COMMON, "ChunkReader: Error opening file for write");
return false;
}
// Get data
u8* ptr = nullptr;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
_class.DoState(p);
size_t const sz = (size_t)ptr;
std::vector<u8> buffer(sz);
ptr = &buffer[0];
p.SetMode(PointerWrap::MODE_WRITE);
_class.DoState(p);
// Create header
SChunkHeader header;
header.Revision = _Revision;
header.ExpectedSize = (u32)sz;
// Write to file
if (!pFile.WriteArray(&header, 1))
{
ERROR_LOG(COMMON, "ChunkReader: Failed writing header");
return false;
}
if (!pFile.WriteArray(&buffer[0], sz))
{
ERROR_LOG(COMMON, "ChunkReader: Failed writing data");
return false;
}
INFO_LOG(COMMON, "ChunkReader: Done writing %s", _rFilename.c_str());
return true;
}
private:
struct SChunkHeader
{
u32 Revision;
u32 ExpectedSize;
};
};

View File

@ -24,21 +24,21 @@
#include "DolphinWX/GameListCtrl.h" #include "DolphinWX/GameListCtrl.h"
#include "DolphinWX/WxUtils.h" #include "DolphinWX/WxUtils.h"
// Sent by child panes to signify that the game list should
// be updated when this modal dialog closes.
wxDEFINE_EVENT(wxDOLPHIN_CFG_REFRESH_LIST, wxCommandEvent); wxDEFINE_EVENT(wxDOLPHIN_CFG_REFRESH_LIST, wxCommandEvent);
wxDEFINE_EVENT(wxDOLPHIN_CFG_RESCAN_LIST, wxCommandEvent);
CConfigMain::CConfigMain(wxWindow* parent, wxWindowID id, const wxString& title, CConfigMain::CConfigMain(wxWindow* parent, wxWindowID id, const wxString& title,
const wxPoint& position, const wxSize& size, long style) const wxPoint& position, const wxSize& size, long style)
: wxDialog(parent, id, title, position, size, style) : wxDialog(parent, id, title, position, size, style)
{ {
// Control refreshing of the ISOs list // Control refreshing of the GameListCtrl
m_refresh_game_list_on_close = false; m_event_on_close = wxEVT_NULL;
Bind(wxEVT_CLOSE_WINDOW, &CConfigMain::OnClose, this); Bind(wxEVT_CLOSE_WINDOW, &CConfigMain::OnClose, this);
Bind(wxEVT_BUTTON, &CConfigMain::OnCloseButton, this, wxID_CLOSE); Bind(wxEVT_BUTTON, &CConfigMain::OnCloseButton, this, wxID_CLOSE);
Bind(wxEVT_SHOW, &CConfigMain::OnShow, this); Bind(wxEVT_SHOW, &CConfigMain::OnShow, this);
Bind(wxDOLPHIN_CFG_REFRESH_LIST, &CConfigMain::OnSetRefreshGameListOnClose, this); Bind(wxDOLPHIN_CFG_REFRESH_LIST, &CConfigMain::OnSetRefreshGameListOnClose, this);
Bind(wxDOLPHIN_CFG_RESCAN_LIST, &CConfigMain::OnSetRescanGameListOnClose, this);
wxDialog::SetExtraStyle(GetExtraStyle() & ~wxWS_EX_BLOCK_EVENTS); wxDialog::SetExtraStyle(GetExtraStyle() & ~wxWS_EX_BLOCK_EVENTS);
@ -115,8 +115,8 @@ void CConfigMain::OnClose(wxCloseEvent& WXUNUSED(event))
SConfig::GetInstance().SaveSettings(); SConfig::GetInstance().SaveSettings();
if (m_refresh_game_list_on_close) if (m_event_on_close != wxEVT_NULL)
AddPendingEvent(wxCommandEvent{DOLPHIN_EVT_RELOAD_GAMELIST}); AddPendingEvent(wxCommandEvent{m_event_on_close});
} }
void CConfigMain::OnShow(wxShowEvent& event) void CConfigMain::OnShow(wxShowEvent& event)
@ -132,5 +132,12 @@ void CConfigMain::OnCloseButton(wxCommandEvent& WXUNUSED(event))
void CConfigMain::OnSetRefreshGameListOnClose(wxCommandEvent& WXUNUSED(event)) void CConfigMain::OnSetRefreshGameListOnClose(wxCommandEvent& WXUNUSED(event))
{ {
m_refresh_game_list_on_close = true; // Don't override a rescan
if (m_event_on_close == wxEVT_NULL)
m_event_on_close = DOLPHIN_EVT_REFRESH_GAMELIST;
}
void CConfigMain::OnSetRescanGameListOnClose(wxCommandEvent& WXUNUSED(event))
{
m_event_on_close = DOLPHIN_EVT_RESCAN_GAMELIST;
} }

View File

@ -10,7 +10,10 @@
class wxNotebook; class wxNotebook;
class wxPanel; class wxPanel;
// Fast refresh - can be fulfilled from cache
wxDECLARE_EVENT(wxDOLPHIN_CFG_REFRESH_LIST, wxCommandEvent); wxDECLARE_EVENT(wxDOLPHIN_CFG_REFRESH_LIST, wxCommandEvent);
// Rescan and refresh - modifies cache
wxDECLARE_EVENT(wxDOLPHIN_CFG_RESCAN_LIST, wxCommandEvent);
class CConfigMain : public wxDialog class CConfigMain : public wxDialog
{ {
@ -41,8 +44,8 @@ private:
void OnCloseButton(wxCommandEvent& event); void OnCloseButton(wxCommandEvent& event);
void OnShow(wxShowEvent& event); void OnShow(wxShowEvent& event);
void OnSetRefreshGameListOnClose(wxCommandEvent& event); void OnSetRefreshGameListOnClose(wxCommandEvent& event);
void OnSetRescanGameListOnClose(wxCommandEvent& event);
wxNotebook* Notebook; wxNotebook* Notebook;
wxEventType m_event_on_close;
bool m_refresh_game_list_on_close;
}; };

View File

@ -163,7 +163,7 @@ void PathConfigPane::OnRecursiveISOCheckBoxChanged(wxCommandEvent& event)
{ {
SConfig::GetInstance().m_RecursiveISOFolder = m_recursive_iso_paths_checkbox->IsChecked(); SConfig::GetInstance().m_RecursiveISOFolder = m_recursive_iso_paths_checkbox->IsChecked();
AddPendingEvent(wxCommandEvent(wxDOLPHIN_CFG_REFRESH_LIST)); AddPendingEvent(wxCommandEvent(wxDOLPHIN_CFG_RESCAN_LIST));
} }
void PathConfigPane::OnAddISOPath(wxCommandEvent& event) void PathConfigPane::OnAddISOPath(wxCommandEvent& event)
@ -179,7 +179,7 @@ void PathConfigPane::OnAddISOPath(wxCommandEvent& event)
} }
else else
{ {
AddPendingEvent(wxCommandEvent(wxDOLPHIN_CFG_REFRESH_LIST)); AddPendingEvent(wxCommandEvent(wxDOLPHIN_CFG_RESCAN_LIST));
m_iso_paths_listbox->Append(dialog.GetPath()); m_iso_paths_listbox->Append(dialog.GetPath());
} }
} }
@ -189,7 +189,7 @@ void PathConfigPane::OnAddISOPath(wxCommandEvent& event)
void PathConfigPane::OnRemoveISOPath(wxCommandEvent& event) void PathConfigPane::OnRemoveISOPath(wxCommandEvent& event)
{ {
AddPendingEvent(wxCommandEvent(wxDOLPHIN_CFG_REFRESH_LIST)); AddPendingEvent(wxCommandEvent(wxDOLPHIN_CFG_RESCAN_LIST));
m_iso_paths_listbox->Delete(m_iso_paths_listbox->GetSelection()); m_iso_paths_listbox->Delete(m_iso_paths_listbox->GetSelection());
// This seems to not be activated on Windows when it should be. wxw bug? // This seems to not be activated on Windows when it should be. wxw bug?

View File

@ -351,8 +351,9 @@ CFrame::CFrame(wxFrame* parent, wxWindowID id, const wxString& title, wxRect geo
// This panel is the parent for rendering and it holds the gamelistctrl // This panel is the parent for rendering and it holds the gamelistctrl
m_panel = new wxPanel(this, IDM_MPANEL, wxDefaultPosition, wxDefaultSize, 0); m_panel = new wxPanel(this, IDM_MPANEL, wxDefaultPosition, wxDefaultSize, 0);
m_game_list_ctrl = new GameListCtrl(m_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_game_list_ctrl =
wxLC_REPORT | wxSUNKEN_BORDER | wxLC_ALIGN_LEFT); new GameListCtrl(m_batch_mode, m_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxLC_REPORT | wxSUNKEN_BORDER | wxLC_ALIGN_LEFT);
m_game_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, &CFrame::OnGameListCtrlItemActivated, this); m_game_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, &CFrame::OnGameListCtrlItemActivated, this);
wxBoxSizer* sizerPanel = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sizerPanel = new wxBoxSizer(wxHORIZONTAL);
@ -490,7 +491,8 @@ void CFrame::BindEvents()
BindMenuBarEvents(); BindMenuBarEvents();
Bind(DOLPHIN_EVT_RELOAD_THEME_BITMAPS, &CFrame::OnReloadThemeBitmaps, this); Bind(DOLPHIN_EVT_RELOAD_THEME_BITMAPS, &CFrame::OnReloadThemeBitmaps, this);
Bind(DOLPHIN_EVT_RELOAD_GAMELIST, &CFrame::OnReloadGameList, this); Bind(DOLPHIN_EVT_REFRESH_GAMELIST, &CFrame::OnRefreshGameList, this);
Bind(DOLPHIN_EVT_RESCAN_GAMELIST, &CFrame::OnRescanGameList, this);
Bind(DOLPHIN_EVT_UPDATE_LOAD_WII_MENU_ITEM, &CFrame::OnUpdateLoadWiiMenuItem, this); Bind(DOLPHIN_EVT_UPDATE_LOAD_WII_MENU_ITEM, &CFrame::OnUpdateLoadWiiMenuItem, this);
Bind(DOLPHIN_EVT_BOOT_SOFTWARE, &CFrame::OnPlay, this); Bind(DOLPHIN_EVT_BOOT_SOFTWARE, &CFrame::OnPlay, this);
Bind(DOLPHIN_EVT_STOP_SOFTWARE, &CFrame::OnStop, this); Bind(DOLPHIN_EVT_STOP_SOFTWARE, &CFrame::OnStop, this);
@ -909,7 +911,7 @@ void CFrame::OnGameListCtrlItemActivated(wxListEvent& WXUNUSED(event))
GetMenuBar()->FindItem(IDM_LIST_WORLD)->Check(true); GetMenuBar()->FindItem(IDM_LIST_WORLD)->Check(true);
GetMenuBar()->FindItem(IDM_LIST_UNKNOWN)->Check(true); GetMenuBar()->FindItem(IDM_LIST_UNKNOWN)->Check(true);
UpdateGameList(); GameListRefresh();
} }
else if (!m_game_list_ctrl->GetISO(0)) else if (!m_game_list_ctrl->GetISO(0))
{ {

View File

@ -98,7 +98,8 @@ public:
void DoStop(); void DoStop();
void UpdateGUI(); void UpdateGUI();
void UpdateGameList(); void GameListRefresh();
void GameListRescan();
void ToggleLogWindow(bool bShow); void ToggleLogWindow(bool bShow);
void ToggleLogConfigWindow(bool bShow); void ToggleLogConfigWindow(bool bShow);
void StatusBarMessage(const char* Text, ...); void StatusBarMessage(const char* Text, ...);
@ -267,7 +268,8 @@ private:
void OnHelp(wxCommandEvent& event); void OnHelp(wxCommandEvent& event);
void OnReloadThemeBitmaps(wxCommandEvent& event); void OnReloadThemeBitmaps(wxCommandEvent& event);
void OnReloadGameList(wxCommandEvent& event); void OnRefreshGameList(wxCommandEvent& event);
void OnRescanGameList(wxCommandEvent& event);
void OnUpdateInterpreterMenuItem(wxUpdateUIEvent& event); void OnUpdateInterpreterMenuItem(wxUpdateUIEvent& event);

View File

@ -774,7 +774,7 @@ void CFrame::OnBootDrive(wxCommandEvent& event)
void CFrame::OnRefresh(wxCommandEvent& WXUNUSED(event)) void CFrame::OnRefresh(wxCommandEvent& WXUNUSED(event))
{ {
UpdateGameList(); GameListRescan();
} }
void CFrame::OnScreenshot(wxCommandEvent& WXUNUSED(event)) void CFrame::OnScreenshot(wxCommandEvent& WXUNUSED(event))
@ -1097,12 +1097,17 @@ void CFrame::OnReloadThemeBitmaps(wxCommandEvent& WXUNUSED(event))
reload_event.SetEventObject(this); reload_event.SetEventObject(this);
wxPostEvent(GetToolBar(), reload_event); wxPostEvent(GetToolBar(), reload_event);
UpdateGameList(); GameListRefresh();
} }
void CFrame::OnReloadGameList(wxCommandEvent& WXUNUSED(event)) void CFrame::OnRefreshGameList(wxCommandEvent& WXUNUSED(event))
{ {
UpdateGameList(); GameListRefresh();
}
void CFrame::OnRescanGameList(wxCommandEvent& WXUNUSED(event))
{
GameListRescan();
} }
void CFrame::OnUpdateInterpreterMenuItem(wxUpdateUIEvent& event) void CFrame::OnUpdateInterpreterMenuItem(wxUpdateUIEvent& event)
@ -1605,9 +1610,16 @@ void CFrame::UpdateGUI()
} }
} }
void CFrame::UpdateGameList() void CFrame::GameListRefresh()
{ {
wxCommandEvent event{DOLPHIN_EVT_RELOAD_GAMELIST, GetId()}; wxCommandEvent event{DOLPHIN_EVT_REFRESH_GAMELIST, GetId()};
event.SetEventObject(this);
wxPostEvent(m_game_list_ctrl, event);
}
void CFrame::GameListRescan()
{
wxCommandEvent event{DOLPHIN_EVT_RESCAN_GAMELIST, GetId()};
event.SetEventObject(this); event.SetEventObject(this);
wxPostEvent(m_game_list_ctrl, event); wxPostEvent(m_game_list_ctrl, event);
} }
@ -1674,17 +1686,19 @@ void CFrame::GameListChanged(wxCommandEvent& event)
SConfig::GetInstance().m_ListDrives = event.IsChecked(); SConfig::GetInstance().m_ListDrives = event.IsChecked();
break; break;
case IDM_PURGE_GAME_LIST_CACHE: case IDM_PURGE_GAME_LIST_CACHE:
std::vector<std::string> rFilenames = std::vector<std::string> filenames =
Common::DoFileSearch({".cache"}, {File::GetUserPath(D_CACHE_IDX)}); Common::DoFileSearch({".cache"}, {File::GetUserPath(D_CACHE_IDX)});
for (const std::string& rFilename : rFilenames) for (const std::string& filename : filenames)
{ {
File::Delete(rFilename); File::Delete(filename);
} }
break; // Do rescan after cache has been cleared
GameListRescan();
return;
} }
UpdateGameList(); GameListRefresh();
} }
// Enable and disable the toolbar // Enable and disable the toolbar
@ -1743,6 +1757,6 @@ void CFrame::OnChangeColumnsVisible(wxCommandEvent& event)
default: default:
return; return;
} }
UpdateGameList(); GameListRefresh();
SConfig::GetInstance().SaveSettings(); SConfig::GetInstance().SaveSettings();
} }

View File

@ -44,6 +44,7 @@
#include "Common/MathUtil.h" #include "Common/MathUtil.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Common/SysConf.h" #include "Common/SysConf.h"
#include "Common/Thread.h"
#include "Core/Boot/Boot.h" #include "Core/Boot/Boot.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
@ -155,36 +156,6 @@ static int CompareGameListItems(const GameListItem* iso1, const GameListItem* is
return 0; return 0;
} }
static std::vector<std::string> GetFileSearchExtensions()
{
std::vector<std::string> extensions;
if (SConfig::GetInstance().m_ListGC)
{
extensions.push_back(".gcm");
extensions.push_back(".tgc");
}
if (SConfig::GetInstance().m_ListWii || SConfig::GetInstance().m_ListGC)
{
extensions.push_back(".iso");
extensions.push_back(".ciso");
extensions.push_back(".gcz");
extensions.push_back(".wbfs");
}
if (SConfig::GetInstance().m_ListWad)
extensions.push_back(".wad");
if (SConfig::GetInstance().m_ListElfDol)
{
extensions.push_back(".dol");
extensions.push_back(".elf");
}
return extensions;
}
static bool ShouldDisplayGameListItem(const GameListItem& item) static bool ShouldDisplayGameListItem(const GameListItem& item)
{ {
const bool show_platform = [&item] { const bool show_platform = [&item] {
@ -240,7 +211,8 @@ static bool ShouldDisplayGameListItem(const GameListItem& item)
} }
} }
wxDEFINE_EVENT(DOLPHIN_EVT_RELOAD_GAMELIST, wxCommandEvent); wxDEFINE_EVENT(DOLPHIN_EVT_REFRESH_GAMELIST, wxCommandEvent);
wxDEFINE_EVENT(DOLPHIN_EVT_RESCAN_GAMELIST, wxCommandEvent);
struct GameListCtrl::ColumnInfo struct GameListCtrl::ColumnInfo
{ {
@ -250,8 +222,8 @@ struct GameListCtrl::ColumnInfo
bool& visible; bool& visible;
}; };
GameListCtrl::GameListCtrl(wxWindow* parent, const wxWindowID id, const wxPoint& pos, GameListCtrl::GameListCtrl(bool disable_scanning, wxWindow* parent, const wxWindowID id,
const wxSize& size, long style) const wxPoint& pos, const wxSize& size, long style)
: wxListCtrl(parent, id, pos, size, style), m_tooltip(nullptr), : wxListCtrl(parent, id, pos, size, style), m_tooltip(nullptr),
m_columns({// {COLUMN, {default_width (without platform padding), resizability, visibility}} m_columns({// {COLUMN, {default_width (without platform padding), resizability, visibility}}
{COLUMN_PLATFORM, 32 + 1 /* icon padding */, false, {COLUMN_PLATFORM, 32 + 1 /* icon padding */, false,
@ -286,9 +258,57 @@ GameListCtrl::GameListCtrl(wxWindow* parent, const wxWindowID id, const wxPoint&
Bind(wxEVT_MENU, &GameListCtrl::OnChangeDisc, this, IDM_LIST_CHANGE_DISC); Bind(wxEVT_MENU, &GameListCtrl::OnChangeDisc, this, IDM_LIST_CHANGE_DISC);
Bind(wxEVT_MENU, &GameListCtrl::OnNetPlayHost, this, IDM_START_NETPLAY); Bind(wxEVT_MENU, &GameListCtrl::OnNetPlayHost, this, IDM_START_NETPLAY);
Bind(DOLPHIN_EVT_RELOAD_GAMELIST, &GameListCtrl::OnReloadGameList, this); Bind(DOLPHIN_EVT_REFRESH_GAMELIST, &GameListCtrl::OnRefreshGameList, this);
Bind(DOLPHIN_EVT_RESCAN_GAMELIST, &GameListCtrl::OnRescanGameList, this);
wxTheApp->Bind(DOLPHIN_EVT_LOCAL_INI_CHANGED, &GameListCtrl::OnLocalIniModified, this); wxTheApp->Bind(DOLPHIN_EVT_LOCAL_INI_CHANGED, &GameListCtrl::OnLocalIniModified, this);
if (!disable_scanning)
{
m_scan_thread = std::thread([&] {
Common::SetCurrentThreadName("gamelist scanner");
if (SyncCacheFile(false))
{
// Account for changes outside the cache (just wii banners atm; could scan Ini here)
// Note this is on the initial load path. Further improvements could be made if updates
// from scan->ui threads described small changelists instead of updating the entire list
// at once.
bool cache_modified = false;
for (auto& file : m_cached_files)
{
if (file->ReloadBannerIfNeeded())
cache_modified = true;
}
QueueEvent(new wxCommandEvent(DOLPHIN_EVT_REFRESH_GAMELIST));
if (cache_modified)
SyncCacheFile(true);
}
else
{
RescanList();
}
m_scan_trigger.Wait();
while (!m_scan_exiting.IsSet())
{
RescanList();
m_scan_trigger.Wait();
}
});
}
}
GameListCtrl::~GameListCtrl()
{
if (m_scan_thread.joinable())
{
m_scan_exiting.Set();
m_scan_trigger.Set();
m_scan_thread.join();
}
} }
template <typename T> template <typename T>
@ -345,8 +365,8 @@ void GameListCtrl::InitBitmaps()
auto& platform_indexes = m_image_indexes.platform; auto& platform_indexes = m_image_indexes.platform;
platform_indexes.resize(static_cast<size_t>(DiscIO::Platform::NUMBER_OF_PLATFORMS)); platform_indexes.resize(static_cast<size_t>(DiscIO::Platform::NUMBER_OF_PLATFORMS));
InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, DiscIO::Platform::GAMECUBE_DISC,
DiscIO::Platform::GAMECUBE_DISC, "Platform_Gamecube"); "Platform_Gamecube");
InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, DiscIO::Platform::WII_DISC, InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, DiscIO::Platform::WII_DISC,
"Platform_Wii"); "Platform_Wii");
InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, DiscIO::Platform::WII_WAD, InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, DiscIO::Platform::WII_WAD,
@ -388,24 +408,42 @@ void GameListCtrl::BrowseForDirectory()
{ {
SConfig::GetInstance().m_ISOFolder.push_back(sPath); SConfig::GetInstance().m_ISOFolder.push_back(sPath);
SConfig::GetInstance().SaveSettings(); SConfig::GetInstance().SaveSettings();
ReloadList(); m_scan_trigger.Set();
} }
} }
} }
void GameListCtrl::ReloadList() void GameListCtrl::RefreshList()
{ {
int scrollPos = wxWindow::GetScrollPos(wxVERTICAL); int scrollPos = wxWindow::GetScrollPos(wxVERTICAL);
// Don't let the user refresh it while a game is running // Don't let the user refresh it while a game is running
if (Core::GetState() != Core::State::Uninitialized) if (Core::GetState() != Core::State::Uninitialized)
return; return;
ScanForISOs(); m_shown_files.clear();
for (auto& item : m_cached_files)
{
if (ShouldDisplayGameListItem(*item))
m_shown_files.push_back(item);
}
// Drives are not cached. Not sure if this is required, but better to err on the
// side of caution if cross-platform issues could come into play.
if (SConfig::GetInstance().m_ListDrives)
{
const Core::TitleDatabase title_database;
for (const auto& drive : cdio_get_devices())
{
auto file = std::make_shared<GameListItem>(drive, title_database);
if (file->IsValid())
m_shown_files.push_back(file);
}
}
Freeze(); Freeze();
ClearAll(); ClearAll();
if (m_ISOFiles.size() != 0) if (!m_shown_files.empty())
{ {
// Don't load bitmaps unless there are games to list // Don't load bitmaps unless there are games to list
InitBitmaps(); InitBitmaps();
@ -430,14 +468,13 @@ void GameListCtrl::ReloadList()
#endif #endif
// set initial sizes for columns // set initial sizes for columns
SetColumnWidth(COLUMN_DUMMY, 0); SetColumnWidth(COLUMN_DUMMY, 0);
for (const auto& c : m_columns) for (const auto& c : m_columns)
{ {
SetColumnWidth(c.id, c.visible ? FromDIP(c.default_width + platform_padding) : 0); SetColumnWidth(c.id, c.visible ? FromDIP(c.default_width + platform_padding) : 0);
} }
// add all items // add all items
for (int i = 0; i < (int)m_ISOFiles.size(); i++) for (int i = 0; i < (int)m_shown_files.size(); i++)
InsertItemInReportView(i); InsertItemInReportView(i);
SetColors(); SetColors();
@ -511,7 +548,7 @@ static wxString NiceSizeFormat(u64 size)
return wxString::Format("%.2f %s", size / unit_size, unit_symbols[unit]); return wxString::Format("%.2f %s", size / unit_size, unit_symbols[unit]);
} }
// Update the column content of the item at _Index // Update the column content of the item at index
void GameListCtrl::UpdateItemAtColumn(long index, int column) void GameListCtrl::UpdateItemAtColumn(long index, int column)
{ {
const auto& iso_file = *GetISO(GetItemData(index)); const auto& iso_file = *GetISO(GetItemData(index));
@ -521,7 +558,7 @@ void GameListCtrl::UpdateItemAtColumn(long index, int column)
case COLUMN_PLATFORM: case COLUMN_PLATFORM:
{ {
SetItemColumnImage(index, COLUMN_PLATFORM, SetItemColumnImage(index, COLUMN_PLATFORM,
m_image_indexes.platform[static_cast<size_t>(iso_file.GetPlatform())]); m_image_indexes.platform[static_cast<size_t>(iso_file.GetPlatform())]);
break; break;
} }
case COLUMN_BANNER: case COLUMN_BANNER:
@ -565,7 +602,7 @@ void GameListCtrl::UpdateItemAtColumn(long index, int column)
break; break;
case COLUMN_COUNTRY: case COLUMN_COUNTRY:
SetItemColumnImage(index, COLUMN_COUNTRY, SetItemColumnImage(index, COLUMN_COUNTRY,
m_image_indexes.flag[static_cast<size_t>(iso_file.GetCountry())]); m_image_indexes.flag[static_cast<size_t>(iso_file.GetCountry())]);
break; break;
case COLUMN_SIZE: case COLUMN_SIZE:
SetItem(index, COLUMN_SIZE, NiceSizeFormat(iso_file.GetFileSize()), -1); SetItem(index, COLUMN_SIZE, NiceSizeFormat(iso_file.GetFileSize()), -1);
@ -631,96 +668,148 @@ void GameListCtrl::SetColors()
} }
} }
void GameListCtrl::ScanForISOs() void GameListCtrl::DoState(PointerWrap* p, u32 size)
{ {
m_ISOFiles.clear(); struct
const Core::TitleDatabase title_database;
auto rFilenames =
Common::DoFileSearch(GetFileSearchExtensions(), SConfig::GetInstance().m_ISOFolder,
SConfig::GetInstance().m_RecursiveISOFolder);
if (rFilenames.size() > 0)
{ {
wxProgressDialog dialog( u32 Revision;
_("Scanning for ISOs"), _("Scanning..."), (int)rFilenames.size() - 1, this, u32 ExpectedSize;
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | } header = {CACHE_REVISION, size};
wxPD_REMAINING_TIME | wxPD_SMOOTH // - makes updates as small as possible (down to 1px) p->Do(header);
); if (p->GetMode() == PointerWrap::MODE_READ)
{
for (u32 i = 0; i < rFilenames.size(); i++) if (header.Revision != CACHE_REVISION || header.ExpectedSize != size)
{ {
std::string FileName; p->SetMode(PointerWrap::MODE_MEASURE);
SplitPath(rFilenames[i], nullptr, &FileName, nullptr); return;
// Update with the progress (i) and the message
dialog.Update(i, wxString::Format(_("Scanning %s"), StrToWxStr(FileName)));
if (dialog.WasCancelled())
break;
auto iso_file = std::make_unique<GameListItem>(rFilenames[i], title_database);
if (iso_file->IsValid() && ShouldDisplayGameListItem(*iso_file))
{
m_ISOFiles.push_back(std::move(iso_file));
}
} }
} }
p->DoEachElement(m_cached_files, [](PointerWrap& state, std::shared_ptr<GameListItem>& elem) {
if (SConfig::GetInstance().m_ListDrives) if (state.GetMode() == PointerWrap::MODE_READ)
{
const std::vector<std::string> drives = cdio_get_devices();
for (const auto& drive : drives)
{ {
auto gli = std::make_unique<GameListItem>(drive, title_database); elem = std::make_shared<GameListItem>();
if (gli->IsValid())
m_ISOFiles.push_back(std::move(gli));
} }
} elem->DoState(state);
});
std::sort(m_ISOFiles.begin(), m_ISOFiles.end());
} }
void GameListCtrl::OnReloadGameList(wxCommandEvent& WXUNUSED(event)) bool GameListCtrl::SyncCacheFile(bool write)
{ {
ReloadList(); std::string filename(File::GetUserPath(D_CACHE_IDX) + "wx_gamelist.cache");
const char* open_mode = write ? "wb" : "rb";
File::IOFile f(filename, open_mode);
if (!f)
return false;
bool success = false;
if (write)
{
// Measure the size of the buffer.
u8* ptr = nullptr;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
DoState(&p);
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
// Then actually do the write.
std::vector<u8> buffer(buffer_size);
ptr = &buffer[0];
p.SetMode(PointerWrap::MODE_WRITE);
DoState(&p, buffer_size);
if (f.WriteBytes(buffer.data(), buffer.size()))
success = true;
}
else
{
std::vector<u8> buffer(f.GetSize());
if (buffer.size() && f.ReadBytes(buffer.data(), buffer.size()))
{
u8* ptr = buffer.data();
PointerWrap p(&ptr, PointerWrap::MODE_READ);
DoState(&p, buffer.size());
if (p.GetMode() == PointerWrap::MODE_READ)
success = true;
}
}
if (!success)
{
// If some file operation failed, try to delete the probably-corrupted cache
f.Close();
File::Delete(filename);
}
return success;
}
void GameListCtrl::RescanList()
{
const std::vector<std::string> search_extensions = {".gcm", ".tgc", ".iso", ".ciso", ".gcz",
".wbfs", ".wad", ".dol", ".elf"};
// TODO This could process paths iteratively as they are found
auto search_results = Common::DoFileSearch(search_extensions, SConfig::GetInstance().m_ISOFolder,
SConfig::GetInstance().m_RecursiveISOFolder);
// TODO rethink some good algorithms to use here
std::vector<std::string> cached_paths;
for (const auto& file : m_cached_files)
cached_paths.emplace_back(file->GetFileName());
std::sort(cached_paths.begin(), cached_paths.end());
std::list<std::string> removed_paths;
std::set_difference(cached_paths.cbegin(), cached_paths.cend(), search_results.cbegin(),
search_results.cend(), std::back_inserter(removed_paths));
std::vector<std::string> new_paths;
std::set_difference(search_results.cbegin(), search_results.cend(), cached_paths.cbegin(),
cached_paths.cend(), std::back_inserter(new_paths));
const Core::TitleDatabase title_database;
// TODO we could store all paths and modification times to judge if file needs to be rescanned.
// If we cached paths that turned out to be invalid, this would save failing on them each time
// refresh is done.
// However if people e.g. set dolphin to recursively scan the root of their drive(s), then we
// would cache way too much data. Maybe just use an upper bound of invalid paths to cache?
// For now, only scan new_paths. This could cause false negatives (file actively being written),
// but otherwise
// should be fine.
for (const auto& path : removed_paths)
{
auto it = std::find_if(
m_cached_files.cbegin(), m_cached_files.cend(),
[&path](const std::shared_ptr<GameListItem>& file) { return file->GetFileName() == path; });
if (it != m_cached_files.cend())
m_cached_files.erase(it);
}
for (const auto& path : new_paths)
{
auto file = std::make_shared<GameListItem>(path, title_database);
if (file->IsValid())
m_cached_files.push_back(std::move(file));
}
for (auto& file : m_cached_files)
file->ReloadINI();
// Post UI event to update the displayed list
QueueEvent(new wxCommandEvent(DOLPHIN_EVT_REFRESH_GAMELIST));
SyncCacheFile(true);
}
void GameListCtrl::OnRefreshGameList(wxCommandEvent& WXUNUSED(event))
{
RefreshList();
}
void GameListCtrl::OnRescanGameList(wxCommandEvent& WXUNUSED(event))
{
m_scan_trigger.Set();
} }
void GameListCtrl::OnLocalIniModified(wxCommandEvent& ev) void GameListCtrl::OnLocalIniModified(wxCommandEvent& ev)
{ {
ev.Skip(); ev.Skip();
std::string game_id = WxStrToStr(ev.GetString()); // We need show any changes to the ini which could impact our columns. Currently only the
// NOTE: The same game may occur multiple times if there are multiple // EmuState/Issues settings can do that. We also need to persist the changes to the cache - so
// physical copies in the search paths. // just trigger a rescan which will sync the cache and then display the new values.
for (std::size_t i = 0; i < m_ISOFiles.size(); ++i) m_scan_trigger.Set();
{
if (m_ISOFiles[i]->GetGameID() != game_id)
continue;
m_ISOFiles[i]->ReloadINI();
// The indexes in m_ISOFiles and the list do not line up.
// We need to find the corresponding item in the list (if it exists)
long item_id = 0;
for (; item_id < GetItemCount(); ++item_id)
{
if (i == static_cast<std::size_t>(GetItemData(item_id)))
break;
}
// If the item is not currently being displayed then we're done.
if (item_id == GetItemCount())
continue;
// Update all the columns
for (int j = FIRST_COLUMN_WITH_CONTENT; j < NUMBER_OF_COLUMN; ++j)
{
// NOTE: Banner is not modified by the INI and updating it will
// duplicate it in memory which is not wanted.
if (j != COLUMN_BANNER && GetColumnWidth(j) != 0)
UpdateItemAtColumn(item_id, j);
}
}
} }
void GameListCtrl::OnColBeginDrag(wxListEvent& event) void GameListCtrl::OnColBeginDrag(wxListEvent& event)
@ -733,8 +822,8 @@ void GameListCtrl::OnColBeginDrag(wxListEvent& event)
const GameListItem* GameListCtrl::GetISO(size_t index) const const GameListItem* GameListCtrl::GetISO(size_t index) const
{ {
if (index < m_ISOFiles.size()) if (index < m_shown_files.size())
return m_ISOFiles[index].get(); return m_shown_files[index].get();
return nullptr; return nullptr;
} }
@ -790,7 +879,7 @@ void GameListCtrl::OnKeyPress(wxListEvent& event)
static int lastKey = 0, sLoop = 0; static int lastKey = 0, sLoop = 0;
int Loop = 0; int Loop = 0;
for (int i = 0; i < (int)m_ISOFiles.size(); i++) for (int i = 0; i < (int)m_shown_files.size(); i++)
{ {
// Easy way to get game string // Easy way to get game string
wxListItem bleh; wxListItem bleh;
@ -806,7 +895,7 @@ void GameListCtrl::OnKeyPress(wxListEvent& event)
if (lastKey == event.GetKeyCode() && Loop < sLoop) if (lastKey == event.GetKeyCode() && Loop < sLoop)
{ {
Loop++; Loop++;
if (i + 1 == (int)m_ISOFiles.size()) if (i + 1 == (int)m_shown_files.size())
i = -1; i = -1;
continue; continue;
} }
@ -827,7 +916,7 @@ void GameListCtrl::OnKeyPress(wxListEvent& event)
// If we get past the last game in the list, // If we get past the last game in the list,
// we'll have to go back to the first one. // we'll have to go back to the first one.
if (i + 1 == (int)m_ISOFiles.size() && sLoop > 0 && Loop > 0) if (i + 1 == (int)m_shown_files.size() && sLoop > 0 && Loop > 0)
i = -1; i = -1;
} }
@ -900,7 +989,7 @@ void GameListCtrl::OnMouseMotion(wxMouseEvent& event)
// Convert to screen coordinates // Convert to screen coordinates
ClientToScreen(&mx, &my); ClientToScreen(&mx, &my);
m_tooltip->SetBoundingRect(wxRect(mx - GetColumnWidth(COLUMN_EMULATION_STATE), my, m_tooltip->SetBoundingRect(wxRect(mx - GetColumnWidth(COLUMN_EMULATION_STATE), my,
GetColumnWidth(COLUMN_EMULATION_STATE), Rect.GetHeight())); GetColumnWidth(COLUMN_EMULATION_STATE), Rect.GetHeight()));
m_tooltip->SetPosition( m_tooltip->SetPosition(
wxPoint(mx - GetColumnWidth(COLUMN_EMULATION_STATE), my - 5 + Rect.GetHeight())); wxPoint(mx - GetColumnWidth(COLUMN_EMULATION_STATE), my - 5 + Rect.GetHeight()));
lastItem = item; lastItem = item;
@ -1044,7 +1133,7 @@ void GameListCtrl::OnRightClick(wxMouseEvent& event)
const GameListItem* GameListCtrl::GetSelectedISO() const const GameListItem* GameListCtrl::GetSelectedISO() const
{ {
if (m_ISOFiles.empty()) if (m_shown_files.empty())
return nullptr; return nullptr;
if (GetSelectedItemCount() == 0) if (GetSelectedItemCount() == 0)
@ -1143,7 +1232,7 @@ void GameListCtrl::OnDeleteISO(wxCommandEvent& WXUNUSED(event))
{ {
for (const GameListItem* iso : GetAllSelectedISOs()) for (const GameListItem* iso : GetAllSelectedISOs())
File::Delete(iso->GetFileName()); File::Delete(iso->GetFileName());
ReloadList(); m_scan_trigger.Set();
} }
} }
@ -1315,7 +1404,7 @@ void GameListCtrl::CompressSelection(bool _compress)
if (!all_good) if (!all_good)
WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action.")); WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action."));
ReloadList(); m_scan_trigger.Set();
} }
bool GameListCtrl::CompressCB(const std::string& text, float percent, void* arg) bool GameListCtrl::CompressCB(const std::string& text, float percent, void* arg)
@ -1388,7 +1477,7 @@ void GameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED(event))
if (!all_good) if (!all_good)
WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action.")); WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action."));
ReloadList(); m_scan_trigger.Set();
} }
void GameListCtrl::OnChangeDisc(wxCommandEvent& WXUNUSED(event)) void GameListCtrl::OnChangeDisc(wxCommandEvent& WXUNUSED(event))

View File

@ -12,6 +12,8 @@
#include <wx/listctrl.h> #include <wx/listctrl.h>
#include <wx/tipwin.h> #include <wx/tipwin.h>
#include "Common/ChunkFile.h"
#include "Common/Event.h"
#include "DolphinWX/ISOFile.h" #include "DolphinWX/ISOFile.h"
class wxEmuStateTip : public wxTipWindow class wxEmuStateTip : public wxTipWindow
@ -31,19 +33,19 @@ public:
} }
}; };
wxDECLARE_EVENT(DOLPHIN_EVT_RELOAD_GAMELIST, wxCommandEvent); wxDECLARE_EVENT(DOLPHIN_EVT_REFRESH_GAMELIST, wxCommandEvent);
wxDECLARE_EVENT(DOLPHIN_EVT_RESCAN_GAMELIST, wxCommandEvent);
class GameListCtrl : public wxListCtrl class GameListCtrl : public wxListCtrl
{ {
public: public:
GameListCtrl(wxWindow* parent, const wxWindowID id, const wxPoint& pos, const wxSize& size, GameListCtrl(bool disable_scanning, wxWindow* parent, const wxWindowID id, const wxPoint& pos,
long style); const wxSize& size, long style);
~GameListCtrl() = default; ~GameListCtrl();
void BrowseForDirectory(); void BrowseForDirectory();
const GameListItem* GetISO(size_t index) const; const GameListItem* GetISO(size_t index) const;
const GameListItem* GetSelectedISO() const; const GameListItem* GetSelectedISO() const;
std::vector<const GameListItem*> GetAllSelectedISOs() const;
static bool IsHidingItems(); static bool IsHidingItems();
@ -70,16 +72,19 @@ public:
private: private:
struct ColumnInfo; struct ColumnInfo;
void ReloadList();
void InitBitmaps(); void InitBitmaps();
void UpdateItemAtColumn(long index, int column); void UpdateItemAtColumn(long index, int column);
void InsertItemInReportView(long index); void InsertItemInReportView(long index);
void SetColors(); void SetColors();
void ScanForISOs(); void RefreshList();
void RescanList();
void DoState(PointerWrap* p, u32 size = 0);
bool SyncCacheFile(bool write);
std::vector<const GameListItem*> GetAllSelectedISOs() const;
// events // events
void OnReloadGameList(wxCommandEvent& event); void OnRefreshGameList(wxCommandEvent& event);
void OnRescanGameList(wxCommandEvent& event);
void OnLeftClick(wxMouseEvent& event); void OnLeftClick(wxMouseEvent& event);
void OnRightClick(wxMouseEvent& event); void OnRightClick(wxMouseEvent& event);
void OnMouseMotion(wxMouseEvent& event); void OnMouseMotion(wxMouseEvent& event);
@ -109,14 +114,23 @@ private:
static bool MultiCompressCB(const std::string& text, float percent, void* arg); static bool MultiCompressCB(const std::string& text, float percent, void* arg);
static bool WiiCompressWarning(); static bool WiiCompressWarning();
std::vector<std::unique_ptr<GameListItem>> m_ISOFiles; struct
struct { {
std::vector<int> flag; std::vector<int> flag;
std::vector<int> platform; std::vector<int> platform;
std::vector<int> utility_banner; std::vector<int> utility_banner;
std::vector<int> emu_state; std::vector<int> emu_state;
} m_image_indexes; } m_image_indexes;
// Actual backing GameListItems are maintained in a background thread and cached to file
static constexpr u32 CACHE_REVISION = 0;
std::list<std::shared_ptr<GameListItem>> m_cached_files;
std::thread m_scan_thread;
Common::Event m_scan_trigger;
Common::Flag m_scan_exiting;
// UI thread's view into the cache
std::vector<std::shared_ptr<GameListItem>> m_shown_files;
int m_last_column; int m_last_column;
int m_last_sort; int m_last_sort;
wxSize m_lastpos; wxSize m_lastpos;

View File

@ -38,8 +38,6 @@
#include "DolphinWX/ISOFile.h" #include "DolphinWX/ISOFile.h"
#include "DolphinWX/WxUtils.h" #include "DolphinWX/WxUtils.h"
static const u32 CACHE_REVISION = 0x129; // Last changed in PR 5102
static std::string GetLanguageString(DiscIO::Language language, static std::string GetLanguageString(DiscIO::Language language,
std::map<DiscIO::Language, std::string> strings) std::map<DiscIO::Language, std::string> strings)
{ {
@ -69,26 +67,8 @@ GameListItem::GameListItem(const std::string& _rFileName, const Core::TitleDatab
m_Revision(0), m_Valid(false), m_ImageWidth(0), m_ImageHeight(0), m_disc_number(0), m_Revision(0), m_Valid(false), m_ImageWidth(0), m_ImageHeight(0), m_disc_number(0),
m_has_custom_name(false) m_has_custom_name(false)
{ {
if (LoadFromCache())
{
m_Valid = true;
// Wii banners can only be read if there is a savefile,
// so sometimes caches don't contain banners. Let's check
// if a banner has become available after the cache was made.
if (m_pImage.empty())
{
std::vector<u32> buffer =
DiscIO::Volume::GetWiiBanner(&m_ImageWidth, &m_ImageHeight, m_title_id);
ReadVolumeBanner(buffer, m_ImageWidth, m_ImageHeight);
if (!m_pImage.empty())
SaveToCache();
}
}
else
{ {
std::unique_ptr<DiscIO::Volume> volume(DiscIO::CreateVolumeFromFilename(_rFileName)); std::unique_ptr<DiscIO::Volume> volume(DiscIO::CreateVolumeFromFilename(_rFileName));
if (volume != nullptr) if (volume != nullptr)
{ {
m_Platform = volume->GetVolumeType(); m_Platform = volume->GetVolumeType();
@ -116,7 +96,6 @@ GameListItem::GameListItem(const std::string& _rFileName, const Core::TitleDatab
ReadVolumeBanner(buffer, m_ImageWidth, m_ImageHeight); ReadVolumeBanner(buffer, m_ImageWidth, m_ImageHeight);
m_Valid = true; m_Valid = true;
SaveToCache();
} }
} }
@ -138,37 +117,28 @@ GameListItem::GameListItem(const std::string& _rFileName, const Core::TitleDatab
m_FileSize = File::GetSize(_rFileName); m_FileSize = File::GetSize(_rFileName);
m_Platform = DiscIO::Platform::ELF_DOL; m_Platform = DiscIO::Platform::ELF_DOL;
m_blob_type = DiscIO::BlobType::DIRECTORY; m_blob_type = DiscIO::BlobType::DIRECTORY;
std::string path, name;
SplitPath(m_FileName, &path, &name, nullptr);
// A bit like the Homebrew Channel icon, except there can be multiple files
// in a folder with their own icons. Useful for those who don't want to have
// a Homebrew Channel-style folder structure.
if (SetWxBannerFromPngFile(path + name + ".png"))
return;
// Homebrew Channel icon. Typical for DOLs and ELFs,
// but can be also used with volumes.
if (SetWxBannerFromPngFile(path + "icon.png"))
return;
} }
else
std::string path, name;
SplitPath(m_FileName, &path, &name, nullptr);
// A bit like the Homebrew Channel icon, except there can be multiple files
// in a folder with their own icons. Useful for those who don't want to have
// a Homebrew Channel-style folder structure.
if (ReadPNGBanner(path + name + ".png"))
return;
// Homebrew Channel icon. Typical for DOLs and ELFs,
// but can be also used with volumes.
if (ReadPNGBanner(path + "icon.png"))
return;
// Volume banner. Typical for everything that isn't a DOL or ELF.
if (!m_pImage.empty())
{ {
// Need to make explicit copy as wxImage uses reference counting for copies combined with only // Volume banner. Typical for everything that isn't a DOL or ELF.
// taking a pointer, not the content, when given a buffer to its constructor. SetWxBannerFromRaw();
m_image.Create(m_ImageWidth, m_ImageHeight, false);
std::memcpy(m_image.GetData(), m_pImage.data(), m_pImage.size());
return;
} }
} }
GameListItem::~GameListItem()
{
}
bool GameListItem::IsValid() const bool GameListItem::IsValid() const
{ {
if (!m_Valid) if (!m_Valid)
@ -198,37 +168,35 @@ void GameListItem::ReloadINI()
} }
} }
bool GameListItem::LoadFromCache()
{
return CChunkFileReader::Load<GameListItem>(CreateCacheFilename(), CACHE_REVISION, *this);
}
void GameListItem::SaveToCache()
{
if (!File::IsDirectory(File::GetUserPath(D_CACHE_IDX)))
File::CreateDir(File::GetUserPath(D_CACHE_IDX));
CChunkFileReader::Save<GameListItem>(CreateCacheFilename(), CACHE_REVISION, *this);
}
void GameListItem::DoState(PointerWrap& p) void GameListItem::DoState(PointerWrap& p)
{ {
p.Do(m_FileName);
p.Do(m_names); p.Do(m_names);
p.Do(m_descriptions); p.Do(m_descriptions);
p.Do(m_company); p.Do(m_company);
p.Do(m_game_id); p.Do(m_game_id);
p.Do(m_title_id); p.Do(m_title_id);
p.Do(m_issues);
p.Do(m_emu_state);
p.Do(m_FileSize); p.Do(m_FileSize);
p.Do(m_VolumeSize); p.Do(m_VolumeSize);
p.Do(m_region); p.Do(m_region);
p.Do(m_Country); p.Do(m_Country);
p.Do(m_Platform);
p.Do(m_blob_type); p.Do(m_blob_type);
p.Do(m_Revision);
p.Do(m_pImage); p.Do(m_pImage);
p.Do(m_Valid);
p.Do(m_ImageWidth); p.Do(m_ImageWidth);
p.Do(m_ImageHeight); p.Do(m_ImageHeight);
p.Do(m_Platform);
p.Do(m_disc_number); p.Do(m_disc_number);
p.Do(m_Revision); p.Do(m_custom_name_titles_txt);
p.Do(m_custom_name);
p.Do(m_has_custom_name);
if (p.GetMode() == PointerWrap::MODE_READ)
{
SetWxBannerFromRaw();
}
} }
bool GameListItem::IsElfOrDol() const bool GameListItem::IsElfOrDol() const
@ -241,27 +209,6 @@ bool GameListItem::IsElfOrDol() const
return name_end == ".elf" || name_end == ".dol"; return name_end == ".elf" || name_end == ".dol";
} }
std::string GameListItem::CreateCacheFilename() const
{
std::string Filename, LegalPathname, extension;
SplitPath(m_FileName, &LegalPathname, &Filename, &extension);
if (Filename.empty())
return Filename; // Disc Drive
// Filename.extension_HashOfFolderPath_Size.cache
// Append hash to prevent ISO name-clashing in different folders.
Filename.append(
StringFromFormat("%s_%x_%" PRIx64 ".cache", extension.c_str(),
HashFletcher((const u8*)LegalPathname.c_str(), LegalPathname.size()),
File::GetSize(m_FileName)));
std::string fullname(File::GetUserPath(D_CACHE_IDX));
fullname += Filename;
return fullname;
}
// Outputs to m_pImage
void GameListItem::ReadVolumeBanner(const std::vector<u32>& buffer, int width, int height) void GameListItem::ReadVolumeBanner(const std::vector<u32>& buffer, int width, int height)
{ {
m_pImage.resize(width * height * 3); m_pImage.resize(width * height * 3);
@ -273,8 +220,7 @@ void GameListItem::ReadVolumeBanner(const std::vector<u32>& buffer, int width, i
} }
} }
// Outputs to m_Bitmap bool GameListItem::SetWxBannerFromPngFile(const std::string& path)
bool GameListItem::ReadPNGBanner(const std::string& path)
{ {
if (!File::Exists(path)) if (!File::Exists(path))
return false; return false;
@ -287,6 +233,37 @@ bool GameListItem::ReadPNGBanner(const std::string& path)
return true; return true;
} }
void GameListItem::SetWxBannerFromRaw()
{
// Need to make explicit copy as wxImage uses reference counting for copies combined with only
// taking a pointer, not the content, when given a buffer to its constructor.
if (!m_pImage.empty())
{
m_image.Create(m_ImageWidth, m_ImageHeight, false);
std::memcpy(m_image.GetData(), m_pImage.data(), m_pImage.size());
}
}
bool GameListItem::ReloadBannerIfNeeded()
{
// Wii banners can only be read if there is a savefile,
// so sometimes caches don't contain banners. Let's check
// if a banner has become available after the cache was made.
if ((m_Platform == DiscIO::Platform::WII_DISC || m_Platform == DiscIO::Platform::WII_WAD) &&
m_pImage.empty())
{
std::vector<u32> buffer =
DiscIO::Volume::GetWiiBanner(&m_ImageWidth, &m_ImageHeight, m_title_id);
if (buffer.size())
{
ReadVolumeBanner(buffer, m_ImageWidth, m_ImageHeight);
SetWxBannerFromRaw();
return true;
}
}
return false;
}
std::string GameListItem::GetDescription(DiscIO::Language language) const std::string GameListItem::GetDescription(DiscIO::Language language) const
{ {
return GetLanguageString(language, m_descriptions); return GetLanguageString(language, m_descriptions);

View File

@ -32,8 +32,9 @@ class PointerWrap;
class GameListItem class GameListItem
{ {
public: public:
GameListItem() = default;
GameListItem(const std::string& file_name, const Core::TitleDatabase& title_database); GameListItem(const std::string& file_name, const Core::TitleDatabase& title_database);
~GameListItem(); ~GameListItem() = default;
// Reload settings after INI changes // Reload settings after INI changes
void ReloadINI(); void ReloadINI();
@ -65,8 +66,20 @@ public:
// to display it // to display it
const wxImage& GetBannerImage() const { return m_image; } const wxImage& GetBannerImage() const { return m_image; }
void DoState(PointerWrap& p); void DoState(PointerWrap& p);
bool ReloadBannerIfNeeded();
private: private:
bool IsElfOrDol() const;
// Outputs to m_pImage
void ReadVolumeBanner(const std::vector<u32>& buffer, int width, int height);
// Outputs to m_image
bool SetWxBannerFromPngFile(const std::string& path);
void SetWxBannerFromRaw();
// IMPORTANT: All data members must be save/restored in DoState.
// If anything is changed, make sure DoState handles it properly and
// GameListCtrl::CACHE_REVISION is incremented.
std::string m_FileName; std::string m_FileName;
std::map<DiscIO::Language, std::string> m_names; std::map<DiscIO::Language, std::string> m_names;
@ -97,15 +110,4 @@ private:
std::string m_custom_name_titles_txt; // Custom title from titles.txt std::string m_custom_name_titles_txt; // Custom title from titles.txt
std::string m_custom_name; // Custom title from INI or titles.txt std::string m_custom_name; // Custom title from INI or titles.txt
bool m_has_custom_name; bool m_has_custom_name;
bool LoadFromCache();
void SaveToCache();
bool IsElfOrDol() const;
std::string CreateCacheFilename() const;
// Outputs to m_pImage
void ReadVolumeBanner(const std::vector<u32>& buffer, int width, int height);
// Outputs to m_Bitmap
bool ReadPNGBanner(const std::string& path);
}; };

View File

@ -208,9 +208,6 @@ void DolphinApp::MacOpenFile(const wxString& fileName)
void DolphinApp::AfterInit() void DolphinApp::AfterInit()
{ {
if (!m_batch_mode)
main_frame->UpdateGameList();
#if defined(USE_ANALYTICS) && USE_ANALYTICS #if defined(USE_ANALYTICS) && USE_ANALYTICS
if (!SConfig::GetInstance().m_analytics_permission_asked) if (!SConfig::GetInstance().m_analytics_permission_asked)
{ {