diff --git a/Source/Core/Common/ChunkFile.h b/Source/Core/Common/ChunkFile.h index 72832c5e97..497a6d439a 100644 --- a/Source/Core/Common/ChunkFile.h +++ b/Source/Core/Common/ChunkFile.h @@ -296,125 +296,3 @@ private: *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 - 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 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 - 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 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; - }; -}; diff --git a/Source/Core/Common/FileSearch.cpp b/Source/Core/Common/FileSearch.cpp index 29b8624b15..783c22d864 100644 --- a/Source/Core/Common/FileSearch.cpp +++ b/Source/Core/Common/FileSearch.cpp @@ -7,10 +7,19 @@ #include "Common/CommonPaths.h" #include "Common/FileSearch.h" + +#ifdef _MSC_VER +#include +namespace fs = std::experimental::filesystem; +#define HAS_STD_FILESYSTEM +#else #include "Common/FileUtil.h" +#endif namespace Common { +#ifndef HAS_STD_FILESYSTEM + static std::vector FileSearchWithTest(const std::vector& directories, bool recursive, std::function callback) @@ -36,10 +45,10 @@ FileSearchWithTest(const std::vector& directories, bool recursive, return result; } -std::vector DoFileSearch(const std::vector& exts, - const std::vector& directories, bool recursive) +std::vector DoFileSearchNoSTL(const std::vector& directories, + const std::vector& exts, bool recursive) { - bool accept_all = std::find(exts.begin(), exts.end(), "") != exts.end(); + bool accept_all = exts.empty(); return FileSearchWithTest(directories, recursive, [&](const File::FSTEntry& entry) { if (accept_all) return true; @@ -52,11 +61,71 @@ std::vector DoFileSearch(const std::vector& exts, }); } -// Result includes the passed directories themselves as well as their subdirectories. -std::vector FindSubdirectories(const std::vector& directories, - bool recursive) +std::vector DoFileSearch(const std::vector& directories, + const std::vector& exts, bool recursive) { - return FileSearchWithTest(directories, true, - [&](const File::FSTEntry& entry) { return entry.isDirectory; }); + return DoFileSearchNoSTL(directories, exts, recursive); } + +#else + +std::vector DoFileSearch(const std::vector& directories, + const std::vector& exts, bool recursive) +{ + bool accept_all = exts.empty(); + + std::vector native_exts; + for (const auto& ext : exts) + native_exts.push_back(ext); + + // N.B. This avoids doing any copies + auto ext_matches = [&native_exts](const fs::path& path) { + const auto& native_path = path.native(); + return std::any_of(native_exts.cbegin(), native_exts.cend(), [&native_path](const auto& ext) { + // TODO provide cross-platform compat for the comparison function, once more platforms + // support std::filesystem + return native_path.length() >= ext.native().length() && + _wcsicmp(&native_path.c_str()[native_path.length() - ext.native().length()], + ext.c_str()) == 0; + }); + }; + + std::vector result; + auto add_filtered = [&](const fs::directory_entry& entry) { + auto& path = entry.path(); + if (accept_all || (ext_matches(path) && !fs::is_directory(path))) + result.emplace_back(path.u8string()); + }; + for (const auto& directory : directories) + { + if (recursive) + { + // TODO use fs::directory_options::follow_directory_symlink ? + for (auto& entry : fs::recursive_directory_iterator(fs::path(directory.c_str()))) + add_filtered(entry); + } + else + { + for (auto& entry : fs::directory_iterator(fs::path(directory.c_str()))) + add_filtered(entry); + } + } + + // Remove duplicates (occurring because caller gave e.g. duplicate or overlapping directories - + // not because std::filesystem returns duplicates). Also note that this pathname-based uniqueness + // isn't as thorough as std::filesystem::equivalent. + std::sort(result.begin(), result.end()); + result.erase(std::unique(result.begin(), result.end()), result.end()); + + // Dolphin expects to be able to use "/" (DIR_SEP) everywhere. std::filesystem uses the OS + // separator. + if (fs::path::preferred_separator != DIR_SEP_CHR) + for (auto& path : result) + std::replace(path.begin(), path.end(), '\\', DIR_SEP_CHR); + + return result; +} + +#endif + } // namespace Common diff --git a/Source/Core/Common/FileSearch.h b/Source/Core/Common/FileSearch.h index dc2f7f6b62..dc38db945a 100644 --- a/Source/Core/Common/FileSearch.h +++ b/Source/Core/Common/FileSearch.h @@ -9,9 +9,9 @@ namespace Common { -std::vector DoFileSearch(const std::vector& exts, - const std::vector& directories, +// Callers can pass empty "exts" to indicate they want all files + directories in results +// Otherwise, only files matching the extensions are returned +std::vector DoFileSearch(const std::vector& directories, + const std::vector& exts = {}, bool recursive = false); -std::vector FindSubdirectories(const std::vector& directories, - bool recursive); } // namespace Common diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp index f12751c8be..3dcdb64d11 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp @@ -148,7 +148,7 @@ GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, u hdr_file.ReadBytes(&m_hdr, BLOCK_SIZE); } - std::vector filenames = Common::DoFileSearch({".gci"}, {m_save_directory}); + std::vector filenames = Common::DoFileSearch({m_save_directory}, {".gci"}); if (filenames.size() > 112) { diff --git a/Source/Core/DolphinQt2/Settings.cpp b/Source/Core/DolphinQt2/Settings.cpp index 346b802237..bac3a4143f 100644 --- a/Source/Core/DolphinQt2/Settings.cpp +++ b/Source/Core/DolphinQt2/Settings.cpp @@ -365,7 +365,7 @@ QVector Settings::GetProfiles(const InputConfig* config) const const std::string path = GetProfilesDir().toStdString() + config->GetProfileName(); QVector vec; - for (const auto& file : Common::DoFileSearch({".ini"}, {path})) + for (const auto& file : Common::DoFileSearch({path}, {".ini"})) { std::string basename; SplitPath(file, nullptr, &basename, nullptr); diff --git a/Source/Core/DolphinQt2/Settings/InterfacePane.cpp b/Source/Core/DolphinQt2/Settings/InterfacePane.cpp index b34f66f1f6..84998ea13d 100644 --- a/Source/Core/DolphinQt2/Settings/InterfacePane.cpp +++ b/Source/Core/DolphinQt2/Settings/InterfacePane.cpp @@ -59,9 +59,8 @@ void InterfacePane::CreateUI() combobox_layout->addRow(tr("&Theme:"), m_combobox_theme); // List avalable themes - auto file_search_results = Common::DoFileSearch( - {""}, {File::GetUserPath(D_THEMES_IDX), File::GetSysDirectory() + THEMES_DIR}, - /*recursive*/ false); + auto file_search_results = + Common::DoFileSearch({File::GetUserPath(D_THEMES_IDX), File::GetSysDirectory() + THEMES_DIR}); for (const std::string& filename : file_search_results) { std::string name, ext; diff --git a/Source/Core/DolphinWX/Config/ConfigMain.cpp b/Source/Core/DolphinWX/Config/ConfigMain.cpp index 48fd35ab96..b04f2cc706 100644 --- a/Source/Core/DolphinWX/Config/ConfigMain.cpp +++ b/Source/Core/DolphinWX/Config/ConfigMain.cpp @@ -24,21 +24,21 @@ #include "DolphinWX/GameListCtrl.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_RESCAN_LIST, wxCommandEvent); CConfigMain::CConfigMain(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& position, const wxSize& size, long style) : wxDialog(parent, id, title, position, size, style) { - // Control refreshing of the ISOs list - m_refresh_game_list_on_close = false; + // Control refreshing of the GameListCtrl + m_event_on_close = wxEVT_NULL; Bind(wxEVT_CLOSE_WINDOW, &CConfigMain::OnClose, this); Bind(wxEVT_BUTTON, &CConfigMain::OnCloseButton, this, wxID_CLOSE); Bind(wxEVT_SHOW, &CConfigMain::OnShow, this); Bind(wxDOLPHIN_CFG_REFRESH_LIST, &CConfigMain::OnSetRefreshGameListOnClose, this); + Bind(wxDOLPHIN_CFG_RESCAN_LIST, &CConfigMain::OnSetRescanGameListOnClose, this); wxDialog::SetExtraStyle(GetExtraStyle() & ~wxWS_EX_BLOCK_EVENTS); @@ -115,8 +115,8 @@ void CConfigMain::OnClose(wxCloseEvent& WXUNUSED(event)) SConfig::GetInstance().SaveSettings(); - if (m_refresh_game_list_on_close) - AddPendingEvent(wxCommandEvent{DOLPHIN_EVT_RELOAD_GAMELIST}); + if (m_event_on_close != wxEVT_NULL) + AddPendingEvent(wxCommandEvent{m_event_on_close}); } void CConfigMain::OnShow(wxShowEvent& event) @@ -132,5 +132,12 @@ void CConfigMain::OnCloseButton(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; } diff --git a/Source/Core/DolphinWX/Config/ConfigMain.h b/Source/Core/DolphinWX/Config/ConfigMain.h index 84c2f9121f..005797308b 100644 --- a/Source/Core/DolphinWX/Config/ConfigMain.h +++ b/Source/Core/DolphinWX/Config/ConfigMain.h @@ -10,7 +10,10 @@ class wxNotebook; class wxPanel; +// Fast refresh - can be fulfilled from cache wxDECLARE_EVENT(wxDOLPHIN_CFG_REFRESH_LIST, wxCommandEvent); +// Rescan and refresh - modifies cache +wxDECLARE_EVENT(wxDOLPHIN_CFG_RESCAN_LIST, wxCommandEvent); class CConfigMain : public wxDialog { @@ -41,8 +44,8 @@ private: void OnCloseButton(wxCommandEvent& event); void OnShow(wxShowEvent& event); void OnSetRefreshGameListOnClose(wxCommandEvent& event); + void OnSetRescanGameListOnClose(wxCommandEvent& event); wxNotebook* Notebook; - - bool m_refresh_game_list_on_close; + wxEventType m_event_on_close; }; diff --git a/Source/Core/DolphinWX/Config/InterfaceConfigPane.cpp b/Source/Core/DolphinWX/Config/InterfaceConfigPane.cpp index 278e1697c3..d3308bc22e 100644 --- a/Source/Core/DolphinWX/Config/InterfaceConfigPane.cpp +++ b/Source/Core/DolphinWX/Config/InterfaceConfigPane.cpp @@ -196,9 +196,8 @@ void InterfaceConfigPane::LoadGUIValues() void InterfaceConfigPane::LoadThemes() { - auto sv = Common::DoFileSearch( - {""}, {File::GetUserPath(D_THEMES_IDX), File::GetSysDirectory() + THEMES_DIR}, - /*recursive*/ false); + auto sv = + Common::DoFileSearch({File::GetUserPath(D_THEMES_IDX), File::GetSysDirectory() + THEMES_DIR}); for (const std::string& filename : sv) { std::string name, ext; diff --git a/Source/Core/DolphinWX/Config/PathConfigPane.cpp b/Source/Core/DolphinWX/Config/PathConfigPane.cpp index 5a0a1c1c13..ec3376acc2 100644 --- a/Source/Core/DolphinWX/Config/PathConfigPane.cpp +++ b/Source/Core/DolphinWX/Config/PathConfigPane.cpp @@ -163,7 +163,7 @@ void PathConfigPane::OnRecursiveISOCheckBoxChanged(wxCommandEvent& event) { 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) @@ -179,7 +179,7 @@ void PathConfigPane::OnAddISOPath(wxCommandEvent& event) } else { - AddPendingEvent(wxCommandEvent(wxDOLPHIN_CFG_REFRESH_LIST)); + AddPendingEvent(wxCommandEvent(wxDOLPHIN_CFG_RESCAN_LIST)); m_iso_paths_listbox->Append(dialog.GetPath()); } } @@ -189,7 +189,7 @@ void PathConfigPane::OnAddISOPath(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()); // This seems to not be activated on Windows when it should be. wxw bug? diff --git a/Source/Core/DolphinWX/Frame.cpp b/Source/Core/DolphinWX/Frame.cpp index b4b4dd46ea..29a7b526c3 100644 --- a/Source/Core/DolphinWX/Frame.cpp +++ b/Source/Core/DolphinWX/Frame.cpp @@ -337,7 +337,8 @@ CFrame::CFrame(wxFrame* parent, wxWindowID id, const wxString& title, wxRect geo wxFrame::CreateToolBar(wxTB_DEFAULT_STYLE | wxTB_TEXT | wxTB_FLAT)->Realize(); // Give it a status bar - SetStatusBar(CreateStatusBar(2, wxST_SIZEGRIP, ID_STATUSBAR)); + SetStatusBar( + CreateStatusBar(2, wxSTB_SIZEGRIP | wxSTB_ELLIPSIZE_END | wxSTB_SHOW_TIPS, ID_STATUSBAR)); if (!SConfig::GetInstance().m_InterfaceStatusbar) GetStatusBar()->Hide(); @@ -351,8 +352,9 @@ CFrame::CFrame(wxFrame* parent, wxWindowID id, const wxString& title, wxRect geo // This panel is the parent for rendering and it holds the gamelistctrl m_panel = new wxPanel(this, IDM_MPANEL, wxDefaultPosition, wxDefaultSize, 0); - m_game_list_ctrl = new CGameListCtrl(m_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, - wxLC_REPORT | wxSUNKEN_BORDER | wxLC_ALIGN_LEFT); + m_game_list_ctrl = + 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); wxBoxSizer* sizerPanel = new wxBoxSizer(wxHORIZONTAL); @@ -490,7 +492,8 @@ void CFrame::BindEvents() BindMenuBarEvents(); 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_BOOT_SOFTWARE, &CFrame::OnPlay, this); Bind(DOLPHIN_EVT_STOP_SOFTWARE, &CFrame::OnStop, this); @@ -876,7 +879,7 @@ void CFrame::OnGameListCtrlItemActivated(wxListEvent& WXUNUSED(event)) // 1. Boot the selected iso // 2. Boot the default or last loaded iso. // 3. Call BrowseForDirectory if the gamelist is empty - if (!m_game_list_ctrl->GetISO(0) && CGameListCtrl::IsHidingItems()) + if (!m_game_list_ctrl->GetISO(0) && GameListCtrl::IsHidingItems()) { SConfig::GetInstance().m_ListGC = SConfig::GetInstance().m_ListWii = SConfig::GetInstance().m_ListWad = SConfig::GetInstance().m_ListElfDol = @@ -909,7 +912,7 @@ void CFrame::OnGameListCtrlItemActivated(wxListEvent& WXUNUSED(event)) GetMenuBar()->FindItem(IDM_LIST_WORLD)->Check(true); GetMenuBar()->FindItem(IDM_LIST_UNKNOWN)->Check(true); - UpdateGameList(); + GameListRefresh(); } else if (!m_game_list_ctrl->GetISO(0)) { diff --git a/Source/Core/DolphinWX/Frame.h b/Source/Core/DolphinWX/Frame.h index 432cd7496f..094df16173 100644 --- a/Source/Core/DolphinWX/Frame.h +++ b/Source/Core/DolphinWX/Frame.h @@ -33,7 +33,7 @@ struct BootParameters; // Class declarations -class CGameListCtrl; +class GameListCtrl; class CCodeWindow; class CConfigMain; class CLogWindow; @@ -98,10 +98,11 @@ public: void DoStop(); void UpdateGUI(); - void UpdateGameList(); + void GameListRefresh(); + void GameListRescan(); void ToggleLogWindow(bool bShow); void ToggleLogConfigWindow(bool bShow); - void StatusBarMessage(const char* Text, ...); + void StatusBarMessage(const char* format, ...); void ClearStatusBar(); void BootGame(const std::string& filename); bool RendererHasFocus(); @@ -143,7 +144,7 @@ private: ADD_PANE_CENTER }; - CGameListCtrl* m_game_list_ctrl = nullptr; + GameListCtrl* m_game_list_ctrl = nullptr; CConfigMain* m_main_config_dialog = nullptr; wxPanel* m_panel = nullptr; CRenderFrame* m_render_frame = nullptr; @@ -267,7 +268,8 @@ private: void OnHelp(wxCommandEvent& event); void OnReloadThemeBitmaps(wxCommandEvent& event); - void OnReloadGameList(wxCommandEvent& event); + void OnRefreshGameList(wxCommandEvent& event); + void OnRescanGameList(wxCommandEvent& event); void OnUpdateInterpreterMenuItem(wxUpdateUIEvent& event); diff --git a/Source/Core/DolphinWX/FrameAui.cpp b/Source/Core/DolphinWX/FrameAui.cpp index bfc2328be9..9ab5a035e7 100644 --- a/Source/Core/DolphinWX/FrameAui.cpp +++ b/Source/Core/DolphinWX/FrameAui.cpp @@ -511,8 +511,7 @@ void CFrame::OnPerspectiveMenu(wxCommandEvent& event) return; } SaveIniPerspectives(); - GetStatusBar()->SetStatusText( - StrToWxStr(std::string("Saved " + m_perspectives[m_active_perspective].name)), 0); + StatusBarMessage("Saved %s", m_perspectives[m_active_perspective].name.c_str()); break; case IDM_PERSPECTIVES_ADD_PANE_TOP: AddPane(ADD_PANE_TOP); diff --git a/Source/Core/DolphinWX/FrameTools.cpp b/Source/Core/DolphinWX/FrameTools.cpp index 8ac6fd7e08..007c6595db 100644 --- a/Source/Core/DolphinWX/FrameTools.cpp +++ b/Source/Core/DolphinWX/FrameTools.cpp @@ -774,7 +774,7 @@ void CFrame::OnBootDrive(wxCommandEvent& event) void CFrame::OnRefresh(wxCommandEvent& WXUNUSED(event)) { - UpdateGameList(); + GameListRescan(); } void CFrame::OnScreenshot(wxCommandEvent& WXUNUSED(event)) @@ -1097,12 +1097,17 @@ void CFrame::OnReloadThemeBitmaps(wxCommandEvent& WXUNUSED(event)) reload_event.SetEventObject(this); 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) @@ -1128,18 +1133,16 @@ void CFrame::ClearStatusBar() } } -void CFrame::StatusBarMessage(const char* Text, ...) +void CFrame::StatusBarMessage(const char* format, ...) { - const int MAX_BYTES = 1024 * 10; - char Str[MAX_BYTES]; - va_list ArgPtr; - va_start(ArgPtr, Text); - vsnprintf(Str, MAX_BYTES, Text, ArgPtr); - va_end(ArgPtr); + va_list args; + va_start(args, format); + std::string msg = StringFromFormatV(format, args); + va_end(args); if (this->GetStatusBar()->IsEnabled()) { - this->GetStatusBar()->SetStatusText(StrToWxStr(Str), 0); + this->GetStatusBar()->SetStatusText(StrToWxStr(msg), 0); } } @@ -1605,9 +1608,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); wxPostEvent(m_game_list_ctrl, event); } @@ -1674,17 +1684,19 @@ void CFrame::GameListChanged(wxCommandEvent& event) SConfig::GetInstance().m_ListDrives = event.IsChecked(); break; case IDM_PURGE_GAME_LIST_CACHE: - std::vector rFilenames = - Common::DoFileSearch({".cache"}, {File::GetUserPath(D_CACHE_IDX)}); + std::vector filenames = + Common::DoFileSearch({File::GetUserPath(D_CACHE_IDX)}, {".cache"}); - 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 @@ -1743,6 +1755,6 @@ void CFrame::OnChangeColumnsVisible(wxCommandEvent& event) default: return; } - UpdateGameList(); + GameListRefresh(); SConfig::GetInstance().SaveSettings(); } diff --git a/Source/Core/DolphinWX/GameListCtrl.cpp b/Source/Core/DolphinWX/GameListCtrl.cpp index 36d334fcbf..4e89a7051c 100644 --- a/Source/Core/DolphinWX/GameListCtrl.cpp +++ b/Source/Core/DolphinWX/GameListCtrl.cpp @@ -44,6 +44,7 @@ #include "Common/MathUtil.h" #include "Common/StringUtil.h" #include "Common/SysConf.h" +#include "Common/Thread.h" #include "Core/Boot/Boot.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -82,7 +83,7 @@ public: static bool sorted = false; static int CompareGameListItems(const GameListItem* iso1, const GameListItem* iso2, - long sortData = CGameListCtrl::COLUMN_TITLE) + long sortData = GameListCtrl::COLUMN_TITLE) { int t = 1; @@ -94,7 +95,7 @@ static int CompareGameListItems(const GameListItem* iso1, const GameListItem* is switch (sortData) { - case CGameListCtrl::COLUMN_TITLE: + case GameListCtrl::COLUMN_TITLE: if (!strcasecmp(iso1->GetName().c_str(), iso2->GetName().c_str())) { if (iso1->GetGameID() != iso2->GetGameID()) @@ -111,34 +112,34 @@ static int CompareGameListItems(const GameListItem* iso1, const GameListItem* is return t * wxStricmp(iso1_filename, iso2_filename); } return strcasecmp(iso1->GetName().c_str(), iso2->GetName().c_str()) * t; - case CGameListCtrl::COLUMN_MAKER: + case GameListCtrl::COLUMN_MAKER: return strcasecmp(iso1->GetCompany().c_str(), iso2->GetCompany().c_str()) * t; - case CGameListCtrl::COLUMN_FILENAME: + case GameListCtrl::COLUMN_FILENAME: return wxStricmp(wxFileNameFromPath(iso1->GetFileName()), wxFileNameFromPath(iso2->GetFileName())) * t; - case CGameListCtrl::COLUMN_ID: + case GameListCtrl::COLUMN_ID: return strcasecmp(iso1->GetGameID().c_str(), iso2->GetGameID().c_str()) * t; - case CGameListCtrl::COLUMN_COUNTRY: + case GameListCtrl::COLUMN_COUNTRY: if (iso1->GetCountry() > iso2->GetCountry()) return 1 * t; if (iso1->GetCountry() < iso2->GetCountry()) return -1 * t; return 0; - case CGameListCtrl::COLUMN_SIZE: + case GameListCtrl::COLUMN_SIZE: if (iso1->GetFileSize() > iso2->GetFileSize()) return 1 * t; if (iso1->GetFileSize() < iso2->GetFileSize()) return -1 * t; return 0; - case CGameListCtrl::COLUMN_PLATFORM: + case GameListCtrl::COLUMN_PLATFORM: if (iso1->GetPlatform() > iso2->GetPlatform()) return 1 * t; if (iso1->GetPlatform() < iso2->GetPlatform()) return -1 * t; return 0; - case CGameListCtrl::COLUMN_EMULATION_STATE: + case GameListCtrl::COLUMN_EMULATION_STATE: { const int nState1 = iso1->GetEmuState(), nState2 = iso2->GetEmuState(); @@ -155,36 +156,6 @@ static int CompareGameListItems(const GameListItem* iso1, const GameListItem* is return 0; } -static std::vector GetFileSearchExtensions() -{ - std::vector 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) { const bool show_platform = [&item] { @@ -240,9 +211,10 @@ 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 CGameListCtrl::ColumnInfo +struct GameListCtrl::ColumnInfo { const int id; const int default_width; @@ -250,9 +222,9 @@ struct CGameListCtrl::ColumnInfo bool& visible; }; -CGameListCtrl::CGameListCtrl(wxWindow* parent, const wxWindowID id, const wxPoint& pos, - const wxSize& size, long style) - : wxListCtrl(parent, id, pos, size, style), toolTip(nullptr), +GameListCtrl::GameListCtrl(bool disable_scanning, wxWindow* parent, const wxWindowID id, + const wxPoint& pos, const wxSize& size, long style) + : wxListCtrl(parent, id, pos, size, style), m_tooltip(nullptr), m_columns({// {COLUMN, {default_width (without platform padding), resizability, visibility}} {COLUMN_PLATFORM, 32 + 1 /* icon padding */, false, SConfig::GetInstance().m_showSystemColumn}, @@ -265,34 +237,78 @@ CGameListCtrl::CGameListCtrl(wxWindow* parent, const wxWindowID id, const wxPoin {COLUMN_EMULATION_STATE, 48, false, SConfig::GetInstance().m_showStateColumn}, {COLUMN_SIZE, wxLIST_AUTOSIZE, false, SConfig::GetInstance().m_showSizeColumn}}) { - Bind(wxEVT_SIZE, &CGameListCtrl::OnSize, this); - Bind(wxEVT_RIGHT_DOWN, &CGameListCtrl::OnRightClick, this); - Bind(wxEVT_LEFT_DOWN, &CGameListCtrl::OnLeftClick, this); - Bind(wxEVT_MOTION, &CGameListCtrl::OnMouseMotion, this); - Bind(wxEVT_LIST_KEY_DOWN, &CGameListCtrl::OnKeyPress, this); - Bind(wxEVT_LIST_COL_BEGIN_DRAG, &CGameListCtrl::OnColBeginDrag, this); - Bind(wxEVT_LIST_COL_CLICK, &CGameListCtrl::OnColumnClick, this); + Bind(wxEVT_SIZE, &GameListCtrl::OnSize, this); + Bind(wxEVT_RIGHT_DOWN, &GameListCtrl::OnRightClick, this); + Bind(wxEVT_LEFT_DOWN, &GameListCtrl::OnLeftClick, this); + Bind(wxEVT_MOTION, &GameListCtrl::OnMouseMotion, this); + Bind(wxEVT_LIST_KEY_DOWN, &GameListCtrl::OnKeyPress, this); + Bind(wxEVT_LIST_COL_BEGIN_DRAG, &GameListCtrl::OnColBeginDrag, this); + Bind(wxEVT_LIST_COL_CLICK, &GameListCtrl::OnColumnClick, this); - Bind(wxEVT_MENU, &CGameListCtrl::OnProperties, this, IDM_PROPERTIES); - Bind(wxEVT_MENU, &CGameListCtrl::OnWiki, this, IDM_GAME_WIKI); - Bind(wxEVT_MENU, &CGameListCtrl::OnOpenContainingFolder, this, IDM_OPEN_CONTAINING_FOLDER); - Bind(wxEVT_MENU, &CGameListCtrl::OnOpenSaveFolder, this, IDM_OPEN_SAVE_FOLDER); - Bind(wxEVT_MENU, &CGameListCtrl::OnExportSave, this, IDM_EXPORT_SAVE); - Bind(wxEVT_MENU, &CGameListCtrl::OnSetDefaultISO, this, IDM_SET_DEFAULT_ISO); - Bind(wxEVT_MENU, &CGameListCtrl::OnCompressISO, this, IDM_COMPRESS_ISO); - Bind(wxEVT_MENU, &CGameListCtrl::OnMultiCompressISO, this, IDM_MULTI_COMPRESS_ISO); - Bind(wxEVT_MENU, &CGameListCtrl::OnMultiDecompressISO, this, IDM_MULTI_DECOMPRESS_ISO); - Bind(wxEVT_MENU, &CGameListCtrl::OnDeleteISO, this, IDM_DELETE_ISO); - Bind(wxEVT_MENU, &CGameListCtrl::OnChangeDisc, this, IDM_LIST_CHANGE_DISC); - Bind(wxEVT_MENU, &CGameListCtrl::OnNetPlayHost, this, IDM_START_NETPLAY); + Bind(wxEVT_MENU, &GameListCtrl::OnProperties, this, IDM_PROPERTIES); + Bind(wxEVT_MENU, &GameListCtrl::OnWiki, this, IDM_GAME_WIKI); + Bind(wxEVT_MENU, &GameListCtrl::OnOpenContainingFolder, this, IDM_OPEN_CONTAINING_FOLDER); + Bind(wxEVT_MENU, &GameListCtrl::OnOpenSaveFolder, this, IDM_OPEN_SAVE_FOLDER); + Bind(wxEVT_MENU, &GameListCtrl::OnExportSave, this, IDM_EXPORT_SAVE); + Bind(wxEVT_MENU, &GameListCtrl::OnSetDefaultISO, this, IDM_SET_DEFAULT_ISO); + Bind(wxEVT_MENU, &GameListCtrl::OnCompressISO, this, IDM_COMPRESS_ISO); + Bind(wxEVT_MENU, &GameListCtrl::OnMultiCompressISO, this, IDM_MULTI_COMPRESS_ISO); + Bind(wxEVT_MENU, &GameListCtrl::OnMultiDecompressISO, this, IDM_MULTI_DECOMPRESS_ISO); + Bind(wxEVT_MENU, &GameListCtrl::OnDeleteISO, this, IDM_DELETE_ISO); + Bind(wxEVT_MENU, &GameListCtrl::OnChangeDisc, this, IDM_LIST_CHANGE_DISC); + Bind(wxEVT_MENU, &GameListCtrl::OnNetPlayHost, this, IDM_START_NETPLAY); - Bind(DOLPHIN_EVT_RELOAD_GAMELIST, &CGameListCtrl::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, &CGameListCtrl::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(); + } + }); + } } -CGameListCtrl::~CGameListCtrl() +GameListCtrl::~GameListCtrl() { + if (m_scan_thread.joinable()) + { + m_scan_exiting.Set(); + m_scan_trigger.Set(); + m_scan_thread.join(); + } } template @@ -307,7 +323,7 @@ static void InitBitmap(wxImageList* img_list, std::vector* vector, wxWindow wxTransparentColour)); } -void CGameListCtrl::InitBitmaps() +void GameListCtrl::InitBitmaps() { const wxSize size = FromDIP(wxSize(96, 32)); const wxSize flag_bmp_size = FromDIP(wxSize(32, 32)); @@ -316,59 +332,63 @@ void CGameListCtrl::InitBitmaps() wxImageList* img_list = new wxImageList(size.GetWidth(), size.GetHeight()); AssignImageList(img_list, wxIMAGE_LIST_SMALL); - m_FlagImageIndex.resize(static_cast(DiscIO::Country::NUMBER_OF_COUNTRIES)); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_JAPAN, + auto& flag_indexes = m_image_indexes.flag; + flag_indexes.resize(static_cast(DiscIO::Country::NUMBER_OF_COUNTRIES)); + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_JAPAN, "Flag_Japan"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_EUROPE, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_EUROPE, "Flag_Europe"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_USA, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_USA, "Flag_USA"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_AUSTRALIA, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_AUSTRALIA, "Flag_Australia"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_FRANCE, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_FRANCE, "Flag_France"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_GERMANY, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_GERMANY, "Flag_Germany"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_ITALY, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_ITALY, "Flag_Italy"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_KOREA, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_KOREA, "Flag_Korea"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_NETHERLANDS, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_NETHERLANDS, "Flag_Netherlands"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_RUSSIA, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_RUSSIA, "Flag_Russia"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_SPAIN, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_SPAIN, "Flag_Spain"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_TAIWAN, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_TAIWAN, "Flag_Taiwan"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_WORLD, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_WORLD, "Flag_International"); - InitBitmap(img_list, &m_FlagImageIndex, this, flag_bmp_size, DiscIO::Country::COUNTRY_UNKNOWN, + InitBitmap(img_list, &flag_indexes, this, flag_bmp_size, DiscIO::Country::COUNTRY_UNKNOWN, "Flag_Unknown"); - m_PlatformImageIndex.resize(static_cast(DiscIO::Platform::NUMBER_OF_PLATFORMS)); - InitBitmap(img_list, &m_PlatformImageIndex, this, platform_bmp_size, - DiscIO::Platform::GAMECUBE_DISC, "Platform_Gamecube"); - InitBitmap(img_list, &m_PlatformImageIndex, this, platform_bmp_size, DiscIO::Platform::WII_DISC, + auto& platform_indexes = m_image_indexes.platform; + platform_indexes.resize(static_cast(DiscIO::Platform::NUMBER_OF_PLATFORMS)); + InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, DiscIO::Platform::GAMECUBE_DISC, + "Platform_Gamecube"); + InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, DiscIO::Platform::WII_DISC, "Platform_Wii"); - InitBitmap(img_list, &m_PlatformImageIndex, this, platform_bmp_size, DiscIO::Platform::WII_WAD, + InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, DiscIO::Platform::WII_WAD, "Platform_Wad"); - InitBitmap(img_list, &m_PlatformImageIndex, this, platform_bmp_size, DiscIO::Platform::ELF_DOL, + InitBitmap(img_list, &platform_indexes, this, platform_bmp_size, DiscIO::Platform::ELF_DOL, "Platform_File"); - m_EmuStateImageIndex.resize(6); - InitBitmap(img_list, &m_EmuStateImageIndex, this, rating_bmp_size, 0, "rating0", true); - InitBitmap(img_list, &m_EmuStateImageIndex, this, rating_bmp_size, 1, "rating1", true); - InitBitmap(img_list, &m_EmuStateImageIndex, this, rating_bmp_size, 2, "rating2", true); - InitBitmap(img_list, &m_EmuStateImageIndex, this, rating_bmp_size, 3, "rating3", true); - InitBitmap(img_list, &m_EmuStateImageIndex, this, rating_bmp_size, 4, "rating4", true); - InitBitmap(img_list, &m_EmuStateImageIndex, this, rating_bmp_size, 5, "rating5", true); + auto& emu_state_indexes = m_image_indexes.emu_state; + emu_state_indexes.resize(6); + InitBitmap(img_list, &emu_state_indexes, this, rating_bmp_size, 0, "rating0", true); + InitBitmap(img_list, &emu_state_indexes, this, rating_bmp_size, 1, "rating1", true); + InitBitmap(img_list, &emu_state_indexes, this, rating_bmp_size, 2, "rating2", true); + InitBitmap(img_list, &emu_state_indexes, this, rating_bmp_size, 3, "rating3", true); + InitBitmap(img_list, &emu_state_indexes, this, rating_bmp_size, 4, "rating4", true); + InitBitmap(img_list, &emu_state_indexes, this, rating_bmp_size, 5, "rating5", true); - m_utility_game_banners.resize(1); - InitBitmap(img_list, &m_utility_game_banners, this, size, 0, "nobanner"); + auto& utility_banner_indexes = m_image_indexes.utility_banner; + utility_banner_indexes.resize(1); + InitBitmap(img_list, &utility_banner_indexes, this, size, 0, "nobanner"); } -void CGameListCtrl::BrowseForDirectory() +void GameListCtrl::BrowseForDirectory() { wxString dirHome; wxGetHomeDir(&dirHome); @@ -388,25 +408,42 @@ void CGameListCtrl::BrowseForDirectory() { SConfig::GetInstance().m_ISOFolder.push_back(sPath); SConfig::GetInstance().SaveSettings(); + m_scan_trigger.Set(); } - - ReloadList(); } } -void CGameListCtrl::ReloadList() +void GameListCtrl::RefreshList() { int scrollPos = wxWindow::GetScrollPos(wxVERTICAL); // Don't let the user refresh it while a game is running if (Core::GetState() != Core::State::Uninitialized) 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(drive, title_database); + if (file->IsValid()) + m_shown_files.push_back(file); + } + } Freeze(); ClearAll(); - if (m_ISOFiles.size() != 0) + if (!m_shown_files.empty()) { // Don't load bitmaps unless there are games to list InitBitmaps(); @@ -431,20 +468,19 @@ void CGameListCtrl::ReloadList() #endif // set initial sizes for columns SetColumnWidth(COLUMN_DUMMY, 0); - for (const auto& c : m_columns) { SetColumnWidth(c.id, c.visible ? FromDIP(c.default_width + platform_padding) : 0); } // 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); SetColors(); // Sort items by Title if (!sorted) - last_column = 0; + m_last_column = 0; sorted = false; wxListEvent event; event.m_col = SConfig::GetInstance().m_ListSort2; @@ -478,7 +514,7 @@ void CGameListCtrl::ReloadList() errorString = _("Dolphin could not find any GameCube/Wii ISOs or WADs. Double-click here to " "set a games directory..."); } - InsertColumn(0, ""); + InsertColumn(COLUMN_DUMMY, ""); long index = InsertItem(0, errorString); SetItemFont(index, *wxITALIC_FONT); SetColumnWidth(0, wxLIST_AUTOSIZE); @@ -512,37 +548,37 @@ static wxString NiceSizeFormat(u64 size) return wxString::Format("%.2f %s", size / unit_size, unit_symbols[unit]); } -// Update the column content of the item at _Index -void CGameListCtrl::UpdateItemAtColumn(long _Index, int column) +// Update the column content of the item at index +void GameListCtrl::UpdateItemAtColumn(long index, int column) { - GameListItem& rISOFile = *m_ISOFiles[GetItemData(_Index)]; + const auto& iso_file = *GetISO(GetItemData(index)); switch (column) { case COLUMN_PLATFORM: { - SetItemColumnImage(_Index, COLUMN_PLATFORM, - m_PlatformImageIndex[static_cast(rISOFile.GetPlatform())]); + SetItemColumnImage(index, COLUMN_PLATFORM, + m_image_indexes.platform[static_cast(iso_file.GetPlatform())]); break; } case COLUMN_BANNER: { - int ImageIndex = m_utility_game_banners[0]; // nobanner + int image_index = m_image_indexes.utility_banner[0]; // nobanner - if (rISOFile.GetBannerImage().IsOk()) + if (iso_file.GetBannerImage().IsOk()) { wxImageList* img_list = GetImageList(wxIMAGE_LIST_SMALL); - ImageIndex = img_list->Add( - WxUtils::ScaleImageToBitmap(rISOFile.GetBannerImage(), this, img_list->GetSize())); + image_index = img_list->Add( + WxUtils::ScaleImageToBitmap(iso_file.GetBannerImage(), this, img_list->GetSize())); } - SetItemColumnImage(_Index, COLUMN_BANNER, ImageIndex); + SetItemColumnImage(index, COLUMN_BANNER, image_index); break; } case COLUMN_TITLE: { - wxString name = StrToWxStr(rISOFile.GetName()); - int disc_number = rISOFile.GetDiscNumber() + 1; + wxString name = StrToWxStr(iso_file.GetName()); + int disc_number = iso_file.GetDiscNumber() + 1; if (disc_number > 1 && name.Lower().find(wxString::Format("disc %i", disc_number)) == std::string::npos && @@ -551,40 +587,40 @@ void CGameListCtrl::UpdateItemAtColumn(long _Index, int column) name = wxString::Format(_("%s (Disc %i)"), name.c_str(), disc_number); } - SetItem(_Index, COLUMN_TITLE, name, -1); + SetItem(index, COLUMN_TITLE, name, -1); break; } case COLUMN_MAKER: - SetItem(_Index, COLUMN_MAKER, StrToWxStr(rISOFile.GetCompany()), -1); + SetItem(index, COLUMN_MAKER, StrToWxStr(iso_file.GetCompany()), -1); break; case COLUMN_FILENAME: - SetItem(_Index, COLUMN_FILENAME, wxFileNameFromPath(StrToWxStr(rISOFile.GetFileName())), -1); + SetItem(index, COLUMN_FILENAME, wxFileNameFromPath(StrToWxStr(iso_file.GetFileName())), -1); break; case COLUMN_EMULATION_STATE: - SetItemColumnImage(_Index, COLUMN_EMULATION_STATE, - m_EmuStateImageIndex[rISOFile.GetEmuState()]); + SetItemColumnImage(index, COLUMN_EMULATION_STATE, + m_image_indexes.emu_state[iso_file.GetEmuState()]); break; case COLUMN_COUNTRY: - SetItemColumnImage(_Index, COLUMN_COUNTRY, - m_FlagImageIndex[static_cast(rISOFile.GetCountry())]); + SetItemColumnImage(index, COLUMN_COUNTRY, + m_image_indexes.flag[static_cast(iso_file.GetCountry())]); break; case COLUMN_SIZE: - SetItem(_Index, COLUMN_SIZE, NiceSizeFormat(rISOFile.GetFileSize()), -1); + SetItem(index, COLUMN_SIZE, NiceSizeFormat(iso_file.GetFileSize()), -1); break; case COLUMN_ID: - SetItem(_Index, COLUMN_ID, rISOFile.GetGameID(), -1); + SetItem(index, COLUMN_ID, iso_file.GetGameID(), -1); break; } } -void CGameListCtrl::InsertItemInReportView(long index) +void GameListCtrl::InsertItemInReportView(long index) { // When using wxListCtrl, there is no hope of per-column text colors. // But for reference, here are the old colors that were used: (BGR) // title: 0xFF0000 // company: 0x007030 - // Insert a first column with nothing in it, that will be used as the Index + // Insert a first column (COLUMN_DUMMY) with nothing in it to use as the Index long item_index; { wxListItem li; @@ -595,7 +631,7 @@ void CGameListCtrl::InsertItemInReportView(long index) } // Iterate over all columns and fill them with content if they are visible - for (int i = 1; i < NUMBER_OF_COLUMN; i++) + for (int i = FIRST_COLUMN_WITH_CONTENT; i < NUMBER_OF_COLUMN; i++) { if (GetColumnWidth(i) != 0) UpdateItemAtColumn(item_index, i); @@ -620,7 +656,7 @@ static wxColour ContrastText(const wxColour& bgc) return (lum > LUM_THRESHOLD) ? *wxBLACK : *wxWHITE; } -void CGameListCtrl::SetColors() +void GameListCtrl::SetColors() { for (long i = 0; i < GetItemCount(); i++) { @@ -632,99 +668,155 @@ void CGameListCtrl::SetColors() } } -void CGameListCtrl::ScanForISOs() +void GameListCtrl::DoState(PointerWrap* p, u32 size) { - m_ISOFiles.clear(); + struct + { + u32 Revision; + u32 ExpectedSize; + } header = {CACHE_REVISION, size}; + p->Do(header); + if (p->GetMode() == PointerWrap::MODE_READ) + { + if (header.Revision != CACHE_REVISION || header.ExpectedSize != size) + { + p->SetMode(PointerWrap::MODE_MEASURE); + return; + } + } + p->DoEachElement(m_cached_files, [](PointerWrap& state, std::shared_ptr& elem) { + if (state.GetMode() == PointerWrap::MODE_READ) + { + elem = std::make_shared(); + } + elem->DoState(state); + }); +} + +bool GameListCtrl::SyncCacheFile(bool write) +{ + 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(ptr); + + // Then actually do the write. + std::vector 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 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() +{ + auto post_status = [&](const wxString& status) { + auto event = new wxCommandEvent(wxEVT_HOST_COMMAND, IDM_UPDATE_STATUS_BAR); + event->SetInt(0); + event->SetString(status); + QueueEvent(event); + }; + + post_status(_("Scanning...")); + + const std::vector 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(SConfig::GetInstance().m_ISOFolder, search_extensions, + SConfig::GetInstance().m_RecursiveISOFolder); + + std::vector 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 removed_paths; + std::set_difference(cached_paths.cbegin(), cached_paths.cend(), search_results.cbegin(), + search_results.cend(), std::back_inserter(removed_paths)); + + std::vector 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; - auto rFilenames = - Common::DoFileSearch(GetFileSearchExtensions(), SConfig::GetInstance().m_ISOFolder, - SConfig::GetInstance().m_RecursiveISOFolder); - - if (rFilenames.size() > 0) + // 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) { - wxProgressDialog dialog( - _("Scanning for ISOs"), _("Scanning..."), (int)rFilenames.size() - 1, this, - wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | - wxPD_REMAINING_TIME | wxPD_SMOOTH // - makes updates as small as possible (down to 1px) - ); - - for (u32 i = 0; i < rFilenames.size(); i++) - { - std::string FileName; - SplitPath(rFilenames[i], nullptr, &FileName, nullptr); - - // 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(rFilenames[i], title_database); - - if (iso_file->IsValid() && ShouldDisplayGameListItem(*iso_file)) - { - m_ISOFiles.push_back(std::move(iso_file)); - } - } + auto it = std::find_if( + m_cached_files.cbegin(), m_cached_files.cend(), + [&path](const std::shared_ptr& 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(path, title_database); + if (file->IsValid()) + m_cached_files.push_back(std::move(file)); } - if (SConfig::GetInstance().m_ListDrives) - { - const std::vector drives = cdio_get_devices(); + for (auto& file : m_cached_files) + file->ReloadINI(); - for (const auto& drive : drives) - { - auto gli = std::make_unique(drive, title_database); + // Post UI event to update the displayed list + QueueEvent(new wxCommandEvent(DOLPHIN_EVT_REFRESH_GAMELIST)); - if (gli->IsValid()) - m_ISOFiles.push_back(std::move(gli)); - } - } + post_status(_("Scan complete!")); - std::sort(m_ISOFiles.begin(), m_ISOFiles.end()); + SyncCacheFile(true); } -void CGameListCtrl::OnReloadGameList(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnRefreshGameList(wxCommandEvent& WXUNUSED(event)) { - ReloadList(); + RefreshList(); } -void CGameListCtrl::OnLocalIniModified(wxCommandEvent& ev) +void GameListCtrl::OnRescanGameList(wxCommandEvent& WXUNUSED(event)) +{ + m_scan_trigger.Set(); +} + +void GameListCtrl::OnLocalIniModified(wxCommandEvent& ev) { ev.Skip(); - std::string game_id = WxStrToStr(ev.GetString()); - // NOTE: The same game may occur multiple times if there are multiple - // physical copies in the search paths. - for (std::size_t i = 0; i < m_ISOFiles.size(); ++i) - { - 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(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 = 1; 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); - } - } + // We need show any changes to the ini which could impact our columns. Currently only the + // EmuState/Issues settings can do that. We also need to persist the changes to the cache - so + // just trigger a rescan which will sync the cache and then display the new values. + m_scan_trigger.Set(); } -void CGameListCtrl::OnColBeginDrag(wxListEvent& event) +void GameListCtrl::OnColBeginDrag(wxListEvent& event) { const int column_id = event.GetColumn(); @@ -732,15 +824,15 @@ void CGameListCtrl::OnColBeginDrag(wxListEvent& event) event.Veto(); } -const GameListItem* CGameListCtrl::GetISO(size_t index) const +const GameListItem* GameListCtrl::GetISO(size_t index) const { - if (index < m_ISOFiles.size()) - return m_ISOFiles[index].get(); + if (index < m_shown_files.size()) + return m_shown_files[index].get(); return nullptr; } -static CGameListCtrl* caller; +static GameListCtrl* caller; static int wxCALLBACK wxListCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) { // return 1 if item1 > item2 @@ -752,32 +844,32 @@ static int wxCALLBACK wxListCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sor return CompareGameListItems(iso1, iso2, sortData); } -void CGameListCtrl::OnColumnClick(wxListEvent& event) +void GameListCtrl::OnColumnClick(wxListEvent& event) { if (event.GetColumn() != COLUMN_BANNER) { int current_column = event.GetColumn(); if (sorted) { - if (last_column == current_column) + if (m_last_column == current_column) { - last_sort = -last_sort; + m_last_sort = -m_last_sort; } else { - SConfig::GetInstance().m_ListSort2 = last_sort; - last_column = current_column; - last_sort = current_column; + SConfig::GetInstance().m_ListSort2 = m_last_sort; + m_last_column = current_column; + m_last_sort = current_column; } - SConfig::GetInstance().m_ListSort = last_sort; + SConfig::GetInstance().m_ListSort = m_last_sort; } else { - last_sort = current_column; - last_column = current_column; + m_last_sort = current_column; + m_last_column = current_column; } caller = this; - SortItems(wxListCompare, last_sort); + SortItems(wxListCompare, m_last_sort); } SetColors(); @@ -786,12 +878,12 @@ void CGameListCtrl::OnColumnClick(wxListEvent& event) } // This is used by keyboard gamelist search -void CGameListCtrl::OnKeyPress(wxListEvent& event) +void GameListCtrl::OnKeyPress(wxListEvent& event) { static int lastKey = 0, sLoop = 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 wxListItem bleh; @@ -807,7 +899,7 @@ void CGameListCtrl::OnKeyPress(wxListEvent& event) if (lastKey == event.GetKeyCode() && Loop < sLoop) { Loop++; - if (i + 1 == (int)m_ISOFiles.size()) + if (i + 1 == (int)m_shown_files.size()) i = -1; continue; } @@ -828,7 +920,7 @@ void CGameListCtrl::OnKeyPress(wxListEvent& event) // If we get past the last game in the list, // 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; } @@ -836,7 +928,7 @@ void CGameListCtrl::OnKeyPress(wxListEvent& event) } // This shows a little tooltip with the current Game's emulation state -void CGameListCtrl::OnMouseMotion(wxMouseEvent& event) +void GameListCtrl::OnMouseMotion(wxMouseEvent& event) { int flags; long subitem = 0; @@ -859,7 +951,7 @@ void CGameListCtrl::OnMouseMotion(wxMouseEvent& event) if (Rect.GetX() + Rect.GetWidth() - GetColumnWidth(COLUMN_EMULATION_STATE) < event.GetX()) #endif { - if (toolTip || lastItem == item || this != FindFocus()) + if (m_tooltip || lastItem == item || this != FindFocus()) { if (lastItem != item) lastItem = -1; @@ -870,7 +962,7 @@ void CGameListCtrl::OnMouseMotion(wxMouseEvent& event) // Emulation status static const char* const emuState[] = {"Broken", "Intro", "In-Game", "Playable", "Perfect"}; - const GameListItem* iso = m_ISOFiles[GetItemData(item)].get(); + const GameListItem* iso = GetISO(GetItemData(item)); const int emu_state = iso->GetEmuState(); const std::string& issues = iso->GetIssues(); @@ -881,11 +973,11 @@ void CGameListCtrl::OnMouseMotion(wxMouseEvent& event) char temp[2048]; sprintf(temp, "^ %s%s%s", emuState[emu_state - 1], issues.size() > 0 ? " :\n" : "", issues.c_str()); - toolTip = new wxEmuStateTip(this, StrToWxStr(temp), &toolTip); + m_tooltip = new wxEmuStateTip(this, StrToWxStr(temp), &m_tooltip); } else { - toolTip = new wxEmuStateTip(this, _("Not Set"), &toolTip); + m_tooltip = new wxEmuStateTip(this, _("Not Set"), &m_tooltip); } // Get item Coords @@ -900,20 +992,20 @@ void CGameListCtrl::OnMouseMotion(wxMouseEvent& event) #endif // Convert to screen coordinates ClientToScreen(&mx, &my); - toolTip->SetBoundingRect(wxRect(mx - GetColumnWidth(COLUMN_EMULATION_STATE), my, - GetColumnWidth(COLUMN_EMULATION_STATE), Rect.GetHeight())); - toolTip->SetPosition( + m_tooltip->SetBoundingRect(wxRect(mx - GetColumnWidth(COLUMN_EMULATION_STATE), my, + GetColumnWidth(COLUMN_EMULATION_STATE), Rect.GetHeight())); + m_tooltip->SetPosition( wxPoint(mx - GetColumnWidth(COLUMN_EMULATION_STATE), my - 5 + Rect.GetHeight())); lastItem = item; } } - if (!toolTip) + if (!m_tooltip) lastItem = -1; event.Skip(); } -void CGameListCtrl::OnLeftClick(wxMouseEvent& event) +void GameListCtrl::OnLeftClick(wxMouseEvent& event) { // Focus the clicked item. int flags; @@ -945,7 +1037,7 @@ static bool IsWADInstalled(const GameListItem& wad) [](const auto& file) { return file.virtualName != "title.tmd"; }); } -void CGameListCtrl::OnRightClick(wxMouseEvent& event) +void GameListCtrl::OnRightClick(wxMouseEvent& event) { // Focus the clicked item. int flags; @@ -1043,9 +1135,9 @@ void CGameListCtrl::OnRightClick(wxMouseEvent& event) } } -const GameListItem* CGameListCtrl::GetSelectedISO() const +const GameListItem* GameListCtrl::GetSelectedISO() const { - if (m_ISOFiles.empty()) + if (m_shown_files.empty()) return nullptr; if (GetSelectedItemCount() == 0) @@ -1055,10 +1147,10 @@ const GameListItem* CGameListCtrl::GetSelectedISO() const if (item == wxNOT_FOUND) return nullptr; - return m_ISOFiles[GetItemData(item)].get(); + return GetISO(GetItemData(item)); } -std::vector CGameListCtrl::GetAllSelectedISOs() const +std::vector GameListCtrl::GetAllSelectedISOs() const { std::vector result; long item = -1; @@ -1067,11 +1159,11 @@ std::vector CGameListCtrl::GetAllSelectedISOs() const item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (item == wxNOT_FOUND) return result; - result.push_back(m_ISOFiles[GetItemData(item)].get()); + result.push_back(GetISO(GetItemData(item))); } } -bool CGameListCtrl::IsHidingItems() +bool GameListCtrl::IsHidingItems() { return !(SConfig::GetInstance().m_ListGC && SConfig::GetInstance().m_ListWii && SConfig::GetInstance().m_ListWad && SConfig::GetInstance().m_ListElfDol && @@ -1084,7 +1176,7 @@ bool CGameListCtrl::IsHidingItems() SConfig::GetInstance().m_ListWorld && SConfig::GetInstance().m_ListUnknown); } -void CGameListCtrl::OnOpenContainingFolder(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnOpenContainingFolder(wxCommandEvent& WXUNUSED(event)) { const GameListItem* iso = GetSelectedISO(); if (!iso) @@ -1095,7 +1187,7 @@ void CGameListCtrl::OnOpenContainingFolder(wxCommandEvent& WXUNUSED(event)) WxUtils::Explore(WxStrToStr(path.GetPath())); } -void CGameListCtrl::OnOpenSaveFolder(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnOpenSaveFolder(wxCommandEvent& WXUNUSED(event)) { const GameListItem* iso = GetSelectedISO(); if (!iso) @@ -1105,7 +1197,7 @@ void CGameListCtrl::OnOpenSaveFolder(wxCommandEvent& WXUNUSED(event)) WxUtils::Explore(path); } -void CGameListCtrl::OnExportSave(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnExportSave(wxCommandEvent& WXUNUSED(event)) { const GameListItem* iso = GetSelectedISO(); if (iso) @@ -1113,7 +1205,7 @@ void CGameListCtrl::OnExportSave(wxCommandEvent& WXUNUSED(event)) } // Save this file as the default file -void CGameListCtrl::OnSetDefaultISO(wxCommandEvent& event) +void GameListCtrl::OnSetDefaultISO(wxCommandEvent& event) { const GameListItem* iso = GetSelectedISO(); if (!iso) @@ -1133,7 +1225,7 @@ void CGameListCtrl::OnSetDefaultISO(wxCommandEvent& event) } } -void CGameListCtrl::OnDeleteISO(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnDeleteISO(wxCommandEvent& WXUNUSED(event)) { const wxString message = GetSelectedItemCount() == 1 ? @@ -1144,11 +1236,11 @@ void CGameListCtrl::OnDeleteISO(wxCommandEvent& WXUNUSED(event)) { for (const GameListItem* iso : GetAllSelectedISOs()) File::Delete(iso->GetFileName()); - ReloadList(); + m_scan_trigger.Set(); } } -void CGameListCtrl::OnProperties(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnProperties(wxCommandEvent& WXUNUSED(event)) { const GameListItem* iso = GetSelectedISO(); if (!iso) @@ -1158,7 +1250,7 @@ void CGameListCtrl::OnProperties(wxCommandEvent& WXUNUSED(event)) ISOProperties->Show(); } -void CGameListCtrl::OnWiki(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnWiki(wxCommandEvent& WXUNUSED(event)) { const GameListItem* iso = GetSelectedISO(); if (!iso) @@ -1169,7 +1261,7 @@ void CGameListCtrl::OnWiki(wxCommandEvent& WXUNUSED(event)) WxUtils::Launch(wikiUrl); } -void CGameListCtrl::OnNetPlayHost(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnNetPlayHost(wxCommandEvent& WXUNUSED(event)) { const GameListItem* iso = GetSelectedISO(); if (!iso) @@ -1192,7 +1284,7 @@ void CGameListCtrl::OnNetPlayHost(wxCommandEvent& WXUNUSED(event)) NetPlayLauncher::Host(config); } -bool CGameListCtrl::MultiCompressCB(const std::string& text, float percent, void* arg) +bool GameListCtrl::MultiCompressCB(const std::string& text, float percent, void* arg) { CompressionProgress* progress = static_cast(arg); @@ -1204,17 +1296,17 @@ bool CGameListCtrl::MultiCompressCB(const std::string& text, float percent, void return progress->dialog->Update(total_percent * progress->dialog->GetRange(), text_string); } -void CGameListCtrl::OnMultiCompressISO(wxCommandEvent& /*event*/) +void GameListCtrl::OnMultiCompressISO(wxCommandEvent& /*event*/) { CompressSelection(true); } -void CGameListCtrl::OnMultiDecompressISO(wxCommandEvent& /*event*/) +void GameListCtrl::OnMultiDecompressISO(wxCommandEvent& /*event*/) { CompressSelection(false); } -void CGameListCtrl::CompressSelection(bool _compress) +void GameListCtrl::CompressSelection(bool _compress) { std::vector items_to_compress; bool wii_compression_warning_accepted = false; @@ -1316,15 +1408,15 @@ void CGameListCtrl::CompressSelection(bool _compress) if (!all_good) WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action.")); - ReloadList(); + m_scan_trigger.Set(); } -bool CGameListCtrl::CompressCB(const std::string& text, float percent, void* arg) +bool GameListCtrl::CompressCB(const std::string& text, float percent, void* arg) { return ((wxProgressDialog*)arg)->Update((int)(percent * 1000), StrToWxStr(text)); } -void CGameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED(event)) { const GameListItem* iso = GetSelectedISO(); if (!iso) @@ -1389,10 +1481,10 @@ void CGameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED(event)) if (!all_good) WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action.")); - ReloadList(); + m_scan_trigger.Set(); } -void CGameListCtrl::OnChangeDisc(wxCommandEvent& WXUNUSED(event)) +void GameListCtrl::OnChangeDisc(wxCommandEvent& WXUNUSED(event)) { const GameListItem* iso = GetSelectedISO(); if (!iso || !Core::IsRunning()) @@ -1400,17 +1492,17 @@ void CGameListCtrl::OnChangeDisc(wxCommandEvent& WXUNUSED(event)) DVDInterface::ChangeDiscAsHost(WxStrToStr(iso->GetFileName())); } -void CGameListCtrl::OnSize(wxSizeEvent& event) +void GameListCtrl::OnSize(wxSizeEvent& event) { event.Skip(); - if (lastpos == event.GetSize()) + if (m_lastpos == event.GetSize()) return; - lastpos = event.GetSize(); + m_lastpos = event.GetSize(); AutomaticColumnWidth(); } -void CGameListCtrl::AutomaticColumnWidth() +void GameListCtrl::AutomaticColumnWidth() { wxRect rc(GetClientRect()); @@ -1444,14 +1536,14 @@ void CGameListCtrl::AutomaticColumnWidth() Thaw(); } -void CGameListCtrl::UnselectAll() +void GameListCtrl::UnselectAll() { for (int i = 0; i < GetItemCount(); i++) { SetItemState(i, 0, wxLIST_STATE_SELECTED); } } -bool CGameListCtrl::WiiCompressWarning() +bool GameListCtrl::WiiCompressWarning() { return wxMessageBox(_("Compressing a Wii disc image will irreversibly change the compressed copy " "by removing padding data. Your disc image will still work. Continue?"), @@ -1462,7 +1554,7 @@ bool CGameListCtrl::WiiCompressWarning() // Windows draws vertical rules between columns when using UXTheme (e.g. Aero, Win10) // This function paints over those lines which removes them. // [The repaint background idea is ripped off from Eclipse SWT which does the same thing] -bool CGameListCtrl::MSWOnNotify(int id, WXLPARAM lparam, WXLPARAM* result) +bool GameListCtrl::MSWOnNotify(int id, WXLPARAM lparam, WXLPARAM* result) { NMLVCUSTOMDRAW* nmlv = reinterpret_cast(lparam); // Intercept the NM_CUSTOMDRAW[CDDS_PREPAINT] diff --git a/Source/Core/DolphinWX/GameListCtrl.h b/Source/Core/DolphinWX/GameListCtrl.h index a7995768c8..f8bdfce19f 100644 --- a/Source/Core/DolphinWX/GameListCtrl.h +++ b/Source/Core/DolphinWX/GameListCtrl.h @@ -7,11 +7,15 @@ #include #include #include +#include #include #include #include +#include "Common/ChunkFile.h" +#include "Common/Event.h" +#include "Common/Flag.h" #include "DolphinWX/ISOFile.h" class wxEmuStateTip : public wxTipWindow @@ -31,26 +35,27 @@ public: } }; -wxDECLARE_EVENT(DOLPHIN_EVT_RELOAD_GAMELIST, wxCommandEvent); +wxDECLARE_EVENT(DOLPHIN_EVT_REFRESH_GAMELIST, wxCommandEvent); +wxDECLARE_EVENT(DOLPHIN_EVT_RESCAN_GAMELIST, wxCommandEvent); -class CGameListCtrl : public wxListCtrl +class GameListCtrl : public wxListCtrl { public: - CGameListCtrl(wxWindow* parent, const wxWindowID id, const wxPoint& pos, const wxSize& size, - long style); - ~CGameListCtrl(); + GameListCtrl(bool disable_scanning, wxWindow* parent, const wxWindowID id, const wxPoint& pos, + const wxSize& size, long style); + ~GameListCtrl(); void BrowseForDirectory(); const GameListItem* GetISO(size_t index) const; const GameListItem* GetSelectedISO() const; - std::vector GetAllSelectedISOs() const; static bool IsHidingItems(); enum { COLUMN_DUMMY = 0, - COLUMN_PLATFORM, + FIRST_COLUMN_WITH_CONTENT, + COLUMN_PLATFORM = FIRST_COLUMN_WITH_CONTENT, COLUMN_BANNER, COLUMN_TITLE, COLUMN_MAKER, @@ -69,17 +74,19 @@ public: private: struct ColumnInfo; - void ReloadList(); - - void ClearIsoFiles() { m_ISOFiles.clear(); } void InitBitmaps(); - void UpdateItemAtColumn(long _Index, int column); - void InsertItemInReportView(long _Index); + void UpdateItemAtColumn(long index, int column); + void InsertItemInReportView(long index); void SetColors(); - void ScanForISOs(); + void RefreshList(); + void RescanList(); + void DoState(PointerWrap* p, u32 size = 0); + bool SyncCacheFile(bool write); + std::vector GetAllSelectedISOs() const; // events - void OnReloadGameList(wxCommandEvent& event); + void OnRefreshGameList(wxCommandEvent& event); + void OnRescanGameList(wxCommandEvent& event); void OnLeftClick(wxMouseEvent& event); void OnRightClick(wxMouseEvent& event); void OnMouseMotion(wxMouseEvent& event); @@ -109,16 +116,27 @@ private: static bool MultiCompressCB(const std::string& text, float percent, void* arg); static bool WiiCompressWarning(); - std::vector m_FlagImageIndex; - std::vector m_PlatformImageIndex; - std::vector m_EmuStateImageIndex; - std::vector m_utility_game_banners; - std::vector> m_ISOFiles; + struct + { + std::vector flag; + std::vector platform; + std::vector utility_banner; + std::vector emu_state; + } m_image_indexes; - int last_column; - int last_sort; - wxSize lastpos; - wxEmuStateTip* toolTip; + // Actual backing GameListItems are maintained in a background thread and cached to file + static constexpr u32 CACHE_REVISION = 0; + std::list> 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> m_shown_files; + + int m_last_column; + int m_last_sort; + wxSize m_lastpos; + wxEmuStateTip* m_tooltip; std::vector m_columns; }; diff --git a/Source/Core/DolphinWX/ISOFile.cpp b/Source/Core/DolphinWX/ISOFile.cpp index b3faa0a7e8..6d6b0a7d94 100644 --- a/Source/Core/DolphinWX/ISOFile.cpp +++ b/Source/Core/DolphinWX/ISOFile.cpp @@ -38,8 +38,6 @@ #include "DolphinWX/ISOFile.h" #include "DolphinWX/WxUtils.h" -static const u32 CACHE_REVISION = 0x129; // Last changed in PR 5102 - static std::string GetLanguageString(DiscIO::Language language, std::map 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_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 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 volume(DiscIO::CreateVolumeFromFilename(_rFileName)); - if (volume != nullptr) { m_Platform = volume->GetVolumeType(); @@ -116,7 +96,6 @@ GameListItem::GameListItem(const std::string& _rFileName, const Core::TitleDatab ReadVolumeBanner(buffer, m_ImageWidth, m_ImageHeight); m_Valid = true; - SaveToCache(); } } @@ -138,37 +117,28 @@ GameListItem::GameListItem(const std::string& _rFileName, const Core::TitleDatab m_FileSize = File::GetSize(_rFileName); m_Platform = DiscIO::Platform::ELF_DOL; 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; } - - 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()) + else { - // 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. - m_image.Create(m_ImageWidth, m_ImageHeight, false); - std::memcpy(m_image.GetData(), m_pImage.data(), m_pImage.size()); - return; + // Volume banner. Typical for everything that isn't a DOL or ELF. + SetWxBannerFromRaw(); } } -GameListItem::~GameListItem() -{ -} - bool GameListItem::IsValid() const { if (!m_Valid) @@ -198,37 +168,35 @@ void GameListItem::ReloadINI() } } -bool GameListItem::LoadFromCache() -{ - return CChunkFileReader::Load(CreateCacheFilename(), CACHE_REVISION, *this); -} - -void GameListItem::SaveToCache() -{ - if (!File::IsDirectory(File::GetUserPath(D_CACHE_IDX))) - File::CreateDir(File::GetUserPath(D_CACHE_IDX)); - - CChunkFileReader::Save(CreateCacheFilename(), CACHE_REVISION, *this); -} - void GameListItem::DoState(PointerWrap& p) { + p.Do(m_FileName); p.Do(m_names); p.Do(m_descriptions); p.Do(m_company); p.Do(m_game_id); p.Do(m_title_id); + p.Do(m_issues); + p.Do(m_emu_state); p.Do(m_FileSize); p.Do(m_VolumeSize); p.Do(m_region); p.Do(m_Country); + p.Do(m_Platform); p.Do(m_blob_type); + p.Do(m_Revision); p.Do(m_pImage); + p.Do(m_Valid); p.Do(m_ImageWidth); p.Do(m_ImageHeight); - p.Do(m_Platform); 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 @@ -241,27 +209,6 @@ bool GameListItem::IsElfOrDol() const 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& buffer, int width, int height) { m_pImage.resize(width * height * 3); @@ -273,8 +220,7 @@ void GameListItem::ReadVolumeBanner(const std::vector& buffer, int width, i } } -// Outputs to m_Bitmap -bool GameListItem::ReadPNGBanner(const std::string& path) +bool GameListItem::SetWxBannerFromPngFile(const std::string& path) { if (!File::Exists(path)) return false; @@ -287,6 +233,37 @@ bool GameListItem::ReadPNGBanner(const std::string& path) 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 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 { return GetLanguageString(language, m_descriptions); diff --git a/Source/Core/DolphinWX/ISOFile.h b/Source/Core/DolphinWX/ISOFile.h index 479ab142bc..84f70c050b 100644 --- a/Source/Core/DolphinWX/ISOFile.h +++ b/Source/Core/DolphinWX/ISOFile.h @@ -32,8 +32,9 @@ class PointerWrap; class GameListItem { public: + GameListItem() = default; GameListItem(const std::string& file_name, const Core::TitleDatabase& title_database); - ~GameListItem(); + ~GameListItem() = default; // Reload settings after INI changes void ReloadINI(); @@ -65,8 +66,20 @@ public: // to display it const wxImage& GetBannerImage() const { return m_image; } void DoState(PointerWrap& p); + bool ReloadBannerIfNeeded(); private: + bool IsElfOrDol() const; + // Outputs to m_pImage + void ReadVolumeBanner(const std::vector& 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::map m_names; @@ -97,15 +110,4 @@ private: std::string m_custom_name_titles_txt; // Custom title from titles.txt std::string m_custom_name; // Custom title from INI or titles.txt 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& buffer, int width, int height); - // Outputs to m_Bitmap - bool ReadPNGBanner(const std::string& path); }; diff --git a/Source/Core/DolphinWX/Input/InputConfigDiag.cpp b/Source/Core/DolphinWX/Input/InputConfigDiag.cpp index f1e4e7e2f9..8aaa51ccb0 100644 --- a/Source/Core/DolphinWX/Input/InputConfigDiag.cpp +++ b/Source/Core/DolphinWX/Input/InputConfigDiag.cpp @@ -264,7 +264,7 @@ void InputConfigDialog::UpdateProfileComboBox() pname += PROFILES_PATH; pname += m_config.GetProfileName(); - std::vector sv = Common::DoFileSearch({".ini"}, {pname}); + std::vector sv = Common::DoFileSearch({pname}, {".ini"}); wxArrayString strs; for (const std::string& filename : sv) diff --git a/Source/Core/DolphinWX/Main.cpp b/Source/Core/DolphinWX/Main.cpp index a73729253f..70f34ab482 100644 --- a/Source/Core/DolphinWX/Main.cpp +++ b/Source/Core/DolphinWX/Main.cpp @@ -208,9 +208,6 @@ void DolphinApp::MacOpenFile(const wxString& fileName) void DolphinApp::AfterInit() { - if (!m_batch_mode) - main_frame->UpdateGameList(); - #if defined(USE_ANALYTICS) && USE_ANALYTICS if (!SConfig::GetInstance().m_analytics_permission_asked) { @@ -481,7 +478,7 @@ void Host_SetWiiMoteConnectionState(int _State) event.SetString(_("Wii Remote Connected")); break; } - // Update field 1 or 2 + // The second field is used for auxiliary info such as this event.SetInt(1); NOTICE_LOG(WIIMOTE, "%s", static_cast(event.GetString().c_str())); diff --git a/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.cpp b/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.cpp index b685e89b08..53ada89a9b 100644 --- a/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.cpp +++ b/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.cpp @@ -9,7 +9,7 @@ #include "DolphinWX/NetPlay/ChangeGameDialog.h" #include "DolphinWX/NetPlay/NetWindow.h" -ChangeGameDialog::ChangeGameDialog(wxWindow* parent, const CGameListCtrl* const game_list) +ChangeGameDialog::ChangeGameDialog(wxWindow* parent, const GameListCtrl* const game_list) : wxDialog(parent, wxID_ANY, _("Select Game")) { const int space5 = FromDIP(5); diff --git a/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.h b/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.h index 53c2200fa6..d0cee54597 100644 --- a/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.h +++ b/Source/Core/DolphinWX/NetPlay/ChangeGameDialog.h @@ -6,13 +6,13 @@ #include -class CGameListCtrl; +class GameListCtrl; class wxListBox; class ChangeGameDialog final : public wxDialog { public: - ChangeGameDialog(wxWindow* parent, const CGameListCtrl* const game_list); + ChangeGameDialog(wxWindow* parent, const GameListCtrl* const game_list); wxString GetChosenGameName() const; diff --git a/Source/Core/DolphinWX/NetPlay/NetPlayLauncher.h b/Source/Core/DolphinWX/NetPlay/NetPlayLauncher.h index 64297885ba..fa245dadc7 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlayLauncher.h +++ b/Source/Core/DolphinWX/NetPlay/NetPlayLauncher.h @@ -8,7 +8,7 @@ #include "Common/CommonTypes.h" #include "Common/IniFile.h" -class CGameListCtrl; +class GameListCtrl; class wxRect; class wxWindow; @@ -24,7 +24,7 @@ public: const wxRect window_defaults{wxDefaultCoord, wxDefaultCoord, 768, 768 - 128}; std::string player_name; - const CGameListCtrl* game_list_ctrl; + const GameListCtrl* game_list_ctrl; wxWindow* parent_window; bool use_traversal; std::string traversal_host; diff --git a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp index 14967fad76..1fd401ea37 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp +++ b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp @@ -36,7 +36,7 @@ wxString GetTraversalLabelText(IniFile::Section& section) } } // Anonymous namespace -NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl* const game_list) +NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const GameListCtrl* const game_list) : wxFrame(parent, wxID_ANY, _("Dolphin NetPlay Setup")), m_game_list(game_list) { IniFile inifile; diff --git a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h index a91e26989b..1e8cd170ab 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h +++ b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h @@ -8,7 +8,7 @@ #include #include -class CGameListCtrl; +class GameListCtrl; class wxCheckBox; class wxChoice; class wxListBox; @@ -20,7 +20,7 @@ class wxTextCtrl; class NetPlaySetupFrame final : public wxFrame { public: - NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl* const game_list); + NetPlaySetupFrame(wxWindow* const parent, const GameListCtrl* const game_list); ~NetPlaySetupFrame(); private: @@ -66,5 +66,5 @@ private: #endif wxString m_traversal_string; - const CGameListCtrl* const m_game_list; + const GameListCtrl* const m_game_list; }; diff --git a/Source/Core/DolphinWX/NetPlay/NetWindow.cpp b/Source/Core/DolphinWX/NetPlay/NetWindow.cpp index 25c015877c..98aefef953 100644 --- a/Source/Core/DolphinWX/NetPlay/NetWindow.cpp +++ b/Source/Core/DolphinWX/NetPlay/NetWindow.cpp @@ -58,13 +58,13 @@ NetPlayServer* NetPlayDialog::netplay_server = nullptr; NetPlayClient* NetPlayDialog::netplay_client = nullptr; NetPlayDialog* NetPlayDialog::npd = nullptr; -void NetPlayDialog::FillWithGameNames(wxListBox* game_lbox, const CGameListCtrl& game_list) +void NetPlayDialog::FillWithGameNames(wxListBox* game_lbox, const GameListCtrl& game_list) { for (u32 i = 0; auto game = game_list.GetISO(i); ++i) game_lbox->Append(StrToWxStr(game->GetUniqueIdentifier())); } -NetPlayDialog::NetPlayDialog(wxWindow* const parent, const CGameListCtrl* const game_list, +NetPlayDialog::NetPlayDialog(wxWindow* const parent, const GameListCtrl* const game_list, const std::string& game, const bool is_hosting) : wxFrame(parent, wxID_ANY, _("Dolphin NetPlay")), m_selected_game(game), m_start_btn(nullptr), m_host_label(nullptr), m_host_type_choice(nullptr), m_host_copy_btn(nullptr), diff --git a/Source/Core/DolphinWX/NetPlay/NetWindow.h b/Source/Core/DolphinWX/NetPlay/NetWindow.h index 84b60ad40f..ce77a6dfd7 100644 --- a/Source/Core/DolphinWX/NetPlay/NetWindow.h +++ b/Source/Core/DolphinWX/NetPlay/NetWindow.h @@ -25,7 +25,7 @@ #include #endif -class CGameListCtrl; +class GameListCtrl; class MD5Dialog; class wxButton; class wxCheckBox; @@ -78,7 +78,7 @@ enum class MD5Target class NetPlayDialog : public wxFrame, public NetPlayUI { public: - NetPlayDialog(wxWindow* parent, const CGameListCtrl* const game_list, const std::string& game, + NetPlayDialog(wxWindow* parent, const GameListCtrl* const game_list, const std::string& game, const bool is_hosting = false); ~NetPlayDialog(); @@ -107,7 +107,7 @@ public: static NetPlayDialog*& GetInstance() { return npd; } static NetPlayClient*& GetNetPlayClient() { return netplay_client; } static NetPlayServer*& GetNetPlayServer() { return netplay_server; } - static void FillWithGameNames(wxListBox* game_lbox, const CGameListCtrl& game_list); + static void FillWithGameNames(wxListBox* game_lbox, const GameListCtrl& game_list); bool IsRecording() override; @@ -163,7 +163,7 @@ private: std::vector m_playerids; Common::FifoQueue m_chat_msgs; - const CGameListCtrl* const m_game_list; + const GameListCtrl* const m_game_list; static NetPlayDialog* npd; static NetPlayServer* netplay_server; diff --git a/Source/Core/VideoCommon/HiresTextures.cpp b/Source/Core/VideoCommon/HiresTextures.cpp index 7b4a1d387c..36b170ef10 100644 --- a/Source/Core/VideoCommon/HiresTextures.cpp +++ b/Source/Core/VideoCommon/HiresTextures.cpp @@ -97,7 +97,7 @@ void HiresTexture::Update() }; std::vector filenames = - Common::DoFileSearch(extensions, {texture_directory}, /*recursive*/ true); + Common::DoFileSearch({texture_directory}, extensions, /*recursive*/ true); const std::string code = game_id + "_"; diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp index 71f38732e7..0575402dd1 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -31,8 +31,9 @@ PostProcessingShaderImplementation::~PostProcessingShaderImplementation() static std::vector GetShaders(const std::string& sub_dir = "") { std::vector paths = - Common::DoFileSearch({".glsl"}, {File::GetUserPath(D_SHADERS_IDX) + sub_dir, - File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir}); + Common::DoFileSearch({File::GetUserPath(D_SHADERS_IDX) + sub_dir, + File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir}, + {".glsl"}); std::vector result; for (std::string path : paths) {