2022-08-22 22:21:23 +02:00
# include "gui/wxgui.h"
# include "gui/GameUpdateWindow.h"
# include "util/helpers/helpers.h"
# include <filesystem>
# include <sstream>
# include "util/helpers/SystemException.h"
# include "gui/CemuApp.h"
# include "Cafe/TitleList/GameInfo.h"
# include "gui/helpers/wxHelpers.h"
# include "wxHelper.h"
std : : string _GetTitleIdTypeStr ( TitleId titleId )
{
TitleIdParser tip ( titleId ) ;
switch ( tip . GetType ( ) )
{
case TitleIdParser : : TITLE_TYPE : : AOC :
return _ ( " DLC " ) . ToStdString ( ) ;
case TitleIdParser : : TITLE_TYPE : : BASE_TITLE :
return _ ( " Base game " ) . ToStdString ( ) ;
case TitleIdParser : : TITLE_TYPE : : BASE_TITLE_DEMO :
return _ ( " Demo " ) . ToStdString ( ) ;
case TitleIdParser : : TITLE_TYPE : : SYSTEM_TITLE :
case TitleIdParser : : TITLE_TYPE : : SYSTEM_OVERLAY_TITLE :
return _ ( " System title " ) . ToStdString ( ) ;
case TitleIdParser : : TITLE_TYPE : : SYSTEM_DATA :
return _ ( " System data title " ) . ToStdString ( ) ;
case TitleIdParser : : TITLE_TYPE : : BASE_TITLE_UPDATE :
return _ ( " Update " ) . ToStdString ( ) ;
default :
break ;
}
return " Unknown " ;
}
bool IsRunningInWine ( ) ;
bool GameUpdateWindow : : ParseUpdate ( const fs : : path & metaPath )
{
m_title_info = TitleInfo ( metaPath ) ;
if ( ! m_title_info . IsValid ( ) )
return false ;
fs : : path target_location = ActiveSettings : : GetMlcPath ( m_title_info . GetInstallPath ( ) ) ;
std : : error_code ec ;
if ( fs : : exists ( target_location , ec ) )
{
try
{
const TitleInfo tmp ( target_location ) ;
if ( ! tmp . IsValid ( ) )
{
// does not exist / is not valid. We allow to overwrite it
}
else
{
TitleIdParser tip ( m_title_info . GetAppTitleId ( ) ) ;
TitleIdParser tipOther ( tmp . GetAppTitleId ( ) ) ;
if ( tip . GetType ( ) ! = tipOther . GetType ( ) )
{
std : : string typeStrToInstall = _GetTitleIdTypeStr ( m_title_info . GetAppTitleId ( ) ) ;
std : : string typeStrCurrentlyInstalled = _GetTitleIdTypeStr ( tmp . GetAppTitleId ( ) ) ;
std : : string wxMsg = wxHelper : : MakeUTF8 ( _ ( " It seems that there is already a title installed at the target location but it has a different type. \n Currently installed: \' {} \' Installing: \' {} \' \n \n This can happen for titles which were installed with very old Cemu versions. \n Do you still want to continue with the installation? It will replace the currently installed title. " ) ) ;
2022-09-05 15:48:44 +03:00
wxMessageDialog dialog ( this , fmt : : format ( fmt : : runtime ( wxMsg ) , typeStrCurrentlyInstalled , typeStrToInstall ) , _ ( " Warning " ) , wxCENTRE | wxYES_NO | wxICON_EXCLAMATION ) ;
2022-08-22 22:21:23 +02:00
if ( dialog . ShowModal ( ) ! = wxID_YES )
return false ;
}
else if ( tmp . GetAppTitleVersion ( ) = = m_title_info . GetAppTitleVersion ( ) )
{
wxMessageDialog dialog ( this , _ ( " It seems that the selected title is already installed, do you want to reinstall it? " ) , _ ( " Warning " ) , wxCENTRE | wxYES_NO ) ;
if ( dialog . ShowModal ( ) ! = wxID_YES )
return false ;
}
else if ( tmp . GetAppTitleVersion ( ) > m_title_info . GetAppTitleVersion ( ) )
{
wxMessageDialog dialog ( this , _ ( " It seems that a newer version is already installed, do you still want to install the older version? " ) , _ ( " Warning " ) , wxCENTRE | wxYES_NO ) ;
if ( dialog . ShowModal ( ) ! = wxID_YES )
return false ;
}
}
// temp rename until done
m_backup_folder = target_location ;
m_backup_folder . replace_extension ( " .backup " ) ;
std : : error_code ec ;
while ( fs : : exists ( m_backup_folder , ec ) | | ec )
{
fs : : remove_all ( m_backup_folder , ec ) ;
if ( ec )
{
const auto error_msg = wxStringFormat2 ( _ ( " Error when trying to move former title installation: \n {} " ) , GetSystemErrorMessage ( ec ) ) ;
wxMessageBox ( error_msg , _ ( " Error " ) , wxOK | wxCENTRE , this ) ;
return false ;
}
// wait so filesystem doesnt
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 100 ) ) ;
}
fs : : rename ( target_location , m_backup_folder ) ;
}
catch ( const std : : exception & ex )
{
forceLog_printf ( " GameUpdateWindow::ParseUpdate exist-error: %s at %s " , ex . what ( ) , target_location . generic_u8string ( ) . c_str ( ) ) ;
}
}
m_target_path = target_location ;
fs : : path source ( metaPath ) ;
m_source_paths =
{
fs : : path ( source ) . append ( " content " ) ,
fs : : path ( source ) . append ( " code " ) ,
fs : : path ( source ) . append ( " meta " )
} ;
m_required_size = 0 ;
for ( auto & path : m_source_paths )
{
for ( const fs : : directory_entry & f : fs : : recursive_directory_iterator ( path ) )
{
if ( is_regular_file ( f . path ( ) ) )
m_required_size + = file_size ( f . path ( ) ) ;
}
}
// checking size is buggy on Wine (on Steam Deck this would return values too small to install bigger updates) - we therefore skip this step
if ( ! IsRunningInWine ( ) )
{
2022-11-18 13:46:05 +02:00
const fs : : space_info targetSpace = fs : : space ( ActiveSettings : : GetMlcPath ( ) ) ;
2022-08-22 22:21:23 +02:00
if ( targetSpace . free < = m_required_size )
{
auto string = wxStringFormat ( _ ( " Not enough space available. \n Required: {0} MB \n Available: {1} MB " ) , L " %lld %lld " , ( m_required_size / 1024 / 1024 ) , ( targetSpace . free / 1024 / 1024 ) ) ;
throw std : : runtime_error ( string ) ;
}
}
return true ;
}
GameUpdateWindow : : GameUpdateWindow ( wxWindow & parent , const fs : : path & filePath )
: wxDialog ( & parent , wxID_ANY , wxEmptyString , wxDefaultPosition , wxDefaultSize , wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX ) ,
m_thread_state ( ThreadRunning )
{
try
{
# if BOOST_OS_WINDOWS
SetLastError ( 0 ) ;
# endif
if ( ! ParseUpdate ( filePath ) )
throw AbortException ( ) ;
}
catch ( const std : : runtime_error & ex )
{
throw SystemException ( ex ) ;
}
auto sizer = new wxBoxSizer ( wxVERTICAL ) ;
TitleIdParser tip ( GetTitleId ( ) ) ;
if ( tip . GetType ( ) = = TitleIdParser : : TITLE_TYPE : : AOC )
2022-09-03 22:55:58 +02:00
SetTitle ( _ ( " Installing DLC... " ) ) ;
2022-08-22 22:21:23 +02:00
else if ( tip . GetType ( ) = = TitleIdParser : : TITLE_TYPE : : BASE_TITLE_UPDATE )
2022-09-03 22:55:58 +02:00
SetTitle ( _ ( " Installing update... " ) ) ;
2022-08-22 22:21:23 +02:00
else if ( tip . IsSystemTitle ( ) )
2022-09-03 22:55:58 +02:00
SetTitle ( _ ( " Installing system title... " ) ) ;
2022-08-22 22:21:23 +02:00
else
2022-09-03 22:55:58 +02:00
SetTitle ( _ ( " Installing title... " ) ) ;
2022-08-22 22:21:23 +02:00
m_processBar = new wxGauge ( this , wxID_ANY , 100 , wxDefaultPosition , wxSize ( 500 , 20 ) , wxGA_HORIZONTAL ) ;
m_processBar - > SetValue ( 0 ) ;
m_processBar - > SetRange ( ( sint32 ) ( m_required_size / 1000 ) ) ;
sizer - > Add ( m_processBar , 0 , wxALL | wxEXPAND , 5 ) ;
wxButton * m_cancelButton = new wxButton ( this , wxID_ANY , _ ( " Cancel " ) , wxDefaultPosition , wxDefaultSize , 0 ) ;
m_cancelButton - > Bind ( wxEVT_BUTTON , & GameUpdateWindow : : OnCancelButton , this ) ;
sizer - > Add ( m_cancelButton , 0 , wxALIGN_RIGHT | wxALL , 5 ) ;
this - > SetSizer ( sizer ) ;
this - > Centre ( wxBOTH ) ;
wxWindowBase : : Layout ( ) ;
wxWindowBase : : Fit ( ) ;
m_timer = new wxTimer ( this ) ;
this - > Bind ( wxEVT_TIMER , & GameUpdateWindow : : OnUpdate , this ) ;
this - > Bind ( wxEVT_CLOSE_WINDOW , & GameUpdateWindow : : OnClose , this ) ;
m_timer - > Start ( 250 ) ;
m_thread_state = ThreadRunning ;
m_thread = std : : thread ( & GameUpdateWindow : : ThreadWork , this ) ;
}
void GameUpdateWindow : : ThreadWork ( )
{
fs : : directory_entry currentDirEntry ;
try
{
// create base directories
for ( auto & path : m_source_paths )
{
if ( ! path . has_stem ( ) )
continue ;
fs : : path targetDir = fs : : path ( m_target_path ) / path . stem ( ) ;
create_directories ( targetDir ) ;
}
for ( auto & path : m_source_paths )
{
if ( m_thread_state = = ThreadCanceled )
break ;
if ( ! path . has_parent_path ( ) )
continue ;
const auto len = path . parent_path ( ) . string ( ) . size ( ) + 1 ;
for ( const fs : : directory_entry & f : fs : : recursive_directory_iterator { path } )
{
if ( m_thread_state = = ThreadCanceled )
break ;
currentDirEntry = f ;
fs : : path relative ( f . path ( ) . string ( ) . substr ( len ) ) ;
fs : : path target = fs : : path ( m_target_path ) / relative ;
if ( is_directory ( f ) )
{
create_directories ( target ) ;
continue ;
}
copy ( f , target , fs : : copy_options : : overwrite_existing ) ;
if ( is_regular_file ( f . path ( ) ) )
{
m_processed_size + = file_size ( f . path ( ) ) ;
}
}
}
}
catch ( const std : : exception & ex )
{
std : : stringstream error_msg ;
error_msg < < GetSystemErrorMessage ( ex ) ;
if ( currentDirEntry ! = fs : : directory_entry { } )
2022-09-09 23:48:52 +02:00
error_msg < < fmt : : format ( " \n {} \n {} " , _ ( " Current file: " ) . ToStdString ( ) , _pathToUtf8 ( currentDirEntry . path ( ) ) ) ;
2022-08-22 22:21:23 +02:00
m_thread_exception = error_msg . str ( ) ;
m_thread_state = ThreadCanceled ;
}
if ( m_thread_state = = ThreadCanceled )
{
if ( fs : : exists ( m_target_path ) )
fs : : remove_all ( m_target_path ) ;
}
else
m_thread_state = ThreadFinished ;
}
GameUpdateWindow : : ~ GameUpdateWindow ( )
{
m_timer - > Stop ( ) ;
if ( m_thread . joinable ( ) )
m_thread . join ( ) ;
}
int GameUpdateWindow : : ShowModal ( )
{
wxDialog : : ShowModal ( ) ;
return m_thread_state = = ThreadCanceled ? wxID_CANCEL : wxID_OK ;
}
void GameUpdateWindow : : OnClose ( wxCloseEvent & event )
{
if ( m_thread_state = = ThreadRunning )
{
wxMessageDialog dialog ( this , _ ( " Do you really want to cancel the installation process? \n \n Canceling the process will delete the applied files. " ) , _ ( " Info " ) , wxCENTRE | wxYES_NO ) ;
if ( dialog . ShowModal ( ) ! = wxID_YES )
return ;
m_thread_state = ThreadCanceled ;
}
m_timer - > Stop ( ) ;
if ( m_thread . joinable ( ) )
m_thread . join ( ) ;
if ( ! m_backup_folder . empty ( ) )
{
if ( m_thread_state = = ThreadCanceled )
{
// restore backup
try
{
if ( fs : : exists ( m_target_path ) )
fs : : remove_all ( m_target_path ) ;
fs : : rename ( m_backup_folder , m_target_path ) ;
}
catch ( const std : : exception & ex )
{
forceLogDebug_printf ( " can't restore update backup: %s " , ex . what ( ) ) ;
}
}
else
{
// delete backup
try
{
if ( fs : : exists ( m_backup_folder ) )
fs : : remove_all ( m_backup_folder ) ;
}
catch ( const std : : exception & ex )
{
forceLogDebug_printf ( " can't delete update backup: %s " , ex . what ( ) ) ;
}
}
m_backup_folder . clear ( ) ;
}
event . Skip ( ) ;
}
void GameUpdateWindow : : OnUpdate ( const wxTimerEvent & event )
{
if ( m_thread_state ! = ThreadRunning )
{
Close ( ) ;
return ;
}
const auto processedSize = ( sint32 ) ( m_processed_size / 1000 ) ;
if ( m_processBar - > GetValue ( ) ! = processedSize )
m_processBar - > SetValue ( processedSize ) ;
}
void GameUpdateWindow : : OnCancelButton ( const wxCommandEvent & event )
{
Close ( ) ;
}