diff --git a/include/fs/archive_base.hpp b/include/fs/archive_base.hpp index c782f6a6..2843be68 100644 --- a/include/fs/archive_base.hpp +++ b/include/fs/archive_base.hpp @@ -246,6 +246,11 @@ public: Helpers::panic("Unimplemented Format for %s archive", name().c_str()); } + virtual HorizonResult renameFile(const FSPath& oldPath, const FSPath& newPath) { + Helpers::panic("Unimplemented RenameFile for %s archive", name().c_str()); + return Result::Success; + } + // Read size bytes from a file starting at offset "offset" into a certain buffer in memory // Returns the number of bytes read, or nullopt if the read failed virtual std::optional readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) = 0; diff --git a/include/services/cfg.hpp b/include/services/cfg.hpp index 28327574..7241a409 100644 --- a/include/services/cfg.hpp +++ b/include/services/cfg.hpp @@ -23,6 +23,7 @@ class CFGService { void genUniqueConsoleHash(u32 messagePointer); void secureInfoGetByte101(u32 messagePointer); void secureInfoGetRegion(u32 messagePointer); + void translateCountryInfo(u32 messagePointer); void getConfigInfo(u32 output, u32 blockID, u32 size, u32 permissionMask); diff --git a/include/services/fs.hpp b/include/services/fs.hpp index 435dc5a6..2fe3ba5d 100644 --- a/include/services/fs.hpp +++ b/include/services/fs.hpp @@ -71,6 +71,7 @@ class FSService { void openDirectory(u32 messagePointer); void openFile(u32 messagePointer); void openFileDirectly(u32 messagePointer); + void renameFile(u32 messagePointer); void setArchivePriority(u32 messagePointer); void setPriority(u32 messagePointer); void setThisSaveDataSecureValue(u32 messagePointer); diff --git a/src/core/fs/archive_ncch.cpp b/src/core/fs/archive_ncch.cpp index d7dd1f57..5935a0c5 100644 --- a/src/core/fs/archive_ncch.cpp +++ b/src/core/fs/archive_ncch.cpp @@ -74,12 +74,13 @@ std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size const u32 lowProgramID = *(u32*)&archivePath[0]; const u32 highProgramID = *(u32*)&archivePath[4]; - // High Title ID of the archive (from Citra). https://3dbrew.org/wiki/Title_list. + // High Title ID of the archive: https://3dbrew.org/wiki/Title_list. constexpr u32 sharedDataArchive = 0x0004009B; constexpr u32 systemDataArchive = 0x000400DB; - // Low ID (taken from Citra) + // Low IDs constexpr u32 miiData = 0x00010202; + constexpr u32 tlsRootCertificates = 0x00010602; constexpr u32 regionManifest = 0x00010402; constexpr u32 badWordList = 0x00010302; constexpr u32 sharedFont = 0x00014002; @@ -88,6 +89,10 @@ std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size if (highProgramID == sharedDataArchive) { if (lowProgramID == miiData) fileData = std::vector(std::begin(MII_DATA), std::end(MII_DATA)); else if (lowProgramID == regionManifest) fileData = std::vector(std::begin(COUNTRY_LIST_DATA), std::end(COUNTRY_LIST_DATA)); + else if (lowProgramID == tlsRootCertificates) { + Helpers::warn("Read from Shared Data archive 00010602"); + return 0; + } else Helpers::panic("[NCCH archive] Read unimplemented NAND file. ID: %08X", lowProgramID); } else if (highProgramID == systemDataArchive && lowProgramID == badWordList) { fileData = std::vector(std::begin(BAD_WORD_LIST_DATA), std::end(BAD_WORD_LIST_DATA)); diff --git a/src/core/services/cfg.cpp b/src/core/services/cfg.cpp index 7cab91bd..0b5f6437 100644 --- a/src/core/services/cfg.cpp +++ b/src/core/services/cfg.cpp @@ -16,6 +16,7 @@ namespace CFGCommands { GenHashConsoleUnique = 0x00030040, GetRegionCanadaUSA = 0x00040000, GetSystemModel = 0x00050000, + TranslateCountryInfo = 0x00080080, GetCountryCodeID = 0x000A0040, GetLocalFriendCodeSeed = 0x04050000, @@ -34,6 +35,7 @@ void CFGService::handleSyncRequest(u32 messagePointer, CFGService::Type type) { case CFGCommands::GetSystemModel: getSystemModel(messagePointer); break; case CFGCommands::GenHashConsoleUnique: genUniqueConsoleHash(messagePointer); break; case CFGCommands::SecureInfoGetRegion: secureInfoGetRegion(messagePointer); break; + case CFGCommands::TranslateCountryInfo: translateCountryInfo(messagePointer); break; default: if (type == Type::S) { @@ -256,4 +258,38 @@ void CFGService::getLocalFriendCodeSeed(u32 messagePointer) { mem.write32(messagePointer, IPC::responseHeader(0x405, 3, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write64(messagePointer + 8, 0); +} + +// https://www.3dbrew.org/wiki/Cfg:TranslateCountryInfo +void CFGService::translateCountryInfo(u32 messagePointer) { + const u32 country = mem.read32(messagePointer + 4); + const u8 direction = mem.read8(messagePointer + 8); + log("CFG::TranslateCountryInfo (country = %d, direction = %d)\n", country, direction); + + // By default the translated code is the input + u32 result = country; + + if (direction == 0) { // Translate from version B to version A + switch (country) { + case 0x6E040000: result = 0x6E030000; break; + case 0x6E050000: result = 0x6E040000; break; + case 0x6E060000: result = 0x6E050000; break; + case 0x6E070000: result = 0x6E060000; break; + case 0x6E030000: result = 0x6E070000; break; + default: break; + } + } else if (direction == 1) { // Translate from version A to version B + switch (country) { + case 0x6E030000: result = 0x6E040000; break; + case 0x6E040000: result = 0x6E050000; break; + case 0x6E050000: result = 0x6E060000; break; + case 0x6E060000: result = 0x6E070000; break; + case 0x6E070000: result = 0x6E030000; break; + default: break; + } + } + + mem.write32(messagePointer, IPC::responseHeader(0x8, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write32(messagePointer + 8, result); } \ No newline at end of file diff --git a/src/core/services/fs.cpp b/src/core/services/fs.cpp index 1c243a57..0bcbab37 100644 --- a/src/core/services/fs.cpp +++ b/src/core/services/fs.cpp @@ -16,6 +16,7 @@ namespace FSCommands { OpenFile = 0x080201C2, OpenFileDirectly = 0x08030204, DeleteFile = 0x08040142, + RenameFile = 0x08050244, DeleteDirectory = 0x08060142, DeleteDirectoryRecursively = 0x08070142, CreateFile = 0x08080202, @@ -187,6 +188,7 @@ void FSService::handleSyncRequest(u32 messagePointer) { case FSCommands::OpenDirectory: openDirectory(messagePointer); break; case FSCommands::OpenFile: [[likely]] openFile(messagePointer); break; case FSCommands::OpenFileDirectly: [[likely]] openFileDirectly(messagePointer); break; + case FSCommands::RenameFile: renameFile(messagePointer); break; case FSCommands::SetArchivePriority: setArchivePriority(messagePointer); break; case FSCommands::SetPriority: setPriority(messagePointer); break; case FSCommands::SetThisSaveDataSecureValue: setThisSaveDataSecureValue(messagePointer); break; @@ -716,4 +718,46 @@ void FSService::cardSlotIsInserted(u32 messagePointer) { mem.write32(messagePointer, IPC::responseHeader(0x821, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write8(messagePointer + 8, cardInserted ? 1 : 0); +} + +void FSService::renameFile(u32 messagePointer) { + log("FS::RenameFile\n"); + + mem.write32(messagePointer, IPC::responseHeader(0x805, 1, 0)); + + const Handle sourceArchiveHandle = mem.read64(messagePointer + 8); + const Handle destArchiveHandle = mem.read64(messagePointer + 24); + + // Read path info + const u32 sourcePathType = mem.read32(messagePointer + 16); + const u32 sourcePathSize = mem.read32(messagePointer + 20); + const u32 sourcePathPointer = mem.read32(messagePointer + 44); + const FSPath sourcePath = readPath(sourcePathType, sourcePathPointer, sourcePathSize); + + const u32 destPathType = mem.read32(messagePointer + 32); + const u32 destPathSize = mem.read32(messagePointer + 36); + const u32 destPathPointer = mem.read32(messagePointer + 52); + const FSPath destPath = readPath(destPathType, destPathPointer, destPathSize); + + const auto sourceArchiveObject = kernel.getObject(sourceArchiveHandle, KernelObjectType::Archive); + const auto destArchiveObject = kernel.getObject(destArchiveHandle, KernelObjectType::Archive); + + if (sourceArchiveObject == nullptr || destArchiveObject == nullptr) { + Helpers::panic("FS::RenameFile: One of the archive handles is invalid"); + } + + const auto sourceArchive = sourceArchiveObject->getData(); + const auto destArchive = destArchiveObject->getData(); + if (!sourceArchive->isOpen || !destArchive->isOpen) { + Helpers::warn("FS::RenameFile: Not both archive sessions are open"); + } + + // This returns error 0xE0C046F8 according to 3DBrew + if (sourceArchive->archive->name() != destArchive->archive->name()) { + Helpers::panic("FS::RenameFile: Both archive handles should belong to the same archive"); + } + + // Everything is OK, let's do the rename. Both archives should match so we don't need the dest anymore + const HorizonResult res = sourceArchive->archive->renameFile(sourcePath, destPath); + mem.write32(messagePointer + 4, static_cast(res)); } \ No newline at end of file