From 9cb35a89729ca3a097cab3218a752331fd3b08ae Mon Sep 17 00:00:00 2001 From: arcum42 Date: Sun, 26 Aug 2018 15:06:35 -0700 Subject: [PATCH] PCSX2: Save/load slot improvements. With these changes, saveslots will be labeled as either empty or with the date that the file was last updated on. The menu items for loading them are also disabled if the slot is empty. It's possible if you are very fast to access the menu before the slots change. It updates when the crc changes. When you save a saveslot, the menu item also changes to show the time you told it to save until the file is actually done saving. Also fix an issue with backup saveslots not working properly from the gui on first load. --- pcsx2/CMakeLists.txt | 1 + pcsx2/System/SysCoreThread.cpp | 1 + pcsx2/gui/AppSaveStates.h | 13 +-- pcsx2/gui/MainFrame.cpp | 12 +- pcsx2/gui/MainFrame.h | 1 + pcsx2/gui/Saveslots.cpp | 108 ++++++++---------- pcsx2/gui/Saveslots.h | 100 ++++++++++++++++ pcsx2/gui/SysState.cpp | 3 + pcsx2/gui/UpdateUI.cpp | 95 ++++++++++----- pcsx2/windows/VCprojects/pcsx2.vcxproj | 1 + .../windows/VCprojects/pcsx2.vcxproj.filters | 3 + 11 files changed, 232 insertions(+), 106 deletions(-) create mode 100644 pcsx2/gui/Saveslots.h diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index f14cfac5fb..640c1c4521 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -343,6 +343,7 @@ set(pcsx2GuiHeaders gui/Panels/MemoryCardPanels.h gui/pxEventThread.h gui/RecentIsoList.h + gui/Saveslots.h ) # Warning: the declaration of the .h are mandatory in case of resources files. It will ensure the creation diff --git a/pcsx2/System/SysCoreThread.cpp b/pcsx2/System/SysCoreThread.cpp index 7a9d28240c..78c73d0c14 100644 --- a/pcsx2/System/SysCoreThread.cpp +++ b/pcsx2/System/SysCoreThread.cpp @@ -236,6 +236,7 @@ void SysCoreThread::GameStartingInThread() sApp.PostAppMethod(&Pcsx2App::resetDebugger); ApplyLoadedPatches(PPT_ONCE_ON_LOAD); + UI_UpdateSysControls(); } bool SysCoreThread::StateCheckInThread() diff --git a/pcsx2/gui/AppSaveStates.h b/pcsx2/gui/AppSaveStates.h index 8fee5eb198..2925f39e2f 100644 --- a/pcsx2/gui/AppSaveStates.h +++ b/pcsx2/gui/AppSaveStates.h @@ -17,6 +17,7 @@ #include "App.h" #include "SaveState.h" +#include "Saveslots.h" // -------------------------------------------------------------------------------------- // SysExecEvent_SaveSinglePlugin @@ -60,15 +61,3 @@ extern void StateCopy_SaveToFile( const wxString& file ); extern void StateCopy_LoadFromFile( const wxString& file ); extern void StateCopy_SaveToSlot( uint num ); extern void StateCopy_LoadFromSlot( uint slot, bool isFromBackup = false ); - -extern void States_registerLoadBackupMenuItem( wxMenuItem* loadBackupMenuItem ); - -extern bool States_isSlotUsed(int num); -extern void States_DefrostCurrentSlotBackup(); -extern void States_DefrostCurrentSlot(); -extern void States_FreezeCurrentSlot(); -extern void States_CycleSlotForward(); -extern void States_CycleSlotBackward(); - -extern void States_SetCurrentSlot( int slot ); -extern int States_GetCurrentSlot(); diff --git a/pcsx2/gui/MainFrame.cpp b/pcsx2/gui/MainFrame.cpp index 66fb797bff..892373bfdf 100644 --- a/pcsx2/gui/MainFrame.cpp +++ b/pcsx2/gui/MainFrame.cpp @@ -28,6 +28,7 @@ #include "AppAccelerators.h" #include "svnrev.h" +#include "Saveslots.h" // ------------------------------------------------------------------------ wxMenu* MainEmuFrame::MakeStatesSubMenu( int baseid, int loadBackupId ) const @@ -36,18 +37,18 @@ wxMenu* MainEmuFrame::MakeStatesSubMenu( int baseid, int loadBackupId ) const for (int i = 0; i < 10; i++) { - mnuSubstates->Append( baseid+i+1, wxsFormat(_("Slot %d"), i) ); + // Will be changed once an iso is loaded. + mnuSubstates->Append(baseid + i + 1, wxsFormat(_("Slot %d"), i)); } - if( loadBackupId>=0 ) + + if (loadBackupId >= 0) { mnuSubstates->AppendSeparator(); - wxMenuItem* m = mnuSubstates->Append( loadBackupId, _("Backup") ); + wxMenuItem* m = mnuSubstates->Append(loadBackupId, _("Backup")); m->Enable( false ); - States_registerLoadBackupMenuItem( m ); } - //mnuSubstates->Append( baseid - 1, _("Other...") ); return mnuSubstates; } @@ -787,4 +788,3 @@ void PerPluginMenuInfo::OnLoaded() ); MyMenu.Enable( GetPluginMenuId_Settings(PluginId), true ); } - diff --git a/pcsx2/gui/MainFrame.h b/pcsx2/gui/MainFrame.h index 07abbb17ac..3f8c47e490 100644 --- a/pcsx2/gui/MainFrame.h +++ b/pcsx2/gui/MainFrame.h @@ -137,6 +137,7 @@ public: void UpdateIsoSrcSelection(); void RemoveCdvdMenu(); void EnableMenuItem( int id, bool enable ); + void SetMenuItemLabel(int id, wxString str); void EnableCdvdPluginSubmenu(bool isEnable = true); bool Destroy(); diff --git a/pcsx2/gui/Saveslots.cpp b/pcsx2/gui/Saveslots.cpp index 3c86f369c6..57a11dc2f2 100644 --- a/pcsx2/gui/Saveslots.cpp +++ b/pcsx2/gui/Saveslots.cpp @@ -17,11 +17,13 @@ #include "App.h" #include "AppSaveStates.h" #include "ConsoleLogger.h" +#include "MainFrame.h" #include "Common.h" #include "GS.h" #include "Elfheader.h" +#include "Saveslots.h" // -------------------------------------------------------------------------------------- // Saveslot Section @@ -29,15 +31,7 @@ static int StatesC = 0; static const int StateSlotsCount = 10; -static wxMenuItem* g_loadBackupMenuItem =NULL; - -bool States_isSlotUsed(int num) -{ - if (ElfCRC == 0) - return false; - else - return wxFileExists( SaveStateBase::GetFilename( num ) ); -} +Saveslot saveslot_cache[10] = {{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}}; // FIXME : Use of the IsSavingOrLoading flag is mostly a hack until we implement a // complete thread to manage queuing savestate tasks, and zipping states to disk. --air @@ -49,90 +43,101 @@ public: wxString GetEventName() const { return L"ClearSavingLoadingFlag"; } virtual ~SysExecEvent_ClearSavingLoadingFlag() = default; - SysExecEvent_ClearSavingLoadingFlag() - { - } - - SysExecEvent_ClearSavingLoadingFlag* Clone() const { return new SysExecEvent_ClearSavingLoadingFlag(); } + SysExecEvent_ClearSavingLoadingFlag() { } + SysExecEvent_ClearSavingLoadingFlag *Clone() const { return new SysExecEvent_ClearSavingLoadingFlag(); } protected: void InvokeEvent() { IsSavingOrLoading = false; + UI_UpdateSysControls(); } }; -void Sstates_updateLoadBackupMenuItem( bool isBeforeSave = false); +void Sstates_updateLoadBackupMenuItem(bool isBeforeSave); void States_FreezeCurrentSlot() { // FIXME : Use of the IsSavingOrLoading flag is mostly a hack until we implement a // complete thread to manage queuing savestate tasks, and zipping states to disk. --air - if( !SysHasValidState() ) + if (!SysHasValidState()) { - Console.WriteLn( "Save state: Aborting (VM is not active)." ); + Console.WriteLn("Save state: Aborting (VM is not active)."); return; } - if( wxGetApp().HasPendingSaves() || IsSavingOrLoading.exchange(true) ) + if (wxGetApp().HasPendingSaves() || IsSavingOrLoading.exchange(true)) { - Console.WriteLn( "Load or save action is already pending." ); + Console.WriteLn("Load or save action is already pending."); return; } - Sstates_updateLoadBackupMenuItem( true ); + Sstates_updateLoadBackupMenuItem(true); - GSchangeSaveState( StatesC, SaveStateBase::GetFilename( StatesC ).ToUTF8() ); - StateCopy_SaveToSlot( StatesC ); - - GetSysExecutorThread().PostIdleEvent( SysExecEvent_ClearSavingLoadingFlag() ); + GSchangeSaveState(StatesC, SaveStateBase::GetFilename(StatesC).ToUTF8()); + StateCopy_SaveToSlot(StatesC); + + // Hack: Update the saveslot saying it's filled *right now* because it's still writing the file and we don't have a timestamp. + saveslot_cache[StatesC].empty = false; + saveslot_cache[StatesC].updated = wxDateTime::Now(); + saveslot_cache[StatesC].crc = ElfCRC; + + GetSysExecutorThread().PostIdleEvent(SysExecEvent_ClearSavingLoadingFlag()); } -void _States_DefrostCurrentSlot( bool isFromBackup ) +void _States_DefrostCurrentSlot(bool isFromBackup) { - if( !SysHasValidState() ) + if (!SysHasValidState()) { - Console.WriteLn( "Load state: Aborting (VM is not active)." ); + Console.WriteLn("Load state: Aborting (VM is not active)."); return; } - if( IsSavingOrLoading.exchange(true) ) + if (IsSavingOrLoading.exchange(true)) { - Console.WriteLn( "Load or save action is already pending." ); + Console.WriteLn("Load or save action is already pending."); return; } - GSchangeSaveState( StatesC, SaveStateBase::GetFilename( StatesC ).ToUTF8() ); - StateCopy_LoadFromSlot( StatesC, isFromBackup ); + GSchangeSaveState(StatesC, SaveStateBase::GetFilename(StatesC).ToUTF8()); + StateCopy_LoadFromSlot(StatesC, isFromBackup); - GetSysExecutorThread().PostIdleEvent( SysExecEvent_ClearSavingLoadingFlag() ); + GetSysExecutorThread().PostIdleEvent(SysExecEvent_ClearSavingLoadingFlag()); - Sstates_updateLoadBackupMenuItem(); + Sstates_updateLoadBackupMenuItem(false); } void States_DefrostCurrentSlot() { - _States_DefrostCurrentSlot( false ); + _States_DefrostCurrentSlot(false); } void States_DefrostCurrentSlotBackup() { - _States_DefrostCurrentSlot( true ); + _States_DefrostCurrentSlot(true); } - -void States_registerLoadBackupMenuItem( wxMenuItem* loadBackupMenuItem ) +// I'd keep an eye on this function, as it may still be problematic. +void Sstates_updateLoadBackupMenuItem(bool isBeforeSave) { - g_loadBackupMenuItem = loadBackupMenuItem; + wxString file = SaveStateBase::GetFilename(StatesC); + + if (!(isBeforeSave && g_Conf->EmuOptions.BackupSavestate)) + { + file = file + L".backup"; + } + + sMainFrame.EnableMenuItem(MenuId_State_LoadBackup, wxFileExists(file)); + sMainFrame.SetMenuItemLabel(MenuId_State_LoadBackup, wxsFormat(L"%s %d", _("Backup"), StatesC)); } static void OnSlotChanged() { - OSDlog( Color_StrongGreen, true, " > Selected savestate slot %d", StatesC ); + OSDlog(Color_StrongGreen, true, " > Selected savestate slot %d", StatesC); - if( GSchangeSaveState != NULL ) + if (GSchangeSaveState != NULL) GSchangeSaveState(StatesC, SaveStateBase::GetFilename(StatesC).utf8_str()); - Sstates_updateLoadBackupMenuItem(); + Sstates_updateLoadBackupMenuItem(false); } int States_GetCurrentSlot() @@ -140,33 +145,20 @@ int States_GetCurrentSlot() return StatesC; } -void Sstates_updateLoadBackupMenuItem( bool isBeforeSave ) +void States_SetCurrentSlot(int slot) { - if( !g_loadBackupMenuItem ) return; - - int slot = States_GetCurrentSlot(); - wxString file = SaveStateBase::GetFilename( slot ); - g_loadBackupMenuItem->Enable( wxFileExists( isBeforeSave && g_Conf->EmuOptions.BackupSavestate ? file : file + L".backup" ) ); - wxString label; - label.Printf(L"%s %d", _("Backup"), slot ); - g_loadBackupMenuItem->SetItemLabel( label ); -} - -void States_SetCurrentSlot( int slot ) -{ - StatesC = std::min( std::max( slot, 0 ), StateSlotsCount ); + StatesC = std::min(std::max(slot, 0), StateSlotsCount); OnSlotChanged(); } void States_CycleSlotForward() { - StatesC = (StatesC+1) % StateSlotsCount; + StatesC = (StatesC + 1) % StateSlotsCount; OnSlotChanged(); } void States_CycleSlotBackward() { - StatesC = (StatesC+StateSlotsCount-1) % StateSlotsCount; + StatesC = (StatesC + StateSlotsCount - 1) % StateSlotsCount; OnSlotChanged(); } - diff --git a/pcsx2/gui/Saveslots.h b/pcsx2/gui/Saveslots.h new file mode 100644 index 0000000000..3886d76c48 --- /dev/null +++ b/pcsx2/gui/Saveslots.h @@ -0,0 +1,100 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2018 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + + +#pragma once + +#include "PS2Edefs.h" +#include "System.h" +#include "Elfheader.h" + +class Saveslot +{ +public: + u32 slot_num; + bool empty; + wxDateTime updated; + u32 crc; + + Saveslot() + { + slot_num = 0; + empty = true; + updated = wxInvalidDateTime; + crc = ElfCRC; + } + + Saveslot(int i) + { + slot_num = i; + empty = true; + updated = wxInvalidDateTime; + crc = ElfCRC; + } + + bool isUsed() + { + return wxFileExists(SaveStateBase::GetFilename(slot_num)); + } + + wxDateTime GetTimestamp() + { + if (!isUsed()) return wxInvalidDateTime; + + return wxDateTime(wxFileModificationTime(SaveStateBase::GetFilename(slot_num))); + } + + void UpdateCache() + { + empty = !isUsed(); + updated = GetTimestamp(); + crc = ElfCRC; + } + + wxString SlotName() + { + if (empty) return wxsFormat(_("Slot %d - Empty"), slot_num); + + if (updated != wxInvalidDateTime) return wxsFormat(_("Slot %d - %s %s"), slot_num, updated.FormatDate(), updated.FormatTime()); + + return wxsFormat(_("Slot %d - Unknown Time"), slot_num); + } + + void ConsoleDump() + { + Console.WriteLn("Slot %i information:", slot_num); + Console.WriteLn("Internal CRC = %i; Current CRC = %i.", crc, ElfCRC); + if (empty) + Console.WriteLn("Slot cache says it is empty."); + else + Console.WriteLn("Slot cache says it is used."); + + if (updated != wxInvalidDateTime) + Console.WriteLn(wxsFormat(_("Write time is %s %s."), updated.FormatDate(), updated.FormatTime())); + + if (isUsed()) + Console.WriteLn(wxsFormat(_("The disk has a file on it dated %s %s."), GetTimestamp().FormatDate(), GetTimestamp().FormatTime())); + } +}; + +extern Saveslot saveslot_cache[10]; +extern void States_DefrostCurrentSlotBackup(); +extern void States_DefrostCurrentSlot(); +extern void States_FreezeCurrentSlot(); +extern void States_CycleSlotForward(); +extern void States_CycleSlotBackward(); +extern void States_SetCurrentSlot(int slot); +extern int States_GetCurrentSlot(); +extern void Sstates_updateLoadBackupMenuItem(bool isBeforeSave); \ No newline at end of file diff --git a/pcsx2/gui/SysState.cpp b/pcsx2/gui/SysState.cpp index 11dceb9e23..caeec947ab 100644 --- a/pcsx2/gui/SysState.cpp +++ b/pcsx2/gui/SysState.cpp @@ -476,6 +476,7 @@ protected: void CleanupEvent() { + UI_UpdateSysControls(); } }; @@ -674,6 +675,7 @@ void StateCopy_SaveToSlot( uint num ) Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", WX_STR(file) ); StateCopy_SaveToFile( file ); + UI_UpdateSysControls(); } void StateCopy_LoadFromSlot( uint slot, bool isFromBackup ) @@ -690,4 +692,5 @@ void StateCopy_LoadFromSlot( uint slot, bool isFromBackup ) Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", WX_STR(file) ); StateCopy_LoadFromFile( file ); + UI_UpdateSysControls(); } diff --git a/pcsx2/gui/UpdateUI.cpp b/pcsx2/gui/UpdateUI.cpp index 5397e2a96b..01b439d6d4 100644 --- a/pcsx2/gui/UpdateUI.cpp +++ b/pcsx2/gui/UpdateUI.cpp @@ -19,84 +19,119 @@ // General Notes: // * It's very important that we re-discover menu items by ID every time we change them, -// because the modern era of configurable GUIs means that we can't be assured the IDs -// exist anymore. +// because the modern era of configurable GUIs means that we can't be assured the IDs +// exist anymore. // This is necessary because this stupid wxWidgets thing has implicit debug errors // in the FindItem call that asserts if the menu options are missing. This is bad // mojo for configurable/dynamic menus. >_< -void MainEmuFrame::EnableMenuItem( int id, bool enable ) +void MainEmuFrame::EnableMenuItem(int id, bool enable) { - if( wxMenuItem* item = m_menubar.FindItem(id) ) - item->Enable( enable ); + if (wxMenuItem *item = m_menubar.FindItem(id)) + item->Enable(enable); } -static void _SaveLoadStuff( bool enabled ) +void MainEmuFrame::SetMenuItemLabel(int id, wxString str) { - sMainFrame.EnableMenuItem( MenuId_Sys_LoadStates, enabled ); - sMainFrame.EnableMenuItem( MenuId_Sys_SaveStates, enabled ); + if (wxMenuItem *item = m_menubar.FindItem(id)) + item->SetItemLabel(str); +} + +static void _SaveLoadStuff(bool enabled) +{ + sMainFrame.EnableMenuItem(MenuId_Sys_LoadStates, enabled); + sMainFrame.EnableMenuItem(MenuId_Sys_SaveStates, enabled); + + for (int i = 0; i < 10; i++) + { + int load_menu_item = MenuId_State_Load01 + i + 1; + int save_menu_item = MenuId_State_Save01 + i + 1; + + // If the cache is out of sync with the actual files, we need to update things. First update, the cache'll be blank, and this will populate everything. + if (saveslot_cache[i].empty == saveslot_cache[i].isUsed()) + { + // If there is actually a file there, or the cache was for a different game, we force an update. + // If the cache says there's a saveslot for the current game that there isn't a file for, writing it is done in a different thread, + // so it might not be written yet. Which is why I cache to begin with. + if (saveslot_cache[i].isUsed() || (saveslot_cache[i].crc != ElfCRC)) + { + saveslot_cache[i].UpdateCache(); + } + } + + sMainFrame.EnableMenuItem(load_menu_item, !saveslot_cache[i].empty); + sMainFrame.SetMenuItemLabel(load_menu_item, saveslot_cache[i].SlotName()); + sMainFrame.SetMenuItemLabel(save_menu_item, saveslot_cache[i].SlotName()); + } + Sstates_updateLoadBackupMenuItem(false); } // Updates the enable/disable status of all System related controls: menus, toolbars, // etc. Typically called by SysEvtHandler whenever the message pump becomes idle. void UI_UpdateSysControls() { - if( wxGetApp().Rpc_TryInvokeAsync( &UI_UpdateSysControls ) ) return; + if (wxGetApp().Rpc_TryInvokeAsync(&UI_UpdateSysControls)) + return; - sApp.PostAction( CoreThreadStatusEvent( CoreThread_Indeterminate ) ); + sApp.PostAction(CoreThreadStatusEvent(CoreThread_Indeterminate)); - //_SaveLoadStuff( true ); - _SaveLoadStuff( SysHasValidState() ); + _SaveLoadStuff(SysHasValidState()); } void UI_DisableSysShutdown() { - if( wxGetApp().Rpc_TryInvokeAsync( &UI_DisableSysShutdown ) ) return; + if (wxGetApp().Rpc_TryInvokeAsync(&UI_DisableSysShutdown)) + return; - sMainFrame.EnableMenuItem( MenuId_Sys_Shutdown, false ); + sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, false); sMainFrame.EnableMenuItem(MenuId_IsoBrowse, !g_Conf->AskOnBoot); wxGetApp().GetRecentIsoManager().EnableItems(!g_Conf->AskOnBoot); } void UI_EnableSysShutdown() { - if( wxGetApp().Rpc_TryInvokeAsync( &UI_EnableSysShutdown ) ) return; + if (wxGetApp().Rpc_TryInvokeAsync(&UI_EnableSysShutdown)) + return; - sMainFrame.EnableMenuItem( MenuId_Sys_Shutdown, true ); + sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, true); } void UI_DisableSysActions() { - if( wxGetApp().Rpc_TryInvokeAsync( &UI_DisableSysActions ) ) return; + if (wxGetApp().Rpc_TryInvokeAsync(&UI_DisableSysActions)) + return; - sMainFrame.EnableMenuItem( MenuId_Sys_Shutdown, false ); - - _SaveLoadStuff( false ); + sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, false); + + _SaveLoadStuff(false); } void UI_EnableSysActions() { - if( wxGetApp().Rpc_TryInvokeAsync( &UI_EnableSysActions ) ) return; + if (wxGetApp().Rpc_TryInvokeAsync(&UI_EnableSysActions)) + return; - sMainFrame.EnableMenuItem( MenuId_Sys_Shutdown, true ); + sMainFrame.EnableMenuItem(MenuId_Sys_Shutdown, true); sMainFrame.EnableMenuItem(MenuId_IsoBrowse, true); wxGetApp().GetRecentIsoManager().EnableItems(true); - - _SaveLoadStuff( true ); + + _SaveLoadStuff(true); } void UI_DisableStateActions() { - if( wxGetApp().Rpc_TryInvokeAsync( &UI_DisableStateActions ) ) return; - _SaveLoadStuff( false ); + if (wxGetApp().Rpc_TryInvokeAsync(&UI_DisableStateActions)) + return; + + _SaveLoadStuff(false); } void UI_EnableStateActions() { - if( wxGetApp().Rpc_TryInvokeAsync( &UI_EnableStateActions ) ) return; - _SaveLoadStuff( true ); + if (wxGetApp().Rpc_TryInvokeAsync(&UI_EnableStateActions)) + return; + + _SaveLoadStuff(true); } - - diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj b/pcsx2/windows/VCprojects/pcsx2.vcxproj index 6318f431eb..3f79092097 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj @@ -427,6 +427,7 @@ + diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters index 0243efc87a..f781ab956b 100644 --- a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters +++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters @@ -1197,6 +1197,9 @@ AppHost\Include + + AppHost\Include + AppHost\Cheats