mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-23 02:36:41 +00:00
utils: implement utilsIsApplicationUpdatable().
Also removed legacy code that has already been reimplemented.
This commit is contained in:
parent
28cd0ce10f
commit
ba0c5d9e35
7 changed files with 97 additions and 571 deletions
|
@ -151,10 +151,20 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element);
|
||||||
/// Furthermore, if the full length for the generated path is >= FS_MAX_PATH, NULL will be returned.
|
/// Furthermore, if the full length for the generated path is >= FS_MAX_PATH, NULL will be returned.
|
||||||
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension);
|
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension);
|
||||||
|
|
||||||
|
/// Returns the current application updated state.
|
||||||
|
bool utilsGetApplicationUpdatedState(void);
|
||||||
|
|
||||||
|
/// Sets the application updated state to true, which makes utilsCloseResources() replace the application NRO.
|
||||||
|
void utilsSetApplicationUpdatedState(void);
|
||||||
|
|
||||||
/// Parses the provided GitHub release JSON data buffer.
|
/// Parses the provided GitHub release JSON data buffer.
|
||||||
/// The data from the output buffer must be freed using utilsFreeGitHubReleaseJsonData().
|
/// The data from the output buffer must be freed using utilsFreeGitHubReleaseJsonData().
|
||||||
bool utilsParseGitHubReleaseJsonData(const char *json_buf, size_t json_buf_size, UtilsGitHubReleaseJsonData *out);
|
bool utilsParseGitHubReleaseJsonData(const char *json_buf, size_t json_buf_size, UtilsGitHubReleaseJsonData *out);
|
||||||
|
|
||||||
|
/// Parses the provided version string and compares it to the application version. Returns true if the application can be updated.
|
||||||
|
/// If both versions are equal, the provided commit hash is compared to our commit hash - if they're different, true will be returned.
|
||||||
|
bool utilsIsApplicationUpdatable(const char *version, const char *commit_hash);
|
||||||
|
|
||||||
/// Frees previously allocated data from a UtilsGitHubReleaseJsonData element.
|
/// Frees previously allocated data from a UtilsGitHubReleaseJsonData element.
|
||||||
NX_INLINE void utilsFreeGitHubReleaseJsonData(UtilsGitHubReleaseJsonData *data)
|
NX_INLINE void utilsFreeGitHubReleaseJsonData(UtilsGitHubReleaseJsonData *data)
|
||||||
{
|
{
|
||||||
|
@ -163,13 +173,6 @@ NX_INLINE void utilsFreeGitHubReleaseJsonData(UtilsGitHubReleaseJsonData *data)
|
||||||
memset(data, 0, sizeof(UtilsGitHubReleaseJsonData));
|
memset(data, 0, sizeof(UtilsGitHubReleaseJsonData));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current application updated state.
|
|
||||||
bool utilsGetApplicationUpdatedState(void);
|
|
||||||
|
|
||||||
/// Sets the application updated state to true, which makes utilsCloseResources() replace the application NRO.
|
|
||||||
/// Use carefully.
|
|
||||||
void utilsSetApplicationUpdatedState(void);
|
|
||||||
|
|
||||||
/// Simple wrapper to sleep the current thread for a specific number of full seconds.
|
/// Simple wrapper to sleep the current thread for a specific number of full seconds.
|
||||||
NX_INLINE void utilsSleep(u64 seconds)
|
NX_INLINE void utilsSleep(u64 seconds)
|
||||||
{
|
{
|
||||||
|
|
546
legacy/util.c
546
legacy/util.c
|
@ -1840,119 +1840,6 @@ void gameCardDumpNSWDBCheck(u32 crc)
|
||||||
if (!found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "No match found in NSWDB.COM XML database! This could either be a bad dump or an undumped gamecard.");
|
if (!found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "No match found in NSWDB.COM XML database! This could either be a bad dump or an undumped gamecard.");
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result networkInit()
|
|
||||||
{
|
|
||||||
if (initNet) return 0;
|
|
||||||
|
|
||||||
Result result = socketInitializeDefault();
|
|
||||||
if (R_SUCCEEDED(result))
|
|
||||||
{
|
|
||||||
curl_global_init(CURL_GLOBAL_ALL);
|
|
||||||
initNet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void networkExit()
|
|
||||||
{
|
|
||||||
if (!initNet) return;
|
|
||||||
|
|
||||||
curl_global_cleanup();
|
|
||||||
socketExit();
|
|
||||||
initNet = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static 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 = malloc(result_sz);
|
|
||||||
if (!result_buf) return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool need_realloc = false;
|
|
||||||
|
|
||||||
while((result_written + bsz) > result_sz)
|
|
||||||
{
|
|
||||||
result_sz <<= 1;
|
|
||||||
need_realloc = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (need_realloc)
|
|
||||||
{
|
|
||||||
char *new_buf = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool performCurlRequest(CURL *curl, const char *url, FILE *filePtr, bool forceHttps, bool verbose)
|
|
||||||
{
|
|
||||||
if (!curl || !url || !strlen(url))
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to perform CURL request!", __func__);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, HTTP_USER_AGENT);
|
|
||||||
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);
|
|
||||||
if (forceHttps) curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
|
|
||||||
|
|
||||||
if (filePtr)
|
|
||||||
{
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCurlFile);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, filePtr);
|
|
||||||
} else {
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCurlBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
CURLcode res;
|
|
||||||
long http_code = 0;
|
|
||||||
double size = 0.0;
|
|
||||||
bool success = false;
|
|
||||||
|
|
||||||
res = curl_easy_perform(curl);
|
|
||||||
|
|
||||||
result_sz = result_written = 0;
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (verbose) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Successfully downloaded %.0lf bytes!", size);
|
|
||||||
success = true;
|
|
||||||
} else {
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CURL request failed for \"%s\" endpoint!\nHTTP status code: %ld", __func__, url, http_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void noIntroDumpCheck(bool isDigital, u32 crc)
|
void noIntroDumpCheck(bool isDigital, u32 crc)
|
||||||
{
|
{
|
||||||
Result result;
|
Result result;
|
||||||
|
@ -2004,436 +1891,3 @@ out:
|
||||||
|
|
||||||
if (R_SUCCEEDED(result)) networkExit();
|
if (R_SUCCEEDED(result)) networkExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateNSWDBXml()
|
|
||||||
{
|
|
||||||
Result result;
|
|
||||||
CURL *curl = NULL;
|
|
||||||
bool success = false;
|
|
||||||
FILE *nswdbXml = NULL;
|
|
||||||
|
|
||||||
result = networkInit();
|
|
||||||
if (R_FAILED(result))
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize socket! (%08X)", __func__, result);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
char xmlPath[256] = {'\0'};
|
|
||||||
snprintf(xmlPath, MAX_CHARACTERS(xmlPath), "%s.tmp", NSWDB_XML_PATH);
|
|
||||||
|
|
||||||
curl = curl_easy_init();
|
|
||||||
if (!curl)
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize CURL context!", __func__);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
nswdbXml = fopen(xmlPath, "wb");
|
|
||||||
if (!nswdbXml)
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open \"%s\" in write mode!", __func__, NSWDB_XML_URL);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Downloading XML database from \"%s\", please wait...", NSWDB_XML_URL);
|
|
||||||
breaks++;
|
|
||||||
|
|
||||||
appletModeOperationWarning();
|
|
||||||
uiRefreshDisplay();
|
|
||||||
breaks++;
|
|
||||||
|
|
||||||
changeHomeButtonBlockStatus(true);
|
|
||||||
|
|
||||||
success = performCurlRequest(curl, NSWDB_XML_URL, nswdbXml, false, true);
|
|
||||||
|
|
||||||
changeHomeButtonBlockStatus(false);
|
|
||||||
|
|
||||||
out:
|
|
||||||
if (nswdbXml) fclose(nswdbXml);
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
remove(NSWDB_XML_PATH);
|
|
||||||
rename(xmlPath, NSWDB_XML_PATH);
|
|
||||||
} else {
|
|
||||||
remove(xmlPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curl) curl_easy_cleanup(curl);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(result)) networkExit();
|
|
||||||
|
|
||||||
breaks += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int versionNumCmp(char *ver1, char *ver2)
|
|
||||||
{
|
|
||||||
int i, curPart, res;
|
|
||||||
char *token = NULL;
|
|
||||||
|
|
||||||
// Define a struct for comparison purposes
|
|
||||||
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));
|
|
||||||
|
|
||||||
// Create copies of the version strings to avoid modifications by strtok()
|
|
||||||
char ver1tok[64] = {'\0'};
|
|
||||||
snprintf(ver1tok, 63, ver1);
|
|
||||||
|
|
||||||
char ver2tok[64] = {'\0'};
|
|
||||||
snprintf(ver2tok, 63, ver2);
|
|
||||||
|
|
||||||
// Parse version string 1
|
|
||||||
i = 0;
|
|
||||||
token = strtok(ver1tok, ".");
|
|
||||||
while(token != NULL && i < 3)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
token = strtok(NULL, ".");
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse version string 2
|
|
||||||
i = 0;
|
|
||||||
token = strtok(ver2tok, ".");
|
|
||||||
while(token != NULL && i < 3)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
token = strtok(NULL, ".");
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct json_object *retrieveJsonObjMemberByNameAndType(struct json_object *jobj, char *memberName, json_type memberType)
|
|
||||||
{
|
|
||||||
if (!jobj || !memberName || !strlen(memberName))
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve member by name and type from JSON object!", __func__);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *memberObj = NULL;
|
|
||||||
json_type memberObjType;
|
|
||||||
|
|
||||||
if (!json_object_object_get_ex(jobj, memberName, &memberObj))
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve member \"%s\" from JSON object!", __func__, memberName);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
memberObjType = json_object_get_type(memberObj);
|
|
||||||
if (memberObjType != memberType)
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid type for member \"%s\" in JSON object! (got \"%s\", expected \"%s\")", __func__, memberName, json_type_to_name(memberObjType), json_type_to_name(memberType));
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return memberObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *retrieveJsonObjStrMemberContentsByName(struct json_object *jobj, char *memberName)
|
|
||||||
{
|
|
||||||
if (!jobj || !memberName || !strlen(memberName))
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve string member contents by name from JSON object!", __func__);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *memberObj = retrieveJsonObjMemberByNameAndType(jobj, memberName, json_type_string);
|
|
||||||
if (!memberObj) return NULL;
|
|
||||||
|
|
||||||
const char *memberObjStr = json_object_get_string(memberObj);
|
|
||||||
if (!memberObjStr || !strlen(memberObjStr))
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: string member \"%s\" from JSON object is empty!", __func__, memberName);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return memberObjStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct json_object *retrieveJsonObjArrayMemberByName(struct json_object *jobj, char *memberName, size_t *outputArrayLength)
|
|
||||||
{
|
|
||||||
if (!jobj || !memberName || !strlen(memberName) || !outputArrayLength)
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve array member by name from JSON object!", __func__);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *memberObj = retrieveJsonObjMemberByNameAndType(jobj, memberName, json_type_array);
|
|
||||||
if (memberObj) *outputArrayLength = json_object_array_length(memberObj);
|
|
||||||
|
|
||||||
return memberObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct json_object *retrieveJsonObjArrayElementByIndex(struct json_object *jobj, size_t idx)
|
|
||||||
{
|
|
||||||
if (!jobj || json_object_get_type(jobj) != json_type_array)
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve element by index from JSON array object!", __func__);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct json_object *memberObjArrayElement = json_object_array_get_idx(jobj, idx);
|
|
||||||
if (!memberObjArrayElement) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve element at index %lu from JSON array object!", __func__, idx);
|
|
||||||
|
|
||||||
return memberObjArrayElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool updateApplication()
|
|
||||||
{
|
|
||||||
if (envIsNso())
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to update application. Not running as a NRO.", __func__);
|
|
||||||
breaks += 2;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result result;
|
|
||||||
CURL *curl = NULL;
|
|
||||||
FILE *nxDumpToolNro = NULL;
|
|
||||||
|
|
||||||
char releaseTag[32] = {'\0'};
|
|
||||||
bool success = false;
|
|
||||||
|
|
||||||
size_t i, assetsCnt = 0;
|
|
||||||
struct json_object *jobj = NULL, *assets = NULL;
|
|
||||||
const char *releaseNameObjStr = NULL, *dlUrlObjStr = NULL;
|
|
||||||
|
|
||||||
char nroPath[NAME_BUF_LEN] = {'\0'};
|
|
||||||
snprintf(nroPath, MAX_CHARACTERS(nroPath), "%s.tmp", (appLaunchPath ? appLaunchPath : NRO_PATH));
|
|
||||||
|
|
||||||
result = networkInit();
|
|
||||||
if (R_FAILED(result))
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize socket! (%08X)", __func__, result);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
curl = curl_easy_init();
|
|
||||||
if (!curl)
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize CURL context!", __func__);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Requesting latest release information from \"%s\"...", GITHUB_API_URL);
|
|
||||||
breaks++;
|
|
||||||
|
|
||||||
uiRefreshDisplay();
|
|
||||||
|
|
||||||
if (!performCurlRequest(curl, GITHUB_API_URL, NULL, true, false)) goto out;
|
|
||||||
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Parsing response JSON data...");
|
|
||||||
breaks++;
|
|
||||||
|
|
||||||
uiRefreshDisplay();
|
|
||||||
|
|
||||||
jobj = json_tokener_parse(result_buf);
|
|
||||||
if (!jobj)
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to parse JSON response!", __func__);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseNameObjStr = retrieveJsonObjStrMemberContentsByName(jobj, GITHUB_API_JSON_RELEASE_NAME);
|
|
||||||
if (!releaseNameObjStr) goto out;
|
|
||||||
|
|
||||||
snprintf(releaseTag, MAX_CHARACTERS(releaseTag), releaseNameObjStr);
|
|
||||||
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Latest release: %s.", releaseTag);
|
|
||||||
breaks++;
|
|
||||||
|
|
||||||
uiRefreshDisplay();
|
|
||||||
|
|
||||||
// Remove the first character from the release name if it's v/V/r/R
|
|
||||||
if (releaseTag[0] == 'v' || releaseTag[0] == 'V' || releaseTag[0] == 'r' || releaseTag[0] == 'R')
|
|
||||||
{
|
|
||||||
u32 releaseTagLen = strlen(releaseTag);
|
|
||||||
memmove(releaseTag, releaseTag + 1, releaseTagLen - 1);
|
|
||||||
releaseTag[releaseTagLen - 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare versions
|
|
||||||
if (versionNumCmp(releaseTag, APP_VERSION) <= 0)
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "You already have the latest version!");
|
|
||||||
breaks += 2;
|
|
||||||
|
|
||||||
// Ask the user if they want to perform a forced update
|
|
||||||
int cur_breaks = breaks;
|
|
||||||
|
|
||||||
if (yesNoPrompt("Do you want to perform a forced update?"))
|
|
||||||
{
|
|
||||||
// Remove the prompt from the screen
|
|
||||||
breaks = cur_breaks;
|
|
||||||
uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB);
|
|
||||||
uiRefreshDisplay();
|
|
||||||
} else {
|
|
||||||
breaks -= 2;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assets = retrieveJsonObjArrayMemberByName(jobj, GITHUB_API_JSON_ASSETS, &assetsCnt);
|
|
||||||
if (!assets) goto out;
|
|
||||||
|
|
||||||
// Cycle through the assets to find the right download URL
|
|
||||||
for(i = 0; i < assetsCnt; i++)
|
|
||||||
{
|
|
||||||
struct json_object *assetElement = retrieveJsonObjArrayElementByIndex(assets, i);
|
|
||||||
if (!assetElement) break;
|
|
||||||
|
|
||||||
const char *assetName = retrieveJsonObjStrMemberContentsByName(assetElement, GITHUB_API_JSON_ASSETS_NAME);
|
|
||||||
if (!assetName) break;
|
|
||||||
|
|
||||||
if (!strncmp(assetName, NRO_NAME, strlen(assetName)))
|
|
||||||
{
|
|
||||||
// Found it
|
|
||||||
dlUrlObjStr = retrieveJsonObjStrMemberContentsByName(assetElement, GITHUB_API_JSON_ASSETS_DL_URL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dlUrlObjStr)
|
|
||||||
{
|
|
||||||
breaks++;
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to locate NRO download URL!", __func__);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Download URL: \"%s\".", dlUrlObjStr);
|
|
||||||
breaks++;
|
|
||||||
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Please wait...");
|
|
||||||
breaks++;
|
|
||||||
|
|
||||||
appletModeOperationWarning();
|
|
||||||
uiRefreshDisplay();
|
|
||||||
breaks++;
|
|
||||||
|
|
||||||
changeHomeButtonBlockStatus(true);
|
|
||||||
|
|
||||||
nxDumpToolNro = fopen(nroPath, "wb");
|
|
||||||
if (!nxDumpToolNro)
|
|
||||||
{
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open \"%s\" in write mode!", __func__, nroPath);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_easy_reset(curl);
|
|
||||||
|
|
||||||
success = performCurlRequest(curl, dlUrlObjStr, nxDumpToolNro, true, true);
|
|
||||||
if (!success) goto out;
|
|
||||||
|
|
||||||
breaks++;
|
|
||||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Please restart the application to reflect the changes.");
|
|
||||||
|
|
||||||
out:
|
|
||||||
if (nxDumpToolNro) fclose(nxDumpToolNro);
|
|
||||||
|
|
||||||
if (strlen(nroPath))
|
|
||||||
{
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
snprintf(strbuf, MAX_CHARACTERS(strbuf), nroPath);
|
|
||||||
nroPath[strlen(nroPath) - 4] = '\0';
|
|
||||||
|
|
||||||
remove(nroPath);
|
|
||||||
rename(strbuf, nroPath);
|
|
||||||
} else {
|
|
||||||
remove(nroPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jobj) json_object_put(jobj);
|
|
||||||
|
|
||||||
if (result_buf)
|
|
||||||
{
|
|
||||||
free(result_buf);
|
|
||||||
result_buf = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curl) curl_easy_cleanup(curl);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(result)) networkExit();
|
|
||||||
|
|
||||||
breaks += 2;
|
|
||||||
|
|
||||||
changeHomeButtonBlockStatus(false);
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
|
@ -290,8 +290,4 @@ void gameCardDumpNSWDBCheck(u32 crc);
|
||||||
|
|
||||||
void noIntroDumpCheck(bool isDigital, u32 crc);
|
void noIntroDumpCheck(bool isDigital, u32 crc);
|
||||||
|
|
||||||
void updateNSWDBXml();
|
|
||||||
|
|
||||||
bool updateApplication();
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
"is_nso": "The application is running as an NSO. Unable to update.",
|
"is_nso": "The application is running as an NSO. Unable to update.",
|
||||||
"already_updated": "The application has already been updated. Please reload.",
|
"already_updated": "The application has already been updated. Please reload.",
|
||||||
"github_json_failed": "Failed to download or parse GitHub release JSON!",
|
"github_json_failed": "Failed to download or parse GitHub release JSON!",
|
||||||
|
"up_to_date": "The application is up to date!",
|
||||||
"app_updated": "Application successfully updated! Please reload for the changes to take effect."
|
"app_updated": "Application successfully updated! Please reload for the changes to take effect."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,14 @@
|
||||||
/* Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits. */
|
/* Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits. */
|
||||||
#define NT_MAX_FILENAME_LENGTH 255
|
#define NT_MAX_FILENAME_LENGTH 255
|
||||||
|
|
||||||
|
/* Type definitions. */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 major;
|
||||||
|
u32 minor;
|
||||||
|
u32 micro;
|
||||||
|
} UtilsApplicationVersion;
|
||||||
|
|
||||||
/* Global variables. */
|
/* Global variables. */
|
||||||
|
|
||||||
static bool g_resourcesInit = false;
|
static bool g_resourcesInit = false;
|
||||||
|
@ -828,6 +836,18 @@ end:
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool utilsGetApplicationUpdatedState(void)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
SCOPED_LOCK(&g_resourcesMutex) ret = g_appUpdated;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void utilsSetApplicationUpdatedState(void)
|
||||||
|
{
|
||||||
|
SCOPED_LOCK(&g_resourcesMutex) g_appUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
bool utilsParseGitHubReleaseJsonData(const char *json_buf, size_t json_buf_size, UtilsGitHubReleaseJsonData *out)
|
bool utilsParseGitHubReleaseJsonData(const char *json_buf, size_t json_buf_size, UtilsGitHubReleaseJsonData *out)
|
||||||
{
|
{
|
||||||
if (!json_buf || !json_buf_size || !out)
|
if (!json_buf || !json_buf_size || !out)
|
||||||
|
@ -906,16 +926,46 @@ end:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool utilsGetApplicationUpdatedState(void)
|
bool utilsIsApplicationUpdatable(const char *version, const char *commit_hash)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
if (!version || !*version || *version != 'v' || !commit_hash || !*commit_hash)
|
||||||
SCOPED_LOCK(&g_resourcesMutex) ret = g_appUpdated;
|
{
|
||||||
return ret;
|
LOG_MSG("Invalid parameters!");
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void utilsSetApplicationUpdatedState(void)
|
bool ret = false;
|
||||||
{
|
UtilsApplicationVersion cur_version = { VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO }, new_version = {0};
|
||||||
SCOPED_LOCK(&g_resourcesMutex) g_appUpdated = true;
|
|
||||||
|
/* Parse version string. */
|
||||||
|
sscanf(version, "v%u.%u.%u", &(new_version.major), &(new_version.minor), &(new_version.micro));
|
||||||
|
|
||||||
|
/* Compare versions. */
|
||||||
|
if (cur_version.major == new_version.major)
|
||||||
|
{
|
||||||
|
if (cur_version.minor == new_version.minor)
|
||||||
|
{
|
||||||
|
if (cur_version.micro == new_version.micro)
|
||||||
|
{
|
||||||
|
/* Versions are equal. Let's compare the commit hashes and return true if they're different. */
|
||||||
|
ret = (strncasecmp(commit_hash, GIT_COMMIT, 7) != 0);
|
||||||
|
} else
|
||||||
|
if (cur_version.micro < new_version.micro)
|
||||||
|
{
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
if (cur_version.minor < new_version.minor)
|
||||||
|
{
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
if (cur_version.major < new_version.major)
|
||||||
|
{
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _utilsGetLaunchPath(int program_argc, const char **program_argv)
|
static void _utilsGetLaunchPath(int program_argc, const char **program_argv)
|
||||||
|
|
|
@ -213,6 +213,9 @@ namespace nxdt::views
|
||||||
/* Return immediately if the JSON task hasn't finished. */
|
/* Return immediately if the JSON task hasn't finished. */
|
||||||
if (!this->json_task.isFinished()) return;
|
if (!this->json_task.isFinished()) return;
|
||||||
|
|
||||||
|
bool pop_view = false;
|
||||||
|
std::string notification = "";
|
||||||
|
|
||||||
/* Retrieve task result. */
|
/* Retrieve task result. */
|
||||||
nxdt::tasks::DownloadDataResult json_task_result = this->json_task.get();
|
nxdt::tasks::DownloadDataResult json_task_result = this->json_task.get();
|
||||||
this->json_buf = json_task_result.first;
|
this->json_buf = json_task_result.first;
|
||||||
|
@ -220,17 +223,37 @@ namespace nxdt::views
|
||||||
|
|
||||||
/* Parse downloaded JSON object. */
|
/* Parse downloaded JSON object. */
|
||||||
if (utilsParseGitHubReleaseJsonData(this->json_buf, this->json_buf_size, &(this->json_data)))
|
if (utilsParseGitHubReleaseJsonData(this->json_buf, this->json_buf_size, &(this->json_data)))
|
||||||
|
{
|
||||||
|
/* Check if the application can be updated. */
|
||||||
|
if (utilsIsApplicationUpdatable(this->json_data.version, this->json_data.commit_hash))
|
||||||
{
|
{
|
||||||
/* Display changelog. */
|
/* Display changelog. */
|
||||||
this->DisplayChangelog();
|
this->DisplayChangelog();
|
||||||
} else {
|
} else {
|
||||||
/* Log downloaded data if we failed to parse it. */
|
/* Update flag. */
|
||||||
|
pop_view = true;
|
||||||
|
|
||||||
|
/* Set notification string. */
|
||||||
|
notification = "options_tab/notifications/up_to_date"_i18n;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Log downloaded data. */
|
||||||
LOG_DATA(this->json_buf, this->json_buf_size, "Failed to parse GitHub release JSON. Downloaded data:");
|
LOG_DATA(this->json_buf, this->json_buf_size, "Failed to parse GitHub release JSON. Downloaded data:");
|
||||||
|
|
||||||
/* Display notification. */
|
/* Update flag. */
|
||||||
brls::Application::notify("options_tab/notifications/github_json_failed"_i18n);
|
pop_view = true;
|
||||||
|
|
||||||
/* Pop view */
|
/* Set notification string. */
|
||||||
|
notification = "options_tab/notifications/github_json_failed"_i18n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pop view (if needed). */
|
||||||
|
if (pop_view)
|
||||||
|
{
|
||||||
|
/* Display notification. */
|
||||||
|
brls::Application::notify(notification);
|
||||||
|
|
||||||
|
/* Pop view. */
|
||||||
this->onCancel();
|
this->onCancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
1
todo.txt
1
todo.txt
|
@ -24,7 +24,6 @@ todo:
|
||||||
usb: improve cancel mechanism
|
usb: improve cancel mechanism
|
||||||
|
|
||||||
others: fix shrinking bar
|
others: fix shrinking bar
|
||||||
others: add version / commit hash check before updating app
|
|
||||||
others: check todo with grep
|
others: check todo with grep
|
||||||
others: dump verification via nswdb / no-intro
|
others: dump verification via nswdb / no-intro
|
||||||
others: fatfs browser for emmc partitions
|
others: fatfs browser for emmc partitions
|
||||||
|
|
Loading…
Reference in a new issue