#include "RPXLoading.h" #include "globals.h" #include "utils/FileReader.h" #include "utils/StringTools.h" #include "utils/ini.h" #include "utils/logger.h" #include #include #include #include #include #include #include #include #include #include std::mutex fileReaderListMutex; std::vector openFileReaders; void RPXLoadingCleanUp() { const std::lock_guard lock(fileReaderListMutex); for (auto &reader : openFileReaders) { delete reader; } openFileReaders.clear(); } /* * Patch the meta xml for the home menu */ DECL_FUNCTION(int32_t, HBM_NN_ACP_ACPGetTitleMetaXmlByDevice, uint32_t titleid_upper, uint32_t titleid_lower, ACPMetaXml *metaxml, uint32_t device) { if (gReplacementInfo.rpxReplacementInfo.isRPXReplaced) { memset(&metaxml->longname_ja, 0, 0x338C - 0x38C); // clear all names snprintf(metaxml->longname_en, sizeof(metaxml->longname_en), "%s", gReplacementInfo.rpxReplacementInfo.metaInformation.longname); snprintf(metaxml->shortname_en, sizeof(metaxml->shortname_en), "%s", gReplacementInfo.rpxReplacementInfo.metaInformation.longname); snprintf(metaxml->publisher_en, sizeof(metaxml->publisher_en), "%s", gReplacementInfo.rpxReplacementInfo.metaInformation.longname); // Disbale the emanual metaxml->e_manual = 0; return 0; } int result = real_HBM_NN_ACP_ACPGetTitleMetaXmlByDevice(titleid_upper, titleid_lower, metaxml, device); return result; } DECL_FUNCTION(int, RPX_FSOpenFile, FSClient *client, FSCmdBlock *block, char *path, const char *mode, int *handle, int error) { const char *iconTex = "iconTex.tga"; std::string_view pathView = path; if (gReplacementInfo.rpxReplacementInfo.isRPXReplaced && pathView.ends_with(iconTex)) { const std::lock_guard lock(fileReaderListMutex); auto *reader = new (std::nothrow) FileReader(reinterpret_cast(gReplacementInfo.rpxReplacementInfo.iconCache), ICON_SIZE); if (!reader) { DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory for the FileReader"); return FS_STATUS_FATAL_ERROR; } openFileReaders.push_back(reader); *handle = reinterpret_cast(reader); return FS_STATUS_OK; } int result = real_RPX_FSOpenFile(client, block, path, mode, handle, error); return result; } DECL_FUNCTION(FSStatus, RPX_FSReadFile, FSClient *client, FSCmdBlock *block, uint8_t *buffer, uint32_t size, uint32_t count, FSFileHandle handle, uint32_t unk1, uint32_t flags) { if (gReplacementInfo.rpxReplacementInfo.isRPXReplaced) { const std::lock_guard lock(fileReaderListMutex); for (auto &reader : openFileReaders) { if ((uint32_t) reader == (uint32_t) handle) { return (FSStatus) (reader->read(buffer, size * count) / size); } } } return real_RPX_FSReadFile(client, block, buffer, size, count, handle, unk1, flags); } DECL_FUNCTION(FSStatus, RPX_FSCloseFile, FSClient *client, FSCmdBlock *block, FSFileHandle handle, uint32_t flags) { if (gReplacementInfo.rpxReplacementInfo.isRPXReplaced) { const std::lock_guard lock(fileReaderListMutex); bool found = false; int index = 0; FileReader *reader = nullptr; for (auto &cur : openFileReaders) { if ((uint32_t) cur == (uint32_t) handle) { found = true; reader = cur; break; } index++; } if (found) { openFileReaders.erase(openFileReaders.begin() + index); delete reader; return FS_STATUS_OK; } } return real_RPX_FSCloseFile(client, block, handle, flags); } DECL_FUNCTION(void, Loader_ReportWarn) { } function_replacement_data_t rpx_utils_function_replacements[] = { REPLACE_FUNCTION_VIA_ADDRESS(Loader_ReportWarn, 0x32002f74, 0x01002f74), REPLACE_FUNCTION_VIA_ADDRESS_FOR_PROCESS(HBM_NN_ACP_ACPGetTitleMetaXmlByDevice, 0x2E36CE44, 0x0E36CE44, FP_TARGET_PROCESS_HOME_MENU), REPLACE_FUNCTION_FOR_PROCESS(RPX_FSOpenFile, LIBRARY_COREINIT, FSOpenFile, FP_TARGET_PROCESS_HOME_MENU), REPLACE_FUNCTION_FOR_PROCESS(RPX_FSReadFile, LIBRARY_COREINIT, FSReadFile, FP_TARGET_PROCESS_HOME_MENU), REPLACE_FUNCTION_FOR_PROCESS(RPX_FSCloseFile, LIBRARY_COREINIT, FSCloseFile, FP_TARGET_PROCESS_HOME_MENU), }; uint32_t rpx_utils_function_replacements_size = sizeof(rpx_utils_function_replacements) / sizeof(function_replacement_data_t); static int parseINIhandler(void *user, const char *section, const char *name, const char *value) { auto *fInfo = (MetaInformation *) user; #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 if (MATCH("menu", "longname")) { fInfo->longname[0] = '\0'; strncat(fInfo->longname, value, sizeof(fInfo->longname) - 1); } else if (MATCH("menu", "shortname")) { fInfo->shortname[0] = '\0'; strncat(fInfo->shortname, value, sizeof(fInfo->shortname) - 1); } else if (MATCH("menu", "author")) { fInfo->author[0] = '\0'; strncat(fInfo->author, value, sizeof(fInfo->author) - 1); } else { return 0; /* unknown section/name, error */ } return 1; } bool RL_LoadFromSDOnNextLaunch(const char *bundle_path) { LOAD_REQUEST request; memset(&request, 0, sizeof(request)); request.command = 0xFC; // IPC_CUSTOM_LOAD_CUSTOM_RPX; request.target = 0; // LOAD_FILE_TARGET_SD_CARD request.filesize = 0; // unknown filesize request.fileoffset = 0; // WUHBRPXInfo fileInfo; bool metaLoaded = false; std::string completePath = std::string("/vol/external01/") + bundle_path; bool isBundle = false; auto rpxInfo = WUHBUtils_GetRPXInfo(completePath.c_str(), BundleSource_FileDescriptor_CafeOS, &fileInfo); if (rpxInfo == WUHB_UTILS_RESULT_SUCCESS) { isBundle = true; request.filesize = ((uint32_t *) &fileInfo.length)[1]; request.fileoffset = ((uint32_t *) &fileInfo.offset)[1]; auto res = -1; if (WUHBUtils_MountBundle("rcc", completePath.c_str(), BundleSource_FileDescriptor_CafeOS, &res) == WUHB_UTILS_RESULT_SUCCESS && res == 0) { uint8_t *buffer; uint32_t bufferSize; if (WUHBUtils_ReadWholeFile("rcc:/meta/meta.ini", &buffer, &bufferSize) == WUHB_UTILS_RESULT_SUCCESS) { buffer[bufferSize - 1] = '\0'; if (ini_parse_string((const char *) buffer, parseINIhandler, &gReplacementInfo.rpxReplacementInfo.metaInformation) < 0) { DEBUG_FUNCTION_LINE_ERR("Failed to load and parse meta.ini"); } else { metaLoaded = true; } free(buffer); } else { DEBUG_FUNCTION_LINE_ERR("Failed to read whole file meta.ini"); } buffer = nullptr; bufferSize = 0; if (WUHBUtils_ReadWholeFile("rcc:/meta/iconTex.tga", &buffer, &bufferSize) == WUHB_UTILS_RESULT_SUCCESS) { uint32_t cpySize = ICON_SIZE; if (bufferSize < cpySize) { cpySize = bufferSize; memset(gReplacementInfo.rpxReplacementInfo.iconCache, 0, ICON_SIZE); } memcpy(gReplacementInfo.rpxReplacementInfo.iconCache, buffer, cpySize); free(buffer); } else { DEBUG_FUNCTION_LINE_ERR("Failed to read iconTex.tga"); } auto outRes = 0; if (WUHBUtils_UnmountBundle("rcc", &outRes) != WUHB_UTILS_RESULT_SUCCESS || outRes != WUHB_UTILS_RESULT_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to unmount bundle"); } } } else { if (!gReplacementInfo.contentReplacementInfo.bundleMountInformation.isMounted) { memset(gReplacementInfo.rpxReplacementInfo.iconCache, 0, sizeof(gReplacementInfo.rpxReplacementInfo.iconCache)); } } if (!metaLoaded) { gReplacementInfo.rpxReplacementInfo.metaInformation.author[0] = '\0'; gReplacementInfo.rpxReplacementInfo.metaInformation.shortname[0] = '\0'; gReplacementInfo.rpxReplacementInfo.metaInformation.longname[0] = '\0'; strncat(gReplacementInfo.rpxReplacementInfo.metaInformation.author, bundle_path, sizeof(gReplacementInfo.rpxReplacementInfo.metaInformation.author) - 1); strncat(gReplacementInfo.rpxReplacementInfo.metaInformation.shortname, bundle_path, sizeof(gReplacementInfo.rpxReplacementInfo.metaInformation.shortname) - 1); strncat(gReplacementInfo.rpxReplacementInfo.metaInformation.longname, bundle_path, sizeof(gReplacementInfo.rpxReplacementInfo.metaInformation.longname) - 1); } request.path[0] = '\0'; strncat(request.path, bundle_path, sizeof(request.path) - 1); OSMemoryBarrier(); int success = false; int mcpFd = IOS_Open("/dev/mcp", (IOSOpenMode) 0); if (mcpFd >= 0) { int out = 0; IOS_Ioctl(mcpFd, 100, &request, sizeof(request), &out, sizeof(out)); if (out > 0) { success = true; } IOS_Close(mcpFd); } OSMemoryBarrier(); if (!success) { gReplacementInfo.rpxReplacementInfo.willRPXBeReplaced = false; DEBUG_FUNCTION_LINE_ERR("Failed to load %s on next restart", request.path); return false; } else { gReplacementInfo.rpxReplacementInfo.willRPXBeReplaced = true; } DEBUG_FUNCTION_LINE("Launch %s on next restart [size: %08X offset: %08X]", request.path, request.filesize, request.fileoffset); gReplacementInfo.contentReplacementInfo.bundleMountInformation.toMountPath[0] = '\0'; if (isBundle) { DEBUG_FUNCTION_LINE_VERBOSE("Loaded file is a .wuhb bundle"); strncat(gReplacementInfo.contentReplacementInfo.bundleMountInformation.toMountPath, completePath.c_str(), sizeof(gReplacementInfo.contentReplacementInfo.bundleMountInformation.toMountPath) - 1); } else { DEBUG_FUNCTION_LINE_VERBOSE("Loaded file is no bundle"); gReplacementInfo.rpxReplacementInfo.willRPXBeReplaced = true; if (gReplacementInfo.contentReplacementInfo.bundleMountInformation.isMounted) { // keep the old /vol/content mounted, this way you can reload just the rpx via wiiload strncat(gReplacementInfo.contentReplacementInfo.bundleMountInformation.toMountPath, gReplacementInfo.contentReplacementInfo.bundleMountInformation.mountedPath, sizeof(gReplacementInfo.contentReplacementInfo.bundleMountInformation.toMountPath) - 2); } } OSMemoryBarrier(); return true; } std::mutex mutex; bool RL_DisableContentRedirection() { std::lock_guard lock(mutex); if (contentLayerHandle != 0) { auto res = ContentRedirection_SetActive(contentLayerHandle, false); if (res != CONTENT_REDIRECTION_RESULT_SUCCESS) { return false; } } if (saveLayerHandle != 0) { auto res = ContentRedirection_SetActive(saveLayerHandle, false); if (res != CONTENT_REDIRECTION_RESULT_SUCCESS) { ContentRedirection_SetActive(contentLayerHandle, true); return false; } } return true; } bool RL_EnableContentRedirection() { std::lock_guard lock(mutex); if (contentLayerHandle != 0) { auto res = ContentRedirection_SetActive(contentLayerHandle, true); if (res != CONTENT_REDIRECTION_RESULT_SUCCESS) { return false; } } if (saveLayerHandle != 0) { auto res = ContentRedirection_SetActive(saveLayerHandle, true); if (res != CONTENT_REDIRECTION_RESULT_SUCCESS) { ContentRedirection_SetActive(contentLayerHandle, false); return false; } } return true; } bool RL_UnmountCurrentRunningBundle() { std::lock_guard lock(mutex); if (gReplacementInfo.contentReplacementInfo.bundleMountInformation.isMounted == false) { return true; } int outRes = -1; if (ContentRedirection_RemoveDevice(WUHB_ROMFS_NAME, &outRes) == CONTENT_REDIRECTION_RESULT_SUCCESS) { if (outRes < 0) { DEBUG_FUNCTION_LINE_ERR("RemoveDevice \"%s\" failed for ContentRedirection Module", WUHB_ROMFS_NAME); OSFatal("RL_UnmountCurrentRunningBundle: RemoveDevice \"" WUHB_ROMFS_NAME "\" failed for ContentRedirection Module"); } } else { DEBUG_FUNCTION_LINE_ERR("ContentRedirection_RemoveDevice failed"); OSFatal("RL_UnmountCurrentRunningBundle: ContentRedirection_RemoveDevice failed"); } bool res = true; if (contentLayerHandle != 0) { if (ContentRedirection_RemoveFSLayer(contentLayerHandle) != CONTENT_REDIRECTION_RESULT_SUCCESS) { res = false; } contentLayerHandle = 0; } if (saveLayerHandle != 0) { if (ContentRedirection_RemoveFSLayer(saveLayerHandle) != CONTENT_REDIRECTION_RESULT_SUCCESS) { res = false; } saveLayerHandle = 0; } romfsUnmount(WUHB_ROMFS_NAME); gReplacementInfo.contentReplacementInfo.bundleMountInformation.isMounted = false; OSMemoryBarrier(); return res; } RPXLoaderVersion RL_GetVersion() { return RPX_LOADER_MODULE_VERSION; } WUMS_EXPORT_FUNCTION(RL_GetVersion); WUMS_EXPORT_FUNCTION(RL_LoadFromSDOnNextLaunch); WUMS_EXPORT_FUNCTION(RL_EnableContentRedirection); WUMS_EXPORT_FUNCTION(RL_DisableContentRedirection); WUMS_EXPORT_FUNCTION(RL_UnmountCurrentRunningBundle);