/*---------------------------------------------------------------------------* Project: Horizon File: FileTransfer.cpp Copyright 2009 Nintendo. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Rev$ *---------------------------------------------------------------------------*/ #include #include #include #include #include #include #include "Aes_define.h" #include "FileTransfer.h" #include "CommonLogger.h" #include "common_Types.h" #include "FileName.h" namespace common { namespace { u64 s_TotalFileSize; u64 s_FinishedFileSize = 0; u64 s_Progress = 0; } bool VerifyMac(nn::fs::FileInputStream* sdFile, nn::fs::FileStream* nandFile, s64 sdFileSize, s64 nandFileSize, const wchar_t* nandPath, void* buf, size_t bufSize); bool ConfirmFile(nn::fs::FileInputStream* from_file, nn::fs::FileStream* to_file, s64 sdFileSize, s64 nandFileSize, void* buf, size_t bufSize, const wchar_t* sdPath, const wchar_t* tmpPath, const wchar_t* truePath); void AddPkcsPadding(u8* paddingSize, void* buf, size_t bufSize, s32* readSize); void AddPathNameAndUpdateContext(nn::fs::FileOutputStream* file, const wchar_t *str, s64 fileSize, nn::crypto::Sha256Context* context); const char* GetCharStr(const wchar_t* path) { static char filename[nn::fs::MAX_FILE_PATH_LENGTH]; std::memset(filename, 0, sizeof(filename)); std::wcstombs(filename, path, sizeof(filename)); filename[sizeof(filename) - 1] = '\0'; return filename; } nn::Result CalculateFileNum(std::wstring currentDirectory, u32& fileNum, s64& fileSize) { nn::fs::FileInputStream fis; nn::fs::Directory dir; nn::Result result; std::vector entryList; //カレントディレクトリのエントリ一覧を格納 std::vector::iterator entryIndex; result = dir.TryInitialize(currentDirectory.c_str()); if(result.IsSuccess()) { nn::fs::DirectoryEntry entry; s32 numEntry; for(;;) { result = dir.TryRead(&numEntry, &entry, 1); if(result.IsFailure()) { dir.Finalize(); return result; } if(numEntry == 0) { // カレントディレクトリを閉じる dir.Finalize(); // カレントディレクトリの子を開く for(entryIndex = entryList.begin(); entryIndex != entryList.end(); entryIndex++) { if(entryIndex->attributes.isDirectory) { CalculateFileNum(currentDirectory + std::wstring(entryIndex->entryName) + std::wstring(L"/"), fileNum, fileSize); } } return nn::ResultSuccess(); } entryList.push_back(entry); fileNum++; fileSize+= entry.entrySize; } } else { NN_LOG("failed initialize directory\n"); dir.Finalize(); return result; } } bool ExistsInList(ImportDataList* fileList, const wchar_t* path, bool isDirectory) { std::wstring sdPath(path); if(isDirectory) { sdPath += std::wstring(L"/"); } char str[nn::fs::MAX_FILE_PATH_LENGTH]; std::strlcpy(str, GetCharStr(sdPath.c_str()), sizeof(str)); bool returnValue = false; for(ImportDataList::iterator it = fileList->begin(); it != fileList->end(); it++) { if(std::strcmp(str, it->fileName.c_str()) == 0) { returnValue = true; NN_LOG("%s exists in FileList.txt\n", str); break; } } return returnValue; } void ExportTwlSaveDirectory(const wchar_t* dirPath, nn::fs::FileOutputStream* list, nn::crypto::Sha256Context* listContext) { NN_LOG("Create Directory %ls\n", dirPath); nn::Result result = nn::fs::TryCreateDirectory(dirPath); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); AddPathNameAndUpdateContext(list, dirPath, -1, listContext); } bool ExportTwlSaveFile(const wchar_t* from_path, const wchar_t* to_path, void* buf, const size_t bufSize, nn::fs::FileOutputStream* list, nn::crypto::Sha256Context* listContext) { NN_LOG("from = %ls\n", from_path); NN_LOG("to = %ls\n", to_path); nn::Result result; bool ret_value = true; // ファイル作成 nn::fs::FileInputStream from_file; nn::fs::FileStream to_file; s64 filesize; s32 readsize; s32 writesize; NN_LOG("Copy File %ls\n", from_path); // 読み込み対象ファイル開く result = from_file.TryInitialize(from_path); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } else { // 読み込み対象ファイルのサイズ取得 result = from_file.TryGetSize(&filesize); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } else { AddPathNameAndUpdateContext(list, to_path, filesize, listContext); nn::crypto::SwAesCtrContext swAesCtrContext; swAesCtrContext.Initialize(iv, common::key, sizeof(key)); size_t totalReadSize = 0; nn::crypto::Sha256Context context; context.Initialize(); // ファイルサイズをヘッダに書いておく // 書き込み対象ファイル作成 result = nn::fs::TryCreateFile(to_path, filesize); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); result = to_file.TryInitialize(to_path, nn::fs::OPEN_MODE_READ | nn::fs::OPEN_MODE_WRITE | nn::fs::OPEN_MODE_CREATE); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } // フルパスをハッシュに含める context.Update(from_path, std::wcslen(from_path) * sizeof(wchar_t)); BackupDataHeader header; BackupDataHeader enc; std::memset(&header, 0, sizeof(header)); std::memset(&enc, 0, sizeof(enc)); header.size = filesize; result = swAesCtrContext.Encrypt(&enc, &header, sizeof(header)); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); context.Update(&enc, sizeof(enc)); s32 writeSize; result = to_file.TryWrite(&writeSize, &enc, sizeof(enc), false); if (result.IsFailure()) { ret_value = false; return ret_value; } while (1) { // バッファの後半半分を暗号・復号用に使う result = from_file.TryRead(&readsize, buf, bufSize / 2); totalReadSize += readsize; if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; return ret_value; } else { if (readsize == 0) { NN_LOG("Add CMAC %ls\n", from_path); // SHA256を計算してCMACを付加する bit8 sha256Hash[nn::crypto::Sha256Context::HASH_SIZE]; context.GetHash(sha256Hash); bit8 cmac[nn::crypto::AES_CMAC_MAC_SIZE]; result = nn::crypto::CalculateAesCmacSw(cmac, sha256Hash, nn::crypto::Sha256Context::HASH_SIZE, common::cmacKey); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); result = to_file.TryWrite(&writesize, cmac, sizeof(cmac)); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } result = to_file.TryFlush(); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } break; } else { NN_LOG("EncryptSize = %d\n", readsize); u8 paddingSize = 0; AddPkcsPadding(&paddingSize, reinterpret_cast (buf), bufSize / 2, &readsize); // 暗号化後SHA256を計算しつつ書き込み result = swAesCtrContext.Encrypt(reinterpret_cast (buf) + bufSize / 2, buf, readsize); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); context.Update(reinterpret_cast (buf) + bufSize / 2, readsize); result = to_file.TryWrite(&writesize, reinterpret_cast (buf) + bufSize / 2, readsize, false); // 事前計算したファイルサイズに一致させるためパディング分減算 readsize -= paddingSize; s_FinishedFileSize += readsize; s_Progress = s_FinishedFileSize * 100 / s_TotalFileSize; NN_LOG("finish = %lld, total = %lld, progress = %lld\n", s_FinishedFileSize, s_TotalFileSize, s_Progress); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } } } } to_file.Finalize(); } } from_file.Finalize(); return ret_value; } bool CopyDirectory(ImportDataList* fileList, const wchar_t * from_path, const wchar_t * to_path, void* buf, const size_t bufSize, bool encode, nn::fs::FileOutputStream* list, nn::crypto::Sha256Context* listContext) { nn::fs::Directory from_dir; nn::fs::DirectoryEntry entry; s32 numread = 0; std::wostringstream target_from; std::wostringstream target_to; bool ret_value = true; nn::Result result = from_dir.TryInitialize(from_path); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); return false; } while (1) { result = from_dir.TryRead(&numread, &entry, 1); if (result.IsFailure() || numread != 1) { break; } if (std::wcscmp(entry.entryName, L".") == 0 || std::wcscmp(entry.entryName, L"..") == 0) { continue; } target_from.str(L""); target_from.clear(std::stringstream::goodbit); target_from << from_path << entry.entryName; target_to.str(L""); target_to.clear(std::stringstream::goodbit); target_to << to_path << entry.entryName; // NAND書き込みの場合はリストに存在するかチェックする if (!encode) { if (!ExistsInList(fileList, target_from.str().c_str(), entry.attributes.isDirectory)) { NN_LOG("============No such file %ls in FileList.txt. Skip=============\n", target_from.str().c_str()); continue; } } // ディレクトリの場合 if (entry.attributes.isDirectory) { // ディレクトリ作成 NN_LOG("Create Directory %ls\n", target_to.str().c_str()); result = nn::fs::TryCreateDirectory(target_to.str().c_str()); if (result.IsSuccess() || result.IsFailure() && result <= nn::fs::ResultAlreadyExists()) { target_from << L"/"; target_to << L"/"; if(encode) { AddPathNameAndUpdateContext(list, target_to.str().c_str(), -1, listContext); } // 再帰処理 if (!CopyDirectory(fileList, target_from.str().c_str(), target_to.str().c_str(), buf, bufSize, encode, list, listContext)) { ret_value = false; } } else { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } } // ファイルの場合 else { std::wostringstream target_tmp; target_tmp.str(L""); target_tmp.clear(std::stringstream::goodbit); if(!encode) { target_tmp << to_path << L"_" << entry.entryName; } else { target_tmp << target_to.str(); } // ファイル作成 nn::fs::FileInputStream from_file; nn::fs::FileStream to_file; s64 filesize; s64 fileSizeWithoutHeaderAndFooter; s32 readsize; s32 writesize; NN_LOG("Copy File %ls\n", target_from.str().c_str()); // 読み込み対象ファイル開く result = from_file.TryInitialize(target_from.str().c_str()); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } else { // 読み込み対象ファイルのサイズ取得 result = from_file.TryGetSize(&filesize); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } else { if(encode) { AddPathNameAndUpdateContext(list, target_to.str().c_str(), filesize, listContext); } nn::crypto::SwAesCtrContext swAesCtrContext; swAesCtrContext.Initialize(iv, common::key, sizeof(key)); size_t totalReadSize = 0; nn::crypto::Sha256Context context; context.Initialize(); // ファイルサイズをヘッダに書いておく if (encode) { // 書き込み対象ファイル作成 result = nn::fs::TryCreateFile(target_tmp.str().c_str(), filesize); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); result = to_file.TryInitialize(target_tmp.str().c_str(), nn::fs::OPEN_MODE_READ | nn::fs::OPEN_MODE_WRITE | nn::fs::OPEN_MODE_CREATE); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } // フルパスをハッシュに含める context.Update(target_from.str().c_str(), target_from.str().size() * sizeof(wchar_t)); BackupDataHeader header; BackupDataHeader enc; std::memset(&header, 0, sizeof(header)); std::memset(&enc, 0, sizeof(enc)); header.size = filesize; result = swAesCtrContext.Encrypt(&enc, &header, sizeof(header)); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); context.Update(&enc, sizeof(enc)); s32 writeSize; result = to_file.TryWrite(&writeSize, &enc, sizeof(enc), false); if (result.IsFailure()) { ret_value = false; continue; } } else { // ヘッダを読む // ハッシュの計算は終わっているので復号化のみ BackupDataHeader header; BackupDataHeader dec; std::memset(&header, 0, sizeof(header)); std::memset(&dec, 0, sizeof(dec)); s32 readSize; result = from_file.TryRead(&readSize, &header, sizeof(header)); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; continue; } swAesCtrContext.Decrypt(&dec, &header, sizeof(header)); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); fileSizeWithoutHeaderAndFooter = dec.size; // 書き込み対象ファイル作成 result = nn::fs::TryCreateFile(target_tmp.str().c_str(), fileSizeWithoutHeaderAndFooter); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); result = to_file.TryInitialize(target_tmp.str().c_str(), nn::fs::OPEN_MODE_READ | nn::fs::OPEN_MODE_WRITE | nn::fs::OPEN_MODE_CREATE); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } } while (1) { // バッファの後半半分を暗号・復号用に使う result = from_file.TryRead(&readsize, buf, bufSize / 2); totalReadSize += readsize; if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; break; } else { if (readsize == 0) { if (encode) { NN_LOG("Add CMAC %ls\n", target_from.str().c_str()); // SHA256を計算してCMACを付加する bit8 sha256Hash[nn::crypto::Sha256Context::HASH_SIZE]; context.GetHash(sha256Hash); bit8 cmac[nn::crypto::AES_CMAC_MAC_SIZE]; result = nn::crypto::CalculateAesCmacSw(cmac, sha256Hash, nn::crypto::Sha256Context::HASH_SIZE, common::cmacKey); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); result = to_file.TryWrite(&writesize, cmac, sizeof(cmac)); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } } result = to_file.TryFlush(); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } // 復号済みなら検証する if (!encode) { if (!ConfirmFile(&from_file, &to_file, filesize, fileSizeWithoutHeaderAndFooter, buf, bufSize, target_from.str().c_str(), target_tmp.str().c_str(), target_to.str().c_str())) { ret_value = false; } } break; } else { if (encode) { NN_LOG("EncryptSize = %d\n", readsize); u8 paddingSize = 0; AddPkcsPadding(&paddingSize, reinterpret_cast(buf), bufSize / 2, &readsize); // 暗号化後SHA256を計算しつつ書き込み result = swAesCtrContext.Encrypt(reinterpret_cast (buf) + bufSize / 2, buf, readsize); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); context.Update(reinterpret_cast (buf) + bufSize / 2, readsize); result = to_file.TryWrite(&writesize, reinterpret_cast (buf) + bufSize / 2, readsize, false); // 事前計算したファイルサイズに一致させるためパディング分減算 readsize -= paddingSize; s_FinishedFileSize += readsize; s_Progress = s_FinishedFileSize * 100 / s_TotalFileSize; NN_LOG("finish = %lld, total = %lld, progress = %lld\n", s_FinishedFileSize, s_TotalFileSize, s_Progress); } else { // ハッシュ検証は通っているので復号化しつつ書き込み // パディング以降は書き込まないよう書き込みサイズを変更する NN_LOG("DecryptSize = %d\n", readsize); result = swAesCtrContext.Decrypt(reinterpret_cast (buf) + bufSize / 2, buf, readsize); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); // パディングまで読んだかどうか bool readDone = false; // パディングまで読んでいたら書き込みサイズを減らす if (fileSizeWithoutHeaderAndFooter < totalReadSize) { readsize -= totalReadSize - fileSizeWithoutHeaderAndFooter; readDone = true; } result = to_file.TryWrite(&writesize, reinterpret_cast (buf) + bufSize / 2, readsize, false); s_FinishedFileSize += readsize; s_Progress = s_FinishedFileSize * 100 / s_TotalFileSize; NN_LOG("finish = %lld, total = %lld, progress = %lld\n", s_FinishedFileSize, s_TotalFileSize, s_Progress); // 読みきったので次のファイルへ if (readDone) { result = to_file.TryFlush(); if (result.IsFailure()) { ret_value = false; } COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); if (!ConfirmFile(&from_file, &to_file, filesize, fileSizeWithoutHeaderAndFooter, buf, bufSize, target_from.str().c_str(), target_tmp.str().c_str(), target_to.str().c_str())) { s_FinishedFileSize -= readsize; ret_value = false; } break; } } if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } } } } to_file.Finalize(); } } from_file.Finalize(); } } from_dir.Finalize(); return ret_value; } u32 GetProgress() { return s_Progress; } void InitializeTransferProgress(u64 totalSize) { s_TotalFileSize = totalSize; s_FinishedFileSize = 0; } bool CalculateAndCompareCmac(nn::crypto::Sha256Context* context, bit8* sdCmac) { nn::Result result; bit8 sha256Hash[nn::crypto::Sha256Context::HASH_SIZE]; bit8 cmac[nn::crypto::AES_CMAC_MAC_SIZE]; context->GetHash(sha256Hash); context->Finalize(); result = nn::crypto::CalculateAesCmacSw(cmac, sha256Hash, sizeof(sha256Hash), common::cmacKey); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); return false; } return std::memcmp(cmac, sdCmac, sizeof(cmac)) == 0; } bool VerifyMac(nn::fs::FileInputStream* sdFile, nn::fs::FileStream* nandFile, s64 sdFileSize, s64 nandFileSize, const wchar_t* nandPath, void* buf, size_t bufSize) { nn::Result result; bit8 sdCmac[nn::crypto::AES_CMAC_MAC_SIZE]; // ハッシュが付加されていないとエラー if(sdFileSize < nn::crypto::AES_CMAC_MAC_SIZE) { return false; } s32 readSize; // ハッシュを取得する nn::crypto::Initialize(); result = sdFile->TrySetPosition(sdFileSize - nn::crypto::AES_CMAC_MAC_SIZE); if (result.IsSuccess()) { result = sdFile->TryRead(&readSize, sdCmac, sizeof(sdCmac)); if(result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); return false; } } else { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); return false; } sdFile->Finalize(); nandFile->SetPosition(0); // ハッシュを計算する nn::crypto::SwAesCtrContext swAesCtrContext; swAesCtrContext.Initialize(iv, common::key, sizeof(key)); nn::crypto::Sha256Context context; context.Initialize(); // NAND上のフルパスをハッシュに含めている context.Update(nandPath, std::wcslen(nandPath) * sizeof(wchar_t)); BackupDataHeader header; BackupDataHeader enc; std::memset(&header, 0, sizeof(header)); std::memset(&enc, 0, sizeof(enc)); header.size = nandFileSize; result = swAesCtrContext.Encrypt(&enc, &header, sizeof(header)); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); context.Update(&enc, sizeof(enc)); bool ret_value = false; size_t totalReadSize = 0; while (1) { result = nandFile->TryRead(&readSize, buf, bufSize / 2); totalReadSize += readSize; if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; break; } else { if (readSize == 0) { ret_value = CalculateAndCompareCmac(&context, sdCmac); break; } else { u8 paddingSize = 0; AddPkcsPadding(&paddingSize, reinterpret_cast(buf), bufSize / 2, &readSize); result = swAesCtrContext.Encrypt(reinterpret_cast(buf) + bufSize / 2, buf, readSize); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); context.Update(reinterpret_cast(buf) + bufSize / 2 , readSize); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } } } } nn::crypto::Finalize(); nandFile->Finalize(); return ret_value; } bool ConfirmFile(nn::fs::FileInputStream* from_file, nn::fs::FileStream* to_file, s64 sdFileSize, s64 nandFileSize, void* buf, size_t bufSize, const wchar_t* sdPath, const wchar_t* tmpPath, const wchar_t* truePath) { nn::Result result; bool ret_value = true; NN_LOG("Verify CMAC %ls\n", sdPath); if (!VerifyMac(from_file, to_file, sdFileSize, nandFileSize, truePath, buf, bufSize)) { // 検証に失敗したので削除する s_FinishedFileSize -= nandFileSize; COMMON_LOGGER("**********Verification Failed %s, Delete**********\n", GetCharStr(sdPath)); result = nn::fs::TryDeleteFile(tmpPath); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); ret_value = false; } else { NN_LOG("Verification Success %s, Rename\n", GetCharStr(sdPath)); // 削除する nn::fs::TryDeleteFile(truePath); // リネームする result = nn::fs::TryRenameFile(tmpPath, truePath); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); if (result.IsFailure()) { COMMON_LOGGER_RESULT_IF_FAILED(result); s_FinishedFileSize -= nandFileSize; ret_value = false; } } return ret_value; } //! @brief 入力データの末尾16バイトをPKCS5で必要バイト数パディングする //! @param[out] paddingSize パディングしたバイト数 //! @param[in] buf 入力データの入ったバッファ //! @param[in] bufSize バッファサイズ //! @param[inout] readSize バッファに読み込んだバイト数。書き込み時に参照するためパディングしたら増加させる void AddPkcsPadding(u8* paddingSize, void* buf, size_t bufSize, s32* readSize) { if (*readSize < bufSize) { if ((*readSize % AES_BLOCK_SIZE) != 0) { *paddingSize = AES_BLOCK_SIZE - *readSize % AES_BLOCK_SIZE; std::memset(reinterpret_cast(buf) + *readSize, *paddingSize, *paddingSize); *readSize += *paddingSize; } } } //! @brief パスにnimのセーブデータディレクトリが含まれているかどうかを返します //! @param[in] str パス //! @return パスにnimのセーブデータディレクトリが含まれているか bool ContainsNimSaveDataDir(const wchar_t* str) { return std::wcsstr(str, common::NIM_SAVEDATA_DIRECTORY_NAME) != NULL; } //! @brief ファイルに文字列とサイズをカンマ区切り、改行付きで追加します //! @param[in] file 文字列を出力したいファイル //! @param[in] str 入力文字列 //! @param[in] fileSize サイズ //! @param[in] context SHA-256計算用コンテキスト void AddPathNameAndUpdateContext(nn::fs::FileOutputStream* file, const wchar_t *str, s64 fileSize, nn::crypto::Sha256Context* context) { nn::Result result; s32 writeSize; if(ContainsNimSaveDataDir(str)) { return; } std::string output(GetCharStr(str)); result = file->TryWrite(&writeSize, output.c_str(), output.size(), true); context->Update(output.c_str(), output.size()); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); char comma = ','; result = file->TryWrite(&writeSize, &comma, sizeof(comma), true); context->Update(&comma, sizeof(comma)); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); char sizeStr[10]; std::memset(sizeStr, 0, sizeof(sizeStr)); s32 sizeStrSize = std::snprintf(sizeStr, sizeof(sizeStr), "%lld", fileSize); result = file->TryWrite(&writeSize, sizeStr, sizeStrSize, true); context->Update(sizeStr, sizeStrSize); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); char newLine = '\n'; result = file->TryWrite(&writeSize, &newLine, sizeof(newLine), true); context->Update(&newLine, sizeof(newLine)); COMMON_LOGGER_RESULT_IF_FAILED_WITH_LINE(result); } }