Skip to content

Commit

Permalink
title: add CSV generator with title record info
Browse files Browse the repository at this point in the history
Uses a dynamically allocated buffer to hold the CSV data, which can then 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: 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 773901b
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 773901b

Please sign in to comment.