2022-08-22 22:21:23 +02:00
# include "gui/wxgui.h"
# include "gui/DownloadGraphicPacksWindow.h"
# include <filesystem>
# include <curl/curl.h>
# include <zip.h>
# include <rapidjson/document.h>
# include <boost/algorithm/string.hpp>
# include "config/ActiveSettings.h"
2022-09-07 02:42:25 +02:00
# include "Common/FileStream.h"
2022-08-22 22:21:23 +02:00
# include "Cafe/CafeSystem.h"
struct DownloadGraphicPacksWindow : : curlDownloadFileState_t
{
std : : vector < uint8 > fileData ;
double progress ;
} ;
size_t DownloadGraphicPacksWindow : : curlDownloadFile_writeData ( void * ptr , size_t size , size_t nmemb , curlDownloadFileState_t * downloadState )
{
const size_t writeSize = size * nmemb ;
const size_t currentSize = downloadState - > fileData . size ( ) ;
const size_t newSize = currentSize + writeSize ;
downloadState - > fileData . resize ( newSize ) ;
memcpy ( downloadState - > fileData . data ( ) + currentSize , ptr , writeSize ) ;
return writeSize ;
}
int DownloadGraphicPacksWindow : : progress_callback ( curlDownloadFileState_t * downloadState , double dltotal , double dlnow , double ultotal , double ulnow )
{
if ( dltotal > 1.0 )
downloadState - > progress = dlnow / dltotal ;
else
downloadState - > progress = 0.0 ;
return 0 ;
}
bool DownloadGraphicPacksWindow : : curlDownloadFile ( const char * url , curlDownloadFileState_t * downloadState )
{
CURL * curl = curl_easy_init ( ) ;
if ( curl = = nullptr )
return false ;
downloadState - > progress = 0.0 ;
curl_easy_setopt ( curl , CURLOPT_URL , url ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , curlDownloadFile_writeData ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , downloadState ) ;
curl_easy_setopt ( curl , CURLOPT_PROGRESSFUNCTION , progress_callback ) ;
curl_easy_setopt ( curl , CURLOPT_PROGRESSDATA , downloadState ) ;
curl_easy_setopt ( curl , CURLOPT_NOPROGRESS , 0 ) ;
curl_easy_setopt ( curl , CURLOPT_FOLLOWLOCATION , true ) ;
curl_easy_setopt ( curl , CURLOPT_SSL_VERIFYHOST , 0 ) ;
curl_easy_setopt ( curl , CURLOPT_SSL_VERIFYPEER , 0 ) ;
2022-08-31 12:04:09 +02:00
curl_easy_setopt ( curl , CURLOPT_USERAGENT , BUILD_VERSION_WITH_NAME_STRING ) ;
2022-08-22 22:21:23 +02:00
downloadState - > fileData . resize ( 0 ) ;
const CURLcode res = curl_easy_perform ( curl ) ;
curl_easy_cleanup ( curl ) ;
return res = = CURLE_OK ;
}
// returns true if the version matches
bool checkGraphicPackDownloadedVersion ( const char * nameVersion , bool & hasVersionFile )
{
hasVersionFile = false ;
2022-10-11 23:03:26 -07:00
const auto path = ActiveSettings : : GetUserDataPath ( " graphicPacks/downloadedGraphicPacks/version.txt " ) ;
2022-08-22 22:21:23 +02:00
std : : unique_ptr < FileStream > file ( FileStream : : openFile2 ( path ) ) ;
std : : string versionInFile ;
if ( file & & file - > readLine ( versionInFile ) )
{
return boost : : iequals ( versionInFile , nameVersion ) ;
}
return false ;
}
void createGraphicPackDownloadedVersionFile ( const char * nameVersion )
{
2022-10-11 23:03:26 -07:00
const auto path = ActiveSettings : : GetUserDataPath ( " graphicPacks/downloadedGraphicPacks/version.txt " ) ;
2022-08-22 22:21:23 +02:00
std : : unique_ptr < FileStream > file ( FileStream : : createFile2 ( path ) ) ;
if ( file )
file - > writeString ( nameVersion ) ;
else
{
2023-05-20 02:46:12 +02:00
cemuLog_log ( LogType : : Force , " Failed to write graphic pack version.txt " ) ;
2022-08-22 22:21:23 +02:00
}
}
void deleteDownloadedGraphicPacks ( )
{
2022-10-11 23:03:26 -07:00
const auto path = ActiveSettings : : GetUserDataPath ( " graphicPacks/downloadedGraphicPacks " ) ;
2022-08-22 22:21:23 +02:00
std : : error_code er ;
2022-09-04 01:27:44 +02:00
if ( ! fs : : exists ( path , er ) )
2022-08-22 22:21:23 +02:00
return ;
try
{
for ( auto & p : fs : : directory_iterator ( path ) )
{
fs : : remove_all ( p . path ( ) , er ) ;
}
}
catch ( std : : filesystem : : filesystem_error & e )
{
cemuLog_log ( LogType : : Force , " Error in deleteDownloadedGraphicPacks(): " ) ;
cemuLog_log ( LogType : : Force , e . what ( ) ) ;
}
}
void DownloadGraphicPacksWindow : : UpdateThread ( )
{
// get github url
std : : string githubAPIUrl ;
curlDownloadFileState_t tempDownloadState ;
2022-09-02 20:54:22 +02:00
std : : string queryUrl ( " https://cemu.info/api2/query_graphicpack_url.php? " ) ;
2022-08-22 22:21:23 +02:00
char temp [ 64 ] ;
2022-09-02 20:54:22 +02:00
sprintf ( temp , " version=%d.%d.%d " , EMULATOR_VERSION_LEAD , EMULATOR_VERSION_MAJOR , EMULATOR_VERSION_MINOR ) ;
2022-08-22 22:21:23 +02:00
queryUrl . append ( temp ) ;
queryUrl . append ( " & " ) ;
sprintf ( temp , " t=%u " , ( uint32 ) std : : chrono : : seconds ( std : : time ( NULL ) ) . count ( ) ) ; // add a dynamic part to the url to bypass overly aggressive caching (like some proxies do)
queryUrl . append ( temp ) ;
if ( curlDownloadFile ( queryUrl . c_str ( ) , & tempDownloadState ) & & boost : : starts_with ( ( const char * ) tempDownloadState . fileData . data ( ) , " http " ) )
{
// convert downloaded data to url string
githubAPIUrl . assign ( tempDownloadState . fileData . cbegin ( ) , tempDownloadState . fileData . cend ( ) ) ;
}
else
{
// cemu api request failed, use hardcoded github url
2023-04-12 15:31:34 +01:00
cemuLog_log ( LogType : : Force , " Graphic pack update request failed or returned invalid URL. Using default repository URL instead " ) ;
2023-04-12 19:37:53 -07:00
githubAPIUrl = " https://api.github.com/repos/cemu-project/cemu_graphic_packs/releases/latest " ;
2022-08-22 22:21:23 +02:00
}
// github API request
if ( curlDownloadFile ( githubAPIUrl . c_str ( ) , & tempDownloadState ) = = false )
{
wxMessageBox ( _ ( " Error " ) , _ ( L " Failed to connect to server " ) , wxOK | wxCENTRE | wxICON_ERROR , this ) ;
m_threadState = ThreadError ;
return ;
}
// parse json result
rapidjson : : Document d ;
d . Parse ( ( const char * ) tempDownloadState . fileData . data ( ) , tempDownloadState . fileData . size ( ) ) ;
if ( d . HasParseError ( ) )
{
m_threadState = ThreadError ;
return ;
}
auto & jsonName = d [ " name " ] ;
if ( jsonName . IsString ( ) = = false )
{
m_threadState = ThreadError ;
return ;
}
const char * assetName = jsonName . GetString ( ) ; // name includes version
if ( d . IsObject ( ) = = false )
{
m_threadState = ThreadError ;
return ;
}
auto & jsonAssets = d [ " assets " ] ;
if ( jsonAssets . IsArray ( ) = = false | | jsonAssets . GetArray ( ) . Size ( ) = = 0 )
{
m_threadState = ThreadError ;
return ;
}
auto & jsonAsset0 = jsonAssets . GetArray ( ) [ 0 ] ;
if ( jsonAsset0 . IsObject ( ) = = false )
{
m_threadState = ThreadError ;
return ;
}
auto & jsonDownloadUrl = jsonAsset0 [ " browser_download_url " ] ;
if ( jsonDownloadUrl . IsString ( ) = = false )
{
m_threadState = ThreadError ;
return ;
}
const char * browserDownloadUrl = jsonDownloadUrl . GetString ( ) ;
// check version
bool hasVersionFile = false ;
if ( checkGraphicPackDownloadedVersion ( assetName , hasVersionFile ) )
{
// already up to date
wxMessageBox ( _ ( " No updates available. " ) , _ ( " Graphic packs " ) , wxOK | wxCENTRE , this - > GetParent ( ) ) ;
m_threadState = ThreadFinished ;
return ;
}
if ( hasVersionFile )
{
// if a version file already exists (and graphic packs are installed) ask the user if he really wants to update
if ( wxMessageBox ( _ ( " Updated graphic packs are available. Do you want to download and install them? " ) , _ ( " Graphic packs " ) , wxYES_NO , this - > GetParent ( ) ) ! = wxYES )
{
// cancel update
m_threadState = ThreadFinished ;
return ;
}
}
// download zip
m_stage = StageDownloading ;
if ( curlDownloadFile ( browserDownloadUrl , m_downloadState . get ( ) ) = = false )
{
wxMessageBox ( _ ( " Error " ) , _ ( L " Failed to connect to server " ) , wxOK | wxCENTRE | wxICON_ERROR , this ) ;
m_threadState = ThreadError ;
return ;
}
m_extractionProgress = 0.0 ;
m_stage = StageExtracting ;
zip_source_t * src ;
zip_t * za ;
zip_error_t error ;
// init zip source
zip_error_init ( & error ) ;
if ( ( src = zip_source_buffer_create ( m_downloadState - > fileData . data ( ) , m_downloadState - > fileData . size ( ) , 1 , & error ) ) = = NULL )
{
zip_error_fini ( & error ) ;
m_threadState = ThreadError ;
return ;
}
// open zip from source
if ( ( za = zip_open_from_source ( src , 0 , & error ) ) = = NULL )
{
zip_source_free ( src ) ;
zip_error_fini ( & error ) ;
m_threadState = ThreadError ;
return ;
}
2022-10-11 23:03:26 -07:00
auto path = ActiveSettings : : GetUserDataPath ( " graphicPacks/downloadedGraphicPacks " ) ;
2022-08-22 22:21:23 +02:00
std : : error_code er ;
//fs::remove_all(path, er); -> Don't delete the whole folder and recreate it immediately afterwards because sometimes it just fails
deleteDownloadedGraphicPacks ( ) ;
fs : : create_directories ( path , er ) ; // make sure downloadedGraphicPacks folder exists
sint32 numEntries = zip_get_num_entries ( za , 0 ) ;
for ( sint32 i = 0 ; i < numEntries ; i + + )
{
m_extractionProgress = ( double ) i / ( double ) numEntries ;
zip_stat_t sb = { 0 } ;
if ( zip_stat_index ( za , i , 0 , & sb ) ! = 0 )
{
assert_dbg ( ) ;
}
if ( std : : strstr ( sb . name , " ../ " ) ! = nullptr | |
std : : strstr ( sb . name , " .. \\ " ) ! = nullptr )
continue ; // bad path
2022-10-11 23:03:26 -07:00
path = ActiveSettings : : GetUserDataPath ( " graphicPacks/downloadedGraphicPacks/{} " , sb . name ) ;
2022-08-22 22:21:23 +02:00
size_t sbNameLen = strlen ( sb . name ) ;
if ( sbNameLen = = 0 )
continue ;
if ( sb . name [ sbNameLen - 1 ] = = ' / ' )
{
fs : : create_directories ( path , er ) ;
continue ;
}
if ( sb . size = = 0 )
continue ;
if ( sb . size > ( 1024 * 1024 * 128 ) )
continue ; // skip unusually huge files
zip_file_t * zipFile = zip_fopen_index ( za , i , 0 ) ;
if ( zipFile = = nullptr )
continue ;
uint8 * fileBuffer = new uint8 [ sb . size ] ;
if ( zip_fread ( zipFile , fileBuffer , sb . size ) = = sb . size )
{
FileStream * fs = FileStream : : createFile2 ( path ) ;
if ( fs )
{
fs - > writeData ( fileBuffer , sb . size ) ;
delete fs ;
}
}
delete [ ] fileBuffer ;
zip_fclose ( zipFile ) ;
}
zip_error_fini ( & error ) ;
createGraphicPackDownloadedVersionFile ( assetName ) ;
m_threadState = ThreadFinished ;
}
DownloadGraphicPacksWindow : : DownloadGraphicPacksWindow ( wxWindow * parent )
: wxDialog ( parent , wxID_ANY , _ ( " Checking version... " ) , wxDefaultPosition , wxDefaultSize , wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX ) ,
m_threadState ( ThreadRunning ) , m_stage ( StageCheckVersion ) , m_currentStage ( StageCheckVersion )
{
auto * sizer = new wxBoxSizer ( wxVERTICAL ) ;
m_processBar = new wxGauge ( this , wxID_ANY , 100 , wxDefaultPosition , wxSize ( 500 , 20 ) , wxGA_HORIZONTAL ) ;
m_processBar - > SetValue ( 0 ) ;
m_processBar - > SetRange ( 100 ) ;
sizer - > Add ( m_processBar , 0 , wxALL | wxEXPAND , 5 ) ;
auto * m_cancelButton = new wxButton ( this , wxID_ANY , _ ( " Cancel " ) , wxDefaultPosition , wxDefaultSize , 0 ) ;
m_cancelButton - > Bind ( wxEVT_BUTTON , & DownloadGraphicPacksWindow : : 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 , & DownloadGraphicPacksWindow : : OnUpdate , this ) ;
this - > Bind ( wxEVT_CLOSE_WINDOW , & DownloadGraphicPacksWindow : : OnClose , this ) ;
m_timer - > Start ( 250 ) ;
m_downloadState = std : : make_unique < curlDownloadFileState_t > ( ) ;
}
DownloadGraphicPacksWindow : : ~ DownloadGraphicPacksWindow ( )
{
m_timer - > Stop ( ) ;
if ( m_thread . joinable ( ) )
m_thread . join ( ) ;
}
const std : : string & DownloadGraphicPacksWindow : : GetException ( ) const
{
return m_threadException ;
}
int DownloadGraphicPacksWindow : : ShowModal ( )
{
2024-03-26 13:09:24 +01:00
if ( CafeSystem : : IsTitleRunning ( ) )
{
wxMessageBox ( _ ( " Graphic packs cannot be updated while a game is running. " ) , _ ( " Graphic packs " ) , 5 , this - > GetParent ( ) ) ;
return wxID_CANCEL ;
}
m_thread = std : : thread ( & DownloadGraphicPacksWindow : : UpdateThread , this ) ;
2022-08-22 22:21:23 +02:00
wxDialog : : ShowModal ( ) ;
return m_threadState = = ThreadCanceled ? wxID_CANCEL : wxID_OK ;
}
void DownloadGraphicPacksWindow : : OnClose ( wxCloseEvent & event )
{
if ( m_threadState = = ThreadRunning )
{
//wxMessageDialog dialog(this, _("Do you really want to cancel the update process?\n\nCanceling the process will delete the applied update."), _("Info"), wxCENTRE | wxYES_NO);
//if (dialog.ShowModal() != wxID_YES)
// return;
m_threadState = ThreadCanceled ;
}
m_timer - > Stop ( ) ;
if ( m_thread . joinable ( ) )
m_thread . join ( ) ;
event . Skip ( ) ;
}
void DownloadGraphicPacksWindow : : OnUpdate ( const wxTimerEvent & event )
{
if ( m_threadState ! = ThreadRunning )
{
Close ( ) ;
return ;
}
if ( m_currentStage ! = m_stage )
{
if ( m_stage = = StageDownloading )
{
this - > SetTitle ( _ ( " Downloading graphic packs... " ) ) ;
}
else if ( m_stage = = StageExtracting )
{
this - > SetTitle ( _ ( " Extracting... " ) ) ;
}
m_currentStage = m_stage ;
}
if ( m_currentStage = = StageDownloading )
{
const sint32 processedSize = ( sint32 ) ( m_downloadState - > progress * 100.0f ) ;
if ( m_processBar - > GetValue ( ) ! = processedSize )
m_processBar - > SetValue ( processedSize ) ;
}
else if ( m_currentStage = = StageExtracting )
{
const sint32 processedSize = ( sint32 ) ( m_extractionProgress * 100.0f ) ;
if ( m_processBar - > GetValue ( ) ! = processedSize )
m_processBar - > SetValue ( processedSize ) ;
}
}
void DownloadGraphicPacksWindow : : OnCancelButton ( const wxCommandEvent & event )
{
Close ( ) ;
}