Skip to content

Commit

Permalink
Uses a dynamically allocated buffer to hold the CSV data, which can t…
Browse files Browse the repository at this point in the history
…hen be written to an output file.

Changes include:

* nxdt_utils: add utilsEscapeCharacters().

* title: add titleGenerateTitleRecordsCsv().
* title: move core logic from titleGetUserApplicationData() into _titleGetUserApplicationData(). titleGetUserApplicationData() now only takes care of duplicating the retrieved data.
* title: update titleGetContentInfosByGameCardContentMetaContext() to make it write the forged NcmContentInfo entry for the Meta NCA at the end of the returned buffer.
* title: fix a bug in titleRefreshGameCardTitleInfo() that prevented a title info's metadata pointer to be updated after retrieving application metadata via ns.
* title: update titleGenerateGameCardApplicationMetadataArray() to add a logfile warning if an application entry doesn't have a valid application metadata pointer.
* title: make _titleGenerateGameCardFileName() a bit easier to read.
  • Loading branch information
DarkMatterCore committed Aug 24, 2024
1 parent 9c7a57e commit 7ed5de9
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 90 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ host/nxdumptool
*.exe
*.7z
*.code-workspace
*.csv

# TODO: remove this after the PoC builds are no longer needed.
main.cpp
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Currently planned changes for this branch include:
* Plaintext [gamecard CardInfo area](https://switchbrew.org/wiki/XCI#CardHeaderEncryptedData) dumps. :white_check_mark:
* [Gamecard InitialData](https://switchbrew.org/wiki/XCI#InitialData) area dumps. :white_check_mark:
* [Gamecard CardIdSet](https://switchbrew.org/wiki/Filesystem_services#GameCardIdSet) dumps. :white_check_mark:
* [Gamecard Hash FS partition](https://switchbrew.org/wiki/XCI#PartitionFs) dumps (in both extracted and raw inage forms). :white_check_mark:
* [Gamecard Hash FS partition](https://switchbrew.org/wiki/XCI#PartitionFs) dumps (in both extracted and raw image forms). :white_check_mark:
* [Lotus ASIC firmware (LAFW) blob](https://switchbrew.org/wiki/Lotus3#User_firmware) dumping from RAM. :white_check_mark:
* Properly detect if an inserted gamecard requires a LAFW update. :white_check_mark:
* Nintendo Submission Package (NSP) dumps for both digital and gamecard-based titles.
Expand Down
38 changes: 38 additions & 0 deletions code_templates/nxdt_rw_poc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,7 @@ int main(int argc, char *argv[])
consolePrint("______________________________\n\n");
if (cur_menu->parent) consolePrint("press b to go back\n");
if (g_umsDeviceCount) consolePrint("press x to safely remove all ums devices\n");
if ((cur_menu->id == MenuId_UserTitles || cur_menu->id == MenuId_SystemTitles) && element_count) consolePrint("press y to dump csv with title info to the sd card\n");
consolePrint("use the sticks to scroll faster\n");
consolePrint("press + to exit\n");
consolePrint("______________________________\n\n");
Expand Down Expand Up @@ -1513,6 +1514,43 @@ int main(int argc, char *argv[])
for(u32 i = 0; i < g_umsDeviceCount; i++) umsUnmountDevice(&(g_umsDevices[i]));
updateStorageList();
} else
if ((btn_down & HidNpadButton_Y) && (cur_menu->id == MenuId_UserTitles || cur_menu->id == MenuId_SystemTitles) && element_count)
{
consoleClear();
consolePrint("dumping title info to csv, please wait...\n");
consoleRefresh();

sprintf(path, DEVOPTAB_SDMC_DEVICE "/" OUTDIR "/%s_title_records.csv", cur_menu->id == MenuId_UserTitles ? "user" : "system");

char *csv_buf = NULL;
size_t csv_buf_size = 0;
u32 proc_title_cnt = 0;

csv_buf = titleGenerateTitleRecordsCsv(&csv_buf_size, &proc_title_cnt, cur_menu->id == MenuId_SystemTitles, false);
if (csv_buf)
{
utilsCreateDirectoryTree(path, false);

FILE *csv_fd = fopen(path, "wb");
if (csv_fd)
{
fwrite(UTF8_BOM, 1, strlen(UTF8_BOM), csv_fd);
fwrite(csv_buf, 1, csv_buf_size, csv_fd);
fclose(csv_fd);

consolePrint("title info dumped to \"%s\". %u title record(s) processed.\n", path, proc_title_cnt);
} else {
consolePrint("failed to open \"%s\" for writing\n", path);
}

free(csv_buf);
} else {
consolePrint("failed to generate csv data\n");
}

consolePrint("press any button to go back");
utilsWaitForButtonPress(0);
} else
if (((btn_down & (HidNpadButton_L)) || (btn_held & HidNpadButton_ZL)) && (cur_menu->id == MenuId_NSP || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_Nca) && title_info->previous)
{
title_info = title_info->previous;
Expand Down
8 changes: 7 additions & 1 deletion include/core/nxdt_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,17 @@ void utilsJoinThread(Thread *thread);
__attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...);

/// Replaces illegal filesystem characters in the provided NULL-terminated UTF-8 string with underscores ('_').
/// If 'ascii_only' is set to true, all codepoints outside of the [0x20,0x7F) range will also be replaced with underscores.
/// If 'ascii_only' is set to true, all codepoints outside of the [0x20,0x7E] range will also be replaced with underscores.
/// Replacements are performed on a per-codepoint basis, which means the string size in bytes can be reduced by this function.
/// Furthermore, if multiple, consecutive illegal characters are found, they will all get replaced by a single underscore.
void utilsReplaceIllegalCharacters(char *str, bool ascii_only);

/// Returns a pointer to a dynamically allocated copy of the provided string with all required characters escaped using another specific character.
/// 'chars_to_escape' must represent a NULL-terminated character string with all characters that need to be escaped.
/// Furthermore, 'escape_char' must represent an ASCII character within the [0x20,0x7E] range, and it must also not be part of 'chars_to_escape'.
/// Returns NULL if an error occurs.
char *utilsEscapeCharacters(const char *str, const char *chars_to_escape, const char escape_char);

/// Trims whitespace characters from the provided string.
void utilsTrimString(char *str);

Expand Down
9 changes: 9 additions & 0 deletions include/core/title.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ char *titleGenerateFileName(TitleInfo *title_info, u8 naming_convention, u8 ille
/// A valid gamecard must be inserted, and title info must have been loaded from it accordingly.
char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replace_type);

/// Returns a pointer to a dynamically allocated buffer that holds a CSV representation of all available user/system title records, depending on the 'is_system' argument.
/// 'out_csv_size' must be a valid pointer. It is used to store the size of the allocated buffer.
/// 'out_proc_title_cnt' may optionally be provided. If available, it will be used to store the number of processed title records.
/// If 'is_system' is false and 'use_gamecard' is true, gamecard title records will be appended to the output buffer.
/// Both 'is_system' and 'use_gamecard' may not be set to true at the same time.
/// Furthermore, if 'is_system' is set to false and orphan titles are available, their records will get appended to the output buffer.
/// Returns NULL if an error occurs.
char *titleGenerateTitleRecordsCsv(size_t *out_csv_size, u32 *out_proc_title_cnt, bool is_system, bool use_gamecard);

/// Returns a pointer to a string holding a user-friendly name for the provided NcmStorageId value. Returns NULL if the provided value is invalid.
const char *titleGetNcmStorageIdName(u8 storage_id);

Expand Down
70 changes: 70 additions & 0 deletions source/core/nxdt_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,76 @@ void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
*ptr2 = '\0';
}

char *utilsEscapeCharacters(const char *str, const char *chars_to_escape, const char escape_char)
{
size_t str_size = 0, chars_to_escape_size = 0;

if (!str || !(str_size = strlen(str)) || !chars_to_escape || !(chars_to_escape_size = strlen(chars_to_escape)) || \
escape_char < 0x20 || escape_char >= 0x7F || memchr(chars_to_escape, (int)escape_char, chars_to_escape_size))
{
LOG_MSG_ERROR("Invalid parameters!");
return NULL;
}

ssize_t units = 0;
u32 code = 0, escape_cnt = 0;
const u8 *ptr = (const u8*)str;
size_t cur_pos = 0, escaped_str_size = 0;
char *ret = NULL;

/* Determine the number of character we need to escape. */
while(cur_pos < str_size)
{
units = decode_utf8(&code, ptr);
if (units < 0) break;

if (memchr(chars_to_escape, (int)code, chars_to_escape_size)) escape_cnt++;

ptr += units;
cur_pos += (size_t)units;
}

/* Short-circuit: check if we don't have to escape anything. */
/* If so, we'll just duplicate the provided string and call it a day. */
if (!escape_cnt)
{
ret = strdup(str);
goto end;
}

/* Calculate escaped string size. */
escaped_str_size = (str_size + escape_cnt);

/* Allocate memory for the output string. */
ret = calloc(sizeof(char), escaped_str_size + 1);
if (!ret)
{
LOG_MSG_ERROR("Failed to allocate memory for the output string! (0x%lX).", escaped_str_size + 1);
goto end;
}

/* Reset current position. */
ptr = (const u8*)str;
cur_pos = 0;

/* Copy characters and deal with the ones that need to be escaped. */
while(cur_pos < escaped_str_size)
{
units = decode_utf8(&code, ptr);
if (units < 0) break;

if (memchr(chars_to_escape, (int)code, chars_to_escape_size)) ret[cur_pos++] = escape_char;

for(ssize_t i = 0; i < units; i++) ret[cur_pos + (size_t)i] = ptr[i];

ptr += units;
cur_pos += (size_t)units;
}

end:
return ret;
}

void utilsTrimString(char *str)
{
size_t strsize = 0;
Expand Down
Loading

0 comments on commit 7ed5de9

Please sign in to comment.