2018-06-21 07:42:46 +01:00
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <errno.h>
# include <math.h>
# include <time.h>
# include <dirent.h>
2018-06-30 07:37:42 +01:00
# include <ctype.h>
2018-06-21 07:42:46 +01:00
# include <sys/stat.h>
# include <sys/statvfs.h>
2018-06-30 07:37:42 +01:00
# include <sys/socket.h>
2018-06-21 07:42:46 +01:00
# include <switch/services/ns.h>
2018-06-30 07:37:42 +01:00
# include <libxml2/libxml/globals.h>
# include <libxml2/libxml/xpath.h>
# include <curl/curl.h>
# include <json-c/json.h>
2018-06-21 07:42:46 +01:00
2018-06-26 02:11:18 +01:00
# include "dumper.h"
2018-05-15 17:00:19 +01:00
# include "fsext.h"
2018-06-21 07:42:46 +01:00
# include "ncmext.h"
# include "ui.h"
# include "util.h"
extern int breaks ;
2018-05-15 17:00:19 +01:00
2018-06-26 02:11:18 +01:00
extern u64 gameCardSize ;
extern u64 gameCardTitleID ;
extern u32 hfs0_partition_cnt ;
2018-06-30 07:37:42 +01:00
const char * nswReleasesXmlUrl = " http://nswdb.com/xml.php " ;
const char * nswReleasesXmlTmpPath = " sdmc:/NSWreleases.xml.tmp " ;
2018-06-26 02:11:18 +01:00
const char * nswReleasesXmlPath = " sdmc:/NSWreleases.xml " ;
const char * nswReleasesRootElement = " releases " ;
const char * nswReleasesChildren = " release " ;
const char * nswReleasesChildrenImageSize = " imagesize " ;
const char * nswReleasesChildrenTitleID = " titleid " ;
const char * nswReleasesChildrenImgCrc = " imgcrc " ;
const char * nswReleasesChildrenReleaseName = " releasename " ;
const char * nswReleasesChildrenCard = " card " ;
2018-06-30 07:37:42 +01:00
const char * githubReleasesApiUrl = " https://api.github.com/repos/DarkMatterCore/gcdumptool/releases/latest " ;
const char * gcDumpToolTmpPath = " sdmc:/switch/gcdumptool.nro.tmp " ;
const char * gcDumpToolPath = " sdmc:/switch/gcdumptool.nro " ;
const char * userAgent = " gcdumptool/ " APP_VERSION " (Nintendo Switch) " ;
static char * result_buf = NULL ;
static size_t result_sz = 0 ;
static size_t result_written = 0 ;
2018-06-21 07:42:46 +01:00
bool isGameCardInserted ( FsDeviceOperator * o )
{
2018-05-15 17:00:19 +01:00
bool inserted ;
2018-06-21 07:42:46 +01:00
if ( R_FAILED ( fsDeviceOperatorIsGameCardInserted ( o , & inserted ) ) ) return false ;
2018-05-15 17:00:19 +01:00
return inserted ;
}
2018-06-21 07:42:46 +01:00
void syncDisplay ( )
{
2018-05-15 17:00:19 +01:00
gfxFlushBuffers ( ) ;
gfxSwapBuffers ( ) ;
gfxWaitForVsync ( ) ;
2018-06-21 07:42:46 +01:00
}
void delay ( u8 seconds )
{
if ( ! seconds ) return ;
time_t timer ;
time ( & timer ) ;
while ( time ( NULL ) < ( timer + seconds ) ) syncDisplay ( ) ;
}
2018-06-30 07:37:42 +01:00
bool getGameCardTitleIDAndVersion ( u64 * titleID , u32 * version )
2018-06-21 07:42:46 +01:00
{
bool success = false ;
Result result ;
ncmContentMetaDatabase ncmDb ;
ncmApplicationMetaKey * appList = ( ncmApplicationMetaKey * ) malloc ( sizeof ( ncmApplicationMetaKey ) ) ;
if ( appList )
{
memset ( appList , 0 , sizeof ( ncmApplicationMetaKey ) ) ;
if ( R_SUCCEEDED ( result = ncmOpenContentMetaDatabase ( FsStorageId_GameCard ) ) )
{
if ( R_SUCCEEDED ( result = ncmGetContentMetaDatabase ( & ncmDb , FsStorageId_GameCard ) ) )
{
if ( R_SUCCEEDED ( result = ncmMetaDatabaseListApplication ( & ncmDb , appList , sizeof ( ncmApplicationMetaKey ) , 0 ) ) )
{
* titleID = appList - > meta_record . titleID ;
2018-06-30 07:37:42 +01:00
* version = appList - > meta_record . titleVersion ;
2018-06-21 07:42:46 +01:00
success = true ;
} else {
2018-06-30 07:37:42 +01:00
uiStatusMsg ( " getGameCardTitleIDAndVersion: MetaDatabaseListApplication failed! (0x%08X) " , result ) ;
2018-06-21 07:42:46 +01:00
}
} else {
2018-06-30 07:37:42 +01:00
uiStatusMsg ( " getGameCardTitleIDAndVersion: GetContentMetaDatabase failed! (0x%08X) " , result ) ;
2018-06-21 07:42:46 +01:00
}
} else {
2018-06-30 07:37:42 +01:00
uiStatusMsg ( " getGameCardTitleIDAndVersion: OpenContentMetaDatabase failed! (0x%08X) " , result ) ;
2018-06-21 07:42:46 +01:00
}
// Seems to cause problems
2018-06-30 07:37:42 +01:00
//if (R_FAILED(result = ncmCloseContentMetaDatabase(FsStorageId_GameCard))) uiStatusMsg("getGameCardTitleIDAndVersion: CloseContentMetaDatabase failed! (0x%08X)", result);
2018-06-21 07:42:46 +01:00
free ( appList ) ;
} else {
2018-06-30 07:37:42 +01:00
uiStatusMsg ( " getGameCardTitleIDAndVersion: Unable to allocate memory for the NCM service operations. " ) ;
2018-06-21 07:42:46 +01:00
}
return success ;
}
2018-06-30 07:37:42 +01:00
void convertTitleVersionToDecimal ( u32 version , char * versionBuf , int versionBufSize )
{
u8 major = ( u8 ) ( ( version > > 26 ) & 0x3F ) ;
u8 middle = ( u8 ) ( ( version > > 20 ) & 0x3F ) ;
u8 minor = ( u8 ) ( ( version > > 16 ) & 0xF ) ;
u16 build = ( u16 ) version ;
snprintf ( versionBuf , versionBufSize , " %u (%u.%u.%u.%u) " , version , major , middle , minor , build ) ;
}
bool getGameCardControlNacp ( u64 titleID , char * nameBuf , int nameBufSize , char * authorBuf , int authorBufSize )
2018-06-21 07:42:46 +01:00
{
if ( titleID = = 0 ) return false ;
bool success = false ;
Result result ;
size_t outsize = 0 ;
NsApplicationControlData * buf = NULL ;
NacpLanguageEntry * langentry = NULL ;
buf = ( NsApplicationControlData * ) malloc ( sizeof ( NsApplicationControlData ) ) ;
if ( buf )
{
memset ( buf , 0 , sizeof ( NsApplicationControlData ) ) ;
if ( R_SUCCEEDED ( result = nsGetApplicationControlData ( 1 , titleID , buf , sizeof ( NsApplicationControlData ) , & outsize ) ) )
{
if ( outsize > = sizeof ( buf - > nacp ) )
{
if ( R_SUCCEEDED ( result = nacpGetLanguageEntry ( & buf - > nacp , & langentry ) ) )
{
strncpy ( nameBuf , langentry - > name , nameBufSize - 1 ) ;
strncpy ( authorBuf , langentry - > author , authorBufSize - 1 ) ;
success = true ;
} else {
2018-06-30 07:37:42 +01:00
uiStatusMsg ( " getGameCardControlNacp: GetLanguageEntry failed! (0x%08X) " , result ) ;
2018-06-21 07:42:46 +01:00
}
} else {
2018-06-22 01:46:17 +01:00
uiStatusMsg ( " getGameCardControlNacp: Control.nacp buffer size (%u bytes) is too small! Expected: %u bytes " , outsize , sizeof ( buf - > nacp ) ) ;
2018-06-21 07:42:46 +01:00
}
} else {
2018-06-30 07:37:42 +01:00
uiStatusMsg ( " getGameCardControlNacp: GetApplicationControlData failed! (0x%08X) " , result ) ;
2018-06-21 07:42:46 +01:00
}
free ( buf ) ;
} else {
2018-06-22 01:46:17 +01:00
uiStatusMsg ( " getGameCardControlNacp: Unable to allocate memory for the NS service operations. " ) ;
2018-06-21 07:42:46 +01:00
}
return success ;
}
int getSdCardFreeSpace ( u64 * out )
{
struct statvfs st ;
int rc ;
rc = statvfs ( " sdmc:/ " , & st ) ;
if ( rc ! = 0 )
{
2018-06-22 01:46:17 +01:00
uiStatusMsg ( " getSdCardFreeSpace: Unable to get SD card filesystem stats! statvfs: %d (%s). " , errno , strerror ( errno ) ) ;
2018-06-21 07:42:46 +01:00
} else {
* out = ( u64 ) ( st . f_bsize * st . f_bfree ) ;
}
return rc ;
}
# define KiB (1024.0)
# define MiB (1024.0 * KiB)
# define GiB (1024.0 * MiB)
void convertSize ( u64 size , char * out , int bufsize )
{
char buffer [ 16 ] ;
double bytes = ( double ) size ;
if ( bytes < 1000.0 )
{
snprintf ( buffer , sizeof ( buffer ) , " %.0lf B " , bytes ) ;
} else
if ( bytes < 10.0 * KiB )
{
snprintf ( buffer , sizeof ( buffer ) , " %.2lf KiB " , floor ( ( bytes * 100.0 ) / KiB ) / 100.0 ) ;
} else
if ( bytes < 100.0 * KiB )
{
snprintf ( buffer , sizeof ( buffer ) , " %.1lf KiB " , floor ( ( bytes * 10.0 ) / KiB ) / 10.0 ) ;
} else
if ( bytes < 1000.0 * KiB )
{
snprintf ( buffer , sizeof ( buffer ) , " %.0lf KiB " , floor ( bytes / KiB ) ) ;
} else
if ( bytes < 10.0 * MiB )
{
snprintf ( buffer , sizeof ( buffer ) , " %.2lf MiB " , floor ( ( bytes * 100.0 ) / MiB ) / 100.0 ) ;
} else
if ( bytes < 100.0 * MiB )
{
snprintf ( buffer , sizeof ( buffer ) , " %.1lf MiB " , floor ( ( bytes * 10.0 ) / MiB ) / 10.0 ) ;
} else
if ( bytes < 1000.0 * MiB )
{
snprintf ( buffer , sizeof ( buffer ) , " %.0lf MiB " , floor ( bytes / MiB ) ) ;
} else
if ( bytes < 10.0 * GiB )
{
snprintf ( buffer , sizeof ( buffer ) , " %.2lf GiB " , floor ( ( bytes * 100.0 ) / GiB ) / 100.0 ) ;
} else
if ( bytes < 100.0 * GiB )
{
snprintf ( buffer , sizeof ( buffer ) , " %.1lf GiB " , floor ( ( bytes * 10.0 ) / GiB ) / 10.0 ) ;
} else {
snprintf ( buffer , sizeof ( buffer ) , " %.0lf GiB " , floor ( bytes / GiB ) ) ;
}
snprintf ( out , bufsize , " %s " , buffer ) ;
}
void waitForButtonPress ( )
{
uiDrawString ( " Press any button to continue " , 0 , breaks * 8 , 255 , 255 , 255 ) ;
syncDisplay ( ) ;
while ( true )
{
hidScanInput ( ) ;
u32 keysDown = hidKeysDown ( CONTROLLER_P1_AUTO ) ;
if ( keysDown & & ! ( keysDown & KEY_TOUCH ) ) break ;
}
}
bool isDirectory ( char * path )
{
DIR * dir = opendir ( path ) ;
if ( ! dir ) return false ;
closedir ( dir ) ;
return true ;
}
void addString ( char * * filenames , int * filenamesCount , char * * nextFilename , const char * string )
{
filenames [ ( * filenamesCount ) + + ] = * nextFilename ;
strcpy ( * nextFilename , string ) ;
* nextFilename + = strlen ( string ) + 1 ;
}
static int sortAlpha ( const void * a , const void * b )
{
return strcasecmp ( * ( ( const char * * ) a ) , * ( ( const char * * ) b ) ) ;
}
void getDirectoryContents ( char * filenameBuffer , char * * filenames , int * filenamesCount , const char * directory , bool skipParent )
{
struct dirent * ent ;
int i , maxFilenamesCount = * filenamesCount ;
char * nextFilename = filenameBuffer ;
char * slash = ( char * ) malloc ( strlen ( directory ) + 2 ) ;
memset ( slash , 0 , strlen ( directory ) + 2 ) ;
snprintf ( slash , strlen ( directory ) + 2 , " %s/ " , directory ) ;
* filenamesCount = 0 ;
if ( ! skipParent ) addString ( filenames , filenamesCount , & nextFilename , " .. " ) ;
DIR * dir = opendir ( slash ) ;
if ( dir )
{
for ( i = 0 ; i < maxFilenamesCount ; i + + )
{
ent = readdir ( dir ) ;
if ( ! ent ) break ;
if ( ( strlen ( ent - > d_name ) = = 1 & & ! strcmp ( ent - > d_name , " . " ) ) | | ( strlen ( ent - > d_name ) = = 2 & & ! strcmp ( ent - > d_name , " .. " ) ) ) continue ;
addString ( filenames , filenamesCount , & nextFilename , ent - > d_name ) ;
}
closedir ( dir ) ;
}
free ( slash ) ;
// ".." should stay at the top
qsort ( filenames + 1 , ( * filenamesCount ) - 1 , sizeof ( char * ) , & sortAlpha ) ;
}
2018-06-26 02:11:18 +01:00
2018-06-30 07:37:42 +01:00
bool parseNSWDBRelease ( xmlDocPtr doc , xmlNodePtr cur , u32 crc )
2018-06-26 02:11:18 +01:00
{
xmlChar * key ;
xmlNodePtr node = cur ;
u8 imageSize = ( u8 ) ( gameCardSize / GAMECARD_SIZE_1GiB ) ;
u8 card = ( hfs0_partition_cnt = = GAMECARD_TYPE1_PARTITION_CNT ? 1 : 2 ) ;
u8 xmlImageSize = 0 ;
u64 xmlTitleID = 0 ;
u32 xmlCrc = 0 ;
u8 xmlCard = 0 ;
char xmlReleaseName [ 256 ] = { ' \0 ' } ;
bool found = false ;
char strbuf [ 512 ] = { ' \0 ' } ;
while ( node ! = NULL )
{
if ( ( ! xmlStrcmp ( node - > name , ( const xmlChar * ) nswReleasesChildrenImageSize ) ) )
{
key = xmlNodeListGetString ( doc , node - > xmlChildrenNode , 1 ) ;
xmlImageSize = ( u8 ) atoi ( ( const char * ) key ) ;
xmlFree ( key ) ;
} else
if ( ( ! xmlStrcmp ( node - > name , ( const xmlChar * ) nswReleasesChildrenTitleID ) ) )
{
key = xmlNodeListGetString ( doc , node - > xmlChildrenNode , 1 ) ;
xmlTitleID = strtoull ( ( const char * ) key , NULL , 16 ) ;
xmlFree ( key ) ;
} else
if ( ( ! xmlStrcmp ( node - > name , ( const xmlChar * ) nswReleasesChildrenImgCrc ) ) )
{
key = xmlNodeListGetString ( doc , node - > xmlChildrenNode , 1 ) ;
xmlCrc = strtoul ( ( const char * ) key , NULL , 16 ) ;
xmlFree ( key ) ;
2018-06-30 07:37:42 +01:00
}
2018-06-26 02:11:18 +01:00
if ( ( ! xmlStrcmp ( node - > name , ( const xmlChar * ) nswReleasesChildrenReleaseName ) ) )
{
key = xmlNodeListGetString ( doc , node - > xmlChildrenNode , 1 ) ;
snprintf ( xmlReleaseName , sizeof ( xmlReleaseName ) / sizeof ( xmlReleaseName [ 0 ] ) , " %s " , ( char * ) key ) ;
xmlFree ( key ) ;
} else
if ( ( ! xmlStrcmp ( node - > name , ( const xmlChar * ) nswReleasesChildrenCard ) ) )
{
key = xmlNodeListGetString ( doc , node - > xmlChildrenNode , 1 ) ;
xmlCard = ( u8 ) atoi ( ( const char * ) key ) ;
xmlFree ( key ) ;
}
node = node - > next ;
}
//snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Cartridge Image Size: %u\nCartridge Title ID: %016lX\nCartridge Image CRC32: %08X\nCartridge Type: %u\n\nXML Image Size: %u\nXML Title ID: %016lX\nXML Image CRC32: %08X\nXML Release Name: %s\nXML Card Type: %u", imageSize, gameCardTitleID, crc, card, xmlImageSize, xmlTitleID, xmlCrc, xmlReleaseName, xmlCard);
//uiDrawString(strbuf, 0, 0, 255, 255, 255);
if ( xmlImageSize = = imageSize & & xmlTitleID = = gameCardTitleID & & xmlCrc = = crc & & xmlCard = = card )
{
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Found matching Scene release: \" %s \" (CRC32: %08X). This is a good dump! " , xmlReleaseName , xmlCrc ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 0 , 255 , 0 ) ;
found = true ;
} else {
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Dump doesn't match Scene release: \" %s \" ! (CRC32: %08X) " , xmlReleaseName , xmlCrc ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
breaks + + ;
return found ;
}
xmlXPathObjectPtr getNodeSet ( xmlDocPtr doc , xmlChar * xpath )
{
xmlXPathContextPtr context = NULL ;
xmlXPathObjectPtr result = NULL ;
context = xmlXPathNewContext ( doc ) ;
result = xmlXPathEvalExpression ( xpath , context ) ;
if ( xmlXPathNodeSetIsEmpty ( result - > nodesetval ) )
{
xmlXPathFreeObject ( result ) ;
return NULL ;
}
return result ;
}
2018-06-30 07:37:42 +01:00
void gameCardDumpNSWDBCheck ( u32 crc )
2018-06-26 02:11:18 +01:00
{
2018-06-30 07:37:42 +01:00
if ( ! gameCardTitleID | | ! hfs0_partition_cnt | | ! crc ) return ;
2018-06-26 02:11:18 +01:00
xmlDocPtr doc = NULL ;
bool found = false ;
char strbuf [ 512 ] = { ' \0 ' } ;
doc = xmlParseFile ( nswReleasesXmlPath ) ;
if ( doc )
{
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " //%s/%s[.//%s='%016lX'] " , nswReleasesRootElement , nswReleasesChildren , nswReleasesChildrenTitleID , gameCardTitleID ) ;
xmlXPathObjectPtr nodeSet = getNodeSet ( doc , ( xmlChar * ) strbuf ) ;
if ( nodeSet )
{
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Found %d %s with Title ID \" %016lX \" " , nodeSet - > nodesetval - > nodeNr , ( nodeSet - > nodesetval - > nodeNr > 1 ? " releases " : " release " ) , gameCardTitleID ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 255 , 255 ) ;
breaks + + ;
u32 i ;
for ( i = 0 ; i < nodeSet - > nodesetval - > nodeNr ; i + + )
{
xmlNodePtr node = nodeSet - > nodesetval - > nodeTab [ i ] - > xmlChildrenNode ;
2018-06-30 07:37:42 +01:00
found = parseNSWDBRelease ( doc , node , crc ) ;
2018-06-26 02:11:18 +01:00
if ( found ) break ;
}
if ( ! found )
{
2018-06-26 02:42:25 +01:00
uiDrawString ( " No matches found in XML document! This could either be a bad dump or an undumped cartridge. " , 0 , breaks * 8 , 255 , 0 , 0 ) ;
2018-06-26 02:11:18 +01:00
} else {
breaks - - ;
}
xmlXPathFreeObject ( nodeSet ) ;
} else {
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Error: unable to find records with Title ID \" %016lX \" within the XML document! " , gameCardTitleID ) ;
2018-07-02 02:36:44 +01:00
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
2018-06-26 02:11:18 +01:00
}
xmlFreeDoc ( doc ) ;
} else {
2018-06-26 02:42:25 +01:00
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Error: failed to open and/or parse \" %s \" ! " , nswReleasesXmlPath ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
2018-06-26 02:11:18 +01:00
}
2018-06-30 07:37:42 +01:00
}
Result networkInit ( )
{
Result result = socketInitializeDefault ( ) ;
if ( R_SUCCEEDED ( result ) ) curl_global_init ( CURL_GLOBAL_ALL ) ;
return result ;
}
void networkDeinit ( )
{
curl_global_cleanup ( ) ;
socketExit ( ) ;
}
size_t writeCurlFile ( char * buffer , size_t size , size_t number_of_items , void * input_stream )
{
size_t total_size = ( size * number_of_items ) ;
if ( fwrite ( buffer , 1 , total_size , input_stream ) ! = total_size ) return 0 ;
return total_size ;
}
static size_t writeCurlBuffer ( char * buffer , size_t size , size_t number_of_items , void * input_stream )
{
( void ) input_stream ;
const size_t bsz = ( size * number_of_items ) ;
if ( result_sz = = 0 | | ! result_buf )
{
result_sz = 0x1000 ;
result_buf = ( char * ) malloc ( result_sz ) ;
if ( ! result_buf ) return 0 ;
}
2018-06-26 02:11:18 +01:00
2018-06-30 07:37:42 +01:00
bool need_realloc = false ;
while ( result_written + bsz > result_sz )
{
result_sz < < = 1 ;
need_realloc = true ;
}
if ( need_realloc )
{
char * new_buf = ( char * ) realloc ( result_buf , result_sz ) ;
if ( ! new_buf ) return 0 ;
result_buf = new_buf ;
}
memcpy ( result_buf + result_written , buffer , bsz ) ;
result_written + = bsz ;
return bsz ;
2018-06-26 02:11:18 +01:00
}
2018-06-30 07:37:42 +01:00
int versionNumCmp ( char * ver1 , char * ver2 )
{
typedef struct {
int major ;
int minor ;
int build ;
} version_t ;
version_t versionNum1 , versionNum2 ;
memset ( & versionNum1 , 0 , sizeof ( version_t ) ) ;
memset ( & versionNum2 , 0 , sizeof ( version_t ) ) ;
int i , curPart , res ;
char * token , * rest ;
// Parse version string 1
i = 0 ;
rest = ver1 ;
while ( ( token = strtok_r ( rest , " . " , & rest ) ) )
{
curPart = atoi ( token ) ;
switch ( i )
{
case 0 :
versionNum1 . major = curPart ;
break ;
case 1 :
versionNum1 . minor = curPart ;
break ;
case 2 :
versionNum1 . build = curPart ;
break ;
default :
break ;
}
i + + ;
if ( i > = 3 ) break ;
}
// Parse version string 2
i = 0 ;
rest = ver2 ;
while ( ( token = strtok_r ( rest , " . " , & rest ) ) )
{
curPart = atoi ( token ) ;
switch ( i )
{
case 0 :
versionNum2 . major = curPart ;
break ;
case 1 :
versionNum2 . minor = curPart ;
break ;
case 2 :
versionNum2 . build = curPart ;
break ;
default :
break ;
}
i + + ;
if ( i > = 3 ) break ;
}
// Compare version_t structs
if ( versionNum1 . major = = versionNum2 . major )
{
if ( versionNum1 . minor = = versionNum2 . minor )
{
if ( versionNum1 . build = = versionNum2 . build )
{
res = 0 ;
} else
if ( versionNum1 . build < versionNum2 . build )
{
res = - 1 ;
} else {
res = 1 ;
}
} else
if ( versionNum1 . minor < versionNum2 . minor )
{
res = - 1 ;
} else {
res = 1 ;
}
} else
if ( versionNum1 . major < versionNum2 . major )
{
res = - 1 ;
} else {
res = 1 ;
}
return res ;
}
void updateNSWDBXml ( )
{
Result result ;
CURL * curl ;
CURLcode res ;
long http_code = 0 ;
double size = 0.0 ;
char strbuf [ 512 ] = { ' \0 ' } ;
bool success = false ;
if ( R_SUCCEEDED ( result = networkInit ( ) ) )
{
curl = curl_easy_init ( ) ;
if ( curl )
{
FILE * nswdbXml = fopen ( nswReleasesXmlTmpPath , " wb " ) ;
if ( nswdbXml )
{
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Downloading XML database from \" %s \" , please wait... " , nswReleasesXmlUrl ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 255 , 255 ) ;
breaks + + ;
syncDisplay ( ) ;
curl_easy_setopt ( curl , CURLOPT_BUFFERSIZE , 102400L ) ;
curl_easy_setopt ( curl , CURLOPT_URL , nswReleasesXmlUrl ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , writeCurlFile ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , nswdbXml ) ;
curl_easy_setopt ( curl , CURLOPT_USERAGENT , userAgent ) ;
curl_easy_setopt ( curl , CURLOPT_FAILONERROR , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_NOPROGRESS , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_NOBODY , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_HEADER , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_FOLLOWLOCATION , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_SSL_VERIFYPEER , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_MAXREDIRS , 50L ) ;
res = curl_easy_perform ( curl ) ;
curl_easy_getinfo ( curl , CURLINFO_RESPONSE_CODE , & http_code ) ;
curl_easy_getinfo ( curl , CURLINFO_SIZE_DOWNLOAD , & size ) ;
if ( res = = CURLE_OK & & http_code > = 200 & & http_code < = 299 & & size > 0 )
{
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Successfully downloaded %.0lf bytes! " , size ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 0 , 255 , 0 ) ;
success = true ;
} else {
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Error: failed to request XML database! HTTP status code: %ld " , http_code ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
fclose ( nswdbXml ) ;
if ( success )
{
remove ( nswReleasesXmlPath ) ;
rename ( nswReleasesXmlTmpPath , nswReleasesXmlPath ) ;
} else {
remove ( nswReleasesXmlTmpPath ) ;
}
} else {
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Error: failed to open \" %s \" in write mode! " , nswReleasesXmlTmpPath ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
curl_easy_cleanup ( curl ) ;
} else {
uiDrawString ( " Error: failed to initialize CURL context! " , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
networkDeinit ( ) ;
} else {
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Error: failed to initialize socket! (%08X) " , result ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
breaks + = 2 ;
}
void updateApplication ( )
{
Result result ;
CURL * curl ;
CURLcode res ;
long http_code = 0 ;
double size = 0.0 ;
char strbuf [ 1024 ] = { ' \0 ' } , downloadUrl [ 512 ] = { ' \0 ' } , releaseTag [ 32 ] = { ' \0 ' } ;
bool success = false ;
struct json_object * jobj , * name , * assets ;
FILE * gcDumpToolNro = NULL ;
if ( R_SUCCEEDED ( result = networkInit ( ) ) )
{
curl = curl_easy_init ( ) ;
if ( curl )
{
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Requesting latest release information from \" %s \" ... " , githubReleasesApiUrl ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 255 , 255 ) ;
breaks + + ;
syncDisplay ( ) ;
curl_easy_setopt ( curl , CURLOPT_BUFFERSIZE , 102400L ) ;
curl_easy_setopt ( curl , CURLOPT_URL , githubReleasesApiUrl ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , writeCurlBuffer ) ;
curl_easy_setopt ( curl , CURLOPT_USERAGENT , userAgent ) ;
curl_easy_setopt ( curl , CURLOPT_FAILONERROR , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_NOPROGRESS , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_NOBODY , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_HEADER , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_FOLLOWLOCATION , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_SSL_VERIFYPEER , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_MAXREDIRS , 50L ) ;
curl_easy_setopt ( curl , CURLOPT_HTTP_VERSION , ( long ) CURL_HTTP_VERSION_2TLS ) ;
res = curl_easy_perform ( curl ) ;
curl_easy_getinfo ( curl , CURLINFO_RESPONSE_CODE , & http_code ) ;
curl_easy_getinfo ( curl , CURLINFO_SIZE_DOWNLOAD , & size ) ;
if ( res = = CURLE_OK & & http_code > = 200 & & http_code < = 299 & & size > 0 )
{
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Parsing response JSON data from \" %s \" ... " , githubReleasesApiUrl ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 255 , 255 ) ;
breaks + + ;
syncDisplay ( ) ;
jobj = json_tokener_parse ( result_buf ) ;
if ( jobj ! = NULL )
{
if ( json_object_object_get_ex ( jobj , " name " , & name ) & & json_object_get_type ( name ) = = json_type_string )
{
snprintf ( releaseTag , sizeof ( releaseTag ) / sizeof ( releaseTag [ 0 ] ) , json_object_get_string ( name ) ) ;
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Latest release: %s " , releaseTag ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 255 , 255 ) ;
breaks + + ;
syncDisplay ( ) ;
// Compare versions
if ( releaseTag [ 0 ] = = ' v ' | | releaseTag [ 0 ] = = ' V ' | | releaseTag [ 0 ] = = ' r ' | | releaseTag [ 0 ] = = ' R ' ) memmove ( releaseTag , releaseTag + 1 , strlen ( releaseTag ) ) ;
if ( versionNumCmp ( releaseTag , APP_VERSION ) > 0 )
{
if ( json_object_object_get_ex ( jobj , " assets " , & assets ) & & json_object_get_type ( assets ) = = json_type_array )
{
assets = json_object_array_get_idx ( assets , 0 ) ;
if ( assets ! = NULL )
{
if ( json_object_object_get_ex ( assets , " browser_download_url " , & assets ) & & json_object_get_type ( assets ) = = json_type_string )
{
snprintf ( downloadUrl , sizeof ( downloadUrl ) / sizeof ( downloadUrl [ 0 ] ) , json_object_get_string ( assets ) ) ;
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Download URL: \" %s \" " , downloadUrl ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 255 , 255 ) ;
breaks + + ;
syncDisplay ( ) ;
gcDumpToolNro = fopen ( gcDumpToolTmpPath , " wb " ) ;
if ( gcDumpToolNro )
{
curl_easy_reset ( curl ) ;
curl_easy_setopt ( curl , CURLOPT_BUFFERSIZE , 102400L ) ;
curl_easy_setopt ( curl , CURLOPT_URL , downloadUrl ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , writeCurlFile ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , gcDumpToolNro ) ;
curl_easy_setopt ( curl , CURLOPT_USERAGENT , userAgent ) ;
curl_easy_setopt ( curl , CURLOPT_FAILONERROR , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_NOPROGRESS , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_NOBODY , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_HEADER , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_FOLLOWLOCATION , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_SSL_VERIFYPEER , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_MAXREDIRS , 50L ) ;
curl_easy_setopt ( curl , CURLOPT_HTTP_VERSION , ( long ) CURL_HTTP_VERSION_2TLS ) ;
res = curl_easy_perform ( curl ) ;
curl_easy_getinfo ( curl , CURLINFO_RESPONSE_CODE , & http_code ) ;
curl_easy_getinfo ( curl , CURLINFO_SIZE_DOWNLOAD , & size ) ;
if ( res = = CURLE_OK & & http_code > = 200 & & http_code < = 299 & & size > 0 )
{
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Successfully downloaded %.0lf bytes! " , size ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 0 , 255 , 0 ) ;
success = true ;
} else {
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Error: failed to request latest update binary! HTTP status code: %ld " , http_code ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
fclose ( gcDumpToolNro ) ;
if ( success )
{
remove ( gcDumpToolPath ) ;
rename ( gcDumpToolTmpPath , gcDumpToolPath ) ;
} else {
remove ( gcDumpToolTmpPath ) ;
}
} else {
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Error: failed to open \" %s \" in write mode! " , gcDumpToolTmpPath ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
} else {
uiDrawString ( " Error: unable to parse download URL from JSON response! " , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
} else {
uiDrawString ( " Error: unable to parse object at index 0 from \" assets \" array in JSON response! " , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
} else {
uiDrawString ( " Error: unable to parse \" assets \" array from JSON response! " , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
} else {
uiDrawString ( " You already have the latest version! " , 0 , breaks * 8 , 255 , 255 , 255 ) ;
}
} else {
uiDrawString ( " Error: unable to parse version tag from JSON response! " , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
json_object_put ( jobj ) ;
} else {
uiDrawString ( " Error: unable to parse JSON response! " , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
} else {
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Error: failed to request latest release information! HTTP status code: %ld " , http_code ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
if ( result_buf ) free ( result_buf ) ;
curl_easy_cleanup ( curl ) ;
} else {
uiDrawString ( " Error: failed to initialize CURL context! " , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
networkDeinit ( ) ;
} else {
snprintf ( strbuf , sizeof ( strbuf ) / sizeof ( strbuf [ 0 ] ) , " Error: failed to initialize socket! (%08X) " , result ) ;
uiDrawString ( strbuf , 0 , breaks * 8 , 255 , 0 , 0 ) ;
}
breaks + = 2 ;
}
void removeIllegalCharacters ( char * name )
2018-06-26 02:11:18 +01:00
{
u32 i , len = strlen ( name ) ;
for ( i = 0 ; i < len ; i + + )
{
if ( memchr ( " ?[]/ \\ =+<>:; \" ,*|^ " , name [ i ] , sizeof ( " ?[]/ \\ =+<>:; \" ,*|^ " ) - 1 ) | | name [ i ] < 0x20 | | name [ i ] > 0x7E ) name [ i ] = ' _ ' ;
}
2018-06-30 07:37:42 +01:00
}
void strtrim ( char * str )
{
if ( ! str | | ! * str ) return ;
char * start = str ;
char * end = start + strlen ( str ) ;
while ( - - end > = start )
{
if ( ! isspace ( * end ) ) break ;
}
* ( + + end ) = ' \0 ' ;
while ( isspace ( * start ) ) start + + ;
if ( start ! = str ) memmove ( str , start , end - start + 1 ) ;
2018-06-26 02:11:18 +01:00
}